diff options
author | Mark Cave-Ayland <mark.cave-ayland@ilande.co.uk> | 2014-05-28 08:28:22 +0100 |
---|---|---|
committer | Mark Cave-Ayland <mark.cave-ayland@ilande.co.uk> | 2014-06-05 21:00:22 +0100 |
commit | ae74bbe7c5b070d26852d6673759d47cd3569722 (patch) | |
tree | 20dc78d8b68be5d7fb69058d920f2640a6ff16b2 /hw/pci-host/apb.c | |
parent | f38b161203a4aa71853a3be60fda69b5f40f1bb3 (diff) |
apb: implement IOMMU translation for PCI host bridge
Signed-off-by: Mark Cave-Ayland <mark.cave-ayland@ilande.co.uk>
Diffstat (limited to 'hw/pci-host/apb.c')
-rw-r--r-- | hw/pci-host/apb.c | 165 |
1 files changed, 165 insertions, 0 deletions
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"); |