diff mbox series

[v2,2/2] via-ide: Also emulate non 100% native mode

Message ID 2acb7e522055bb9ac45586c1792edc7615ef3ae6.1583714522.git.balaton@eik.bme.hu
State New
Headers show
Series Implement "non 100% native mode" in via-ide | expand

Commit Message

BALATON Zoltan March 9, 2020, 12:42 a.m. UTC
Some machines operate in "non 100% native mode" where interrupts are
fixed at legacy IDE interrupts and some guests expect this behaviour
without checking based on knowledge about hardware. Even Linux has
arch specific workarounds for this that are activated on such boards
so this needs to be emulated as well.

Signed-off-by: BALATON Zoltan <balaton@eik.bme.hu>
---
v2: Don't use PCI_INTERRUPT_LINE in via_ide_set_irq()

 hw/ide/via.c            | 57 +++++++++++++++++++++++++++++++++++------
 hw/mips/mips_fulong2e.c |  2 +-
 include/hw/ide.h        |  3 ++-
 3 files changed, 52 insertions(+), 10 deletions(-)

Comments

Mark Cave-Ayland March 9, 2020, 4:46 p.m. UTC | #1
On 09/03/2020 00:42, BALATON Zoltan wrote:

> Some machines operate in "non 100% native mode" where interrupts are
> fixed at legacy IDE interrupts and some guests expect this behaviour
> without checking based on knowledge about hardware. Even Linux has
> arch specific workarounds for this that are activated on such boards
> so this needs to be emulated as well.
> 
> Signed-off-by: BALATON Zoltan <balaton@eik.bme.hu>
> ---
> v2: Don't use PCI_INTERRUPT_LINE in via_ide_set_irq()
> 
>  hw/ide/via.c            | 57 +++++++++++++++++++++++++++++++++++------
>  hw/mips/mips_fulong2e.c |  2 +-
>  include/hw/ide.h        |  3 ++-
>  3 files changed, 52 insertions(+), 10 deletions(-)
> 
> diff --git a/hw/ide/via.c b/hw/ide/via.c
> index 096de8dba0..44ecc2af29 100644
> --- a/hw/ide/via.c
> +++ b/hw/ide/via.c
> @@ -1,9 +1,10 @@
>  /*
> - * QEMU IDE Emulation: PCI VIA82C686B support.
> + * QEMU VIA southbridge IDE emulation (VT82C686B, VT8231)
>   *
>   * Copyright (c) 2003 Fabrice Bellard
>   * Copyright (c) 2006 Openedhand Ltd.
>   * Copyright (c) 2010 Huacai Chen <zltjiangshi@gmail.com>
> + * Copyright (c) 2019-2020 BALATON Zoltan
>   *
>   * Permission is hereby granted, free of charge, to any person obtaining a copy
>   * of this software and associated documentation files (the "Software"), to deal
> @@ -25,6 +26,8 @@
>   */
>  
>  #include "qemu/osdep.h"
> +#include "qemu/range.h"
> +#include "hw/qdev-properties.h"
>  #include "hw/pci/pci.h"
>  #include "migration/vmstate.h"
>  #include "qemu/module.h"
> @@ -111,14 +114,40 @@ static void via_ide_set_irq(void *opaque, int n, int level)
>      } else {
>          d->config[0x70 + n * 8] &= ~0x80;
>      }
> -
>      level = (d->config[0x70] & 0x80) || (d->config[0x78] & 0x80);
> -    n = pci_get_byte(d->config + PCI_INTERRUPT_LINE);
> -    if (n) {
> -        qemu_set_irq(isa_get_irq(NULL, n), level);
> +
> +    /*
> +     * Some machines operate in "non 100% native mode" where PCI_INTERRUPT_LINE
> +     * is not used but IDE always uses ISA IRQ 14 and 15 even in native mode.
> +     * Some guest drivers expect this, often without checking.
> +     */
> +    if (!(pci_get_byte(d->config + PCI_CLASS_PROG) & (n ? 4 : 1)) ||
> +        PCI_IDE(d)->flags & BIT(PCI_IDE_LEGACY_IRQ)) {
> +        qemu_set_irq(isa_get_irq(NULL, (n ? 15 : 14)), level);
> +    } else {
> +        qemu_set_irq(isa_get_irq(NULL, 14), level);
>      }
>  }

There's still the need to convert this to qdev gpio, but for now I'll review the
updated version of this patch (and provide an example once the rest of the patchset
is okay).

This looks much closer to what I was expecting with the fixed IRQs, and in fact
doesn't it make the feature bit requirement obsolete? The PCI_CLASS_PROG bit is now
the single source of truth as to whether native or legacy IRQs are used, and the code
routes the IRQ accordingly.

> +static uint32_t via_ide_config_read(PCIDevice *d, uint32_t address, int len)
> +{
> +    /*
> +     * The pegasos2 firmware writes to PCI_INTERRUPT_LINE but on real
> +     * hardware it's fixed at 14 and won't change. Some guests also expect
> +     * legacy interrupts, without reading PCI_INTERRUPT_LINE but Linux
> +     * depends on this to read 14. We set it to 14 in the reset method and
> +     * also set the wmask to 0 to emulate this but that turns out to be not
> +     * enough. QEMU resets the PCI bus after this device and
> +     * pci_do_device_reset() called from pci_device_reset() will zero
> +     * PCI_INTERRUPT_LINE so this config_read function is to counter that and
> +     * restore the correct value, otherwise this should not be needed.
> +     */
> +    if (range_covers_byte(address, len, PCI_INTERRUPT_LINE)) {
> +        pci_set_byte(d->config + PCI_INTERRUPT_LINE, 14);
> +    }
> +    return pci_default_read_config(d, address, len);
> +}

The comment here is interesting so I had a quick look at pci_do_device_reset() to see
what is happening there, and to me it seems that the real bug is
pci_do_device_reset() doesn't honour wmask for PCI_INTERRUPT_LINE. This is because
normally the BIOS would write this value long after the PCI bus has been physically
reset and since via-ide is the first device to hardwire a value here, this wouldn't
have been needed up until now.

Fortunately it seems that there is already precedent for this so does the following
diff work?

diff --git a/hw/pci/pci.c b/hw/pci/pci.c
index e1ed6677e1..4ae0e0e90f 100644
--- a/hw/pci/pci.c
+++ b/hw/pci/pci.c
@@ -302,8 +302,10 @@ static void pci_do_device_reset(PCIDevice *dev)
     pci_word_test_and_clear_mask(dev->config + PCI_STATUS,
                                  pci_get_word(dev->wmask + PCI_STATUS) |
                                  pci_get_word(dev->w1cmask + PCI_STATUS));
+    pci_word_test_and_clear_mask(dev->config + PCI_INTERRUPT_LINE,
+                                 pci_get_word(dev->wmask + PCI_INTERRUPT_LINE) |
+                                 pci_get_word(dev->w1cmask + PCI_INTERRUPT_LINE));
     dev->config[PCI_CACHE_LINE_SIZE] = 0x0;
