Hi all, This patch series introduces the PCI passthrough for Xen. First, we have HostPCIDevice that help to access one PCI device of the host. Then, there is an additions in the QEMU code, pci_check_bar_overlap. There are also several change in pci_ids and pci_regs. Last part, but not least, the PCI passthrough device himself. Cut in 3 parts (or file), there is one to take care of the initialisation of a passthrough device. The second one handle everything about the config address space, there are specifics functions for every config register. The third one is to handle MSI. There is a patch series on xen-devel (applied to xen-unstable) that add the support of setting a PCI passthrough device through QMP from libxl (xen tool stack). It is just a call to device_add, with the driver parametter hostaddr="0000:07:00.1". Change since last time: - msitraslate code have been removed. - code for the power management capability is removed, but will be re-added for the next version of the patch series as a separate patch. - new patch to remove a check in pci_parse_devaddr. - use pci_default_config_write, so no more hack to handle the BAR mapping in QEMU. - improve the code in general (a bit more comprehensible). - update to QOM. Change v4-v5: - return -errno if there is an error in host_pci_get_* - rename internal function get_value to get_hex_value (and return the same error value has get_resource) Change v3-v4: - host_pci_get_* can now return an error, and take an extra parameter, a pointer to store the wanted value. - The memory_region for the PCI BAR are handled "manualy" because calling pci_default_write_config was not possible, because the XenPT handle the PCIIORegion it self. This make possible to do a device_remove. - Introduction of PT_ERR and PT_WARN macro to print debug and error messages. Also, these macro as well as PT_LOG will always print the short BDF of the device in the guest point of view. - PT_ERR is print by default (for all error messages). - Some debug/error message have been improve and should be a bit more useful. - hw_error have been removed from the code, and have been replaced by either a call to qemu_system_shudown_request() (that lead to a domain destroy) or a failed in the initialisation of the device. - Now, every patchs should compile with no error. Change v2-v3; - in host-pci-device.c: - Return more usefull error code in get_ressource(). - Use macro in host_pci_find_ext_cap_offset instead of raw number. But I still not sure if PCI_MAX_EXT_CAP is right, it''s result is 480 like it was before, so it''s maybe ok. - All use of MSI stuff in two first pci passthrough patch have been removed and move to the last patch. Change v1-v2: - fix style issue (checkpatch.pl) - set the original authors, add some missing copyright headers - HostPCIDevice: - introduce HostPCIIORegions (with base_addr, size, flags) - save all flags from ./resource and store it in a separate field. - fix endianess on write - new host_pci_dev_put function - use pci.c like interface host_pci_get/set_byte/word/long (instead of host_pci_read/write_) - compile HostPCIDevice only on linux (as well as xen_pci_passthrough) - introduce apic-msidef.h file. - no more run_one_timer, if a pci device is in the middle of a power transition, just "return an error" in config read/write - use a global var mapped_machine_irq (local to xen_pci_passthrough.c) - add msitranslate and power-mgmt ad qdev property Allen Kay (2): Introduce Xen PCI Passthrough, qdevice (1/3) Introduce Xen PCI Passthrough, PCI config space helpers (2/3) Anthony PERARD (7): pci_ids: Add INTEL_82599_VF id. pci_regs: Fix value of PCI_EXP_TYPE_RC_EC. pci_regs: Add PCI_EXP_TYPE_PCIE_BRIDGE configure: Introduce --enable-xen-pci-passthrough. Introduce HostPCIDevice to access a pci device on the host. Introduce apic-msidef.h pci: Do not check if a bus exist in pci_parse_devaddr. Jiang Yunhong (1): Introduce Xen PCI Passthrough, MSI (3/3) Yuji Shimada (1): pci.c: Add pci_check_bar_overlap Makefile.target | 6 + configure | 25 + hw/apic-msidef.h | 30 + hw/apic.c | 11 +- hw/host-pci-device.c | 278 +++++ hw/host-pci-device.h | 75 ++ hw/pci.c | 51 +- hw/pci.h | 3 + hw/pci_ids.h | 1 + hw/pci_regs.h | 3 +- hw/xen_common.h | 3 + hw/xen_pci_passthrough.c | 856 +++++++++++++++ hw/xen_pci_passthrough.h | 313 ++++++ hw/xen_pci_passthrough_config_init.c | 1972 ++++++++++++++++++++++++++++++++++ hw/xen_pci_passthrough_msi.c | 667 ++++++++++++ xen-all.c | 12 + 16 files changed, 4291 insertions(+), 15 deletions(-) create mode 100644 hw/apic-msidef.h create mode 100644 hw/host-pci-device.c create mode 100644 hw/host-pci-device.h create mode 100644 hw/xen_pci_passthrough.c create mode 100644 hw/xen_pci_passthrough.h create mode 100644 hw/xen_pci_passthrough_config_init.c create mode 100644 hw/xen_pci_passthrough_msi.c -- Anthony PERARD
Signed-off-by: Anthony PERARD <anthony.perard@citrix.com> --- hw/pci_ids.h | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) diff --git a/hw/pci_ids.h b/hw/pci_ids.h index e8235a7..943106a 100644 --- a/hw/pci_ids.h +++ b/hw/pci_ids.h @@ -118,6 +118,7 @@ #define PCI_DEVICE_ID_INTEL_82801I_UHCI6 0x2939 #define PCI_DEVICE_ID_INTEL_82801I_EHCI1 0x293a #define PCI_DEVICE_ID_INTEL_82801I_EHCI2 0x293c +#define PCI_DEVICE_ID_INTEL_82599_VF 0x10ed #define PCI_VENDOR_ID_XEN 0x5853 #define PCI_DEVICE_ID_XEN_PLATFORM 0x0001 -- Anthony PERARD
Anthony PERARD
2012-Feb-13 12:20 UTC
[PATCH V6 02/11] pci_regs: Fix value of PCI_EXP_TYPE_RC_EC.
Value check in PCI Express Base Specification rev 1.1 Signed-off-by: Anthony PERARD <anthony.perard@citrix.com> --- hw/pci_regs.h | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/hw/pci_regs.h b/hw/pci_regs.h index e8357c3..6b42515 100644 --- a/hw/pci_regs.h +++ b/hw/pci_regs.h @@ -393,7 +393,7 @@ #define PCI_EXP_TYPE_DOWNSTREAM 0x6 /* Downstream Port */ #define PCI_EXP_TYPE_PCI_BRIDGE 0x7 /* PCI/PCI-X Bridge */ #define PCI_EXP_TYPE_RC_END 0x9 /* Root Complex Integrated Endpoint */ -#define PCI_EXP_TYPE_RC_EC 0x10 /* Root Complex Event Collector */ +#define PCI_EXP_TYPE_RC_EC 0xa /* Root Complex Event Collector */ #define PCI_EXP_FLAGS_SLOT 0x0100 /* Slot implemented */ #define PCI_EXP_FLAGS_IRQ 0x3e00 /* Interrupt message number */ #define PCI_EXP_DEVCAP 4 /* Device capabilities */ -- Anthony PERARD
Anthony PERARD
2012-Feb-13 12:20 UTC
[PATCH V6 03/11] pci_regs: Add PCI_EXP_TYPE_PCIE_BRIDGE
Signed-off-by: Anthony PERARD <anthony.perard@citrix.com> --- hw/pci_regs.h | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) diff --git a/hw/pci_regs.h b/hw/pci_regs.h index 6b42515..56a404b 100644 --- a/hw/pci_regs.h +++ b/hw/pci_regs.h @@ -392,6 +392,7 @@ #define PCI_EXP_TYPE_UPSTREAM 0x5 /* Upstream Port */ #define PCI_EXP_TYPE_DOWNSTREAM 0x6 /* Downstream Port */ #define PCI_EXP_TYPE_PCI_BRIDGE 0x7 /* PCI/PCI-X Bridge */ +#define PCI_EXP_TYPE_PCIE_BRIDGE 0x8 /* PCI/PCI-X to PCIE Bridge */ #define PCI_EXP_TYPE_RC_END 0x9 /* Root Complex Integrated Endpoint */ #define PCI_EXP_TYPE_RC_EC 0xa /* Root Complex Event Collector */ #define PCI_EXP_FLAGS_SLOT 0x0100 /* Slot implemented */ -- Anthony PERARD
Anthony PERARD
2012-Feb-13 12:20 UTC
[PATCH V6 04/11] configure: Introduce --enable-xen-pci-passthrough.
Signed-off-by: Anthony PERARD <anthony.perard@citrix.com> --- configure | 25 +++++++++++++++++++++++++ 1 files changed, 25 insertions(+), 0 deletions(-) diff --git a/configure b/configure index 763db24..0787992 100755 --- a/configure +++ b/configure @@ -132,6 +132,7 @@ vnc_png="" vnc_thread="no" xen="" xen_ctrl_version="" +xen_pci_passthrough="" linux_aio="" cap_ng="" attr="" @@ -657,6 +658,10 @@ for opt do ;; --enable-xen) xen="yes" ;; + --disable-xen-pci-passthrough) xen_pci_passthrough="no" + ;; + --enable-xen-pci-passthrough) xen_pci_passthrough="yes" + ;; --disable-brlapi) brlapi="no" ;; --enable-brlapi) brlapi="yes" @@ -1005,6 +1010,8 @@ echo " (affects only QEMU, not qemu-img)" echo " --enable-mixemu enable mixer emulation" echo " --disable-xen disable xen backend driver support" echo " --enable-xen enable xen backend driver support" +echo " --disable-xen-pci-passthrough" +echo " --enable-xen-pci-passthrough" echo " --disable-brlapi disable BrlAPI" echo " --enable-brlapi enable BrlAPI" echo " --disable-vnc-tls disable TLS encryption for VNC server" @@ -1458,6 +1465,21 @@ EOF fi fi +if test "$xen_pci_passthrough" != "no"; then + if test "$xen" = "yes" && test "$linux" = "yes"; then + xen_pci_passthrough=yes + else + if test "$xen_pci_passthrough" = "yes"; then + echo "ERROR" + echo "ERROR: User requested feature Xen PCI Passthrough" + echo "ERROR: but this feature require /sys from Linux" + echo "ERROR" + exit 1; + fi + xen_pci_passthrough=no + fi +fi + ########################################## # pkg-config probe @@ -3592,6 +3614,9 @@ case "$target_arch2" in if test "$xen" = "yes" -a "$target_softmmu" = "yes" ; then target_phys_bits=64 echo "CONFIG_XEN=y" >> $config_target_mak + if test "$xen_pci_passthrough" = yes; then + echo "CONFIG_XEN_PCI_PASSTHROUGH=y" >> "$config_target_mak" + fi else echo "CONFIG_NO_XEN=y" >> $config_target_mak fi -- Anthony PERARD
Anthony PERARD
2012-Feb-13 12:20 UTC
[PATCH V6 05/11] Introduce HostPCIDevice to access a pci device on the host.
Signed-off-by: Anthony PERARD <anthony.perard@citrix.com> --- Makefile.target | 3 + hw/host-pci-device.c | 278 ++++++++++++++++++++++++++++++++++++++++++++++++++ hw/host-pci-device.h | 75 ++++++++++++++ 3 files changed, 356 insertions(+), 0 deletions(-) create mode 100644 hw/host-pci-device.c create mode 100644 hw/host-pci-device.h diff --git a/Makefile.target b/Makefile.target index 68481a3..92f375b 100644 --- a/Makefile.target +++ b/Makefile.target @@ -216,6 +216,9 @@ obj-$(CONFIG_NO_XEN) += xen-stub.o obj-i386-$(CONFIG_XEN) += xen_platform.o +# Xen PCI Passthrough +obj-i386-$(CONFIG_XEN_PCI_PASSTHROUGH) += host-pci-device.o + # Inter-VM PCI shared memory CONFIG_IVSHMEM ifeq ($(CONFIG_KVM), y) diff --git a/hw/host-pci-device.c b/hw/host-pci-device.c new file mode 100644 index 0000000..3dacb30 --- /dev/null +++ b/hw/host-pci-device.c @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2011 Citrix Ltd. + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + * + */ + +#include "qemu-common.h" +#include "host-pci-device.h" + +#define PCI_MAX_EXT_CAP \ + ((PCIE_CONFIG_SPACE_SIZE - PCI_CONFIG_SPACE_SIZE) / (PCI_CAP_SIZEOF + 4)) + +enum error_code { + ERROR_SYNTAX = 1, +}; + +static int path_to(const HostPCIDevice *d, + const char *name, char *buf, ssize_t size) +{ + return snprintf(buf, size, "/sys/bus/pci/devices/%04x:%02x:%02x.%x/%s", + d->domain, d->bus, d->dev, d->func, name); +} + +static int get_resource(HostPCIDevice *d) +{ + int i, rc = 0; + FILE *f; + char path[PATH_MAX]; + unsigned long long start, end, flags, size; + + path_to(d, "resource", path, sizeof (path)); + f = fopen(path, "r"); + if (!f) { + fprintf(stderr, "Error: Can''t open %s: %s\n", path, strerror(errno)); + return -errno; + } + + for (i = 0; i < PCI_NUM_REGIONS; i++) { + if (fscanf(f, "%llx %llx %llx", &start, &end, &flags) != 3) { + fprintf(stderr, "Error: Syntax error in %s\n", path); + rc = ERROR_SYNTAX; + break; + } + if (start) { + size = end - start + 1; + } else { + size = 0; + } + + if (i < PCI_ROM_SLOT) { + d->io_regions[i].base_addr = start; + d->io_regions[i].size = size; + d->io_regions[i].flags = flags; + } else { + d->rom.base_addr = start; + d->rom.size = size; + d->rom.flags = flags; + } + } + + fclose(f); + return rc; +} + +static int get_hex_value(HostPCIDevice *d, const char *name, + unsigned long *pvalue) +{ + char path[PATH_MAX]; + FILE *f; + unsigned long value; + + path_to(d, name, path, sizeof (path)); + f = fopen(path, "r"); + if (!f) { + fprintf(stderr, "Error: Can''t open %s: %s\n", path, strerror(errno)); + return -errno; + } + if (fscanf(f, "%lx\n", &value) != 1) { + fprintf(stderr, "Error: Syntax error in %s\n", path); + fclose(f); + return ERROR_SYNTAX; + } + fclose(f); + *pvalue = value; + return 0; +} + +static bool pci_dev_is_virtfn(HostPCIDevice *d) +{ + char path[PATH_MAX]; + struct stat buf; + + path_to(d, "physfn", path, sizeof (path)); + return !stat(path, &buf); +} + +static int host_pci_config_fd(HostPCIDevice *d) +{ + char path[PATH_MAX]; + + if (d->config_fd < 0) { + path_to(d, "config", path, sizeof (path)); + d->config_fd = open(path, O_RDWR); + if (d->config_fd < 0) { + fprintf(stderr, "HostPCIDevice: Can not open ''%s'': %s\n", + path, strerror(errno)); + } + } + return d->config_fd; +} +static int host_pci_config_read(HostPCIDevice *d, int pos, void *buf, int len) +{ + int fd = host_pci_config_fd(d); + int res = 0; + +again: + res = pread(fd, buf, len, pos); + if (res != len) { + if (res < 0 && (errno == EINTR || errno == EAGAIN)) { + goto again; + } + fprintf(stderr, "%s: read failed: %s (fd: %i)\n", + __func__, strerror(errno), fd); + return -errno; + } + return 0; +} +static int host_pci_config_write(HostPCIDevice *d, + int pos, const void *buf, int len) +{ + int fd = host_pci_config_fd(d); + int res = 0; + +again: + res = pwrite(fd, buf, len, pos); + if (res != len) { + if (res < 0 && (errno == EINTR || errno == EAGAIN)) { + goto again; + } + fprintf(stderr, "%s: write failed: %s\n", + __func__, strerror(errno)); + return -errno; + } + return 0; +} + +int host_pci_get_byte(HostPCIDevice *d, int pos, uint8_t *p) +{ + uint8_t buf; + int rc = host_pci_config_read(d, pos, &buf, 1); + if (rc == 0) { + *p = buf; + } + return rc; +} +int host_pci_get_word(HostPCIDevice *d, int pos, uint16_t *p) +{ + uint16_t buf; + int rc = host_pci_config_read(d, pos, &buf, 2); + if (rc == 0) { + *p = le16_to_cpu(buf); + } + return rc; +} +int host_pci_get_long(HostPCIDevice *d, int pos, uint32_t *p) +{ + uint32_t buf; + int rc = host_pci_config_read(d, pos, &buf, 4); + if (rc == 0) { + *p = le32_to_cpu(buf); + } + return rc; +} +int host_pci_get_block(HostPCIDevice *d, int pos, uint8_t *buf, int len) +{ + return host_pci_config_read(d, pos, buf, len); +} + +int host_pci_set_byte(HostPCIDevice *d, int pos, uint8_t data) +{ + return host_pci_config_write(d, pos, &data, 1); +} +int host_pci_set_word(HostPCIDevice *d, int pos, uint16_t data) +{ + data = cpu_to_le16(data); + return host_pci_config_write(d, pos, &data, 2); +} +int host_pci_set_long(HostPCIDevice *d, int pos, uint32_t data) +{ + data = cpu_to_le32(data); + return host_pci_config_write(d, pos, &data, 4); +} +int host_pci_set_block(HostPCIDevice *d, int pos, uint8_t *buf, int len) +{ + return host_pci_config_write(d, pos, buf, len); +} + +uint32_t host_pci_find_ext_cap_offset(HostPCIDevice *d, uint32_t cap) +{ + uint32_t header = 0; + int max_cap = PCI_MAX_EXT_CAP; + int pos = PCI_CONFIG_SPACE_SIZE; + + do { + if (host_pci_get_long(d, pos, &header)) { + break; + } + /* + * If we have no capabilities, this is indicated by cap ID, + * cap version and next pointer all being 0. + */ + if (header == 0) { + break; + } + + if (PCI_EXT_CAP_ID(header) == cap) { + return pos; + } + + pos = PCI_EXT_CAP_NEXT(header); + if (pos < PCI_CONFIG_SPACE_SIZE) { + break; + } + + max_cap--; + } while (max_cap > 0); + + return 0; +} + +HostPCIDevice *host_pci_device_get(uint8_t bus, uint8_t dev, uint8_t func) +{ + HostPCIDevice *d = NULL; + unsigned long v = 0; + + d = g_new0(HostPCIDevice, 1); + + d->config_fd = -1; + d->domain = 0; + d->bus = bus; + d->dev = dev; + d->func = func; + + if (host_pci_config_fd(d) == -1) { + goto error; + } + if (get_resource(d) != 0) { + goto error; + } + + if (get_hex_value(d, "vendor", &v)) { + goto error; + } + d->vendor_id = v; + if (get_hex_value(d, "device", &v)) { + goto error; + } + d->device_id = v; + d->is_virtfn = pci_dev_is_virtfn(d); + + return d; +error: + if (d->config_fd >= 0) { + close(d->config_fd); + } + g_free(d); + return NULL; +} + +void host_pci_device_put(HostPCIDevice *d) +{ + if (d->config_fd >= 0) { + close(d->config_fd); + } + g_free(d); +} diff --git a/hw/host-pci-device.h b/hw/host-pci-device.h new file mode 100644 index 0000000..c8880eb --- /dev/null +++ b/hw/host-pci-device.h @@ -0,0 +1,75 @@ +#ifndef HW_HOST_PCI_DEVICE +# define HW_HOST_PCI_DEVICE + +#include "pci.h" + +/* + * from linux/ioport.h + * IO resources have these defined flags. + */ +#define IORESOURCE_BITS 0x000000ff /* Bus-specific bits */ + +#define IORESOURCE_TYPE_BITS 0x00000f00 /* Resource type */ +#define IORESOURCE_IO 0x00000100 +#define IORESOURCE_MEM 0x00000200 +#define IORESOURCE_IRQ 0x00000400 +#define IORESOURCE_DMA 0x00000800 + +#define IORESOURCE_PREFETCH 0x00001000 /* No side effects */ +#define IORESOURCE_READONLY 0x00002000 +#define IORESOURCE_CACHEABLE 0x00004000 +#define IORESOURCE_RANGELENGTH 0x00008000 +#define IORESOURCE_SHADOWABLE 0x00010000 + +#define IORESOURCE_SIZEALIGN 0x00020000 /* size indicates alignment */ +#define IORESOURCE_STARTALIGN 0x00040000 /* start field is alignment */ + +#define IORESOURCE_MEM_64 0x00100000 + + /* Userland may not map this resource */ +#define IORESOURCE_EXCLUSIVE 0x08000000 +#define IORESOURCE_DISABLED 0x10000000 +#define IORESOURCE_UNSET 0x20000000 +#define IORESOURCE_AUTO 0x40000000 + /* Driver has marked this resource busy */ +#define IORESOURCE_BUSY 0x80000000 + + +typedef struct HostPCIIORegion { + unsigned long flags; + pcibus_t base_addr; + pcibus_t size; +} HostPCIIORegion; + +typedef struct HostPCIDevice { + uint16_t domain; + uint8_t bus; + uint8_t dev; + uint8_t func; + + uint16_t vendor_id; + uint16_t device_id; + + HostPCIIORegion io_regions[PCI_NUM_REGIONS - 1]; + HostPCIIORegion rom; + + bool is_virtfn; + + int config_fd; +} HostPCIDevice; + +HostPCIDevice *host_pci_device_get(uint8_t bus, uint8_t dev, uint8_t func); +void host_pci_device_put(HostPCIDevice *pci_dev); + +int host_pci_get_byte(HostPCIDevice *d, int pos, uint8_t *p); +int host_pci_get_word(HostPCIDevice *d, int pos, uint16_t *p); +int host_pci_get_long(HostPCIDevice *d, int pos, uint32_t *p); +int host_pci_get_block(HostPCIDevice *d, int pos, uint8_t *buf, int len); +int host_pci_set_byte(HostPCIDevice *d, int pos, uint8_t data); +int host_pci_set_word(HostPCIDevice *d, int pos, uint16_t data); +int host_pci_set_long(HostPCIDevice *d, int pos, uint32_t data); +int host_pci_set_block(HostPCIDevice *d, int pos, uint8_t *buf, int len); + +uint32_t host_pci_find_ext_cap_offset(HostPCIDevice *s, uint32_t cap); + +#endif /* !HW_HOST_PCI_DEVICE */ -- Anthony PERARD
From: Yuji Shimada <shimada-yxb@necst.nec.co.jp> This function helps Xen PCI Passthrough device to check for overlap. Signed-off-by: Yuji Shimada <shimada-yxb@necst.nec.co.jp> Signed-off-by: Anthony PERARD <anthony.perard@citrix.com> --- hw/pci.c | 47 +++++++++++++++++++++++++++++++++++++++++++++++ hw/pci.h | 3 +++ 2 files changed, 50 insertions(+), 0 deletions(-) diff --git a/hw/pci.c b/hw/pci.c index 5f4f80e..ebb5de9 100644 --- a/hw/pci.c +++ b/hw/pci.c @@ -1985,6 +1985,53 @@ MemoryRegion *pci_address_space_io(PCIDevice *dev) return dev->bus->address_space_io; } +int pci_check_bar_overlap(PCIDevice *dev, + pcibus_t addr, pcibus_t size, uint8_t type) +{ + PCIBus *bus = dev->bus; + PCIDevice *devices = NULL; + PCIIORegion *r; + int i, j; + int rc = 0; + + /* check Overlapped to Base Address */ + for (i = 0; i < ARRAY_SIZE(bus->devices); i++) { + devices = bus->devices[i]; + if (!devices) { + continue; + } + + /* skip itself */ + if (devices->devfn == dev->devfn) { + continue; + } + + for (j = 0; j < PCI_NUM_REGIONS; j++) { + r = &devices->io_regions[j]; + + /* skip different resource type, but don''t skip when + * prefetch and non-prefetch memory are compared. + */ + if (type != r->type) { + if (type & PCI_BASE_ADDRESS_SPACE_IO || + r->type & PCI_BASE_ADDRESS_SPACE_IO) { + continue; + } + } + + if ((addr < (r->addr + r->size)) && ((addr + size) > r->addr)) { + printf("Overlapped to device[%02x:%02x.%x][Region:%d]" + "[Address:%"PRIx64"h][Size:%"PRIx64"h]\n", + pci_bus_num(bus), PCI_SLOT(devices->devfn), + PCI_FUNC(devices->devfn), j, r->addr, r->size); + rc = 1; + } + } + } + + return rc; +} + static void pci_device_class_init(ObjectClass *klass, void *data) { DeviceClass *k = DEVICE_CLASS(klass); diff --git a/hw/pci.h b/hw/pci.h index 33b0b18..f05fda5 100644 --- a/hw/pci.h +++ b/hw/pci.h @@ -566,4 +566,7 @@ extern const VMStateDescription vmstate_pci_device; .offset = vmstate_offset_pointer(_state, _field, PCIDevice), \ } +int pci_check_bar_overlap(PCIDevice *dev, + pcibus_t addr, pcibus_t size, uint8_t type); + #endif -- Anthony PERARD
Anthony PERARD
2012-Feb-13 12:20 UTC
[PATCH V6 07/11] Introduce Xen PCI Passthrough, qdevice (1/3)
From: Allen Kay <allen.m.kay@intel.com> A more complete history can be found here: git://xenbits.xensource.com/qemu-xen-unstable.git Signed-off-by: Allen Kay <allen.m.kay@intel.com> Signed-off-by: Guy Zana <guy@neocleus.com> Signed-off-by: Anthony PERARD <anthony.perard@citrix.com> --- Makefile.target | 2 + hw/xen_common.h | 3 + hw/xen_pci_passthrough.c | 814 ++++++++++++++++++++++++++++++++++ hw/xen_pci_passthrough.h | 263 +++++++++++ hw/xen_pci_passthrough_config_init.c | 11 + xen-all.c | 12 + 6 files changed, 1105 insertions(+), 0 deletions(-) create mode 100644 hw/xen_pci_passthrough.c create mode 100644 hw/xen_pci_passthrough.h create mode 100644 hw/xen_pci_passthrough_config_init.c diff --git a/Makefile.target b/Makefile.target index 92f375b..8fc2ca3 100644 --- a/Makefile.target +++ b/Makefile.target @@ -218,6 +218,8 @@ obj-i386-$(CONFIG_XEN) += xen_platform.o # Xen PCI Passthrough obj-i386-$(CONFIG_XEN_PCI_PASSTHROUGH) += host-pci-device.o +obj-i386-$(CONFIG_XEN_PCI_PASSTHROUGH) += xen_pci_passthrough.o +obj-i386-$(CONFIG_XEN_PCI_PASSTHROUGH) += xen_pci_passthrough_config_init.o # Inter-VM PCI shared memory CONFIG_IVSHMEM diff --git a/hw/xen_common.h b/hw/xen_common.h index 0409ac7..48916fd 100644 --- a/hw/xen_common.h +++ b/hw/xen_common.h @@ -135,4 +135,7 @@ static inline int xc_fd(xc_interface *xen_xc) void destroy_hvm_domain(void); +/* shutdown/destroy current domain because of an error */ +void xen_shutdown_fatal_error(const char *fmt, ...) GCC_FMT_ATTR(1, 2); + #endif /* QEMU_HW_XEN_COMMON_H */ diff --git a/hw/xen_pci_passthrough.c b/hw/xen_pci_passthrough.c new file mode 100644 index 0000000..4ab1218 --- /dev/null +++ b/hw/xen_pci_passthrough.c @@ -0,0 +1,814 @@ +/* + * Copyright (c) 2007, Neocleus Corporation. + * Copyright (c) 2007, Intel Corporation. + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + * + * Alex Novik <alex@neocleus.com> + * Allen Kay <allen.m.kay@intel.com> + * Guy Zana <guy@neocleus.com> + * + * This file implements direct PCI assignment to a HVM guest + */ + +/* + * Interrupt Disable policy: + * + * INTx interrupt: + * Initialize(register_real_device) + * Map INTx(xc_physdev_map_pirq): + * <fail> + * - Set real Interrupt Disable bit to ''1''. + * - Set machine_irq and assigned_device->machine_irq to ''0''. + * * Don''t bind INTx. + * + * Bind INTx(xc_domain_bind_pt_pci_irq): + * <fail> + * - Set real Interrupt Disable bit to ''1''. + * - Unmap INTx. + * - Decrement mapped_machine_irq[machine_irq] + * - Set assigned_device->machine_irq to ''0''. + * + * Write to Interrupt Disable bit by guest software(pt_cmd_reg_write) + * Write ''0'' + * - Set real bit to ''0'' if assigned_device->machine_irq isn''t ''0''. + * + * Write ''1'' + * - Set real bit to ''1''. + */ + +#include <sys/ioctl.h> + +#include "pci.h" +#include "xen.h" +#include "xen_backend.h" +#include "xen_pci_passthrough.h" + +#define PCI_BAR_ENTRIES (6) + +#define PT_NR_IRQS (256) +uint8_t mapped_machine_irq[PT_NR_IRQS] = {0}; + +void pt_log(const PCIDevice *d, const char *f, ...) +{ + va_list ap; + + va_start(ap, f); + if (d) { + fprintf(stderr, "[%02x:%02x.%x] ", pci_bus_num(d->bus), + PCI_SLOT(d->devfn), PCI_FUNC(d->devfn)); + } + vfprintf(stderr, f, ap); + va_end(ap); +} + + +/* Config Space */ +static int pt_pci_config_access_check(PCIDevice *d, uint32_t address, int len) +{ + /* check offset range */ + if (address >= 0xFF) { + PT_ERR(d, "Failed to access register with offset exceeding 0xFF. " + "(addr: 0x%02x, len: %d)\n", address, len); + return -1; + } + + /* check read size */ + if ((len != 1) && (len != 2) && (len != 4)) { + PT_ERR(d, "Failed to access register with invalid access length. " + "(addr: 0x%02x, len: %d)\n", address, len); + return -1; + } + + /* check offset alignment */ + if (address & (len - 1)) { + PT_ERR(d, "Failed to access register with invalid access size " + "alignment. (addr: 0x%02x, len: %d)\n", address, len); + return -1; + } + + return 0; +} + +int pt_bar_offset_to_index(uint32_t offset) +{ + int index = 0; + + /* check Exp ROM BAR */ + if (offset == PCI_ROM_ADDRESS) { + return PCI_ROM_SLOT; + } + + /* calculate BAR index */ + index = (offset - PCI_BASE_ADDRESS_0) >> 2; + if (index >= PCI_NUM_REGIONS) { + return -1; + } + + return index; +} + +static uint32_t pt_pci_read_config(PCIDevice *d, uint32_t addr, int len) +{ + XenPCIPassthroughState *s = DO_UPCAST(XenPCIPassthroughState, dev, d); + uint32_t val = 0; + XenPTRegGroup *reg_grp_entry = NULL; + XenPTReg *reg_entry = NULL; + int rc = 0; + int emul_len = 0; + uint32_t find_addr = addr; + + if (pt_pci_config_access_check(d, addr, len)) { + goto exit; + } + + /* find register group entry */ + reg_grp_entry = pt_find_reg_grp(s, addr); + if (reg_grp_entry) { + /* check 0-Hardwired register group */ + if (reg_grp_entry->reg_grp->grp_type == GRP_TYPE_HARDWIRED) { + /* no need to emulate, just return 0 */ + val = 0; + goto exit; + } + } + + /* read I/O device register value */ + rc = host_pci_get_block(s->real_device, addr, (uint8_t *)&val, len); + if (rc < 0) { + PT_ERR(d, "pci_read_block failed. return value: %d.\n", rc); + memset(&val, 0xff, len); + } + + /* just return the I/O device register value for + * passthrough type register group */ + if (reg_grp_entry == NULL) { + goto exit; + } + + /* adjust the read value to appropriate CFC-CFF window */ + val <<= (addr & 3) << 3; + emul_len = len; + + /* loop around the guest requested size */ + while (emul_len > 0) { + /* find register entry to be emulated */ + reg_entry = pt_find_reg(reg_grp_entry, find_addr); + if (reg_entry) { + XenPTRegInfo *reg = reg_entry->reg; + uint32_t real_offset = reg_grp_entry->base_offset + reg->offset; + uint32_t valid_mask = 0xFFFFFFFF >> ((4 - emul_len) << 3); + uint8_t *ptr_val = NULL; + + valid_mask <<= (find_addr - real_offset) << 3; + ptr_val = (uint8_t *)&val + (real_offset & 3); + + /* do emulation based on register size */ + switch (reg->size) { + case 1: + if (reg->u.b.read) { + rc = reg->u.b.read(s, reg_entry, ptr_val, valid_mask); + } + break; + case 2: + if (reg->u.w.read) { + rc = reg->u.w.read(s, reg_entry, + (uint16_t *)ptr_val, valid_mask); + } + break; + case 4: + if (reg->u.dw.read) { + rc = reg->u.dw.read(s, reg_entry, + (uint32_t *)ptr_val, valid_mask); + } + break; + } + + if (rc < 0) { + xen_shutdown_fatal_error("Internal error: Invalid read " + "emulation. (%s, rc: %d)\n", + __func__, rc); + return 0; + } + + /* calculate next address to find */ + emul_len -= reg->size; + if (emul_len > 0) { + find_addr = real_offset + reg->size; + } + } else { + /* nothing to do with passthrough type register, + * continue to find next byte */ + emul_len--; + find_addr++; + } + } + + /* need to shift back before returning them to pci bus emulator */ + val >>= ((addr & 3) << 3); + +exit: + PT_LOG_CONFIG(d, addr, val, len); + return val; +} + +static void pt_pci_write_config(PCIDevice *d, uint32_t addr, + uint32_t val, int len) +{ + XenPCIPassthroughState *s = DO_UPCAST(XenPCIPassthroughState, dev, d); + int index = 0; + XenPTRegGroup *reg_grp_entry = NULL; + int rc = 0; + uint32_t read_val = 0; + int emul_len = 0; + XenPTReg *reg_entry = NULL; + uint32_t find_addr = addr; + XenPTRegInfo *reg = NULL; + + if (pt_pci_config_access_check(d, addr, len)) { + return; + } + + PT_LOG_CONFIG(d, addr, val, len); + + /* check unused BAR register */ + index = pt_bar_offset_to_index(addr); + if ((index >= 0) && (val > 0 && val < PT_BAR_ALLF) && + (s->bases[index].bar_flag == PT_BAR_FLAG_UNUSED)) { + PT_WARN(d, "Guest attempt to set address to unused Base Address " + "Register. (addr: 0x%02x, len: %d)\n", addr, len); + } + + /* find register group entry */ + reg_grp_entry = pt_find_reg_grp(s, addr); + if (reg_grp_entry) { + /* check 0-Hardwired register group */ + if (reg_grp_entry->reg_grp->grp_type == GRP_TYPE_HARDWIRED) { + /* ignore silently */ + PT_WARN(d, "Access to 0-Hardwired register. " + "(addr: 0x%02x, len: %d)\n", addr, len); + return; + } + } + + rc = host_pci_get_block(s->real_device, addr, (uint8_t *)&read_val, len); + if (rc < 0) { + PT_ERR(d, "pci_read_block failed. return value: %d.\n", rc); + memset(&read_val, 0xff, len); + } + + /* pass directly to the real device for passthrough type register group */ + if (reg_grp_entry == NULL) { + goto out; + } + + pci_default_write_config(d, addr, val, len); + + /* adjust the read and write value to appropriate CFC-CFF window */ + read_val <<= (addr & 3) << 3; + val <<= (addr & 3) << 3; + emul_len = len; + + /* loop around the guest requested size */ + while (emul_len > 0) { + /* find register entry to be emulated */ + reg_entry = pt_find_reg(reg_grp_entry, find_addr); + if (reg_entry) { + reg = reg_entry->reg; + uint32_t real_offset = reg_grp_entry->base_offset + reg->offset; + uint32_t valid_mask = 0xFFFFFFFF >> ((4 - emul_len) << 3); + uint8_t *ptr_val = NULL; + + valid_mask <<= (find_addr - real_offset) << 3; + ptr_val = (uint8_t *)&val + (real_offset & 3); + + /* do emulation based on register size */ + switch (reg->size) { + case 1: + if (reg->u.b.write) { + rc = reg->u.b.write(s, reg_entry, ptr_val, + read_val >> ((real_offset & 3) << 3), + valid_mask); + } + break; + case 2: + if (reg->u.w.write) { + rc = reg->u.w.write(s, reg_entry, (uint16_t *)ptr_val, + (read_val >> ((real_offset & 3) << 3)), + valid_mask); + } + break; + case 4: + if (reg->u.dw.write) { + rc = reg->u.dw.write(s, reg_entry, (uint32_t *)ptr_val, + (read_val >> ((real_offset & 3) << 3)), + valid_mask); + } + break; + } + + if (rc < 0) { + xen_shutdown_fatal_error("Internal error: Invalid write" + " emulation. (%s, rc: %d)\n", + __func__, rc); + return; + } + + /* calculate next address to find */ + emul_len -= reg->size; + if (emul_len > 0) { + find_addr = real_offset + reg->size; + } + } else { + /* nothing to do with passthrough type register, + * continue to find next byte */ + emul_len--; + find_addr++; + } + } + + /* need to shift back before passing them to host_pci_device */ + val >>= (addr & 3) << 3; + +out: + if (!(reg && reg->no_wb)) { + /* unknown regs are passed through */ + rc = host_pci_set_block(s->real_device, addr, (uint8_t *)&val, len); + + if (rc < 0) { + PT_ERR(d, "pci_write_block failed. return value: %d.\n", rc); + } + } +} + +/* ioport/iomem space*/ +static void pt_iomem_map(XenPCIPassthroughState *s, int i, + pcibus_t e_phys, pcibus_t e_size) +{ + uint32_t old_ebase = s->bases[i].e_physbase; + bool first_map = s->bases[i].e_size == 0; + int rc = 0; + + s->bases[i].e_physbase = e_phys; + s->bases[i].e_size = e_size; + + PT_LOG(&s->dev, "BAR %i, e_phys=%#"PRIx64" maddr=%#"PRIx64 + " len=%#"PRIx64" first_map=%d\n", + i, e_phys, s->bases[i].access.maddr, e_size, first_map); + + if (e_size == 0) { + return; + } + + if (!first_map && old_ebase != PT_PCI_BAR_UNMAPPED) { + /* Remove old mapping */ + rc = xc_domain_memory_mapping(xen_xc, xen_domid, + old_ebase >> XC_PAGE_SHIFT, + s->bases[i].access.maddr >> XC_PAGE_SHIFT, + (e_size + XC_PAGE_SIZE - 1) >> XC_PAGE_SHIFT, + DPCI_REMOVE_MAPPING); + if (rc) { + PT_ERR(&s->dev, "remove old mapping failed! (rc: %i)\n", rc); + return; + } + } + + /* map only valid guest address */ + if (e_phys != PCI_BAR_UNMAPPED) { + /* Create new mapping */ + rc = xc_domain_memory_mapping(xen_xc, xen_domid, + s->bases[i].e_physbase >> XC_PAGE_SHIFT, + s->bases[i].access.maddr >> XC_PAGE_SHIFT, + (e_size+XC_PAGE_SIZE-1) >> XC_PAGE_SHIFT, + DPCI_ADD_MAPPING); + + if (rc) { + PT_ERR(&s->dev, "create new mapping failed! (rc: %i)\n", rc); + } + } +} + +static void pt_ioport_map(XenPCIPassthroughState *s, int i, + pcibus_t e_phys, pcibus_t e_size) +{ + uint32_t old_ebase = s->bases[i].e_physbase; + bool first_map = s->bases[i].e_size == 0; + int rc = 0; + + s->bases[i].e_physbase = e_phys; + s->bases[i].e_size = e_size; + + PT_LOG(&s->dev, "BAR %i, e_phys=%#04"PRIx64" pio_base=%#04"PRIx64 + " len=%"PRId64" first_map=%d\n", + i, e_phys, s->bases[i].access.pio_base, e_size, first_map); + + if (e_size == 0) { + return; + } + + if (!first_map && old_ebase != PT_PCI_BAR_UNMAPPED) { + /* Remove old mapping */ + rc = xc_domain_ioport_mapping(xen_xc, xen_domid, old_ebase, + s->bases[i].access.pio_base, e_size, + DPCI_REMOVE_MAPPING); + if (rc) { + PT_ERR(&s->dev, "remove old mapping failed! (rc: %i)\n", rc); + return; + } + } + + /* map only valid guest address (include 0) */ + if (e_phys != PCI_BAR_UNMAPPED) { + /* Create new mapping */ + rc = xc_domain_ioport_mapping(xen_xc, xen_domid, e_phys, + s->bases[i].access.pio_base, e_size, + DPCI_ADD_MAPPING); + if (rc) { + PT_ERR(&s->dev, "create new mapping failed! (rc: %i)\n", rc); + } + } + +} + + +/* mapping BAR */ + +void pt_bar_mapping_one(XenPCIPassthroughState *s, int bar, + int io_enable, int mem_enable) +{ + PCIDevice *d = &s->dev; + const PCIIORegion *r; + XenPTRegGroup *reg_grp_entry = NULL; + XenPTReg *reg_entry = NULL; + XenPTRegion *base = NULL; + pcibus_t r_size = 0, r_addr = PCI_BAR_UNMAPPED; + int rc = 0; + + r = &d->io_regions[bar]; + + /* check valid region */ + if (!r->size) { + return; + } + + base = &s->bases[bar]; + /* skip unused BAR or upper 64bit BAR */ + if ((base->bar_flag == PT_BAR_FLAG_UNUSED) + || (base->bar_flag == PT_BAR_FLAG_UPPER)) { + return; + } + + /* copy region address to temporary */ + r_addr = pci_get_bar_addr(d, bar); + + /* need unmapping in case I/O Space or Memory Space is disabled */ + if (((base->bar_flag == PT_BAR_FLAG_IO) && !io_enable) || + ((base->bar_flag == PT_BAR_FLAG_MEM) && !mem_enable)) { + r_addr = PCI_BAR_UNMAPPED; + } + /* or ROM address is disabled. */ + if ((bar == PCI_ROM_SLOT) && (r_addr != PCI_BAR_UNMAPPED)) { + reg_grp_entry = pt_find_reg_grp(s, PCI_ROM_ADDRESS); + if (reg_grp_entry) { + reg_entry = pt_find_reg(reg_grp_entry, PCI_ROM_ADDRESS); + if (reg_entry && !(reg_entry->data & PCI_ROM_ADDRESS_ENABLE)) { + r_addr = PCI_BAR_UNMAPPED; + } + } + } + + /* prevent guest software mapping memory resource to 00000000h */ + if ((base->bar_flag == PT_BAR_FLAG_MEM) && (r_addr == 0)) { + r_addr = PCI_BAR_UNMAPPED; + } + + r_size = pt_get_emul_size(base->bar_flag, r->size); + + rc = pci_check_bar_overlap(d, r_addr, r_size, r->type); + if (rc) { + PT_WARN(d, "Region: %d (addr: %#"FMT_PCIBUS + ", len: %#"FMT_PCIBUS") is overlapped.\n", + bar, r_addr, r_size); + } + + /* check whether we need to update the mapping or not */ + if (r_addr != s->bases[bar].e_physbase) { + /* mapping BAR */ + if (base->bar_flag == PT_BAR_FLAG_IO) { + pt_ioport_map(s, bar, r_addr, r_size); + } else { + pt_iomem_map(s, bar, r_addr, r_size); + } + } +} + +void pt_bar_mapping(XenPCIPassthroughState *s, int io_enable, int mem_enable) +{ + int i; + + for (i = 0; i < PCI_NUM_REGIONS; i++) { + pt_bar_mapping_one(s, i, io_enable, mem_enable); + } +} + +static uint64_t bar_read(void *o, target_phys_addr_t addr, unsigned size) +{ + PCIDevice *d = o; + /* if this function is called, that probably means that there is a + * misconfiguration of the IOMMU. */ + PT_ERR(d, "Should not read BAR through QEMU. @0x"TARGET_FMT_plx"\n", addr); + return 0; +} +static void bar_write(void *o, target_phys_addr_t addr, + uint64_t data, unsigned size) +{ + PCIDevice *d = o; + /* Same comment as bar_read function */ + PT_ERR(d, "Should not write BAR through QEMU. @0x"TARGET_FMT_plx"\n", addr); +} + +static const MemoryRegionOps ops = { + .endianness = DEVICE_NATIVE_ENDIAN, + .read = bar_read, + .write = bar_write, +}; + +/* register regions */ +static int pt_register_regions(XenPCIPassthroughState *s) +{ + int i = 0; + HostPCIDevice *d = s->real_device; + + /* Register PIO/MMIO BARs */ + for (i = 0; i < PCI_BAR_ENTRIES; i++) { + HostPCIIORegion *r = &d->io_regions[i]; + uint8_t type; + + if (r->base_addr == 0 || r->size == 0) { + continue; + } + + s->bases[i].e_physbase = r->base_addr; + s->bases[i].access.u = r->base_addr; + + if (r->flags & IORESOURCE_IO) { + type = PCI_BASE_ADDRESS_SPACE_IO; + } else { + type = PCI_BASE_ADDRESS_SPACE_MEMORY; + if (r->flags & IORESOURCE_PREFETCH) { + type |= PCI_BASE_ADDRESS_MEM_PREFETCH; + } + } + + memory_region_init_io(&s->bar[i], &ops, &s->dev, + "xen-pci-pt-bar", r->size); + pci_register_bar(&s->dev, i, type, &s->bar[i]); + + PT_LOG(&s->dev, "IO region %i registered (size=0x%08"PRIx64 + " base_addr=0x%08"PRIx64" type: %#x)\n", + i, r->size, r->base_addr, type); + } + + /* Register expansion ROM address */ + if (d->rom.base_addr && d->rom.size) { + uint32_t bar_data = 0; + + /* Re-set BAR reported by OS, otherwise ROM can''t be read. */ + if (host_pci_get_long(d, PCI_ROM_ADDRESS, &bar_data)) { + return 0; + } + if ((bar_data & PCI_ROM_ADDRESS_MASK) == 0) { + bar_data |= d->rom.base_addr & PCI_ROM_ADDRESS_MASK; + host_pci_set_long(d, PCI_ROM_ADDRESS, bar_data); + } + + s->bases[PCI_ROM_SLOT].e_physbase = d->rom.base_addr; + s->bases[PCI_ROM_SLOT].access.maddr = d->rom.base_addr; + + memory_region_init_rom_device(&s->rom, NULL, NULL, + "xen-pci-pt-rom", d->rom.size); + pci_register_bar(&s->dev, PCI_ROM_SLOT, PCI_BASE_ADDRESS_MEM_PREFETCH, + &s->rom); + + PT_LOG(&s->dev, "Expansion ROM registered (size=0x%08"PRIx64 + " base_addr=0x%08"PRIx64")\n", + d->rom.size, d->rom.base_addr); + } + + return 0; +} + +static void pt_unregister_regions(XenPCIPassthroughState *s) +{ + int i, type, rc; + uint32_t e_size; + PCIDevice *d = &s->dev; + + for (i = 0; i < PCI_NUM_REGIONS; i++) { + e_size = s->bases[i].e_size; + if ((e_size == 0) || (s->bases[i].e_physbase == PT_PCI_BAR_UNMAPPED)) { + continue; + } + + type = d->io_regions[i].type; + + if (type & PCI_BASE_ADDRESS_SPACE_IO) { + rc = xc_domain_ioport_mapping(xen_xc, xen_domid, + s->bases[i].e_physbase, + s->bases[i].access.pio_base, + e_size, + DPCI_REMOVE_MAPPING); + if (rc != 0) { + PT_ERR(d, "remove old io mapping failed!\n"); + continue; + } + } else { + rc = xc_domain_memory_mapping(xen_xc, xen_domid, + s->bases[i].e_physbase >> XC_PAGE_SHIFT, + s->bases[i].access.maddr >> XC_PAGE_SHIFT, + (e_size + XC_PAGE_SIZE - 1) >> XC_PAGE_SHIFT, + DPCI_REMOVE_MAPPING); + if (rc != 0) { + PT_ERR(d, "remove old mem mapping failed!\n"); + continue; + } + } + } +} + +static int pt_initfn(PCIDevice *d) +{ + XenPCIPassthroughState *s = DO_UPCAST(XenPCIPassthroughState, dev, d); + int dom, bus; + unsigned slot, func; + int rc = 0; + uint8_t machine_irq = 0; + int pirq = PT_UNASSIGNED_PIRQ; + + if (pci_parse_devaddr(s->hostaddr, &dom, &bus, &slot, &func) < 0) { + PT_ERR(d, "Failed to parse BDF: %s\n", s->hostaddr); + return -1; + } + + /* register real device */ + PT_LOG(d, "Assigning real physical device %02x:%02x.%x" + " to devfn %#x\n", bus, slot, func, s->dev.devfn); + + s->real_device = host_pci_device_get(bus, slot, func); + if (!s->real_device) { + return -1; + } + + s->is_virtfn = s->real_device->is_virtfn; + if (s->is_virtfn) { + PT_LOG(d, "%04x:%02x:%02x.%x is a SR-IOV Virtual Function\n", + s->real_device->domain, bus, slot, func); + } + + /* Initialize virtualized PCI configuration (Extended 256 Bytes) */ + if (host_pci_get_block(s->real_device, 0, d->config, + PCI_CONFIG_SPACE_SIZE) == -1) { + host_pci_device_put(s->real_device); + return -1; + } + + /* Handle real device''s MMIO/PIO BARs */ + pt_register_regions(s); + + /* Bind interrupt */ + if (!s->dev.config[PCI_INTERRUPT_PIN]) { + PT_LOG(d, "no pin interrupt\n"); + goto out; + } + + host_pci_get_byte(s->real_device, PCI_INTERRUPT_LINE, &machine_irq); + rc = xc_physdev_map_pirq(xen_xc, xen_domid, machine_irq, &pirq); + + if (rc < 0) { + PT_ERR(d, "Mapping machine irq %u to pirq %i failed, (rc: %d)\n", + machine_irq, pirq, rc); + + /* Disable PCI intx assertion (turn on bit10 of devctl) */ + host_pci_set_word(s->real_device, + PCI_COMMAND, + pci_get_word(s->dev.config + PCI_COMMAND) + | PCI_COMMAND_INTX_DISABLE); + machine_irq = 0; + s->machine_irq = 0; + } else { + machine_irq = pirq; + s->machine_irq = pirq; + mapped_machine_irq[machine_irq]++; + } + + /* bind machine_irq to device */ + if (machine_irq != 0) { + uint8_t e_intx = pci_intx(s); + + rc = xc_domain_bind_pt_pci_irq(xen_xc, xen_domid, machine_irq, + pci_bus_num(d->bus), + PCI_SLOT(d->devfn), + e_intx); + if (rc < 0) { + PT_ERR(d, "Binding of interrupt %i failed! (rc: %d)\n", + e_intx, rc); + + /* Disable PCI intx assertion (turn on bit10 of devctl) */ + host_pci_set_word(s->real_device, PCI_COMMAND, + *(uint16_t *)(&s->dev.config[PCI_COMMAND]) + | PCI_COMMAND_INTX_DISABLE); + mapped_machine_irq[machine_irq]--; + + if (mapped_machine_irq[machine_irq] == 0) { + if (xc_physdev_unmap_pirq(xen_xc, xen_domid, machine_irq)) { + PT_ERR(d, "Unmapping of machine interrupt %i failed!" + " (rc: %d)\n", machine_irq, rc); + } + } + s->machine_irq = 0; + } + } + +out: + PT_LOG(d, "Real physical device %02x:%02x.%x registered successfuly!\n", + bus, slot, func); + + return 0; +} + +static int pt_unregister_device(PCIDevice *d) +{ + XenPCIPassthroughState *s = DO_UPCAST(XenPCIPassthroughState, dev, d); + uint8_t machine_irq = s->machine_irq; + uint8_t intx = pci_intx(s); + int rc; + + if (machine_irq) { + rc = xc_domain_unbind_pt_irq(xen_xc, xen_domid, machine_irq, + PT_IRQ_TYPE_PCI, + pci_bus_num(d->bus), + PCI_SLOT(s->dev.devfn), + intx, + 0 /* isa_irq */); + if (rc < 0) { + PT_ERR(d, "unbinding of interrupt INT%c failed." + " (machine irq: %i, rc: %d)" + " But bravely continuing on..\n", + ''a'' + intx, rc, machine_irq); + } + } + + if (machine_irq) { + mapped_machine_irq[machine_irq]--; + + if (mapped_machine_irq[machine_irq] == 0) { + rc = xc_physdev_unmap_pirq(xen_xc, xen_domid, machine_irq); + + if (rc < 0) { + PT_ERR(d, "unmapping of interrupt %i failed. (rc: %d)" + " But bravely continuing on..\n", + machine_irq, rc); + } + } + } + + /* unregister real device''s MMIO/PIO BARs */ + pt_unregister_regions(s); + + host_pci_device_put(s->real_device); + + return 0; +} + +static Property xen_pci_passthrough_properties[] = { + DEFINE_PROP_STRING("hostaddr", XenPCIPassthroughState, hostaddr), + DEFINE_PROP_END_OF_LIST(), +}; + +static void xen_pci_passthrough_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); + + k->init = pt_initfn; + k->exit = pt_unregister_device; + k->config_read = pt_pci_read_config; + k->config_write = pt_pci_write_config; + dc->desc = "Assign an host PCI device with Xen"; + dc->props = xen_pci_passthrough_properties; +}; + +static TypeInfo xen_pci_passthrough_info = { + .name = "xen-pci-passthrough", + .parent = TYPE_PCI_DEVICE, + .instance_size = sizeof(XenPCIPassthroughState), + .class_init = xen_pci_passthrough_class_init, +}; + +static void xen_pci_passthrough_register(void) +{ + type_register_static(&xen_pci_passthrough_info); +} + +device_init(xen_pci_passthrough_register); diff --git a/hw/xen_pci_passthrough.h b/hw/xen_pci_passthrough.h new file mode 100644 index 0000000..7a609b5 --- /dev/null +++ b/hw/xen_pci_passthrough.h @@ -0,0 +1,263 @@ +#ifndef QEMU_HW_XEN_PCI_PASSTHROUGH_H +# define QEMU_HW_XEN_PCI_PASSTHROUGH_H + +#include "qemu-common.h" +#include "xen_common.h" +#include "pci.h" +#include "host-pci-device.h" + +/* #define PT_LOGGING_ENABLED */ +/* #define PT_DEBUG_PCI_CONFIG_ACCESS */ + +void pt_log(const PCIDevice *d, const char *f, ...) GCC_FMT_ATTR(2, 3); + +#define PT_ERR(d, _f, _a...) pt_log(d, "%s: Error: " _f, __func__, ##_a) + +#ifdef PT_LOGGING_ENABLED +# define PT_LOG(d, _f, _a...) pt_log(d, "%s: " _f, __func__, ##_a) +# define PT_WARN(d, _f, _a...) pt_log(d, "%s: Warning: " _f, __func__, ##_a) +#else +# define PT_LOG(d, _f, _a...) +# define PT_WARN(d, _f, _a...) +#endif + +#ifdef PT_DEBUG_PCI_CONFIG_ACCESS +# define PT_LOG_CONFIG(d, addr, val, len) \ + pt_log(d, "%s: address=0x%04x val=0x%08x len=%d\n", \ + __func__, addr, val, len) +#else +# define PT_LOG_CONFIG(d, addr, val, len) +#endif + + +typedef struct XenPTRegInfo XenPTRegInfo; +typedef struct XenPTReg XenPTReg; + +typedef struct XenPCIPassthroughState XenPCIPassthroughState; + +/* function type for config reg */ +typedef int (*conf_reg_init) + (XenPCIPassthroughState *, XenPTRegInfo *, uint32_t real_offset, + uint32_t *data); +typedef int (*conf_dword_write) + (XenPCIPassthroughState *, XenPTReg *cfg_entry, + uint32_t *val, uint32_t dev_value, uint32_t valid_mask); +typedef int (*conf_word_write) + (XenPCIPassthroughState *, XenPTReg *cfg_entry, + uint16_t *val, uint16_t dev_value, uint16_t valid_mask); +typedef int (*conf_byte_write) + (XenPCIPassthroughState *, XenPTReg *cfg_entry, + uint8_t *val, uint8_t dev_value, uint8_t valid_mask); +typedef int (*conf_dword_read) + (XenPCIPassthroughState *, XenPTReg *cfg_entry, + uint32_t *val, uint32_t valid_mask); +typedef int (*conf_word_read) + (XenPCIPassthroughState *, XenPTReg *cfg_entry, + uint16_t *val, uint16_t valid_mask); +typedef int (*conf_byte_read) + (XenPCIPassthroughState *, XenPTReg *cfg_entry, + uint8_t *val, uint8_t valid_mask); +typedef int (*conf_dword_restore) + (XenPCIPassthroughState *, XenPTReg *cfg_entry, uint32_t real_offset, + uint32_t dev_value, uint32_t *val); +typedef int (*conf_word_restore) + (XenPCIPassthroughState *, XenPTReg *cfg_entry, uint32_t real_offset, + uint16_t dev_value, uint16_t *val); +typedef int (*conf_byte_restore) + (XenPCIPassthroughState *, XenPTReg *cfg_entry, uint32_t real_offset, + uint8_t dev_value, uint8_t *val); + +#define PT_BAR_ALLF 0xFFFFFFFF /* BAR ALLF value */ +#define PT_PCI_BAR_UNMAPPED (-1) + + +typedef enum { + GRP_TYPE_HARDWIRED = 0, /* 0 Hardwired reg group */ + GRP_TYPE_EMU, /* emul reg group */ +} RegisterGroupType; + +typedef enum { + PT_BAR_FLAG_MEM = 0, /* Memory type BAR */ + PT_BAR_FLAG_IO, /* I/O type BAR */ + PT_BAR_FLAG_UPPER, /* upper 64bit BAR */ + PT_BAR_FLAG_UNUSED, /* unused BAR */ +} PTBarFlag; + + +typedef struct XenPTRegion { + /* Virtual phys base & size */ + uint32_t e_physbase; + uint32_t e_size; + /* BAR flag */ + PTBarFlag bar_flag; + /* Translation of the emulated address */ + union { + uint64_t maddr; + uint64_t pio_base; + uint64_t u; + } access; +} XenPTRegion; + +/* XenPTRegInfo declaration + * - only for emulated register (either a part or whole bit). + * - for passthrough register that need special behavior (like interacting with + * other component), set emu_mask to all 0 and specify r/w func properly. + * - do NOT use ALL F for init_val, otherwise the tbl will not be registered. + */ + +/* emulated register infomation */ +struct XenPTRegInfo { + uint32_t offset; + uint32_t size; + uint32_t init_val; + /* reg read only field mask (ON:RO/ROS, OFF:other) */ + uint32_t ro_mask; + /* reg emulate field mask (ON:emu, OFF:passthrough) */ + uint32_t emu_mask; + /* no write back allowed */ + uint32_t no_wb; + conf_reg_init init; + /* read/write/restore function pointer + * for double_word/word/byte size */ + union { + struct { + conf_dword_write write; + conf_dword_read read; + conf_dword_restore restore; + } dw; + struct { + conf_word_write write; + conf_word_read read; + conf_word_restore restore; + } w; + struct { + conf_byte_write write; + conf_byte_read read; + conf_byte_restore restore; + } b; + } u; +}; + +/* emulated register management */ +struct XenPTReg { + QLIST_ENTRY(XenPTReg) entries; + XenPTRegInfo *reg; + uint32_t data; /* emulated value */ +}; + +typedef struct XenPTRegGroupInfo XenPTRegGroupInfo; + +/* emul reg group size initialize method */ +typedef int (*pt_reg_size_init_fn) + (XenPCIPassthroughState *, const XenPTRegGroupInfo *, + uint32_t base_offset, uint8_t *size); + +/* emulated register group infomation */ +struct XenPTRegGroupInfo { + uint8_t grp_id; + RegisterGroupType grp_type; + uint8_t grp_size; + pt_reg_size_init_fn size_init; + XenPTRegInfo *emu_reg_tbl; +}; + +/* emul register group management table */ +typedef struct XenPTRegGroup { + QLIST_ENTRY(XenPTRegGroup) entries; + const XenPTRegGroupInfo *reg_grp; + uint32_t base_offset; + uint8_t size; + QLIST_HEAD(, XenPTReg) reg_tbl_list; +} XenPTRegGroup; + + +#define PT_UNASSIGNED_PIRQ (-1) + +struct XenPCIPassthroughState { + PCIDevice dev; + + char *hostaddr; + bool is_virtfn; + HostPCIDevice *real_device; + XenPTRegion bases[PCI_NUM_REGIONS]; /* Access regions */ + QLIST_HEAD(, XenPTRegGroup) reg_grp_tbl; + + uint32_t machine_irq; + + MemoryRegion bar[PCI_NUM_REGIONS - 1]; + MemoryRegion rom; +}; + +int pt_config_init(XenPCIPassthroughState *s); +void pt_config_delete(XenPCIPassthroughState *s); +void pt_bar_mapping(XenPCIPassthroughState *s, int io_enable, int mem_enable); +void pt_bar_mapping_one(XenPCIPassthroughState *s, int bar, + int io_enable, int mem_enable); +XenPTRegGroup *pt_find_reg_grp(XenPCIPassthroughState *s, uint32_t address); +XenPTReg *pt_find_reg(XenPTRegGroup *reg_grp, uint32_t address); +int pt_bar_offset_to_index(uint32_t offset); + +static inline pcibus_t pt_get_emul_size(PTBarFlag flag, pcibus_t r_size) +{ + /* align resource size (memory type only) */ + if (flag == PT_BAR_FLAG_MEM) { + return (r_size + XC_PAGE_SIZE - 1) & XC_PAGE_MASK; + } else { + return r_size; + } +} + +/* INTx */ +/* The PCI Local Bus Specification, Rev. 3.0, + * Section 6.2.4 Miscellaneous Registers, pp 223 + * outlines 5 valid values for the interrupt pin (intx). + * 0: For devices (or device functions) that don''t use an interrupt in + * 1: INTA# + * 2: INTB# + * 3: INTC# + * 4: INTD# + * + * Xen uses the following 4 values for intx + * 0: INTA# + * 1: INTB# + * 2: INTC# + * 3: INTD# + * + * Observing that these list of values are not the same, pci_read_intx() + * uses the following mapping from hw to xen values. + * This seems to reflect the current usage within Xen. + * + * PCI hardware | Xen | Notes + * ----------------+-----+---------------------------------------------------- + * 0 | 0 | No interrupt + * 1 | 0 | INTA# + * 2 | 1 | INTB# + * 3 | 2 | INTC# + * 4 | 3 | INTD# + * any other value | 0 | This should never happen, log error message + */ + +static inline uint8_t pci_read_intx(XenPCIPassthroughState *s) +{ + uint8_t v = 0; + host_pci_get_byte(s->real_device, PCI_INTERRUPT_PIN, &v); + return v; +} + +static inline uint8_t pci_intx(XenPCIPassthroughState *s) +{ + uint8_t r_val = pci_read_intx(s); + + PT_LOG(&s->dev, "intx=%i\n", r_val); + if (r_val < 1 || r_val > 4) { + PT_LOG(&s->dev, "Interrupt pin read from hardware is out of range:" + " value=%i, acceptable range is 1 - 4\n", r_val); + r_val = 0; + } else { + r_val -= 1; + } + + return r_val; +} + +#endif /* !QEMU_HW_XEN_PCI_PASSTHROUGH_H */ diff --git a/hw/xen_pci_passthrough_config_init.c b/hw/xen_pci_passthrough_config_init.c new file mode 100644 index 0000000..1e9de64 --- /dev/null +++ b/hw/xen_pci_passthrough_config_init.c @@ -0,0 +1,11 @@ +#include "xen_pci_passthrough.h" + +XenPTRegGroup *pt_find_reg_grp(XenPCIPassthroughState *s, uint32_t address) +{ + return NULL; +} + +XenPTReg *pt_find_reg(XenPTRegGroup *reg_grp, uint32_t address) +{ + return NULL; +} diff --git a/xen-all.c b/xen-all.c index fd39168..e83ba2b 100644 --- a/xen-all.c +++ b/xen-all.c @@ -1013,3 +1013,15 @@ void xen_register_framebuffer(MemoryRegion *mr) { framebuffer = mr; } + +void xen_shutdown_fatal_error(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, "Will destroy the domain.\n"); + /* destroy the domain */ + qemu_system_shutdown_request(); +} -- Anthony PERARD
Anthony PERARD
2012-Feb-13 12:20 UTC
[PATCH V6 08/11] Introduce Xen PCI Passthrough, PCI config space helpers (2/3)
From: Allen Kay <allen.m.kay@intel.com> A more complete history can be found here: git://xenbits.xensource.com/qemu-xen-unstable.git Signed-off-by: Allen Kay <allen.m.kay@intel.com> Signed-off-by: Guy Zana <guy@neocleus.com> Signed-off-by: Anthony PERARD <anthony.perard@citrix.com> --- hw/xen_pci_passthrough.c | 10 + hw/xen_pci_passthrough.h | 2 + hw/xen_pci_passthrough_config_init.c | 1481 ++++++++++++++++++++++++++++++++++ 3 files changed, 1493 insertions(+), 0 deletions(-) diff --git a/hw/xen_pci_passthrough.c b/hw/xen_pci_passthrough.c index 4ab1218..bdc3690 100644 --- a/hw/xen_pci_passthrough.c +++ b/hw/xen_pci_passthrough.c @@ -676,6 +676,13 @@ static int pt_initfn(PCIDevice *d) /* Handle real device''s MMIO/PIO BARs */ pt_register_regions(s); + /* reinitialize each config register to be emulated */ + if (pt_config_init(s)) { + PT_ERR(d, "PCI Config space initialisation failed.\n"); + host_pci_device_put(s->real_device); + return -1; + } + /* Bind interrupt */ if (!s->dev.config[PCI_INTERRUPT_PIN]) { PT_LOG(d, "no pin interrupt\n"); @@ -773,6 +780,9 @@ static int pt_unregister_device(PCIDevice *d) } } + /* delete all emulated config registers */ + pt_config_delete(s); + /* unregister real device''s MMIO/PIO BARs */ pt_unregister_regions(s); diff --git a/hw/xen_pci_passthrough.h b/hw/xen_pci_passthrough.h index 7a609b5..0b9902d 100644 --- a/hw/xen_pci_passthrough.h +++ b/hw/xen_pci_passthrough.h @@ -70,6 +70,8 @@ typedef int (*conf_byte_restore) #define PT_BAR_ALLF 0xFFFFFFFF /* BAR ALLF value */ #define PT_PCI_BAR_UNMAPPED (-1) +#define PCI_CAP_MAX 48 + typedef enum { GRP_TYPE_HARDWIRED = 0, /* 0 Hardwired reg group */ diff --git a/hw/xen_pci_passthrough_config_init.c b/hw/xen_pci_passthrough_config_init.c index 1e9de64..2fb27ff 100644 --- a/hw/xen_pci_passthrough_config_init.c +++ b/hw/xen_pci_passthrough_config_init.c @@ -1,11 +1,1492 @@ +/* + * Copyright (c) 2007, Neocleus Corporation. + * Copyright (c) 2007, Intel Corporation. + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + * + * Alex Novik <alex@neocleus.com> + * Allen Kay <allen.m.kay@intel.com> + * Guy Zana <guy@neocleus.com> + * + * This file implements direct PCI assignment to a HVM guest + */ + +#include "qemu-timer.h" +#include "xen_backend.h" #include "xen_pci_passthrough.h" +#define PT_MERGE_VALUE(value, data, val_mask) \ + (((value) & (val_mask)) | ((data) & ~(val_mask))) + +#define PT_INVALID_REG 0xFFFFFFFF /* invalid register value */ + +/* prototype */ + +static int pt_ptr_reg_init(XenPCIPassthroughState *s, XenPTRegInfo *reg, + uint32_t real_offset, uint32_t *data); + + +/* helper */ + +/* A return value of 1 means the capability should NOT be exposed to guest. */ +static int pt_hide_dev_cap(const HostPCIDevice *d, uint8_t grp_id) +{ + switch (grp_id) { + case PCI_CAP_ID_EXP: + /* The PCI Express Capability Structure of the VF of Intel 82599 10GbE + * Controller looks trivial, e.g., the PCI Express Capabilities + * Register is 0. We should not try to expose it to guest. + * + * The datasheet is available at + * http://download.intel.com/design/network/datashts/82599_datasheet.pdf + * + * See ''Table 9.7. VF PCIe Configuration Space'' of the datasheet, the + * PCI Express Capability Structure of the VF of Intel 82599 10GbE + * Controller looks trivial, e.g., the PCI Express Capabilities + * Register is 0, so the Capability Version is 0 and + * pt_pcie_size_init() would fail. + */ + if (d->vendor_id == PCI_VENDOR_ID_INTEL && + d->device_id == PCI_DEVICE_ID_INTEL_82599_VF) { + return 1; + } + break; + } + return 0; +} + +/* find emulate register group entry */ XenPTRegGroup *pt_find_reg_grp(XenPCIPassthroughState *s, uint32_t address) { + XenPTRegGroup *entry = NULL; + + /* find register group entry */ + QLIST_FOREACH(entry, &s->reg_grp_tbl, entries) { + /* check address */ + if ((entry->base_offset <= address) + && ((entry->base_offset + entry->size) > address)) { + return entry; + } + } + + /* group entry not found */ return NULL; } +/* find emulate register entry */ XenPTReg *pt_find_reg(XenPTRegGroup *reg_grp, uint32_t address) { + XenPTReg *reg_entry = NULL; + XenPTRegInfo *reg = NULL; + uint32_t real_offset = 0; + + /* find register entry */ + QLIST_FOREACH(reg_entry, ®_grp->reg_tbl_list, entries) { + reg = reg_entry->reg; + real_offset = reg_grp->base_offset + reg->offset; + /* check address */ + if ((real_offset <= address) + && ((real_offset + reg->size) > address)) { + return reg_entry; + } + } + return NULL; } + +/* parse BAR */ +static PTBarFlag pt_bar_reg_parse(XenPCIPassthroughState *s, XenPTRegInfo *reg) +{ + PCIDevice *d = &s->dev; + XenPTRegion *region = NULL; + PCIIORegion *r; + int index = 0; + + /* check 64bit BAR */ + index = pt_bar_offset_to_index(reg->offset); + if ((0 < index) && (index < PCI_ROM_SLOT)) { + int flags = s->real_device->io_regions[index - 1].flags; + + if ((flags & IORESOURCE_MEM) && (flags & IORESOURCE_MEM_64)) { + region = &s->bases[index - 1]; + if (region->bar_flag != PT_BAR_FLAG_UPPER) { + return PT_BAR_FLAG_UPPER; + } + } + } + + /* check unused BAR */ + r = &d->io_regions[index]; + if (r->size == 0) { + return PT_BAR_FLAG_UNUSED; + } + + /* for ExpROM BAR */ + if (index == PCI_ROM_SLOT) { + return PT_BAR_FLAG_MEM; + } + + /* check BAR I/O indicator */ + if (s->real_device->io_regions[index].flags & IORESOURCE_IO) { + return PT_BAR_FLAG_IO; + } else { + return PT_BAR_FLAG_MEM; + } +} + + +/**************** + * general register functions + */ + +/* register initialization function */ + +static int pt_common_reg_init(XenPCIPassthroughState *s, + XenPTRegInfo *reg, uint32_t real_offset, + uint32_t *data) +{ + *data = reg->init_val; + return 0; +} + +/* Read register functions */ + +static int pt_byte_reg_read(XenPCIPassthroughState *s, XenPTReg *cfg_entry, + uint8_t *value, uint8_t valid_mask) +{ + XenPTRegInfo *reg = cfg_entry->reg; + uint8_t valid_emu_mask = 0; + + /* emulate byte register */ + valid_emu_mask = reg->emu_mask & valid_mask; + *value = PT_MERGE_VALUE(*value, cfg_entry->data, ~valid_emu_mask); + + return 0; +} +static int pt_word_reg_read(XenPCIPassthroughState *s, XenPTReg *cfg_entry, + uint16_t *value, uint16_t valid_mask) +{ + XenPTRegInfo *reg = cfg_entry->reg; + uint16_t valid_emu_mask = 0; + + /* emulate word register */ + valid_emu_mask = reg->emu_mask & valid_mask; + *value = PT_MERGE_VALUE(*value, cfg_entry->data, ~valid_emu_mask); + + return 0; +} +static int pt_long_reg_read(XenPCIPassthroughState *s, XenPTReg *cfg_entry, + uint32_t *value, uint32_t valid_mask) +{ + XenPTRegInfo *reg = cfg_entry->reg; + uint32_t valid_emu_mask = 0; + + /* emulate long register */ + valid_emu_mask = reg->emu_mask & valid_mask; + *value = PT_MERGE_VALUE(*value, cfg_entry->data, ~valid_emu_mask); + + return 0; +} + +/* Write register functions */ + +static int pt_byte_reg_write(XenPCIPassthroughState *s, XenPTReg *cfg_entry, + uint8_t *value, uint8_t dev_value, + uint8_t valid_mask) +{ + XenPTRegInfo *reg = cfg_entry->reg; + uint8_t writable_mask = 0; + uint8_t throughable_mask = 0; + + /* modify emulate register */ + writable_mask = reg->emu_mask & ~reg->ro_mask & valid_mask; + cfg_entry->data = PT_MERGE_VALUE(*value, cfg_entry->data, writable_mask); + + /* create value for writing to I/O device register */ + throughable_mask = ~reg->emu_mask & valid_mask; + *value = PT_MERGE_VALUE(*value, dev_value, throughable_mask); + + return 0; +} +static int pt_word_reg_write(XenPCIPassthroughState *s, XenPTReg *cfg_entry, + uint16_t *value, uint16_t dev_value, + uint16_t valid_mask) +{ + XenPTRegInfo *reg = cfg_entry->reg; + uint16_t writable_mask = 0; + uint16_t throughable_mask = 0; + + /* modify emulate register */ + writable_mask = reg->emu_mask & ~reg->ro_mask & valid_mask; + cfg_entry->data = PT_MERGE_VALUE(*value, cfg_entry->data, writable_mask); + + /* create value for writing to I/O device register */ + throughable_mask = ~reg->emu_mask & valid_mask; + *value = PT_MERGE_VALUE(*value, dev_value, throughable_mask); + + return 0; +} +static int pt_long_reg_write(XenPCIPassthroughState *s, XenPTReg *cfg_entry, + uint32_t *value, uint32_t dev_value, + uint32_t valid_mask) +{ + XenPTRegInfo *reg = cfg_entry->reg; + uint32_t writable_mask = 0; + uint32_t throughable_mask = 0; + + /* modify emulate register */ + writable_mask = reg->emu_mask & ~reg->ro_mask & valid_mask; + cfg_entry->data = PT_MERGE_VALUE(*value, cfg_entry->data, writable_mask); + + /* create value for writing to I/O device register */ + throughable_mask = ~reg->emu_mask & valid_mask; + *value = PT_MERGE_VALUE(*value, dev_value, throughable_mask); + + return 0; +} + +/* common restore register fonctions */ +static int pt_byte_reg_restore(XenPCIPassthroughState *s, XenPTReg *cfg_entry, + uint32_t real_offset, uint8_t dev_value, + uint8_t *value) +{ + XenPTRegInfo *reg = cfg_entry->reg; + PCIDevice *d = &s->dev; + + /* use I/O device register''s value as restore value */ + *value = pci_get_byte(d->config + real_offset); + + /* create value for restoring to I/O device register */ + *value = PT_MERGE_VALUE(*value, dev_value, reg->emu_mask); + + return 0; +} +static int pt_word_reg_restore(XenPCIPassthroughState *s, XenPTReg *cfg_entry, + uint32_t real_offset, uint16_t dev_value, + uint16_t *value) +{ + XenPTRegInfo *reg = cfg_entry->reg; + PCIDevice *d = &s->dev; + + /* use I/O device register''s value as restore value */ + *value = pci_get_word(d->config + real_offset); + + /* create value for restoring to I/O device register */ + *value = PT_MERGE_VALUE(*value, dev_value, reg->emu_mask); + + return 0; +} + + +/* XenPTRegInfo declaration + * - only for emulated register (either a part or whole bit). + * - for passthrough register that need special behavior (like interacting with + * other component), set emu_mask to all 0 and specify r/w func properly. + * - do NOT use ALL F for init_val, otherwise the tbl will not be registered. + */ + +/******************** + * Header Type0 + */ + +static int pt_vendor_reg_init(XenPCIPassthroughState *s, + XenPTRegInfo *reg, uint32_t real_offset, + uint32_t *data) +{ + *data = s->real_device->vendor_id; + return 0; +} +static int pt_device_reg_init(XenPCIPassthroughState *s, + XenPTRegInfo *reg, uint32_t real_offset, + uint32_t *data) +{ + *data = s->real_device->device_id; + return 0; +} +static int pt_status_reg_init(XenPCIPassthroughState *s, + XenPTRegInfo *reg, uint32_t real_offset, + uint32_t *data) +{ + XenPTRegGroup *reg_grp_entry = NULL; + XenPTReg *reg_entry = NULL; + uint32_t reg_field = 0; + + /* find Header register group */ + reg_grp_entry = pt_find_reg_grp(s, PCI_CAPABILITY_LIST); + if (reg_grp_entry) { + /* find Capabilities Pointer register */ + reg_entry = pt_find_reg(reg_grp_entry, PCI_CAPABILITY_LIST); + if (reg_entry) { + /* check Capabilities Pointer register */ + if (reg_entry->data) { + reg_field |= PCI_STATUS_CAP_LIST; + } else { + reg_field &= ~PCI_STATUS_CAP_LIST; + } + } else { + xen_shutdown_fatal_error("Internal error: Couldn''t find XenPTReg*" + " for Capabilities Pointer register." + " (%s)\n", __func__); + return -1; + } + } else { + xen_shutdown_fatal_error("Internal error: Couldn''t find XenPTRegGroup" + " for Header. (%s)\n", __func__); + return -1; + } + + *data = reg_field; + return 0; +} +static int pt_header_type_reg_init(XenPCIPassthroughState *s, + XenPTRegInfo *reg, uint32_t real_offset, + uint32_t *data) +{ + /* read PCI_HEADER_TYPE */ + *data = reg->init_val | 0x80; + return 0; +} + +/* initialize Interrupt Pin register */ +static int pt_irqpin_reg_init(XenPCIPassthroughState *s, + XenPTRegInfo *reg, uint32_t real_offset, + uint32_t *data) +{ + *data = pci_read_intx(s); + return 0; +} + +/* Command register */ +static int pt_cmd_reg_read(XenPCIPassthroughState *s, XenPTReg *cfg_entry, + uint16_t *value, uint16_t valid_mask) +{ + XenPTRegInfo *reg = cfg_entry->reg; + uint16_t valid_emu_mask = 0; + uint16_t emu_mask = reg->emu_mask; + + if (s->is_virtfn) { + emu_mask |= PCI_COMMAND_MEMORY; + } + + /* emulate word register */ + valid_emu_mask = emu_mask & valid_mask; + *value = PT_MERGE_VALUE(*value, cfg_entry->data, ~valid_emu_mask); + + return 0; +} +static int pt_cmd_reg_write(XenPCIPassthroughState *s, XenPTReg *cfg_entry, + uint16_t *value, uint16_t dev_value, + uint16_t valid_mask) +{ + XenPTRegInfo *reg = cfg_entry->reg; + uint16_t writable_mask = 0; + uint16_t throughable_mask = 0; + uint16_t wr_value = *value; + uint16_t emu_mask = reg->emu_mask; + + if (s->is_virtfn) { + emu_mask |= PCI_COMMAND_MEMORY; + } + + /* modify emulate register */ + writable_mask = ~reg->ro_mask & valid_mask; + cfg_entry->data = PT_MERGE_VALUE(*value, cfg_entry->data, writable_mask); + + /* create value for writing to I/O device register */ + throughable_mask = ~emu_mask & valid_mask; + + if (*value & PCI_COMMAND_INTX_DISABLE) { + throughable_mask |= PCI_COMMAND_INTX_DISABLE; + } else { + if (s->machine_irq) { + throughable_mask |= PCI_COMMAND_INTX_DISABLE; + } + } + + *value = PT_MERGE_VALUE(*value, dev_value, throughable_mask); + + pt_bar_mapping(s, wr_value & PCI_COMMAND_IO, + wr_value & PCI_COMMAND_MEMORY); + + return 0; +} +static int pt_cmd_reg_restore(XenPCIPassthroughState *s, XenPTReg *cfg_entry, + uint32_t real_offset, uint16_t dev_value, + uint16_t *value) +{ + XenPTRegInfo *reg = cfg_entry->reg; + PCIDevice *d = &s->dev; + uint16_t restorable_mask = 0; + + /* use I/O device register''s value as restore value */ + *value = pci_get_word(d->config + real_offset); + + /* create value for restoring the I/O device register + * but do not include Fast Back-to-Back Enable bit. + */ + restorable_mask = reg->emu_mask & ~PCI_COMMAND_FAST_BACK; + *value = PT_MERGE_VALUE(*value, dev_value, restorable_mask); + + if (!s->machine_irq) { + *value |= PCI_COMMAND_INTX_DISABLE; + } else { + *value &= ~PCI_COMMAND_INTX_DISABLE; + } + + return 0; +} + +/* BAR */ +#define PT_BAR_MEM_RO_MASK 0x0000000F /* BAR ReadOnly mask(Memory) */ +#define PT_BAR_MEM_EMU_MASK 0xFFFFFFF0 /* BAR emul mask(Memory) */ +#define PT_BAR_IO_RO_MASK 0x00000003 /* BAR ReadOnly mask(I/O) */ +#define PT_BAR_IO_EMU_MASK 0xFFFFFFFC /* BAR emul mask(I/O) */ + +static inline uint32_t base_address_with_flags(HostPCIIORegion *hr) +{ + if ((hr->flags & PCI_BASE_ADDRESS_SPACE) == PCI_BASE_ADDRESS_SPACE_IO) { + return hr->base_addr | (hr->flags & ~PCI_BASE_ADDRESS_IO_MASK); + } else { + return hr->base_addr | (hr->flags & ~PCI_BASE_ADDRESS_MEM_MASK); + } +} + +static int pt_bar_reg_init(XenPCIPassthroughState *s, XenPTRegInfo *reg, + uint32_t real_offset, uint32_t *data) +{ + uint32_t reg_field = 0; + int index; + + index = pt_bar_offset_to_index(reg->offset); + if (index < 0 || index >= PCI_NUM_REGIONS) { + PT_ERR(&s->dev, "Internal error: Invalid BAR index [%d].\n", index); + return -1; + } + + s->bases[index].e_physbase = PT_PCI_BAR_UNMAPPED; + + /* set BAR flag */ + s->bases[index].bar_flag = pt_bar_reg_parse(s, reg); + if (s->bases[index].bar_flag == PT_BAR_FLAG_UNUSED) { + reg_field = PT_INVALID_REG; + } + + *data = reg_field; + return 0; +} +static int pt_bar_reg_read(XenPCIPassthroughState *s, XenPTReg *cfg_entry, + uint32_t *value, uint32_t valid_mask) +{ + XenPTRegInfo *reg = cfg_entry->reg; + uint32_t valid_emu_mask = 0; + uint32_t bar_emu_mask = 0; + int index; + + /* get BAR index */ + index = pt_bar_offset_to_index(reg->offset); + if (index < 0 || index >= PCI_NUM_REGIONS) { + PT_ERR(&s->dev, "Internal error: Invalid BAR index [%d].\n", index); + return -1; + } + + /* use fixed-up value from kernel sysfs */ + *value = base_address_with_flags(&s->real_device->io_regions[index]); + + /* set emulate mask depend on BAR flag */ + switch (s->bases[index].bar_flag) { + case PT_BAR_FLAG_MEM: + bar_emu_mask = PT_BAR_MEM_EMU_MASK; + break; + case PT_BAR_FLAG_IO: + bar_emu_mask = PT_BAR_IO_EMU_MASK; + break; + case PT_BAR_FLAG_UPPER: + bar_emu_mask = PT_BAR_ALLF; + break; + default: + break; + } + + /* emulate BAR */ + valid_emu_mask = bar_emu_mask & valid_mask; + *value = PT_MERGE_VALUE(*value, cfg_entry->data, ~valid_emu_mask); + + return 0; +} +static int pt_bar_reg_write(XenPCIPassthroughState *s, XenPTReg *cfg_entry, + uint32_t *value, uint32_t dev_value, + uint32_t valid_mask) +{ + XenPTRegInfo *reg = cfg_entry->reg; + XenPTRegGroup *reg_grp_entry = NULL; + XenPTReg *reg_entry = NULL; + XenPTRegion *base = NULL; + PCIDevice *d = &s->dev; + const PCIIORegion *r; + uint32_t writable_mask = 0; + uint32_t throughable_mask = 0; + uint32_t bar_emu_mask = 0; + uint32_t bar_ro_mask = 0; + uint32_t r_size = 0; + int index = 0; + + index = pt_bar_offset_to_index(reg->offset); + if (index < 0 || index >= PCI_NUM_REGIONS) { + PT_ERR(d, "Internal error: Invalid BAR index [%d].\n", index); + return -1; + } + + r = &d->io_regions[index]; + base = &s->bases[index]; + r_size = pt_get_emul_size(base->bar_flag, r->size); + + /* set emulate mask and read-only mask values depend on the BAR flag */ + switch (s->bases[index].bar_flag) { + case PT_BAR_FLAG_MEM: + bar_emu_mask = PT_BAR_MEM_EMU_MASK; + bar_ro_mask = PT_BAR_MEM_RO_MASK | (r_size - 1); + break; + case PT_BAR_FLAG_IO: + bar_emu_mask = PT_BAR_IO_EMU_MASK; + bar_ro_mask = PT_BAR_IO_RO_MASK | (r_size - 1); + break; + case PT_BAR_FLAG_UPPER: + bar_emu_mask = PT_BAR_ALLF; + bar_ro_mask = 0; /* all upper 32bit are R/W */ + break; + default: + break; + } + + /* modify emulate register */ + writable_mask = bar_emu_mask & ~bar_ro_mask & valid_mask; + cfg_entry->data = PT_MERGE_VALUE(*value, cfg_entry->data, writable_mask); + + /* check whether we need to update the virtual region address or not */ + switch (s->bases[index].bar_flag) { + case PT_BAR_FLAG_MEM: + /* nothing to do */ + break; + case PT_BAR_FLAG_IO: + /* nothing to do */ + break; + case PT_BAR_FLAG_UPPER: + if (cfg_entry->data) { + if (cfg_entry->data != (PT_BAR_ALLF & ~bar_ro_mask)) { + PT_WARN(d, "Guest attempt to set high MMIO Base Address. " + "Ignore mapping. " + "(offset: 0x%02x, high address: 0x%08x)\n", + reg->offset, cfg_entry->data); + } + } + break; + default: + break; + } + + /* create value for writing to I/O device register */ + throughable_mask = ~bar_emu_mask & valid_mask; + *value = PT_MERGE_VALUE(*value, dev_value, throughable_mask); + + /* After BAR reg update, we need to remap BAR */ + reg_grp_entry = pt_find_reg_grp(s, PCI_COMMAND); + if (reg_grp_entry) { + reg_entry = pt_find_reg(reg_grp_entry, PCI_COMMAND); + if (reg_entry) { + pt_bar_mapping_one(s, index, reg_entry->data & PCI_COMMAND_IO, + reg_entry->data & PCI_COMMAND_MEMORY); + } + } + + return 0; +} +static int pt_bar_reg_restore(XenPCIPassthroughState *s, XenPTReg *cfg_entry, + uint32_t real_offset, uint32_t dev_value, + uint32_t *value) +{ + XenPTRegInfo *reg = cfg_entry->reg; + uint32_t bar_emu_mask = 0; + int index = 0; + + /* get BAR index */ + index = pt_bar_offset_to_index(reg->offset); + if (index < 0) { + PT_ERR(&s->dev, "Internal error: Invalid BAR index [%d].\n", index); + return -1; + } + + /* use value from kernel sysfs */ + if (s->bases[index].bar_flag == PT_BAR_FLAG_UPPER) { + *value = s->real_device->io_regions[index - 1].base_addr >> 32; + } else { + *value = base_address_with_flags(&s->real_device->io_regions[index]); + } + + /* set emulate mask depend on BAR flag */ + switch (s->bases[index].bar_flag) { + case PT_BAR_FLAG_MEM: + bar_emu_mask = PT_BAR_MEM_EMU_MASK; + break; + case PT_BAR_FLAG_IO: + bar_emu_mask = PT_BAR_IO_EMU_MASK; + break; + case PT_BAR_FLAG_UPPER: + bar_emu_mask = PT_BAR_ALLF; + break; + default: + break; + } + + /* create value for restoring to I/O device register */ + *value = PT_MERGE_VALUE(*value, dev_value, bar_emu_mask); + + return 0; +} + +/* write Exp ROM BAR */ +static int pt_exp_rom_bar_reg_write(XenPCIPassthroughState *s, + XenPTReg *cfg_entry, uint32_t *value, + uint32_t dev_value, uint32_t valid_mask) +{ + XenPTRegInfo *reg = cfg_entry->reg; + XenPTRegGroup *reg_grp_entry = NULL; + XenPTReg *reg_entry = NULL; + XenPTRegion *base = NULL; + PCIDevice *d = (PCIDevice *)&s->dev; + PCIIORegion *r; + uint32_t writable_mask = 0; + uint32_t throughable_mask = 0; + pcibus_t r_size = 0; + uint32_t bar_emu_mask = 0; + uint32_t bar_ro_mask = 0; + + r = &d->io_regions[PCI_ROM_SLOT]; + r_size = r->size; + base = &s->bases[PCI_ROM_SLOT]; + /* align memory type resource size */ + pt_get_emul_size(base->bar_flag, r_size); + + /* set emulate mask and read-only mask */ + bar_emu_mask = reg->emu_mask; + bar_ro_mask = (reg->ro_mask | (r_size - 1)) & ~PCI_ROM_ADDRESS_ENABLE; + + /* modify emulate register */ + writable_mask = ~bar_ro_mask & valid_mask; + cfg_entry->data = PT_MERGE_VALUE(*value, cfg_entry->data, writable_mask); + + /* update the corresponding virtual region address */ + /* + * When guest code tries to get block size of mmio, it will write all "1"s + * into pci bar register. In this case, cfg_entry->data == writable_mask. + * Especially for devices with large mmio, the value of writable_mask + * is likely to be a guest physical address that has been mapped to ram + * rather than mmio. Remapping this value to mmio should be prevented. + */ + + if (cfg_entry->data != writable_mask) { + r->addr = cfg_entry->data; + } + + /* create value for writing to I/O device register */ + throughable_mask = ~bar_emu_mask & valid_mask; + *value = PT_MERGE_VALUE(*value, dev_value, throughable_mask); + + /* After BAR reg update, we need to remap BAR*/ + reg_grp_entry = pt_find_reg_grp(s, PCI_COMMAND); + if (reg_grp_entry) { + reg_entry = pt_find_reg(reg_grp_entry, PCI_COMMAND); + if (reg_entry) { + pt_bar_mapping_one(s, PCI_ROM_SLOT, + reg_entry->data & PCI_COMMAND_IO, + reg_entry->data & PCI_COMMAND_MEMORY); + } + } + + return 0; +} +/* restore ROM BAR */ +static int pt_exp_rom_bar_reg_restore(XenPCIPassthroughState *s, + XenPTReg *cfg_entry, + uint32_t real_offset, + uint32_t dev_value, uint32_t *value) +{ + XenPTRegInfo *reg = cfg_entry->reg; + uint32_t v; + + if (host_pci_get_long(s->real_device, PCI_ROM_ADDRESS, &v)) { + return -1; + } + /* use value from kernel sysfs */ + *value = PT_MERGE_VALUE(v, dev_value, reg->emu_mask); + return 0; +} + +/* Header Type0 reg static infomation table */ +static XenPTRegInfo pt_emu_reg_header0_tbl[] = { + /* Vendor ID reg */ + { + .offset = PCI_VENDOR_ID, + .size = 2, + .init_val = 0x0000, + .ro_mask = 0xFFFF, + .emu_mask = 0xFFFF, + .init = pt_vendor_reg_init, + .u.w.read = pt_word_reg_read, + .u.w.write = pt_word_reg_write, + .u.w.restore = NULL, + }, + /* Device ID reg */ + { + .offset = PCI_DEVICE_ID, + .size = 2, + .init_val = 0x0000, + .ro_mask = 0xFFFF, + .emu_mask = 0xFFFF, + .init = pt_device_reg_init, + .u.w.read = pt_word_reg_read, + .u.w.write = pt_word_reg_write, + .u.w.restore = NULL, + }, + /* Command reg */ + { + .offset = PCI_COMMAND, + .size = 2, + .init_val = 0x0000, + .ro_mask = 0xF880, + .emu_mask = 0x0740, + .init = pt_common_reg_init, + .u.w.read = pt_cmd_reg_read, + .u.w.write = pt_cmd_reg_write, + .u.w.restore = pt_cmd_reg_restore, + }, + /* Capabilities Pointer reg */ + { + .offset = PCI_CAPABILITY_LIST, + .size = 1, + .init_val = 0x00, + .ro_mask = 0xFF, + .emu_mask = 0xFF, + .init = pt_ptr_reg_init, + .u.b.read = pt_byte_reg_read, + .u.b.write = pt_byte_reg_write, + .u.b.restore = NULL, + }, + /* Status reg */ + /* use emulated Cap Ptr value to initialize, + * so need to be declared after Cap Ptr reg + */ + { + .offset = PCI_STATUS, + .size = 2, + .init_val = 0x0000, + .ro_mask = 0x06FF, + .emu_mask = 0x0010, + .init = pt_status_reg_init, + .u.w.read = pt_word_reg_read, + .u.w.write = pt_word_reg_write, + .u.w.restore = NULL, + }, + /* Cache Line Size reg */ + { + .offset = PCI_CACHE_LINE_SIZE, + .size = 1, + .init_val = 0x00, + .ro_mask = 0x00, + .emu_mask = 0xFF, + .init = pt_common_reg_init, + .u.b.read = pt_byte_reg_read, + .u.b.write = pt_byte_reg_write, + .u.b.restore = pt_byte_reg_restore, + }, + /* Latency Timer reg */ + { + .offset = PCI_LATENCY_TIMER, + .size = 1, + .init_val = 0x00, + .ro_mask = 0x00, + .emu_mask = 0xFF, + .init = pt_common_reg_init, + .u.b.read = pt_byte_reg_read, + .u.b.write = pt_byte_reg_write, + .u.b.restore = pt_byte_reg_restore, + }, + /* Header Type reg */ + { + .offset = PCI_HEADER_TYPE, + .size = 1, + .init_val = 0x00, + .ro_mask = 0xFF, + .emu_mask = 0x00, + .init = pt_header_type_reg_init, + .u.b.read = pt_byte_reg_read, + .u.b.write = pt_byte_reg_write, + .u.b.restore = NULL, + }, + /* Interrupt Line reg */ + { + .offset = PCI_INTERRUPT_LINE, + .size = 1, + .init_val = 0x00, + .ro_mask = 0x00, + .emu_mask = 0xFF, + .init = pt_common_reg_init, + .u.b.read = pt_byte_reg_read, + .u.b.write = pt_byte_reg_write, + .u.b.restore = NULL, + }, + /* Interrupt Pin reg */ + { + .offset = PCI_INTERRUPT_PIN, + .size = 1, + .init_val = 0x00, + .ro_mask = 0xFF, + .emu_mask = 0xFF, + .init = pt_irqpin_reg_init, + .u.b.read = pt_byte_reg_read, + .u.b.write = pt_byte_reg_write, + .u.b.restore = NULL, + }, + /* BAR 0 reg */ + /* mask of BAR need to be decided later, depends on IO/MEM type */ + { + .offset = PCI_BASE_ADDRESS_0, + .size = 4, + .init_val = 0x00000000, + .init = pt_bar_reg_init, + .u.dw.read = pt_bar_reg_read, + .u.dw.write = pt_bar_reg_write, + .u.dw.restore = pt_bar_reg_restore, + }, + /* BAR 1 reg */ + { + .offset = PCI_BASE_ADDRESS_1, + .size = 4, + .init_val = 0x00000000, + .init = pt_bar_reg_init, + .u.dw.read = pt_bar_reg_read, + .u.dw.write = pt_bar_reg_write, + .u.dw.restore = pt_bar_reg_restore, + }, + /* BAR 2 reg */ + { + .offset = PCI_BASE_ADDRESS_2, + .size = 4, + .init_val = 0x00000000, + .init = pt_bar_reg_init, + .u.dw.read = pt_bar_reg_read, + .u.dw.write = pt_bar_reg_write, + .u.dw.restore = pt_bar_reg_restore, + }, + /* BAR 3 reg */ + { + .offset = PCI_BASE_ADDRESS_3, + .size = 4, + .init_val = 0x00000000, + .init = pt_bar_reg_init, + .u.dw.read = pt_bar_reg_read, + .u.dw.write = pt_bar_reg_write, + .u.dw.restore = pt_bar_reg_restore, + }, + /* BAR 4 reg */ + { + .offset = PCI_BASE_ADDRESS_4, + .size = 4, + .init_val = 0x00000000, + .init = pt_bar_reg_init, + .u.dw.read = pt_bar_reg_read, + .u.dw.write = pt_bar_reg_write, + .u.dw.restore = pt_bar_reg_restore, + }, + /* BAR 5 reg */ + { + .offset = PCI_BASE_ADDRESS_5, + .size = 4, + .init_val = 0x00000000, + .init = pt_bar_reg_init, + .u.dw.read = pt_bar_reg_read, + .u.dw.write = pt_bar_reg_write, + .u.dw.restore = pt_bar_reg_restore, + }, + /* Expansion ROM BAR reg */ + { + .offset = PCI_ROM_ADDRESS, + .size = 4, + .init_val = 0x00000000, + .ro_mask = 0x000007FE, + .emu_mask = 0xFFFFF800, + .init = pt_bar_reg_init, + .u.dw.read = pt_long_reg_read, + .u.dw.write = pt_exp_rom_bar_reg_write, + .u.dw.restore = pt_exp_rom_bar_reg_restore, + }, + { + .size = 0, + }, +}; + + +/********************************* + * Vital Product Data Capability + */ + +/* Vital Product Data Capability Structure reg static infomation table */ +static XenPTRegInfo pt_emu_reg_vpd_tbl[] = { + { + .offset = PCI_CAP_LIST_NEXT, + .size = 1, + .init_val = 0x00, + .ro_mask = 0xFF, + .emu_mask = 0xFF, + .init = pt_ptr_reg_init, + .u.b.read = pt_byte_reg_read, + .u.b.write = pt_byte_reg_write, + .u.b.restore = NULL, + }, + { + .size = 0, + }, +}; + + +/************************************** + * Vendor Specific Capability + */ + +/* Vendor Specific Capability Structure reg static infomation table */ +static XenPTRegInfo pt_emu_reg_vendor_tbl[] = { + { + .offset = PCI_CAP_LIST_NEXT, + .size = 1, + .init_val = 0x00, + .ro_mask = 0xFF, + .emu_mask = 0xFF, + .init = pt_ptr_reg_init, + .u.b.read = pt_byte_reg_read, + .u.b.write = pt_byte_reg_write, + .u.b.restore = NULL, + }, + { + .size = 0, + }, +}; + + +/***************************** + * PCI Express Capability + */ + +static inline uint8_t get_capability_version(XenPCIPassthroughState *s, + uint32_t offset) +{ + uint8_t flags = pci_get_byte(s->dev.config + offset + PCI_EXP_FLAGS); + return flags & PCI_EXP_FLAGS_VERS; +} + +static inline uint8_t get_device_type(XenPCIPassthroughState *s, + uint32_t offset) +{ + uint8_t flags = pci_get_byte(s->dev.config + offset + PCI_EXP_FLAGS); + return (flags & PCI_EXP_FLAGS_TYPE) >> 4; +} + +/* initialize Link Control register */ +static int pt_linkctrl_reg_init(XenPCIPassthroughState *s, + XenPTRegInfo *reg, uint32_t real_offset, + uint32_t *data) +{ + uint8_t cap_ver = get_capability_version(s, real_offset - reg->offset); + uint8_t dev_type = get_device_type(s, real_offset - reg->offset); + + /* no need to initialize in case of Root Complex Integrated Endpoint + * with cap_ver 1.x + */ + if ((dev_type == PCI_EXP_TYPE_RC_END) && (cap_ver == 1)) { + *data = PT_INVALID_REG; + } + + *data = reg->init_val; + return 0; +} +/* initialize Device Control 2 register */ +static int pt_devctrl2_reg_init(XenPCIPassthroughState *s, + XenPTRegInfo *reg, uint32_t real_offset, + uint32_t *data) +{ + uint8_t cap_ver = get_capability_version(s, real_offset - reg->offset); + + /* no need to initialize in case of cap_ver 1.x */ + if (cap_ver == 1) { + *data = PT_INVALID_REG; + } + + *data = reg->init_val; + return 0; +} +/* initialize Link Control 2 register */ +static int pt_linkctrl2_reg_init(XenPCIPassthroughState *s, + XenPTRegInfo *reg, uint32_t real_offset, + uint32_t *data) +{ + uint8_t cap_ver = get_capability_version(s, real_offset - reg->offset); + uint32_t reg_field = 0; + + /* no need to initialize in case of cap_ver 1.x */ + if (cap_ver == 1) { + reg_field = PT_INVALID_REG; + } else { + /* set Supported Link Speed */ + uint8_t lnkcap = pci_get_byte(s->dev.config + real_offset - reg->offset + + PCI_EXP_LNKCAP); + reg_field |= PCI_EXP_LNKCAP_SLS & lnkcap; + } + + *data = reg_field; + return 0; +} + +/* PCI Express Capability Structure reg static infomation table */ +static XenPTRegInfo pt_emu_reg_pcie_tbl[] = { + /* Next Pointer reg */ + { + .offset = PCI_CAP_LIST_NEXT, + .size = 1, + .init_val = 0x00, + .ro_mask = 0xFF, + .emu_mask = 0xFF, + .init = pt_ptr_reg_init, + .u.b.read = pt_byte_reg_read, + .u.b.write = pt_byte_reg_write, + .u.b.restore = NULL, + }, + /* Device Capabilities reg */ + { + .offset = PCI_EXP_DEVCAP, + .size = 4, + .init_val = 0x00000000, + .ro_mask = 0x1FFCFFFF, + .emu_mask = 0x10000000, + .init = pt_common_reg_init, + .u.dw.read = pt_long_reg_read, + .u.dw.write = pt_long_reg_write, + .u.dw.restore = NULL, + }, + /* Device Control reg */ + { + .offset = PCI_EXP_DEVCTL, + .size = 2, + .init_val = 0x2810, + .ro_mask = 0x8400, + .emu_mask = 0xFFFF, + .init = pt_common_reg_init, + .u.w.read = pt_word_reg_read, + .u.w.write = pt_word_reg_write, + .u.w.restore = pt_word_reg_restore, + }, + /* Link Control reg */ + { + .offset = PCI_EXP_LNKCTL, + .size = 2, + .init_val = 0x0000, + .ro_mask = 0xFC34, + .emu_mask = 0xFFFF, + .init = pt_linkctrl_reg_init, + .u.w.read = pt_word_reg_read, + .u.w.write = pt_word_reg_write, + .u.w.restore = pt_word_reg_restore, + }, + /* Device Control 2 reg */ + { + .offset = 0x28, + .size = 2, + .init_val = 0x0000, + .ro_mask = 0xFFE0, + .emu_mask = 0xFFFF, + .init = pt_devctrl2_reg_init, + .u.w.read = pt_word_reg_read, + .u.w.write = pt_word_reg_write, + .u.w.restore = pt_word_reg_restore, + }, + /* Link Control 2 reg */ + { + .offset = 0x30, + .size = 2, + .init_val = 0x0000, + .ro_mask = 0xE040, + .emu_mask = 0xFFFF, + .init = pt_linkctrl2_reg_init, + .u.w.read = pt_word_reg_read, + .u.w.write = pt_word_reg_write, + .u.w.restore = pt_word_reg_restore, + }, + { + .size = 0, + }, +}; + + +/**************************** + * Capabilities + */ + +/* capability structure register group size functions */ + +static int pt_reg_grp_size_init(XenPCIPassthroughState *s, + const XenPTRegGroupInfo *grp_reg, + uint32_t base_offset, uint8_t *size) +{ + *size = grp_reg->grp_size; + return 0; +} +/* get Vendor Specific Capability Structure register group size */ +static int pt_vendor_size_init(XenPCIPassthroughState *s, + const XenPTRegGroupInfo *grp_reg, + uint32_t base_offset, uint8_t *size) +{ + *size = pci_get_byte(s->dev.config + base_offset + 0x02); + return 0; +} +/* get PCI Express Capability Structure register group size */ +static int pt_pcie_size_init(XenPCIPassthroughState *s, + const XenPTRegGroupInfo *grp_reg, + uint32_t base_offset, uint8_t *size) +{ + PCIDevice *d = &s->dev; + uint8_t version = get_capability_version(s, base_offset); + uint8_t type = get_device_type(s, base_offset); + uint8_t pcie_size = 0; + + + /* calculate size depend on capability version and device/port type */ + /* in case of PCI Express Base Specification Rev 1.x */ + if (version == 1) { + /* The PCI Express Capabilities, Device Capabilities, and Device + * Status/Control registers are required for all PCI Express devices. + * The Link Capabilities and Link Status/Control are required for all + * Endpoints that are not Root Complex Integrated Endpoints. Endpoints + * are not required to implement registers other than those listed + * above and terminate the capability structure. + */ + switch (type) { + case PCI_EXP_TYPE_ENDPOINT: + case PCI_EXP_TYPE_LEG_END: + pcie_size = 0x14; + break; + case PCI_EXP_TYPE_RC_END: + /* has no link */ + pcie_size = 0x0C; + break; + /* only EndPoint passthrough is supported */ + case PCI_EXP_TYPE_ROOT_PORT: + case PCI_EXP_TYPE_UPSTREAM: + case PCI_EXP_TYPE_DOWNSTREAM: + case PCI_EXP_TYPE_PCI_BRIDGE: + case PCI_EXP_TYPE_PCIE_BRIDGE: + case PCI_EXP_TYPE_RC_EC: + default: + PT_ERR(d, "Unsupported device/port type %#x.\n", type); + return -1; + } + } + /* in case of PCI Express Base Specification Rev 2.0 */ + else if (version == 2) { + switch (type) { + case PCI_EXP_TYPE_ENDPOINT: + case PCI_EXP_TYPE_LEG_END: + case PCI_EXP_TYPE_RC_END: + /* For Functions that do not implement the registers, + * these spaces must be hardwired to 0b. + */ + pcie_size = 0x3C; + break; + /* only EndPoint passthrough is supported */ + case PCI_EXP_TYPE_ROOT_PORT: + case PCI_EXP_TYPE_UPSTREAM: + case PCI_EXP_TYPE_DOWNSTREAM: + case PCI_EXP_TYPE_PCI_BRIDGE: + case PCI_EXP_TYPE_PCIE_BRIDGE: + case PCI_EXP_TYPE_RC_EC: + default: + PT_ERR(d, "Unsupported device/port type %#x.\n", type); + return -1; + } + } else { + PT_ERR(d, "Unsupported capability version %#x.\n", version); + return -1; + } + + *size = pcie_size; + return 0; +} + +static const XenPTRegGroupInfo pt_emu_reg_grp_tbl[] = { + /* Header Type0 reg group */ + { + .grp_id = 0xFF, + .grp_type = GRP_TYPE_EMU, + .grp_size = 0x40, + .size_init = pt_reg_grp_size_init, + .emu_reg_tbl = pt_emu_reg_header0_tbl, + }, + /* AGP Capability Structure reg group */ + { + .grp_id = PCI_CAP_ID_AGP, + .grp_type = GRP_TYPE_HARDWIRED, + .grp_size = 0x30, + .size_init = pt_reg_grp_size_init, + }, + /* Vital Product Data Capability Structure reg group */ + { + .grp_id = PCI_CAP_ID_VPD, + .grp_type = GRP_TYPE_EMU, + .grp_size = 0x08, + .size_init = pt_reg_grp_size_init, + .emu_reg_tbl = pt_emu_reg_vpd_tbl, + }, + /* Slot Identification reg group */ + { + .grp_id = PCI_CAP_ID_SLOTID, + .grp_type = GRP_TYPE_HARDWIRED, + .grp_size = 0x04, + .size_init = pt_reg_grp_size_init, + }, + /* PCI-X Capabilities List Item reg group */ + { + .grp_id = PCI_CAP_ID_PCIX, + .grp_type = GRP_TYPE_HARDWIRED, + .grp_size = 0x18, + .size_init = pt_reg_grp_size_init, + }, + /* Vendor Specific Capability Structure reg group */ + { + .grp_id = PCI_CAP_ID_VNDR, + .grp_type = GRP_TYPE_EMU, + .grp_size = 0xFF, + .size_init = pt_vendor_size_init, + .emu_reg_tbl = pt_emu_reg_vendor_tbl, + }, + /* SHPC Capability List Item reg group */ + { + .grp_id = PCI_CAP_ID_SHPC, + .grp_type = GRP_TYPE_HARDWIRED, + .grp_size = 0x08, + .size_init = pt_reg_grp_size_init, + }, + /* Subsystem ID and Subsystem Vendor ID Capability List Item reg group */ + { + .grp_id = PCI_CAP_ID_SSVID, + .grp_type = GRP_TYPE_HARDWIRED, + .grp_size = 0x08, + .size_init = pt_reg_grp_size_init, + }, + /* AGP 8x Capability Structure reg group */ + { + .grp_id = PCI_CAP_ID_AGP3, + .grp_type = GRP_TYPE_HARDWIRED, + .grp_size = 0x30, + .size_init = pt_reg_grp_size_init, + }, + /* PCI Express Capability Structure reg group */ + { + .grp_id = PCI_CAP_ID_EXP, + .grp_type = GRP_TYPE_EMU, + .grp_size = 0xFF, + .size_init = pt_pcie_size_init, + .emu_reg_tbl = pt_emu_reg_pcie_tbl, + }, + { + .grp_size = 0, + }, +}; + +/* initialize Capabilities Pointer or Next Pointer register */ +static int pt_ptr_reg_init(XenPCIPassthroughState *s, + XenPTRegInfo *reg, uint32_t real_offset, + uint32_t *data) +{ + int i; + uint8_t *config = s->dev.config; + uint32_t reg_field = pci_get_byte(config + real_offset); + uint8_t cap_id = 0; + + /* find capability offset */ + while (reg_field) { + for (i = 0; pt_emu_reg_grp_tbl[i].grp_size != 0; i++) { + if (pt_hide_dev_cap(s->real_device, + pt_emu_reg_grp_tbl[i].grp_id)) { + continue; + } + + cap_id = pci_get_byte(config + reg_field + PCI_CAP_LIST_ID); + if (pt_emu_reg_grp_tbl[i].grp_id == cap_id) { + if (pt_emu_reg_grp_tbl[i].grp_type == GRP_TYPE_EMU) { + goto out; + } + /* ignore the 0 hardwired capability, find next one */ + break; + } + } + + /* next capability */ + reg_field = pci_get_byte(config + reg_field + PCI_CAP_LIST_NEXT); + } + +out: + *data = reg_field; + return 0; +} + + +/************* + * Main + */ + +static uint8_t find_cap_offset(XenPCIPassthroughState *s, uint8_t cap) +{ + uint8_t id; + unsigned max_cap = PCI_CAP_MAX; + uint8_t pos = PCI_CAPABILITY_LIST; + uint8_t status = 0; + + if (host_pci_get_byte(s->real_device, PCI_STATUS, &status)) { + return 0; + } + if ((status & PCI_STATUS_CAP_LIST) == 0) { + return 0; + } + + while (max_cap--) { + if (host_pci_get_byte(s->real_device, pos, &pos)) { + break; + } + if (pos < PCI_CONFIG_HEADER_SIZE) { + break; + } + + pos &= ~3; + if (host_pci_get_byte(s->real_device, pos + PCI_CAP_LIST_ID, &id)) { + break; + } + + if (id == 0xff) { + break; + } + if (id == cap) { + return pos; + } + + pos += PCI_CAP_LIST_NEXT; + } + return 0; +} + +static int pt_config_reg_init(XenPCIPassthroughState *s, + XenPTRegGroup *reg_grp, XenPTRegInfo *reg) +{ + XenPTReg *reg_entry; + uint32_t data = 0; + int rc = 0; + + reg_entry = g_new0(XenPTReg, 1); + reg_entry->reg = reg; + + if (reg->init) { + /* initialize emulate register */ + rc = reg->init(s, reg_entry->reg, + reg_grp->base_offset + reg->offset, &data); + if (rc < 0) { + free(reg_entry); + return rc; + } + if (data == PT_INVALID_REG) { + /* free unused BAR register entry */ + free(reg_entry); + return 0; + } + /* set register value */ + reg_entry->data = data; + } + /* list add register entry */ + QLIST_INSERT_HEAD(®_grp->reg_tbl_list, reg_entry, entries); + + return 0; +} + +int pt_config_init(XenPCIPassthroughState *s) +{ + int i, rc; + + QLIST_INIT(&s->reg_grp_tbl); + + for (i = 0; pt_emu_reg_grp_tbl[i].grp_size != 0; i++) { + uint32_t reg_grp_offset = 0; + XenPTRegGroup *reg_grp_entry = NULL; + + if (pt_emu_reg_grp_tbl[i].grp_id != 0xFF) { + if (pt_hide_dev_cap(s->real_device, + pt_emu_reg_grp_tbl[i].grp_id)) { + continue; + } + + reg_grp_offset = find_cap_offset(s, pt_emu_reg_grp_tbl[i].grp_id); + + if (!reg_grp_offset) { + continue; + } + } + + reg_grp_entry = g_new0(XenPTRegGroup, 1); + QLIST_INIT(®_grp_entry->reg_tbl_list); + QLIST_INSERT_HEAD(&s->reg_grp_tbl, reg_grp_entry, entries); + + reg_grp_entry->base_offset = reg_grp_offset; + reg_grp_entry->reg_grp = pt_emu_reg_grp_tbl + i; + if (pt_emu_reg_grp_tbl[i].size_init) { + /* get register group size */ + rc = pt_emu_reg_grp_tbl[i].size_init(s, reg_grp_entry->reg_grp, + reg_grp_offset, + ®_grp_entry->size); + if (rc < 0) { + pt_config_delete(s); + return rc; + } + } + + if (pt_emu_reg_grp_tbl[i].grp_type == GRP_TYPE_EMU) { + if (pt_emu_reg_grp_tbl[i].emu_reg_tbl) { + int j = 0; + XenPTRegInfo *reg_tbl = pt_emu_reg_grp_tbl[i].emu_reg_tbl; + /* initialize capability register */ + for (j = 0; reg_tbl->size != 0; j++, reg_tbl++) { + /* initialize capability register */ + rc = pt_config_reg_init(s, reg_grp_entry, reg_tbl); + if (rc < 0) { + pt_config_delete(s); + return rc; + } + } + } + } + } + + return 0; +} + +/* delete all emulate register */ +void pt_config_delete(XenPCIPassthroughState *s) +{ + struct XenPTRegGroup *reg_group, *next_grp; + struct XenPTReg *reg, *next_reg; + + /* free all register group entry */ + QLIST_FOREACH_SAFE(reg_group, &s->reg_grp_tbl, entries, next_grp) { + /* free all register entry */ + QLIST_FOREACH_SAFE(reg, ®_group->reg_tbl_list, entries, next_reg) { + QLIST_REMOVE(reg, entries); + g_free(reg); + } + + QLIST_REMOVE(reg_group, entries); + g_free(reg_group); + } +} -- Anthony PERARD
This patch move the msi definition from apic.c to apic-msidef.h. So it can be used also by other .c files. Signed-off-by: Anthony PERARD <anthony.perard@citrix.com> --- hw/apic-msidef.h | 28 ++++++++++++++++++++++++++++ hw/apic.c | 11 +---------- 2 files changed, 29 insertions(+), 10 deletions(-) create mode 100644 hw/apic-msidef.h diff --git a/hw/apic-msidef.h b/hw/apic-msidef.h new file mode 100644 index 0000000..3182f0b --- /dev/null +++ b/hw/apic-msidef.h @@ -0,0 +1,28 @@ +#ifndef HW_APIC_MSIDEF_H +#define HW_APIC_MSIDEF_H + +/* + * Intel APIC constants: from include/asm/msidef.h + */ + +/* + * Shifts for MSI data + */ + +#define MSI_DATA_VECTOR_SHIFT 0 +#define MSI_DATA_VECTOR_MASK 0x000000ff + +#define MSI_DATA_DELIVERY_MODE_SHIFT 8 +#define MSI_DATA_LEVEL_SHIFT 14 +#define MSI_DATA_TRIGGER_SHIFT 15 + +/* + * Shift/mask fields for msi address + */ + +#define MSI_ADDR_DEST_MODE_SHIFT 2 + +#define MSI_ADDR_DEST_ID_SHIFT 12 +#define MSI_ADDR_DEST_ID_MASK 0x00ffff0 + +#endif /* HW_APIC_MSIDEF_H */ diff --git a/hw/apic.c b/hw/apic.c index 086c544..4429927 100644 --- a/hw/apic.c +++ b/hw/apic.c @@ -22,19 +22,10 @@ #include "host-utils.h" #include "trace.h" #include "pc.h" +#include "apic-msidef.h" #define MAX_APIC_WORDS 8 -/* Intel APIC constants: from include/asm/msidef.h */ -#define MSI_DATA_VECTOR_SHIFT 0 -#define MSI_DATA_VECTOR_MASK 0x000000ff -#define MSI_DATA_DELIVERY_MODE_SHIFT 8 -#define MSI_DATA_TRIGGER_SHIFT 15 -#define MSI_DATA_LEVEL_SHIFT 14 -#define MSI_ADDR_DEST_MODE_SHIFT 2 -#define MSI_ADDR_DEST_ID_SHIFT 12 -#define MSI_ADDR_DEST_ID_MASK 0x00ffff0 - static APICCommonState *local_apics[MAX_APICS + 1]; static void apic_set_irq(APICCommonState *s, int vector_num, int trigger_mode); -- Anthony PERARD
Anthony PERARD
2012-Feb-13 12:20 UTC
[PATCH V6 10/11] Introduce Xen PCI Passthrough, MSI (3/3)
From: Jiang Yunhong <yunhong.jiang@intel.com> A more complete history can be found here: git://xenbits.xensource.com/qemu-xen-unstable.git Signed-off-by: Jiang Yunhong <yunhong.jiang@intel.com> Signed-off-by: Shan Haitao <haitao.shan@intel.com> Signed-off-by: Anthony PERARD <anthony.perard@citrix.com> --- Makefile.target | 1 + hw/apic-msidef.h | 2 + hw/xen_pci_passthrough.c | 32 ++ hw/xen_pci_passthrough.h | 48 +++ hw/xen_pci_passthrough_config_init.c | 480 ++++++++++++++++++++++++ hw/xen_pci_passthrough_msi.c | 667 ++++++++++++++++++++++++++++++++++ 6 files changed, 1230 insertions(+), 0 deletions(-) create mode 100644 hw/xen_pci_passthrough_msi.c diff --git a/Makefile.target b/Makefile.target index 8fc2ca3..3517cab 100644 --- a/Makefile.target +++ b/Makefile.target @@ -220,6 +220,7 @@ obj-i386-$(CONFIG_XEN) += xen_platform.o obj-i386-$(CONFIG_XEN_PCI_PASSTHROUGH) += host-pci-device.o obj-i386-$(CONFIG_XEN_PCI_PASSTHROUGH) += xen_pci_passthrough.o obj-i386-$(CONFIG_XEN_PCI_PASSTHROUGH) += xen_pci_passthrough_config_init.o +obj-i386-$(CONFIG_XEN_PCI_PASSTHROUGH) += xen_pci_passthrough_msi.o # Inter-VM PCI shared memory CONFIG_IVSHMEM diff --git a/hw/apic-msidef.h b/hw/apic-msidef.h index 3182f0b..6e2eb71 100644 --- a/hw/apic-msidef.h +++ b/hw/apic-msidef.h @@ -22,6 +22,8 @@ #define MSI_ADDR_DEST_MODE_SHIFT 2 +#define MSI_ADDR_REDIRECTION_SHIFT 3 + #define MSI_ADDR_DEST_ID_SHIFT 12 #define MSI_ADDR_DEST_ID_MASK 0x00ffff0 diff --git a/hw/xen_pci_passthrough.c b/hw/xen_pci_passthrough.c index bdc3690..1257ce2 100644 --- a/hw/xen_pci_passthrough.c +++ b/hw/xen_pci_passthrough.c @@ -36,6 +36,20 @@ * * Write ''1'' * - Set real bit to ''1''. + * + * MSI interrupt: + * Initialize MSI register(pt_msi_setup, pt_msi_update) + * Bind MSI(xc_domain_update_msi_irq) + * <fail> + * - Unmap MSI. + * - Set dev->msi->pirq to ''-1''. + * + * MSI-X interrupt: + * Initialize MSI-X register(pt_msix_update_one) + * Bind MSI-X(xc_domain_update_msi_irq) + * <fail> + * - Unmap MSI-X. + * - Set entry->pirq to ''-1''. */ #include <sys/ioctl.h> @@ -362,6 +376,7 @@ static void pt_iomem_map(XenPCIPassthroughState *s, int i, } if (!first_map && old_ebase != PT_PCI_BAR_UNMAPPED) { + pt_add_msix_mapping(s, i); /* Remove old mapping */ rc = xc_domain_memory_mapping(xen_xc, xen_domid, old_ebase >> XC_PAGE_SHIFT, @@ -386,6 +401,16 @@ static void pt_iomem_map(XenPCIPassthroughState *s, int i, if (rc) { PT_ERR(&s->dev, "create new mapping failed! (rc: %i)\n", rc); } + + rc = pt_remove_msix_mapping(s, i); + if (rc != 0) { + PT_ERR(&s->dev, "Remove MSI-X MMIO mapping failed! (rc: %d)\n", + rc); + } + + if (old_ebase != e_phys && old_ebase != -1) { + pt_msix_update_remap(s, i); + } } } @@ -766,6 +791,13 @@ static int pt_unregister_device(PCIDevice *d) } } + if (s->msi) { + pt_msi_disable(s); + } + if (s->msix) { + pt_msix_disable(s); + } + if (machine_irq) { mapped_machine_irq[machine_irq]--; diff --git a/hw/xen_pci_passthrough.h b/hw/xen_pci_passthrough.h index 0b9902d..deeba89 100644 --- a/hw/xen_pci_passthrough.h +++ b/hw/xen_pci_passthrough.h @@ -174,6 +174,37 @@ typedef struct XenPTRegGroup { #define PT_UNASSIGNED_PIRQ (-1) +typedef struct XenPTMSI { + uint16_t flags; + uint32_t addr_lo; /* guest message address */ + uint32_t addr_hi; /* guest message upper address */ + uint16_t data; /* guest message data */ + uint32_t ctrl_offset; /* saved control offset */ + int pirq; /* guest pirq corresponding */ + bool initialized; /* when guest MSI is initialized */ + bool mapped; /* when pirq is mapped */ +} XenPTMSI; + +typedef struct XenPTMSIXEntry { + int pirq; + uint64_t addr; + uint32_t data; + uint32_t vector_ctrl; + bool updated; /* indicate whether MSI ADDR or DATA is updated */ +} XenPTMSIXEntry; +typedef struct XenPTMSIX { + uint32_t ctrl_offset; + bool enabled; + int total_entries; + int bar_index; + uint64_t table_base; + uint32_t table_off; + uint32_t table_offset_adjust; /* page align mmap */ + uint64_t mmio_base_addr; + MemoryRegion mmio; + void *phys_iomem_base; + XenPTMSIXEntry msix_entry[0]; +} XenPTMSIX; struct XenPCIPassthroughState { PCIDevice dev; @@ -186,6 +217,9 @@ struct XenPCIPassthroughState { uint32_t machine_irq; + XenPTMSI *msi; + XenPTMSIX *msix; + MemoryRegion bar[PCI_NUM_REGIONS - 1]; MemoryRegion rom; }; @@ -262,4 +296,18 @@ static inline uint8_t pci_intx(XenPCIPassthroughState *s) return r_val; } +/* MSI/MSI-X */ +int pt_msi_set_enable(XenPCIPassthroughState *s, bool en); +int pt_msi_setup(XenPCIPassthroughState *s); +int pt_msi_update(XenPCIPassthroughState *d); +void pt_msi_disable(XenPCIPassthroughState *s); + +int pt_msix_init(XenPCIPassthroughState *s, uint32_t base); +void pt_msix_delete(XenPCIPassthroughState *s); +int pt_msix_update(XenPCIPassthroughState *s); +int pt_msix_update_remap(XenPCIPassthroughState *s, int bar_index); +void pt_msix_disable(XenPCIPassthroughState *s); +int pt_add_msix_mapping(XenPCIPassthroughState *s, int bar_index); +int pt_remove_msix_mapping(XenPCIPassthroughState *s, int bar_index); + #endif /* !QEMU_HW_XEN_PCI_PASSTHROUGH_H */ diff --git a/hw/xen_pci_passthrough_config_init.c b/hw/xen_pci_passthrough_config_init.c index 2fb27ff..430c26a 100644 --- a/hw/xen_pci_passthrough_config_init.c +++ b/hw/xen_pci_passthrough_config_init.c @@ -1125,6 +1125,419 @@ static XenPTRegInfo pt_emu_reg_pcie_tbl[] = { }; +/******************************** + * MSI Capability + */ + +/* Helper */ +static bool pt_msgdata_check_type(uint32_t offset, uint16_t flags) +{ + /* check the offset whether matches the type or not */ + bool is_32 = (offset == PCI_MSI_DATA_32) && !(flags & PCI_MSI_FLAGS_64BIT); + bool is_64 = (offset == PCI_MSI_DATA_64) && (flags & PCI_MSI_FLAGS_64BIT); + return is_32 || is_64; +} + +/* Message Control register */ +static int pt_msgctrl_reg_init(XenPCIPassthroughState *s, + XenPTRegInfo *reg, uint32_t real_offset, + uint32_t *data) +{ + PCIDevice *d = &s->dev; + XenPTMSI *msi = s->msi; + uint16_t reg_field = 0; + + /* use I/O device register''s value as initial value */ + reg_field = pci_get_word(d->config + real_offset); + + if (reg_field & PCI_MSI_FLAGS_ENABLE) { + PT_LOG(&s->dev, "MSI already enabled, disabling it first\n"); + host_pci_set_word(s->real_device, real_offset, + reg_field & ~PCI_MSI_FLAGS_ENABLE); + } + msi->flags |= reg_field; + msi->ctrl_offset = real_offset; + msi->initialized = false; + msi->mapped = false; + + *data = reg->init_val; + return 0; +} +static int pt_msgctrl_reg_write(XenPCIPassthroughState *s, XenPTReg *cfg_entry, + uint16_t *value, uint16_t dev_value, + uint16_t valid_mask) +{ + XenPTRegInfo *reg = cfg_entry->reg; + XenPTMSI *msi = s->msi; + uint16_t writable_mask = 0; + uint16_t throughable_mask = 0; + uint16_t val; + + /* Currently no support for multi-vector */ + if (*value & PCI_MSI_FLAGS_QSIZE) { + PT_WARN(&s->dev, "Tries to set more than 1 vector ctrl %x\n", *value); + } + + /* modify emulate register */ + writable_mask = reg->emu_mask & ~reg->ro_mask & valid_mask; + cfg_entry->data = PT_MERGE_VALUE(*value, cfg_entry->data, writable_mask); + msi->flags |= cfg_entry->data & ~PCI_MSI_FLAGS_ENABLE; + + /* create value for writing to I/O device register */ + val = *value; + throughable_mask = ~reg->emu_mask & valid_mask; + *value = PT_MERGE_VALUE(*value, dev_value, throughable_mask); + + /* update MSI */ + if (val & PCI_MSI_FLAGS_ENABLE) { + /* setup MSI pirq for the first time */ + if (!msi->initialized) { + /* Init physical one */ + PT_LOG(&s->dev, "setup MSI\n"); + if (pt_msi_setup(s)) { + /* We do not broadcast the error to the framework code, so + * that MSI errors are contained in MSI emulation code and + * QEMU can go on running. + * Guest MSI would be actually not working. + */ + *value &= ~PCI_MSI_FLAGS_ENABLE; + PT_WARN(&s->dev, "Can not map MSI.\n"); + return 0; + } + if (pt_msi_update(s)) { + *value &= ~PCI_MSI_FLAGS_ENABLE; + PT_WARN(&s->dev, "Can not bind MSI\n"); + return 0; + } + msi->initialized = true; + msi->mapped = true; + } + msi->flags |= PCI_MSI_FLAGS_ENABLE; + } else { + msi->flags &= ~PCI_MSI_FLAGS_ENABLE; + } + + /* pass through MSI_ENABLE bit */ + *value &= ~PCI_MSI_FLAGS_ENABLE; + *value |= val & PCI_MSI_FLAGS_ENABLE; + + return 0; +} + +/* initialize Message Upper Address register */ +static int pt_msgaddr64_reg_init(XenPCIPassthroughState *s, + XenPTRegInfo *reg, uint32_t real_offset, + uint32_t *data) +{ + /* no need to initialize in case of 32 bit type */ + if (!(s->msi->flags & PCI_MSI_FLAGS_64BIT)) { + *data = PT_INVALID_REG; + } else { + *data = reg->init_val; + } + + return 0; +} +/* this function will be called twice (for 32 bit and 64 bit type) */ +/* initialize Message Data register */ +static int pt_msgdata_reg_init(XenPCIPassthroughState *s, + XenPTRegInfo *reg, uint32_t real_offset, + uint32_t *data) +{ + uint32_t flags = s->msi->flags; + uint32_t offset = reg->offset; + + /* check the offset whether matches the type or not */ + if (pt_msgdata_check_type(offset, flags)) { + *data = reg->init_val; + } else { + *data = PT_INVALID_REG; + } + return 0; +} + +/* write Message Address register */ +static int pt_msgaddr32_reg_write(XenPCIPassthroughState *s, + XenPTReg *cfg_entry, uint32_t *value, + uint32_t dev_value, uint32_t valid_mask) +{ + XenPTRegInfo *reg = cfg_entry->reg; + uint32_t writable_mask = 0; + uint32_t throughable_mask = 0; + uint32_t old_addr = cfg_entry->data; + + /* modify emulate register */ + writable_mask = reg->emu_mask & ~reg->ro_mask & valid_mask; + cfg_entry->data = PT_MERGE_VALUE(*value, cfg_entry->data, writable_mask); + s->msi->addr_lo = cfg_entry->data; + + /* create value for writing to I/O device register */ + throughable_mask = ~reg->emu_mask & valid_mask; + *value = PT_MERGE_VALUE(*value, dev_value, throughable_mask); + + /* update MSI */ + if (cfg_entry->data != old_addr) { + if (s->msi->mapped) { + pt_msi_update(s); + } + } + + return 0; +} +/* write Message Upper Address register */ +static int pt_msgaddr64_reg_write(XenPCIPassthroughState *s, + XenPTReg *cfg_entry, uint32_t *value, + uint32_t dev_value, uint32_t valid_mask) +{ + XenPTRegInfo *reg = cfg_entry->reg; + uint32_t writable_mask = 0; + uint32_t throughable_mask = 0; + uint32_t old_addr = cfg_entry->data; + + /* check whether the type is 64 bit or not */ + if (!(s->msi->flags & PCI_MSI_FLAGS_64BIT)) { + PT_ERR(&s->dev, + "Can''t write to the upper address without 64 bit support\n"); + return -1; + } + + /* modify emulate register */ + writable_mask = reg->emu_mask & ~reg->ro_mask & valid_mask; + cfg_entry->data = PT_MERGE_VALUE(*value, cfg_entry->data, writable_mask); + /* update the msi_info too */ + s->msi->addr_hi = cfg_entry->data; + + /* create value for writing to I/O device register */ + throughable_mask = ~reg->emu_mask & valid_mask; + *value = PT_MERGE_VALUE(*value, dev_value, throughable_mask); + + /* update MSI */ + if (cfg_entry->data != old_addr) { + if (s->msi->mapped) { + pt_msi_update(s); + } + } + + return 0; +} + + +/* this function will be called twice (for 32 bit and 64 bit type) */ +/* write Message Data register */ +static int pt_msgdata_reg_write(XenPCIPassthroughState *s, XenPTReg *cfg_entry, + uint16_t *value, uint16_t dev_value, + uint16_t valid_mask) +{ + XenPTRegInfo *reg = cfg_entry->reg; + XenPTMSI *msi = s->msi; + uint16_t writable_mask = 0; + uint16_t throughable_mask = 0; + uint16_t old_data = cfg_entry->data; + uint32_t offset = reg->offset; + + /* check the offset whether matches the type or not */ + if (!pt_msgdata_check_type(offset, msi->flags)) { + /* exit I/O emulator */ + PT_ERR(&s->dev, "the offset does not match the 32/64 bit type!\n"); + return -1; + } + + /* modify emulate register */ + writable_mask = reg->emu_mask & ~reg->ro_mask & valid_mask; + cfg_entry->data = PT_MERGE_VALUE(*value, cfg_entry->data, writable_mask); + /* update the msi_info too */ + msi->data = cfg_entry->data; + + /* create value for writing to I/O device register */ + throughable_mask = ~reg->emu_mask & valid_mask; + *value = PT_MERGE_VALUE(*value, dev_value, throughable_mask); + + /* update MSI */ + if (cfg_entry->data != old_data) { + if (msi->mapped) { + pt_msi_update(s); + } + } + + return 0; +} + +/* MSI Capability Structure reg static infomation table */ +static XenPTRegInfo pt_emu_reg_msi_tbl[] = { + /* Next Pointer reg */ + { + .offset = PCI_CAP_LIST_NEXT, + .size = 1, + .init_val = 0x00, + .ro_mask = 0xFF, + .emu_mask = 0xFF, + .init = pt_ptr_reg_init, + .u.b.read = pt_byte_reg_read, + .u.b.write = pt_byte_reg_write, + .u.b.restore = NULL, + }, + /* Message Control reg */ + { + .offset = PCI_MSI_FLAGS, + .size = 2, + .init_val = 0x0000, + .ro_mask = 0xFF8E, + .emu_mask = 0x007F, + .init = pt_msgctrl_reg_init, + .u.w.read = pt_word_reg_read, + .u.w.write = pt_msgctrl_reg_write, + .u.w.restore = NULL, + }, + /* Message Address reg */ + { + .offset = PCI_MSI_ADDRESS_LO, + .size = 4, + .init_val = 0x00000000, + .ro_mask = 0x00000003, + .emu_mask = 0xFFFFFFFF, + .no_wb = 1, + .init = pt_common_reg_init, + .u.dw.read = pt_long_reg_read, + .u.dw.write = pt_msgaddr32_reg_write, + .u.dw.restore = NULL, + }, + /* Message Upper Address reg (if PCI_MSI_FLAGS_64BIT set) */ + { + .offset = PCI_MSI_ADDRESS_HI, + .size = 4, + .init_val = 0x00000000, + .ro_mask = 0x00000000, + .emu_mask = 0xFFFFFFFF, + .no_wb = 1, + .init = pt_msgaddr64_reg_init, + .u.dw.read = pt_long_reg_read, + .u.dw.write = pt_msgaddr64_reg_write, + .u.dw.restore = NULL, + }, + /* Message Data reg (16 bits of data for 32-bit devices) */ + { + .offset = PCI_MSI_DATA_32, + .size = 2, + .init_val = 0x0000, + .ro_mask = 0x0000, + .emu_mask = 0xFFFF, + .no_wb = 1, + .init = pt_msgdata_reg_init, + .u.w.read = pt_word_reg_read, + .u.w.write = pt_msgdata_reg_write, + .u.w.restore = NULL, + }, + /* Message Data reg (16 bits of data for 64-bit devices) */ + { + .offset = PCI_MSI_DATA_64, + .size = 2, + .init_val = 0x0000, + .ro_mask = 0x0000, + .emu_mask = 0xFFFF, + .no_wb = 1, + .init = pt_msgdata_reg_init, + .u.w.read = pt_word_reg_read, + .u.w.write = pt_msgdata_reg_write, + .u.w.restore = NULL, + }, + { + .size = 0, + }, +}; + + +/************************************** + * MSI-X Capability + */ + +/* Message Control register for MSI-X */ +static int pt_msixctrl_reg_init(XenPCIPassthroughState *s, + XenPTRegInfo *reg, uint32_t real_offset, + uint32_t *data) +{ + PCIDevice *d = &s->dev; + uint16_t reg_field = 0; + + /* use I/O device register''s value as initial value */ + reg_field = pci_get_word(d->config + real_offset); + + if (reg_field & PCI_MSIX_FLAGS_ENABLE) { + PT_LOG(d, "MSIX already enabled, disabling it first\n"); + host_pci_set_word(s->real_device, real_offset, + reg_field & ~PCI_MSIX_FLAGS_ENABLE); + } + + s->msix->ctrl_offset = real_offset; + + *data = reg->init_val; + return 0; +} +static int pt_msixctrl_reg_write(XenPCIPassthroughState *s, + XenPTReg *cfg_entry, uint16_t *value, + uint16_t dev_value, uint16_t valid_mask) +{ + XenPTRegInfo *reg = cfg_entry->reg; + uint16_t writable_mask = 0; + uint16_t throughable_mask = 0; + + int debug_msix_enabled_old; + + /* modify emulate register */ + writable_mask = reg->emu_mask & ~reg->ro_mask & valid_mask; + cfg_entry->data = PT_MERGE_VALUE(*value, cfg_entry->data, writable_mask); + + /* create value for writing to I/O device register */ + throughable_mask = ~reg->emu_mask & valid_mask; + *value = PT_MERGE_VALUE(*value, dev_value, throughable_mask); + + /* update MSI-X */ + if ((*value & PCI_MSIX_FLAGS_ENABLE) + && !(*value & PCI_MSIX_FLAGS_MASKALL)) { + pt_msix_update(s); + } + + debug_msix_enabled_old = s->msix->enabled; + s->msix->enabled = !!(*value & PCI_MSIX_FLAGS_ENABLE); + if (s->msix->enabled != debug_msix_enabled_old) { + PT_LOG(&s->dev, "%s MSI-X\n", + s->msix->enabled ? "enable" : "disable"); + } + + return 0; +} + +/* MSI-X Capability Structure reg static infomation table */ +static XenPTRegInfo pt_emu_reg_msix_tbl[] = { + /* Next Pointer reg */ + { + .offset = PCI_CAP_LIST_NEXT, + .size = 1, + .init_val = 0x00, + .ro_mask = 0xFF, + .emu_mask = 0xFF, + .init = pt_ptr_reg_init, + .u.b.read = pt_byte_reg_read, + .u.b.write = pt_byte_reg_write, + .u.b.restore = NULL, + }, + /* Message Control reg */ + { + .offset = PCI_MSI_FLAGS, + .size = 2, + .init_val = 0x0000, + .ro_mask = 0x3FFF, + .emu_mask = 0x0000, + .init = pt_msixctrl_reg_init, + .u.w.read = pt_word_reg_read, + .u.w.write = pt_msixctrl_reg_write, + .u.w.restore = NULL, + }, + { + .size = 0, + }, +}; + + /**************************** * Capabilities */ @@ -1218,6 +1631,49 @@ static int pt_pcie_size_init(XenPCIPassthroughState *s, *size = pcie_size; return 0; } +/* get MSI Capability Structure register group size */ +static int pt_msi_size_init(XenPCIPassthroughState *s, + const XenPTRegGroupInfo *grp_reg, + uint32_t base_offset, uint8_t *size) +{ + PCIDevice *d = &s->dev; + uint16_t msg_ctrl = 0; + uint8_t msi_size = 0xa; + + msg_ctrl = pci_get_word(d->config + (base_offset + PCI_MSI_FLAGS)); + + /* check if 64-bit address is capable of per-vector masking */ + if (msg_ctrl & PCI_MSI_FLAGS_64BIT) { + msi_size += 4; + } + if (msg_ctrl & PCI_MSI_FLAGS_MASKBIT) { + msi_size += 10; + } + + s->msi = g_new0(XenPTMSI, 1); + s->msi->pirq = PT_UNASSIGNED_PIRQ; + + *size = msi_size; + return 0; +} +/* get MSI-X Capability Structure register group size */ +static int pt_msix_size_init(XenPCIPassthroughState *s, + const XenPTRegGroupInfo *grp_reg, + uint32_t base_offset, uint8_t *size) +{ + int rc = 0; + + rc = pt_msix_init(s, base_offset); + + if (rc < 0) { + PT_ERR(&s->dev, "Internal error: Invalid pt_msix_init.\n"); + return rc; + } + + *size = grp_reg->grp_size; + return 0; +} + static const XenPTRegGroupInfo pt_emu_reg_grp_tbl[] = { /* Header Type0 reg group */ @@ -1250,6 +1706,14 @@ static const XenPTRegGroupInfo pt_emu_reg_grp_tbl[] = { .grp_size = 0x04, .size_init = pt_reg_grp_size_init, }, + /* MSI Capability Structure reg group */ + { + .grp_id = PCI_CAP_ID_MSI, + .grp_type = GRP_TYPE_EMU, + .grp_size = 0xFF, + .size_init = pt_msi_size_init, + .emu_reg_tbl = pt_emu_reg_msi_tbl, + }, /* PCI-X Capabilities List Item reg group */ { .grp_id = PCI_CAP_ID_PCIX, @@ -1294,6 +1758,14 @@ static const XenPTRegGroupInfo pt_emu_reg_grp_tbl[] = { .size_init = pt_pcie_size_init, .emu_reg_tbl = pt_emu_reg_pcie_tbl, }, + /* MSI-X Capability Structure reg group */ + { + .grp_id = PCI_CAP_ID_MSIX, + .grp_type = GRP_TYPE_EMU, + .grp_size = 0x0C, + .size_init = pt_msix_size_init, + .emu_reg_tbl = pt_emu_reg_msix_tbl, + }, { .grp_size = 0, }, @@ -1478,6 +1950,14 @@ void pt_config_delete(XenPCIPassthroughState *s) struct XenPTRegGroup *reg_group, *next_grp; struct XenPTReg *reg, *next_reg; + /* free MSI/MSI-X info table */ + if (s->msix) { + pt_msix_delete(s); + } + if (s->msi) { + g_free(s->msi); + } + /* free all register group entry */ QLIST_FOREACH_SAFE(reg_group, &s->reg_grp_tbl, entries, next_grp) { /* free all register entry */ diff --git a/hw/xen_pci_passthrough_msi.c b/hw/xen_pci_passthrough_msi.c new file mode 100644 index 0000000..0b81060 --- /dev/null +++ b/hw/xen_pci_passthrough_msi.c @@ -0,0 +1,667 @@ +/* + * Copyright (c) 2007, Intel Corporation. + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + * + * Jiang Yunhong <yunhong.jiang@intel.com> + * + * This file implements direct PCI assignment to a HVM guest + */ + +#include <sys/mman.h> + +#include "xen_backend.h" +#include "xen_pci_passthrough.h" +#include "apic-msidef.h" + + +#define AUTO_ASSIGN -1 + +/* shift count for gflags */ +#define GFLAGS_SHIFT_DEST_ID 0 +#define GFLAGS_SHIFT_RH 8 +#define GFLAGS_SHIFT_DM 9 +#define GLFAGS_SHIFT_DELIV_MODE 12 +#define GLFAGS_SHIFT_TRG_MODE 15 + + +/* + * Helpers + */ + +static inline uint8_t msi_vector(uint32_t data) +{ + return (data & MSI_DATA_VECTOR_MASK) >> MSI_DATA_VECTOR_SHIFT; +} + +static inline uint8_t msi_dest_id(uint32_t addr) +{ + return (addr & MSI_ADDR_DEST_ID_MASK) >> MSI_ADDR_DEST_ID_SHIFT; +} + +static inline uint32_t msi_ext_dest_id(uint32_t addr_hi) +{ + return addr_hi & 0xffffff00; +} + +static uint32_t msi_gflags(uint32_t data, uint64_t addr) +{ + uint32_t result = 0; + int rh, dm, dest_id, deliv_mode, trig_mode; + + rh = (addr >> MSI_ADDR_REDIRECTION_SHIFT) & 0x1; + dm = (addr >> MSI_ADDR_DEST_MODE_SHIFT) & 0x1; + dest_id = msi_dest_id(addr); + deliv_mode = (data >> MSI_DATA_DELIVERY_MODE_SHIFT) & 0x7; + trig_mode = (data >> MSI_DATA_TRIGGER_SHIFT) & 0x1; + + result = dest_id | (rh << GFLAGS_SHIFT_RH) | (dm << GFLAGS_SHIFT_DM) | + (deliv_mode << GLFAGS_SHIFT_DELIV_MODE) | + (trig_mode << GLFAGS_SHIFT_TRG_MODE); + + return result; +} + +static inline uint64_t msi_addr64(XenPTMSI *msi) +{ + return (uint64_t)msi->addr_hi << 32 | msi->addr_lo; +} + +static int msi_msix_enable(XenPCIPassthroughState *s, + uint32_t address, + uint16_t flag, + bool enable) +{ + uint16_t val = 0; + + if (!address) { + return -1; + } + + host_pci_get_word(s->real_device, address, &val); + if (enable) { + val |= flag; + } else { + val &= ~flag; + } + host_pci_set_word(s->real_device, address, val); + return 0; +} + +static int msi_msix_setup(XenPCIPassthroughState *s, + uint64_t addr, + uint32_t data, + int *ppirq, + bool is_msix, + int msix_entry, + bool is_not_mapped) +{ + uint8_t gvec = msi_vector(data); + int rc = 0; + + assert((!is_msix && msix_entry == 0) || is_msix); + + if (gvec == 0) { + /* if gvec is 0, the guest is asking for a particular pirq that + * is passed as dest_id */ + *ppirq = msi_ext_dest_id(addr >> 32) | msi_dest_id(addr); + if (!*ppirq) { + /* this probably identifies an misconfiguration of the guest, + * try the emulated path */ + *ppirq = PT_UNASSIGNED_PIRQ; + } else { + PT_LOG(&s->dev, "requested pirq %d for MSI%s" + " (vec: %#x, entry: %#x)\n", + *ppirq, is_msix ? "-X" : "", gvec, msix_entry); + } + } + + if (is_not_mapped) { + uint64_t table_base = 0; + + if (is_msix) { + table_base = s->msix->table_base; + } + + rc = xc_physdev_map_pirq_msi(xen_xc, xen_domid, AUTO_ASSIGN, ppirq, + PCI_DEVFN(s->real_device->dev, + s->real_device->func), + s->real_device->bus, + msix_entry, table_base); + if (rc) { + PT_ERR(&s->dev, "Mapping of MSI%s (rc: %i, vec: %#x, entry %#x)\n", + is_msix ? "-X" : "", rc, gvec, msix_entry); + return rc; + } + } + + return 0; +} +static int msi_msix_update(XenPCIPassthroughState *s, + uint64_t addr, + uint32_t data, + int pirq, + bool is_msix, + int msix_entry, + int *old_pirq) +{ + PCIDevice *d = &s->dev; + uint8_t gvec = msi_vector(data); + uint32_t gflags = msi_gflags(data, addr); + int rc = 0; + uint64_t table_addr = 0; + + PT_LOG(d, "Updating MSI%s with pirq %d gvec %#x gflags %#x (entry: %#x)\n", + is_msix ? "-X" : "", pirq, gvec, gflags, msix_entry); + + if (is_msix) { + table_addr = s->msix->mmio_base_addr; + } + + rc = xc_domain_update_msi_irq(xen_xc, xen_domid, gvec, + pirq, gflags, table_addr); + + if (rc) { + PT_ERR(d, "Updating of MSI%s failed. (rc: %d)\n", + is_msix ? "-X" : "", rc); + + if (xc_physdev_unmap_pirq(xen_xc, xen_domid, *old_pirq)) { + PT_ERR(d, "Unmapping of MSI%s pirq %d failed.\n", + is_msix ? "-X" : "", *old_pirq); + } + *old_pirq = PT_UNASSIGNED_PIRQ; + } + return rc; +} + +static int msi_msix_disable(XenPCIPassthroughState *s, + uint64_t addr, + uint32_t data, + int pirq, + bool is_msix, + bool is_binded) +{ + PCIDevice *d = &s->dev; + uint8_t gvec = msi_vector(data); + uint32_t gflags = msi_gflags(data, addr); + int rc = 0; + + if (pirq == PT_UNASSIGNED_PIRQ) { + return 0; + } + + if (is_binded) { + PT_LOG(d, "Unbind MSI%s with pirq %d, gvec %#x\n", + is_msix ? "-X" : "", pirq, gvec); + rc = xc_domain_unbind_msi_irq(xen_xc, xen_domid, gvec, pirq, gflags); + if (rc) { + PT_ERR(d, "Unbinding of MSI%s failed. (pirq: %d, gvec: %#x)\n", + is_msix ? "-X" : "", pirq, gvec); + return rc; + } + } + + PT_LOG(d, "Unmap MSI%s pirq %d\n", is_msix ? "-X" : "", pirq); + rc = xc_physdev_unmap_pirq(xen_xc, xen_domid, pirq); + if (rc) { + PT_ERR(d, "Unmapping of MSI%s pirq %d failed. (rc: %i)\n", + is_msix ? "-X" : "", pirq, rc); + return rc; + } + + return 0; +} + +/* + * MSI virtualization functions + */ + +int pt_msi_set_enable(XenPCIPassthroughState *s, bool enable) +{ + PT_LOG(&s->dev, "%s MSI.\n", enable ? "enabling" : "disabling"); + + if (!s->msi) { + return -1; + } + + return msi_msix_enable(s, s->msi->ctrl_offset, PCI_MSI_FLAGS_ENABLE, + enable); +} + +/* setup physical msi, but don''t enable it */ +int pt_msi_setup(XenPCIPassthroughState *s) +{ + int pirq = PT_UNASSIGNED_PIRQ; + int rc = 0; + XenPTMSI *msi = s->msi; + + if (msi->initialized) { + PT_ERR(&s->dev, + "Setup physical MSI when it has been properly initialized.\n"); + return -1; + } + + rc = msi_msix_setup(s, msi_addr64(msi), msi->data, &pirq, false, 0, true); + if (rc) { + return rc; + } + + if (pirq < 0) { + PT_ERR(&s->dev, "Invalid pirq number: %d.\n", pirq); + return -1; + } + + msi->pirq = pirq; + PT_LOG(&s->dev, "MSI mapped with pirq %d.\n", pirq); + + return 0; +} + +int pt_msi_update(XenPCIPassthroughState *s) +{ + XenPTMSI *msi = s->msi; + return msi_msix_update(s, msi_addr64(msi), msi->data, msi->pirq, + false, 0, &msi->pirq); +} + +void pt_msi_disable(XenPCIPassthroughState *s) +{ + XenPTMSI *msi = s->msi; + + if (!msi) { + return; + } + + pt_msi_set_enable(s, false); + + msi_msix_disable(s, msi_addr64(msi), msi->data, msi->pirq, false, + msi->initialized); + + /* clear msi info */ + msi->flags = 0; + msi->mapped = false; + msi->pirq = PT_UNASSIGNED_PIRQ; +} + +/* + * MSI-X virtualization functions + */ + +static int msix_set_enable(XenPCIPassthroughState *s, bool enabled) +{ + PT_LOG(&s->dev, "%s MSI-X.\n", enabled ? "enabling" : "disabling"); + + if (!s->msix) { + return -1; + } + + return msi_msix_enable(s, s->msix->ctrl_offset, PCI_MSIX_FLAGS_ENABLE, + enabled); +} + +static void mask_physical_msix_entry(XenPCIPassthroughState *s, + int entry_nr, int mask) +{ + void *phys_off; + + phys_off = s->msix->phys_iomem_base + PCI_MSIX_ENTRY_SIZE * entry_nr + + PCI_MSIX_ENTRY_VECTOR_CTRL; + *(uint32_t *)phys_off = mask; +} + +static int pt_msix_update_one(XenPCIPassthroughState *s, int entry_nr) +{ + XenPTMSIXEntry *entry = NULL; + int pirq; + int rc; + + if (entry_nr < 0 || entry_nr >= s->msix->total_entries) { + return -EINVAL; + } + + entry = &s->msix->msix_entry[entry_nr]; + + if (!entry->updated) { + return 0; + } + + pirq = entry->pirq; + + rc = msi_msix_setup(s, entry->data, entry->data, &pirq, true, entry_nr, + entry->pirq == PT_UNASSIGNED_PIRQ); + if (rc) { + return rc; + } + if (entry->pirq == PT_UNASSIGNED_PIRQ) { + entry->pirq = pirq; + } + + rc = msi_msix_update(s, entry->addr, entry->data, pirq, true, + entry_nr, &entry->pirq); + + if (!rc) { + entry->updated = false; + } + + return rc; +} + +int pt_msix_update(XenPCIPassthroughState *s) +{ + XenPTMSIX *msix = s->msix; + int i; + + for (i = 0; i < msix->total_entries; i++) { + pt_msix_update_one(s, i); + } + + return 0; +} + +void pt_msix_disable(XenPCIPassthroughState *s) +{ + int i = 0; + + msix_set_enable(s, false); + + for (i = 0; i < s->msix->total_entries; i++) { + XenPTMSIXEntry *entry = &s->msix->msix_entry[i]; + + msi_msix_disable(s, entry->addr, entry->data, entry->pirq, true, true); + + /* clear MSI-X info */ + entry->pirq = PT_UNASSIGNED_PIRQ; + entry->updated = false; + } +} + +int pt_msix_update_remap(XenPCIPassthroughState *s, int bar_index) +{ + XenPTMSIXEntry *entry; + int i, ret; + + if (!(s->msix && s->msix->bar_index == bar_index)) { + return 0; + } + + for (i = 0; i < s->msix->total_entries; i++) { + entry = &s->msix->msix_entry[i]; + if (entry->pirq != PT_UNASSIGNED_PIRQ) { + ret = xc_domain_unbind_pt_irq(xen_xc, xen_domid, entry->pirq, + PT_IRQ_TYPE_MSI, 0, 0, 0, 0); + if (ret) { + PT_ERR(&s->dev, "unbind MSI-X entry %d failed\n", entry->pirq); + } + entry->updated = true; + } + } + pt_msix_update(s); + + return 0; +} + +static uint32_t get_entry_value(XenPTMSIXEntry *e, int offset) +{ + switch (offset) { + case PCI_MSIX_ENTRY_LOWER_ADDR: + return e->addr & UINT32_MAX; + case PCI_MSIX_ENTRY_UPPER_ADDR: + return e->addr >> 32; + case PCI_MSIX_ENTRY_DATA: + return e->data; + case PCI_MSIX_ENTRY_VECTOR_CTRL: + return e->vector_ctrl; + default: + return 0; + } +} + +static void set_entry_value(XenPTMSIXEntry *e, int offset, uint32_t val) +{ + switch (offset) { + case PCI_MSIX_ENTRY_LOWER_ADDR: + e->addr = (e->addr & ((uint64_t)UINT32_MAX << 32)) | val; + break; + case PCI_MSIX_ENTRY_UPPER_ADDR: + e->addr = (uint64_t)val << 32 | (e->addr & UINT32_MAX); + break; + case PCI_MSIX_ENTRY_DATA: + e->data = val; + break; + case PCI_MSIX_ENTRY_VECTOR_CTRL: + e->vector_ctrl = val; + break; + } +} + +static void pci_msix_write(void *opaque, target_phys_addr_t addr, + uint64_t val, unsigned size) +{ + XenPCIPassthroughState *s = opaque; + XenPTMSIX *msix = s->msix; + XenPTMSIXEntry *entry; + int entry_nr, offset; + + entry_nr = addr / PCI_MSIX_ENTRY_SIZE; + if (entry_nr < 0 || entry_nr >= msix->total_entries) { + PT_ERR(&s->dev, "asked MSI-X entry ''%i'' invalid!\n", entry_nr); + return; + } + entry = &msix->msix_entry[entry_nr]; + offset = addr % PCI_MSIX_ENTRY_SIZE; + + if (offset != PCI_MSIX_ENTRY_VECTOR_CTRL) { + const volatile uint32_t *vec_ctrl; + + if (get_entry_value(entry, offset) == val) { + return; + } + + /* + * If Xen intercepts the mask bit access, entry->vec_ctrl may not be + * up-to-date. Read from hardware directly. + */ + vec_ctrl = s->msix->phys_iomem_base + entry_nr * PCI_MSIX_ENTRY_SIZE + + PCI_MSIX_ENTRY_VECTOR_CTRL; + + if (msix->enabled && !(*vec_ctrl & PCI_MSIX_ENTRY_CTRL_MASKBIT)) { + PT_ERR(&s->dev, "Can''t update msix entry %d since MSI-X is already " + "enabled.\n", entry_nr); + return; + } + + entry->updated = true; + } + + set_entry_value(entry, offset, val); + + if (offset == PCI_MSIX_ENTRY_VECTOR_CTRL) { + if (msix->enabled && !(val & PCI_MSIX_ENTRY_CTRL_MASKBIT)) { + pt_msix_update_one(s, entry_nr); + } + mask_physical_msix_entry(s, entry_nr, + entry->vector_ctrl & PCI_MSIX_ENTRY_CTRL_MASKBIT); + } +} + +static uint64_t pci_msix_read(void *opaque, target_phys_addr_t addr, + unsigned size) +{ + XenPCIPassthroughState *s = opaque; + XenPTMSIX *msix = s->msix; + int entry_nr, offset; + + entry_nr = addr / PCI_MSIX_ENTRY_SIZE; + if (entry_nr < 0 || entry_nr >= msix->total_entries) { + PT_ERR(&s->dev, "asked MSI-X entry ''%i'' invalid!\n", entry_nr); + return 0; + } + + offset = addr % PCI_MSIX_ENTRY_SIZE; + + return get_entry_value(&msix->msix_entry[entry_nr], offset); +} + +static const MemoryRegionOps pci_msix_ops = { + .read = pci_msix_read, + .write = pci_msix_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, +}; + +int pt_add_msix_mapping(XenPCIPassthroughState *s, int bar_index) +{ + XenPTMSIX *msix = s->msix; + + if (!(s->msix && s->msix->bar_index == bar_index)) { + return 0; + } + + memory_region_set_enabled(&msix->mmio, false); + return xc_domain_memory_mapping(xen_xc, xen_domid, + s->msix->mmio_base_addr >> XC_PAGE_SHIFT, + (s->bases[bar_index].access.maddr + s->msix->table_off) + >> XC_PAGE_SHIFT, + (s->msix->total_entries * PCI_MSIX_ENTRY_SIZE + XC_PAGE_SIZE - 1) + >> XC_PAGE_SHIFT, + DPCI_ADD_MAPPING); +} + +int pt_remove_msix_mapping(XenPCIPassthroughState *s, int bar_index) +{ + XenPTMSIX *msix = s->msix; + + if (!(msix && msix->bar_index == bar_index)) { + return 0; + } + + memory_region_set_enabled(&msix->mmio, true); + + msix->mmio_base_addr = s->bases[bar_index].e_physbase + msix->table_off; + + return xc_domain_memory_mapping(xen_xc, xen_domid, + s->msix->mmio_base_addr >> XC_PAGE_SHIFT, + (s->bases[bar_index].access.maddr + s->msix->table_off) + >> XC_PAGE_SHIFT, + (s->msix->total_entries * PCI_MSIX_ENTRY_SIZE + XC_PAGE_SIZE - 1) + >> XC_PAGE_SHIFT, + DPCI_REMOVE_MAPPING); +} + +int pt_msix_init(XenPCIPassthroughState *s, uint32_t base) +{ + uint8_t id = 0; + uint16_t control = 0; + uint32_t table_off = 0; + int i, total_entries, bar_index; + HostPCIDevice *hd = s->real_device; + PCIDevice *d = &s->dev; + int fd = -1; + XenPTMSIX *msix = NULL; + int rc = 0; + + rc = host_pci_get_byte(hd, base + PCI_CAP_LIST_ID, &id); + if (rc) { + return rc; + } + + if (id != PCI_CAP_ID_MSIX) { + PT_ERR(d, "Invalid id %#x base %#x\n", id, base); + return -1; + } + + host_pci_get_word(hd, base + PCI_MSIX_FLAGS, &control); + total_entries = control & PCI_MSIX_FLAGS_QSIZE; + total_entries += 1; + + s->msix = g_malloc0(sizeof (XenPTMSIX) + + total_entries * sizeof (XenPTMSIXEntry)); + msix = s->msix; + + msix->total_entries = total_entries; + for (i = 0; i < total_entries; i++) { + msix->msix_entry[i].pirq = PT_UNASSIGNED_PIRQ; + } + + memory_region_init_io(&msix->mmio, &pci_msix_ops, s, "passthrough-msix", + total_entries * PCI_MSIX_ENTRY_SIZE); + + host_pci_get_long(hd, base + PCI_MSIX_TABLE, &table_off); + bar_index = msix->bar_index = table_off & PCI_MSIX_FLAGS_BIRMASK; + table_off = msix->table_off = table_off & ~PCI_MSIX_FLAGS_BIRMASK; + msix->table_base = s->real_device->io_regions[bar_index].base_addr; + PT_LOG(d, "get MSI-X table BAR base 0x%"PRIx64"\n", msix->table_base); + + fd = open("/dev/mem", O_RDWR); + if (fd == -1) { + rc = -errno; + PT_ERR(d, "Can''t open /dev/mem: %s\n", strerror(errno)); + goto error_out; + } + PT_LOG(d, "table_off = %#x, total_entries = %d\n", + table_off, total_entries); + msix->table_offset_adjust = table_off & 0x0fff; + msix->phys_iomem_base + mmap(NULL, + total_entries * PCI_MSIX_ENTRY_SIZE + msix->table_offset_adjust, + PROT_WRITE | PROT_READ, + MAP_SHARED | MAP_LOCKED, + fd, + msix->table_base + table_off - msix->table_offset_adjust); + + if (msix->phys_iomem_base == MAP_FAILED) { + rc = -errno; + PT_ERR(d, "Can''t map physical MSI-X table: %s\n", strerror(errno)); + close(fd); + goto error_out; + } + msix->phys_iomem_base = (char *)msix->phys_iomem_base + + msix->table_offset_adjust; + + close(fd); + + PT_LOG(d, "mapping physical MSI-X table to %p\n", msix->phys_iomem_base); + + memory_region_transaction_begin(); + memory_region_add_subregion_overlap(&s->bar[bar_index], msix->table_off, + &msix->mmio, + 2); /* Priority: pci default + 1 */ + memory_region_set_enabled(&msix->mmio, false); + memory_region_transaction_commit(); + + return 0; + +error_out: + memory_region_destroy(&msix->mmio); + g_free(s->msix); + s->msix = NULL; + return rc; +} + +void pt_msix_delete(XenPCIPassthroughState *s) +{ + XenPTMSIX *msix = s->msix; + + if (!msix) { + return; + } + + /* unmap the MSI-X memory mapped register area */ + if (msix->phys_iomem_base) { + PT_LOG(&s->dev, "unmapping physical MSI-X table from %p\n", + msix->phys_iomem_base); + munmap(msix->phys_iomem_base, msix->total_entries * PCI_MSIX_ENTRY_SIZE + + msix->table_offset_adjust); + } + + memory_region_del_subregion(&s->bar[msix->bar_index], &msix->mmio); + memory_region_destroy(&msix->mmio); + + g_free(s->msix); + s->msix = NULL; +} -- Anthony PERARD
Anthony PERARD
2012-Feb-13 12:20 UTC
[PATCH V6 11/11] pci: Do not check if a bus exist in pci_parse_devaddr.
Actually, pci_parse_devaddr checks if the dom/bus of the PCI address exist. But this should be the jobs of a caller. In fact, the two callers of this function will try to retrieve the PCIBus related to the devaddr and return an error if they cannot. Signed-off-by: Anthony PERARD <anthony.perard@citrix.com> --- hw/pci.c | 4 ---- 1 files changed, 0 insertions(+), 4 deletions(-) diff --git a/hw/pci.c b/hw/pci.c index ebb5de9..da7cf79 100644 --- a/hw/pci.c +++ b/hw/pci.c @@ -529,10 +529,6 @@ int pci_parse_devaddr(const char *addr, int *domp, int *busp, if (*e) return -1; - /* Note: QEMU doesn''t implement domains other than 0 */ - if (!pci_find_bus(pci_find_root_bus(dom), bus)) - return -1; - *domp = dom; *busp = bus; *slotp = slot; -- Anthony PERARD
Michael S. Tsirkin
2012-Feb-13 12:53 UTC
Re: [PATCH V6 11/11] pci: Do not check if a bus exist in pci_parse_devaddr.
On Mon, Feb 13, 2012 at 12:20:13PM +0000, Anthony PERARD wrote:> Actually, pci_parse_devaddr checks if the dom/bus of the PCI address exist. But > this should be the jobs of a caller. In fact, the two callers of this function > will try to retrieve the PCIBus related to the devaddr and return an error if > they cannot. > > Signed-off-by: Anthony PERARD <anthony.perard@citrix.com>I agree. It''s a good patch. And this will help address the bridges. Want me to queue this?> --- > hw/pci.c | 4 ---- > 1 files changed, 0 insertions(+), 4 deletions(-) > > diff --git a/hw/pci.c b/hw/pci.c > index ebb5de9..da7cf79 100644 > --- a/hw/pci.c > +++ b/hw/pci.c > @@ -529,10 +529,6 @@ int pci_parse_devaddr(const char *addr, int *domp, int *busp, > if (*e) > return -1; > > - /* Note: QEMU doesn''t implement domains other than 0 */ > - if (!pci_find_bus(pci_find_root_bus(dom), bus)) > - return -1; > - > *domp = dom; > *busp = bus; > *slotp = slot; > -- > Anthony PERARD
Anthony PERARD
2012-Feb-13 15:24 UTC
Re: [PATCH V6 11/11] pci: Do not check if a bus exist in pci_parse_devaddr.
On Mon, 13 Feb 2012, Michael S. Tsirkin wrote:> On Mon, Feb 13, 2012 at 12:20:13PM +0000, Anthony PERARD wrote: > > Actually, pci_parse_devaddr checks if the dom/bus of the PCI address exist. But > > this should be the jobs of a caller. In fact, the two callers of this function > > will try to retrieve the PCIBus related to the devaddr and return an error if > > they cannot. > > > > Signed-off-by: Anthony PERARD <anthony.perard@citrix.com> > > I agree. It''s a good patch. And this will help address the bridges. > Want me to queue this?Yes, go ahead. Thanks you. -- Anthony PERARD
Jan Beulich
2012-Feb-14 13:13 UTC
Re: [Xen-devel] [PATCH V6 10/11] Introduce Xen PCI Passthrough, MSI (3/3)
>>> On 13.02.12 at 13:20, Anthony PERARD <anthony.perard@citrix.com> wrote: > From: Jiang Yunhong <yunhong.jiang@intel.com> > > A more complete history can be found here: > git://xenbits.xensource.com/qemu-xen-unstable.gitThis needs to be updated to include the changes in http://xenbits.xen.org/gitweb/?p=qemu-xen-unstable.git;a=commitdiff;h=bb36d632e4cabf47882adff07a45c6702c4a5b30 (and hopefully the broken function that got fixed in http://xenbits.xen.org/gitweb/?p=qemu-xen-unstable.git;a=commitdiff;h=8cc8a3651c9c5bc2d0086d12f4b870fc525b9387 didn''t even make it into this patch set). In particular it must be avoided to map the MSI-X table with PROT_WRITE, and the respective MMIO range should not get assigned to the guest at all. Jan> Signed-off-by: Jiang Yunhong <yunhong.jiang@intel.com> > Signed-off-by: Shan Haitao <haitao.shan@intel.com> > Signed-off-by: Anthony PERARD <anthony.perard@citrix.com> > --- > Makefile.target | 1 + > hw/apic-msidef.h | 2 + > hw/xen_pci_passthrough.c | 32 ++ > hw/xen_pci_passthrough.h | 48 +++ > hw/xen_pci_passthrough_config_init.c | 480 ++++++++++++++++++++++++ > hw/xen_pci_passthrough_msi.c | 667 > ++++++++++++++++++++++++++++++++++ > 6 files changed, 1230 insertions(+), 0 deletions(-) > create mode 100644 hw/xen_pci_passthrough_msi.c > > diff --git a/Makefile.target b/Makefile.target > index 8fc2ca3..3517cab 100644 > --- a/Makefile.target > +++ b/Makefile.target > @@ -220,6 +220,7 @@ obj-i386-$(CONFIG_XEN) += xen_platform.o > obj-i386-$(CONFIG_XEN_PCI_PASSTHROUGH) += host-pci-device.o > obj-i386-$(CONFIG_XEN_PCI_PASSTHROUGH) += xen_pci_passthrough.o > obj-i386-$(CONFIG_XEN_PCI_PASSTHROUGH) += xen_pci_passthrough_config_init.o > +obj-i386-$(CONFIG_XEN_PCI_PASSTHROUGH) += xen_pci_passthrough_msi.o > > # Inter-VM PCI shared memory > CONFIG_IVSHMEM > diff --git a/hw/apic-msidef.h b/hw/apic-msidef.h > index 3182f0b..6e2eb71 100644 > --- a/hw/apic-msidef.h > +++ b/hw/apic-msidef.h > @@ -22,6 +22,8 @@ > > #define MSI_ADDR_DEST_MODE_SHIFT 2 > > +#define MSI_ADDR_REDIRECTION_SHIFT 3 > + > #define MSI_ADDR_DEST_ID_SHIFT 12 > #define MSI_ADDR_DEST_ID_MASK 0x00ffff0 > > diff --git a/hw/xen_pci_passthrough.c b/hw/xen_pci_passthrough.c > index bdc3690..1257ce2 100644 > --- a/hw/xen_pci_passthrough.c > +++ b/hw/xen_pci_passthrough.c > @@ -36,6 +36,20 @@ > * > * Write ''1'' > * - Set real bit to ''1''. > + * > + * MSI interrupt: > + * Initialize MSI register(pt_msi_setup, pt_msi_update) > + * Bind MSI(xc_domain_update_msi_irq) > + * <fail> > + * - Unmap MSI. > + * - Set dev->msi->pirq to ''-1''. > + * > + * MSI-X interrupt: > + * Initialize MSI-X register(pt_msix_update_one) > + * Bind MSI-X(xc_domain_update_msi_irq) > + * <fail> > + * - Unmap MSI-X. > + * - Set entry->pirq to ''-1''. > */ > > #include <sys/ioctl.h> > @@ -362,6 +376,7 @@ static void pt_iomem_map(XenPCIPassthroughState *s, int > i, > } > > if (!first_map && old_ebase != PT_PCI_BAR_UNMAPPED) { > + pt_add_msix_mapping(s, i); > /* Remove old mapping */ > rc = xc_domain_memory_mapping(xen_xc, xen_domid, > old_ebase >> XC_PAGE_SHIFT, > @@ -386,6 +401,16 @@ static void pt_iomem_map(XenPCIPassthroughState *s, int > i, > if (rc) { > PT_ERR(&s->dev, "create new mapping failed! (rc: %i)\n", rc); > } > + > + rc = pt_remove_msix_mapping(s, i); > + if (rc != 0) { > + PT_ERR(&s->dev, "Remove MSI-X MMIO mapping failed! (rc: %d)\n", > + rc); > + } > + > + if (old_ebase != e_phys && old_ebase != -1) { > + pt_msix_update_remap(s, i); > + } > } > } > > @@ -766,6 +791,13 @@ static int pt_unregister_device(PCIDevice *d) > } > } > > + if (s->msi) { > + pt_msi_disable(s); > + } > + if (s->msix) { > + pt_msix_disable(s); > + } > + > if (machine_irq) { > mapped_machine_irq[machine_irq]--; > > diff --git a/hw/xen_pci_passthrough.h b/hw/xen_pci_passthrough.h > index 0b9902d..deeba89 100644 > --- a/hw/xen_pci_passthrough.h > +++ b/hw/xen_pci_passthrough.h > @@ -174,6 +174,37 @@ typedef struct XenPTRegGroup { > > > #define PT_UNASSIGNED_PIRQ (-1) > +typedef struct XenPTMSI { > + uint16_t flags; > + uint32_t addr_lo; /* guest message address */ > + uint32_t addr_hi; /* guest message upper address */ > + uint16_t data; /* guest message data */ > + uint32_t ctrl_offset; /* saved control offset */ > + int pirq; /* guest pirq corresponding */ > + bool initialized; /* when guest MSI is initialized */ > + bool mapped; /* when pirq is mapped */ > +} XenPTMSI; > + > +typedef struct XenPTMSIXEntry { > + int pirq; > + uint64_t addr; > + uint32_t data; > + uint32_t vector_ctrl; > + bool updated; /* indicate whether MSI ADDR or DATA is updated */ > +} XenPTMSIXEntry; > +typedef struct XenPTMSIX { > + uint32_t ctrl_offset; > + bool enabled; > + int total_entries; > + int bar_index; > + uint64_t table_base; > + uint32_t table_off; > + uint32_t table_offset_adjust; /* page align mmap */ > + uint64_t mmio_base_addr; > + MemoryRegion mmio; > + void *phys_iomem_base; > + XenPTMSIXEntry msix_entry[0]; > +} XenPTMSIX; > > struct XenPCIPassthroughState { > PCIDevice dev; > @@ -186,6 +217,9 @@ struct XenPCIPassthroughState { > > uint32_t machine_irq; > > + XenPTMSI *msi; > + XenPTMSIX *msix; > + > MemoryRegion bar[PCI_NUM_REGIONS - 1]; > MemoryRegion rom; > }; > @@ -262,4 +296,18 @@ static inline uint8_t pci_intx(XenPCIPassthroughState > *s) > return r_val; > } > > +/* MSI/MSI-X */ > +int pt_msi_set_enable(XenPCIPassthroughState *s, bool en); > +int pt_msi_setup(XenPCIPassthroughState *s); > +int pt_msi_update(XenPCIPassthroughState *d); > +void pt_msi_disable(XenPCIPassthroughState *s); > + > +int pt_msix_init(XenPCIPassthroughState *s, uint32_t base); > +void pt_msix_delete(XenPCIPassthroughState *s); > +int pt_msix_update(XenPCIPassthroughState *s); > +int pt_msix_update_remap(XenPCIPassthroughState *s, int bar_index); > +void pt_msix_disable(XenPCIPassthroughState *s); > +int pt_add_msix_mapping(XenPCIPassthroughState *s, int bar_index); > +int pt_remove_msix_mapping(XenPCIPassthroughState *s, int bar_index); > + > #endif /* !QEMU_HW_XEN_PCI_PASSTHROUGH_H */ > diff --git a/hw/xen_pci_passthrough_config_init.c > b/hw/xen_pci_passthrough_config_init.c > index 2fb27ff..430c26a 100644 > --- a/hw/xen_pci_passthrough_config_init.c > +++ b/hw/xen_pci_passthrough_config_init.c > @@ -1125,6 +1125,419 @@ static XenPTRegInfo pt_emu_reg_pcie_tbl[] = { > }; > > > +/******************************** > + * MSI Capability > + */ > + > +/* Helper */ > +static bool pt_msgdata_check_type(uint32_t offset, uint16_t flags) > +{ > + /* check the offset whether matches the type or not */ > + bool is_32 = (offset == PCI_MSI_DATA_32) && !(flags & > PCI_MSI_FLAGS_64BIT); > + bool is_64 = (offset == PCI_MSI_DATA_64) && (flags & > PCI_MSI_FLAGS_64BIT); > + return is_32 || is_64; > +} > + > +/* Message Control register */ > +static int pt_msgctrl_reg_init(XenPCIPassthroughState *s, > + XenPTRegInfo *reg, uint32_t real_offset, > + uint32_t *data) > +{ > + PCIDevice *d = &s->dev; > + XenPTMSI *msi = s->msi; > + uint16_t reg_field = 0; > + > + /* use I/O device register''s value as initial value */ > + reg_field = pci_get_word(d->config + real_offset); > + > + if (reg_field & PCI_MSI_FLAGS_ENABLE) { > + PT_LOG(&s->dev, "MSI already enabled, disabling it first\n"); > + host_pci_set_word(s->real_device, real_offset, > + reg_field & ~PCI_MSI_FLAGS_ENABLE); > + } > + msi->flags |= reg_field; > + msi->ctrl_offset = real_offset; > + msi->initialized = false; > + msi->mapped = false; > + > + *data = reg->init_val; > + return 0; > +} > +static int pt_msgctrl_reg_write(XenPCIPassthroughState *s, XenPTReg > *cfg_entry, > + uint16_t *value, uint16_t dev_value, > + uint16_t valid_mask) > +{ > + XenPTRegInfo *reg = cfg_entry->reg; > + XenPTMSI *msi = s->msi; > + uint16_t writable_mask = 0; > + uint16_t throughable_mask = 0; > + uint16_t val; > + > + /* Currently no support for multi-vector */ > + if (*value & PCI_MSI_FLAGS_QSIZE) { > + PT_WARN(&s->dev, "Tries to set more than 1 vector ctrl %x\n", *value); > + } > + > + /* modify emulate register */ > + writable_mask = reg->emu_mask & ~reg->ro_mask & valid_mask; > + cfg_entry->data = PT_MERGE_VALUE(*value, cfg_entry->data, writable_mask); > + msi->flags |= cfg_entry->data & ~PCI_MSI_FLAGS_ENABLE; > + > + /* create value for writing to I/O device register */ > + val = *value; > + throughable_mask = ~reg->emu_mask & valid_mask; > + *value = PT_MERGE_VALUE(*value, dev_value, throughable_mask); > + > + /* update MSI */ > + if (val & PCI_MSI_FLAGS_ENABLE) { > + /* setup MSI pirq for the first time */ > + if (!msi->initialized) { > + /* Init physical one */ > + PT_LOG(&s->dev, "setup MSI\n"); > + if (pt_msi_setup(s)) { > + /* We do not broadcast the error to the framework code, so > + * that MSI errors are contained in MSI emulation code and > + * QEMU can go on running. > + * Guest MSI would be actually not working. > + */ > + *value &= ~PCI_MSI_FLAGS_ENABLE; > + PT_WARN(&s->dev, "Can not map MSI.\n"); > + return 0; > + } > + if (pt_msi_update(s)) { > + *value &= ~PCI_MSI_FLAGS_ENABLE; > + PT_WARN(&s->dev, "Can not bind MSI\n"); > + return 0; > + } > + msi->initialized = true; > + msi->mapped = true; > + } > + msi->flags |= PCI_MSI_FLAGS_ENABLE; > + } else { > + msi->flags &= ~PCI_MSI_FLAGS_ENABLE; > + } > + > + /* pass through MSI_ENABLE bit */ > + *value &= ~PCI_MSI_FLAGS_ENABLE; > + *value |= val & PCI_MSI_FLAGS_ENABLE; > + > + return 0; > +} > + > +/* initialize Message Upper Address register */ > +static int pt_msgaddr64_reg_init(XenPCIPassthroughState *s, > + XenPTRegInfo *reg, uint32_t real_offset, > + uint32_t *data) > +{ > + /* no need to initialize in case of 32 bit type */ > + if (!(s->msi->flags & PCI_MSI_FLAGS_64BIT)) { > + *data = PT_INVALID_REG; > + } else { > + *data = reg->init_val; > + } > + > + return 0; > +} > +/* this function will be called twice (for 32 bit and 64 bit type) */ > +/* initialize Message Data register */ > +static int pt_msgdata_reg_init(XenPCIPassthroughState *s, > + XenPTRegInfo *reg, uint32_t real_offset, > + uint32_t *data) > +{ > + uint32_t flags = s->msi->flags; > + uint32_t offset = reg->offset; > + > + /* check the offset whether matches the type or not */ > + if (pt_msgdata_check_type(offset, flags)) { > + *data = reg->init_val; > + } else { > + *data = PT_INVALID_REG; > + } > + return 0; > +} > + > +/* write Message Address register */ > +static int pt_msgaddr32_reg_write(XenPCIPassthroughState *s, > + XenPTReg *cfg_entry, uint32_t *value, > + uint32_t dev_value, uint32_t valid_mask) > +{ > + XenPTRegInfo *reg = cfg_entry->reg; > + uint32_t writable_mask = 0; > + uint32_t throughable_mask = 0; > + uint32_t old_addr = cfg_entry->data; > + > + /* modify emulate register */ > + writable_mask = reg->emu_mask & ~reg->ro_mask & valid_mask; > + cfg_entry->data = PT_MERGE_VALUE(*value, cfg_entry->data, writable_mask); > + s->msi->addr_lo = cfg_entry->data; > + > + /* create value for writing to I/O device register */ > + throughable_mask = ~reg->emu_mask & valid_mask; > + *value = PT_MERGE_VALUE(*value, dev_value, throughable_mask); > + > + /* update MSI */ > + if (cfg_entry->data != old_addr) { > + if (s->msi->mapped) { > + pt_msi_update(s); > + } > + } > + > + return 0; > +} > +/* write Message Upper Address register */ > +static int pt_msgaddr64_reg_write(XenPCIPassthroughState *s, > + XenPTReg *cfg_entry, uint32_t *value, > + uint32_t dev_value, uint32_t valid_mask) > +{ > + XenPTRegInfo *reg = cfg_entry->reg; > + uint32_t writable_mask = 0; > + uint32_t throughable_mask = 0; > + uint32_t old_addr = cfg_entry->data; > + > + /* check whether the type is 64 bit or not */ > + if (!(s->msi->flags & PCI_MSI_FLAGS_64BIT)) { > + PT_ERR(&s->dev, > + "Can''t write to the upper address without 64 bit > support\n"); > + return -1; > + } > + > + /* modify emulate register */ > + writable_mask = reg->emu_mask & ~reg->ro_mask & valid_mask; > + cfg_entry->data = PT_MERGE_VALUE(*value, cfg_entry->data, writable_mask); > + /* update the msi_info too */ > + s->msi->addr_hi = cfg_entry->data; > + > + /* create value for writing to I/O device register */ > + throughable_mask = ~reg->emu_mask & valid_mask; > + *value = PT_MERGE_VALUE(*value, dev_value, throughable_mask); > + > + /* update MSI */ > + if (cfg_entry->data != old_addr) { > + if (s->msi->mapped) { > + pt_msi_update(s); > + } > + } > + > + return 0; > +} > + > + > +/* this function will be called twice (for 32 bit and 64 bit type) */ > +/* write Message Data register */ > +static int pt_msgdata_reg_write(XenPCIPassthroughState *s, XenPTReg > *cfg_entry, > + uint16_t *value, uint16_t dev_value, > + uint16_t valid_mask) > +{ > + XenPTRegInfo *reg = cfg_entry->reg; > + XenPTMSI *msi = s->msi; > + uint16_t writable_mask = 0; > + uint16_t throughable_mask = 0; > + uint16_t old_data = cfg_entry->data; > + uint32_t offset = reg->offset; > + > + /* check the offset whether matches the type or not */ > + if (!pt_msgdata_check_type(offset, msi->flags)) { > + /* exit I/O emulator */ > + PT_ERR(&s->dev, "the offset does not match the 32/64 bit type!\n"); > + return -1; > + } > + > + /* modify emulate register */ > + writable_mask = reg->emu_mask & ~reg->ro_mask & valid_mask; > + cfg_entry->data = PT_MERGE_VALUE(*value, cfg_entry->data, writable_mask); > + /* update the msi_info too */ > + msi->data = cfg_entry->data; > + > + /* create value for writing to I/O device register */ > + throughable_mask = ~reg->emu_mask & valid_mask; > + *value = PT_MERGE_VALUE(*value, dev_value, throughable_mask); > + > + /* update MSI */ > + if (cfg_entry->data != old_data) { > + if (msi->mapped) { > + pt_msi_update(s); > + } > + } > + > + return 0; > +} > + > +/* MSI Capability Structure reg static infomation table */ > +static XenPTRegInfo pt_emu_reg_msi_tbl[] = { > + /* Next Pointer reg */ > + { > + .offset = PCI_CAP_LIST_NEXT, > + .size = 1, > + .init_val = 0x00, > + .ro_mask = 0xFF, > + .emu_mask = 0xFF, > + .init = pt_ptr_reg_init, > + .u.b.read = pt_byte_reg_read, > + .u.b.write = pt_byte_reg_write, > + .u.b.restore = NULL, > + }, > + /* Message Control reg */ > + { > + .offset = PCI_MSI_FLAGS, > + .size = 2, > + .init_val = 0x0000, > + .ro_mask = 0xFF8E, > + .emu_mask = 0x007F, > + .init = pt_msgctrl_reg_init, > + .u.w.read = pt_word_reg_read, > + .u.w.write = pt_msgctrl_reg_write, > + .u.w.restore = NULL, > + }, > + /* Message Address reg */ > + { > + .offset = PCI_MSI_ADDRESS_LO, > + .size = 4, > + .init_val = 0x00000000, > + .ro_mask = 0x00000003, > + .emu_mask = 0xFFFFFFFF, > + .no_wb = 1, > + .init = pt_common_reg_init, > + .u.dw.read = pt_long_reg_read, > + .u.dw.write = pt_msgaddr32_reg_write, > + .u.dw.restore = NULL, > + }, > + /* Message Upper Address reg (if PCI_MSI_FLAGS_64BIT set) */ > + { > + .offset = PCI_MSI_ADDRESS_HI, > + .size = 4, > + .init_val = 0x00000000, > + .ro_mask = 0x00000000, > + .emu_mask = 0xFFFFFFFF, > + .no_wb = 1, > + .init = pt_msgaddr64_reg_init, > + .u.dw.read = pt_long_reg_read, > + .u.dw.write = pt_msgaddr64_reg_write, > + .u.dw.restore = NULL, > + }, > + /* Message Data reg (16 bits of data for 32-bit devices) */ > + { > + .offset = PCI_MSI_DATA_32, > + .size = 2, > + .init_val = 0x0000, > + .ro_mask = 0x0000, > + .emu_mask = 0xFFFF, > + .no_wb = 1, > + .init = pt_msgdata_reg_init, > + .u.w.read = pt_word_reg_read, > + .u.w.write = pt_msgdata_reg_write, > + .u.w.restore = NULL, > + }, > + /* Message Data reg (16 bits of data for 64-bit devices) */ > + { > + .offset = PCI_MSI_DATA_64, > + .size = 2, > + .init_val = 0x0000, > + .ro_mask = 0x0000, > + .emu_mask = 0xFFFF, > + .no_wb = 1, > + .init = pt_msgdata_reg_init, > + .u.w.read = pt_word_reg_read, > + .u.w.write = pt_msgdata_reg_write, > + .u.w.restore = NULL, > + }, > + { > + .size = 0, > + }, > +}; > + > + > +/************************************** > + * MSI-X Capability > + */ > + > +/* Message Control register for MSI-X */ > +static int pt_msixctrl_reg_init(XenPCIPassthroughState *s, > + XenPTRegInfo *reg, uint32_t real_offset, > + uint32_t *data) > +{ > + PCIDevice *d = &s->dev; > + uint16_t reg_field = 0; > + > + /* use I/O device register''s value as initial value */ > + reg_field = pci_get_word(d->config + real_offset); > + > + if (reg_field & PCI_MSIX_FLAGS_ENABLE) { > + PT_LOG(d, "MSIX already enabled, disabling it first\n"); > + host_pci_set_word(s->real_device, real_offset, > + reg_field & ~PCI_MSIX_FLAGS_ENABLE); > + } > + > + s->msix->ctrl_offset = real_offset; > + > + *data = reg->init_val; > + return 0; > +} > +static int pt_msixctrl_reg_write(XenPCIPassthroughState *s, > + XenPTReg *cfg_entry, uint16_t *value, > + uint16_t dev_value, uint16_t valid_mask) > +{ > + XenPTRegInfo *reg = cfg_entry->reg; > + uint16_t writable_mask = 0; > + uint16_t throughable_mask = 0; > + > + int debug_msix_enabled_old; > + > + /* modify emulate register */ > + writable_mask = reg->emu_mask & ~reg->ro_mask & valid_mask; > + cfg_entry->data = PT_MERGE_VALUE(*value, cfg_entry->data, writable_mask); > + > + /* create value for writing to I/O device register */ > + throughable_mask = ~reg->emu_mask & valid_mask; > + *value = PT_MERGE_VALUE(*value, dev_value, throughable_mask); > + > + /* update MSI-X */ > + if ((*value & PCI_MSIX_FLAGS_ENABLE) > + && !(*value & PCI_MSIX_FLAGS_MASKALL)) { > + pt_msix_update(s); > + } > + > + debug_msix_enabled_old = s->msix->enabled; > + s->msix->enabled = !!(*value & PCI_MSIX_FLAGS_ENABLE); > + if (s->msix->enabled != debug_msix_enabled_old) { > + PT_LOG(&s->dev, "%s MSI-X\n", > + s->msix->enabled ? "enable" : "disable"); > + } > + > + return 0; > +} > + > +/* MSI-X Capability Structure reg static infomation table */ > +static XenPTRegInfo pt_emu_reg_msix_tbl[] = { > + /* Next Pointer reg */ > + { > + .offset = PCI_CAP_LIST_NEXT, > + .size = 1, > + .init_val = 0x00, > + .ro_mask = 0xFF, > + .emu_mask = 0xFF, > + .init = pt_ptr_reg_init, > + .u.b.read = pt_byte_reg_read, > + .u.b.write = pt_byte_reg_write, > + .u.b.restore = NULL, > + }, > + /* Message Control reg */ > + { > + .offset = PCI_MSI_FLAGS, > + .size = 2, > + .init_val = 0x0000, > + .ro_mask = 0x3FFF, > + .emu_mask = 0x0000, > + .init = pt_msixctrl_reg_init, > + .u.w.read = pt_word_reg_read, > + .u.w.write = pt_msixctrl_reg_write, > + .u.w.restore = NULL, > + }, > + { > + .size = 0, > + }, > +}; > + > + > /**************************** > * Capabilities > */ > @@ -1218,6 +1631,49 @@ static int pt_pcie_size_init(XenPCIPassthroughState > *s, > *size = pcie_size; > return 0; > } > +/* get MSI Capability Structure register group size */ > +static int pt_msi_size_init(XenPCIPassthroughState *s, > + const XenPTRegGroupInfo *grp_reg, > + uint32_t base_offset, uint8_t *size) > +{ > + PCIDevice *d = &s->dev; > + uint16_t msg_ctrl = 0; > + uint8_t msi_size = 0xa; > + > + msg_ctrl = pci_get_word(d->config + (base_offset + PCI_MSI_FLAGS)); > + > + /* check if 64-bit address is capable of per-vector masking */ > + if (msg_ctrl & PCI_MSI_FLAGS_64BIT) { > + msi_size += 4; > + } > + if (msg_ctrl & PCI_MSI_FLAGS_MASKBIT) { > + msi_size += 10; > + } > + > + s->msi = g_new0(XenPTMSI, 1); > + s->msi->pirq = PT_UNASSIGNED_PIRQ; > + > + *size = msi_size; > + return 0; > +} > +/* get MSI-X Capability Structure register group size */ > +static int pt_msix_size_init(XenPCIPassthroughState *s, > + const XenPTRegGroupInfo *grp_reg, > + uint32_t base_offset, uint8_t *size) > +{ > + int rc = 0; > + > + rc = pt_msix_init(s, base_offset); > + > + if (rc < 0) { > + PT_ERR(&s->dev, "Internal error: Invalid pt_msix_init.\n"); > + return rc; > + } > + > + *size = grp_reg->grp_size; > + return 0; > +} > + > > static const XenPTRegGroupInfo pt_emu_reg_grp_tbl[] = { > /* Header Type0 reg group */ > @@ -1250,6 +1706,14 @@ static const XenPTRegGroupInfo pt_emu_reg_grp_tbl[] = > { > .grp_size = 0x04, > .size_init = pt_reg_grp_size_init, > }, > + /* MSI Capability Structure reg group */ > + { > + .grp_id = PCI_CAP_ID_MSI, > + .grp_type = GRP_TYPE_EMU, > + .grp_size = 0xFF, > + .size_init = pt_msi_size_init, > + .emu_reg_tbl = pt_emu_reg_msi_tbl, > + }, > /* PCI-X Capabilities List Item reg group */ > { > .grp_id = PCI_CAP_ID_PCIX, > @@ -1294,6 +1758,14 @@ static const XenPTRegGroupInfo pt_emu_reg_grp_tbl[] = > { > .size_init = pt_pcie_size_init, > .emu_reg_tbl = pt_emu_reg_pcie_tbl, > }, > + /* MSI-X Capability Structure reg group */ > + { > + .grp_id = PCI_CAP_ID_MSIX, > + .grp_type = GRP_TYPE_EMU, > + .grp_size = 0x0C, > + .size_init = pt_msix_size_init, > + .emu_reg_tbl = pt_emu_reg_msix_tbl, > + }, > { > .grp_size = 0, > }, > @@ -1478,6 +1950,14 @@ void pt_config_delete(XenPCIPassthroughState *s) > struct XenPTRegGroup *reg_group, *next_grp; > struct XenPTReg *reg, *next_reg; > > + /* free MSI/MSI-X info table */ > + if (s->msix) { > + pt_msix_delete(s); > + } > + if (s->msi) { > + g_free(s->msi); > + } > + > /* free all register group entry */ > QLIST_FOREACH_SAFE(reg_group, &s->reg_grp_tbl, entries, next_grp) { > /* free all register entry */ > diff --git a/hw/xen_pci_passthrough_msi.c b/hw/xen_pci_passthrough_msi.c > new file mode 100644 > index 0000000..0b81060 > --- /dev/null > +++ b/hw/xen_pci_passthrough_msi.c > @@ -0,0 +1,667 @@ > +/* > + * Copyright (c) 2007, Intel Corporation. > + * > + * This work is licensed under the terms of the GNU GPL, version 2. See > + * the COPYING file in the top-level directory. > + * > + * Jiang Yunhong <yunhong.jiang@intel.com> > + * > + * This file implements direct PCI assignment to a HVM guest > + */ > + > +#include <sys/mman.h> > + > +#include "xen_backend.h" > +#include "xen_pci_passthrough.h" > +#include "apic-msidef.h" > + > + > +#define AUTO_ASSIGN -1 > + > +/* shift count for gflags */ > +#define GFLAGS_SHIFT_DEST_ID 0 > +#define GFLAGS_SHIFT_RH 8 > +#define GFLAGS_SHIFT_DM 9 > +#define GLFAGS_SHIFT_DELIV_MODE 12 > +#define GLFAGS_SHIFT_TRG_MODE 15 > + > + > +/* > + * Helpers > + */ > + > +static inline uint8_t msi_vector(uint32_t data) > +{ > + return (data & MSI_DATA_VECTOR_MASK) >> MSI_DATA_VECTOR_SHIFT; > +} > + > +static inline uint8_t msi_dest_id(uint32_t addr) > +{ > + return (addr & MSI_ADDR_DEST_ID_MASK) >> MSI_ADDR_DEST_ID_SHIFT; > +} > + > +static inline uint32_t msi_ext_dest_id(uint32_t addr_hi) > +{ > + return addr_hi & 0xffffff00; > +} > + > +static uint32_t msi_gflags(uint32_t data, uint64_t addr) > +{ > + uint32_t result = 0; > + int rh, dm, dest_id, deliv_mode, trig_mode; > + > + rh = (addr >> MSI_ADDR_REDIRECTION_SHIFT) & 0x1; > + dm = (addr >> MSI_ADDR_DEST_MODE_SHIFT) & 0x1; > + dest_id = msi_dest_id(addr); > + deliv_mode = (data >> MSI_DATA_DELIVERY_MODE_SHIFT) & 0x7; > + trig_mode = (data >> MSI_DATA_TRIGGER_SHIFT) & 0x1; > + > + result = dest_id | (rh << GFLAGS_SHIFT_RH) | (dm << GFLAGS_SHIFT_DM) | > + (deliv_mode << GLFAGS_SHIFT_DELIV_MODE) | > + (trig_mode << GLFAGS_SHIFT_TRG_MODE); > + > + return result; > +} > + > +static inline uint64_t msi_addr64(XenPTMSI *msi) > +{ > + return (uint64_t)msi->addr_hi << 32 | msi->addr_lo; > +} > + > +static int msi_msix_enable(XenPCIPassthroughState *s, > + uint32_t address, > + uint16_t flag, > + bool enable) > +{ > + uint16_t val = 0; > + > + if (!address) { > + return -1; > + } > + > + host_pci_get_word(s->real_device, address, &val); > + if (enable) { > + val |= flag; > + } else { > + val &= ~flag; > + } > + host_pci_set_word(s->real_device, address, val); > + return 0; > +} > + > +static int msi_msix_setup(XenPCIPassthroughState *s, > + uint64_t addr, > + uint32_t data, > + int *ppirq, > + bool is_msix, > + int msix_entry, > + bool is_not_mapped) > +{ > + uint8_t gvec = msi_vector(data); > + int rc = 0; > + > + assert((!is_msix && msix_entry == 0) || is_msix); > + > + if (gvec == 0) { > + /* if gvec is 0, the guest is asking for a particular pirq that > + * is passed as dest_id */ > + *ppirq = msi_ext_dest_id(addr >> 32) | msi_dest_id(addr); > + if (!*ppirq) { > + /* this probably identifies an misconfiguration of the guest, > + * try the emulated path */ > + *ppirq = PT_UNASSIGNED_PIRQ; > + } else { > + PT_LOG(&s->dev, "requested pirq %d for MSI%s" > + " (vec: %#x, entry: %#x)\n", > + *ppirq, is_msix ? "-X" : "", gvec, msix_entry); > + } > + } > + > + if (is_not_mapped) { > + uint64_t table_base = 0; > + > + if (is_msix) { > + table_base = s->msix->table_base; > + } > + > + rc = xc_physdev_map_pirq_msi(xen_xc, xen_domid, AUTO_ASSIGN, ppirq, > + PCI_DEVFN(s->real_device->dev, > + s->real_device->func), > + s->real_device->bus, > + msix_entry, table_base); > + if (rc) { > + PT_ERR(&s->dev, "Mapping of MSI%s (rc: %i, vec: %#x, entry > %#x)\n", > + is_msix ? "-X" : "", rc, gvec, msix_entry); > + return rc; > + } > + } > + > + return 0; > +} > +static int msi_msix_update(XenPCIPassthroughState *s, > + uint64_t addr, > + uint32_t data, > + int pirq, > + bool is_msix, > + int msix_entry, > + int *old_pirq) > +{ > + PCIDevice *d = &s->dev; > + uint8_t gvec = msi_vector(data); > + uint32_t gflags = msi_gflags(data, addr); > + int rc = 0; > + uint64_t table_addr = 0; > + > + PT_LOG(d, "Updating MSI%s with pirq %d gvec %#x gflags %#x (entry: > %#x)\n", > + is_msix ? "-X" : "", pirq, gvec, gflags, msix_entry); > + > + if (is_msix) { > + table_addr = s->msix->mmio_base_addr; > + } > + > + rc = xc_domain_update_msi_irq(xen_xc, xen_domid, gvec, > + pirq, gflags, table_addr); > + > + if (rc) { > + PT_ERR(d, "Updating of MSI%s failed. (rc: %d)\n", > + is_msix ? "-X" : "", rc); > + > + if (xc_physdev_unmap_pirq(xen_xc, xen_domid, *old_pirq)) { > + PT_ERR(d, "Unmapping of MSI%s pirq %d failed.\n", > + is_msix ? "-X" : "", *old_pirq); > + } > + *old_pirq = PT_UNASSIGNED_PIRQ; > + } > + return rc; > +} > + > +static int msi_msix_disable(XenPCIPassthroughState *s, > + uint64_t addr, > + uint32_t data, > + int pirq, > + bool is_msix, > + bool is_binded) > +{ > + PCIDevice *d = &s->dev; > + uint8_t gvec = msi_vector(data); > + uint32_t gflags = msi_gflags(data, addr); > + int rc = 0; > + > + if (pirq == PT_UNASSIGNED_PIRQ) { > + return 0; > + } > + > + if (is_binded) { > + PT_LOG(d, "Unbind MSI%s with pirq %d, gvec %#x\n", > + is_msix ? "-X" : "", pirq, gvec); > + rc = xc_domain_unbind_msi_irq(xen_xc, xen_domid, gvec, pirq, > gflags); > + if (rc) { > + PT_ERR(d, "Unbinding of MSI%s failed. (pirq: %d, gvec: %#x)\n", > + is_msix ? "-X" : "", pirq, gvec); > + return rc; > + } > + } > + > + PT_LOG(d, "Unmap MSI%s pirq %d\n", is_msix ? "-X" : "", pirq); > + rc = xc_physdev_unmap_pirq(xen_xc, xen_domid, pirq); > + if (rc) { > + PT_ERR(d, "Unmapping of MSI%s pirq %d failed. (rc: %i)\n", > + is_msix ? "-X" : "", pirq, rc); > + return rc; > + } > + > + return 0; > +} > + > +/* > + * MSI virtualization functions > + */ > + > +int pt_msi_set_enable(XenPCIPassthroughState *s, bool enable) > +{ > + PT_LOG(&s->dev, "%s MSI.\n", enable ? "enabling" : "disabling"); > + > + if (!s->msi) { > + return -1; > + } > + > + return msi_msix_enable(s, s->msi->ctrl_offset, PCI_MSI_FLAGS_ENABLE, > + enable); > +} > + > +/* setup physical msi, but don''t enable it */ > +int pt_msi_setup(XenPCIPassthroughState *s) > +{ > + int pirq = PT_UNASSIGNED_PIRQ; > + int rc = 0; > + XenPTMSI *msi = s->msi; > + > + if (msi->initialized) { > + PT_ERR(&s->dev, > + "Setup physical MSI when it has been properly > initialized.\n"); > + return -1; > + } > + > + rc = msi_msix_setup(s, msi_addr64(msi), msi->data, &pirq, false, 0, true); > + if (rc) { > + return rc; > + } > + > + if (pirq < 0) { > + PT_ERR(&s->dev, "Invalid pirq number: %d.\n", pirq); > + return -1; > + } > + > + msi->pirq = pirq; > + PT_LOG(&s->dev, "MSI mapped with pirq %d.\n", pirq); > + > + return 0; > +} > + > +int pt_msi_update(XenPCIPassthroughState *s) > +{ > + XenPTMSI *msi = s->msi; > + return msi_msix_update(s, msi_addr64(msi), msi->data, msi->pirq, > + false, 0, &msi->pirq); > +} > + > +void pt_msi_disable(XenPCIPassthroughState *s) > +{ > + XenPTMSI *msi = s->msi; > + > + if (!msi) { > + return; > + } > + > + pt_msi_set_enable(s, false); > + > + msi_msix_disable(s, msi_addr64(msi), msi->data, msi->pirq, false, > + msi->initialized); > + > + /* clear msi info */ > + msi->flags = 0; > + msi->mapped = false; > + msi->pirq = PT_UNASSIGNED_PIRQ; > +} > + > +/* > + * MSI-X virtualization functions > + */ > + > +static int msix_set_enable(XenPCIPassthroughState *s, bool enabled) > +{ > + PT_LOG(&s->dev, "%s MSI-X.\n", enabled ? "enabling" : "disabling"); > + > + if (!s->msix) { > + return -1; > + } > + > + return msi_msix_enable(s, s->msix->ctrl_offset, PCI_MSIX_FLAGS_ENABLE, > + enabled); > +} > + > +static void mask_physical_msix_entry(XenPCIPassthroughState *s, > + int entry_nr, int mask) > +{ > + void *phys_off; > + > + phys_off = s->msix->phys_iomem_base + PCI_MSIX_ENTRY_SIZE * entry_nr > + + PCI_MSIX_ENTRY_VECTOR_CTRL; > + *(uint32_t *)phys_off = mask; > +} > + > +static int pt_msix_update_one(XenPCIPassthroughState *s, int entry_nr) > +{ > + XenPTMSIXEntry *entry = NULL; > + int pirq; > + int rc; > + > + if (entry_nr < 0 || entry_nr >= s->msix->total_entries) { > + return -EINVAL; > + } > + > + entry = &s->msix->msix_entry[entry_nr]; > + > + if (!entry->updated) { > + return 0; > + } > + > + pirq = entry->pirq; > + > + rc = msi_msix_setup(s, entry->data, entry->data, &pirq, true, entry_nr, > + entry->pirq == PT_UNASSIGNED_PIRQ); > + if (rc) { > + return rc; > + } > + if (entry->pirq == PT_UNASSIGNED_PIRQ) { > + entry->pirq = pirq; > + } > + > + rc = msi_msix_update(s, entry->addr, entry->data, pirq, true, > + entry_nr, &entry->pirq); > + > + if (!rc) { > + entry->updated = false; > + } > + > + return rc; > +} > + > +int pt_msix_update(XenPCIPassthroughState *s) > +{ > + XenPTMSIX *msix = s->msix; > + int i; > + > + for (i = 0; i < msix->total_entries; i++) { > + pt_msix_update_one(s, i); > + } > + > + return 0; > +} > + > +void pt_msix_disable(XenPCIPassthroughState *s) > +{ > + int i = 0; > + > + msix_set_enable(s, false); > + > + for (i = 0; i < s->msix->total_entries; i++) { > + XenPTMSIXEntry *entry = &s->msix->msix_entry[i]; > + > + msi_msix_disable(s, entry->addr, entry->data, entry->pirq, true, true); > + > + /* clear MSI-X info */ > + entry->pirq = PT_UNASSIGNED_PIRQ; > + entry->updated = false; > + } > +} > + > +int pt_msix_update_remap(XenPCIPassthroughState *s, int bar_index) > +{ > + XenPTMSIXEntry *entry; > + int i, ret; > + > + if (!(s->msix && s->msix->bar_index == bar_index)) { > + return 0; > + } > + > + for (i = 0; i < s->msix->total_entries; i++) { > + entry = &s->msix->msix_entry[i]; > + if (entry->pirq != PT_UNASSIGNED_PIRQ) { > + ret = xc_domain_unbind_pt_irq(xen_xc, xen_domid, entry->pirq, > + PT_IRQ_TYPE_MSI, 0, 0, 0, 0); > + if (ret) { > + PT_ERR(&s->dev, "unbind MSI-X entry %d failed\n", entry->pirq); > + } > + entry->updated = true; > + } > + } > + pt_msix_update(s); > + > + return 0; > +} > + > +static uint32_t get_entry_value(XenPTMSIXEntry *e, int offset) > +{ > + switch (offset) { > + case PCI_MSIX_ENTRY_LOWER_ADDR: > + return e->addr & UINT32_MAX; > + case PCI_MSIX_ENTRY_UPPER_ADDR: > + return e->addr >> 32; > + case PCI_MSIX_ENTRY_DATA: > + return e->data; > + case PCI_MSIX_ENTRY_VECTOR_CTRL: > + return e->vector_ctrl; > + default: > + return 0; > + } > +} > + > +static void set_entry_value(XenPTMSIXEntry *e, int offset, uint32_t val) > +{ > + switch (offset) { > + case PCI_MSIX_ENTRY_LOWER_ADDR: > + e->addr = (e->addr & ((uint64_t)UINT32_MAX << 32)) | val; > + break; > + case PCI_MSIX_ENTRY_UPPER_ADDR: > + e->addr = (uint64_t)val << 32 | (e->addr & UINT32_MAX); > + break; > + case PCI_MSIX_ENTRY_DATA: > + e->data = val; > + break; > + case PCI_MSIX_ENTRY_VECTOR_CTRL: > + e->vector_ctrl = val; > + break; > + } > +} > + > +static void pci_msix_write(void *opaque, target_phys_addr_t addr, > + uint64_t val, unsigned size) > +{ > + XenPCIPassthroughState *s = opaque; > + XenPTMSIX *msix = s->msix; > + XenPTMSIXEntry *entry; > + int entry_nr, offset; > + > + entry_nr = addr / PCI_MSIX_ENTRY_SIZE; > + if (entry_nr < 0 || entry_nr >= msix->total_entries) { > + PT_ERR(&s->dev, "asked MSI-X entry ''%i'' invalid!\n", entry_nr); > + return; > + } > + entry = &msix->msix_entry[entry_nr]; > + offset = addr % PCI_MSIX_ENTRY_SIZE; > + > + if (offset != PCI_MSIX_ENTRY_VECTOR_CTRL) { > + const volatile uint32_t *vec_ctrl; > + > + if (get_entry_value(entry, offset) == val) { > + return; > + } > + > + /* > + * If Xen intercepts the mask bit access, entry->vec_ctrl may not be > + * up-to-date. Read from hardware directly. > + */ > + vec_ctrl = s->msix->phys_iomem_base + entry_nr * PCI_MSIX_ENTRY_SIZE > + + PCI_MSIX_ENTRY_VECTOR_CTRL; > + > + if (msix->enabled && !(*vec_ctrl & PCI_MSIX_ENTRY_CTRL_MASKBIT)) { > + PT_ERR(&s->dev, "Can''t update msix entry %d since MSI-X is already > " > + "enabled.\n", entry_nr); > + return; > + } > + > + entry->updated = true; > + } > + > + set_entry_value(entry, offset, val); > + > + if (offset == PCI_MSIX_ENTRY_VECTOR_CTRL) { > + if (msix->enabled && !(val & PCI_MSIX_ENTRY_CTRL_MASKBIT)) { > + pt_msix_update_one(s, entry_nr); > + } > + mask_physical_msix_entry(s, entry_nr, > + entry->vector_ctrl & PCI_MSIX_ENTRY_CTRL_MASKBIT); > + } > +} > + > +static uint64_t pci_msix_read(void *opaque, target_phys_addr_t addr, > + unsigned size) > +{ > + XenPCIPassthroughState *s = opaque; > + XenPTMSIX *msix = s->msix; > + int entry_nr, offset; > + > + entry_nr = addr / PCI_MSIX_ENTRY_SIZE; > + if (entry_nr < 0 || entry_nr >= msix->total_entries) { > + PT_ERR(&s->dev, "asked MSI-X entry ''%i'' invalid!\n", entry_nr); > + return 0; > + } > + > + offset = addr % PCI_MSIX_ENTRY_SIZE; > + > + return get_entry_value(&msix->msix_entry[entry_nr], offset); > +} > + > +static const MemoryRegionOps pci_msix_ops = { > + .read = pci_msix_read, > + .write = pci_msix_write, > + .endianness = DEVICE_NATIVE_ENDIAN, > + .valid = { > + .min_access_size = 4, > + .max_access_size = 4, > + .unaligned = false, > + }, > +}; > + > +int pt_add_msix_mapping(XenPCIPassthroughState *s, int bar_index) > +{ > + XenPTMSIX *msix = s->msix; > + > + if (!(s->msix && s->msix->bar_index == bar_index)) { > + return 0; > + } > + > + memory_region_set_enabled(&msix->mmio, false); > + return xc_domain_memory_mapping(xen_xc, xen_domid, > + s->msix->mmio_base_addr >> XC_PAGE_SHIFT, > + (s->bases[bar_index].access.maddr + s->msix->table_off) > + >> XC_PAGE_SHIFT, > + (s->msix->total_entries * PCI_MSIX_ENTRY_SIZE + XC_PAGE_SIZE - 1) > + >> XC_PAGE_SHIFT, > + DPCI_ADD_MAPPING); > +} > + > +int pt_remove_msix_mapping(XenPCIPassthroughState *s, int bar_index) > +{ > + XenPTMSIX *msix = s->msix; > + > + if (!(msix && msix->bar_index == bar_index)) { > + return 0; > + } > + > + memory_region_set_enabled(&msix->mmio, true); > + > + msix->mmio_base_addr = s->bases[bar_index].e_physbase + msix->table_off; > + > + return xc_domain_memory_mapping(xen_xc, xen_domid, > + s->msix->mmio_base_addr >> XC_PAGE_SHIFT, > + (s->bases[bar_index].access.maddr + s->msix->table_off) > + >> XC_PAGE_SHIFT, > + (s->msix->total_entries * PCI_MSIX_ENTRY_SIZE + XC_PAGE_SIZE - 1) > + >> XC_PAGE_SHIFT, > + DPCI_REMOVE_MAPPING); > +} > + > +int pt_msix_init(XenPCIPassthroughState *s, uint32_t base) > +{ > + uint8_t id = 0; > + uint16_t control = 0; > + uint32_t table_off = 0; > + int i, total_entries, bar_index; > + HostPCIDevice *hd = s->real_device; > + PCIDevice *d = &s->dev; > + int fd = -1; > + XenPTMSIX *msix = NULL; > + int rc = 0; > + > + rc = host_pci_get_byte(hd, base + PCI_CAP_LIST_ID, &id); > + if (rc) { > + return rc; > + } > + > + if (id != PCI_CAP_ID_MSIX) { > + PT_ERR(d, "Invalid id %#x base %#x\n", id, base); > + return -1; > + } > + > + host_pci_get_word(hd, base + PCI_MSIX_FLAGS, &control); > + total_entries = control & PCI_MSIX_FLAGS_QSIZE; > + total_entries += 1; > + > + s->msix = g_malloc0(sizeof (XenPTMSIX) > + + total_entries * sizeof (XenPTMSIXEntry)); > + msix = s->msix; > + > + msix->total_entries = total_entries; > + for (i = 0; i < total_entries; i++) { > + msix->msix_entry[i].pirq = PT_UNASSIGNED_PIRQ; > + } > + > + memory_region_init_io(&msix->mmio, &pci_msix_ops, s, "passthrough-msix", > + total_entries * PCI_MSIX_ENTRY_SIZE); > + > + host_pci_get_long(hd, base + PCI_MSIX_TABLE, &table_off); > + bar_index = msix->bar_index = table_off & PCI_MSIX_FLAGS_BIRMASK; > + table_off = msix->table_off = table_off & ~PCI_MSIX_FLAGS_BIRMASK; > + msix->table_base = s->real_device->io_regions[bar_index].base_addr; > + PT_LOG(d, "get MSI-X table BAR base 0x%"PRIx64"\n", msix->table_base); > + > + fd = open("/dev/mem", O_RDWR); > + if (fd == -1) { > + rc = -errno; > + PT_ERR(d, "Can''t open /dev/mem: %s\n", strerror(errno)); > + goto error_out; > + } > + PT_LOG(d, "table_off = %#x, total_entries = %d\n", > + table_off, total_entries); > + msix->table_offset_adjust = table_off & 0x0fff; > + msix->phys_iomem_base > + mmap(NULL, > + total_entries * PCI_MSIX_ENTRY_SIZE + msix->table_offset_adjust, > + PROT_WRITE | PROT_READ, > + MAP_SHARED | MAP_LOCKED, > + fd, > + msix->table_base + table_off - msix->table_offset_adjust); > + > + if (msix->phys_iomem_base == MAP_FAILED) { > + rc = -errno; > + PT_ERR(d, "Can''t map physical MSI-X table: %s\n", strerror(errno)); > + close(fd); > + goto error_out; > + } > + msix->phys_iomem_base = (char *)msix->phys_iomem_base > + + msix->table_offset_adjust; > + > + close(fd); > + > + PT_LOG(d, "mapping physical MSI-X table to %p\n", msix->phys_iomem_base); > + > + memory_region_transaction_begin(); > + memory_region_add_subregion_overlap(&s->bar[bar_index], msix->table_off, > + &msix->mmio, > + 2); /* Priority: pci default + 1 */ > + memory_region_set_enabled(&msix->mmio, false); > + memory_region_transaction_commit(); > + > + return 0; > + > +error_out: > + memory_region_destroy(&msix->mmio); > + g_free(s->msix); > + s->msix = NULL; > + return rc; > +} > + > +void pt_msix_delete(XenPCIPassthroughState *s) > +{ > + XenPTMSIX *msix = s->msix; > + > + if (!msix) { > + return; > + } > + > + /* unmap the MSI-X memory mapped register area */ > + if (msix->phys_iomem_base) { > + PT_LOG(&s->dev, "unmapping physical MSI-X table from %p\n", > + msix->phys_iomem_base); > + munmap(msix->phys_iomem_base, msix->total_entries * PCI_MSIX_ENTRY_SIZE > + + msix->table_offset_adjust); > + } > + > + memory_region_del_subregion(&s->bar[msix->bar_index], &msix->mmio); > + memory_region_destroy(&msix->mmio); > + > + g_free(s->msix); > + s->msix = NULL; > +} > -- > Anthony PERARD > > > _______________________________________________ > Xen-devel mailing list > Xen-devel@lists.xensource.com > http://lists.xensource.com/xen-devel
Anthony PERARD
2012-Feb-14 13:46 UTC
Re: [Xen-devel] [PATCH V6 10/11] Introduce Xen PCI Passthrough, MSI (3/3)
On Tue, Feb 14, 2012 at 13:13, Jan Beulich <JBeulich@suse.com> wrote:>>>> On 13.02.12 at 13:20, Anthony PERARD <anthony.perard@citrix.com> wrote: >> From: Jiang Yunhong <yunhong.jiang@intel.com> >> >> A more complete history can be found here: >> git://xenbits.xensource.com/qemu-xen-unstable.git > > This needs to be updated to include the changes in > http://xenbits.xen.org/gitweb/?p=qemu-xen-unstable.git;a=commitdiff;h=bb36d632e4cabf47882adff07a45c6702c4a5b30 > (and hopefully the broken function that got fixed in > http://xenbits.xen.org/gitweb/?p=qemu-xen-unstable.git;a=commitdiff;h=8cc8a3651c9c5bc2d0086d12f4b870fc525b9387 > didn''t even make it into this patch set).For the first patch/link, the ''volatile'' variable should already be in the patch (without a separate patch :( ). For the second, yes, unregister_iomem is done by other functions in QEMU.> In particular it must be avoided > to map the MSI-X table with PROT_WRITE, and the respective MMIO > range should not get assigned to the guest at all.I will check that. Regards, -- Anthony PERARD
Jan Beulich
2012-Feb-14 14:00 UTC
Re: [Xen-devel] [PATCH V6 10/11] Introduce Xen PCI Passthrough, MSI (3/3)
>>> On 14.02.12 at 14:46, Anthony PERARD <anthony.perard@citrix.com> wrote: > On Tue, Feb 14, 2012 at 13:13, Jan Beulich <JBeulich@suse.com> wrote: >>>>> On 13.02.12 at 13:20, Anthony PERARD <anthony.perard@citrix.com> wrote: >>> From: Jiang Yunhong <yunhong.jiang@intel.com> >>> >>> A more complete history can be found here: >>> git://xenbits.xensource.com/qemu-xen-unstable.git >> >> This needs to be updated to include the changes in >> > http://xenbits.xen.org/gitweb/?p=qemu-xen-unstable.git;a=commitdiff;h=bb36d63 > 2e4cabf47882adff07a45c6702c4a5b30 >> (and hopefully the broken function that got fixed in >> > http://xenbits.xen.org/gitweb/?p=qemu-xen-unstable.git;a=commitdiff;h=8cc8a36 > 51c9c5bc2d0086d12f4b870fc525b9387 >> didn''t even make it into this patch set). > > For the first patch/link, the ''volatile'' variable should already be in > the patch (without a separate patch :( ).Good that you said "volatile" - that was the wrong link. The correct one is http://xenbits.xen.org/gitweb/?p=qemu-xen-unstable.git;a=commitdiff;h=56d7747a3cf811910c4cf865e1ebcb8b82502005 I''m sorry for that, Jan> For the second, yes, > unregister_iomem is done by other functions in QEMU. > >> In particular it must be avoided >> to map the MSI-X table with PROT_WRITE, and the respective MMIO >> range should not get assigned to the guest at all. > > I will check that. > > Regards, > > -- > Anthony PERARD