From e09c49f40de32620e99f67a71d4508c7fe97dd84 Mon Sep 17 00:00:00 2001 From: Mark Cave-Ayland Date: Sat, 24 May 2014 11:42:36 +0100 Subject: [PATCH 1/8] cg3: move initialisation from realizefn to initfn MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Initialisation cleanup as suggested by Andreas. Signed-off-by: Mark Cave-Ayland CC: Andreas Färber --- hw/display/cg3.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/hw/display/cg3.c b/hw/display/cg3.c index f5a8299e5e..cd9297defe 100644 --- a/hw/display/cg3.c +++ b/hw/display/cg3.c @@ -274,6 +274,20 @@ static const GraphicHwOps cg3_ops = { .gfx_update = cg3_update_display, }; +static void cg3_initfn(Object *obj) +{ + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + CG3State *s = CG3(obj); + + memory_region_init_ram(&s->rom, NULL, "cg3.prom", FCODE_MAX_ROM_SIZE); + memory_region_set_readonly(&s->rom, true); + sysbus_init_mmio(sbd, &s->rom); + + memory_region_init_io(&s->reg, NULL, &cg3_reg_ops, s, "cg3.reg", + CG3_REG_SIZE); + sysbus_init_mmio(sbd, &s->reg); +} + static void cg3_realizefn(DeviceState *dev, Error **errp) { SysBusDevice *sbd = SYS_BUS_DEVICE(dev); @@ -282,11 +296,7 @@ static void cg3_realizefn(DeviceState *dev, Error **errp) char *fcode_filename; /* FCode ROM */ - memory_region_init_ram(&s->rom, NULL, "cg3.prom", FCODE_MAX_ROM_SIZE); vmstate_register_ram_global(&s->rom); - memory_region_set_readonly(&s->rom, true); - sysbus_init_mmio(sbd, &s->rom); - fcode_filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, CG3_ROM_FILE); if (fcode_filename) { ret = load_image_targphys(fcode_filename, s->prom_addr, @@ -296,10 +306,6 @@ static void cg3_realizefn(DeviceState *dev, Error **errp) } } - memory_region_init_io(&s->reg, NULL, &cg3_reg_ops, s, "cg3.reg", - CG3_REG_SIZE); - sysbus_init_mmio(sbd, &s->reg); - memory_region_init_ram(&s->vram_mem, NULL, "cg3.vram", s->vram_size); vmstate_register_ram_global(&s->vram_mem); sysbus_init_mmio(sbd, &s->vram_mem); @@ -374,6 +380,7 @@ static const TypeInfo cg3_info = { .name = TYPE_CG3, .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(CG3State), + .instance_init = cg3_initfn, .class_init = cg3_class_init, }; From 366d4f7e0007a5540897fbac6e377c57d8c79a73 Mon Sep 17 00:00:00 2001 From: Mark Cave-Ayland Date: Sat, 24 May 2014 11:51:50 +0100 Subject: [PATCH 2/8] cg3: add extra check to prevent CG3 register array overflow The case statements in the CG3 read and write register routines have a maximum value of CG3_REG_SIZE, so if a value were written to this offset then it would overflow the register array. Currently this cannot be exploited since the MemoryRegion restricts accesses to the range 0 ... CG3_REG_SIZE - 1, but it seems worth clarifying this for future review and/or static analysis. Signed-off-by: Mark Cave-Ayland CC: Paolo Bonzini --- hw/display/cg3.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hw/display/cg3.c b/hw/display/cg3.c index cd9297defe..65ef7a7fe6 100644 --- a/hw/display/cg3.c +++ b/hw/display/cg3.c @@ -177,7 +177,7 @@ static uint64_t cg3_reg_read(void *opaque, hwaddr addr, unsigned size) /* monitor ID 6, board type = 1 (color) */ val = s->regs[1] | CG3_SR_1152_900_76_B | CG3_SR_ID_COLOR; break; - case CG3_REG_FBC_CURSTART ... CG3_REG_SIZE: + case CG3_REG_FBC_CURSTART ... CG3_REG_SIZE - 1: val = s->regs[addr - 0x10]; break; default: @@ -247,7 +247,7 @@ static void cg3_reg_write(void *opaque, hwaddr addr, uint64_t val, qemu_irq_lower(s->irq); } break; - case CG3_REG_FBC_CURSTART ... CG3_REG_SIZE: + case CG3_REG_FBC_CURSTART ... CG3_REG_SIZE - 1: s->regs[addr - 0x10] = val; break; default: From d4ad9dec14aef3a61a23c2787bb660d07c943f04 Mon Sep 17 00:00:00 2001 From: Mark Cave-Ayland Date: Sat, 24 May 2014 12:19:44 +0100 Subject: [PATCH 3/8] tcx: move initialisation from SysBusDevice class to TCX class realizefn MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is an intermediate step to bring TCX in line with CG3. Signed-off-by: Mark Cave-Ayland CC: Andreas Färber --- hw/display/tcx.c | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/hw/display/tcx.c b/hw/display/tcx.c index 2551b677aa..8fc4e38bcf 100644 --- a/hw/display/tcx.c +++ b/hw/display/tcx.c @@ -530,8 +530,9 @@ static const GraphicHwOps tcx24_ops = { .gfx_update = tcx24_update_display, }; -static int tcx_init1(SysBusDevice *dev) +static void tcx_realizefn(DeviceState *dev, Error **errp) { + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); TCXState *s = TCX(dev); ram_addr_t vram_offset = 0; int size, ret; @@ -547,15 +548,14 @@ static int tcx_init1(SysBusDevice *dev) memory_region_init_ram(&s->rom, NULL, "tcx.prom", FCODE_MAX_ROM_SIZE); vmstate_register_ram_global(&s->rom); memory_region_set_readonly(&s->rom, true); - sysbus_init_mmio(dev, &s->rom); + sysbus_init_mmio(sbd, &s->rom); fcode_filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, TCX_ROM_FILE); if (fcode_filename) { ret = load_image_targphys(fcode_filename, s->prom_addr, FCODE_MAX_ROM_SIZE); if (ret < 0 || ret > FCODE_MAX_ROM_SIZE) { - fprintf(stderr, "tcx: could not load prom '%s'\n", TCX_ROM_FILE); - return -1; + error_report("tcx: could not load prom '%s'", TCX_ROM_FILE); } } @@ -564,23 +564,23 @@ static int tcx_init1(SysBusDevice *dev) size = s->vram_size; memory_region_init_alias(&s->vram_8bit, OBJECT(s), "tcx.vram.8bit", &s->vram_mem, vram_offset, size); - sysbus_init_mmio(dev, &s->vram_8bit); + sysbus_init_mmio(sbd, &s->vram_8bit); vram_offset += size; vram_base += size; /* DAC */ memory_region_init_io(&s->dac, OBJECT(s), &tcx_dac_ops, s, "tcx.dac", TCX_DAC_NREGS); - sysbus_init_mmio(dev, &s->dac); + sysbus_init_mmio(sbd, &s->dac); /* TEC (dummy) */ memory_region_init_io(&s->tec, OBJECT(s), &dummy_ops, s, "tcx.tec", TCX_TEC_NREGS); - sysbus_init_mmio(dev, &s->tec); + sysbus_init_mmio(sbd, &s->tec); /* THC: NetBSD writes here even with 8-bit display: dummy */ memory_region_init_io(&s->thc24, OBJECT(s), &dummy_ops, s, "tcx.thc24", TCX_THC_NREGS_24); - sysbus_init_mmio(dev, &s->thc24); + sysbus_init_mmio(sbd, &s->thc24); if (s->depth == 24) { /* 24-bit plane */ @@ -589,7 +589,7 @@ static int tcx_init1(SysBusDevice *dev) s->vram24_offset = vram_offset; memory_region_init_alias(&s->vram_24bit, OBJECT(s), "tcx.vram.24bit", &s->vram_mem, vram_offset, size); - sysbus_init_mmio(dev, &s->vram_24bit); + sysbus_init_mmio(sbd, &s->vram_24bit); vram_offset += size; vram_base += size; @@ -599,20 +599,19 @@ static int tcx_init1(SysBusDevice *dev) s->cplane_offset = vram_offset; memory_region_init_alias(&s->vram_cplane, OBJECT(s), "tcx.vram.cplane", &s->vram_mem, vram_offset, size); - sysbus_init_mmio(dev, &s->vram_cplane); + sysbus_init_mmio(sbd, &s->vram_cplane); s->con = graphic_console_init(DEVICE(dev), 0, &tcx24_ops, s); } else { /* THC 8 bit (dummy) */ memory_region_init_io(&s->thc8, OBJECT(s), &dummy_ops, s, "tcx.thc8", TCX_THC_NREGS_8); - sysbus_init_mmio(dev, &s->thc8); + sysbus_init_mmio(sbd, &s->thc8); s->con = graphic_console_init(DEVICE(dev), 0, &tcx_ops, s); } qemu_console_resize(s->con, s->width, s->height); - return 0; } static Property tcx_properties[] = { @@ -627,9 +626,8 @@ static Property tcx_properties[] = { static void tcx_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); - SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); - k->init = tcx_init1; + dc->realize = tcx_realizefn; dc->reset = tcx_reset; dc->vmsd = &vmstate_tcx; dc->props = tcx_properties; From 01b91ac2be83e321853851437f69c0bc57ea4162 Mon Sep 17 00:00:00 2001 From: Mark Cave-Ayland Date: Sat, 24 May 2014 12:44:53 +0100 Subject: [PATCH 4/8] tcx: move initialisation from realizefn to initfn MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Initialisation cleanup as suggested by Andreas. Signed-off-by: Mark Cave-Ayland CC: Andreas Färber --- hw/display/tcx.c | 46 ++++++++++++++++++++++++++++------------------ hw/sparc/sun4m.c | 10 +++++----- 2 files changed, 33 insertions(+), 23 deletions(-) diff --git a/hw/display/tcx.c b/hw/display/tcx.c index 8fc4e38bcf..28c742cc24 100644 --- a/hw/display/tcx.c +++ b/hw/display/tcx.c @@ -530,6 +530,33 @@ static const GraphicHwOps tcx24_ops = { .gfx_update = tcx24_update_display, }; +static void tcx_initfn(Object *obj) +{ + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + TCXState *s = TCX(obj); + + memory_region_init_ram(&s->rom, NULL, "tcx.prom", FCODE_MAX_ROM_SIZE); + memory_region_set_readonly(&s->rom, true); + sysbus_init_mmio(sbd, &s->rom); + + /* DAC */ + memory_region_init_io(&s->dac, OBJECT(s), &tcx_dac_ops, s, + "tcx.dac", TCX_DAC_NREGS); + sysbus_init_mmio(sbd, &s->dac); + + /* TEC (dummy) */ + memory_region_init_io(&s->tec, OBJECT(s), &dummy_ops, s, + "tcx.tec", TCX_TEC_NREGS); + sysbus_init_mmio(sbd, &s->tec); + + /* THC: NetBSD writes here even with 8-bit display: dummy */ + memory_region_init_io(&s->thc24, OBJECT(s), &dummy_ops, s, "tcx.thc24", + TCX_THC_NREGS_24); + sysbus_init_mmio(sbd, &s->thc24); + + return; +} + static void tcx_realizefn(DeviceState *dev, Error **errp) { SysBusDevice *sbd = SYS_BUS_DEVICE(dev); @@ -545,11 +572,7 @@ static void tcx_realizefn(DeviceState *dev, Error **errp) vram_base = memory_region_get_ram_ptr(&s->vram_mem); /* FCode ROM */ - memory_region_init_ram(&s->rom, NULL, "tcx.prom", FCODE_MAX_ROM_SIZE); vmstate_register_ram_global(&s->rom); - memory_region_set_readonly(&s->rom, true); - sysbus_init_mmio(sbd, &s->rom); - fcode_filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, TCX_ROM_FILE); if (fcode_filename) { ret = load_image_targphys(fcode_filename, s->prom_addr, @@ -568,20 +591,6 @@ static void tcx_realizefn(DeviceState *dev, Error **errp) vram_offset += size; vram_base += size; - /* DAC */ - memory_region_init_io(&s->dac, OBJECT(s), &tcx_dac_ops, s, - "tcx.dac", TCX_DAC_NREGS); - sysbus_init_mmio(sbd, &s->dac); - - /* TEC (dummy) */ - memory_region_init_io(&s->tec, OBJECT(s), &dummy_ops, s, - "tcx.tec", TCX_TEC_NREGS); - sysbus_init_mmio(sbd, &s->tec); - /* THC: NetBSD writes here even with 8-bit display: dummy */ - memory_region_init_io(&s->thc24, OBJECT(s), &dummy_ops, s, "tcx.thc24", - TCX_THC_NREGS_24); - sysbus_init_mmio(sbd, &s->thc24); - if (s->depth == 24) { /* 24-bit plane */ size = s->vram_size * 4; @@ -637,6 +646,7 @@ static const TypeInfo tcx_info = { .name = TYPE_TCX, .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(TCXState), + .instance_init = tcx_initfn, .class_init = tcx_class_init, }; diff --git a/hw/sparc/sun4m.c b/hw/sparc/sun4m.c index 4e793c2760..67e3663bfd 100644 --- a/hw/sparc/sun4m.c +++ b/hw/sparc/sun4m.c @@ -543,14 +543,14 @@ static void tcx_init(hwaddr addr, int vram_size, int width, s = SYS_BUS_DEVICE(dev); /* FCode ROM */ sysbus_mmio_map(s, 0, addr); - /* 8-bit plane */ - sysbus_mmio_map(s, 1, addr + 0x00800000ULL); /* DAC */ - sysbus_mmio_map(s, 2, addr + 0x00200000ULL); + sysbus_mmio_map(s, 1, addr + 0x00200000ULL); /* TEC (dummy) */ - sysbus_mmio_map(s, 3, addr + 0x00700000ULL); + sysbus_mmio_map(s, 2, addr + 0x00700000ULL); /* THC 24 bit: NetBSD writes here even with 8-bit display: dummy */ - sysbus_mmio_map(s, 4, addr + 0x00301000ULL); + sysbus_mmio_map(s, 3, addr + 0x00301000ULL); + /* 8-bit plane */ + sysbus_mmio_map(s, 4, addr + 0x00800000ULL); if (depth == 24) { /* 24-bit plane */ sysbus_mmio_map(s, 5, addr + 0x02000000ULL); From ea9a6606b1559baaf4ddeba3cdce9858055f4044 Mon Sep 17 00:00:00 2001 From: Mark Cave-Ayland Date: Wed, 28 May 2014 08:28:21 +0100 Subject: [PATCH 5/8] apb: Move IOMMU registers into a separate IOMMUState struct Signed-off-by: Mark Cave-Ayland --- hw/pci-host/apb.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/hw/pci-host/apb.c b/hw/pci-host/apb.c index 252caefda7..bea7092eeb 100644 --- a/hw/pci-host/apb.c +++ b/hw/pci-host/apb.c @@ -70,6 +70,10 @@ do { printf("APB: " fmt , ## __VA_ARGS__); } while (0) #define MAX_IVEC 0x40 #define NO_IRQ_REQUEST (MAX_IVEC + 1) +typedef struct IOMMUState { + uint32_t regs[4]; +} IOMMUState; + #define TYPE_APB "pbm" #define APB_DEVICE(obj) \ @@ -83,7 +87,7 @@ typedef struct APBState { MemoryRegion pci_mmio; MemoryRegion pci_ioport; uint64_t pci_irq_in; - uint32_t iommu[4]; + IOMMUState iommu; uint32_t pci_control[16]; uint32_t pci_irq_map[8]; uint32_t obio_irq_map[32]; @@ -145,6 +149,7 @@ static void apb_config_writel (void *opaque, hwaddr addr, uint64_t val, unsigned size) { APBState *s = opaque; + IOMMUState *is = &s->iommu; APB_DPRINTF("%s: addr " TARGET_FMT_plx " val %" PRIx64 "\n", __func__, addr, val); @@ -153,7 +158,7 @@ static void apb_config_writel (void *opaque, hwaddr addr, /* XXX: not implemented yet */ break; case 0x200 ... 0x20b: /* IOMMU */ - s->iommu[(addr & 0xf) >> 2] = val; + is->regs[(addr & 0xf) >> 2] = val; break; case 0x20c ... 0x3ff: /* IOMMU flush */ break; @@ -228,6 +233,7 @@ static uint64_t apb_config_readl (void *opaque, hwaddr addr, unsigned size) { APBState *s = opaque; + IOMMUState *is = &s->iommu; uint32_t val; switch (addr & 0xffff) { @@ -236,7 +242,7 @@ static uint64_t apb_config_readl (void *opaque, /* XXX: not implemented yet */ break; case 0x200 ... 0x20b: /* IOMMU */ - val = s->iommu[(addr & 0xf) >> 2]; + val = is->regs[(addr & 0xf) >> 2]; break; case 0x20c ... 0x3ff: /* IOMMU flush */ val = 0; @@ -390,6 +396,7 @@ PCIBus *pci_apb_init(hwaddr special_base, SysBusDevice *s; PCIHostState *phb; APBState *d; + IOMMUState *is; PCIDevice *pci_dev; PCIBridge *br; @@ -420,6 +427,10 @@ PCIBus *pci_apb_init(hwaddr special_base, pci_create_simple(phb->bus, 0, "pbm-pci"); + /* APB IOMMU */ + is = &d->iommu; + memset(is, 0, sizeof(IOMMUState)); + /* APB secondary busses */ pci_dev = pci_create_multifunction(phb->bus, PCI_DEVFN(1, 0), true, "pbm-bridge"); From fd7fbc8ff713ebf8fa2ae5078f1024079bde90b1 Mon Sep 17 00:00:00 2001 From: Mark Cave-Ayland Date: Wed, 28 May 2014 08:28:21 +0100 Subject: [PATCH 6/8] apb: fix IOMMU register sizes According to the referenced documentation, the IOMMU has 3 64-bit registers consisting of a control register, base register and flush register. Signed-off-by: Mark Cave-Ayland --- hw/pci-host/apb.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/hw/pci-host/apb.c b/hw/pci-host/apb.c index bea7092eeb..e25791fcdd 100644 --- a/hw/pci-host/apb.c +++ b/hw/pci-host/apb.c @@ -71,7 +71,7 @@ do { printf("APB: " fmt , ## __VA_ARGS__); } while (0) #define NO_IRQ_REQUEST (MAX_IVEC + 1) typedef struct IOMMUState { - uint32_t regs[4]; + uint32_t regs[6]; } IOMMUState; #define TYPE_APB "pbm" @@ -157,11 +157,9 @@ static void apb_config_writel (void *opaque, hwaddr addr, case 0x30 ... 0x4f: /* DMA error registers */ /* XXX: not implemented yet */ break; - case 0x200 ... 0x20b: /* IOMMU */ + case 0x200 ... 0x217: /* IOMMU */ is->regs[(addr & 0xf) >> 2] = val; break; - case 0x20c ... 0x3ff: /* IOMMU flush */ - break; case 0xc00 ... 0xc3f: /* PCI interrupt control */ if (addr & 4) { unsigned int ino = (addr & 0x3f) >> 3; @@ -241,12 +239,9 @@ static uint64_t apb_config_readl (void *opaque, val = 0; /* XXX: not implemented yet */ break; - case 0x200 ... 0x20b: /* IOMMU */ + case 0x200 ... 0x217: /* IOMMU */ val = is->regs[(addr & 0xf) >> 2]; break; - case 0x20c ... 0x3ff: /* IOMMU flush */ - val = 0; - break; case 0xc00 ... 0xc3f: /* PCI interrupt control */ if (addr & 4) { val = s->pci_irq_map[(addr & 0x3f) >> 3]; From f38b161203a4aa71853a3be60fda69b5f40f1bb3 Mon Sep 17 00:00:00 2001 From: Mark Cave-Ayland Date: Wed, 28 May 2014 08:28:22 +0100 Subject: [PATCH 7/8] apb: handle reading/writing of IOMMU control registers While the registers are documented as being 64-bit, Linux seems to access them in two halves as 2 x 32-bit accesses. Make sure that we can correctly handle this case. Signed-off-by: Mark Cave-Ayland --- hw/pci-host/apb.c | 103 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 100 insertions(+), 3 deletions(-) diff --git a/hw/pci-host/apb.c b/hw/pci-host/apb.c index e25791fcdd..b57b034ea5 100644 --- a/hw/pci-host/apb.c +++ b/hw/pci-host/apb.c @@ -46,6 +46,16 @@ do { printf("APB: " fmt , ## __VA_ARGS__); } while (0) #define APB_DPRINTF(fmt, ...) #endif +/* debug IOMMU */ +//#define DEBUG_IOMMU + +#ifdef DEBUG_IOMMU +#define IOMMU_DPRINTF(fmt, ...) \ +do { printf("IOMMU: " fmt , ## __VA_ARGS__); } while (0) +#else +#define IOMMU_DPRINTF(fmt, ...) +#endif + /* * Chipset docs: * PBM: "UltraSPARC IIi User's Manual", @@ -70,8 +80,12 @@ do { printf("APB: " fmt , ## __VA_ARGS__); } while (0) #define MAX_IVEC 0x40 #define NO_IRQ_REQUEST (MAX_IVEC + 1) +#define IOMMU_NREGS 3 +#define IOMMU_CTRL 0x0 +#define IOMMU_BASE 0x8 + typedef struct IOMMUState { - uint32_t regs[6]; + uint64_t regs[IOMMU_NREGS]; } IOMMUState; #define TYPE_APB "pbm" @@ -145,6 +159,89 @@ static inline void pbm_clear_request(APBState *s, unsigned int irq_num) s->irq_request = NO_IRQ_REQUEST; } +static void iommu_config_write(void *opaque, hwaddr addr, + uint64_t val, unsigned size) +{ + IOMMUState *is = opaque; + + IOMMU_DPRINTF("IOMMU config write: 0x%" HWADDR_PRIx " val: %" PRIx64 + " size: %d\n", addr, val, size); + + switch (addr) { + case IOMMU_CTRL: + if (size == 4) { + is->regs[IOMMU_CTRL >> 3] &= 0xffffffffULL; + is->regs[IOMMU_CTRL >> 3] |= val << 32; + } else { + is->regs[IOMMU_CTRL] = val; + } + break; + case IOMMU_CTRL + 0x4: + is->regs[IOMMU_CTRL >> 3] &= 0xffffffff00000000ULL; + is->regs[IOMMU_CTRL >> 3] |= val & 0xffffffffULL; + break; + case IOMMU_BASE: + if (size == 4) { + is->regs[IOMMU_BASE >> 3] &= 0xffffffffULL; + is->regs[IOMMU_BASE >> 3] |= val << 32; + } else { + is->regs[IOMMU_BASE] = val; + } + break; + case IOMMU_BASE + 0x4: + is->regs[IOMMU_BASE >> 3] &= 0xffffffff00000000ULL; + is->regs[IOMMU_BASE >> 3] |= val & 0xffffffffULL; + break; + default: + qemu_log_mask(LOG_UNIMP, + "apb iommu: Unimplemented register write " + "reg 0x%" HWADDR_PRIx " size 0x%x value 0x%" PRIx64 "\n", + addr, size, val); + break; + } +} + +static uint64_t iommu_config_read(void *opaque, hwaddr addr, unsigned size) +{ + IOMMUState *is = opaque; + uint64_t val; + + switch (addr) { + case IOMMU_CTRL: + if (size == 4) { + val = is->regs[IOMMU_CTRL >> 3] >> 32; + } else { + val = is->regs[IOMMU_CTRL >> 3]; + } + break; + case IOMMU_CTRL + 0x4: + val = is->regs[IOMMU_CTRL >> 3] & 0xffffffffULL; + break; + case IOMMU_BASE: + if (size == 4) { + val = is->regs[IOMMU_BASE >> 3] >> 32; + } else { + val = is->regs[IOMMU_BASE >> 3]; + } + break; + case IOMMU_BASE + 0x4: + val = is->regs[IOMMU_BASE >> 3] & 0xffffffffULL; + break; + default: + qemu_log_mask(LOG_UNIMP, + "apb iommu: Unimplemented register read " + "reg 0x%" HWADDR_PRIx " size 0x%x\n", + addr, size); + val = 0; + break; + } + + IOMMU_DPRINTF("IOMMU config read: 0x%" HWADDR_PRIx " val: %" PRIx64 + " size: %d\n", addr, val, size); + + return val; +} + static void apb_config_writel (void *opaque, hwaddr addr, uint64_t val, unsigned size) { @@ -158,7 +255,7 @@ static void apb_config_writel (void *opaque, hwaddr addr, /* XXX: not implemented yet */ break; case 0x200 ... 0x217: /* IOMMU */ - is->regs[(addr & 0xf) >> 2] = val; + iommu_config_write(is, (addr & 0xf), val, size); break; case 0xc00 ... 0xc3f: /* PCI interrupt control */ if (addr & 4) { @@ -240,7 +337,7 @@ static uint64_t apb_config_readl (void *opaque, /* XXX: not implemented yet */ break; case 0x200 ... 0x217: /* IOMMU */ - val = is->regs[(addr & 0xf) >> 2]; + val = iommu_config_read(is, (addr & 0xf), size); break; case 0xc00 ... 0xc3f: /* PCI interrupt control */ if (addr & 4) { From ae74bbe7c5b070d26852d6673759d47cd3569722 Mon Sep 17 00:00:00 2001 From: Mark Cave-Ayland Date: Wed, 28 May 2014 08:28:22 +0100 Subject: [PATCH 8/8] apb: implement IOMMU translation for PCI host bridge Signed-off-by: Mark Cave-Ayland --- hw/pci-host/apb.c | 165 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) diff --git a/hw/pci-host/apb.c b/hw/pci-host/apb.c index b57b034ea5..1497008258 100644 --- a/hw/pci-host/apb.c +++ b/hw/pci-host/apb.c @@ -80,11 +80,48 @@ do { printf("IOMMU: " fmt , ## __VA_ARGS__); } while (0) #define MAX_IVEC 0x40 #define NO_IRQ_REQUEST (MAX_IVEC + 1) +#define IOMMU_PAGE_SIZE_8K (1ULL << 13) +#define IOMMU_PAGE_MASK_8K (~(IOMMU_PAGE_SIZE_8K - 1)) +#define IOMMU_PAGE_SIZE_64K (1ULL << 16) +#define IOMMU_PAGE_MASK_64K (~(IOMMU_PAGE_SIZE_64K - 1)) + #define IOMMU_NREGS 3 + #define IOMMU_CTRL 0x0 +#define IOMMU_CTRL_TBW_SIZE (1ULL << 2) +#define IOMMU_CTRL_MMU_EN (1ULL) + +#define IOMMU_CTRL_TSB_SHIFT 16 + #define IOMMU_BASE 0x8 +#define IOMMU_TTE_DATA_V (1ULL << 63) +#define IOMMU_TTE_DATA_SIZE (1ULL << 61) +#define IOMMU_TTE_DATA_W (1ULL << 1) + +#define IOMMU_TTE_PHYS_MASK_8K 0x1ffffffe000 +#define IOMMU_TTE_PHYS_MASK_64K 0x1ffffff8000 + +#define IOMMU_TSB_8K_OFFSET_MASK_8M 0x00000000007fe000ULL +#define IOMMU_TSB_8K_OFFSET_MASK_16M 0x0000000000ffe000ULL +#define IOMMU_TSB_8K_OFFSET_MASK_32M 0x0000000001ffe000ULL +#define IOMMU_TSB_8K_OFFSET_MASK_64M 0x0000000003ffe000ULL +#define IOMMU_TSB_8K_OFFSET_MASK_128M 0x0000000007ffe000ULL +#define IOMMU_TSB_8K_OFFSET_MASK_256M 0x000000000fffe000ULL +#define IOMMU_TSB_8K_OFFSET_MASK_512M 0x000000001fffe000ULL +#define IOMMU_TSB_8K_OFFSET_MASK_1G 0x000000003fffe000ULL + +#define IOMMU_TSB_64K_OFFSET_MASK_64M 0x0000000003ff0000ULL +#define IOMMU_TSB_64K_OFFSET_MASK_128M 0x0000000007ff0000ULL +#define IOMMU_TSB_64K_OFFSET_MASK_256M 0x000000000fff0000ULL +#define IOMMU_TSB_64K_OFFSET_MASK_512M 0x000000001fff0000ULL +#define IOMMU_TSB_64K_OFFSET_MASK_1G 0x000000003fff0000ULL +#define IOMMU_TSB_64K_OFFSET_MASK_2G 0x000000007fff0000ULL + typedef struct IOMMUState { + AddressSpace iommu_as; + MemoryRegion iommu; + uint64_t regs[IOMMU_NREGS]; } IOMMUState; @@ -159,6 +196,129 @@ static inline void pbm_clear_request(APBState *s, unsigned int irq_num) s->irq_request = NO_IRQ_REQUEST; } +static AddressSpace *pbm_pci_dma_iommu(PCIBus *bus, void *opaque, int devfn) +{ + IOMMUState *is = opaque; + + return &is->iommu_as; +} + +static IOMMUTLBEntry pbm_translate_iommu(MemoryRegion *iommu, hwaddr addr) +{ + IOMMUState *is = container_of(iommu, IOMMUState, iommu); + hwaddr baseaddr, offset; + uint64_t tte; + uint32_t tsbsize; + IOMMUTLBEntry ret = { + .target_as = &address_space_memory, + .iova = 0, + .translated_addr = 0, + .addr_mask = ~(hwaddr)0, + .perm = IOMMU_NONE, + }; + + if (!(is->regs[IOMMU_CTRL >> 3] & IOMMU_CTRL_MMU_EN)) { + /* IOMMU disabled, passthrough using standard 8K page */ + ret.iova = addr & IOMMU_PAGE_MASK_8K; + ret.translated_addr = addr; + ret.addr_mask = IOMMU_PAGE_MASK_8K; + ret.perm = IOMMU_RW; + + return ret; + } + + baseaddr = is->regs[IOMMU_BASE >> 3]; + tsbsize = (is->regs[IOMMU_CTRL >> 3] >> IOMMU_CTRL_TSB_SHIFT) & 0x7; + + if (is->regs[IOMMU_CTRL >> 3] & IOMMU_CTRL_TBW_SIZE) { + /* 64K */ + switch (tsbsize) { + case 0: + offset = (addr & IOMMU_TSB_64K_OFFSET_MASK_64M) >> 13; + break; + case 1: + offset = (addr & IOMMU_TSB_64K_OFFSET_MASK_128M) >> 13; + break; + case 2: + offset = (addr & IOMMU_TSB_64K_OFFSET_MASK_256M) >> 13; + break; + case 3: + offset = (addr & IOMMU_TSB_64K_OFFSET_MASK_512M) >> 13; + break; + case 4: + offset = (addr & IOMMU_TSB_64K_OFFSET_MASK_1G) >> 13; + break; + case 5: + offset = (addr & IOMMU_TSB_64K_OFFSET_MASK_2G) >> 13; + break; + default: + /* Not implemented, error */ + return ret; + } + } else { + /* 8K */ + switch (tsbsize) { + case 0: + offset = (addr & IOMMU_TSB_8K_OFFSET_MASK_8M) >> 10; + break; + case 1: + offset = (addr & IOMMU_TSB_8K_OFFSET_MASK_16M) >> 10; + break; + case 2: + offset = (addr & IOMMU_TSB_8K_OFFSET_MASK_32M) >> 10; + break; + case 3: + offset = (addr & IOMMU_TSB_8K_OFFSET_MASK_64M) >> 10; + break; + case 4: + offset = (addr & IOMMU_TSB_8K_OFFSET_MASK_128M) >> 10; + break; + case 5: + offset = (addr & IOMMU_TSB_8K_OFFSET_MASK_256M) >> 10; + break; + case 6: + offset = (addr & IOMMU_TSB_8K_OFFSET_MASK_512M) >> 10; + break; + case 7: + offset = (addr & IOMMU_TSB_8K_OFFSET_MASK_1G) >> 10; + break; + } + } + + tte = ldq_be_phys(&address_space_memory, baseaddr + offset); + + if (!(tte & IOMMU_TTE_DATA_V)) { + /* Invalid mapping */ + return ret; + } + + if (tte & IOMMU_TTE_DATA_W) { + /* Writeable */ + ret.perm = IOMMU_RW; + } else { + ret.perm = IOMMU_RO; + } + + /* Extract phys */ + if (tte & IOMMU_TTE_DATA_SIZE) { + /* 64K */ + ret.iova = addr & IOMMU_PAGE_MASK_64K; + ret.translated_addr = tte & IOMMU_TTE_PHYS_MASK_64K; + ret.addr_mask = (IOMMU_PAGE_SIZE_64K - 1); + } else { + /* 8K */ + ret.iova = addr & IOMMU_PAGE_MASK_8K; + ret.translated_addr = tte & IOMMU_TTE_PHYS_MASK_8K; + ret.addr_mask = (IOMMU_PAGE_SIZE_8K - 1); + } + + return ret; +} + +static MemoryRegionIOMMUOps pbm_iommu_ops = { + .translate = pbm_translate_iommu, +}; + static void iommu_config_write(void *opaque, hwaddr addr, uint64_t val, unsigned size) { @@ -523,6 +683,11 @@ PCIBus *pci_apb_init(hwaddr special_base, is = &d->iommu; memset(is, 0, sizeof(IOMMUState)); + memory_region_init_iommu(&is->iommu, OBJECT(dev), &pbm_iommu_ops, + "iommu-apb", UINT64_MAX); + address_space_init(&is->iommu_as, &is->iommu, "pbm-as"); + pci_setup_iommu(phb->bus, pbm_pci_dma_iommu, is); + /* APB secondary busses */ pci_dev = pci_create_multifunction(phb->bus, PCI_DEVFN(1, 0), true, "pbm-bridge");