-    dev->config[PCI_INTERRUPT_LINE] = 0x0;
     for (r = 0; r < PCI_NUM_REGIONS; ++r) {
         PCIIORegion *region = &dev->io_regions[r];
         if (!region->size) {

If this works, it will help simplify your patch even further.

>  static void via_ide_reset(DeviceState *dev)
>  {
>      PCIIDEState *d = PCI_IDE(dev);
> @@ -169,7 +198,8 @@ static void via_ide_realize(PCIDevice *dev, Error **errp)
>  
>      pci_config_set_prog_interface(pci_conf, 0x8f); /* native PCI ATA mode */
>      pci_set_long(pci_conf + PCI_CAPABILITY_LIST, 0x000000c0);
> -    dev->wmask[PCI_INTERRUPT_LINE] = 0xf;
> +    dev->wmask[PCI_CLASS_PROG] = 5;

Possibly a separate patch for PCI_CLASS_PROG change? Although with the latest version
of the patchset is it even still needed?

> +    dev->wmask[PCI_INTERRUPT_LINE] = 0;
>  
>      memory_region_init_io(&d->data_bar[0], OBJECT(d), &pci_ide_data_le_ops,
>                            &d->bus[0], "via-ide0-data", 8);
> @@ -213,14 +243,23 @@ static void via_ide_exitfn(PCIDevice *dev)
>      }
>  }
>  
> -void via_ide_init(PCIBus *bus, DriveInfo **hd_table, int devfn)
> +void via_ide_init(PCIBus *bus, DriveInfo **hd_table, int devfn,
> +                  bool legacy_irq)
>  {
>      PCIDevice *dev;
>  
> -    dev = pci_create_simple(bus, devfn, "via-ide");
> +    dev = pci_create(bus, devfn, "via-ide");
> +    qdev_prop_set_bit(&dev->qdev, "legacy-irq", legacy_irq);
> +    qdev_init_nofail(&dev->qdev);
>      pci_ide_create_devs(dev, hd_table);
>  }
>  
> +static Property via_ide_properties[] = {
> +    DEFINE_PROP_BIT("legacy-irq", PCIIDEState, flags, PCI_IDE_LEGACY_IRQ,
> +                    false),
> +    DEFINE_PROP_END_OF_LIST(),
> +};
> +
>  static void via_ide_class_init(ObjectClass *klass, void *data)
>  {
>      DeviceClass *dc = DEVICE_CLASS(klass);
> @@ -229,10 +268,12 @@ static void via_ide_class_init(ObjectClass *klass, void *data)
>      dc->reset = via_ide_reset;
>      k->realize = via_ide_realize;
>      k->exit = via_ide_exitfn;
> +    k->config_read = via_ide_config_read;
>      k->vendor_id = PCI_VENDOR_ID_VIA;
>      k->device_id = PCI_DEVICE_ID_VIA_IDE;
>      k->revision = 0x06;
>      k->class_id = PCI_CLASS_STORAGE_IDE;
> +    device_class_set_props(dc, via_ide_properties);
>      set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
>  }
>  
> diff --git a/hw/mips/mips_fulong2e.c b/hw/mips/mips_fulong2e.c
> index 4727b1d3a4..150182c62a 100644
> --- a/hw/mips/mips_fulong2e.c
> +++ b/hw/mips/mips_fulong2e.c
> @@ -257,7 +257,7 @@ static void vt82c686b_southbridge_init(PCIBus *pci_bus, int slot, qemu_irq intc,
>      isa_create_simple(isa_bus, TYPE_VT82C686B_SUPERIO);
>  
>      ide_drive_get(hd, ARRAY_SIZE(hd));
> -    via_ide_init(pci_bus, hd, PCI_DEVFN(slot, 1));
> +    via_ide_init(pci_bus, hd, PCI_DEVFN(slot, 1), false);
>  
>      pci_create_simple(pci_bus, PCI_DEVFN(slot, 2), "vt82c686b-usb-uhci");
>      pci_create_simple(pci_bus, PCI_DEVFN(slot, 3), "vt82c686b-usb-uhci");
> diff --git a/include/hw/ide.h b/include/hw/ide.h
> index d88c5ee695..2a7001ccba 100644
> --- a/include/hw/ide.h
> +++ b/include/hw/ide.h
> @@ -18,7 +18,8 @@ PCIDevice *pci_piix3_xen_ide_init(PCIBus *bus, DriveInfo **hd_table, int devfn);
>  PCIDevice *pci_piix3_ide_init(PCIBus *bus, DriveInfo **hd_table, int devfn);
>  PCIDevice *pci_piix4_ide_init(PCIBus *bus, DriveInfo **hd_table, int devfn);
>  int pci_piix3_xen_ide_unplug(DeviceState *dev, bool aux);
> -void via_ide_init(PCIBus *bus, DriveInfo **hd_table, int devfn);
> +void via_ide_init(PCIBus *bus, DriveInfo **hd_table, int devfn,
> +                  bool legacy_irq);
>  
>  /* ide-mmio.c */
>  void mmio_ide_init_drives(DeviceState *dev, DriveInfo *hd0, DriveInfo *hd1);


ATB,

Mark.
Mark Cave-Ayland March 9, 2020, 7:50 p.m. UTC | #2
On 09/03/2020 00:42, BALATON Zoltan wrote:

> Some machines operate in "non 100% native mode" where interrupts are
> fixed at legacy IDE interrupts and some guests expect this behaviour
> without checking based on knowledge about hardware. Even Linux has
> arch specific workarounds for this that are activated on such boards
> so this needs to be emulated as well.
> 
> Signed-off-by: BALATON Zoltan <balaton@eik.bme.hu>
> ---
> v2: Don't use PCI_INTERRUPT_LINE in via_ide_set_irq()
> 
>  hw/ide/via.c            | 57 +++++++++++++++++++++++++++++++++++------
>  hw/mips/mips_fulong2e.c |  2 +-
>  include/hw/ide.h        |  3 ++-
>  3 files changed, 52 insertions(+), 10 deletions(-)
> 
> diff --git a/hw/ide/via.c b/hw/ide/via.c
> index 096de8dba0..44ecc2af29 100644
> --- a/hw/ide/via.c
> +++ b/hw/ide/via.c
> @@ -1,9 +1,10 @@
>  /*
> - * QEMU IDE Emulation: PCI VIA82C686B support.
> + * QEMU VIA southbridge IDE emulation (VT82C686B, VT8231)
>   *
>   * Copyright (c) 2003 Fabrice Bellard
>   * Copyright (c) 2006 Openedhand Ltd.
>   * Copyright (c) 2010 Huacai Chen <zltjiangshi@gmail.com>
> + * Copyright (c) 2019-2020 BALATON Zoltan
>   *
>   * Permission is hereby granted, free of charge, to any person obtaining a copy
>   * of this software and associated documentation files (the "Software"), to deal
> @@ -25,6 +26,8 @@
>   */
>  
>  #include "qemu/osdep.h"
> +#include "qemu/range.h"
> +#include "hw/qdev-properties.h"
>  #include "hw/pci/pci.h"
>  #include "migration/vmstate.h"
>  #include "qemu/module.h"
> @@ -111,14 +114,40 @@ static void via_ide_set_irq(void *opaque, int n, int level)
>      } else {
>          d->config[0x70 + n * 8] &= ~0x80;
>      }
> -
>      level = (d->config[0x70] & 0x80) || (d->config[0x78] & 0x80);
> -    n = pci_get_byte(d->config + PCI_INTERRUPT_LINE);
> -    if (n) {
> -        qemu_set_irq(isa_get_irq(NULL, n), level);
> +
> +    /*
> +     * Some machines operate in "non 100% native mode" where PCI_INTERRUPT_LINE
> +     * is not used but IDE always uses ISA IRQ 14 and 15 even in native mode.
> +     * Some guest drivers expect this, often without checking.
> +     */
> +    if (!(pci_get_byte(d->config + PCI_CLASS_PROG) & (n ? 4 : 1)) ||

One other thing whilst I'm here: the above line is quite cryptic to read unless
you're very familiar with the PCI IDE specifications. How about something like this
towards the top of the function:

int native = n ? pci_get_byte(d->config + PCI_CLASS_PROG) & 4 :
                 pci_get_byte(d->config + PCI_CLASS_PROG) & 1;

and change the if() accordingly:

if (native) {
   ...
} else {
   ...
}

With that you can could probably drop the comment since it's really obvious what the
code is doing when reading it against the datasheet.

> +        PCI_IDE(d)->flags & BIT(PCI_IDE_LEGACY_IRQ)) {
> +        qemu_set_irq(isa_get_irq(NULL, (n ? 15 : 14)), level);
> +    } else {
> +        qemu_set_irq(isa_get_irq(NULL, 14), level);
>      }
>  }
>  
> +static uint32_t via_ide_config_read(PCIDevice *d, uint32_t address, int len)
> +{
> +    /*
> +     * The pegasos2 firmware writes to PCI_INTERRUPT_LINE but on real
> +     * hardware it's fixed at 14 and won't change. Some guests also expect
> +     * legacy interrupts, without reading PCI_INTERRUPT_LINE but Linux
> +     * depends on this to read 14. We set it to 14 in the reset method and
> +     * also set the wmask to 0 to emulate this but that turns out to be not
> +     * enough. QEMU resets the PCI bus after this device and
> +     * pci_do_device_reset() called from pci_device_reset() will zero
> +     * PCI_INTERRUPT_LINE so this config_read function is to counter that and
> +     * restore the correct value, otherwise this should not be needed.
> +     */
> +    if (range_covers_byte(address, len, PCI_INTERRUPT_LINE)) {
> +        pci_set_byte(d->config + PCI_INTERRUPT_LINE, 14);
> +    }
> +    return pci_default_read_config(d, address, len);
> +}
> +
>  static void via_ide_reset(DeviceState *dev)
>  {
>      PCIIDEState *d = PCI_IDE(dev);
> @@ -169,7 +198,8 @@ static void via_ide_realize(PCIDevice *dev, Error **errp)
>  
>      pci_config_set_prog_interface(pci_conf, 0x8f); /* native PCI ATA mode */
>      pci_set_long(pci_conf + PCI_CAPABILITY_LIST, 0x000000c0);
> -    dev->wmask[PCI_INTERRUPT_LINE] = 0xf;
> +    dev->wmask[PCI_CLASS_PROG] = 5;
> +    dev->wmask[PCI_INTERRUPT_LINE] = 0;
>  
>      memory_region_init_io(&d->data_bar[0], OBJECT(d), &pci_ide_data_le_ops,
>                            &d->bus[0], "via-ide0-data", 8);
> @@ -213,14 +243,23 @@ static void via_ide_exitfn(PCIDevice *dev)
>      }
>  }
>  
> -void via_ide_init(PCIBus *bus, DriveInfo **hd_table, int devfn)
> +void via_ide_init(PCIBus *bus, DriveInfo **hd_table, int devfn,
> +                  bool legacy_irq)
>  {
>      PCIDevice *dev;
>  
> -    dev = pci_create_simple(bus, devfn, "via-ide");
> +    dev = pci_create(bus, devfn, "via-ide");
> +    qdev_prop_set_bit(&dev->qdev, "legacy-irq", legacy_irq);
> +    qdev_init_nofail(&dev->qdev);
>      pci_ide_create_devs(dev, hd_table);
>  }
>  
> +static Property via_ide_properties[] = {
> +    DEFINE_PROP_BIT("legacy-irq", PCIIDEState, flags, PCI_IDE_LEGACY_IRQ,
> +                    false),
> +    DEFINE_PROP_END_OF_LIST(),
> +};
> +
>  static void via_ide_class_init(ObjectClass *klass, void *data)
>  {
>      DeviceClass *dc = DEVICE_CLASS(klass);
> @@ -229,10 +268,12 @@ static void via_ide_class_init(ObjectClass *klass, void *data)
>      dc->reset = via_ide_reset;
>      k->realize = via_ide_realize;
>      k->exit = via_ide_exitfn;
> +    k->config_read = via_ide_config_read;
>      k->vendor_id = PCI_VENDOR_ID_VIA;
>      k->device_id = PCI_DEVICE_ID_VIA_IDE;
>      k->revision = 0x06;
>      k->class_id = PCI_CLASS_STORAGE_IDE;
> +    device_class_set_props(dc, via_ide_properties);
>      set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
>  }
>  
> diff --git a/hw/mips/mips_fulong2e.c b/hw/mips/mips_fulong2e.c
> index 4727b1d3a4..150182c62a 100644
> --- a/hw/mips/mips_fulong2e.c
> +++ b/hw/mips/mips_fulong2e.c
> @@ -257,7 +257,7 @@ static void vt82c686b_southbridge_init(PCIBus *pci_bus, int slot, qemu_irq intc,
>      isa_create_simple(isa_bus, TYPE_VT82C686B_SUPERIO);
>  
>      ide_drive_get(hd, ARRAY_SIZE(hd));
> -    via_ide_init(pci_bus, hd, PCI_DEVFN(slot, 1));
> +    via_ide_init(pci_bus, hd, PCI_DEVFN(slot, 1), false);
>  
>      pci_create_simple(pci_bus, PCI_DEVFN(slot, 2), "vt82c686b-usb-uhci");
>      pci_create_simple(pci_bus, PCI_DEVFN(slot, 3), "vt82c686b-usb-uhci");
> diff --git a/include/hw/ide.h b/include/hw/ide.h
> index d88c5ee695..2a7001ccba 100644
> --- a/include/hw/ide.h
> +++ b/include/hw/ide.h
> @@ -18,7 +18,8 @@ PCIDevice *pci_piix3_xen_ide_init(PCIBus *bus, DriveInfo **hd_table, int devfn);
>  PCIDevice *pci_piix3_ide_init(PCIBus *bus, DriveInfo **hd_table, int devfn);
>  PCIDevice *pci_piix4_ide_init(PCIBus *bus, DriveInfo **hd_table, int devfn);
>  int pci_piix3_xen_ide_unplug(DeviceState *dev, bool aux);
> -void via_ide_init(PCIBus *bus, DriveInfo **hd_table, int devfn);
> +void via_ide_init(PCIBus *bus, DriveInfo **hd_table, int devfn,
> +                  bool legacy_irq);
>  
>  /* ide-mmio.c */
>  void mmio_ide_init_drives(DeviceState *dev, DriveInfo *hd0, DriveInfo *hd1);


ATB,

Mark.
BALATON Zoltan March 9, 2020, 8:17 p.m. UTC | #3
On Mon, 9 Mar 2020, Mark Cave-Ayland wrote:
> On 09/03/2020 00:42, BALATON Zoltan wrote:
>> Some machines operate in "non 100% native mode" where interrupts are
>> fixed at legacy IDE interrupts and some guests expect this behaviour
>> without checking based on knowledge about hardware. Even Linux has
>> arch specific workarounds for this that are activated on such boards
>> so this needs to be emulated as well.
>>
>> Signed-off-by: BALATON Zoltan <balaton@eik.bme.hu>
>> ---
>> v2: Don't use PCI_INTERRUPT_LINE in via_ide_set_irq()
>>
>>  hw/ide/via.c            | 57 +++++++++++++++++++++++++++++++++++------
>>  hw/mips/mips_fulong2e.c |  2 +-
>>  include/hw/ide.h        |  3 ++-
>>  3 files changed, 52 insertions(+), 10 deletions(-)
>>
>> diff --git a/hw/ide/via.c b/hw/ide/via.c
>> index 096de8dba0..44ecc2af29 100644
>> --- a/hw/ide/via.c
>> +++ b/hw/ide/via.c
>> @@ -1,9 +1,10 @@
>>  /*
>> - * QEMU IDE Emulation: PCI VIA82C686B support.
>> + * QEMU VIA southbridge IDE emulation (VT82C686B, VT8231)
>>   *
>>   * Copyright (c) 2003 Fabrice Bellard
>>   * Copyright (c) 2006 Openedhand Ltd.
>>   * Copyright (c) 2010 Huacai Chen <zltjiangshi@gmail.com>
>> + * Copyright (c) 2019-2020 BALATON Zoltan
>>   *
>>   * Permission is hereby granted, free of charge, to any person obtaining a copy
>>   * of this software and associated documentation files (the "Software"), to deal
>> @@ -25,6 +26,8 @@
>>   */
>>
>>  #include "qemu/osdep.h"
>> +#include "qemu/range.h"
>> +#include "hw/qdev-properties.h"
>>  #include "hw/pci/pci.h"
>>  #include "migration/vmstate.h"
>>  #include "qemu/module.h"
>> @@ -111,14 +114,40 @@ static void via_ide_set_irq(void *opaque, int n, int level)
>>      } else {
>>          d->config[0x70 + n * 8] &= ~0x80;
>>      }
>> -
>>      level = (d->config[0x70] & 0x80) || (d->config[0x78] & 0x80);
>> -    n = pci_get_byte(d->config + PCI_INTERRUPT_LINE);
>> -    if (n) {
>> -        qemu_set_irq(isa_get_irq(NULL, n), level);
>> +
>> +    /*
>> +     * Some machines operate in "non 100% native mode" where PCI_INTERRUPT_LINE
>> +     * is not used but IDE always uses ISA IRQ 14 and 15 even in native mode.
>> +     * Some guest drivers expect this, often without checking.
>> +     */
>> +    if (!(pci_get_byte(d->config + PCI_CLASS_PROG) & (n ? 4 : 1)) ||
>> +        PCI_IDE(d)->flags & BIT(PCI_IDE_LEGACY_IRQ)) {
>> +        qemu_set_irq(isa_get_irq(NULL, (n ? 15 : 14)), level);
>> +    } else {
>> +        qemu_set_irq(isa_get_irq(NULL, 14), level);
>>      }
>>  }
>
> There's still the need to convert this to qdev gpio, but for now I'll review the
> updated version of this patch (and provide an example once the rest of the patchset
> is okay).

