8719224166
We have to update the calculation of the stolen memory size because
we've seen devices using values of 0xf0 and above for the graphics mode
select field. The new calculation was taken from the linux kernel [1].
[1] 7c626ce4ba/arch/x86/kernel/early-quirks.c (L455-L460)
Signed-off-by: Corvin Köhne <c.koehne@beckhoff.com>
Reviewed-by: Alex Williamson <alex.williamson@redhat.com>
746 lines
25 KiB
C
746 lines
25 KiB
C
/*
|
|
* IGD device quirks
|
|
*
|
|
* Copyright Red Hat, Inc. 2016
|
|
*
|
|
* Authors:
|
|
* Alex Williamson <alex.williamson@redhat.com>
|
|
*
|
|
* This work is licensed under the terms of the GNU GPL, version 2. See
|
|
* the COPYING file in the top-level directory.
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
#include "qemu/units.h"
|
|
#include "qemu/error-report.h"
|
|
#include "qapi/error.h"
|
|
#include "hw/hw.h"
|
|
#include "hw/nvram/fw_cfg.h"
|
|
#include "pci.h"
|
|
#include "trace.h"
|
|
|
|
/*
|
|
* Intel IGD support
|
|
*
|
|
* Obviously IGD is not a discrete device, this is evidenced not only by it
|
|
* being integrated into the CPU, but by the various chipset and BIOS
|
|
* dependencies that it brings along with it. Intel is trying to move away
|
|
* from this and Broadwell and newer devices can run in what Intel calls
|
|
* "Universal Pass-Through" mode, or UPT. Theoretically in UPT mode, nothing
|
|
* more is required beyond assigning the IGD device to a VM. There are
|
|
* however support limitations to this mode. It only supports IGD as a
|
|
* secondary graphics device in the VM and it doesn't officially support any
|
|
* physical outputs.
|
|
*
|
|
* The code here attempts to enable what we'll call legacy mode assignment,
|
|
* IGD retains most of the capabilities we expect for it to have on bare
|
|
* metal. To enable this mode, the IGD device must be assigned to the VM
|
|
* at PCI address 00:02.0, it must have a ROM, it very likely needs VGA
|
|
* support, we must have VM BIOS support for reserving and populating some
|
|
* of the required tables, and we need to tweak the chipset with revisions
|
|
* and IDs and an LPC/ISA bridge device. The intention is to make all of
|
|
* this happen automatically by installing the device at the correct VM PCI
|
|
* bus address. If any of the conditions are not met, we cross our fingers
|
|
* and hope the user knows better.
|
|
*
|
|
* NB - It is possible to enable physical outputs in UPT mode by supplying
|
|
* an OpRegion table. We don't do this by default because the guest driver
|
|
* behaves differently if an OpRegion is provided and no monitor is attached
|
|
* vs no OpRegion and a monitor being attached or not. Effectively, if a
|
|
* headless setup is desired, the OpRegion gets in the way of that.
|
|
*/
|
|
|
|
/*
|
|
* This presumes the device is already known to be an Intel VGA device, so we
|
|
* take liberties in which device ID bits match which generation. This should
|
|
* not be taken as an indication that all the devices are supported, or even
|
|
* supportable, some of them don't even support VT-d.
|
|
* See linux:include/drm/i915_pciids.h for IDs.
|
|
*/
|
|
static int igd_gen(VFIOPCIDevice *vdev)
|
|
{
|
|
if ((vdev->device_id & 0xfff) == 0xa84) {
|
|
return 8; /* Broxton */
|
|
}
|
|
|
|
switch (vdev->device_id & 0xff00) {
|
|
/* Old, untested, unavailable, unknown */
|
|
case 0x0000:
|
|
case 0x2500:
|
|
case 0x2700:
|
|
case 0x2900:
|
|
case 0x2a00:
|
|
case 0x2e00:
|
|
case 0x3500:
|
|
case 0xa000:
|
|
return -1;
|
|
/* SandyBridge, IvyBridge, ValleyView, Haswell */
|
|
case 0x0100:
|
|
case 0x0400:
|
|
case 0x0a00:
|
|
case 0x0c00:
|
|
case 0x0d00:
|
|
case 0x0f00:
|
|
return 6;
|
|
/* BroadWell, CherryView, SkyLake, KabyLake */
|
|
case 0x1600:
|
|
case 0x1900:
|
|
case 0x2200:
|
|
case 0x5900:
|
|
return 8;
|
|
/* ElkhartLake */
|
|
case 0x4500:
|
|
return 11;
|
|
/* TigerLake */
|
|
case 0x9A00:
|
|
return 12;
|
|
}
|
|
|
|
/*
|
|
* Unfortunately, Intel changes it's specification quite often. This makes
|
|
* it impossible to use a suitable default value for unknown devices.
|
|
*/
|
|
return -1;
|
|
}
|
|
|
|
typedef struct VFIOIGDQuirk {
|
|
struct VFIOPCIDevice *vdev;
|
|
uint32_t index;
|
|
uint64_t bdsm;
|
|
} VFIOIGDQuirk;
|
|
|
|
#define IGD_GMCH 0x50 /* Graphics Control Register */
|
|
#define IGD_BDSM 0x5c /* Base Data of Stolen Memory */
|
|
#define IGD_BDSM_GEN11 0xc0 /* Base Data of Stolen Memory of gen 11 and later */
|
|
|
|
|
|
/*
|
|
* The rather short list of registers that we copy from the host devices.
|
|
* The LPC/ISA bridge values are definitely needed to support the vBIOS, the
|
|
* host bridge values may or may not be needed depending on the guest OS.
|
|
* Since we're only munging revision and subsystem values on the host bridge,
|
|
* we don't require our own device. The LPC/ISA bridge needs to be our very
|
|
* own though.
|
|
*/
|
|
typedef struct {
|
|
uint8_t offset;
|
|
uint8_t len;
|
|
} IGDHostInfo;
|
|
|
|
static const IGDHostInfo igd_host_bridge_infos[] = {
|
|
{PCI_REVISION_ID, 2},
|
|
{PCI_SUBSYSTEM_VENDOR_ID, 2},
|
|
{PCI_SUBSYSTEM_ID, 2},
|
|
};
|
|
|
|
static const IGDHostInfo igd_lpc_bridge_infos[] = {
|
|
{PCI_VENDOR_ID, 2},
|
|
{PCI_DEVICE_ID, 2},
|
|
{PCI_REVISION_ID, 2},
|
|
{PCI_SUBSYSTEM_VENDOR_ID, 2},
|
|
{PCI_SUBSYSTEM_ID, 2},
|
|
};
|
|
|
|
static int vfio_pci_igd_copy(VFIOPCIDevice *vdev, PCIDevice *pdev,
|
|
struct vfio_region_info *info,
|
|
const IGDHostInfo *list, int len)
|
|
{
|
|
int i, ret;
|
|
|
|
for (i = 0; i < len; i++) {
|
|
ret = pread(vdev->vbasedev.fd, pdev->config + list[i].offset,
|
|
list[i].len, info->offset + list[i].offset);
|
|
if (ret != list[i].len) {
|
|
error_report("IGD copy failed: %m");
|
|
return -errno;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Stuff a few values into the host bridge.
|
|
*/
|
|
static int vfio_pci_igd_host_init(VFIOPCIDevice *vdev,
|
|
struct vfio_region_info *info)
|
|
{
|
|
PCIBus *bus;
|
|
PCIDevice *host_bridge;
|
|
int ret;
|
|
|
|
bus = pci_device_root_bus(&vdev->pdev);
|
|
host_bridge = pci_find_device(bus, 0, PCI_DEVFN(0, 0));
|
|
|
|
if (!host_bridge) {
|
|
error_report("Can't find host bridge");
|
|
return -ENODEV;
|
|
}
|
|
|
|
ret = vfio_pci_igd_copy(vdev, host_bridge, info, igd_host_bridge_infos,
|
|
ARRAY_SIZE(igd_host_bridge_infos));
|
|
if (!ret) {
|
|
trace_vfio_pci_igd_host_bridge_enabled(vdev->vbasedev.name);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* IGD LPC/ISA bridge support code. The vBIOS needs this, but we can't write
|
|
* arbitrary values into just any bridge, so we must create our own. We try
|
|
* to handle if the user has created it for us, which they might want to do
|
|
* to enable multifunction so we don't occupy the whole PCI slot.
|
|
*/
|
|
static void vfio_pci_igd_lpc_bridge_realize(PCIDevice *pdev, Error **errp)
|
|
{
|
|
if (pdev->devfn != PCI_DEVFN(0x1f, 0)) {
|
|
error_setg(errp, "VFIO dummy ISA/LPC bridge must have address 1f.0");
|
|
}
|
|
}
|
|
|
|
static void vfio_pci_igd_lpc_bridge_class_init(ObjectClass *klass, void *data)
|
|
{
|
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
|
PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
|
|
|
|
set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories);
|
|
dc->desc = "VFIO dummy ISA/LPC bridge for IGD assignment";
|
|
dc->hotpluggable = false;
|
|
k->realize = vfio_pci_igd_lpc_bridge_realize;
|
|
k->class_id = PCI_CLASS_BRIDGE_ISA;
|
|
}
|
|
|
|
static const TypeInfo vfio_pci_igd_lpc_bridge_info = {
|
|
.name = "vfio-pci-igd-lpc-bridge",
|
|
.parent = TYPE_PCI_DEVICE,
|
|
.class_init = vfio_pci_igd_lpc_bridge_class_init,
|
|
.interfaces = (InterfaceInfo[]) {
|
|
{ INTERFACE_CONVENTIONAL_PCI_DEVICE },
|
|
{ },
|
|
},
|
|
};
|
|
|
|
static void vfio_pci_igd_register_types(void)
|
|
{
|
|
type_register_static(&vfio_pci_igd_lpc_bridge_info);
|
|
}
|
|
|
|
type_init(vfio_pci_igd_register_types)
|
|
|
|
static int vfio_pci_igd_lpc_init(VFIOPCIDevice *vdev,
|
|
struct vfio_region_info *info)
|
|
{
|
|
PCIDevice *lpc_bridge;
|
|
int ret;
|
|
|
|
lpc_bridge = pci_find_device(pci_device_root_bus(&vdev->pdev),
|
|
0, PCI_DEVFN(0x1f, 0));
|
|
if (!lpc_bridge) {
|
|
lpc_bridge = pci_create_simple(pci_device_root_bus(&vdev->pdev),
|
|
PCI_DEVFN(0x1f, 0), "vfio-pci-igd-lpc-bridge");
|
|
}
|
|
|
|
ret = vfio_pci_igd_copy(vdev, lpc_bridge, info, igd_lpc_bridge_infos,
|
|
ARRAY_SIZE(igd_lpc_bridge_infos));
|
|
if (!ret) {
|
|
trace_vfio_pci_igd_lpc_bridge_enabled(vdev->vbasedev.name);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* IGD Gen8 and newer support up to 8MB for the GTT and use a 64bit PTE
|
|
* entry, older IGDs use 2MB and 32bit. Each PTE maps a 4k page. Therefore
|
|
* we either have 2M/4k * 4 = 2k or 8M/4k * 8 = 16k as the maximum iobar index
|
|
* for programming the GTT.
|
|
*
|
|
* See linux:include/drm/i915_drm.h for shift and mask values.
|
|
*/
|
|
static int vfio_igd_gtt_max(VFIOPCIDevice *vdev)
|
|
{
|
|
uint32_t gmch = vfio_pci_read_config(&vdev->pdev, IGD_GMCH, sizeof(gmch));
|
|
int ggms, gen = igd_gen(vdev);
|
|
|
|
gmch = vfio_pci_read_config(&vdev->pdev, IGD_GMCH, sizeof(gmch));
|
|
ggms = (gmch >> (gen < 8 ? 8 : 6)) & 0x3;
|
|
if (gen > 6) {
|
|
ggms = 1 << ggms;
|
|
}
|
|
|
|
ggms *= MiB;
|
|
|
|
return (ggms / (4 * KiB)) * (gen < 8 ? 4 : 8);
|
|
}
|
|
|
|
/*
|
|
* The IGD ROM will make use of stolen memory (GGMS) for support of VESA modes.
|
|
* Somehow the host stolen memory range is used for this, but how the ROM gets
|
|
* it is a mystery, perhaps it's hardcoded into the ROM. Thankfully though, it
|
|
* reprograms the GTT through the IOBAR where we can trap it and transpose the
|
|
* programming to the VM allocated buffer. That buffer gets reserved by the VM
|
|
* firmware via the fw_cfg entry added below. Here we're just monitoring the
|
|
* IOBAR address and data registers to detect a write sequence targeting the
|
|
* GTTADR. This code is developed by observed behavior and doesn't have a
|
|
* direct spec reference, unfortunately.
|
|
*/
|
|
static uint64_t vfio_igd_quirk_data_read(void *opaque,
|
|
hwaddr addr, unsigned size)
|
|
{
|
|
VFIOIGDQuirk *igd = opaque;
|
|
VFIOPCIDevice *vdev = igd->vdev;
|
|
|
|
igd->index = ~0;
|
|
|
|
return vfio_region_read(&vdev->bars[4].region, addr + 4, size);
|
|
}
|
|
|
|
static void vfio_igd_quirk_data_write(void *opaque, hwaddr addr,
|
|
uint64_t data, unsigned size)
|
|
{
|
|
VFIOIGDQuirk *igd = opaque;
|
|
VFIOPCIDevice *vdev = igd->vdev;
|
|
uint64_t val = data;
|
|
int gen = igd_gen(vdev);
|
|
|
|
/*
|
|
* Programming the GGMS starts at index 0x1 and uses every 4th index (ie.
|
|
* 0x1, 0x5, 0x9, 0xd,...). For pre-Gen8 each 4-byte write is a whole PTE
|
|
* entry, with 0th bit enable set. For Gen8 and up, PTEs are 64bit, so
|
|
* entries 0x5 & 0xd are the high dword, in our case zero. Each PTE points
|
|
* to a 4k page, which we translate to a page from the VM allocated region,
|
|
* pointed to by the BDSM register. If this is not set, we fail.
|
|
*
|
|
* We trap writes to the full configured GTT size, but we typically only
|
|
* see the vBIOS writing up to (nearly) the 1MB barrier. In fact it often
|
|
* seems to miss the last entry for an even 1MB GTT. Doing a gratuitous
|
|
* write of that last entry does work, but is hopefully unnecessary since
|
|
* we clear the previous GTT on initialization.
|
|
*/
|
|
if ((igd->index % 4 == 1) && igd->index < vfio_igd_gtt_max(vdev)) {
|
|
if (gen < 8 || (igd->index % 8 == 1)) {
|
|
uint64_t base;
|
|
|
|
if (gen < 11) {
|
|
base = pci_get_long(vdev->pdev.config + IGD_BDSM);
|
|
} else {
|
|
base = pci_get_quad(vdev->pdev.config + IGD_BDSM_GEN11);
|
|
}
|
|
if (!base) {
|
|
hw_error("vfio-igd: Guest attempted to program IGD GTT before "
|
|
"BIOS reserved stolen memory. Unsupported BIOS?");
|
|
}
|
|
|
|
val = data - igd->bdsm + base;
|
|
} else {
|
|
val = 0; /* upper 32bits of pte, we only enable below 4G PTEs */
|
|
}
|
|
|
|
trace_vfio_pci_igd_bar4_write(vdev->vbasedev.name,
|
|
igd->index, data, val);
|
|
}
|
|
|
|
vfio_region_write(&vdev->bars[4].region, addr + 4, val, size);
|
|
|
|
igd->index = ~0;
|
|
}
|
|
|
|
static const MemoryRegionOps vfio_igd_data_quirk = {
|
|
.read = vfio_igd_quirk_data_read,
|
|
.write = vfio_igd_quirk_data_write,
|
|
.endianness = DEVICE_LITTLE_ENDIAN,
|
|
};
|
|
|
|
static uint64_t vfio_igd_quirk_index_read(void *opaque,
|
|
hwaddr addr, unsigned size)
|
|
{
|
|
VFIOIGDQuirk *igd = opaque;
|
|
VFIOPCIDevice *vdev = igd->vdev;
|
|
|
|
igd->index = ~0;
|
|
|
|
return vfio_region_read(&vdev->bars[4].region, addr, size);
|
|
}
|
|
|
|
static void vfio_igd_quirk_index_write(void *opaque, hwaddr addr,
|
|
uint64_t data, unsigned size)
|
|
{
|
|
VFIOIGDQuirk *igd = opaque;
|
|
VFIOPCIDevice *vdev = igd->vdev;
|
|
|
|
igd->index = data;
|
|
|
|
vfio_region_write(&vdev->bars[4].region, addr, data, size);
|
|
}
|
|
|
|
static const MemoryRegionOps vfio_igd_index_quirk = {
|
|
.read = vfio_igd_quirk_index_read,
|
|
.write = vfio_igd_quirk_index_write,
|
|
.endianness = DEVICE_LITTLE_ENDIAN,
|
|
};
|
|
|
|
#define IGD_BDSM_MMIO_OFFSET 0x1080C0
|
|
|
|
static uint64_t vfio_igd_quirk_bdsm_read(void *opaque,
|
|
hwaddr addr, unsigned size)
|
|
{
|
|
VFIOPCIDevice *vdev = opaque;
|
|
uint64_t offset;
|
|
|
|
offset = IGD_BDSM_GEN11 + addr;
|
|
|
|
switch (size) {
|
|
case 1:
|
|
return pci_get_byte(vdev->pdev.config + offset);
|
|
case 2:
|
|
return pci_get_word(vdev->pdev.config + offset);
|
|
case 4:
|
|
return pci_get_long(vdev->pdev.config + offset);
|
|
case 8:
|
|
return pci_get_quad(vdev->pdev.config + offset);
|
|
default:
|
|
hw_error("igd: unsupported read size, %u bytes", size);
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void vfio_igd_quirk_bdsm_write(void *opaque, hwaddr addr,
|
|
uint64_t data, unsigned size)
|
|
{
|
|
VFIOPCIDevice *vdev = opaque;
|
|
uint64_t offset;
|
|
|
|
offset = IGD_BDSM_GEN11 + addr;
|
|
|
|
switch (size) {
|
|
case 1:
|
|
pci_set_byte(vdev->pdev.config + offset, data);
|
|
break;
|
|
case 2:
|
|
pci_set_word(vdev->pdev.config + offset, data);
|
|
break;
|
|
case 4:
|
|
pci_set_long(vdev->pdev.config + offset, data);
|
|
break;
|
|
case 8:
|
|
pci_set_quad(vdev->pdev.config + offset, data);
|
|
break;
|
|
default:
|
|
hw_error("igd: unsupported read size, %u bytes", size);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static const MemoryRegionOps vfio_igd_bdsm_quirk = {
|
|
.read = vfio_igd_quirk_bdsm_read,
|
|
.write = vfio_igd_quirk_bdsm_write,
|
|
.endianness = DEVICE_LITTLE_ENDIAN,
|
|
};
|
|
|
|
void vfio_probe_igd_bar0_quirk(VFIOPCIDevice *vdev, int nr)
|
|
{
|
|
VFIOQuirk *quirk;
|
|
int gen;
|
|
|
|
/*
|
|
* This must be an Intel VGA device at address 00:02.0 for us to even
|
|
* consider enabling legacy mode. Some driver have dependencies on the PCI
|
|
* bus address.
|
|
*/
|
|
if (!vfio_pci_is(vdev, PCI_VENDOR_ID_INTEL, PCI_ANY_ID) ||
|
|
!vfio_is_vga(vdev) || nr != 0 ||
|
|
&vdev->pdev != pci_find_device(pci_device_root_bus(&vdev->pdev),
|
|
0, PCI_DEVFN(0x2, 0))) {
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Only on IGD devices of gen 11 and above, the BDSM register is mirrored
|
|
* into MMIO space and read from MMIO space by the Windows driver.
|
|
*/
|
|
gen = igd_gen(vdev);
|
|
if (gen < 11) {
|
|
return;
|
|
}
|
|
|
|
quirk = vfio_quirk_alloc(1);
|
|
quirk->data = vdev;
|
|
|
|
memory_region_init_io(&quirk->mem[0], OBJECT(vdev), &vfio_igd_bdsm_quirk,
|
|
vdev, "vfio-igd-bdsm-quirk", 8);
|
|
memory_region_add_subregion_overlap(vdev->bars[0].region.mem,
|
|
IGD_BDSM_MMIO_OFFSET, &quirk->mem[0],
|
|
1);
|
|
|
|
QLIST_INSERT_HEAD(&vdev->bars[nr].quirks, quirk, next);
|
|
}
|
|
|
|
static int igd_get_stolen_mb(int gen, uint32_t gmch)
|
|
{
|
|
int gms;
|
|
|
|
if (gen < 8) {
|
|
gms = (gmch >> 3) & 0x1f;
|
|
} else {
|
|
gms = (gmch >> 8) & 0xff;
|
|
}
|
|
|
|
if (gen < 9) {
|
|
if (gms > 0x10) {
|
|
error_report("Unsupported IGD GMS value 0x%x", gms);
|
|
return 0;
|
|
}
|
|
return gms * 32;
|
|
} else {
|
|
if (gms < 0xf0)
|
|
return gms * 32;
|
|
else
|
|
return gms * 4 + 4;
|
|
}
|
|
}
|
|
|
|
void vfio_probe_igd_bar4_quirk(VFIOPCIDevice *vdev, int nr)
|
|
{
|
|
g_autofree struct vfio_region_info *rom = NULL;
|
|
g_autofree struct vfio_region_info *opregion = NULL;
|
|
g_autofree struct vfio_region_info *host = NULL;
|
|
g_autofree struct vfio_region_info *lpc = NULL;
|
|
VFIOQuirk *quirk;
|
|
VFIOIGDQuirk *igd;
|
|
PCIDevice *lpc_bridge;
|
|
int i, ret, ggms_mb, gms_mb = 0, gen;
|
|
uint64_t *bdsm_size;
|
|
uint32_t gmch;
|
|
uint16_t cmd_orig, cmd;
|
|
Error *err = NULL;
|
|
|
|
/*
|
|
* This must be an Intel VGA device at address 00:02.0 for us to even
|
|
* consider enabling legacy mode. The vBIOS has dependencies on the
|
|
* PCI bus address.
|
|
*/
|
|
if (!vfio_pci_is(vdev, PCI_VENDOR_ID_INTEL, PCI_ANY_ID) ||
|
|
!vfio_is_vga(vdev) || nr != 4 ||
|
|
&vdev->pdev != pci_find_device(pci_device_root_bus(&vdev->pdev),
|
|
0, PCI_DEVFN(0x2, 0))) {
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* We need to create an LPC/ISA bridge at PCI bus address 00:1f.0 that we
|
|
* can stuff host values into, so if there's already one there and it's not
|
|
* one we can hack on, legacy mode is no-go. Sorry Q35.
|
|
*/
|
|
lpc_bridge = pci_find_device(pci_device_root_bus(&vdev->pdev),
|
|
0, PCI_DEVFN(0x1f, 0));
|
|
if (lpc_bridge && !object_dynamic_cast(OBJECT(lpc_bridge),
|
|
"vfio-pci-igd-lpc-bridge")) {
|
|
error_report("IGD device %s cannot support legacy mode due to existing "
|
|
"devices at address 1f.0", vdev->vbasedev.name);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* IGD is not a standard, they like to change their specs often. We
|
|
* only attempt to support back to SandBridge and we hope that newer
|
|
* devices maintain compatibility with generation 8.
|
|
*/
|
|
gen = igd_gen(vdev);
|
|
if (gen == -1) {
|
|
error_report("IGD device %s is unsupported in legacy mode, "
|
|
"try SandyBridge or newer", vdev->vbasedev.name);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Most of what we're doing here is to enable the ROM to run, so if
|
|
* there's no ROM, there's no point in setting up this quirk.
|
|
* NB. We only seem to get BIOS ROMs, so a UEFI VM would need CSM support.
|
|
*/
|
|
ret = vfio_get_region_info(&vdev->vbasedev,
|
|
VFIO_PCI_ROM_REGION_INDEX, &rom);
|
|
if ((ret || !rom->size) && !vdev->pdev.romfile) {
|
|
error_report("IGD device %s has no ROM, legacy mode disabled",
|
|
vdev->vbasedev.name);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Ignore the hotplug corner case, mark the ROM failed, we can't
|
|
* create the devices we need for legacy mode in the hotplug scenario.
|
|
*/
|
|
if (vdev->pdev.qdev.hotplugged) {
|
|
error_report("IGD device %s hotplugged, ROM disabled, "
|
|
"legacy mode disabled", vdev->vbasedev.name);
|
|
vdev->rom_read_failed = true;
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Check whether we have all the vfio device specific regions to
|
|
* support legacy mode (added in Linux v4.6). If not, bail.
|
|
*/
|
|
ret = vfio_get_dev_region_info(&vdev->vbasedev,
|
|
VFIO_REGION_TYPE_PCI_VENDOR_TYPE | PCI_VENDOR_ID_INTEL,
|
|
VFIO_REGION_SUBTYPE_INTEL_IGD_OPREGION, &opregion);
|
|
if (ret) {
|
|
error_report("IGD device %s does not support OpRegion access,"
|
|
"legacy mode disabled", vdev->vbasedev.name);
|
|
return;
|
|
}
|
|
|
|
ret = vfio_get_dev_region_info(&vdev->vbasedev,
|
|
VFIO_REGION_TYPE_PCI_VENDOR_TYPE | PCI_VENDOR_ID_INTEL,
|
|
VFIO_REGION_SUBTYPE_INTEL_IGD_HOST_CFG, &host);
|
|
if (ret) {
|
|
error_report("IGD device %s does not support host bridge access,"
|
|
"legacy mode disabled", vdev->vbasedev.name);
|
|
return;
|
|
}
|
|
|
|
ret = vfio_get_dev_region_info(&vdev->vbasedev,
|
|
VFIO_REGION_TYPE_PCI_VENDOR_TYPE | PCI_VENDOR_ID_INTEL,
|
|
VFIO_REGION_SUBTYPE_INTEL_IGD_LPC_CFG, &lpc);
|
|
if (ret) {
|
|
error_report("IGD device %s does not support LPC bridge access,"
|
|
"legacy mode disabled", vdev->vbasedev.name);
|
|
return;
|
|
}
|
|
|
|
gmch = vfio_pci_read_config(&vdev->pdev, IGD_GMCH, 4);
|
|
|
|
/*
|
|
* If IGD VGA Disable is clear (expected) and VGA is not already enabled,
|
|
* try to enable it. Probably shouldn't be using legacy mode without VGA,
|
|
* but also no point in us enabling VGA if disabled in hardware.
|
|
*/
|
|
if (!(gmch & 0x2) && !vdev->vga && !vfio_populate_vga(vdev, &err)) {
|
|
error_reportf_err(err, VFIO_MSG_PREFIX, vdev->vbasedev.name);
|
|
error_report("IGD device %s failed to enable VGA access, "
|
|
"legacy mode disabled", vdev->vbasedev.name);
|
|
return;
|
|
}
|
|
|
|
/* Create our LPC/ISA bridge */
|
|
ret = vfio_pci_igd_lpc_init(vdev, lpc);
|
|
if (ret) {
|
|
error_report("IGD device %s failed to create LPC bridge, "
|
|
"legacy mode disabled", vdev->vbasedev.name);
|
|
return;
|
|
}
|
|
|
|
/* Stuff some host values into the VM PCI host bridge */
|
|
ret = vfio_pci_igd_host_init(vdev, host);
|
|
if (ret) {
|
|
error_report("IGD device %s failed to modify host bridge, "
|
|
"legacy mode disabled", vdev->vbasedev.name);
|
|
return;
|
|
}
|
|
|
|
/* Setup OpRegion access */
|
|
if (!vfio_pci_igd_opregion_init(vdev, opregion, &err)) {
|
|
error_append_hint(&err, "IGD legacy mode disabled\n");
|
|
error_reportf_err(err, VFIO_MSG_PREFIX, vdev->vbasedev.name);
|
|
return;
|
|
}
|
|
|
|
/* Setup our quirk to munge GTT addresses to the VM allocated buffer */
|
|
quirk = vfio_quirk_alloc(2);
|
|
igd = quirk->data = g_malloc0(sizeof(*igd));
|
|
igd->vdev = vdev;
|
|
igd->index = ~0;
|
|
if (gen < 11) {
|
|
igd->bdsm = vfio_pci_read_config(&vdev->pdev, IGD_BDSM, 4);
|
|
} else {
|
|
igd->bdsm = vfio_pci_read_config(&vdev->pdev, IGD_BDSM_GEN11, 4);
|
|
igd->bdsm |=
|
|
(uint64_t)vfio_pci_read_config(&vdev->pdev, IGD_BDSM_GEN11 + 4, 4) << 32;
|
|
}
|
|
igd->bdsm &= ~((1 * MiB) - 1); /* 1MB aligned */
|
|
|
|
memory_region_init_io(&quirk->mem[0], OBJECT(vdev), &vfio_igd_index_quirk,
|
|
igd, "vfio-igd-index-quirk", 4);
|
|
memory_region_add_subregion_overlap(vdev->bars[nr].region.mem,
|
|
0, &quirk->mem[0], 1);
|
|
|
|
memory_region_init_io(&quirk->mem[1], OBJECT(vdev), &vfio_igd_data_quirk,
|
|
igd, "vfio-igd-data-quirk", 4);
|
|
memory_region_add_subregion_overlap(vdev->bars[nr].region.mem,
|
|
4, &quirk->mem[1], 1);
|
|
|
|
QLIST_INSERT_HEAD(&vdev->bars[nr].quirks, quirk, next);
|
|
|
|
/* Determine the size of stolen memory needed for GTT */
|
|
ggms_mb = (gmch >> (gen < 8 ? 8 : 6)) & 0x3;
|
|
if (gen > 6) {
|
|
ggms_mb = 1 << ggms_mb;
|
|
}
|
|
|
|
gms_mb = igd_get_stolen_mb(gen, gmch);
|
|
|
|
/*
|
|
* Request reserved memory for stolen memory via fw_cfg. VM firmware
|
|
* must allocate a 1MB aligned reserved memory region below 4GB with
|
|
* the requested size (in bytes) for use by the Intel PCI class VGA
|
|
* device at VM address 00:02.0. The base address of this reserved
|
|
* memory region must be written to the device BDSM register at PCI
|
|
* config offset 0x5C.
|
|
*/
|
|
bdsm_size = g_malloc(sizeof(*bdsm_size));
|
|
*bdsm_size = cpu_to_le64((ggms_mb + gms_mb) * MiB);
|
|
fw_cfg_add_file(fw_cfg_find(), "etc/igd-bdsm-size",
|
|
bdsm_size, sizeof(*bdsm_size));
|
|
|
|
/* GMCH is read-only, emulated */
|
|
pci_set_long(vdev->pdev.config + IGD_GMCH, gmch);
|
|
pci_set_long(vdev->pdev.wmask + IGD_GMCH, 0);
|
|
pci_set_long(vdev->emulated_config_bits + IGD_GMCH, ~0);
|
|
|
|
/* BDSM is read-write, emulated. The BIOS needs to be able to write it */
|
|
if (gen < 11) {
|
|
pci_set_long(vdev->pdev.config + IGD_BDSM, 0);
|
|
pci_set_long(vdev->pdev.wmask + IGD_BDSM, ~0);
|
|
pci_set_long(vdev->emulated_config_bits + IGD_BDSM, ~0);
|
|
} else {
|
|
pci_set_quad(vdev->pdev.config + IGD_BDSM_GEN11, 0);
|
|
pci_set_quad(vdev->pdev.wmask + IGD_BDSM_GEN11, ~0);
|
|
pci_set_quad(vdev->emulated_config_bits + IGD_BDSM_GEN11, ~0);
|
|
}
|
|
|
|
/*
|
|
* This IOBAR gives us access to GTTADR, which allows us to write to
|
|
* the GTT itself. So let's go ahead and write zero to all the GTT
|
|
* entries to avoid spurious DMA faults. Be sure I/O access is enabled
|
|
* before talking to the device.
|
|
*/
|
|
if (pread(vdev->vbasedev.fd, &cmd_orig, sizeof(cmd_orig),
|
|
vdev->config_offset + PCI_COMMAND) != sizeof(cmd_orig)) {
|
|
error_report("IGD device %s - failed to read PCI command register",
|
|
vdev->vbasedev.name);
|
|
}
|
|
|
|
cmd = cmd_orig | PCI_COMMAND_IO;
|
|
|
|
if (pwrite(vdev->vbasedev.fd, &cmd, sizeof(cmd),
|
|
vdev->config_offset + PCI_COMMAND) != sizeof(cmd)) {
|
|
error_report("IGD device %s - failed to write PCI command register",
|
|
vdev->vbasedev.name);
|
|
}
|
|
|
|
for (i = 1; i < vfio_igd_gtt_max(vdev); i += 4) {
|
|
vfio_region_write(&vdev->bars[4].region, 0, i, 4);
|
|
vfio_region_write(&vdev->bars[4].region, 4, 0, 4);
|
|
}
|
|
|
|
if (pwrite(vdev->vbasedev.fd, &cmd_orig, sizeof(cmd_orig),
|
|
vdev->config_offset + PCI_COMMAND) != sizeof(cmd_orig)) {
|
|
error_report("IGD device %s - failed to restore PCI command register",
|
|
vdev->vbasedev.name);
|
|
}
|
|
|
|
trace_vfio_pci_igd_bdsm_enabled(vdev->vbasedev.name, ggms_mb + gms_mb);
|
|
}
|