diff --git a/hw/arm/realview.c b/hw/arm/realview.c index feabfc58d4..d6f47bf4d4 100644 --- a/hw/arm/realview.c +++ b/hw/arm/realview.c @@ -217,9 +217,13 @@ static void realview_init(QEMUMachineInitArgs *args, dev = qdev_create(NULL, "realview_pci"); busdev = SYS_BUS_DEVICE(dev); qdev_init_nofail(dev); - sysbus_mmio_map(busdev, 0, 0x61000000); /* PCI self-config */ - sysbus_mmio_map(busdev, 1, 0x62000000); /* PCI config */ - sysbus_mmio_map(busdev, 2, 0x63000000); /* PCI I/O */ + sysbus_mmio_map(busdev, 0, 0x10019000); /* PCI controller registers */ + sysbus_mmio_map(busdev, 1, 0x60000000); /* PCI self-config */ + sysbus_mmio_map(busdev, 2, 0x61000000); /* PCI config */ + sysbus_mmio_map(busdev, 3, 0x62000000); /* PCI I/O */ + sysbus_mmio_map(busdev, 4, 0x63000000); /* PCI memory window 1 */ + sysbus_mmio_map(busdev, 5, 0x64000000); /* PCI memory window 2 */ + sysbus_mmio_map(busdev, 6, 0x68000000); /* PCI memory window 3 */ sysbus_connect_irq(busdev, 0, pic[48]); sysbus_connect_irq(busdev, 1, pic[49]); sysbus_connect_irq(busdev, 2, pic[50]); @@ -303,12 +307,12 @@ static void realview_init(QEMUMachineInitArgs *args, /* 0x58000000 PISMO. */ /* 0x5c000000 PISMO. */ /* 0x60000000 PCI. */ - /* 0x61000000 PCI Self Config. */ - /* 0x62000000 PCI Config. */ - /* 0x63000000 PCI IO. */ - /* 0x64000000 PCI mem 0. */ - /* 0x68000000 PCI mem 1. */ - /* 0x6c000000 PCI mem 2. */ + /* 0x60000000 PCI Self Config. */ + /* 0x61000000 PCI Config. */ + /* 0x62000000 PCI IO. */ + /* 0x63000000 PCI mem 0. */ + /* 0x64000000 PCI mem 1. */ + /* 0x68000000 PCI mem 2. */ /* ??? Hack to map an additional page of ram for the secondary CPU startup code. I guess this works on real hardware because the diff --git a/hw/arm/versatilepb.c b/hw/arm/versatilepb.c index 25c665ab27..753757ea19 100644 --- a/hw/arm/versatilepb.c +++ b/hw/arm/versatilepb.c @@ -224,16 +224,19 @@ static void versatile_init(QEMUMachineInitArgs *args, int board_id) dev = qdev_create(NULL, "versatile_pci"); busdev = SYS_BUS_DEVICE(dev); qdev_init_nofail(dev); - sysbus_mmio_map(busdev, 0, 0x41000000); /* PCI self-config */ - sysbus_mmio_map(busdev, 1, 0x42000000); /* PCI config */ + sysbus_mmio_map(busdev, 0, 0x10001000); /* PCI controller regs */ + sysbus_mmio_map(busdev, 1, 0x41000000); /* PCI self-config */ + sysbus_mmio_map(busdev, 2, 0x42000000); /* PCI config */ + sysbus_mmio_map(busdev, 3, 0x43000000); /* PCI I/O */ + sysbus_mmio_map(busdev, 4, 0x44000000); /* PCI memory window 1 */ + sysbus_mmio_map(busdev, 5, 0x50000000); /* PCI memory window 2 */ + sysbus_mmio_map(busdev, 6, 0x60000000); /* PCI memory window 3 */ sysbus_connect_irq(busdev, 0, sic[27]); sysbus_connect_irq(busdev, 1, sic[28]); sysbus_connect_irq(busdev, 2, sic[29]); sysbus_connect_irq(busdev, 3, sic[30]); pci_bus = (PCIBus *)qdev_get_child_bus(dev, "pci"); - /* The Versatile PCI bridge does not provide access to PCI IO space, - so many of the qemu PCI devices are not useable. */ for(n = 0; n < nb_nics; n++) { nd = &nd_table[n]; diff --git a/hw/pci-host/versatile.c b/hw/pci-host/versatile.c index d67ca796fb..540daf75cc 100644 --- a/hw/pci-host/versatile.c +++ b/hw/pci-host/versatile.c @@ -9,34 +9,235 @@ #include "hw/sysbus.h" #include "hw/pci/pci.h" +#include "hw/pci/pci_bus.h" #include "hw/pci/pci_host.h" #include "exec/address-spaces.h" +/* Old and buggy versions of QEMU used the wrong mapping from + * PCI IRQs to system interrupt lines. Unfortunately the Linux + * kernel also had the corresponding bug in setting up interrupts + * (so older kernels work on QEMU and not on real hardware). + * We automatically detect these broken kernels and flip back + * to the broken irq mapping by spotting guest writes to the + * PCI_INTERRUPT_LINE register to see where the guest thinks + * interrupts are going to be routed. So we start in state + * ASSUME_OK on reset, and transition to either BROKEN or + * FORCE_OK at the first write to an INTERRUPT_LINE register for + * a slot where broken and correct interrupt mapping would differ. + * Once in either BROKEN or FORCE_OK we never transition again; + * this allows a newer kernel to use the INTERRUPT_LINE + * registers arbitrarily once it has indicated that it isn't + * broken in its init code somewhere. + */ +enum { + PCI_VPB_IRQMAP_ASSUME_OK, + PCI_VPB_IRQMAP_BROKEN, + PCI_VPB_IRQMAP_FORCE_OK, +}; + typedef struct { - SysBusDevice busdev; + PCIHostState parent_obj; + qemu_irq irq[4]; - int realview; + MemoryRegion controlregs; MemoryRegion mem_config; MemoryRegion mem_config2; - MemoryRegion isa; + /* Containers representing the PCI address spaces */ + MemoryRegion pci_io_space; + MemoryRegion pci_mem_space; + /* Alias regions into PCI address spaces which we expose as sysbus regions. + * The offsets into pci_mem_space are controlled by the imap registers. + */ + MemoryRegion pci_io_window; + MemoryRegion pci_mem_window[3]; + PCIBus pci_bus; + PCIDevice pci_dev; + + /* Constant for life of device: */ + int realview; + uint32_t mem_win_size[3]; + + /* Variable state: */ + uint32_t imap[3]; + uint32_t smap[3]; + uint32_t selfid; + uint32_t flags; + uint8_t irq_mapping; } PCIVPBState; -static inline uint32_t vpb_pci_config_addr(hwaddr addr) +static void pci_vpb_update_window(PCIVPBState *s, int i) { - return addr & 0xffffff; + /* Adjust the offset of the alias region we use for + * the memory window i to account for a change in the + * value of the corresponding IMAP register. + * Note that the semantics of the IMAP register differ + * for realview and versatile variants of the controller. + */ + hwaddr offset; + if (s->realview) { + /* Top bits of register (masked according to window size) provide + * top bits of PCI address. + */ + offset = s->imap[i] & ~(s->mem_win_size[i] - 1); + } else { + /* Bottom 4 bits of register provide top 4 bits of PCI address */ + offset = s->imap[i] << 28; + } + memory_region_set_alias_offset(&s->pci_mem_window[i], offset); } +static void pci_vpb_update_all_windows(PCIVPBState *s) +{ + /* Update all alias windows based on the current register state */ + int i; + + for (i = 0; i < 3; i++) { + pci_vpb_update_window(s, i); + } +} + +static int pci_vpb_post_load(void *opaque, int version_id) +{ + PCIVPBState *s = opaque; + pci_vpb_update_all_windows(s); + return 0; +} + +static const VMStateDescription pci_vpb_vmstate = { + .name = "versatile-pci", + .version_id = 1, + .minimum_version_id = 1, + .post_load = pci_vpb_post_load, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(imap, PCIVPBState, 3), + VMSTATE_UINT32_ARRAY(smap, PCIVPBState, 3), + VMSTATE_UINT32(selfid, PCIVPBState), + VMSTATE_UINT32(flags, PCIVPBState), + VMSTATE_UINT8(irq_mapping, PCIVPBState), + VMSTATE_END_OF_LIST() + } +}; + +#define TYPE_VERSATILE_PCI "versatile_pci" +#define PCI_VPB(obj) \ + OBJECT_CHECK(PCIVPBState, (obj), TYPE_VERSATILE_PCI) + +#define TYPE_VERSATILE_PCI_HOST "versatile_pci_host" +#define PCI_VPB_HOST(obj) \ + OBJECT_CHECK(PCIDevice, (obj), TYPE_VERSATILE_PCIHOST) + +typedef enum { + PCI_IMAP0 = 0x0, + PCI_IMAP1 = 0x4, + PCI_IMAP2 = 0x8, + PCI_SELFID = 0xc, + PCI_FLAGS = 0x10, + PCI_SMAP0 = 0x14, + PCI_SMAP1 = 0x18, + PCI_SMAP2 = 0x1c, +} PCIVPBControlRegs; + +static void pci_vpb_reg_write(void *opaque, hwaddr addr, + uint64_t val, unsigned size) +{ + PCIVPBState *s = opaque; + + switch (addr) { + case PCI_IMAP0: + case PCI_IMAP1: + case PCI_IMAP2: + { + int win = (addr - PCI_IMAP0) >> 2; + s->imap[win] = val; + pci_vpb_update_window(s, win); + break; + } + case PCI_SELFID: + s->selfid = val; + break; + case PCI_FLAGS: + s->flags = val; + break; + case PCI_SMAP0: + case PCI_SMAP1: + case PCI_SMAP2: + { + int win = (addr - PCI_SMAP0) >> 2; + s->smap[win] = val; + break; + } + default: + qemu_log_mask(LOG_GUEST_ERROR, + "pci_vpb_reg_write: Bad offset %x\n", (int)addr); + break; + } +} + +static uint64_t pci_vpb_reg_read(void *opaque, hwaddr addr, + unsigned size) +{ + PCIVPBState *s = opaque; + + switch (addr) { + case PCI_IMAP0: + case PCI_IMAP1: + case PCI_IMAP2: + { + int win = (addr - PCI_IMAP0) >> 2; + return s->imap[win]; + } + case PCI_SELFID: + return s->selfid; + case PCI_FLAGS: + return s->flags; + case PCI_SMAP0: + case PCI_SMAP1: + case PCI_SMAP2: + { + int win = (addr - PCI_SMAP0) >> 2; + return s->smap[win]; + } + default: + qemu_log_mask(LOG_GUEST_ERROR, + "pci_vpb_reg_read: Bad offset %x\n", (int)addr); + return 0; + } +} + +static const MemoryRegionOps pci_vpb_reg_ops = { + .read = pci_vpb_reg_read, + .write = pci_vpb_reg_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + static void pci_vpb_config_write(void *opaque, hwaddr addr, uint64_t val, unsigned size) { - pci_data_write(opaque, vpb_pci_config_addr(addr), val, size); + PCIVPBState *s = opaque; + if (!s->realview && (addr & 0xff) == PCI_INTERRUPT_LINE + && s->irq_mapping == PCI_VPB_IRQMAP_ASSUME_OK) { + uint8_t devfn = addr >> 8; + if ((PCI_SLOT(devfn) % PCI_NUM_PINS) != 2) { + if (val == 27) { + s->irq_mapping = PCI_VPB_IRQMAP_BROKEN; + } else { + s->irq_mapping = PCI_VPB_IRQMAP_FORCE_OK; + } + } + } + pci_data_write(&s->pci_bus, addr, val, size); } static uint64_t pci_vpb_config_read(void *opaque, hwaddr addr, unsigned size) { + PCIVPBState *s = opaque; uint32_t val; - val = pci_data_read(opaque, vpb_pci_config_addr(addr), size); + val = pci_data_read(&s->pci_bus, addr, size); return val; } @@ -48,7 +249,47 @@ static const MemoryRegionOps pci_vpb_config_ops = { static int pci_vpb_map_irq(PCIDevice *d, int irq_num) { - return irq_num; + PCIVPBState *s = container_of(d->bus, PCIVPBState, pci_bus); + + if (s->irq_mapping == PCI_VPB_IRQMAP_BROKEN) { + /* Legacy broken IRQ mapping for compatibility with old and + * buggy Linux guests + */ + return irq_num; + } + + /* Slot to IRQ mapping for RealView Platform Baseboard 926 backplane + * name slot IntA IntB IntC IntD + * A 31 IRQ28 IRQ29 IRQ30 IRQ27 + * B 30 IRQ27 IRQ28 IRQ29 IRQ30 + * C 29 IRQ30 IRQ27 IRQ28 IRQ29 + * Slot C is for the host bridge; A and B the peripherals. + * Our output irqs 0..3 correspond to the baseboard's 27..30. + * + * This mapping function takes account of an oddity in the PB926 + * board wiring, where the FPGA's P_nINTA input is connected to + * the INTB connection on the board PCI edge connector, P_nINTB + * is connected to INTC, and so on, so everything is one number + * further round from where you might expect. + */ + return pci_swizzle_map_irq_fn(d, irq_num + 2); +} + +static int pci_vpb_rv_map_irq(PCIDevice *d, int irq_num) +{ + /* Slot to IRQ mapping for RealView EB and PB1176 backplane + * name slot IntA IntB IntC IntD + * A 31 IRQ50 IRQ51 IRQ48 IRQ49 + * B 30 IRQ49 IRQ50 IRQ51 IRQ48 + * C 29 IRQ48 IRQ49 IRQ50 IRQ51 + * Slot C is for the host bridge; A and B the peripherals. + * Our output irqs 0..3 correspond to the baseboard's 48..51. + * + * The PB1176 and EB boards don't have the PB926 wiring oddity + * described above; P_nINTA connects to INTA, P_nINTB to INTB + * and so on, which is why this mapping function is different. + */ + return pci_swizzle_map_irq_fn(d, irq_num + 3); } static void pci_vpb_set_irq(void *opaque, int irq_num, int level) @@ -58,53 +299,109 @@ static void pci_vpb_set_irq(void *opaque, int irq_num, int level) qemu_set_irq(pic[irq_num], level); } -static int pci_vpb_init(SysBusDevice *dev) +static void pci_vpb_reset(DeviceState *d) { - PCIVPBState *s = FROM_SYSBUS(PCIVPBState, dev); - PCIBus *bus; + PCIVPBState *s = PCI_VPB(d); + + s->imap[0] = 0; + s->imap[1] = 0; + s->imap[2] = 0; + s->smap[0] = 0; + s->smap[1] = 0; + s->smap[2] = 0; + s->selfid = 0; + s->flags = 0; + s->irq_mapping = PCI_VPB_IRQMAP_ASSUME_OK; + + pci_vpb_update_all_windows(s); +} + +static void pci_vpb_init(Object *obj) +{ + PCIHostState *h = PCI_HOST_BRIDGE(obj); + PCIVPBState *s = PCI_VPB(obj); + + memory_region_init(&s->pci_io_space, "pci_io", 1ULL << 32); + memory_region_init(&s->pci_mem_space, "pci_mem", 1ULL << 32); + + pci_bus_new_inplace(&s->pci_bus, DEVICE(obj), "pci", + &s->pci_mem_space, &s->pci_io_space, + PCI_DEVFN(11, 0), TYPE_PCI_BUS); + h->bus = &s->pci_bus; + + object_initialize(&s->pci_dev, TYPE_VERSATILE_PCI_HOST); + qdev_set_parent_bus(DEVICE(&s->pci_dev), BUS(&s->pci_bus)); + object_property_set_int(OBJECT(&s->pci_dev), PCI_DEVFN(29, 0), "addr", + NULL); + + /* Window sizes for VersatilePB; realview_pci's init will override */ + s->mem_win_size[0] = 0x0c000000; + s->mem_win_size[1] = 0x10000000; + s->mem_win_size[2] = 0x10000000; +} + +static void pci_vpb_realize(DeviceState *dev, Error **errp) +{ + PCIVPBState *s = PCI_VPB(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + pci_map_irq_fn mapfn; int i; for (i = 0; i < 4; i++) { - sysbus_init_irq(dev, &s->irq[i]); + sysbus_init_irq(sbd, &s->irq[i]); } - bus = pci_register_bus(&dev->qdev, "pci", - pci_vpb_set_irq, pci_vpb_map_irq, s->irq, - get_system_memory(), get_system_io(), - PCI_DEVFN(11, 0), 4, TYPE_PCI_BUS); - /* ??? Register memory space. */ + if (s->realview) { + mapfn = pci_vpb_rv_map_irq; + } else { + mapfn = pci_vpb_map_irq; + } + + pci_bus_irqs(&s->pci_bus, pci_vpb_set_irq, mapfn, s->irq, 4); /* Our memory regions are: - * 0 : PCI self config window - * 1 : PCI config window - * 2 : PCI IO window (realview_pci only) + * 0 : our control registers + * 1 : PCI self config window + * 2 : PCI config window + * 3 : PCI IO window + * 4..6 : PCI memory windows */ - memory_region_init_io(&s->mem_config, &pci_vpb_config_ops, bus, + memory_region_init_io(&s->controlregs, &pci_vpb_reg_ops, s, "pci-vpb-regs", + 0x1000); + sysbus_init_mmio(sbd, &s->controlregs); + memory_region_init_io(&s->mem_config, &pci_vpb_config_ops, s, "pci-vpb-selfconfig", 0x1000000); - sysbus_init_mmio(dev, &s->mem_config); - memory_region_init_io(&s->mem_config2, &pci_vpb_config_ops, bus, + sysbus_init_mmio(sbd, &s->mem_config); + memory_region_init_io(&s->mem_config2, &pci_vpb_config_ops, s, "pci-vpb-config", 0x1000000); - sysbus_init_mmio(dev, &s->mem_config2); - if (s->realview) { - isa_mmio_setup(&s->isa, 0x0100000); - sysbus_init_mmio(dev, &s->isa); + sysbus_init_mmio(sbd, &s->mem_config2); + + /* The window into I/O space is always into a fixed base address; + * its size is the same for both realview and versatile. + */ + memory_region_init_alias(&s->pci_io_window, "pci-vbp-io-window", + &s->pci_io_space, 0, 0x100000); + + sysbus_init_mmio(sbd, &s->pci_io_space); + + /* Create the alias regions corresponding to our three windows onto + * PCI memory space. The sizes vary from board to board; the base + * offsets are guest controllable via the IMAP registers. + */ + for (i = 0; i < 3; i++) { + memory_region_init_alias(&s->pci_mem_window[i], "pci-vbp-window", + &s->pci_mem_space, 0, s->mem_win_size[i]); + sysbus_init_mmio(sbd, &s->pci_mem_window[i]); } - pci_create_simple(bus, -1, "versatile_pci_host"); - return 0; -} - -static int pci_realview_init(SysBusDevice *dev) -{ - PCIVPBState *s = FROM_SYSBUS(PCIVPBState, dev); - s->realview = 1; - return pci_vpb_init(dev); + /* TODO Remove once realize propagates to child devices. */ + object_property_set_bool(OBJECT(&s->pci_dev), true, "realized", errp); } static int versatile_pci_host_init(PCIDevice *d) { pci_set_word(d->config + PCI_STATUS, - PCI_STATUS_66MHZ | PCI_STATUS_DEVSEL_MEDIUM); + PCI_STATUS_66MHZ | PCI_STATUS_DEVSEL_MEDIUM); pci_set_byte(d->config + PCI_LATENCY_TIMER, 0x10); return 0; } @@ -120,7 +417,7 @@ static void versatile_pci_host_class_init(ObjectClass *klass, void *data) } static const TypeInfo versatile_pci_host_info = { - .name = "versatile_pci_host", + .name = TYPE_VERSATILE_PCI_HOST, .parent = TYPE_PCI_DEVICE, .instance_size = sizeof(PCIDevice), .class_init = versatile_pci_host_class_init, @@ -128,30 +425,36 @@ static const TypeInfo versatile_pci_host_info = { static void pci_vpb_class_init(ObjectClass *klass, void *data) { - SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass); + DeviceClass *dc = DEVICE_CLASS(klass); - sdc->init = pci_vpb_init; + dc->realize = pci_vpb_realize; + dc->reset = pci_vpb_reset; + dc->vmsd = &pci_vpb_vmstate; } static const TypeInfo pci_vpb_info = { - .name = "versatile_pci", - .parent = TYPE_SYS_BUS_DEVICE, + .name = TYPE_VERSATILE_PCI, + .parent = TYPE_PCI_HOST_BRIDGE, .instance_size = sizeof(PCIVPBState), + .instance_init = pci_vpb_init, .class_init = pci_vpb_class_init, }; -static void pci_realview_class_init(ObjectClass *klass, void *data) +static void pci_realview_init(Object *obj) { - SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass); + PCIVPBState *s = PCI_VPB(obj); - sdc->init = pci_realview_init; + s->realview = 1; + /* The PCI window sizes are different on Realview boards */ + s->mem_win_size[0] = 0x01000000; + s->mem_win_size[1] = 0x04000000; + s->mem_win_size[2] = 0x08000000; } static const TypeInfo pci_realview_info = { .name = "realview_pci", - .parent = TYPE_SYS_BUS_DEVICE, - .instance_size = sizeof(PCIVPBState), - .class_init = pci_realview_class_init, + .parent = TYPE_VERSATILE_PCI, + .instance_init = pci_realview_init, }; static void versatile_pci_register_types(void)