Index: usr.sbin/bhyve/Makefile =================================================================== --- usr.sbin/bhyve/Makefile +++ usr.sbin/bhyve/Makefile @@ -43,7 +43,9 @@ pci_emul.c \ pci_hda.c \ pci_fbuf.c \ + pci_gvt-d.c \ pci_hostbridge.c \ + pci_igd_lpc.c \ pci_irq.c \ pci_lpc.c \ pci_nvme.c \ Index: usr.sbin/bhyve/pci_emul.h =================================================================== --- usr.sbin/bhyve/pci_emul.h +++ usr.sbin/bhyve/pci_emul.h @@ -40,6 +40,8 @@ #include +#include + #define PCI_BARMAX PCIR_MAX_BAR_0 /* BAR registers in a Type 0 header */ struct vmctx; @@ -53,6 +55,9 @@ /* instance creation */ int (*pe_init)(struct vmctx *, struct pci_devinst *, char *opts); + int (*pe_quirks_init)(struct vmctx *ctx, struct pci_devinst *pi, + char *opts); + void (*pe_quirks_deinit)(struct vmctx *ctx, struct pci_devinst *pi); /* ACPI DSDT enumeration */ void (*pe_write_dsdt)(struct pci_devinst *); @@ -95,6 +100,7 @@ enum pcibar_type type; /* io or memory */ uint64_t size; uint64_t addr; + uint8_t lobits; }; #define PI_NAMESZ 40 @@ -226,6 +232,8 @@ enum pcibar_type type, uint64_t size); int pci_emul_alloc_pbar(struct pci_devinst *pdi, int idx, uint64_t hostbase, enum pcibar_type type, uint64_t size); +uint64_t pci_emul_alloc_mmio(enum pcibar_type type, uint64_t size, uint64_t mask); +int passthru_modify_bar_registration(struct pci_devinst *pi, int idx, int registration); int pci_emul_add_msicap(struct pci_devinst *pi, int msgnum); int pci_emul_add_pciecap(struct pci_devinst *pi, int pcie_device_type); void pci_emul_capwrite(struct pci_devinst *pi, int offset, int bytes, @@ -301,4 +309,10 @@ return (*(uint32_t *)(pi->pi_cfgdata + offset)); } +static __inline int +is_gvt_d(struct pci_devinst *pi) +{ + return (strcmp(pi->pi_d->pe_emu, "gvt-d") == 0); +} + #endif /* _PCI_EMUL_H_ */ Index: usr.sbin/bhyve/pci_emul.c =================================================================== --- usr.sbin/bhyve/pci_emul.c +++ usr.sbin/bhyve/pci_emul.c @@ -491,6 +491,10 @@ break; case PCIBAR_MEM32: case PCIBAR_MEM64: + if (is_gvt_d(pi)) { + error = passthru_modify_bar_registration(pi, idx, registration); + break; + } bzero(&mr, sizeof(struct mem_range)); mr.name = pi->pi_name; mr.base = pi->pi_bar[idx].addr; @@ -692,6 +696,40 @@ return (0); } +// mask should be a power of 2 minus 1 (e.g. 0x000FFFFF) +uint64_t +pci_emul_alloc_mmio(enum pcibar_type type, uint64_t size, uint64_t mask) +{ + uint64_t *baseptr, limit; + + switch (type) { + case PCIBAR_IO: + baseptr = &pci_emul_iobase; + limit = PCI_EMUL_IOLIMIT; + break; + case PCIBAR_MEM32: + baseptr = &pci_emul_membase32; + limit = PCI_EMUL_MEMLIMIT32; + break; + case PCIBAR_MEM64: + baseptr = &pci_emul_membase64; + limit = PCI_EMUL_MEMLIMIT64; + break; + default: + return 0; + } + + // align base + const uint64_t base = (*baseptr + mask) & ~mask; + + if (base + size > limit) + return 0; + + *baseptr = base + size; + + return base; +} + #define CAP_START_OFFSET 0x40 static int pci_emul_add_capability(struct pci_devinst *pi, u_char *capdata, int caplen) Index: usr.sbin/bhyve/pci_gvt-d.c =================================================================== --- /dev/null +++ usr.sbin/bhyve/pci_gvt-d.c @@ -0,0 +1,464 @@ +/*- + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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 + * ) + */ +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); Index: usr.sbin/bhyve/pci_igd_lpc.c =================================================================== --- /dev/null +++ usr.sbin/bhyve/pci_igd_lpc.c @@ -0,0 +1,107 @@ +/*- + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include "pci_emul.h" + +#ifndef _PATH_DEVPCI +#define _PATH_DEVPCI "/dev/pci" +#endif + +int pcifd = -1; + +static uint32_t +read_config(long reg, int width) +{ + // igd-lpc is always connected to 0:1f.0 + struct pci_io pi = { + .pi_sel = { + .pc_dev = 0x1f, + }, + .pi_reg = reg, + .pi_width = width, + }; + + if (ioctl(pcifd, PCIOCREAD, &pi) < 0) + return (0); + + return (pi.pi_data); +} + +static int +pci_igd_lpc_init(struct vmctx *ctx, struct pci_devinst *pi, char *opts) +{ + // only allow igd-lpc on 0:1f.0 + if (pi->pi_bus != 0 || pi->pi_slot != 0x1f || pi->pi_func != 0x00) { + warn("igd-lpc only allowed on 0:1f.0"); + return (-1); + } + + // open host device + if (pcifd < 0) { + pcifd = open(_PATH_DEVPCI, O_RDWR, 0); + if (pcifd < 0) { + warn("failed to open %s", _PATH_DEVPCI); + return (-1); + } + } + + /* + * The VID, DID, REVID, SUBVID and SUBDID of igd-lpc need aligned with + * physical one. Without these physical values, GVT-d GOP driver + * couldn't work. + */ + pci_set_cfgdata16(pi, PCIR_DEVICE, read_config(PCIR_DEVICE, 2)); + pci_set_cfgdata16(pi, PCIR_VENDOR, read_config(PCIR_VENDOR, 2)); + pci_set_cfgdata8(pi, PCIR_REVID, read_config(PCIR_REVID, 1)); + pci_set_cfgdata16(pi, PCIR_SUBVEND_0, read_config(PCIR_SUBVEND_0, 2)); + pci_set_cfgdata16(pi, PCIR_SUBDEV_0, read_config(PCIR_SUBDEV_0, 2)); + + return (0); +} + +struct pci_devemu pci_de_igd_lpc = { .pe_emu = "igd-lpc", + .pe_init = pci_igd_lpc_init }; +PCI_EMUL_SET(pci_de_igd_lpc); Index: usr.sbin/bhyve/pci_passthru.h =================================================================== --- /dev/null +++ usr.sbin/bhyve/pci_passthru.h @@ -0,0 +1,80 @@ +/*- + * 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$ + */ + +#ifndef __PCI_PASSTHRU_H__ +#define __PCI_PASSTHRU_H__ + +#include + +#include + +#include "pci_emul.h" + +struct passthru_mmio_mapping { + uint64_t gpa; + uint64_t len; + uint64_t hpa; +}; + +struct passthru_softc { + struct pci_devinst *psc_pi; + struct pcibar psc_bar[PCI_BARMAX + 1]; + struct { + int capoff; + int msgctrl; + int emulated; + } psc_msi; + struct { + int capoff; + } psc_msix; + struct pcisel psc_sel; + + struct passthru_mmio_mapping psc_mmio_map[2]; +}; + +#define PT_MAP_PPTDEV_MMIO 1 +#define PT_UNMAP_PPTDEV_MMIO 0 + +int passthru_modify_pptdev_mmio(struct vmctx *ctx, struct passthru_softc *sc, + struct passthru_mmio_mapping *map, int registration); +uint32_t read_config(const struct pcisel *sel, long reg, int width); +void write_config(const struct pcisel *sel, long reg, int width, uint32_t data); +int bar_access(int coff); +int passthru_init(struct vmctx *ctx, struct pci_devinst *pi, char *opts); +int passthru_cfgwrite(struct vmctx *ctx, int vcpu, struct pci_devinst *pi, + int coff, int bytes, uint32_t val); +int passthru_cfgread(struct vmctx *ctx, int vcpu, struct pci_devinst *pi, + int coff, int bytes, uint32_t *rv); +void passthru_write(struct vmctx *ctx, int vcpu, struct pci_devinst *pi, + int baridx, uint64_t offset, int size, uint64_t value); +uint64_t passthru_read(struct vmctx *ctx, int vcpu, struct pci_devinst *pi, + int baridx, uint64_t offset, int size); + +#endif Index: usr.sbin/bhyve/pci_passthru.c =================================================================== --- usr.sbin/bhyve/pci_passthru.c +++ usr.sbin/bhyve/pci_passthru.c @@ -58,9 +58,8 @@ #include #include -#include -#include "pci_emul.h" #include "mem.h" +#include "pci_passthru.h" #ifndef _PATH_DEVPCI #define _PATH_DEVPCI "/dev/pci" @@ -83,20 +82,6 @@ static int iofd = -1; static int memfd = -1; -struct passthru_softc { - struct pci_devinst *psc_pi; - struct pcibar psc_bar[PCI_BARMAX + 1]; - struct { - int capoff; - int msgctrl; - int emulated; - } psc_msi; - struct { - int capoff; - } psc_msix; - struct pcisel psc_sel; -}; - static int msi_caplen(int msgctrl) { @@ -119,7 +104,7 @@ return (len); } -static uint32_t +uint32_t read_config(const struct pcisel *sel, long reg, int width) { struct pci_io pi; @@ -135,7 +120,7 @@ return (pi.pi_data); } -static void +void write_config(const struct pcisel *sel, long reg, int width, uint32_t data) { struct pci_io pi; @@ -149,6 +134,70 @@ (void)ioctl(pcifd, PCIOCWRITE, &pi); /* XXX */ } +int +passthru_modify_pptdev_mmio(struct vmctx *ctx, struct passthru_softc *sc, struct passthru_mmio_mapping *map, int registration) +{ + if (registration == PT_MAP_PPTDEV_MMIO) + return vm_map_pptdev_mmio(ctx, sc->psc_sel.pc_bus, sc->psc_sel.pc_dev, sc->psc_sel.pc_func, map->gpa, map->len, map->hpa); + else + return vm_unmap_pptdev_mmio(ctx, sc->psc_sel.pc_bus, sc->psc_sel.pc_dev, sc->psc_sel.pc_func, map->gpa, map->len); +} + +int +passthru_modify_bar_registration(struct pci_devinst *pi, int idx, int registration) +{ + int error; + struct passthru_softc *sc; + struct passthru_mmio_mapping map; + + sc = pi->pi_arg; + + /* + * If the guest writes a new value to a 64-bit BAR, two writes are neccessary. + * vm_map_pptdev_mmio can fail in that case due to an invalid address after the first write. + * To avoid it, skip registration in that case. + */ + if ((registration == PT_MAP_PPTDEV_MMIO) && (pi->pi_bar[idx].type == PCIBAR_MEM64)) + if ((pci_get_cfgdata32(pi, PCIR_BAR(idx + 0)) == ~0U) || + (pci_get_cfgdata32(pi, PCIR_BAR(idx + 1)) == ~0U)) + return 0; + + if (idx != pci_msix_table_bar(pi)) { + map.gpa = pi->pi_bar[idx].addr; + map.len = pi->pi_bar[idx].size; + map.hpa = sc->psc_bar[idx].addr; + return passthru_modify_pptdev_mmio(pi->pi_vmctx, sc, &map, registration); + } + + // special handling for MSI-X table + uint32_t table_offset, table_size; + + table_offset = rounddown2(pi->pi_msix.table_offset, 4096); + table_size = pi->pi_msix.table_offset - table_offset; + table_size += pi->pi_msix.table_count * MSIX_TABLE_ENTRY_SIZE; + table_size = roundup2(table_size, 4096); + + map.gpa = pi->pi_bar[idx].addr; + map.len = table_offset; + map.hpa = sc->psc_bar[idx].addr; + + // map/unmap everything before MSI-X table + if (map.len > 0) + if ((error = passthru_modify_pptdev_mmio(pi->pi_vmctx, sc, &map, registration)) != 0) + return error; + + map.gpa += table_offset + table_size; + map.len = pi->pi_bar[idx].size - (table_offset + table_size); + map.hpa += table_offset + table_size; + + // map/unmap everything behind MSI-X table + if (map.len > 0) + if ((error = passthru_modify_pptdev_mmio(pi->pi_vmctx, sc, &map, registration)) != 0) + return error; + + return (0); +} + #ifdef LEGACY_SUPPORT static int passthru_add_msicap(struct pci_devinst *pi, int msgnum, int nextptr) @@ -556,12 +605,33 @@ sc->psc_bar[i].type = bartype; sc->psc_bar[i].size = size; sc->psc_bar[i].addr = base; + sc->psc_bar[i].lobits = 0; /* Allocate the BAR in the guest I/O or MMIO space */ error = pci_emul_alloc_pbar(pi, i, base, bartype, size); if (error) return (-1); + /* Use same prefetchable property as physical bar */ + if (is_gvt_d(pi)) { + uint8_t lobits = pci_get_cfgdata8(pi, PCIR_BAR(i)); + if (bartype == PCIBAR_MEM32 || bartype == PCIBAR_MEM64) { + if (bar.pbi_base & PCIM_BAR_MEM_PREFETCH) + lobits |= PCIM_BAR_MEM_PREFETCH; + else + lobits &= ~PCIM_BAR_MEM_PREFETCH; + pci_set_cfgdata8(pi, PCIR_BAR(i), lobits); + lobits &= ~PCIM_BAR_MEM_BASE; + } + else { + lobits |= PCIM_BAR_IO_SPACE; + pci_set_cfgdata8(pi, PCIR_BAR(i), lobits); + lobits &= ~PCIM_BAR_IO_BASE; + } + sc->psc_bar[i].lobits = lobits; + pi->pi_bar[i].lobits = lobits; + } + /* The MSI-X table needs special handling */ if (i == pci_msix_table_bar(pi)) { error = init_msix_table(ctx, sc, base); @@ -615,7 +685,7 @@ return (error); } -static int +int passthru_init(struct vmctx *ctx, struct pci_devinst *pi, char *opts) { int bus, slot, func, error, memflags; @@ -702,16 +772,23 @@ sc->psc_pi = pi; /* initialize config space */ - error = cfginit(ctx, pi, bus, slot, func); + if ((error = cfginit(ctx, pi, bus, slot, func)) != 0) + goto done; + + if (pi->pi_d->pe_quirks_init) + if ((error = pi->pi_d->pe_quirks_init(ctx, pi, opts)) != 0) + goto done; done: if (error) { + if (pi->pi_d->pe_quirks_deinit) + pi->pi_d->pe_quirks_deinit(ctx, pi); free(sc); vm_unassign_pptdev(ctx, bus, slot, func); } return (error); } -static int +int bar_access(int coff) { if (coff >= PCIR_BAR(0) && coff < PCIR_BAR(PCI_BARMAX + 1)) @@ -747,6 +824,38 @@ } static int +passthru_cfgread_bar(struct vmctx *ctx, int vcpu, struct pci_devinst *pi, + int coff, int bytes, uint32_t *rv) +{ + const int idx = (coff - PCIR_BAR(0)) / 4; + int update_idx = idx; + if (pi->pi_bar[idx].type == PCIBAR_MEMHI64) + --update_idx; + + if (pci_get_cfgdata32(pi, PCIR_BAR(idx)) != ~0U) { + // return address of BAR + if (bytes == 1) + *rv = pci_get_cfgdata8(pi, coff); + else if (bytes == 2) + *rv = pci_get_cfgdata16(pi, coff); + else + *rv = pci_get_cfgdata32(pi, coff); + + return (0); + } + + // return size of BAR + uint64_t size = ~(uint64_t)(pi->pi_bar[update_idx].size - 1); + size |= pi->pi_bar[update_idx].lobits; + if (pi->pi_bar[idx].type == PCIBAR_MEMHI64) + size >>= 32; + assert(bytes == 4); + *rv = size; + + return (0); +} + +int passthru_cfgread(struct vmctx *ctx, int vcpu, struct pci_devinst *pi, int coff, int bytes, uint32_t *rv) { @@ -757,9 +866,16 @@ /* * PCI BARs and MSI capability is emulated. */ - if (bar_access(coff) || msicap_access(sc, coff)) + if (msicap_access(sc, coff)) return (-1); + if (bar_access(coff)) { + if (is_gvt_d(pi)) + return passthru_cfgread_bar(ctx, vcpu, pi, coff, bytes, rv); + else + return (-1); + } + #ifdef LEGACY_SUPPORT /* * Emulate PCIR_CAP_PTR if this device does not support MSI capability @@ -791,6 +907,82 @@ } static int +passthru_cfgwrite_bar(struct vmctx *ctx, int vcpu, struct pci_devinst *pi, + int coff, int bytes, uint32_t val) +{ + const int idx = (coff - PCIR_BAR(0)) / 4; + int update_idx = idx; + + switch (pi->pi_bar[idx].type) { + case PCIBAR_MEMHI64: + --update_idx; + case PCIBAR_IO: + case PCIBAR_MEM32: + case PCIBAR_MEM64: + { + const uint16_t cmd = pci_get_cfgdata16(pi, PCIR_COMMAND); + if ((cmd & PCIM_CMD_MEMEN && pi->pi_bar[idx].type != PCIBAR_IO) || + (cmd & PCIM_CMD_PORTEN && pi->pi_bar[idx].type == PCIBAR_IO)) { + passthru_modify_bar_registration(pi, update_idx, 0); + } + + if (val == ~0U) { + // guest wants to read size of BAR + pci_set_cfgdata32(pi, coff, ~0U); + pi->pi_bar[update_idx].addr = 0; + break; + } + + // guest sets address of BAR + uint64_t mask, bar; + mask = ~(pi->pi_bar[update_idx].size - 1); + if (pi->pi_bar[idx].type == PCIBAR_MEMHI64) + mask >>= 32; + bar = val & mask; + if (pi->pi_bar[idx].type != PCIBAR_MEMHI64) + bar |= pi->pi_bar[update_idx].lobits; + pci_set_cfgdata32(pi, coff, bar); + + /* Only register BAR if it contains a valid address */ + uint32_t lo, hi; + + lo = pci_get_cfgdata32(pi, PCIR_BAR(update_idx)); + if (pi->pi_bar[update_idx].type == PCIBAR_IO) { + if ((lo & PCIM_BAR_IO_BASE) == PCIM_BAR_IO_BASE) + lo = ~0U; + else + lo &= PCIM_BAR_IO_BASE; + } else { + if ((lo & PCIM_BAR_MEM_BASE) == PCIM_BAR_MEM_BASE) + lo = ~0U; + else + lo &= PCIM_BAR_MEM_BASE; + } + + if (pi->pi_bar[update_idx].type == PCIBAR_MEM64) + hi = pci_get_cfgdata32(pi, PCIR_BAR(update_idx + 1)); + else + hi = 0; + + if (lo != ~0U && hi != ~0U) { + pi->pi_bar[update_idx].addr = (uint64_t)lo | ((uint64_t)hi << 32U); + if ((cmd & PCIM_CMD_MEMEN && pi->pi_bar[idx].type != PCIBAR_IO) || + (cmd & PCIM_CMD_PORTEN && pi->pi_bar[idx].type == PCIBAR_IO)) { + passthru_modify_bar_registration(pi, update_idx, 1); + } + } + else + pi->pi_bar[update_idx].addr = 0; + break; + } + default: + pi->pi_bar[idx].addr = 0; + break; + } + return (0); +} + +int passthru_cfgwrite(struct vmctx *ctx, int vcpu, struct pci_devinst *pi, int coff, int bytes, uint32_t val) { @@ -803,8 +995,12 @@ /* * PCI BARs are emulated */ - if (bar_access(coff)) - return (-1); + if (bar_access(coff)) { + if (is_gvt_d(pi)) + return passthru_cfgwrite_bar(ctx, vcpu, pi, coff, bytes, val); + else + return (-1); + } /* * MSI capability is emulated @@ -866,7 +1062,7 @@ return (0); } -static void +void passthru_write(struct vmctx *ctx, int vcpu, struct pci_devinst *pi, int baridx, uint64_t offset, int size, uint64_t value) { @@ -889,7 +1085,7 @@ } } -static uint64_t +uint64_t passthru_read(struct vmctx *ctx, int vcpu, struct pci_devinst *pi, int baridx, uint64_t offset, int size) {