There's no need to do that now. I think it only makes sense to do that 
when the 686B and VT8231 models are also qdevified (which I'll plan to do 
when cleaning up my pegasos2 patches later) since that may change how this 
should be qdevified. But I don't have time to fully do it now so don't 
even ask. This will have to do for now as it's not worse than it is 
already and does fix clients so I see no immediate need to force more 
clean ups upon me.

> This looks much closer to what I was expecting with the fixed IRQs, and in fact
> doesn't it make the feature bit requirement obsolete? The PCI_CLASS_PROG bit is now
> the single source of truth as to whether native or legacy IRQs are used, and the code
> routes the IRQ accordingly.

No, the feature bit is still needed to flag if this device should work 
with 100% native mode as on fulong2e or with forced legacy IRQ non-100% 
native mode as on pegasos2. In both cases PCI_CLASS_PROG is actually set 
to native mode (most of the time, Linux fixes this up on pegasos2 for it's 
own convenience but other OSes don't care about it but we still need to 
know to use legacy interrupts) so the feature bit is needed to know when 
to use legacy and when native interrupts.

The hardcoded IRQ14 in native mode is also wrong, If you check VT8231 
datasheet it clearly says that the IRQ raised is selected by 
PCI_INTERRUPT_LINE so I think my previous version was correct but this 
still works because we fixed PCI_INTERRUPT_LINE to 14 so we know here it's 
14 without looking at the config reg that you forbid me to do. But due to 
coincidence it still works and matches your ideals about PCI specs that I 
don't think apply for this device but I could not convince you about that 
so if this is what it takes then so be it. As long as it works with 
clients I don't care, we can always clean this up later.

