From: Eduard - Gabriel Munteanu <eduard.munteanu@linux360.ro> To: mst@redhat.com Cc: joro@8bytes.org, blauwirbel@gmail.com, paul@codesourcery.com, avi@redhat.com, anthony@codemonkey.ws, av1474@comtv.ru, yamahata@valinux.co.jp, kvm@vger.kernel.org, qemu-devel@nongnu.org, Eduard - Gabriel Munteanu <eduard.munteanu@linux360.ro> Subject: [PATCH 3/7] AMD IOMMU emulation Date: Sat, 28 Aug 2010 17:54:54 +0300 [thread overview] Message-ID: <1283007298-10942-4-git-send-email-eduard.munteanu@linux360.ro> (raw) In-Reply-To: <1283007298-10942-1-git-send-email-eduard.munteanu@linux360.ro> This introduces emulation for the AMD IOMMU, described in "AMD I/O Virtualization Technology (IOMMU) Specification". Signed-off-by: Eduard - Gabriel Munteanu <eduard.munteanu@linux360.ro> --- Makefile.target | 2 +- hw/amd_iommu.c | 663 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ hw/pc.c | 2 + hw/pci_ids.h | 2 + hw/pci_regs.h | 1 + 5 files changed, 669 insertions(+), 1 deletions(-) create mode 100644 hw/amd_iommu.c diff --git a/Makefile.target b/Makefile.target index 3ef4666..d4eeccd 100644 --- a/Makefile.target +++ b/Makefile.target @@ -195,7 +195,7 @@ obj-i386-y += cirrus_vga.o apic.o ioapic.o piix_pci.o obj-i386-y += vmmouse.o vmport.o hpet.o applesmc.o obj-i386-y += device-hotplug.o pci-hotplug.o smbios.o wdt_ib700.o obj-i386-y += debugcon.o multiboot.o -obj-i386-y += pc_piix.o +obj-i386-y += pc_piix.o amd_iommu.o # shared objects obj-ppc-y = ppc.o diff --git a/hw/amd_iommu.c b/hw/amd_iommu.c new file mode 100644 index 0000000..43e0426 --- /dev/null +++ b/hw/amd_iommu.c @@ -0,0 +1,663 @@ +/* + * AMD IOMMU emulation + * + * Copyright (c) 2010 Eduard - Gabriel Munteanu + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "pc.h" +#include "hw.h" +#include "pci.h" +#include "qlist.h" + +/* Capability registers */ +#define CAPAB_HEADER 0x00 +#define CAPAB_REV_TYPE 0x02 +#define CAPAB_FLAGS 0x03 +#define CAPAB_BAR_LOW 0x04 +#define CAPAB_BAR_HIGH 0x08 +#define CAPAB_RANGE 0x0C +#define CAPAB_MISC 0x10 + +#define CAPAB_SIZE 0x14 +#define CAPAB_REG_SIZE 0x04 + +/* Capability header data */ +#define CAPAB_FLAG_IOTLBSUP (1 << 0) +#define CAPAB_FLAG_HTTUNNEL (1 << 1) +#define CAPAB_FLAG_NPCACHE (1 << 2) +#define CAPAB_INIT_REV (1 << 3) +#define CAPAB_INIT_TYPE 3 +#define CAPAB_INIT_REV_TYPE (CAPAB_REV | CAPAB_TYPE) +#define CAPAB_INIT_FLAGS (CAPAB_FLAG_NPCACHE | CAPAB_FLAG_HTTUNNEL) +#define CAPAB_INIT_MISC (64 << 15) | (48 << 8) +#define CAPAB_BAR_MASK ~((1UL << 14) - 1) + +/* MMIO registers */ +#define MMIO_DEVICE_TABLE 0x0000 +#define MMIO_COMMAND_BASE 0x0008 +#define MMIO_EVENT_BASE 0x0010 +#define MMIO_CONTROL 0x0018 +#define MMIO_EXCL_BASE 0x0020 +#define MMIO_EXCL_LIMIT 0x0028 +#define MMIO_COMMAND_HEAD 0x2000 +#define MMIO_COMMAND_TAIL 0x2008 +#define MMIO_EVENT_HEAD 0x2010 +#define MMIO_EVENT_TAIL 0x2018 +#define MMIO_STATUS 0x2020 + +#define MMIO_SIZE 0x4000 + +#define MMIO_DEVTAB_SIZE_MASK ((1ULL << 12) - 1) +#define MMIO_DEVTAB_BASE_MASK (((1ULL << 52) - 1) & ~MMIO_DEVTAB_SIZE_MASK) +#define MMIO_DEVTAB_ENTRY_SIZE 32 +#define MMIO_DEVTAB_SIZE_UNIT 4096 + +#define MMIO_CMDBUF_SIZE_BYTE (MMIO_COMMAND_BASE + 7) +#define MMIO_CMDBUF_SIZE_MASK 0x0F +#define MMIO_CMDBUF_BASE_MASK MMIO_DEVTAB_BASE_MASK +#define MMIO_CMDBUF_DEFAULT_SIZE 8 +#define MMIO_CMDBUF_HEAD_MASK (((1ULL << 19) - 1) & ~0x0F) +#define MMIO_CMDBUF_TAIL_MASK MMIO_EVTLOG_HEAD_MASK + +#define MMIO_EVTLOG_SIZE_BYTE (MMIO_EVENT_BASE + 7) +#define MMIO_EVTLOG_SIZE_MASK MMIO_CMDBUF_SIZE_MASK +#define MMIO_EVTLOG_BASE_MASK MMIO_CMDBUF_BASE_MASK +#define MMIO_EVTLOG_DEFAULT_SIZE MMIO_CMDBUF_DEFAULT_SIZE +#define MMIO_EVTLOG_HEAD_MASK (((1ULL << 19) - 1) & ~0x0F) +#define MMIO_EVTLOG_TAIL_MASK MMIO_EVTLOG_HEAD_MASK + +#define MMIO_EXCL_BASE_MASK MMIO_DEVTAB_BASE_MASK +#define MMIO_EXCL_ENABLED_MASK (1ULL << 0) +#define MMIO_EXCL_ALLOW_MASK (1ULL << 1) +#define MMIO_EXCL_LIMIT_MASK MMIO_DEVTAB_BASE_MASK +#define MMIO_EXCL_LIMIT_LOW 0xFFF + +#define MMIO_CONTROL_IOMMUEN (1ULL << 0) +#define MMIO_CONTROL_HTTUNEN (1ULL << 1) +#define MMIO_CONTROL_EVENTLOGEN (1ULL << 2) +#define MMIO_CONTROL_EVENTINTEN (1ULL << 3) +#define MMIO_CONTROL_COMWAITINTEN (1ULL << 4) +#define MMIO_CONTROL_CMDBUFEN (1ULL << 12) + +#define MMIO_STATUS_EVTLOG_OF (1ULL << 0) +#define MMIO_STATUS_EVTLOG_INTR (1ULL << 1) +#define MMIO_STATUS_COMWAIT_INTR (1ULL << 2) +#define MMIO_STATUS_EVTLOG_RUN (1ULL << 3) +#define MMIO_STATUS_CMDBUF_RUN (1ULL << 4) + +#define CMDBUF_ID_BYTE 0x07 +#define CMDBUF_ID_RSHIFT 4 +#define CMDBUF_ENTRY_SIZE 0x10 + +#define CMD_COMPLETION_WAIT 0x01 +#define CMD_INVAL_DEVTAB_ENTRY 0x02 +#define CMD_INVAL_IOMMU_PAGES 0x03 +#define CMD_INVAL_IOTLB_PAGES 0x04 +#define CMD_INVAL_INTR_TABLE 0x05 + +#define DEVTAB_ENTRY_SIZE 32 + +/* Device table entry bits 0:63 */ +#define DEV_VALID (1ULL << 0) +#define DEV_TRANSLATION_VALID (1ULL << 1) +#define DEV_MODE_MASK 0x7 +#define DEV_MODE_RSHIFT 9 +#define DEV_PT_ROOT_MASK 0xFFFFFFFFFF000 +#define DEV_PT_ROOT_RSHIFT 12 +#define DEV_PERM_SHIFT 61 +#define DEV_PERM_READ (1ULL << 61) +#define DEV_PERM_WRITE (1ULL << 62) + +/* Device table entry bits 64:127 */ +#define DEV_DOMAIN_ID_MASK ((1ULL << 16) - 1) +#define DEV_IOTLB_SUPPORT (1ULL << 17) +#define DEV_SUPPRESS_PF (1ULL << 18) +#define DEV_SUPPRESS_ALL_PF (1ULL << 19) +#define DEV_IOCTL_MASK ~3 +#define DEV_IOCTL_RSHIFT 20 +#define DEV_IOCTL_DENY 0 +#define DEV_IOCTL_PASSTHROUGH 1 +#define DEV_IOCTL_TRANSLATE 2 +#define DEV_CACHE (1ULL << 37) +#define DEV_SNOOP_DISABLE (1ULL << 38) +#define DEV_EXCL (1ULL << 39) + +/* Event codes and flags, as stored in the info field */ +#define EVENT_ILLEGAL_DEVTAB_ENTRY (0x1U << 24) +#define EVENT_IOPF (0x2U << 24) +#define EVENT_IOPF_I (1U << 3) +#define EVENT_IOPF_PR (1U << 4) +#define EVENT_IOPF_RW (1U << 5) +#define EVENT_IOPF_PE (1U << 6) +#define EVENT_IOPF_RZ (1U << 7) +#define EVENT_IOPF_TR (1U << 8) +#define EVENT_DEV_TAB_HW_ERROR (0x3U << 24) +#define EVENT_PAGE_TAB_HW_ERROR (0x4U << 24) +#define EVENT_ILLEGAL_COMMAND_ERROR (0x5U << 24) +#define EVENT_COMMAND_HW_ERROR (0x6U << 24) +#define EVENT_IOTLB_INV_TIMEOUT (0x7U << 24) +#define EVENT_INVALID_DEV_REQUEST (0x8U << 24) + +#define EVENT_LEN 16 + +typedef struct AMDIOMMUState { + PCIDevice dev; + + int capab_offset; + unsigned char *capab; + + int mmio_index; + target_phys_addr_t mmio_addr; + unsigned char *mmio_buf; + int mmio_enabled; + + int enabled; + int ats_enabled; + + target_phys_addr_t devtab; + size_t devtab_len; + + target_phys_addr_t cmdbuf; + int cmdbuf_enabled; + size_t cmdbuf_len; + size_t cmdbuf_head; + size_t cmdbuf_tail; + int completion_wait_intr; + + target_phys_addr_t evtlog; + int evtlog_enabled; + int evtlog_intr; + target_phys_addr_t evtlog_len; + target_phys_addr_t evtlog_head; + target_phys_addr_t evtlog_tail; + + target_phys_addr_t excl_base; + target_phys_addr_t excl_limit; + int excl_enabled; + int excl_allow; +} AMDIOMMUState; + +typedef struct AMDIOMMUEvent { + uint16_t devfn; + uint16_t reserved; + uint16_t domid; + uint16_t info; + uint64_t addr; +} __attribute__((packed)) AMDIOMMUEvent; + +static void amd_iommu_completion_wait(AMDIOMMUState *st, + uint8_t *cmd) +{ + uint64_t addr; + + if (cmd[0] & 1) { + addr = le64_to_cpu(*(uint64_t *) cmd) & 0xFFFFFFFFFFFF8; + cpu_physical_memory_write(addr, cmd + 8, 8); + } + + if (cmd[0] & 2) + st->mmio_buf[MMIO_STATUS] |= MMIO_STATUS_COMWAIT_INTR; +} + +static void amd_iommu_invalidate_iotlb(AMDIOMMUState *st, + uint8_t *cmd) +{ + PCIDevice *dev; + PCIBus *bus = st->dev.bus; + int bus_num = pci_bus_num(bus); + int devfn = *(uint16_t *) cmd; + + dev = pci_find_device(bus, bus_num, PCI_SLOT(devfn), PCI_FUNC(devfn)); + if (dev) { + pci_memory_invalidate_range(dev, 0, -1); + } +} + +static void amd_iommu_cmdbuf_run(AMDIOMMUState *st) +{ + uint8_t cmd[16]; + int type; + + if (!st->cmdbuf_enabled) { + return; + } + + /* Check if there's work to do. */ + if (st->cmdbuf_head == st->cmdbuf_tail) { + return; + } + + cpu_physical_memory_read(st->cmdbuf + st->cmdbuf_head, cmd, 16); + type = cmd[CMDBUF_ID_BYTE] >> CMDBUF_ID_RSHIFT; + switch (type) { + case CMD_COMPLETION_WAIT: + amd_iommu_completion_wait(st, cmd); + break; + case CMD_INVAL_DEVTAB_ENTRY: + break; + case CMD_INVAL_IOMMU_PAGES: + break; + case CMD_INVAL_IOTLB_PAGES: + amd_iommu_invalidate_iotlb(st, cmd); + break; + case CMD_INVAL_INTR_TABLE: + break; + default: + break; + } + + /* Increment and wrap head pointer. */ + st->cmdbuf_head += CMDBUF_ENTRY_SIZE; + if (st->cmdbuf_head >= st->cmdbuf_len) { + st->cmdbuf_head = 0; + } +} + +static uint32_t amd_iommu_mmio_buf_read(AMDIOMMUState *st, + size_t offset, + size_t size) +{ + ssize_t i; + uint32_t ret; + + if (!size) { + return 0; + } + + ret = st->mmio_buf[offset + size - 1]; + for (i = size - 2; i >= 0; i--) { + ret <<= 8; + ret |= st->mmio_buf[offset + i]; + } + + return ret; +} + +static void amd_iommu_mmio_buf_write(AMDIOMMUState *st, + size_t offset, + size_t size, + uint32_t val) +{ + size_t i; + + for (i = 0; i < size; i++) { + st->mmio_buf[offset + i] = val & 0xFF; + val >>= 8; + } +} + +static void amd_iommu_update_mmio(AMDIOMMUState *st, + target_phys_addr_t addr) +{ + size_t reg = addr & ~0x07; + uint64_t *base = (uint64_t *) &st->mmio_buf[reg]; + uint64_t val = le64_to_cpu(*base); + + switch (reg) { + case MMIO_CONTROL: + st->enabled = !!(val & MMIO_CONTROL_IOMMUEN); + st->ats_enabled = !!(val & MMIO_CONTROL_HTTUNEN); + st->evtlog_enabled = st->enabled && + !!(val & MMIO_CONTROL_EVENTLOGEN); + st->evtlog_intr = !!(val & MMIO_CONTROL_EVENTINTEN); + st->completion_wait_intr = !!(val & MMIO_CONTROL_COMWAITINTEN); + st->cmdbuf_enabled = st->enabled && + !!(val & MMIO_CONTROL_CMDBUFEN); + + /* Update status flags depending on the control register. */ + if (st->cmdbuf_enabled) { + st->mmio_buf[MMIO_STATUS] |= MMIO_STATUS_CMDBUF_RUN; + } else { + st->mmio_buf[MMIO_STATUS] &= ~MMIO_STATUS_CMDBUF_RUN; + } + if (st->evtlog_enabled) { + st->mmio_buf[MMIO_STATUS] |= MMIO_STATUS_EVTLOG_RUN; + } else { + st->mmio_buf[MMIO_STATUS] &= ~MMIO_STATUS_EVTLOG_RUN; + } + + amd_iommu_cmdbuf_run(st); + break; + case MMIO_DEVICE_TABLE: + st->devtab = (target_phys_addr_t) (val & MMIO_DEVTAB_BASE_MASK); + st->devtab_len = ((val & MMIO_DEVTAB_SIZE_MASK) + 1) * + (MMIO_DEVTAB_SIZE_UNIT / MMIO_DEVTAB_ENTRY_SIZE); + break; + case MMIO_COMMAND_BASE: + st->cmdbuf = (target_phys_addr_t) (val & MMIO_CMDBUF_BASE_MASK); + st->cmdbuf_len = 1UL << (st->mmio_buf[MMIO_CMDBUF_SIZE_BYTE] & + MMIO_CMDBUF_SIZE_MASK); + amd_iommu_cmdbuf_run(st); + break; + case MMIO_COMMAND_HEAD: + st->cmdbuf_head = val & MMIO_CMDBUF_HEAD_MASK; + amd_iommu_cmdbuf_run(st); + break; + case MMIO_COMMAND_TAIL: + st->cmdbuf_tail = val & MMIO_CMDBUF_TAIL_MASK; + amd_iommu_cmdbuf_run(st); + break; + case MMIO_EVENT_BASE: + st->evtlog = (target_phys_addr_t) (val & MMIO_EVTLOG_BASE_MASK); + st->evtlog_len = 1UL << (st->mmio_buf[MMIO_EVTLOG_SIZE_BYTE] & + MMIO_EVTLOG_SIZE_MASK); + break; + case MMIO_EVENT_HEAD: + st->evtlog_head = val & MMIO_EVTLOG_HEAD_MASK; + break; + case MMIO_EVENT_TAIL: + st->evtlog_tail = val & MMIO_EVTLOG_TAIL_MASK; + break; + case MMIO_EXCL_BASE: + st->excl_base = (target_phys_addr_t) (val & MMIO_EXCL_BASE_MASK); + st->excl_enabled = val & MMIO_EXCL_ENABLED_MASK; + st->excl_allow = val & MMIO_EXCL_ALLOW_MASK; + break; + case MMIO_EXCL_LIMIT: + st->excl_limit = (target_phys_addr_t) ((val & MMIO_EXCL_LIMIT_MASK) | + MMIO_EXCL_LIMIT_LOW); + break; + default: + break; + } +} + +static uint32_t amd_iommu_mmio_readb(void *opaque, target_phys_addr_t addr) +{ + AMDIOMMUState *st = opaque; + + return amd_iommu_mmio_buf_read(st, addr, 1); +} + +static uint32_t amd_iommu_mmio_readw(void *opaque, target_phys_addr_t addr) +{ + AMDIOMMUState *st = opaque; + + return amd_iommu_mmio_buf_read(st, addr, 2); +} + +static uint32_t amd_iommu_mmio_readl(void *opaque, target_phys_addr_t addr) +{ + AMDIOMMUState *st = opaque; + + return amd_iommu_mmio_buf_read(st, addr, 4); +} + +static void amd_iommu_mmio_writeb(void *opaque, + target_phys_addr_t addr, + uint32_t val) +{ + AMDIOMMUState *st = opaque; + + amd_iommu_mmio_buf_write(st, addr, 1, val); + amd_iommu_update_mmio(st, addr); +} + +static void amd_iommu_mmio_writew(void *opaque, + target_phys_addr_t addr, + uint32_t val) +{ + AMDIOMMUState *st = opaque; + + amd_iommu_mmio_buf_write(st, addr, 2, val); + amd_iommu_update_mmio(st, addr); +} + +static void amd_iommu_mmio_writel(void *opaque, + target_phys_addr_t addr, + uint32_t val) +{ + AMDIOMMUState *st = opaque; + + amd_iommu_mmio_buf_write(st, addr, 4, val); + amd_iommu_update_mmio(st, addr); +} + +static CPUReadMemoryFunc * const amd_iommu_mmio_read[] = { + amd_iommu_mmio_readb, + amd_iommu_mmio_readw, + amd_iommu_mmio_readl, +}; + +static CPUWriteMemoryFunc * const amd_iommu_mmio_write[] = { + amd_iommu_mmio_writeb, + amd_iommu_mmio_writew, + amd_iommu_mmio_writel, +}; + +static void amd_iommu_enable_mmio(AMDIOMMUState *st) +{ + target_phys_addr_t addr; + uint8_t *capab_wmask = st->dev.wmask + st->capab_offset; + + st->mmio_index = cpu_register_io_memory(amd_iommu_mmio_read, + amd_iommu_mmio_write, st); + if (st->mmio_index < 0) { + return; + } + + addr = le64_to_cpu(*(uint64_t *) &st->capab[CAPAB_BAR_LOW]) & CAPAB_BAR_MASK; + cpu_register_physical_memory(addr, MMIO_SIZE, st->mmio_index); + + st->mmio_addr = addr; + st->mmio_enabled = 1; + + /* Further changes to the capability are prohibited. */ + memset(capab_wmask + CAPAB_BAR_LOW, 0x00, CAPAB_REG_SIZE); + memset(capab_wmask + CAPAB_BAR_HIGH, 0x00, CAPAB_REG_SIZE); +} + +static void amd_iommu_write_capab(PCIDevice *dev, + uint32_t addr, uint32_t val, int len) +{ + AMDIOMMUState *st = DO_UPCAST(AMDIOMMUState, dev, dev); + + pci_default_write_config(dev, addr, val, len); + + if (!st->mmio_enabled && st->capab[CAPAB_BAR_LOW] & 0x1) { + amd_iommu_enable_mmio(st); + } +} + +static void amd_iommu_reset(DeviceState *dev) +{ + AMDIOMMUState *st = DO_UPCAST(AMDIOMMUState, dev.qdev, dev); + unsigned char *capab = st->capab; + uint8_t *capab_wmask = st->dev.wmask + st->capab_offset; + + st->enabled = 0; + st->ats_enabled = 0; + st->mmio_enabled = 0; + + capab[CAPAB_REV_TYPE] = CAPAB_REV_TYPE; + capab[CAPAB_FLAGS] = CAPAB_FLAGS; + capab[CAPAB_BAR_LOW] = 0; + capab[CAPAB_BAR_HIGH] = 0; + capab[CAPAB_RANGE] = 0; + *((uint32_t *) &capab[CAPAB_MISC]) = cpu_to_le32(CAPAB_INIT_MISC); + + /* Changes to the capability are allowed after system reset. */ + memset(capab_wmask + CAPAB_BAR_LOW, 0xFF, CAPAB_REG_SIZE); + memset(capab_wmask + CAPAB_BAR_HIGH, 0xFF, CAPAB_REG_SIZE); + + memset(st->mmio_buf, 0, MMIO_SIZE); + st->mmio_buf[MMIO_CMDBUF_SIZE_BYTE] = MMIO_CMDBUF_DEFAULT_SIZE; + st->mmio_buf[MMIO_EVTLOG_SIZE_BYTE] = MMIO_EVTLOG_DEFAULT_SIZE; +} + +static void amd_iommu_log_event(AMDIOMMUState *st, AMDIOMMUEvent *evt) +{ + if (!st->evtlog_enabled || + (st->mmio_buf[MMIO_STATUS] | MMIO_STATUS_EVTLOG_OF)) { + return; + } + + if (st->evtlog_tail >= st->evtlog_len) { + st->mmio_buf[MMIO_STATUS] |= MMIO_STATUS_EVTLOG_OF; + } + + cpu_physical_memory_write(st->evtlog + st->evtlog_tail, + (uint8_t *) evt, EVENT_LEN); + + st->evtlog_tail += EVENT_LEN; + st->mmio_buf[MMIO_STATUS] |= MMIO_STATUS_EVTLOG_INTR; +} + +static void amd_iommu_page_fault(AMDIOMMUState *st, + int devfn, + unsigned domid, + target_phys_addr_t addr, + int present, + int is_write) +{ + AMDIOMMUEvent evt; + unsigned info; + + evt.devfn = cpu_to_le16(devfn); + evt.reserved = 0; + evt.domid = cpu_to_le16(domid); + evt.addr = cpu_to_le64(addr); + + info = EVENT_IOPF; + if (present) { + info |= EVENT_IOPF_PR; + } + if (is_write) { + info |= EVENT_IOPF_RW; + } + evt.info = cpu_to_le16(info); + + amd_iommu_log_event(st, &evt); +} + +static inline uint64_t amd_iommu_get_perms(uint64_t entry) +{ + return (entry & (DEV_PERM_READ | DEV_PERM_WRITE)) >> DEV_PERM_SHIFT; +} + +static int amd_iommu_translate(PCIDevice *iommu, + PCIDevice *dev, + pcibus_t addr, + target_phys_addr_t *paddr, + target_phys_addr_t *len, + unsigned perms) +{ + int devfn, present; + target_phys_addr_t entry_addr, pte_addr; + uint64_t entry[4], pte, page_offset, pte_perms; + unsigned level, domid; + AMDIOMMUState *st = DO_UPCAST(AMDIOMMUState, dev, iommu); + + if (!st->enabled) { + goto no_translation; + } + + /* Get device table entry. */ + devfn = dev->devfn; + entry_addr = st->devtab + devfn * DEVTAB_ENTRY_SIZE; + cpu_physical_memory_read(entry_addr, (uint8_t *) entry, 32); + + pte = entry[0]; + if (!(pte & DEV_VALID) || !(pte & DEV_TRANSLATION_VALID)) { + goto no_translation; + } + domid = entry[1] & DEV_DOMAIN_ID_MASK; + level = (pte >> DEV_MODE_RSHIFT) & DEV_MODE_MASK; + while (level > 0) { + /* + * Check permissions: the bitwise + * implication perms -> entry_perms must be true. + */ + pte_perms = amd_iommu_get_perms(pte); + present = pte & 1; + if (!present || perms != (perms & pte_perms)) { + amd_iommu_page_fault(st, devfn, domid, addr, + present, !!(perms & IOMMU_PERM_WRITE)); + return -EPERM; + } + + /* Go to the next lower level. */ + pte_addr = pte & DEV_PT_ROOT_MASK; + pte_addr += ((addr >> (3 + 9 * level)) & 0x1FF) << 3; + pte = ldq_phys(pte_addr); + level = (pte >> DEV_MODE_RSHIFT) & DEV_MODE_MASK; + } + page_offset = addr & 4095; + *paddr = (pte & DEV_PT_ROOT_MASK) + page_offset; + *len = 4096 - page_offset; + + return 0; + +no_translation: + *paddr = addr; + *len = -1; + return 0; +} + +static int amd_iommu_pci_initfn(PCIDevice *dev) +{ + AMDIOMMUState *st = DO_UPCAST(AMDIOMMUState, dev, dev); + + pci_config_set_vendor_id(st->dev.config, PCI_VENDOR_ID_AMD); + pci_config_set_device_id(st->dev.config, PCI_DEVICE_ID_AMD_IOMMU); + pci_config_set_class(st->dev.config, PCI_CLASS_SYSTEM_IOMMU); + + /* Secure Device capability */ + st->capab_offset = pci_add_capability(&st->dev, + PCI_CAP_ID_SEC, + CAPAB_SIZE); + st->capab = st->dev.config + st->capab_offset; + dev->config_write = amd_iommu_write_capab; + + /* Allocate backing space for the MMIO registers. */ + st->mmio_buf = qemu_malloc(MMIO_SIZE); + + pci_register_iommu(dev, amd_iommu_translate); + + return 0; +} + +static const VMStateDescription vmstate_amd_iommu = { + .name = "amd-iommu", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField []) { + VMSTATE_PCI_DEVICE(dev, AMDIOMMUState), + VMSTATE_END_OF_LIST() + } +}; + +static PCIDeviceInfo amd_iommu_pci_info = { + .qdev.name = "amd-iommu", + .qdev.desc = "AMD IOMMU", + .qdev.size = sizeof(AMDIOMMUState), + .qdev.reset = amd_iommu_reset, + .qdev.vmsd = &vmstate_amd_iommu, + .init = amd_iommu_pci_initfn, +}; + +static void amd_iommu_register(void) +{ + pci_qdev_register(&amd_iommu_pci_info); +} + +device_init(amd_iommu_register); diff --git a/hw/pc.c b/hw/pc.c index a96187f..e2456b0 100644 --- a/hw/pc.c +++ b/hw/pc.c @@ -1068,6 +1068,8 @@ void pc_pci_device_init(PCIBus *pci_bus) int max_bus; int bus; + pci_create_simple(pci_bus, -1, "amd-iommu"); + max_bus = drive_get_max_bus(IF_SCSI); for (bus = 0; bus <= max_bus; bus++) { pci_create_simple(pci_bus, -1, "lsi53c895a"); diff --git a/hw/pci_ids.h b/hw/pci_ids.h index 39e9f1d..d790312 100644 --- a/hw/pci_ids.h +++ b/hw/pci_ids.h @@ -26,6 +26,7 @@ #define PCI_CLASS_MEMORY_RAM 0x0500 +#define PCI_CLASS_SYSTEM_IOMMU 0x0806 #define PCI_CLASS_SYSTEM_OTHER 0x0880 #define PCI_CLASS_SERIAL_USB 0x0c03 @@ -56,6 +57,7 @@ #define PCI_VENDOR_ID_AMD 0x1022 #define PCI_DEVICE_ID_AMD_LANCE 0x2000 +#define PCI_DEVICE_ID_AMD_IOMMU 0x0000 /* FIXME */ #define PCI_VENDOR_ID_MOTOROLA 0x1057 #define PCI_DEVICE_ID_MOTOROLA_MPC106 0x0002 diff --git a/hw/pci_regs.h b/hw/pci_regs.h index 0f9f84c..6695e41 100644 --- a/hw/pci_regs.h +++ b/hw/pci_regs.h @@ -209,6 +209,7 @@ #define PCI_CAP_ID_SHPC 0x0C /* PCI Standard Hot-Plug Controller */ #define PCI_CAP_ID_SSVID 0x0D /* Bridge subsystem vendor/device ID */ #define PCI_CAP_ID_AGP3 0x0E /* AGP Target PCI-PCI bridge */ +#define PCI_CAP_ID_SEC 0x0F /* Secure Device (AMD IOMMU) */ #define PCI_CAP_ID_EXP 0x10 /* PCI Express */ #define PCI_CAP_ID_MSIX 0x11 /* MSI-X */ #define PCI_CAP_ID_AF 0x13 /* PCI Advanced Features */ -- 1.7.1
WARNING: multiple messages have this Message-ID (diff)
From: Eduard - Gabriel Munteanu <eduard.munteanu@linux360.ro> To: mst@redhat.com Cc: kvm@vger.kernel.org, joro@8bytes.org, qemu-devel@nongnu.org, blauwirbel@gmail.com, yamahata@valinux.co.jp, paul@codesourcery.com, Eduard - Gabriel Munteanu <eduard.munteanu@linux360.ro>, avi@redhat.com Subject: [Qemu-devel] [PATCH 3/7] AMD IOMMU emulation Date: Sat, 28 Aug 2010 17:54:54 +0300 [thread overview] Message-ID: <1283007298-10942-4-git-send-email-eduard.munteanu@linux360.ro> (raw) In-Reply-To: <1283007298-10942-1-git-send-email-eduard.munteanu@linux360.ro> This introduces emulation for the AMD IOMMU, described in "AMD I/O Virtualization Technology (IOMMU) Specification". Signed-off-by: Eduard - Gabriel Munteanu <eduard.munteanu@linux360.ro> --- Makefile.target | 2 +- hw/amd_iommu.c | 663 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ hw/pc.c | 2 + hw/pci_ids.h | 2 + hw/pci_regs.h | 1 + 5 files changed, 669 insertions(+), 1 deletions(-) create mode 100644 hw/amd_iommu.c diff --git a/Makefile.target b/Makefile.target index 3ef4666..d4eeccd 100644 --- a/Makefile.target +++ b/Makefile.target @@ -195,7 +195,7 @@ obj-i386-y += cirrus_vga.o apic.o ioapic.o piix_pci.o obj-i386-y += vmmouse.o vmport.o hpet.o applesmc.o obj-i386-y += device-hotplug.o pci-hotplug.o smbios.o wdt_ib700.o obj-i386-y += debugcon.o multiboot.o -obj-i386-y += pc_piix.o +obj-i386-y += pc_piix.o amd_iommu.o # shared objects obj-ppc-y = ppc.o diff --git a/hw/amd_iommu.c b/hw/amd_iommu.c new file mode 100644 index 0000000..43e0426 --- /dev/null +++ b/hw/amd_iommu.c @@ -0,0 +1,663 @@ +/* + * AMD IOMMU emulation + * + * Copyright (c) 2010 Eduard - Gabriel Munteanu + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "pc.h" +#include "hw.h" +#include "pci.h" +#include "qlist.h" + +/* Capability registers */ +#define CAPAB_HEADER 0x00 +#define CAPAB_REV_TYPE 0x02 +#define CAPAB_FLAGS 0x03 +#define CAPAB_BAR_LOW 0x04 +#define CAPAB_BAR_HIGH 0x08 +#define CAPAB_RANGE 0x0C +#define CAPAB_MISC 0x10 + +#define CAPAB_SIZE 0x14 +#define CAPAB_REG_SIZE 0x04 + +/* Capability header data */ +#define CAPAB_FLAG_IOTLBSUP (1 << 0) +#define CAPAB_FLAG_HTTUNNEL (1 << 1) +#define CAPAB_FLAG_NPCACHE (1 << 2) +#define CAPAB_INIT_REV (1 << 3) +#define CAPAB_INIT_TYPE 3 +#define CAPAB_INIT_REV_TYPE (CAPAB_REV | CAPAB_TYPE) +#define CAPAB_INIT_FLAGS (CAPAB_FLAG_NPCACHE | CAPAB_FLAG_HTTUNNEL) +#define CAPAB_INIT_MISC (64 << 15) | (48 << 8) +#define CAPAB_BAR_MASK ~((1UL << 14) - 1) + +/* MMIO registers */ +#define MMIO_DEVICE_TABLE 0x0000 +#define MMIO_COMMAND_BASE 0x0008 +#define MMIO_EVENT_BASE 0x0010 +#define MMIO_CONTROL 0x0018 +#define MMIO_EXCL_BASE 0x0020 +#define MMIO_EXCL_LIMIT 0x0028 +#define MMIO_COMMAND_HEAD 0x2000 +#define MMIO_COMMAND_TAIL 0x2008 +#define MMIO_EVENT_HEAD 0x2010 +#define MMIO_EVENT_TAIL 0x2018 +#define MMIO_STATUS 0x2020 + +#define MMIO_SIZE 0x4000 + +#define MMIO_DEVTAB_SIZE_MASK ((1ULL << 12) - 1) +#define MMIO_DEVTAB_BASE_MASK (((1ULL << 52) - 1) & ~MMIO_DEVTAB_SIZE_MASK) +#define MMIO_DEVTAB_ENTRY_SIZE 32 +#define MMIO_DEVTAB_SIZE_UNIT 4096 + +#define MMIO_CMDBUF_SIZE_BYTE (MMIO_COMMAND_BASE + 7) +#define MMIO_CMDBUF_SIZE_MASK 0x0F +#define MMIO_CMDBUF_BASE_MASK MMIO_DEVTAB_BASE_MASK +#define MMIO_CMDBUF_DEFAULT_SIZE 8 +#define MMIO_CMDBUF_HEAD_MASK (((1ULL << 19) - 1) & ~0x0F) +#define MMIO_CMDBUF_TAIL_MASK MMIO_EVTLOG_HEAD_MASK + +#define MMIO_EVTLOG_SIZE_BYTE (MMIO_EVENT_BASE + 7) +#define MMIO_EVTLOG_SIZE_MASK MMIO_CMDBUF_SIZE_MASK +#define MMIO_EVTLOG_BASE_MASK MMIO_CMDBUF_BASE_MASK +#define MMIO_EVTLOG_DEFAULT_SIZE MMIO_CMDBUF_DEFAULT_SIZE +#define MMIO_EVTLOG_HEAD_MASK (((1ULL << 19) - 1) & ~0x0F) +#define MMIO_EVTLOG_TAIL_MASK MMIO_EVTLOG_HEAD_MASK + +#define MMIO_EXCL_BASE_MASK MMIO_DEVTAB_BASE_MASK +#define MMIO_EXCL_ENABLED_MASK (1ULL << 0) +#define MMIO_EXCL_ALLOW_MASK (1ULL << 1) +#define MMIO_EXCL_LIMIT_MASK MMIO_DEVTAB_BASE_MASK +#define MMIO_EXCL_LIMIT_LOW 0xFFF + +#define MMIO_CONTROL_IOMMUEN (1ULL << 0) +#define MMIO_CONTROL_HTTUNEN (1ULL << 1) +#define MMIO_CONTROL_EVENTLOGEN (1ULL << 2) +#define MMIO_CONTROL_EVENTINTEN (1ULL << 3) +#define MMIO_CONTROL_COMWAITINTEN (1ULL << 4) +#define MMIO_CONTROL_CMDBUFEN (1ULL << 12) + +#define MMIO_STATUS_EVTLOG_OF (1ULL << 0) +#define MMIO_STATUS_EVTLOG_INTR (1ULL << 1) +#define MMIO_STATUS_COMWAIT_INTR (1ULL << 2) +#define MMIO_STATUS_EVTLOG_RUN (1ULL << 3) +#define MMIO_STATUS_CMDBUF_RUN (1ULL << 4) + +#define CMDBUF_ID_BYTE 0x07 +#define CMDBUF_ID_RSHIFT 4 +#define CMDBUF_ENTRY_SIZE 0x10 + +#define CMD_COMPLETION_WAIT 0x01 +#define CMD_INVAL_DEVTAB_ENTRY 0x02 +#define CMD_INVAL_IOMMU_PAGES 0x03 +#define CMD_INVAL_IOTLB_PAGES 0x04 +#define CMD_INVAL_INTR_TABLE 0x05 + +#define DEVTAB_ENTRY_SIZE 32 + +/* Device table entry bits 0:63 */ +#define DEV_VALID (1ULL << 0) +#define DEV_TRANSLATION_VALID (1ULL << 1) +#define DEV_MODE_MASK 0x7 +#define DEV_MODE_RSHIFT 9 +#define DEV_PT_ROOT_MASK 0xFFFFFFFFFF000 +#define DEV_PT_ROOT_RSHIFT 12 +#define DEV_PERM_SHIFT 61 +#define DEV_PERM_READ (1ULL << 61) +#define DEV_PERM_WRITE (1ULL << 62) + +/* Device table entry bits 64:127 */ +#define DEV_DOMAIN_ID_MASK ((1ULL << 16) - 1) +#define DEV_IOTLB_SUPPORT (1ULL << 17) +#define DEV_SUPPRESS_PF (1ULL << 18) +#define DEV_SUPPRESS_ALL_PF (1ULL << 19) +#define DEV_IOCTL_MASK ~3 +#define DEV_IOCTL_RSHIFT 20 +#define DEV_IOCTL_DENY 0 +#define DEV_IOCTL_PASSTHROUGH 1 +#define DEV_IOCTL_TRANSLATE 2 +#define DEV_CACHE (1ULL << 37) +#define DEV_SNOOP_DISABLE (1ULL << 38) +#define DEV_EXCL (1ULL << 39) + +/* Event codes and flags, as stored in the info field */ +#define EVENT_ILLEGAL_DEVTAB_ENTRY (0x1U << 24) +#define EVENT_IOPF (0x2U << 24) +#define EVENT_IOPF_I (1U << 3) +#define EVENT_IOPF_PR (1U << 4) +#define EVENT_IOPF_RW (1U << 5) +#define EVENT_IOPF_PE (1U << 6) +#define EVENT_IOPF_RZ (1U << 7) +#define EVENT_IOPF_TR (1U << 8) +#define EVENT_DEV_TAB_HW_ERROR (0x3U << 24) +#define EVENT_PAGE_TAB_HW_ERROR (0x4U << 24) +#define EVENT_ILLEGAL_COMMAND_ERROR (0x5U << 24) +#define EVENT_COMMAND_HW_ERROR (0x6U << 24) +#define EVENT_IOTLB_INV_TIMEOUT (0x7U << 24) +#define EVENT_INVALID_DEV_REQUEST (0x8U << 24) + +#define EVENT_LEN 16 + +typedef struct AMDIOMMUState { + PCIDevice dev; + + int capab_offset; + unsigned char *capab; + + int mmio_index; + target_phys_addr_t mmio_addr; + unsigned char *mmio_buf; + int mmio_enabled; + + int enabled; + int ats_enabled; + + target_phys_addr_t devtab; + size_t devtab_len; + + target_phys_addr_t cmdbuf; + int cmdbuf_enabled; + size_t cmdbuf_len; + size_t cmdbuf_head; + size_t cmdbuf_tail; + int completion_wait_intr; + + target_phys_addr_t evtlog; + int evtlog_enabled; + int evtlog_intr; + target_phys_addr_t evtlog_len; + target_phys_addr_t evtlog_head; + target_phys_addr_t evtlog_tail; + + target_phys_addr_t excl_base; + target_phys_addr_t excl_limit; + int excl_enabled; + int excl_allow; +} AMDIOMMUState; + +typedef struct AMDIOMMUEvent { + uint16_t devfn; + uint16_t reserved; + uint16_t domid; + uint16_t info; + uint64_t addr; +} __attribute__((packed)) AMDIOMMUEvent; + +static void amd_iommu_completion_wait(AMDIOMMUState *st, + uint8_t *cmd) +{ + uint64_t addr; + + if (cmd[0] & 1) { + addr = le64_to_cpu(*(uint64_t *) cmd) & 0xFFFFFFFFFFFF8; + cpu_physical_memory_write(addr, cmd + 8, 8); + } + + if (cmd[0] & 2) + st->mmio_buf[MMIO_STATUS] |= MMIO_STATUS_COMWAIT_INTR; +} + +static void amd_iommu_invalidate_iotlb(AMDIOMMUState *st, + uint8_t *cmd) +{ + PCIDevice *dev; + PCIBus *bus = st->dev.bus; + int bus_num = pci_bus_num(bus); + int devfn = *(uint16_t *) cmd; + + dev = pci_find_device(bus, bus_num, PCI_SLOT(devfn), PCI_FUNC(devfn)); + if (dev) { + pci_memory_invalidate_range(dev, 0, -1); + } +} + +static void amd_iommu_cmdbuf_run(AMDIOMMUState *st) +{ + uint8_t cmd[16]; + int type; + + if (!st->cmdbuf_enabled) { + return; + } + + /* Check if there's work to do. */ + if (st->cmdbuf_head == st->cmdbuf_tail) { + return; + } + + cpu_physical_memory_read(st->cmdbuf + st->cmdbuf_head, cmd, 16); + type = cmd[CMDBUF_ID_BYTE] >> CMDBUF_ID_RSHIFT; + switch (type) { + case CMD_COMPLETION_WAIT: + amd_iommu_completion_wait(st, cmd); + break; + case CMD_INVAL_DEVTAB_ENTRY: + break; + case CMD_INVAL_IOMMU_PAGES: + break; + case CMD_INVAL_IOTLB_PAGES: + amd_iommu_invalidate_iotlb(st, cmd); + break; + case CMD_INVAL_INTR_TABLE: + break; + default: + break; + } + + /* Increment and wrap head pointer. */ + st->cmdbuf_head += CMDBUF_ENTRY_SIZE; + if (st->cmdbuf_head >= st->cmdbuf_len) { + st->cmdbuf_head = 0; + } +} + +static uint32_t amd_iommu_mmio_buf_read(AMDIOMMUState *st, + size_t offset, + size_t size) +{ + ssize_t i; + uint32_t ret; + + if (!size) { + return 0; + } + + ret = st->mmio_buf[offset + size - 1]; + for (i = size - 2; i >= 0; i--) { + ret <<= 8; + ret |= st->mmio_buf[offset + i]; + } + + return ret; +} + +static void amd_iommu_mmio_buf_write(AMDIOMMUState *st, + size_t offset, + size_t size, + uint32_t val) +{ + size_t i; + + for (i = 0; i < size; i++) { + st->mmio_buf[offset + i] = val & 0xFF; + val >>= 8; + } +} + +static void amd_iommu_update_mmio(AMDIOMMUState *st, + target_phys_addr_t addr) +{ + size_t reg = addr & ~0x07; + uint64_t *base = (uint64_t *) &st->mmio_buf[reg]; + uint64_t val = le64_to_cpu(*base); + + switch (reg) { + case MMIO_CONTROL: + st->enabled = !!(val & MMIO_CONTROL_IOMMUEN); + st->ats_enabled = !!(val & MMIO_CONTROL_HTTUNEN); + st->evtlog_enabled = st->enabled && + !!(val & MMIO_CONTROL_EVENTLOGEN); + st->evtlog_intr = !!(val & MMIO_CONTROL_EVENTINTEN); + st->completion_wait_intr = !!(val & MMIO_CONTROL_COMWAITINTEN); + st->cmdbuf_enabled = st->enabled && + !!(val & MMIO_CONTROL_CMDBUFEN); + + /* Update status flags depending on the control register. */ + if (st->cmdbuf_enabled) { + st->mmio_buf[MMIO_STATUS] |= MMIO_STATUS_CMDBUF_RUN; + } else { + st->mmio_buf[MMIO_STATUS] &= ~MMIO_STATUS_CMDBUF_RUN; + } + if (st->evtlog_enabled) { + st->mmio_buf[MMIO_STATUS] |= MMIO_STATUS_EVTLOG_RUN; + } else { + st->mmio_buf[MMIO_STATUS] &= ~MMIO_STATUS_EVTLOG_RUN; + } + + amd_iommu_cmdbuf_run(st); + break; + case MMIO_DEVICE_TABLE: + st->devtab = (target_phys_addr_t) (val & MMIO_DEVTAB_BASE_MASK); + st->devtab_len = ((val & MMIO_DEVTAB_SIZE_MASK) + 1) * + (MMIO_DEVTAB_SIZE_UNIT / MMIO_DEVTAB_ENTRY_SIZE); + break; + case MMIO_COMMAND_BASE: + st->cmdbuf = (target_phys_addr_t) (val & MMIO_CMDBUF_BASE_MASK); + st->cmdbuf_len = 1UL << (st->mmio_buf[MMIO_CMDBUF_SIZE_BYTE] & + MMIO_CMDBUF_SIZE_MASK); + amd_iommu_cmdbuf_run(st); + break; + case MMIO_COMMAND_HEAD: + st->cmdbuf_head = val & MMIO_CMDBUF_HEAD_MASK; + amd_iommu_cmdbuf_run(st); + break; + case MMIO_COMMAND_TAIL: + st->cmdbuf_tail = val & MMIO_CMDBUF_TAIL_MASK; + amd_iommu_cmdbuf_run(st); + break; + case MMIO_EVENT_BASE: + st->evtlog = (target_phys_addr_t) (val & MMIO_EVTLOG_BASE_MASK); + st->evtlog_len = 1UL << (st->mmio_buf[MMIO_EVTLOG_SIZE_BYTE] & + MMIO_EVTLOG_SIZE_MASK); + break; + case MMIO_EVENT_HEAD: + st->evtlog_head = val & MMIO_EVTLOG_HEAD_MASK; + break; + case MMIO_EVENT_TAIL: + st->evtlog_tail = val & MMIO_EVTLOG_TAIL_MASK; + break; + case MMIO_EXCL_BASE: + st->excl_base = (target_phys_addr_t) (val & MMIO_EXCL_BASE_MASK); + st->excl_enabled = val & MMIO_EXCL_ENABLED_MASK; + st->excl_allow = val & MMIO_EXCL_ALLOW_MASK; + break; + case MMIO_EXCL_LIMIT: + st->excl_limit = (target_phys_addr_t) ((val & MMIO_EXCL_LIMIT_MASK) | + MMIO_EXCL_LIMIT_LOW); + break; + default: + break; + } +} + +static uint32_t amd_iommu_mmio_readb(void *opaque, target_phys_addr_t addr) +{ + AMDIOMMUState *st = opaque; + + return amd_iommu_mmio_buf_read(st, addr, 1); +} + +static uint32_t amd_iommu_mmio_readw(void *opaque, target_phys_addr_t addr) +{ + AMDIOMMUState *st = opaque; + + return amd_iommu_mmio_buf_read(st, addr, 2); +} + +static uint32_t amd_iommu_mmio_readl(void *opaque, target_phys_addr_t addr) +{ + AMDIOMMUState *st = opaque; + + return amd_iommu_mmio_buf_read(st, addr, 4); +} + +static void amd_iommu_mmio_writeb(void *opaque, + target_phys_addr_t addr, + uint32_t val) +{ + AMDIOMMUState *st = opaque; + + amd_iommu_mmio_buf_write(st, addr, 1, val); + amd_iommu_update_mmio(st, addr); +} + +static void amd_iommu_mmio_writew(void *opaque, + target_phys_addr_t addr, + uint32_t val) +{ + AMDIOMMUState *st = opaque; + + amd_iommu_mmio_buf_write(st, addr, 2, val); + amd_iommu_update_mmio(st, addr); +} + +static void amd_iommu_mmio_writel(void *opaque, + target_phys_addr_t addr, + uint32_t val) +{ + AMDIOMMUState *st = opaque; + + amd_iommu_mmio_buf_write(st, addr, 4, val); + amd_iommu_update_mmio(st, addr); +} + +static CPUReadMemoryFunc * const amd_iommu_mmio_read[] = { + amd_iommu_mmio_readb, + amd_iommu_mmio_readw, + amd_iommu_mmio_readl, +}; + +static CPUWriteMemoryFunc * const amd_iommu_mmio_write[] = { + amd_iommu_mmio_writeb, + amd_iommu_mmio_writew, + amd_iommu_mmio_writel, +}; + +static void amd_iommu_enable_mmio(AMDIOMMUState *st) +{ + target_phys_addr_t addr; + uint8_t *capab_wmask = st->dev.wmask + st->capab_offset; + + st->mmio_index = cpu_register_io_memory(amd_iommu_mmio_read, + amd_iommu_mmio_write, st); + if (st->mmio_index < 0) { + return; + } + + addr = le64_to_cpu(*(uint64_t *) &st->capab[CAPAB_BAR_LOW]) & CAPAB_BAR_MASK; + cpu_register_physical_memory(addr, MMIO_SIZE, st->mmio_index); + + st->mmio_addr = addr; + st->mmio_enabled = 1; + + /* Further changes to the capability are prohibited. */ + memset(capab_wmask + CAPAB_BAR_LOW, 0x00, CAPAB_REG_SIZE); + memset(capab_wmask + CAPAB_BAR_HIGH, 0x00, CAPAB_REG_SIZE); +} + +static void amd_iommu_write_capab(PCIDevice *dev, + uint32_t addr, uint32_t val, int len) +{ + AMDIOMMUState *st = DO_UPCAST(AMDIOMMUState, dev, dev); + + pci_default_write_config(dev, addr, val, len); + + if (!st->mmio_enabled && st->capab[CAPAB_BAR_LOW] & 0x1) { + amd_iommu_enable_mmio(st); + } +} + +static void amd_iommu_reset(DeviceState *dev) +{ + AMDIOMMUState *st = DO_UPCAST(AMDIOMMUState, dev.qdev, dev); + unsigned char *capab = st->capab; + uint8_t *capab_wmask = st->dev.wmask + st->capab_offset; + + st->enabled = 0; + st->ats_enabled = 0; + st->mmio_enabled = 0; + + capab[CAPAB_REV_TYPE] = CAPAB_REV_TYPE; + capab[CAPAB_FLAGS] = CAPAB_FLAGS; + capab[CAPAB_BAR_LOW] = 0; + capab[CAPAB_BAR_HIGH] = 0; + capab[CAPAB_RANGE] = 0; + *((uint32_t *) &capab[CAPAB_MISC]) = cpu_to_le32(CAPAB_INIT_MISC); + + /* Changes to the capability are allowed after system reset. */ + memset(capab_wmask + CAPAB_BAR_LOW, 0xFF, CAPAB_REG_SIZE); + memset(capab_wmask + CAPAB_BAR_HIGH, 0xFF, CAPAB_REG_SIZE); + + memset(st->mmio_buf, 0, MMIO_SIZE); + st->mmio_buf[MMIO_CMDBUF_SIZE_BYTE] = MMIO_CMDBUF_DEFAULT_SIZE; + st->mmio_buf[MMIO_EVTLOG_SIZE_BYTE] = MMIO_EVTLOG_DEFAULT_SIZE; +} + +static void amd_iommu_log_event(AMDIOMMUState *st, AMDIOMMUEvent *evt) +{ + if (!st->evtlog_enabled || + (st->mmio_buf[MMIO_STATUS] | MMIO_STATUS_EVTLOG_OF)) { + return; + } + + if (st->evtlog_tail >= st->evtlog_len) { + st->mmio_buf[MMIO_STATUS] |= MMIO_STATUS_EVTLOG_OF; + } + + cpu_physical_memory_write(st->evtlog + st->evtlog_tail, + (uint8_t *) evt, EVENT_LEN); + + st->evtlog_tail += EVENT_LEN; + st->mmio_buf[MMIO_STATUS] |= MMIO_STATUS_EVTLOG_INTR; +} + +static void amd_iommu_page_fault(AMDIOMMUState *st, + int devfn, + unsigned domid, + target_phys_addr_t addr, + int present, + int is_write) +{ + AMDIOMMUEvent evt; + unsigned info; + + evt.devfn = cpu_to_le16(devfn); + evt.reserved = 0; + evt.domid = cpu_to_le16(domid); + evt.addr = cpu_to_le64(addr); + + info = EVENT_IOPF; + if (present) { + info |= EVENT_IOPF_PR; + } + if (is_write) { + info |= EVENT_IOPF_RW; + } + evt.info = cpu_to_le16(info); + + amd_iommu_log_event(st, &evt); +} + +static inline uint64_t amd_iommu_get_perms(uint64_t entry) +{ + return (entry & (DEV_PERM_READ | DEV_PERM_WRITE)) >> DEV_PERM_SHIFT; +} + +static int amd_iommu_translate(PCIDevice *iommu, + PCIDevice *dev, + pcibus_t addr, + target_phys_addr_t *paddr, + target_phys_addr_t *len, + unsigned perms) +{ + int devfn, present; + target_phys_addr_t entry_addr, pte_addr; + uint64_t entry[4], pte, page_offset, pte_perms; + unsigned level, domid; + AMDIOMMUState *st = DO_UPCAST(AMDIOMMUState, dev, iommu); + + if (!st->enabled) { + goto no_translation; + } + + /* Get device table entry. */ + devfn = dev->devfn; + entry_addr = st->devtab + devfn * DEVTAB_ENTRY_SIZE; + cpu_physical_memory_read(entry_addr, (uint8_t *) entry, 32); + + pte = entry[0]; + if (!(pte & DEV_VALID) || !(pte & DEV_TRANSLATION_VALID)) { + goto no_translation; + } + domid = entry[1] & DEV_DOMAIN_ID_MASK; + level = (pte >> DEV_MODE_RSHIFT) & DEV_MODE_MASK; + while (level > 0) { + /* + * Check permissions: the bitwise + * implication perms -> entry_perms must be true. + */ + pte_perms = amd_iommu_get_perms(pte); + present = pte & 1; + if (!present || perms != (perms & pte_perms)) { + amd_iommu_page_fault(st, devfn, domid, addr, + present, !!(perms & IOMMU_PERM_WRITE)); + return -EPERM; + } + + /* Go to the next lower level. */ + pte_addr = pte & DEV_PT_ROOT_MASK; + pte_addr += ((addr >> (3 + 9 * level)) & 0x1FF) << 3; + pte = ldq_phys(pte_addr); + level = (pte >> DEV_MODE_RSHIFT) & DEV_MODE_MASK; + } + page_offset = addr & 4095; + *paddr = (pte & DEV_PT_ROOT_MASK) + page_offset; + *len = 4096 - page_offset; + + return 0; + +no_translation: + *paddr = addr; + *len = -1; + return 0; +} + +static int amd_iommu_pci_initfn(PCIDevice *dev) +{ + AMDIOMMUState *st = DO_UPCAST(AMDIOMMUState, dev, dev); + + pci_config_set_vendor_id(st->dev.config, PCI_VENDOR_ID_AMD); + pci_config_set_device_id(st->dev.config, PCI_DEVICE_ID_AMD_IOMMU); + pci_config_set_class(st->dev.config, PCI_CLASS_SYSTEM_IOMMU); + + /* Secure Device capability */ + st->capab_offset = pci_add_capability(&st->dev, + PCI_CAP_ID_SEC, + CAPAB_SIZE); + st->capab = st->dev.config + st->capab_offset; + dev->config_write = amd_iommu_write_capab; + + /* Allocate backing space for the MMIO registers. */ + st->mmio_buf = qemu_malloc(MMIO_SIZE); + + pci_register_iommu(dev, amd_iommu_translate); + + return 0; +} + +static const VMStateDescription vmstate_amd_iommu = { + .name = "amd-iommu", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField []) { + VMSTATE_PCI_DEVICE(dev, AMDIOMMUState), + VMSTATE_END_OF_LIST() + } +}; + +static PCIDeviceInfo amd_iommu_pci_info = { + .qdev.name = "amd-iommu", + .qdev.desc = "AMD IOMMU", + .qdev.size = sizeof(AMDIOMMUState), + .qdev.reset = amd_iommu_reset, + .qdev.vmsd = &vmstate_amd_iommu, + .init = amd_iommu_pci_initfn, +}; + +static void amd_iommu_register(void) +{ + pci_qdev_register(&amd_iommu_pci_info); +} + +device_init(amd_iommu_register); diff --git a/hw/pc.c b/hw/pc.c index a96187f..e2456b0 100644 --- a/hw/pc.c +++ b/hw/pc.c @@ -1068,6 +1068,8 @@ void pc_pci_device_init(PCIBus *pci_bus) int max_bus; int bus; + pci_create_simple(pci_bus, -1, "amd-iommu"); + max_bus = drive_get_max_bus(IF_SCSI); for (bus = 0; bus <= max_bus; bus++) { pci_create_simple(pci_bus, -1, "lsi53c895a"); diff --git a/hw/pci_ids.h b/hw/pci_ids.h index 39e9f1d..d790312 100644 --- a/hw/pci_ids.h +++ b/hw/pci_ids.h @@ -26,6 +26,7 @@ #define PCI_CLASS_MEMORY_RAM 0x0500 +#define PCI_CLASS_SYSTEM_IOMMU 0x0806 #define PCI_CLASS_SYSTEM_OTHER 0x0880 #define PCI_CLASS_SERIAL_USB 0x0c03 @@ -56,6 +57,7 @@ #define PCI_VENDOR_ID_AMD 0x1022 #define PCI_DEVICE_ID_AMD_LANCE 0x2000 +#define PCI_DEVICE_ID_AMD_IOMMU 0x0000 /* FIXME */ #define PCI_VENDOR_ID_MOTOROLA 0x1057 #define PCI_DEVICE_ID_MOTOROLA_MPC106 0x0002 diff --git a/hw/pci_regs.h b/hw/pci_regs.h index 0f9f84c..6695e41 100644 --- a/hw/pci_regs.h +++ b/hw/pci_regs.h @@ -209,6 +209,7 @@ #define PCI_CAP_ID_SHPC 0x0C /* PCI Standard Hot-Plug Controller */ #define PCI_CAP_ID_SSVID 0x0D /* Bridge subsystem vendor/device ID */ #define PCI_CAP_ID_AGP3 0x0E /* AGP Target PCI-PCI bridge */ +#define PCI_CAP_ID_SEC 0x0F /* Secure Device (AMD IOMMU) */ #define PCI_CAP_ID_EXP 0x10 /* PCI Express */ #define PCI_CAP_ID_MSIX 0x11 /* MSI-X */ #define PCI_CAP_ID_AF 0x13 /* PCI Advanced Features */ -- 1.7.1
next prev parent reply other threads:[~2010-08-28 14:56 UTC|newest] Thread overview: 98+ messages / expand[flat|nested] mbox.gz Atom feed top 2010-08-28 14:54 [PATCH 0/7] AMD IOMMU emulation patchset v4 Eduard - Gabriel Munteanu 2010-08-28 14:54 ` [Qemu-devel] " Eduard - Gabriel Munteanu 2010-08-28 14:54 ` [PATCH 1/7] pci: expand tabs to spaces in pci_regs.h Eduard - Gabriel Munteanu 2010-08-28 14:54 ` [Qemu-devel] " Eduard - Gabriel Munteanu 2010-08-31 20:29 ` Michael S. Tsirkin 2010-08-31 20:29 ` [Qemu-devel] " Michael S. Tsirkin 2010-08-31 22:58 ` Eduard - Gabriel Munteanu 2010-08-31 22:58 ` [Qemu-devel] " Eduard - Gabriel Munteanu 2010-09-01 10:39 ` Michael S. Tsirkin 2010-09-01 10:39 ` [Qemu-devel] " Michael S. Tsirkin 2010-08-28 14:54 ` [PATCH 2/7] pci: memory access API and IOMMU support Eduard - Gabriel Munteanu 2010-08-28 14:54 ` [Qemu-devel] " Eduard - Gabriel Munteanu 2010-09-02 5:28 ` Michael S. Tsirkin 2010-09-02 5:28 ` [Qemu-devel] " Michael S. Tsirkin 2010-09-02 8:40 ` Eduard - Gabriel Munteanu 2010-09-02 8:40 ` [Qemu-devel] " Eduard - Gabriel Munteanu 2010-09-02 9:49 ` Michael S. Tsirkin 2010-09-02 9:49 ` [Qemu-devel] " Michael S. Tsirkin 2010-09-04 9:01 ` Blue Swirl 2010-09-04 9:01 ` [Qemu-devel] " Blue Swirl 2010-09-05 7:10 ` Michael S. Tsirkin 2010-09-05 7:10 ` [Qemu-devel] " Michael S. Tsirkin 2010-08-28 14:54 ` Eduard - Gabriel Munteanu [this message] 2010-08-28 14:54 ` [Qemu-devel] [PATCH 3/7] AMD IOMMU emulation Eduard - Gabriel Munteanu 2010-08-28 15:58 ` Blue Swirl 2010-08-28 15:58 ` [Qemu-devel] " Blue Swirl 2010-08-28 21:53 ` Eduard - Gabriel Munteanu 2010-08-28 21:53 ` [Qemu-devel] " Eduard - Gabriel Munteanu 2010-08-29 20:37 ` Blue Swirl 2010-08-29 20:37 ` [Qemu-devel] " Blue Swirl 2010-08-30 3:07 ` [Qemu-devel] " Isaku Yamahata 2010-08-30 3:07 ` Isaku Yamahata 2010-08-30 5:54 ` Eduard - Gabriel Munteanu 2010-08-30 5:54 ` Eduard - Gabriel Munteanu 2010-08-28 14:54 ` [PATCH 4/7] ide: use the PCI memory access interface Eduard - Gabriel Munteanu 2010-08-28 14:54 ` [Qemu-devel] " Eduard - Gabriel Munteanu 2010-09-02 5:19 ` Michael S. Tsirkin 2010-09-02 5:19 ` [Qemu-devel] " Michael S. Tsirkin 2010-09-02 9:12 ` Eduard - Gabriel Munteanu 2010-09-02 9:12 ` [Qemu-devel] " Eduard - Gabriel Munteanu 2010-09-02 9:58 ` Michael S. Tsirkin 2010-09-02 9:58 ` [Qemu-devel] " Michael S. Tsirkin 2010-09-02 15:01 ` Eduard - Gabriel Munteanu 2010-09-02 15:01 ` [Qemu-devel] " Eduard - Gabriel Munteanu 2010-09-02 15:24 ` Avi Kivity 2010-09-02 15:24 ` [Qemu-devel] " Avi Kivity 2010-09-02 15:39 ` Michael S. Tsirkin 2010-09-02 15:39 ` [Qemu-devel] " Michael S. Tsirkin 2010-09-02 16:07 ` Avi Kivity 2010-09-02 16:07 ` [Qemu-devel] " Avi Kivity 2010-09-02 15:31 ` Michael S. Tsirkin 2010-09-02 15:31 ` [Qemu-devel] " Michael S. Tsirkin 2010-08-28 14:54 ` [PATCH 5/7] rtl8139: " Eduard - Gabriel Munteanu 2010-08-28 14:54 ` [Qemu-devel] " Eduard - Gabriel Munteanu 2010-08-28 14:54 ` [PATCH 6/7] eepro100: " Eduard - Gabriel Munteanu 2010-08-28 14:54 ` [Qemu-devel] " Eduard - Gabriel Munteanu 2010-08-28 14:54 ` [PATCH 7/7] ac97: " Eduard - Gabriel Munteanu 2010-08-28 14:54 ` [Qemu-devel] " Eduard - Gabriel Munteanu 2010-08-28 16:00 ` [PATCH 0/7] AMD IOMMU emulation patchset v4 Blue Swirl 2010-08-28 16:00 ` [Qemu-devel] " Blue Swirl 2010-08-29 9:55 ` Joerg Roedel 2010-08-29 9:55 ` [Qemu-devel] " Joerg Roedel 2010-08-29 20:44 ` Blue Swirl 2010-08-29 20:44 ` [Qemu-devel] " Blue Swirl 2010-08-29 22:08 ` [PATCH 2/7] pci: memory access API and IOMMU support Eduard - Gabriel Munteanu 2010-08-29 22:08 ` [Qemu-devel] " Eduard - Gabriel Munteanu 2010-08-29 22:11 ` Eduard - Gabriel Munteanu 2010-08-29 22:11 ` [Qemu-devel] " Eduard - Gabriel Munteanu 2010-09-01 20:10 ` [Qemu-devel] " Stefan Weil 2010-09-01 20:10 ` Stefan Weil 2010-09-02 6:00 ` Michael S. Tsirkin 2010-09-02 6:00 ` Michael S. Tsirkin 2010-09-02 9:08 ` Eduard - Gabriel Munteanu 2010-09-02 9:08 ` Eduard - Gabriel Munteanu 2010-09-02 13:24 ` Anthony Liguori 2010-09-02 13:24 ` Anthony Liguori 2010-09-02 8:51 ` Eduard - Gabriel Munteanu 2010-09-02 8:51 ` Eduard - Gabriel Munteanu 2010-09-02 16:05 ` Stefan Weil 2010-09-02 16:05 ` Stefan Weil 2010-09-02 16:14 ` Eduard - Gabriel Munteanu 2010-09-02 16:14 ` Eduard - Gabriel Munteanu 2010-09-13 20:01 ` [PATCH RFC] dma_rw.h (was Re: [PATCH 0/7] AMD IOMMU emulation patchset v4) Michael S. Tsirkin 2010-09-13 20:01 ` [Qemu-devel] " Michael S. Tsirkin 2010-09-13 20:45 ` Anthony Liguori 2010-09-13 20:45 ` Anthony Liguori 2010-09-16 7:12 ` Eduard - Gabriel Munteanu 2010-09-16 7:12 ` Eduard - Gabriel Munteanu 2010-09-16 9:35 ` Michael S. Tsirkin 2010-09-16 9:35 ` Michael S. Tsirkin 2010-09-16 7:06 ` Eduard - Gabriel Munteanu 2010-09-16 7:06 ` [Qemu-devel] " Eduard - Gabriel Munteanu 2010-09-16 9:20 ` Michael S. Tsirkin 2010-09-16 9:20 ` [Qemu-devel] " Michael S. Tsirkin 2010-09-16 11:15 ` Eduard - Gabriel Munteanu 2010-09-16 11:15 ` [Qemu-devel] " Eduard - Gabriel Munteanu -- strict thread matches above, loose matches on Subject: below -- 2010-08-15 19:27 [PATCH 0/7] AMD IOMMU emulation patches v3 Eduard - Gabriel Munteanu 2010-08-15 19:27 ` [PATCH 3/7] AMD IOMMU emulation Eduard - Gabriel Munteanu 2010-08-16 17:57 ` Blue Swirl
Reply instructions: You may reply publicly to this message via plain-text email using any one of the following methods: * Save the following mbox file, import it into your mail client, and reply-to-all from there: mbox Avoid top-posting and favor interleaved quoting: https://en.wikipedia.org/wiki/Posting_style#Interleaved_style * Reply using the --to, --cc, and --in-reply-to switches of git-send-email(1): git send-email \ --in-reply-to=1283007298-10942-4-git-send-email-eduard.munteanu@linux360.ro \ --to=eduard.munteanu@linux360.ro \ --cc=anthony@codemonkey.ws \ --cc=av1474@comtv.ru \ --cc=avi@redhat.com \ --cc=blauwirbel@gmail.com \ --cc=joro@8bytes.org \ --cc=kvm@vger.kernel.org \ --cc=mst@redhat.com \ --cc=paul@codesourcery.com \ --cc=qemu-devel@nongnu.org \ --cc=yamahata@valinux.co.jp \ /path/to/YOUR_REPLY https://kernel.org/pub/software/scm/git/docs/git-send-email.html * If your mail client supports setting the In-Reply-To header via mailto: links, try the mailto: linkBe sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.