| /* |
| * graphics passthrough |
| */ |
| #include "qemu/osdep.h" |
| #include "qapi/error.h" |
| #include "xen_pt.h" |
| #include "xen-host-pci-device.h" |
| #include "hw/xen/xen-legacy-backend.h" |
| |
| static unsigned long igd_guest_opregion; |
| static unsigned long igd_host_opregion; |
| |
| #define XEN_PCI_INTEL_OPREGION_MASK 0xfff |
| |
| typedef struct VGARegion { |
| int type; /* Memory or port I/O */ |
| uint64_t guest_base_addr; |
| uint64_t machine_base_addr; |
| uint64_t size; /* size of the region */ |
| int rc; |
| } VGARegion; |
| |
| #define IORESOURCE_IO 0x00000100 |
| #define IORESOURCE_MEM 0x00000200 |
| |
| static struct VGARegion vga_args[] = { |
| { |
| .type = IORESOURCE_IO, |
| .guest_base_addr = 0x3B0, |
| .machine_base_addr = 0x3B0, |
| .size = 0xC, |
| .rc = -1, |
| }, |
| { |
| .type = IORESOURCE_IO, |
| .guest_base_addr = 0x3C0, |
| .machine_base_addr = 0x3C0, |
| .size = 0x20, |
| .rc = -1, |
| }, |
| { |
| .type = IORESOURCE_MEM, |
| .guest_base_addr = 0xa0000 >> XC_PAGE_SHIFT, |
| .machine_base_addr = 0xa0000 >> XC_PAGE_SHIFT, |
| .size = 0x20, |
| .rc = -1, |
| }, |
| }; |
| |
| /* |
| * register VGA resources for the domain with assigned gfx |
| */ |
| int xen_pt_register_vga_regions(XenHostPCIDevice *dev) |
| { |
| int i = 0; |
| |
| if (!is_igd_vga_passthrough(dev)) { |
| return 0; |
| } |
| |
| for (i = 0 ; i < ARRAY_SIZE(vga_args); i++) { |
| if (vga_args[i].type == IORESOURCE_IO) { |
| vga_args[i].rc = xc_domain_ioport_mapping(xen_xc, xen_domid, |
| vga_args[i].guest_base_addr, |
| vga_args[i].machine_base_addr, |
| vga_args[i].size, DPCI_ADD_MAPPING); |
| } else { |
| vga_args[i].rc = xc_domain_memory_mapping(xen_xc, xen_domid, |
| vga_args[i].guest_base_addr, |
| vga_args[i].machine_base_addr, |
| vga_args[i].size, DPCI_ADD_MAPPING); |
| } |
| |
| if (vga_args[i].rc) { |
| XEN_PT_ERR(NULL, "VGA %s mapping failed! (rc: %i)\n", |
| vga_args[i].type == IORESOURCE_IO ? "ioport" : "memory", |
| vga_args[i].rc); |
| return vga_args[i].rc; |
| } |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * unregister VGA resources for the domain with assigned gfx |
| */ |
| int xen_pt_unregister_vga_regions(XenHostPCIDevice *dev) |
| { |
| int i = 0; |
| int ret = 0; |
| |
| if (!is_igd_vga_passthrough(dev)) { |
| return 0; |
| } |
| |
| for (i = 0 ; i < ARRAY_SIZE(vga_args); i++) { |
| if (vga_args[i].type == IORESOURCE_IO) { |
| vga_args[i].rc = xc_domain_ioport_mapping(xen_xc, xen_domid, |
| vga_args[i].guest_base_addr, |
| vga_args[i].machine_base_addr, |
| vga_args[i].size, DPCI_REMOVE_MAPPING); |
| } else { |
| vga_args[i].rc = xc_domain_memory_mapping(xen_xc, xen_domid, |
| vga_args[i].guest_base_addr, |
| vga_args[i].machine_base_addr, |
| vga_args[i].size, DPCI_REMOVE_MAPPING); |
| } |
| |
| if (vga_args[i].rc) { |
| XEN_PT_ERR(NULL, "VGA %s unmapping failed! (rc: %i)\n", |
| vga_args[i].type == IORESOURCE_IO ? "ioport" : "memory", |
| vga_args[i].rc); |
| return vga_args[i].rc; |
| } |
| } |
| |
| if (igd_guest_opregion) { |
| ret = xc_domain_memory_mapping(xen_xc, xen_domid, |
| (unsigned long)(igd_guest_opregion >> XC_PAGE_SHIFT), |
| (unsigned long)(igd_host_opregion >> XC_PAGE_SHIFT), |
| 3, |
| DPCI_REMOVE_MAPPING); |
| if (ret) { |
| return ret; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static void *get_vgabios(XenPCIPassthroughState *s, int *size, |
| XenHostPCIDevice *dev) |
| { |
| return pci_assign_dev_load_option_rom(&s->dev, size, |
| dev->domain, dev->bus, |
| dev->dev, dev->func); |
| } |
| |
| /* Refer to Seabios. */ |
| struct rom_header { |
| uint16_t signature; |
| uint8_t size; |
| uint8_t initVector[4]; |
| uint8_t reserved[17]; |
| uint16_t pcioffset; |
| uint16_t pnpoffset; |
| } __attribute__((packed)); |
| |
| struct pci_data { |
| uint32_t signature; |
| uint16_t vendor; |
| uint16_t device; |
| uint16_t vitaldata; |
| uint16_t dlen; |
| uint8_t drevision; |
| uint8_t class_lo; |
| uint16_t class_hi; |
| uint16_t ilen; |
| uint16_t irevision; |
| uint8_t type; |
| uint8_t indicator; |
| uint16_t reserved; |
| } __attribute__((packed)); |
| |
| void xen_pt_setup_vga(XenPCIPassthroughState *s, XenHostPCIDevice *dev, |
| Error **errp) |
| { |
| unsigned char *bios = NULL; |
| struct rom_header *rom; |
| int bios_size; |
| char *c = NULL; |
| char checksum = 0; |
| uint32_t len = 0; |
| struct pci_data *pd = NULL; |
| |
| if (!is_igd_vga_passthrough(dev)) { |
| error_setg(errp, "Need to enable igd-passthrough"); |
| return; |
| } |
| |
| bios = get_vgabios(s, &bios_size, dev); |
| if (!bios) { |
| error_setg(errp, "VGA: Can't get VBIOS"); |
| return; |
| } |
| |
| if (bios_size < sizeof(struct rom_header)) { |
| error_setg(errp, "VGA: VBIOS image corrupt (too small)"); |
| return; |
| } |
| |
| /* Currently we fixed this address as a primary. */ |
| rom = (struct rom_header *)bios; |
| |
| if (rom->pcioffset + sizeof(struct pci_data) > bios_size) { |
| error_setg(errp, "VGA: VBIOS image corrupt (bad pcioffset field)"); |
| return; |
| } |
| |
| pd = (void *)(bios + (unsigned char)rom->pcioffset); |
| |
| /* We may need to fixup Device Identification. */ |
| if (pd->device != s->real_device.device_id) { |
| pd->device = s->real_device.device_id; |
| |
| len = rom->size * 512; |
| if (len > bios_size) { |
| error_setg(errp, "VGA: VBIOS image corrupt (bad size field)"); |
| return; |
| } |
| |
| /* Then adjust the bios checksum */ |
| for (c = (char *)bios; c < ((char *)bios + len); c++) { |
| checksum += *c; |
| } |
| if (checksum) { |
| bios[len - 1] -= checksum; |
| XEN_PT_LOG(&s->dev, "vga bios checksum is adjusted %x!\n", |
| checksum); |
| } |
| } |
| |
| /* Currently we fixed this address as a primary for legacy BIOS. */ |
| cpu_physical_memory_rw(0xc0000, bios, bios_size, 1); |
| } |
| |
| uint32_t igd_read_opregion(XenPCIPassthroughState *s) |
| { |
| uint32_t val = 0; |
| |
| if (!igd_guest_opregion) { |
| return val; |
| } |
| |
| val = igd_guest_opregion; |
| |
| XEN_PT_LOG(&s->dev, "Read opregion val=%x\n", val); |
| return val; |
| } |
| |
| #define XEN_PCI_INTEL_OPREGION_PAGES 0x3 |
| #define XEN_PCI_INTEL_OPREGION_ENABLE_ACCESSED 0x1 |
| void igd_write_opregion(XenPCIPassthroughState *s, uint32_t val) |
| { |
| int ret; |
| |
| if (igd_guest_opregion) { |
| XEN_PT_LOG(&s->dev, "opregion register already been set, ignoring %x\n", |
| val); |
| return; |
| } |
| |
| /* We just work with LE. */ |
| xen_host_pci_get_block(&s->real_device, XEN_PCI_INTEL_OPREGION, |
| (uint8_t *)&igd_host_opregion, 4); |
| igd_guest_opregion = (unsigned long)(val & ~XEN_PCI_INTEL_OPREGION_MASK) |
| | (igd_host_opregion & XEN_PCI_INTEL_OPREGION_MASK); |
| |
| ret = xc_domain_iomem_permission(xen_xc, xen_domid, |
| (unsigned long)(igd_host_opregion >> XC_PAGE_SHIFT), |
| XEN_PCI_INTEL_OPREGION_PAGES, |
| XEN_PCI_INTEL_OPREGION_ENABLE_ACCESSED); |
| |
| if (ret) { |
| XEN_PT_ERR(&s->dev, "[%d]:Can't enable to access IGD host opregion:" |
| " 0x%lx.\n", ret, |
| (unsigned long)(igd_host_opregion >> XC_PAGE_SHIFT)), |
| igd_guest_opregion = 0; |
| return; |
| } |
| |
| ret = xc_domain_memory_mapping(xen_xc, xen_domid, |
| (unsigned long)(igd_guest_opregion >> XC_PAGE_SHIFT), |
| (unsigned long)(igd_host_opregion >> XC_PAGE_SHIFT), |
| XEN_PCI_INTEL_OPREGION_PAGES, |
| DPCI_ADD_MAPPING); |
| |
| if (ret) { |
| XEN_PT_ERR(&s->dev, "[%d]:Can't map IGD host opregion:0x%lx to" |
| " guest opregion:0x%lx.\n", ret, |
| (unsigned long)(igd_host_opregion >> XC_PAGE_SHIFT), |
| (unsigned long)(igd_guest_opregion >> XC_PAGE_SHIFT)); |
| igd_guest_opregion = 0; |
| return; |
| } |
| |
| XEN_PT_LOG(&s->dev, "Map OpRegion: 0x%lx -> 0x%lx\n", |
| (unsigned long)(igd_host_opregion >> XC_PAGE_SHIFT), |
| (unsigned long)(igd_guest_opregion >> XC_PAGE_SHIFT)); |
| } |