>> +static uint32_t via_ide_config_read(PCIDevice *d, uint32_t address, int len)
>> +{
>> +    /*
>> +     * The pegasos2 firmware writes to PCI_INTERRUPT_LINE but on real
>> +     * hardware it's fixed at 14 and won't change. Some guests also expect
>> +     * legacy interrupts, without reading PCI_INTERRUPT_LINE but Linux
>> +     * depends on this to read 14. We set it to 14 in the reset method and
>> +     * also set the wmask to 0 to emulate this but that turns out to be not
>> +     * enough. QEMU resets the PCI bus after this device and
>> +     * pci_do_device_reset() called from pci_device_reset() will zero
>> +     * PCI_INTERRUPT_LINE so this config_read function is to counter that and
>> +     * restore the correct value, otherwise this should not be needed.
>> +     */
>> +    if (range_covers_byte(address, len, PCI_INTERRUPT_LINE)) {
>> +        pci_set_byte(d->config + PCI_INTERRUPT_LINE, 14);
>> +    }
>> +    return pci_default_read_config(d, address, len);
>> +}
>
> The comment here is interesting so I had a quick look at pci_do_device_reset() to see
> what is happening there, and to me it seems that the real bug is
> pci_do_device_reset() doesn't honour wmask for PCI_INTERRUPT_LINE. This is because
> normally the BIOS would write this value long after the PCI bus has been physically
> reset and since via-ide is the first device to hardwire a value here, this wouldn't
> have been needed up until now.
>
> Fortunately it seems that there is already precedent for this so does the following
> diff work?
>
> diff --git a/hw/pci/pci.c b/hw/pci/pci.c
> index e1ed6677e1..4ae0e0e90f 100644
> --- a/hw/pci/pci.c
> +++ b/hw/pci/pci.c
> @@ -302,8 +302,10 @@ static void pci_do_device_reset(PCIDevice *dev)
>     pci_word_test_and_clear_mask(dev->config + PCI_STATUS,
>                                  pci_get_word(dev->wmask + PCI_STATUS) |
>                                  pci_get_word(dev->w1cmask + PCI_STATUS));
> +    pci_word_test_and_clear_mask(dev->config + PCI_INTERRUPT_LINE,
> +                                 pci_get_word(dev->wmask + PCI_INTERRUPT_LINE) |
> +                                 pci_get_word(dev->w1cmask + PCI_INTERRUPT_LINE));
>     dev->config[PCI_CACHE_LINE_SIZE] = 0x0;
> -    dev->config[PCI_INTERRUPT_LINE] = 0x0;
>     for (r = 0; r < PCI_NUM_REGIONS; ++r) {
>         PCIIORegion *region = &dev->io_regions[r];
>         if (!region->size) {
>
> If this works, it will help simplify your patch even further.

This seems to work but originally I did not want to touch anything that 
could break other parts so I did not investigate this further. Thanks for 
the suggestion, I've added this patch in v3 and removed workaround from 
via-ide.

>>  static void via_ide_reset(DeviceState *dev)
>>  {
>>      PCIIDEState *d = PCI_IDE(dev);
>> @@ -169,7 +198,8 @@ static void via_ide_realize(PCIDevice *dev, Error **errp)
>>
>>      pci_config_set_prog_interface(pci_conf, 0x8f); /* native PCI ATA mode */
>>      pci_set_long(pci_conf + PCI_CAPABILITY_LIST, 0x000000c0);
>> -    dev->wmask[PCI_INTERRUPT_LINE] = 0xf;
>> +    dev->wmask[PCI_CLASS_PROG] = 5;
>
> Possibly a separate patch for PCI_CLASS_PROG change? Although with the latest version
> of the patchset is it even still needed?

I don't see a need to add another one line patch for this here unless 
someone authorative asks for that. This is needed to allow Linux on 
pegasos2 to use this reg to signal its driver that it should use legacy 
interrupts. That is, while everything else is using native mode and this 
reg should also say that the pegasos2 fixup function of Linux sets this 
reg to legacy mode so it knows later that legacy interrupts should be used 
on this board. Therefore we need to allow this change.

Regards,
BALATON Zoltan
BALATON Zoltan March 9, 2020, 8:19 p.m. UTC | #4
On Mon, 9 Mar 2020, Mark Cave-Ayland wrote:
> On 09/03/2020 00:42, BALATON Zoltan wrote:
>
>> Some machines operate in "non 100% native mode" where interrupts are
>> fixed at legacy IDE interrupts and some guests expect this behaviour
>> without checking based on knowledge about hardware. Even Linux has
>> arch specific workarounds for this that are activated on such boards
>> so this needs to be emulated as well.
>>
>> Signed-off-by: BALATON Zoltan <balaton@eik.bme.hu>
>> ---
>> v2: Don't use PCI_INTERRUPT_LINE in via_ide_set_irq()
>>
>>  hw/ide/via.c            | 57 +++++++++++++++++++++++++++++++++++------
>>  hw/mips/mips_fulong2e.c |  2 +-
>>  include/hw/ide.h        |  3 ++-
>>  3 files changed, 52 insertions(+), 10 deletions(-)
>>
>> diff --git a/hw/ide/via.c b/hw/ide/via.c
>> index 096de8dba0..44ecc2af29 100644
>> --- a/hw/ide/via.c
>> +++ b/hw/ide/via.c
>> @@ -1,9 +1,10 @@
>>  /*
>> - * QEMU IDE Emulation: PCI VIA82C686B support.
>> + * QEMU VIA southbridge IDE emulation (VT82C686B, VT8231)
>>   *
>>   * Copyright (c) 2003 Fabrice Bellard
>>   * Copyright (c) 2006 Openedhand Ltd.
>>   * Copyright (c) 2010 Huacai Chen <zltjiangshi@gmail.com>
>> + * Copyright (c) 2019-2020 BALATON Zoltan
>>   *
>>   * Permission is hereby granted, free of charge, to any person obtaining a copy
>>   * of this software and associated documentation files (the "Software"), to deal
>> @@ -25,6 +26,8 @@
>>   */
>>
>>  #include "qemu/osdep.h"
>> +#include "qemu/range.h"
>> +#include "hw/qdev-properties.h"
>>  #include "hw/pci/pci.h"
>>  #include "migration/vmstate.h"
>>  #include "qemu/module.h"
>> @@ -111,14 +114,40 @@ static void via_ide_set_irq(void *opaque, int n, int level)
>>      } else {
>>          d->config[0x70 + n * 8] &= ~0x80;
>>      }
>> -
>>      level = (d->config[0x70] & 0x80) || (d->config[0x78] & 0x80);
>> -    n = pci_get_byte(d->config + PCI_INTERRUPT_LINE);
>> -    if (n) {
>> -        qemu_set_irq(isa_get_irq(NULL, n), level);
>> +
>> +    /*
>> +     * Some machines operate in "non 100% native mode" where PCI_INTERRUPT_LINE
>> +     * is not used but IDE always uses ISA IRQ 14 and 15 even in native mode.
>> +     * Some guest drivers expect this, often without checking.
>> +     */
>> +    if (!(pci_get_byte(d->config + PCI_CLASS_PROG) & (n ? 4 : 1)) ||
>
> One other thing whilst I'm here: the above line is quite cryptic to read unless
> you're very familiar with the PCI IDE specifications. How about something like this
> towards the top of the function:
>
> int native = n ? pci_get_byte(d->config + PCI_CLASS_PROG) & 4 :
>                 pci_get_byte(d->config + PCI_CLASS_PROG) & 1;
>
> and change the if() accordingly:
>
> if (native) {
>   ...
> } else {
>   ...
> }
>
> With that you can could probably drop the comment since it's really obvious what the
> code is doing when reading it against the datasheet.

Too late, already sent v3, may consider later but the comment is actually 
for the line after the || not the one checking CLASS_PROG.

Regards,
BALATON Zoltan
Mark Cave-Ayland March 10, 2020, 6:12 p.m. UTC | #5
On 09/03/2020 20:17, BALATON Zoltan wrote:

> On Mon, 9 Mar 2020, Mark Cave-Ayland wrote:
>> On 09/03/2020 00:42, BALATON Zoltan wrote:
>>> Some machines operate in "non 100% native mode" where interrupts are
>>> fixed at legacy IDE interrupts and some guests expect this behaviour
>>> without checking based on knowledge about hardware. Even Linux has
>>> arch specific workarounds for this that are activated on such boards
>>> so this needs to be emulated as well.
>>>
>>> Signed-off-by: BALATON Zoltan <balaton@eik.bme.hu>
>>> ---
>>> v2: Don't use PCI_INTERRUPT_LINE in via_ide_set_irq()
>>>
>>>  hw/ide/via.c            | 57 +++++++++++++++++++++++++++++++++++------
>>>  hw/mips/mips_fulong2e.c |  2 +-
>>>  include/hw/ide.h        |  3 ++-
>>>  3 files changed, 52 insertions(+), 10 deletions(-)
>>>
>>> diff --git a/hw/ide/via.c b/hw/ide/via.c
>>> index 096de8dba0..44ecc2af29 100644
>>> --- a/hw/ide/via.c
>>> +++ b/hw/ide/via.c
>>> @@ -1,9 +1,10 @@
>>>  /*
>>> - * QEMU IDE Emulation: PCI VIA82C686B support.
>>> + * QEMU VIA southbridge IDE emulation (VT82C686B, VT8231)
>>>   *
>>>   * Copyright (c) 2003 Fabrice Bellard
>>>   * Copyright (c) 2006 Openedhand Ltd.
>>>   * Copyright (c) 2010 Huacai Chen <zltjiangshi@gmail.com>
>>> + * Copyright (c) 2019-2020 BALATON Zoltan
>>>   *
>>>   * Permission is hereby granted, free of charge, to any person obtaining a copy
>>>   * of this software and associated documentation files (the "Software"), to deal
>>> @@ -25,6 +26,8 @@
>>>   */
>>>
>>>  #include "qemu/osdep.h"
>>> +#include "qemu/range.h"
>>> +#include "hw/qdev-properties.h"
>>>  #include "hw/pci/pci.h"
>>>  #include "migration/vmstate.h"
>>>  #include "qemu/module.h"
>>> @@ -111,14 +114,40 @@ static void via_ide_set_irq(void *opaque, int n, int level)
>>>      } else {
>>>          d->config[0x70 + n * 8] &= ~0x80;
>>>      }
>>> -
>>>      level = (d->config[0x70] & 0x80) || (d->config[0x78] & 0x80);
>>> -    n = pci_get_byte(d->config + PCI_INTERRUPT_LINE);
>>> -    if (n) {
>>> -        qemu_set_irq(isa_get_irq(NULL, n), level);
>>> +
>>> +    /*
>>> +     * Some machines operate in "non 100% native mode" where PCI_INTERRUPT_LINE
>>> +     * is not used but IDE always uses ISA IRQ 14 and 15 even in native mode.
>>> +     * Some guest drivers expect this, often without checking.
>>> +     */
>>> +    if (!(pci_get_byte(d->config + PCI_CLASS_PROG) & (n ? 4 : 1)) ||
>>> +        PCI_IDE(d)->flags & BIT(PCI_IDE_LEGACY_IRQ)) {
>>> +        qemu_set_irq(isa_get_irq(NULL, (n ? 15 : 14)), level);
>>> +    } else {
>>> +        qemu_set_irq(isa_get_irq(NULL, 14), level);
>>>      }
>>>  }
>>
>> There's still the need to convert this to qdev gpio, but for now I'll review the
>> updated version of this patch (and provide an example once the rest of the patchset
>> is okay).
> 
> There's no need to do that now. I think it only makes sense to do that when the 686B
> and VT8231 models are also qdevified (which I'll plan to do when cleaning up my
> pegasos2 patches later) since that may change how this should be qdevified. But I
> don't have time to fully do it now so don't even ask. This will have to do for now as
> it's not worse than it is already and does fix clients so I see no immediate need to
> force more clean ups upon me.
> 
>> This looks much closer to what I was expecting with the fixed IRQs, and in fact
>> doesn't it make the feature bit requirement obsolete? The PCI_CLASS_PROG bit is now
>> the single source of truth as to whether native or legacy IRQs are used, and the code
>> routes the IRQ accordingly.
> 
> No, the feature bit is still needed to flag if this device should work with 100%
> native mode as on fulong2e or with forced legacy IRQ non-100% native mode as on
> pegasos2. In both cases PCI_CLASS_PROG is actually set to native mode (most of the
> time, Linux fixes this up on pegasos2 for it's own convenience but other OSes don't
> care about it but we still need to know to use legacy interrupts) so the feature bit
> is needed to know when to use legacy and when native interrupts.
> 
> The hardcoded IRQ14 in native mode is also wrong, If you check VT8231 datasheet it
> clearly says that the IRQ raised is selected by PCI_INTERRUPT_LINE so I think my
> previous version was correct but this still works because we fixed PCI_INTERRUPT_LINE
> to 14 so we know here it's 14 without looking at the config reg that you forbid me to
> do. But due to coincidence it still works and matches your ideals about PCI specs
> that I don't think apply for this device but I could not convince you about that so
> if this is what it takes then so be it. As long as it works with clients I don't
> care, we can always clean this up later.

Actually I've just realised that the conversion to qdev solves all these issues,
because qdev has the concept of connecting outputs to inputs. The way to do this is
to define the VIA(PCI)IDEState with an array of two name legacy IRQs (as detailed in
my previous email), plus the PCI IRQ.

Then all via_ide_set_irq() has to do is set each qdev gpio level accordingly; if a
board isn't interested in an IRQ it just doesn't wire it up. The final piece of the
puzzle is what do we do in PCI native mode, but that is simple: always drive the
legacy IRQs if there is no PCI IRQ connected.

>>> +static uint32_t via_ide_config_read(PCIDevice *d, uint32_t address, int len)
>>> +{
>>> +    /*
>>> +     * The pegasos2 firmware writes to PCI_INTERRUPT_LINE but on real
>>> +     * hardware it's fixed at 14 and won't change. Some guests also expect
>>> +     * legacy interrupts, without reading PCI_INTERRUPT_LINE but Linux
>>> +     * depends on this to read 14. We set it to 14 in the reset method and
>>> +     * also set the wmask to 0 to emulate this but that turns out to be not
>>> +     * enough. QEMU resets the PCI bus after this device and
>>> +     * pci_do_device_reset() called from pci_device_reset() will zero
>>> +     * PCI_INTERRUPT_LINE so this config_read function is to counter that and
>>> +     * restore the correct value, otherwise this should not be needed.
>>> +     */
>>> +    if (range_covers_byte(address, len, PCI_INTERRUPT_LINE)) {
>>> +        pci_set_byte(d->config + PCI_INTERRUPT_LINE, 14);
>>> +    }
>>> +    return pci_default_read_config(d, address, len);
>>> +}
>>
>> The comment here is interesting so I had a quick look at pci_do_device_reset() to see
>> what is happening there, and to me it seems that the real bug is
>> pci_do_device_reset() doesn't honour wmask for PCI_INTERRUPT_LINE. This is because
>> normally the BIOS would write this value long after the PCI bus has been physically
>> reset and since via-ide is the first device to hardwire a value here, this wouldn't
>> have been needed up until now.
>>
>> Fortunately it seems that there is already precedent for this so does the following
>> diff work?
>>
>> diff --git a/hw/pci/pci.c b/hw/pci/pci.c
>> index e1ed6677e1..4ae0e0e90f 100644
>> --- a/hw/pci/pci.c
>> +++ b/hw/pci/pci.c
>> @@ -302,8 +302,10 @@ static void pci_do_device_reset(PCIDevice *dev)
>>     pci_word_test_and_clear_mask(dev->config + PCI_STATUS,
>>                                  pci_get_word(dev->wmask + PCI_STATUS) |
>>                                  pci_get_word(dev->w1cmask + PCI_STATUS));
>> +    pci_word_test_and_clear_mask(dev->config + PCI_INTERRUPT_LINE,
>> +                                 pci_get_word(dev->wmask + PCI_INTERRUPT_LINE) |
>> +                                 pci_get_word(dev->w1cmask + PCI_INTERRUPT_LINE));
>>     dev->config[PCI_CACHE_LINE_SIZE] = 0x0;
>> -    dev->config[PCI_INTERRUPT_LINE] = 0x0;
>>     for (r = 0; r < PCI_NUM_REGIONS; ++r) {
>>         PCIIORegion *region = &dev->io_regions[r];
>>         if (!region->size) {
>>
>> If this works, it will help simplify your patch even further.
> 
> This seems to work but originally I did not want to touch anything that could break
> other parts so I did not investigate this further. Thanks for the suggestion, I've
> added this patch in v3 and removed workaround from via-ide.
> 
>>>  static void via_ide_reset(DeviceState *dev)
>>>  {
>>>      PCIIDEState *d = PCI_IDE(dev);
>>> @@ -169,7 +198,8 @@ static void via_ide_realize(PCIDevice *dev, Error **errp)
>>>
>>>      pci_config_set_prog_interface(pci_conf, 0x8f); /* native PCI ATA mode */
>>>      pci_set_long(pci_conf + PCI_CAPABILITY_LIST, 0x000000c0);
>>> -    dev->wmask[PCI_INTERRUPT_LINE] = 0xf;
>>> +    dev->wmask[PCI_CLASS_PROG] = 5;
>>
>> Possibly a separate patch for PCI_CLASS_PROG change? Although with the latest version
>> of the patchset is it even still needed?
> 
> I don't see a need to add another one line patch for this here unless someone
> authorative asks for that. This is needed to allow Linux on pegasos2 to use this reg
> to signal its driver that it should use legacy interrupts. That is, while everything
> else is using native mode and this reg should also say that the pegasos2 fixup
> function of Linux sets this reg to legacy mode so it knows later that legacy
> interrupts should be used on this board. Therefore we need to allow this change.


ATB,

Mark.
BALATON Zoltan March 10, 2020, 6:34 p.m. UTC | #6
On Tue, 10 Mar 2020, Mark Cave-Ayland wrote:
> On 09/03/2020 20:17, BALATON Zoltan wrote:
>> On Mon, 9 Mar 2020, Mark Cave-Ayland wrote:
>>> On 09/03/2020 00:42, BALATON Zoltan wrote:
>>>> Some machines operate in "non 100% native mode" where interrupts are
>>>> fixed at legacy IDE interrupts and some guests expect this behaviour
>>>> without checking based on knowledge about hardware. Even Linux has
>>>> arch specific workarounds for this that are activated on such boards
>>>> so this needs to be emulated as well.
>>>>
>>>> Signed-off-by: BALATON Zoltan <balaton@eik.bme.hu>
>>>> ---
>>>> v2: Don't use PCI_INTERRUPT_LINE in via_ide_set_irq()
>>>>
>>>>  hw/ide/via.c            | 57 +++++++++++++++++++++++++++++++++++------
>>>>  hw/mips/mips_fulong2e.c |  2 +-
>>>>  include/hw/ide.h        |  3 ++-
>>>>  3 files changed, 52 insertions(+), 10 deletions(-)
>>>>
>>>> diff --git a/hw/ide/via.c b/hw/ide/via.c
>>>> index 096de8dba0..44ecc2af29 100644
>>>> --- a/hw/ide/via.c
>>>> +++ b/hw/ide/via.c
>>>> @@ -1,9 +1,10 @@
>>>>  /*
>>>> - * QEMU IDE Emulation: PCI VIA82C686B support.
>>>> + * QEMU VIA southbridge IDE emulation (VT82C686B, VT8231)
>>>>   *
>>>>   * Copyright (c) 2003 Fabrice Bellard
>>>>   * Copyright (c) 2006 Openedhand Ltd.
>>>>   * Copyright (c) 2010 Huacai Chen <zltjiangshi@gmail.com>
>>>> + * Copyright (c) 2019-2020 BALATON Zoltan
>>>>   *
>>>>   * Permission is hereby granted, free of charge, to any person obtaining a copy
>>>>   * of this software and associated documentation files (the "Software"), to deal
>>>> @@ -25,6 +26,8 @@
>>>>   */
>>>>
>>>>  #include "qemu/osdep.h"
>>>> +#include "qemu/range.h"
>>>> +#include "hw/qdev-properties.h"
>>>>  #include "hw/pci/pci.h"
>>>>  #include "migration/vmstate.h"
>>>>  #include "qemu/module.h"
>>>> @@ -111,14 +114,40 @@ static void via_ide_set_irq(void *opaque, int n, int level)
>>>>      } else {
>>>>          d->config[0x70 + n * 8] &= ~0x80;
>>>>      }
>>>> -
>>>>      level = (d->config[0x70] & 0x80) || (d->config[0x78] & 0x80);
>>>> -    n = pci_get_byte(d->config + PCI_INTERRUPT_LINE);
>>>> -    if (n) {
>>>> -        qemu_set_irq(isa_get_irq(NULL, n), level);
>>>> +
>>>> +    /*
>>>> +     * Some machines operate in "non 100% native mode" where PCI_INTERRUPT_LINE
>>>> +     * is not used but IDE always uses ISA IRQ 14 and 15 even in native mode.
>>>> +     * Some guest drivers expect this, often without checking.
>>>> +     */
>>>> +    if (!(pci_get_byte(d->config + PCI_CLASS_PROG) & (n ? 4 : 1)) ||
>>>> +        PCI_IDE(d)->flags & BIT(PCI_IDE_LEGACY_IRQ)) {
>>>> +        qemu_set_irq(isa_get_irq(NULL, (n ? 15 : 14)), level);
>>>> +    } else {
>>>> +        qemu_set_irq(isa_get_irq(NULL, 14), level);
>>>>      }
>>>>  }
>>>
>>> There's still the need to convert this to qdev gpio, but for now I'll review the
>>> updated version of this patch (and provide an example once the rest of the patchset
>>> is okay).
>>
>> There's no need to do that now. I think it only makes sense to do that when the 686B
>> and VT8231 models are also qdevified (which I'll plan to do when cleaning up my
>> pegasos2 patches later) since that may change how this should be qdevified. But I
>> don't have time to fully do it now so don't even ask. This will have to do for now as
>> it's not worse than it is already and does fix clients so I see no immediate need to
>> force more clean ups upon me.
>>
>>> This looks much closer to what I was expecting with the fixed IRQs, and in fact
>>> doesn't it make the feature bit requirement obsolete? The PCI_CLASS_PROG bit is now
>>> the single source of truth as to whether native or legacy IRQs are used, and the code
>>> routes the IRQ accordingly.
>>
>> No, the feature bit is still needed to flag if this device should work with 100%
>> native mode as on fulong2e or with forced legacy IRQ non-100% native mode as on
>> pegasos2. In both cases PCI_CLASS_PROG is actually set to native mode (most of the
>> time, Linux fixes this up on pegasos2 for it's own convenience but other OSes don't
>> care about it but we still need to know to use legacy interrupts) so the feature bit
>> is needed to know when to use legacy and when native interrupts.
>>
>> The hardcoded IRQ14 in native mode is also wrong, If you check VT8231 datasheet it
>> clearly says that the IRQ raised is selected by PCI_INTERRUPT_LINE so I think my
>> previous version was correct but this still works because we fixed PCI_INTERRUPT_LINE
>> to 14 so we know here it's 14 without looking at the config reg that you forbid me to
>> do. But due to coincidence it still works and matches your ideals about PCI specs
>> that I don't think apply for this device but I could not convince you about that so
>> if this is what it takes then so be it. As long as it works with clients I don't
>> care, we can always clean this up later.
>
> Actually I've just realised that the conversion to qdev solves all these issues,
> because qdev has the concept of connecting outputs to inputs. The way to do this is
> to define the VIA(PCI)IDEState with an array of two name legacy IRQs (as detailed in
> my previous email), plus the PCI IRQ.

This device (being part of an integrated southbridge chip) has no PCI IRQ 
at all, stop talking about it, that only may apply to CMD646. Once you 
accept this it may be easier to actually get to a solution. The via-ide 
either drives the legacy interrupts in legacy mode and on pegasos2 always, 
regardless of mode. Or in true native mode it drives one ISA interrupt for 
both channels selected by PCI_INTERRUPT_LINE config reg. This is clear 
from the VT8231 datasheet and my test with guests.

> Then all via_ide_set_irq() has to do is set each qdev gpio level accordingly; if a
> board isn't interested in an IRQ it just doesn't wire it up. The final piece of the
> puzzle is what do we do in PCI native mode, but that is simple: always drive the
> legacy IRQs if there is no PCI IRQ connected.

How does the device code know if there's anything connected to the gpio it 
defines? I don't see your proposal would be simpler or even easier to 
understand (aka cleaner) than what I've implemented. You'll need to 
explain a bit more but I think the real solution is to model VT82C686B and 
VT8231 and not parts of it in qdev. We don't want to model the inside of 
these chips.

Regards,
BALATON Zoltan
BALATON Zoltan March 10, 2020, 7:28 p.m. UTC | #7
On Tue, 10 Mar 2020, BALATON Zoltan wrote:
> On Tue, 10 Mar 2020, Mark Cave-Ayland wrote:
>> On 09/03/2020 20:17, BALATON Zoltan wrote:
>>> On Mon, 9 Mar 2020, Mark Cave-Ayland wrote:
>>>> On 09/03/2020 00:42, BALATON Zoltan wrote:
>>>>> Some machines operate in "non 100% native mode" where interrupts are
>>>>> fixed at legacy IDE interrupts and some guests expect this behaviour
>>>>> without checking based on knowledge about hardware. Even Linux has
>>>>> arch specific workarounds for this that are activated on such boards
>>>>> so this needs to be emulated as well.
>>>>> 
>>>>> Signed-off-by: BALATON Zoltan <balaton@eik.bme.hu>
>>>>> ---
>>>>> v2: Don't use PCI_INTERRUPT_LINE in via_ide_set_irq()
>>>>> 
>>>>>  hw/ide/via.c            | 57 +++++++++++++++++++++++++++++++++++------
>>>>>  hw/mips/mips_fulong2e.c |  2 +-
>>>>>  include/hw/ide.h        |  3 ++-
>>>>>  3 files changed, 52 insertions(+), 10 deletions(-)
>>>>> 
>>>>> diff --git a/hw/ide/via.c b/hw/ide/via.c
>>>>> index 096de8dba0..44ecc2af29 100644
>>>>> --- a/hw/ide/via.c
>>>>> +++ b/hw/ide/via.c
>>>>> @@ -1,9 +1,10 @@
>>>>>  /*
>>>>> - * QEMU IDE Emulation: PCI VIA82C686B support.
>>>>> + * QEMU VIA southbridge IDE emulation (VT82C686B, VT8231)
>>>>>   *
>>>>>   * Copyright (c) 2003 Fabrice Bellard
>>>>>   * Copyright (c) 2006 Openedhand Ltd.
>>>>>   * Copyright (c) 2010 Huacai Chen <zltjiangshi@gmail.com>
>>>>> + * Copyright (c) 2019-2020 BALATON Zoltan
>>>>>   *
>>>>>   * Permission is hereby granted, free of charge, to any person 
>>>>> obtaining a copy
>>>>>   * of this software and associated documentation files (the 
>>>>> "Software"), to deal
>>>>> @@ -25,6 +26,8 @@
>>>>>   */
>>>>> 
>>>>>  #include "qemu/osdep.h"
>>>>> +#include "qemu/range.h"
>>>>> +#include "hw/qdev-properties.h"
>>>>>  #include "hw/pci/pci.h"
>>>>>  #include "migration/vmstate.h"
>>>>>  #include "qemu/module.h"
>>>>> @@ -111,14 +114,40 @@ static void via_ide_set_irq(void *opaque, int n, 
>>>>> int level)
>>>>>      } else {
>>>>>          d->config[0x70 + n * 8] &= ~0x80;
>>>>>      }
>>>>> -
>>>>>      level = (d->config[0x70] & 0x80) || (d->config[0x78] & 0x80);
>>>>> -    n = pci_get_byte(d->config + PCI_INTERRUPT_LINE);
>>>>> -    if (n) {
>>>>> -        qemu_set_irq(isa_get_irq(NULL, n), level);
>>>>> +
>>>>> +    /*
>>>>> +     * Some machines operate in "non 100% native mode" where 
>>>>> PCI_INTERRUPT_LINE
>>>>> +     * is not used but IDE always uses ISA IRQ 14 and 15 even in native 
>>>>> mode.
>>>>> +     * Some guest drivers expect this, often without checking.
>>>>> +     */
>>>>> +    if (!(pci_get_byte(d->config + PCI_CLASS_PROG) & (n ? 4 : 1)) ||
>>>>> +        PCI_IDE(d)->flags & BIT(PCI_IDE_LEGACY_IRQ)) {
>>>>> +        qemu_set_irq(isa_get_irq(NULL, (n ? 15 : 14)), level);
>>>>> +    } else {
>>>>> +        qemu_set_irq(isa_get_irq(NULL, 14), level);
>>>>>      }
>>>>>  }
>>>> 
>>>> There's still the need to convert this to qdev gpio, but for now I'll 
>>>> review the
>>>> updated version of this patch (and provide an example once the rest of 
>>>> the patchset
>>>> is okay).
>>> 
>>> There's no need to do that now. I think it only makes sense to do that 
>>> when the 686B
>>> and VT8231 models are also qdevified (which I'll plan to do when cleaning 
>>> up my
>>> pegasos2 patches later) since that may change how this should be 
>>> qdevified. But I
>>> don't have time to fully do it now so don't even ask. This will have to do 
>>> for now as
>>> it's not worse than it is already and does fix clients so I see no 
>>> immediate need to
>>> force more clean ups upon me.
>>> 
>>>> This looks much closer to what I was expecting with the fixed IRQs, and 
>>>> in fact
>>>> doesn't it make the feature bit requirement obsolete? The PCI_CLASS_PROG 
>>>> bit is now
>>>> the single source of truth as to whether native or legacy IRQs are used, 
>>>> and the code
>>>> routes the IRQ accordingly.
>>> 
>>> No, the feature bit is still needed to flag if this device should work 
>>> with 100%
>>> native mode as on fulong2e or with forced legacy IRQ non-100% native mode 
>>> as on
>>> pegasos2. In both cases PCI_CLASS_PROG is actually set to native mode 
>>> (most of the
>>> time, Linux fixes this up on pegasos2 for it's own convenience but other 
>>> OSes don't
>>> care about it but we still need to know to use legacy interrupts) so the 
>>> feature bit
>>> is needed to know when to use legacy and when native interrupts.
>>> 
>>> The hardcoded IRQ14 in native mode is also wrong, If you check VT8231 
>>> datasheet it
>>> clearly says that the IRQ raised is selected by PCI_INTERRUPT_LINE so I 
>>> think my
>>> previous version was correct but this still works because we fixed 
>>> PCI_INTERRUPT_LINE
>>> to 14 so we know here it's 14 without looking at the config reg that you 
>>> forbid me to
>>> do. But due to coincidence it still works and matches your ideals about 
>>> PCI specs
>>> that I don't think apply for this device but I could not convince you 
>>> about that so
>>> if this is what it takes then so be it. As long as it works with clients I 
>>> don't
>>> care, we can always clean this up later.
>> 
>> Actually I've just realised that the conversion to qdev solves all these issues,
>> because qdev has the concept of connecting outputs to inputs. The way to do this is
>> to define the VIA(PCI)IDEState with an array of two name legacy IRQs (as detailed in
>> my previous email), plus the PCI IRQ.
>
> This device (being part of an integrated southbridge chip) has no PCI IRQ at 
> all, stop talking about it, that only may apply to CMD646. Once you accept 
> this it may be easier to actually get to a solution. The via-ide either 
> drives the legacy interrupts in legacy mode and on pegasos2 always, 
> regardless of mode. Or in true native mode it drives one ISA interrupt for 
> both channels selected by PCI_INTERRUPT_LINE config reg. This is clear from 
> the VT8231 datasheet and my test with guests.
>
>> Then all via_ide_set_irq() has to do is set each qdev gpio level accordingly; if a
>> board isn't interested in an IRQ it just doesn't wire it up. The final piece of the
>> puzzle is what do we do in PCI native mode, but that is simple: always drive the
>> legacy IRQs if there is no PCI IRQ connected.

Also even though we don't model that but real hardware can switch between 
native and legacy mode so in reality all boards need all irqs as they 
could be used depending on device setting. I think this legacy-irq thing 
is something peculiar to pegasos2 so using a feature but only set by that 
board is appropriate here. Unless you can propose something simpler you 
could just accept it at least for now and maybe we can try to clean it up 
later when other parts of the southbridge are qdevified.

> How does the device code know if there's anything connected to the gpio it 
> defines? I don't see your proposal would be simpler or even easier to 
> understand (aka cleaner) than what I've implemented. You'll need to explain a 
> bit more but I think the real solution is to model VT82C686B and VT8231 and 
> not parts of it in qdev. We don't want to model the inside of these chips.
>
> Regards,
> BALATON Zoltan
diff mbox series

Patch

diff --git a/hw/ide/via.c b/hw/ide/via.c
index 096de8dba0..44ecc2af29 100644
--- a/hw/ide/via.c
+++ b/hw/ide/via.c
@@ -1,9 +1,10 @@ 
 /*
- * QEMU IDE Emulation: PCI VIA82C686B support.
+ * QEMU VIA southbridge IDE emulation (VT82C686B, VT8231)
  *
  * Copyright (c) 2003 Fabrice Bellard
  * Copyright (c) 2006 Openedhand Ltd.
  * Copyright (c) 2010 Huacai Chen <zltjiangshi@gmail.com>
+ * Copyright (c) 2019-2020 BALATON Zoltan
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -25,6 +26,8 @@ 
  */
 
 #include "qemu/osdep.h"
+#include "qemu/range.h"
+#include "hw/qdev-properties.h"
 #include "hw/pci/pci.h"
 #include "migration/vmstate.h"
 #include "qemu/module.h"
@@ -111,14 +114,40 @@  static void via_ide_set_irq(void *opaque, int n, int level)
     } else {
         d->config[0x70 + n * 8] &= ~0x80;
     }
-
     level = (d->config[0x70] & 0x80) || (d->config[0x78] & 0x80);
-    n = pci_get_byte(d->config + PCI_INTERRUPT_LINE);
-    if (n) {
-        qemu_set_irq(isa_get_irq(NULL, n), level);
+
+    /*
+     * Some machines operate in "non 100% native mode" where PCI_INTERRUPT_LINE
+     * is not used but IDE always uses ISA IRQ 14 and 15 even in native mode.
+     * Some guest drivers expect this, often without checking.
+     */
+    if (!(pci_get_byte(d->config + PCI_CLASS_PROG) & (n ? 4 : 1)) ||
+        PCI_IDE(d)->flags & BIT(PCI_IDE_LEGACY_IRQ)) {
+        qemu_set_irq(isa_get_irq(NULL, (n ? 15 : 14)), level);
+    } else {
+        qemu_set_irq(isa_get_irq(NULL, 14), level);
     }
 }
 
+static uint32_t via_ide_config_read(PCIDevice *d, uint32_t address, int len)
+{
+    /*
+     * The pegasos2 firmware writes to PCI_INTERRUPT_LINE but on real
+     * hardware it's fixed at 14 and won't change. Some guests also expect
+     * legacy interrupts, without reading PCI_INTERRUPT_LINE but Linux
+     * depends on this to read 14. We set it to 14 in the reset method and
+     * also set the wmask to 0 to emulate this but that turns out to be not
+     * enough. QEMU resets the PCI bus after this device and
+     * pci_do_device_reset() called from pci_device_reset() will zero
+     * PCI_INTERRUPT_LINE so this config_read function is to counter that and
+     * restore the correct value, otherwise this should not be needed.
+     */
+    if (range_covers_byte(address, len, PCI_INTERRUPT_LINE)) {
+        pci_set_byte(d->config + PCI_INTERRUPT_LINE, 14);
+    }
+    return pci_default_read_config(d, address, len);
+}
+
 static void via_ide_reset(DeviceState *dev)
 {
     PCIIDEState *d = PCI_IDE(dev);
@@ -169,7 +198,8 @@  static void via_ide_realize(PCIDevice *dev, Error **errp)
 
     pci_config_set_prog_interface(pci_conf, 0x8f); /* native PCI ATA mode */
     pci_set_long(pci_conf + PCI_CAPABILITY_LIST, 0x000000c0);
-    dev->wmask[PCI_INTERRUPT_LINE] = 0xf;
+    dev->wmask[PCI_CLASS_PROG] = 5;
+    dev->wmask[PCI_INTERRUPT_LINE] = 0;
 
     memory_region_init_io(&d->data_bar[0], OBJECT(d), &pci_ide_data_le_ops,
                           &d->bus[0], "via-ide0-data", 8);
@@ -213,14 +243,23 @@  static void via_ide_exitfn(PCIDevice *dev)
     }
 }
 
-void via_ide_init(PCIBus *bus, DriveInfo **hd_table, int devfn)
+void via_ide_init(PCIBus *bus, DriveInfo **hd_table, int devfn,
+                  bool legacy_irq)
 {
     PCIDevice *dev;
 
-    dev = pci_create_simple(bus, devfn, "via-ide");
+    dev = pci_create(bus, devfn, "via-ide");
+    qdev_prop_set_bit(&dev->qdev, "legacy-irq", legacy_irq);
+    qdev_init_nofail(&dev->qdev);
     pci_ide_create_devs(dev, hd_table);
 }
 
+static Property via_ide_properties[] = {
+    DEFINE_PROP_BIT("legacy-irq", PCIIDEState, flags, PCI_IDE_LEGACY_IRQ,
+                    false),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
 static void via_ide_class_init(ObjectClass *klass, void *data)
 {
     DeviceClass *dc = DEVICE_CLASS(klass);
@@ -229,10 +268,12 @@  static void via_ide_class_init(ObjectClass *klass, void *data)
     dc->reset = via_ide_reset;
     k->realize = via_ide_realize;
     k->exit = via_ide_exitfn;
+    k->config_read = via_ide_config_read;
     k->vendor_id = PCI_VENDOR_ID_VIA;
     k->device_id = PCI_DEVICE_ID_VIA_IDE;
     k->revision = 0x06;
     k->class_id = PCI_CLASS_STORAGE_IDE;
+    device_class_set_props(dc, via_ide_properties);
     set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
 }
 
diff --git a/hw/mips/mips_fulong2e.c b/hw/mips/mips_fulong2e.c
index 4727b1d3a4..150182c62a 100644
--- a/hw/mips/mips_fulong2e.c
+++ b/hw/mips/mips_fulong2e.c
@@ -257,7 +257,7 @@  static void vt82c686b_southbridge_init(PCIBus *pci_bus, int slot, qemu_irq intc,
     isa_create_simple(isa_bus, TYPE_VT82C686B_SUPERIO);
 
     ide_drive_get(hd, ARRAY_SIZE(hd));
-    via_ide_init(pci_bus, hd, PCI_DEVFN(slot, 1));
+    via_ide_init(pci_bus, hd, PCI_DEVFN(slot, 1), false);
 
     pci_create_simple(pci_bus, PCI_DEVFN(slot, 2), "vt82c686b-usb-uhci");
     pci_create_simple(pci_bus, PCI_DEVFN(slot, 3), "vt82c686b-usb-uhci");
diff --git a/include/hw/ide.h b/include/hw/ide.h
index d88c5ee695..2a7001ccba 100644
--- a/include/hw/ide.h
+++ b/include/hw/ide.h
@@ -18,7 +18,8 @@  PCIDevice *pci_piix3_xen_ide_init(PCIBus *bus, DriveInfo **hd_table, int devfn);
 PCIDevice *pci_piix3_ide_init(PCIBus *bus, DriveInfo **hd_table, int devfn);
 PCIDevice *pci_piix4_ide_init(PCIBus *bus, DriveInfo **hd_table, int devfn);
 int pci_piix3_xen_ide_unplug(DeviceState *dev, bool aux);
-void via_ide_init(PCIBus *bus, DriveInfo **hd_table, int devfn);
+void via_ide_init(PCIBus *bus, DriveInfo **hd_table, int devfn,
+                  bool legacy_irq);
 
 /* ide-mmio.c */
 void mmio_ide_init_drives(DeviceState *dev, DriveInfo *hd0, DriveInfo *hd1);