From d2b5931756fdb9f839180e33898cd1e3e4fbdc90 Mon Sep 17 00:00:00 2001 From: pbrook Date: Sun, 24 Sep 2006 00:16:34 +0000 Subject: [PATCH] PCI shared IRQ fix (original patch by andrzej zaborowski). git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@2165 c046a42c-6fe2-441c-8c8c-71466251a162 --- hw/apb_pci.c | 16 ++++++++--- hw/grackle_pci.c | 14 +++++---- hw/pci.c | 16 +++++++++-- hw/piix_pci.c | 72 ++++++++++++++-------------------------------- hw/prep_pci.c | 20 +++++++++---- hw/unin_pci.c | 13 +++++++-- hw/versatile_pci.c | 9 ++++-- vl.h | 10 +++++-- 8 files changed, 95 insertions(+), 75 deletions(-) diff --git a/hw/apb_pci.c b/hw/apb_pci.c index 02e9824b39..6fa885e9ea 100644 --- a/hw/apb_pci.c +++ b/hw/apb_pci.c @@ -179,10 +179,18 @@ static CPUReadMemoryFunc *pci_apb_ioread[] = { &pci_apb_ioreadl, }; -/* ??? This is probably wrong. */ -static void pci_apb_set_irq(PCIDevice *d, void *pic, int irq_num, int level) +static int pci_apb_map_irq(PCIDevice *pci_dev, int irq_num) { - pic_set_irq_new(pic, d->config[PCI_INTERRUPT_LINE], level); + /* ??? As mentioned below this is probably wrong. */ + return irq_num; +} + +static void pci_apb_set_irq(void *pic, int irq_num, int level) +{ + /* ??? This is almost certainly wrong. However the rest of the sun4u + IRQ handling is missing, as is OpenBIOS support, so it wouldn't work + anyway. */ + pic_set_irq_new(pic, irq_num, level); } PCIBus *pci_apb_init(target_ulong special_base, target_ulong mem_base, @@ -194,7 +202,7 @@ PCIBus *pci_apb_init(target_ulong special_base, target_ulong mem_base, s = qemu_mallocz(sizeof(APBState)); /* Ultrasparc APB main bus */ - s->bus = pci_register_bus(pci_apb_set_irq, pic, 0); + s->bus = pci_register_bus(pci_apb_set_irq, pci_apb_map_irq, pic, 0); pci_mem_config = cpu_register_io_memory(0, pci_apb_config_read, pci_apb_config_write, s); diff --git a/hw/grackle_pci.c b/hw/grackle_pci.c index e3cbceb49a..8f8e5c0638 100644 --- a/hw/grackle_pci.c +++ b/hw/grackle_pci.c @@ -74,11 +74,15 @@ static CPUReadMemoryFunc *pci_grackle_read[] = { &pci_host_data_readl, }; -/* XXX: we do not simulate the hardware - we rely on the BIOS to - set correctly for irq line field */ -static void pci_grackle_set_irq(PCIDevice *d, void *pic, int irq_num, int level) +/* Don't know if this matches real hardware, but it agrees with OHW. */ +static int pci_grackle_map_irq(PCIDevice *pci_dev, int irq_num) { - heathrow_pic_set_irq(pic, d->config[PCI_INTERRUPT_LINE], level); + return (irq_num + (pci_dev->devfn >> 3)) & 3; +} + +static void pci_grackle_set_irq(void *pic, int irq_num, int level) +{ + heathrow_pic_set_irq(pic, irq_num + 8, level); } PCIBus *pci_grackle_init(uint32_t base, void *pic) @@ -88,7 +92,7 @@ PCIBus *pci_grackle_init(uint32_t base, void *pic) int pci_mem_config, pci_mem_data; s = qemu_mallocz(sizeof(GrackleState)); - s->bus = pci_register_bus(pci_grackle_set_irq, pic, 0); + s->bus = pci_register_bus(pci_grackle_set_irq, pci_grackle_map_irq, pic, 0); pci_mem_config = cpu_register_io_memory(0, pci_grackle_config_read, pci_grackle_config_write, s); diff --git a/hw/pci.c b/hw/pci.c index 75bd981104..78f05cd90f 100644 --- a/hw/pci.c +++ b/hw/pci.c @@ -29,11 +29,15 @@ struct PCIBus { int bus_num; int devfn_min; pci_set_irq_fn set_irq; + pci_map_irq_fn map_irq; uint32_t config_reg; /* XXX: suppress */ /* low level pic */ SetIRQFunc *low_set_irq; void *irq_opaque; PCIDevice *devices[256]; + /* The bus IRQ state is the logical OR of the connected devices. + Keep a count of the number of devices with raised IRQs. */ + int irq_count[4]; }; static void pci_update_mappings(PCIDevice *d); @@ -42,13 +46,16 @@ target_phys_addr_t pci_mem_base; static int pci_irq_index; static PCIBus *first_bus; -PCIBus *pci_register_bus(pci_set_irq_fn set_irq, void *pic, int devfn_min) +PCIBus *pci_register_bus(pci_set_irq_fn set_irq, pci_map_irq_fn map_irq, + void *pic, int devfn_min) { PCIBus *bus; bus = qemu_mallocz(sizeof(PCIBus)); bus->set_irq = set_irq; + bus->map_irq = map_irq; bus->irq_opaque = pic; bus->devfn_min = devfn_min; + memset(bus->irq_count, 0, sizeof(bus->irq_count)); first_bus = bus; return bus; } @@ -100,6 +107,7 @@ PCIDevice *pci_register_device(PCIBus *bus, const char *name, pci_dev->bus = bus; pci_dev->devfn = devfn; pstrcpy(pci_dev->name, sizeof(pci_dev->name), name); + memset(pci_dev->irq_state, 0, sizeof(pci_dev->irq_state)); if (!config_read) config_read = pci_default_read_config; @@ -404,7 +412,11 @@ uint32_t pci_data_read(void *opaque, uint32_t addr, int len) void pci_set_irq(PCIDevice *pci_dev, int irq_num, int level) { PCIBus *bus = pci_dev->bus; - bus->set_irq(pci_dev, bus->irq_opaque, irq_num, level); + + irq_num = bus->map_irq(pci_dev, irq_num); + bus->irq_count[irq_num] += level - pci_dev->irq_state[irq_num]; + pci_dev->irq_state[irq_num] = level; + bus->set_irq(bus->irq_opaque, irq_num, bus->irq_count[irq_num] != 0); } /***********************************************************/ diff --git a/hw/piix_pci.c b/hw/piix_pci.c index 672b736ad0..939521c7bb 100644 --- a/hw/piix_pci.c +++ b/hw/piix_pci.c @@ -40,7 +40,17 @@ static uint32_t i440fx_addr_readl(void* opaque, uint32_t addr) return s->config_reg; } -static void piix3_set_irq(PCIDevice *pci_dev, void *pic, int irq_num, int level); +static void piix3_set_irq(void *pic, int irq_num, int level); + +/* return the global irq number corresponding to a given device irq + pin. We could also use the bus number to have a more precise + mapping. */ +static int pci_slot_get_pirq(PCIDevice *pci_dev, int irq_num) +{ + int slot_addend; + slot_addend = (pci_dev->devfn >> 3) - 1; + return (irq_num + slot_addend) & 3; +} PCIBus *i440fx_init(void) { @@ -49,7 +59,7 @@ PCIBus *i440fx_init(void) I440FXState *s; s = qemu_mallocz(sizeof(I440FXState)); - b = pci_register_bus(piix3_set_irq, NULL, 0); + b = pci_register_bus(piix3_set_irq, pci_slot_get_pirq, NULL, 0); s->bus = b; register_ioport_write(0xcf8, 4, 4, i440fx_addr_writel, s); @@ -83,65 +93,25 @@ static PCIDevice *piix3_dev; /* just used for simpler irq handling. */ #define PCI_IRQ_WORDS ((PCI_DEVICES_MAX + 31) / 32) -static uint32_t pci_irq_levels[4][PCI_IRQ_WORDS]; +static int pci_irq_levels[4]; -/* return the global irq number corresponding to a given device irq - pin. We could also use the bus number to have a more precise - mapping. */ -static inline int pci_slot_get_pirq(PCIDevice *pci_dev, int irq_num) +static void piix3_set_irq(void *pic, int irq_num, int level) { - int slot_addend; - slot_addend = (pci_dev->devfn >> 3) - 1; - return (irq_num + slot_addend) & 3; -} + int i, pic_irq, pic_level; -static inline int get_pci_irq_level(int irq_num) -{ - int pic_level; -#if (PCI_IRQ_WORDS == 2) - pic_level = ((pci_irq_levels[irq_num][0] | - pci_irq_levels[irq_num][1]) != 0); -#else - { - int i; - pic_level = 0; - for(i = 0; i < PCI_IRQ_WORDS; i++) { - if (pci_irq_levels[irq_num][i]) { - pic_level = 1; - break; - } - } - } -#endif - return pic_level; -} - -static void piix3_set_irq(PCIDevice *pci_dev, void *pic, int irq_num, int level) -{ - int irq_index, shift, pic_irq, pic_level; - uint32_t *p; - - irq_num = pci_slot_get_pirq(pci_dev, irq_num); - irq_index = pci_dev->irq_index; - p = &pci_irq_levels[irq_num][irq_index >> 5]; - shift = (irq_index & 0x1f); - *p = (*p & ~(1 << shift)) | (level << shift); + pci_irq_levels[irq_num] = level; /* now we change the pic irq level according to the piix irq mappings */ /* XXX: optimize */ pic_irq = piix3_dev->config[0x60 + irq_num]; if (pic_irq < 16) { - /* the pic level is the logical OR of all the PCI irqs mapped + /* The pic level is the logical OR of all the PCI irqs mapped to it */ pic_level = 0; - if (pic_irq == piix3_dev->config[0x60]) - pic_level |= get_pci_irq_level(0); - if (pic_irq == piix3_dev->config[0x61]) - pic_level |= get_pci_irq_level(1); - if (pic_irq == piix3_dev->config[0x62]) - pic_level |= get_pci_irq_level(2); - if (pic_irq == piix3_dev->config[0x63]) - pic_level |= get_pci_irq_level(3); + for (i = 0; i < 4; i++) { + if (pic_irq == piix3_dev->config[0x60 + i]) + pic_level |= pci_irq_levels[i]; + } pic_set_irq(pic_irq, pic_level); } } diff --git a/hw/prep_pci.c b/hw/prep_pci.c index a31b74c80a..b5ff8eb0ff 100644 --- a/hw/prep_pci.c +++ b/hw/prep_pci.c @@ -117,11 +117,21 @@ static CPUReadMemoryFunc *PPC_PCIIO_read[] = { &PPC_PCIIO_readl, }; -static void prep_set_irq(PCIDevice *d, void *pic, int irq_num, int level) +/* Don't know if this matches real hardware, but it agrees with OHW. */ +static int prep_map_irq(PCIDevice *pci_dev, int irq_num) { - /* XXX: we do not simulate the hardware - we rely on the BIOS to - set correctly for irq line field */ - pic_set_irq(d->config[PCI_INTERRUPT_LINE], level); + return (irq_num + (pci_dev->devfn >> 3)) & 3; +} + +static int prep_irq_levels[4]; + +static void prep_set_irq(void *pic, int irq_num, int level) +{ + int pic_irq_num; + prep_irq_levels[irq_num] = level; + level |= prep_irq_levels[irq_num ^ 2]; + pic_irq_num = (irq_num == 0 || irq_num == 2) ? 9 : 11; + pic_set_irq(pic_irq_num, level); } PCIBus *pci_prep_init(void) @@ -131,7 +141,7 @@ PCIBus *pci_prep_init(void) int PPC_io_memory; s = qemu_mallocz(sizeof(PREPPCIState)); - s->bus = pci_register_bus(prep_set_irq, NULL, 0); + s->bus = pci_register_bus(prep_set_irq, prep_map_irq, NULL, 0); register_ioport_write(0xcf8, 4, 4, pci_prep_addr_writel, s); register_ioport_read(0xcf8, 4, 4, pci_prep_addr_readl, s); diff --git a/hw/unin_pci.c b/hw/unin_pci.c index a7e3600047..d132d6ec2e 100644 --- a/hw/unin_pci.c +++ b/hw/unin_pci.c @@ -140,9 +140,15 @@ static CPUReadMemoryFunc *pci_unin_read[] = { }; #endif -static void pci_unin_set_irq(PCIDevice *d, void *pic, int irq_num, int level) +/* Don't know if this matches real hardware, but it agrees with OHW. */ +static int pci_unin_map_irq(PCIDevice *pci_dev, int irq_num) { - openpic_set_irq(pic, d->config[PCI_INTERRUPT_LINE], level); + return (irq_num + (pci_dev->devfn >> 3)) & 3; +} + +static void pci_unin_set_irq(void *pic, int irq_num, int level) +{ + openpic_set_irq(pic, irq_num + 8, level); } PCIBus *pci_pmac_init(void *pic) @@ -154,7 +160,8 @@ PCIBus *pci_pmac_init(void *pic) /* Use values found on a real PowerMac */ /* Uninorth main bus */ s = qemu_mallocz(sizeof(UNINState)); - s->bus = pci_register_bus(pci_unin_set_irq, NULL, 11 << 3); + s->bus = pci_register_bus(pci_unin_set_irq, pci_unin_map_irq, + pic, 11 << 3); pci_mem_config = cpu_register_io_memory(0, pci_unin_main_config_read, pci_unin_main_config_write, s); diff --git a/hw/versatile_pci.c b/hw/versatile_pci.c index aac61c7b55..9addda8f01 100644 --- a/hw/versatile_pci.c +++ b/hw/versatile_pci.c @@ -79,7 +79,12 @@ static CPUReadMemoryFunc *pci_vpb_config_read[] = { static int pci_vpb_irq; -static void pci_vpb_set_irq(PCIDevice *d, void *pic, int irq_num, int level) +static int pci_vpb_map_irq(PCIDevice *d, int irq_num) +{ + return irq_num; +} + +static void pci_vpb_set_irq(void *pic, int irq_num, int level) { pic_set_irq_new(pic, pci_vpb_irq + irq_num, level); } @@ -100,7 +105,7 @@ PCIBus *pci_vpb_init(void *pic, int irq, int realview) base = 0x40000000; name = "Versatile/PB PCI Controller"; } - s = pci_register_bus(pci_vpb_set_irq, pic, 11 << 3); + s = pci_register_bus(pci_vpb_set_irq, pci_vpb_map_irq, pic, 11 << 3); /* ??? Register memory space. */ mem_config = cpu_register_io_memory(0, pci_vpb_config_read, diff --git a/vl.h b/vl.h index fef2ef8c21..3f654f2f4a 100644 --- a/vl.h +++ b/vl.h @@ -733,6 +733,9 @@ struct PCIDevice { PCIConfigWriteFunc *config_write; /* ??? This is a PC-specific hack, and should be removed. */ int irq_index; + + /* Current IRQ levels. Used internally by the generic PCI code. */ + int irq_state[4]; }; PCIDevice *pci_register_device(PCIBus *bus, const char *name, @@ -753,9 +756,10 @@ void pci_default_write_config(PCIDevice *d, void pci_device_save(PCIDevice *s, QEMUFile *f); int pci_device_load(PCIDevice *s, QEMUFile *f); -typedef void (*pci_set_irq_fn)(PCIDevice *pci_dev, void *pic, - int irq_num, int level); -PCIBus *pci_register_bus(pci_set_irq_fn set_irq, void *pic, int devfn_min); +typedef void (*pci_set_irq_fn)(void *pic, int irq_num, int level); +typedef int (*pci_map_irq_fn)(PCIDevice *pci_dev, int irq_num); +PCIBus *pci_register_bus(pci_set_irq_fn set_irq, pci_map_irq_fn map_irq, + void *pic, int devfn_min); void pci_nic_init(PCIBus *bus, NICInfo *nd); void pci_data_write(void *opaque, uint32_t addr, uint32_t val, int len);