Changeset View
Changeset View
Standalone View
Standalone View
usr.sbin/bhyve/pci_gvt-d.c
- This file was added.
/*- | |||||
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD | |||||
* | |||||
* Copyright (c) 2020 Beckhoff Automation GmbH & Co. KG | |||||
* All rights reserved. | |||||
* | |||||
* Redistribution and use in source and binary forms, with or without | |||||
* modification, are permitted provided that the following conditions | |||||
* are met: | |||||
* 1. Redistributions of source code must retain the above copyright | |||||
* notice, this list of conditions and the following disclaimer. | |||||
* 2. Redistributions in binary form must reproduce the above copyright | |||||
* notice, this list of conditions and the following disclaimer in the | |||||
* documentation and/or other materials provided with the distribution. | |||||
* | |||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR OR CONTRIBUTORS ``AS IS'' AND | |||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE | |||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |||||
* SUCH DAMAGE. | |||||
* | |||||
* $FreeBSD$ | |||||
*/ | |||||
#include <sys/cdefs.h> | |||||
__FBSDID("$FreeBSD$"); | |||||
#include <sys/param.h> | |||||
#include <sys/types.h> | |||||
#include <dev/pci/pcireg.h> | |||||
#include <stdio.h> | |||||
#include <stdlib.h> | |||||
#include <string.h> | |||||
#include <err.h> | |||||
#include <errno.h> | |||||
#include <fcntl.h> | |||||
#include <sysexits.h> | |||||
#include <unistd.h> | |||||
#include <machine/vmm.h> | |||||
#include "inout.h" | |||||
#include "pci_passthru.h" | |||||
#define MB (1024 * 1024UL) | |||||
/* | |||||
* PCI definitions | |||||
*/ | |||||
#define PCIR_GGC 0x50 /* GMCH Graphics Control register */ | |||||
#define PCIR_BDSM 0x5C /* Base Data of Stolen Memory register */ | |||||
#define PCIR_ASLS_CTL 0xFC /* Opregion start address register */ | |||||
#define PCIM_GEN5_75_GGC_GMS_MASK \ | |||||
0x000000F0 /* Bits 7:4 contain Graphics Mode Select */ | |||||
#define PCIM_GEN6_GGC_GMS_MASK \ | |||||
0x000000F8 /* Bits 7:3 contain Graphics Mode Select */ | |||||
#define PCIM_GEN8_GGC_GMS_MASK \ | |||||
0x0000FF00 /* Bits 15:8 contain Graphics Mode Select */ | |||||
#define PCIM_BDSM_GSM_MASK \ | |||||
0xFFF00000 /* Bits 31:20 contain base address of gsm */ | |||||
#define PCIM_ASLS_OPREGION_MASK 0xFFFFF000 /* Opregion is 4k aligned */ | |||||
#define GPU_OPREGION_LEN 0x00004000 /* Size of Opregion (16 KB) */ | |||||
// Known device ids for different generations of Intel graphics | |||||
// see https://www.graphics-drivers.eu/intel-pci-hardware-id-string.html for | |||||
// complete list Westmere & Ironlake | |||||
static const uint16_t igd_devid_gen5_75[] = { 0x0042, 0x0046 }; | |||||
// Sandy Bridge | |||||
static const uint16_t igd_devid_gen6[] = { 0x0102, 0x0106, 0x010A, 0x0112, | |||||
0x0116, 0x0122, 0x0126 }; | |||||
// Ivy Bridge | |||||
static const uint16_t igd_devid_gen7[] = { 0x0152, 0x0156, 0x015A, 0x0162, | |||||
0x0166, 0x016A }; | |||||
// Haswsell | |||||
static const uint16_t igd_devid_gen7_5[] = { 0x0402, 0x0406, 0x040A, 0x0412, | |||||
0x0416, 0x041A, 0x041E, 0x0A06, 0x0A0E, 0x0A16, 0x0A1E, 0x0A26, 0x0A2E, | |||||
0x0C02, 0x0C06, 0x0C12, 0x0C16, 0x0C22, 0x0C26, 0x0D06, 0x0D16, 0x0D22, | |||||
0x0D26 }; | |||||
// Broadwell | |||||
static const uint16_t igd_devid_gen8[] = { 0x1606, 0x160E, 0x1612, 0x1616, | |||||
0x161A, 0x161E, 0x1622, 0x1626, 0x162A, 0x162B }; | |||||
// Skylake | |||||
static const uint16_t igd_devid_gen9[] = { 0x1902, 0x1906, 0x190B, 0x190E, | |||||
0x1912, 0x1913, 0x1916, 0x1917, 0x191B, 0x191D, 0x191E, 0x1921, 0x1923, | |||||
0x1926, 0x1927, 0x192B, 0x192D, 0x1932, 0x193A, 0x193B, 0x193D }; | |||||
// Kaby Lake & Whiskey Lake & Amber Lake & Coffee Lake & Comet Lake | |||||
static const uint16_t igd_devid_gen9_5[] = { 0x3E90, 0x3E91, 0x3E92, 0x3E93, | |||||
0x3E94, 0x3E96, 0x3E98, 0x3E99, 0x3E9A, 0x3E9B, 0x3E9C, 0x3EA0, 0x3EA1, | |||||
0x3EA5, 0x3EA6, 0x3EA7, 0x3EA8, 0x3EA9, 0x5902, 0x5906, 0x590B, 0x5912, | |||||
0x5916, 0x5917, 0x591B, 0x591C, 0x591D, 0x591E, 0x5921, 0x5926, 0x5927, | |||||
0x87C0, 0x87CA, 0x9B21, 0x9B41, 0x9BA2, 0x9BA4, 0x9BA5, 0x9BA8, 0x9BAA, | |||||
0x9BAC, 0x9BC2, 0x9BC4, 0x9BC5, 0x9BC6, 0x9BC8, 0x9BCA, 0x9BCC, 0x9BE6, | |||||
0x9BF6 }; | |||||
static int | |||||
array_contains(const uint16_t *array, uint64_t elements, uint16_t item) | |||||
{ | |||||
for (uint64_t i = 0; i < elements; ++i) | |||||
if (array[i] == item) | |||||
return 1; | |||||
return 0; | |||||
} | |||||
#define IGD_FUNC_IS_IGD_GEN(gen) \ | |||||
static int igd_gen##gen##_is_igd_gen(int devid) \ | |||||
{ \ | |||||
return array_contains(igd_devid_gen##gen, \ | |||||
sizeof(igd_devid_gen##gen) / sizeof(uint16_t), devid); \ | |||||
} | |||||
/* | |||||
* GVT-d definitions | |||||
*/ | |||||
#define GVT_D_MAP_OPREGION 0 | |||||
#define GVT_D_MAP_GSM 1 | |||||
/* | |||||
* GVT-d: Handler for passthru of igd | |||||
* | |||||
* Keep it as struct instead of a single function pointer, since new | |||||
* generations of Intel graphics could need other funcs. | |||||
* e.g. Intel Elkhartlake and Intel Tigerlake: | |||||
* They will need different handling for GSM and Opregion (See ACRN-Hypervisor | |||||
* <https://github.com/projectacrn/acrn-hypervisor/blob/master/devicemodel/hw/pci/passthrough.c>) | |||||
*/ | |||||
struct igd_funcs { | |||||
int (*is_igd_gen)(int devid); | |||||
uint64_t (*get_gsm_len)(struct vmctx *ctx, struct passthru_softc *sc); | |||||
}; | |||||
/* | |||||
* GVT-d: Handler for igd of gen5.75 (Westmere & Ironlake) | |||||
*/ | |||||
IGD_FUNC_IS_IGD_GEN(5_75); | |||||
static uint64_t | |||||
igd_gen5_75_get_gsm_len(struct vmctx *ctx, struct passthru_softc *sc) | |||||
{ | |||||
uint16_t ggc_val = read_config(&sc->psc_sel, PCIR_GGC, 2); | |||||
uint8_t gms_val = (ggc_val & PCIM_GEN5_75_GGC_GMS_MASK) >> | |||||
4; /* Bits 7:4 contain Graphics Mode Select */ | |||||
switch (gms_val) { | |||||
case 0x05: | |||||
return 32 * MB; | |||||
case 0x06: | |||||
return 48 * MB; | |||||
case 0x07: | |||||
return 64 * MB; | |||||
case 0x08: | |||||
return 128 * MB; | |||||
case 0x09: | |||||
return 256 * MB; | |||||
case 0x0A: | |||||
return 96 * MB; | |||||
case 0x0B: | |||||
return 160 * MB; | |||||
case 0x0C: | |||||
return 224 * MB; | |||||
case 0x0D: | |||||
return 352 * MB; | |||||
} | |||||
warnx("Unknown Graphic Mode (%x)", gms_val); | |||||
return 0; | |||||
} | |||||
/* | |||||
* GVT-d: Handler for igd of gen6 (Sandy Bridge) | |||||
*/ | |||||
IGD_FUNC_IS_IGD_GEN(6); | |||||
static uint64_t | |||||
igd_gen6_get_gsm_len(struct vmctx *ctx, struct passthru_softc *sc) | |||||
{ | |||||
uint16_t ggc_val = read_config(&sc->psc_sel, PCIR_GGC, 2); | |||||
uint8_t gms_val = (ggc_val & PCIM_GEN6_GGC_GMS_MASK) >> | |||||
3; /* Bits 7:3 contain Graphics Mode Select */ | |||||
if (gms_val <= 0x10) | |||||
return gms_val * 32 * MB; | |||||
warnx("Unknown Graphic Mode (%x)", gms_val); | |||||
return 0; | |||||
} | |||||
/* | |||||
* GVT-d: Handler for igd of gen7 (Ivy Bridge) | |||||
*/ | |||||
IGD_FUNC_IS_IGD_GEN(7); | |||||
/* | |||||
* GVT-d: Handler for igd of gen7.5 (Haswell) | |||||
*/ | |||||
IGD_FUNC_IS_IGD_GEN(7_5); | |||||
/* | |||||
* GVT-d: Handler for igd of gen8 (Broadwell) | |||||
*/ | |||||
IGD_FUNC_IS_IGD_GEN(8); | |||||
static uint64_t | |||||
igd_gen8_get_gsm_len(struct vmctx *ctx, struct passthru_softc *sc) | |||||
{ | |||||
uint16_t ggc_val = read_config(&sc->psc_sel, PCIR_GGC, 2); | |||||
uint8_t gms_val = (ggc_val & PCIM_GEN8_GGC_GMS_MASK) >> | |||||
8; /* Bits 15:8 contain Graphics Mode Select */ | |||||
if ((gms_val <= 0x10) || (gms_val == 0x20) || (gms_val == 0x30) || | |||||
(gms_val == 0x3F)) | |||||
return gms_val * 32 * MB; | |||||
warnx("Unknown Graphic Mode (%x)", gms_val); | |||||
return 0; | |||||
} | |||||
/* | |||||
* GVT-d: Handler for igd of gen9 (Skylake) | |||||
*/ | |||||
IGD_FUNC_IS_IGD_GEN(9); | |||||
static uint64_t | |||||
igd_gen9_get_gsm_len(struct vmctx *ctx, struct passthru_softc *sc) | |||||
{ | |||||
uint16_t ggc_val = read_config(&sc->psc_sel, PCIR_GGC, 2); | |||||
uint8_t gms_val = (ggc_val & PCIM_GEN8_GGC_GMS_MASK) >> | |||||
8; /* Bits 15:8 contain Graphics Mode Select */ | |||||
if ((gms_val <= 0x10) || (gms_val == 0x20) || (gms_val == 0x30) || | |||||
(gms_val == 0x40)) | |||||
return gms_val * 32 * MB; | |||||
else if (gms_val >= 0xF0 && gms_val <= 0xFE) | |||||
return gms_val * 4 * MB; | |||||
warnx("Unknown Graphic Mode (%x)", gms_val); | |||||
return 0; | |||||
} | |||||
/* | |||||
* GVT-d: Handler for igd of gen9.5 (Kaby Lake & Whiskey Lake & Amber Lake & | |||||
* Coffee Lake & Comet Lake) | |||||
*/ | |||||
IGD_FUNC_IS_IGD_GEN(9_5); | |||||
// Westmere & Ironlake | |||||
static const struct igd_funcs igd_gen5_75 = { | |||||
.is_igd_gen = igd_gen5_75_is_igd_gen, | |||||
.get_gsm_len = igd_gen5_75_get_gsm_len | |||||
}; | |||||
// Sandy Bridge | |||||
static const struct igd_funcs igd_gen6 = { .is_igd_gen = igd_gen6_is_igd_gen, | |||||
.get_gsm_len = igd_gen6_get_gsm_len }; | |||||
// Ivy Bridge | |||||
static const struct igd_funcs igd_gen7 = { .is_igd_gen = igd_gen7_is_igd_gen, | |||||
.get_gsm_len = igd_gen6_get_gsm_len }; | |||||
// Haswell | |||||
static const struct igd_funcs igd_gen7_5 = { | |||||
.is_igd_gen = igd_gen7_5_is_igd_gen, | |||||
.get_gsm_len = igd_gen6_get_gsm_len | |||||
}; | |||||
// Broadwell | |||||
static const struct igd_funcs igd_gen8 = { .is_igd_gen = igd_gen8_is_igd_gen, | |||||
.get_gsm_len = igd_gen8_get_gsm_len }; | |||||
// Skylake | |||||
static const struct igd_funcs igd_gen9 = { .is_igd_gen = igd_gen9_is_igd_gen, | |||||
.get_gsm_len = igd_gen9_get_gsm_len }; | |||||
// Kaby Lake & Whiskey Lake & Amber Lake & Coffee Lake & Comet Lake | |||||
static const struct igd_funcs igd_gen9_5 = { | |||||
.is_igd_gen = igd_gen9_5_is_igd_gen, | |||||
.get_gsm_len = igd_gen9_get_gsm_len | |||||
}; | |||||
static const struct igd_funcs *igd_gen_map[] = { &igd_gen5_75, &igd_gen6, | |||||
&igd_gen7, &igd_gen7_5, &igd_gen8, &igd_gen9, &igd_gen9_5 }; | |||||
static const struct igd_funcs * | |||||
get_igd_funcs(const uint16_t devid) | |||||
{ | |||||
for (int i = 0; i < sizeof(igd_gen_map) / sizeof(struct igd_funcs *); | |||||
++i) { | |||||
if (igd_gen_map[i]->is_igd_gen(devid)) | |||||
return igd_gen_map[i]; | |||||
} | |||||
return NULL; | |||||
} | |||||
static int | |||||
gvt_d_init(struct vmctx *ctx, struct pci_devinst *pi, char *opts) | |||||
{ | |||||
int error; | |||||
struct passthru_softc *sc; | |||||
sc = pi->pi_arg; | |||||
/* Enable Memory and IO Space for device */ | |||||
uint16_t cmd = read_config(&sc->psc_sel, PCIR_COMMAND, 0x02); | |||||
cmd |= PCIM_CMD_MEMEN | PCIM_CMD_PORTEN | PCIM_CMD_BUSMASTEREN; | |||||
write_config(&sc->psc_sel, PCIR_COMMAND, 0x02, cmd); | |||||
pci_set_cfgdata16(pi, PCIR_COMMAND, cmd); | |||||
// check vendor == Intel | |||||
const uint16_t dev_vendor = read_config(&sc->psc_sel, PCIR_VENDOR, 2); | |||||
if (dev_vendor != 0x8086) { | |||||
warnx("Unknown vendor (%x) of igd", dev_vendor); | |||||
return -ENODEV; | |||||
} | |||||
// check if device is a display device | |||||
if (read_config(&sc->psc_sel, PCIR_CLASS, 1) != PCIC_DISPLAY) { | |||||
warnx("%s is no display device", pi->pi_name); | |||||
return -ENODEV; | |||||
} | |||||
/* | |||||
* GVT-d: Create LPC-Device at 0:1f.0 | |||||
* | |||||
* Otherwise GOP-Driver wouldn't work for Windows | |||||
*/ | |||||
printf("Add igd-lpc at slot 0:1f.0 to enable GVT-d for igd\n"); | |||||
if ((error = pci_parse_slot("0:31:0,igd-lpc")) != 0) { | |||||
warnx("Failed to add igd-lpc"); | |||||
return error; | |||||
} | |||||
/* | |||||
* GVT-d: Get IGD funcs | |||||
*/ | |||||
const struct igd_funcs *igd = get_igd_funcs( | |||||
read_config(&sc->psc_sel, PCIR_DEVICE, 2)); | |||||
if (igd == NULL) { | |||||
warnx("Unsupported igd-device (%x)", | |||||
read_config(&sc->psc_sel, PCIR_DEVICE, 2)); | |||||
return -ENODEV; | |||||
} | |||||
struct passthru_mmio_mapping *opregion = | |||||
&sc->psc_mmio_map[GVT_D_MAP_OPREGION]; | |||||
struct passthru_mmio_mapping *gsm = &sc->psc_mmio_map[GVT_D_MAP_GSM]; | |||||
// GVT-d: get opregion length | |||||
opregion->len = GPU_OPREGION_LEN; | |||||
// GVT-d: get opregion hpa | |||||
opregion->hpa = read_config(&sc->psc_sel, PCIR_ASLS_CTL, 4) & | |||||
PCIM_ASLS_OPREGION_MASK; | |||||
// GVT-d: get gsm len | |||||
gsm->len = igd->get_gsm_len(ctx, sc); | |||||
// GVT-d: get gsm hpa | |||||
gsm->hpa = read_config(&sc->psc_sel, PCIR_BDSM, 4) & PCIM_BDSM_GSM_MASK; | |||||
if (opregion->len == 0 || gsm->len == 0) { | |||||
warnx("Could not determine size of opregion or gsm"); | |||||
return -ENODEV; | |||||
} | |||||
/* | |||||
* GVT-d: Allocate Opregion and GSM in guest space | |||||
*/ | |||||
opregion->gpa = pci_emul_alloc_mmio( | |||||
PCIBAR_MEM32, opregion->len, ~PCIM_ASLS_OPREGION_MASK); | |||||
gsm->gpa = pci_emul_alloc_mmio( | |||||
PCIBAR_MEM32, gsm->len, ~PCIM_BDSM_GSM_MASK); | |||||
if (opregion->gpa == 0 || gsm->gpa == 0) { | |||||
error = -ENOMEM; | |||||
goto failed_opregion; | |||||
} | |||||
/* | |||||
* GVT-d: Write address of Opregion and GSM into PCI register | |||||
*/ | |||||
// GVT-d: set opregion gpa | |||||
uint32_t asls_val = read_config(&sc->psc_sel, PCIR_ASLS_CTL, 4); | |||||
pci_set_cfgdata32(sc->psc_pi, PCIR_ASLS_CTL, | |||||
opregion->gpa | (asls_val & ~PCIM_ASLS_OPREGION_MASK)); | |||||
// GVT-d: set gsm gpa | |||||
uint32_t bdsm_val = read_config(&sc->psc_sel, PCIR_BDSM, 4); | |||||
pci_set_cfgdata32( | |||||
sc->psc_pi, PCIR_BDSM, gsm->gpa | (bdsm_val & ~PCIM_BDSM_GSM_MASK)); | |||||
/* | |||||
* GVT-d: Map Opregion and GSM into guest space | |||||
*/ | |||||
if ((error = passthru_modify_pptdev_mmio( | |||||
ctx, sc, opregion, PT_MAP_PPTDEV_MMIO)) != 0) | |||||
goto failed_opregion; | |||||
if ((error = passthru_modify_pptdev_mmio( | |||||
ctx, sc, gsm, PT_MAP_PPTDEV_MMIO)) != 0) | |||||
goto failed_gsm; | |||||
return (0); | |||||
failed_opregion: | |||||
opregion->gpa = 0; | |||||
failed_gsm: | |||||
gsm->gpa = 0; | |||||
return error; | |||||
} | |||||
static void | |||||
gvt_d_deinit(struct vmctx *ctx, struct pci_devinst *pi) | |||||
{ | |||||
struct passthru_softc *sc; | |||||
sc = pi->pi_arg; | |||||
struct passthru_mmio_mapping *gsm = &sc->psc_mmio_map[GVT_D_MAP_GSM]; | |||||
struct passthru_mmio_mapping *opregion = | |||||
&sc->psc_mmio_map[GVT_D_MAP_OPREGION]; | |||||
// gpa is only set, if it's initialized | |||||
if (gsm->gpa) | |||||
passthru_modify_pptdev_mmio(ctx, sc, gsm, PT_UNMAP_PPTDEV_MMIO); | |||||
if (opregion->gpa) | |||||
passthru_modify_pptdev_mmio( | |||||
ctx, sc, opregion, PT_UNMAP_PPTDEV_MMIO); | |||||
} | |||||
static int | |||||
gvt_d_cfgwrite(struct vmctx *ctx, int vcpu, struct pci_devinst *pi, int coff, | |||||
int bytes, uint32_t val) | |||||
{ | |||||
/* | |||||
* Prevent write to BDSM and ASLS_CTL | |||||
* | |||||
* BDSM: contains Base of Data Stolen Memory | |||||
* ASLS_CTL: contains address of Opregion | |||||
*/ | |||||
if (coff == PCIR_BDSM || coff == PCIR_ASLS_CTL) | |||||
return (0); | |||||
return passthru_cfgwrite(ctx, vcpu, pi, coff, bytes, val); | |||||
} | |||||
static int | |||||
gvt_d_cfgread(struct vmctx *ctx, int vcpu, struct pci_devinst *pi, int coff, | |||||
int bytes, uint32_t *rv) | |||||
{ | |||||
/* | |||||
* Emulate BDSM and ASLS_CTL | |||||
* | |||||
* BDSM: contains Base of Data Stolen Memory | |||||
* ASLS_CTL: contains address of Opregion | |||||
*/ | |||||
if ((coff >= PCIR_BDSM && coff < PCIR_BDSM + 4) || | |||||
(coff >= PCIR_ASLS_CTL && coff < PCIR_ASLS_CTL + 4)) | |||||
return (-1); | |||||
return passthru_cfgread(ctx, vcpu, pi, coff, bytes, rv); | |||||
} | |||||
struct pci_devemu gvt_d = { | |||||
.pe_emu = "gvt-d", | |||||
.pe_init = passthru_init, | |||||
.pe_quirks_init = gvt_d_init, | |||||
.pe_quirks_deinit = gvt_d_deinit, | |||||
.pe_cfgwrite = gvt_d_cfgwrite, | |||||
.pe_cfgread = gvt_d_cfgread, | |||||
.pe_barwrite = passthru_write, | |||||
.pe_barread = passthru_read, | |||||
}; | |||||
PCI_EMUL_SET(gvt_d); |