diff --git a/lib/libvmmapi/vmmapi.h b/lib/libvmmapi/vmmapi.h --- a/lib/libvmmapi/vmmapi.h +++ b/lib/libvmmapi/vmmapi.h @@ -73,6 +73,7 @@ VM_SYSMEM, VM_BOOTROM, VM_FRAMEBUFFER, + VM_VIDEOBIOS }; /* @@ -180,6 +181,8 @@ vm_paddr_t gpa, size_t len, vm_paddr_t hpa); int vm_unmap_pptdev_mmio(struct vmctx *ctx, int bus, int slot, int func, vm_paddr_t gpa, size_t len); +int vm_get_vbios(struct vmctx *ctx, int bus, int slot, int func, + uint16_t vendor, uint16_t dev_id, void *bios, uint64_t *size); int vm_setup_pptdev_msi(struct vmctx *ctx, int vcpu, int bus, int slot, int func, uint64_t addr, uint64_t msg, int numvec); int vm_setup_pptdev_msix(struct vmctx *ctx, int vcpu, int bus, int slot, diff --git a/lib/libvmmapi/vmmapi.c b/lib/libvmmapi/vmmapi.c --- a/lib/libvmmapi/vmmapi.c +++ b/lib/libvmmapi/vmmapi.c @@ -1009,6 +1009,30 @@ return (ioctl(ctx->fd, VM_UNMAP_PPTDEV_MMIO, &pptmmio)); } +int +vm_get_vbios(struct vmctx *ctx, int bus, int slot, int func, uint16_t vendor, + uint16_t dev_id, void *bios, uint64_t *size) +{ + struct vm_vbios vbios; + + bzero(&vbios, sizeof(vbios)); + vbios.bus = bus; + vbios.slot = slot; + vbios.func = func; + vbios.vendor = vendor; + vbios.dev_id = dev_id; + vbios.bios = bios; + if (size != NULL) + vbios.size = *size; + + const int error = ioctl(ctx->fd, VM_GET_VBIOS, &vbios); + + if (size) + *size = vbios.size; + + return (error); +} + int vm_setup_pptdev_msi(struct vmctx *ctx, int vcpu, int bus, int slot, int func, uint64_t addr, uint64_t msg, int numvec) @@ -1684,7 +1708,7 @@ VM_SET_CAPABILITY, VM_GET_CAPABILITY, VM_BIND_PPTDEV, VM_UNBIND_PPTDEV, VM_MAP_PPTDEV_MMIO, VM_PPTDEV_MSI, VM_PPTDEV_MSIX, VM_UNMAP_PPTDEV_MMIO, VM_PPTDEV_DISABLE_MSIX, - VM_INJECT_NMI, VM_STATS, VM_STAT_DESC, + VM_GET_VBIOS, VM_INJECT_NMI, VM_STATS, VM_STAT_DESC, VM_SET_X2APIC_STATE, VM_GET_X2APIC_STATE, VM_GET_HPET_CAPABILITIES, VM_GET_GPA_PMAP, VM_GLA2GPA, VM_GLA2GPA_NOFAULT, diff --git a/sys/amd64/include/vmm_dev.h b/sys/amd64/include/vmm_dev.h --- a/sys/amd64/include/vmm_dev.h +++ b/sys/amd64/include/vmm_dev.h @@ -146,6 +146,16 @@ size_t len; }; +struct vm_vbios { + int bus; + int slot; + int func; + uint16_t vendor; + uint16_t dev_id; + void *bios; + uint64_t size; +}; + struct vm_pptdev_msi { int vcpu; int bus; @@ -309,6 +319,7 @@ IOCNUM_PPTDEV_MSIX = 44, IOCNUM_PPTDEV_DISABLE_MSIX = 45, IOCNUM_UNMAP_PPTDEV_MMIO = 46, + IOCNUM_GET_VBIOS = 47, /* statistics */ IOCNUM_VM_STATS = 50, @@ -427,6 +438,8 @@ _IOW('v', IOCNUM_PPTDEV_DISABLE_MSIX, struct vm_pptdev) #define VM_UNMAP_PPTDEV_MMIO \ _IOW('v', IOCNUM_UNMAP_PPTDEV_MMIO, struct vm_pptdev_mmio) +#define VM_GET_VBIOS \ + _IOWR('v', IOCNUM_GET_VBIOS, struct vm_vbios) #define VM_INJECT_NMI \ _IOW('v', IOCNUM_INJECT_NMI, struct vm_nmi) #define VM_STATS \ diff --git a/sys/amd64/vmm/amd/amdgpu_bios.h b/sys/amd64/vmm/amd/amdgpu_bios.h new file mode 100644 --- /dev/null +++ b/sys/amd64/vmm/amd/amdgpu_bios.h @@ -0,0 +1,37 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2021 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$ + */ + +#pragma once + +#include +#include + +int vm_amdgpu_get_vbios(struct vm *vm, int bus, int slot, int func, + uint16_t vendor, uint16_t dev_id, void *bios, uint64_t *size); diff --git a/sys/amd64/vmm/amd/amdgpu_bios.c b/sys/amd64/vmm/amd/amdgpu_bios.c new file mode 100644 --- /dev/null +++ b/sys/amd64/vmm/amd/amdgpu_bios.c @@ -0,0 +1,317 @@ +/* + * Copyright 2008 Advanced Micro Devices, Inc. + * Copyright 2008 Red Hat Inc. + * Copyright 2009 Jerome Glisse. + * Copyright 2021 Beckhoff Automation GmbH & Co. KG + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Dave Airlie + * Alex Deucher + * Jerome Glisse + */ + +/* + * This file is a modified copy of + */ + +/* includes */ +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "amdgpu_bios.h" +#include "atombios.h" +#include "contrib/dev/acpica/include/acpi.h" +#include "contrib/dev/acpica/include/acpixf.h" + +/* type definitons */ +#define __iomem +#define true 1 +#define false 0 +typedef uint32_t acpi_size; + +/* log definitions */ +#define DRM_ERROR uprintf + +#define GFP_NATIVE_MASK (M_NOWAIT | M_WAITOK | M_USE_RESERVE | M_ZERO) +#define GFP_KERNEL M_WAITOK +#define __GFP_ZERO M_ZERO +#define kzalloc(size, flags) kmalloc(size, (flags) | __GFP_ZERO) + +#define memcpy_fromio(a, b, c) memcpy((a), (b), (c)) + +#define acpi_get_table AcpiGetTable + +#define PCI_DEVFN(bus, slot, func) ((((bus) & 0xff) << 8) | (((slot) & 0x1f) << 3) | ((func) & 0x07)) +#define PCI_SLOT(devfn) (((devfn) >> 3) & 0x1f) +#define PCI_FUNC(devfn) ((devfn) & 0x07) +#define PCI_BUS_NUM(devfn) (((devfn) >> 8) & 0xff) + +MALLOC_DECLARE(M_VMMDEV); + +typedef unsigned gfp_t; + +static inline gfp_t +linux_check_m_flags(gfp_t flags) +{ + const gfp_t m = M_NOWAIT | M_WAITOK; + + /* make sure either M_NOWAIT or M_WAITOK is set */ + if ((flags & m) == 0) + flags |= M_NOWAIT; + else if ((flags & m) == m) + flags &= ~M_WAITOK; + + /* mask away LinuxKPI specific flags */ + return (flags & GFP_NATIVE_MASK); +} + +static inline void * +kmalloc(size_t size, gfp_t flags) +{ + return (malloc(size, M_VMMDEV, linux_check_m_flags(flags))); +} + +static inline void +kfree(const void *ptr) +{ + free(__DECONST(void *, ptr), M_VMMDEV); +} + +static inline void * +kmemdup(const void *src, size_t len, gfp_t gfp) +{ + void *dst; + + dst = kmalloc(len, gfp); + if (dst != NULL) { + memcpy(dst, src, len); + } + return (dst); +} + +struct device { + device_t bsddev; +}; + +struct pci_dev { + struct device dev; + uint16_t device; + uint16_t vendor; + unsigned int devfn; +}; +struct amdgpu_device { + struct pci_dev *pdev; + uint8_t *bios; + uint32_t bios_size; +}; + +/* + * BIOS. + */ + +#define AMD_VBIOS_SIGNATURE " 761295520" +#define AMD_VBIOS_SIGNATURE_OFFSET 0x30 +#define AMD_VBIOS_SIGNATURE_SIZE sizeof(AMD_VBIOS_SIGNATURE) +#define AMD_VBIOS_SIGNATURE_END (AMD_VBIOS_SIGNATURE_OFFSET + AMD_VBIOS_SIGNATURE_SIZE) +#define AMD_IS_VALID_VBIOS(p) ((p)[0] == 0x55 && (p)[1] == 0xAA) +#define AMD_VBIOS_LENGTH(p) ((p)[2] << 9) + +/* Check if current bios is an ATOM BIOS. + * Return true if it is ATOM BIOS. Otherwise, return false. + */ +static bool check_atom_bios(uint8_t *bios, size_t size) +{ + uint16_t tmp, bios_header_start; + + if (!bios || size < 0x49) { + return false; + } + + if (!AMD_IS_VALID_VBIOS(bios)) { + return false; + } + + bios_header_start = bios[0x48] | (bios[0x49] << 8); + if (!bios_header_start) { + return false; + } + + tmp = bios_header_start + 4; + if (size < tmp) { + return false; + } + + if (!memcmp(bios + tmp, "ATOM", 4) || + !memcmp(bios + tmp, "MOTA", 4)) { + return true; + } + + return false; +} + +#define pci_map_rom(pdev, sizep) \ + vga_pci_map_bios(pdev->dev.bsddev, sizep) +#define pci_unmap_rom(pdev, bios) \ + vga_pci_unmap_bios(pdev->dev.bsddev, bios) + +static +bool amdgpu_read_bios(struct amdgpu_device *adev) +{ + uint8_t __iomem *bios; + size_t size; + + adev->bios = NULL; + /* XXX: some cards may return 0 for rom size? ddx has a workaround */ + bios = pci_map_rom(adev->pdev, &size); + if (!bios) { + return false; + } + + adev->bios = kzalloc(size, GFP_KERNEL); + if (adev->bios == NULL) { + pci_unmap_rom(adev->pdev, bios); + return false; + } + adev->bios_size = size; + memcpy_fromio(adev->bios, bios, size); + pci_unmap_rom(adev->pdev, bios); + + if (!check_atom_bios(adev->bios, size)) { + kfree(adev->bios); + return false; + } + + return true; +} + +static bool amdgpu_acpi_vfct_bios(struct amdgpu_device *adev) +{ + struct acpi_table_header *hdr; + acpi_size tbl_size; + UEFI_ACPI_VFCT *vfct; + unsigned offset; + + if (!ACPI_SUCCESS(acpi_get_table("VFCT", 1, &hdr))) + return false; + tbl_size = hdr->Length; + if (tbl_size < sizeof(UEFI_ACPI_VFCT)) { + DRM_ERROR("ACPI VFCT table present but broken (too short #1)\n"); + return false; + } + + vfct = (UEFI_ACPI_VFCT *)hdr; + offset = vfct->VBIOSImageOffset; + + while (offset < tbl_size) { + GOP_VBIOS_CONTENT *vbios = (GOP_VBIOS_CONTENT *)((char *)hdr + offset); + VFCT_IMAGE_HEADER *vhdr = &vbios->VbiosHeader; + + offset += sizeof(VFCT_IMAGE_HEADER); + if (offset > tbl_size) { + DRM_ERROR("ACPI VFCT image header truncated\n"); + return false; + } + + offset += vhdr->ImageLength; + if (offset > tbl_size) { + DRM_ERROR("ACPI VFCT image truncated\n"); + return false; + } + + if (vhdr->ImageLength && + vhdr->PCIBus == PCI_BUS_NUM(adev->pdev->devfn) && + vhdr->PCIDevice == PCI_SLOT(adev->pdev->devfn) && + vhdr->PCIFunction == PCI_FUNC(adev->pdev->devfn) && + vhdr->VendorID == adev->pdev->vendor && + vhdr->DeviceID == adev->pdev->device) { + adev->bios = kmemdup(&vbios->VbiosContent, + vhdr->ImageLength, + GFP_KERNEL); + + if (!check_atom_bios(adev->bios, vhdr->ImageLength)) { + kfree(adev->bios); + return false; + } + adev->bios_size = vhdr->ImageLength; + return true; + } + } + + DRM_ERROR("ACPI VFCT table present but broken (too short #2)\n"); + return false; +} + +static +bool amdgpu_get_bios(struct amdgpu_device *adev) +{ + if (amdgpu_acpi_vfct_bios(adev)) { + goto success; + } + + if (amdgpu_read_bios(adev)) { + goto success; + } + + DRM_ERROR("Unable to locate a BIOS ROM\n"); + return false; + +success: + return true; +} + +int +vm_amdgpu_get_vbios(struct vm *vm, int bus, int slot, int func, + uint16_t vendor, uint16_t dev_id, void *bios, uint64_t *size) +{ + int error = 0; + + struct pci_dev pdev; + struct amdgpu_device adev; + + adev.pdev = &pdev; + pdev.dev.bsddev = pci_find_bsf(bus, slot, func); + pdev.devfn = PCI_DEVFN(bus, slot, func); + pdev.vendor = vendor; + pdev.device = dev_id; + + if (!amdgpu_get_bios(&adev)) + return ENOENT; + + if (bios) { + *size = min(adev.bios_size, *size); + error = copyout(adev.bios, bios, *size); + } else if (size) { + *size = adev.bios_size; + } + + kfree(adev.bios); + + return (error); +} diff --git a/sys/amd64/vmm/amd/atombios.h b/sys/amd64/vmm/amd/atombios.h new file mode 100644 --- /dev/null +++ b/sys/amd64/vmm/amd/atombios.h @@ -0,0 +1,80 @@ +/* + * Copyright 2006-2007 Advanced Micro Devices, Inc. + * Copyright 2021 Beckhoff Automation GmbH & Co. KG + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * This file is a modified copy of + */ + +/****************************************************************************/ +/*Portion I: Definitions shared between VBIOS and Driver */ +/****************************************************************************/ + +#pragma once + +typedef unsigned long ULONG; +typedef unsigned char UCHAR; +typedef unsigned short USHORT; + +// +// AMD ACPI Table +// +#pragma pack(1) + +typedef struct { + ULONG Signature; + ULONG TableLength; //Length + UCHAR Revision; + UCHAR Checksum; + UCHAR OemId[6]; + UCHAR OemTableId[8]; //UINT64 OemTableId; + ULONG OemRevision; + ULONG CreatorId; + ULONG CreatorRevision; +} AMD_ACPI_DESCRIPTION_HEADER; + +typedef struct { + AMD_ACPI_DESCRIPTION_HEADER SHeader; + UCHAR TableUUID[16]; //0x24 + ULONG VBIOSImageOffset; //0x34. Offset to the first GOP_VBIOS_CONTENT block from the beginning of the stucture. + ULONG Lib1ImageOffset; //0x38. Offset to the first GOP_LIB1_CONTENT block from the beginning of the stucture. + ULONG Reserved[4]; //0x3C +}UEFI_ACPI_VFCT; + +typedef struct { + ULONG PCIBus; //0x4C + ULONG PCIDevice; //0x50 + ULONG PCIFunction; //0x54 + USHORT VendorID; //0x58 + USHORT DeviceID; //0x5A + USHORT SSVID; //0x5C + USHORT SSID; //0x5E + ULONG Revision; //0x60 + ULONG ImageLength; //0x64 +}VFCT_IMAGE_HEADER; + +typedef struct { + VFCT_IMAGE_HEADER VbiosHeader; + UCHAR VbiosContent[1]; +}GOP_VBIOS_CONTENT; + +#pragma pack() diff --git a/sys/amd64/vmm/vmm.c b/sys/amd64/vmm/vmm.c --- a/sys/amd64/vmm/vmm.c +++ b/sys/amd64/vmm/vmm.c @@ -134,7 +134,7 @@ bool sysmem; struct vm_object *object; }; -#define VM_MAX_MEMSEGS 3 +#define VM_MAX_MEMSEGS 4 struct mem_map { vm_paddr_t gpa; diff --git a/sys/amd64/vmm/vmm_dev.c b/sys/amd64/vmm/vmm_dev.c --- a/sys/amd64/vmm/vmm_dev.c +++ b/sys/amd64/vmm/vmm_dev.c @@ -60,6 +60,7 @@ #include #include +#include "amd/amdgpu_bios.h" #include "vmm_lapic.h" #include "vmm_stat.h" #include "vmm_mem.h" @@ -366,6 +367,7 @@ struct vm_capability *vmcap; struct vm_pptdev *pptdev; struct vm_pptdev_mmio *pptmmio; + struct vm_vbios *vbios; struct vm_pptdev_msi *pptmsi; struct vm_pptdev_msix *pptmsix; struct vm_nmi *vmnmi; @@ -533,6 +535,13 @@ error = ppt_unmap_mmio(sc->vm, pptmmio->bus, pptmmio->slot, pptmmio->func, pptmmio->gpa, pptmmio->len); break; + case VM_GET_VBIOS: + vbios = (struct vm_vbios *)data; + /* currently only amd cpus are supported */ + error = vm_amdgpu_get_vbios(sc->vm, vbios->bus, vbios->slot, + vbios->func, vbios->vendor, vbios->dev_id, vbios->bios, + &vbios->size); + break; case VM_BIND_PPTDEV: pptdev = (struct vm_pptdev *)data; error = vm_assign_pptdev(sc->vm, pptdev->bus, pptdev->slot, diff --git a/sys/dev/pci/pcireg.h b/sys/dev/pci/pcireg.h --- a/sys/dev/pci/pcireg.h +++ b/sys/dev/pci/pcireg.h @@ -1098,3 +1098,9 @@ #define PCIM_OSC_CTL_PCIE_PME 0x04 /* PCIe Native Power Mgt Events */ #define PCIM_OSC_CTL_PCIE_AER 0x08 /* PCIe Advanced Error Reporting */ #define PCIM_OSC_CTL_PCIE_CAP_STRUCT 0x10 /* Various Capability Structures */ + +/* + * PCI Vendors + */ +#define PCI_VENDOR_INTEL 0x8086 +#define PCI_VENDOR_AMD 0x1002 diff --git a/sys/modules/vmm/Makefile b/sys/modules/vmm/Makefile --- a/sys/modules/vmm/Makefile +++ b/sys/modules/vmm/Makefile @@ -58,6 +58,7 @@ svm_support.S \ npt.c \ ivrs_drv.c \ + amdgpu_bios.c \ amdvi_hw.c \ svm_msr.c diff --git a/usr.sbin/bhyve/Makefile b/usr.sbin/bhyve/Makefile --- a/usr.sbin/bhyve/Makefile +++ b/usr.sbin/bhyve/Makefile @@ -42,6 +42,7 @@ pci_emul.c \ pci_hda.c \ pci_fbuf.c \ + pci_gvt-d_amd.c \ pci_hostbridge.c \ pci_irq.c \ pci_lpc.c \ diff --git a/usr.sbin/bhyve/pci_emul.h b/usr.sbin/bhyve/pci_emul.h --- a/usr.sbin/bhyve/pci_emul.h +++ b/usr.sbin/bhyve/pci_emul.h @@ -42,6 +42,8 @@ #include #define PCI_BARMAX PCIR_MAX_BAR_0 /* BAR registers in a Type 0 header */ +#define PCI_BARMAX_WITH_ROM (PCI_BARMAX + 1) +#define PCI_ROM_IDX (PCI_BARMAX + 1) struct vmctx; struct pci_devinst; @@ -76,8 +78,8 @@ struct pci_devinst *pi, int baridx, uint64_t offset, int size); - void (*pe_baraddr)(struct vmctx *ctx, struct pci_devinst *pi, - int baridx, int enabled, uint64_t address); + int (*pe_baraddr)(struct vmctx *ctx, struct pci_devinst *pi, int baridx, + int enabled, uint64_t address); /* Save/restore device state */ int (*pe_snapshot)(struct vm_snapshot_meta *meta); @@ -92,13 +94,15 @@ PCIBAR_IO, PCIBAR_MEM32, PCIBAR_MEM64, - PCIBAR_MEMHI64 + PCIBAR_MEMHI64, + PCIBAR_ROM, }; struct pcibar { enum pcibar_type type; /* io or memory */ uint64_t size; uint64_t addr; + uint8_t lobits; }; #define PI_NAMESZ 40 @@ -164,7 +168,8 @@ void *pi_arg; /* devemu-private data */ u_char pi_cfgdata[PCI_REGMAX + 1]; - struct pcibar pi_bar[PCI_BARMAX + 1]; + /* ROM is handled like a BAR */ + struct pcibar pi_bar[PCI_BARMAX_WITH_ROM + 1]; }; struct msicap { diff --git a/usr.sbin/bhyve/pci_emul.c b/usr.sbin/bhyve/pci_emul.c --- a/usr.sbin/bhyve/pci_emul.c +++ b/usr.sbin/bhyve/pci_emul.c @@ -38,6 +38,7 @@ #include #include +#include #include #include #include @@ -73,6 +74,10 @@ #define MAXSLOTS (PCI_SLOTMAX + 1) #define MAXFUNCS (PCI_FUNCMAX + 1) +#define GB (1024 * 1024 * 1024UL) + +#define max(a, b) (((a) > (b)) ? (a) : (b)) + struct funcinfo { nvlist_t *fi_config; struct pci_devemu *fi_pde; @@ -102,10 +107,21 @@ SET_DECLARE(pci_devemu_set, struct pci_devemu); static uint64_t pci_emul_iobase; +static uint64_t pci_emul_iolim; static uint64_t pci_emul_membase32; +static uint64_t pci_emul_memlim32; static uint64_t pci_emul_membase64; static uint64_t pci_emul_memlim64; +struct pcibarlist { + struct pci_devinst *pdi; + int idx; + enum pcibar_type type; + uint64_t size; + struct pcibarlist *next; +}; +struct pcibarlist *pci_bars; + #define PCI_EMUL_IOBASE 0x2000 #define PCI_EMUL_IOLIMIT 0x10000 @@ -114,6 +130,7 @@ SYSRES_MEM(PCI_EMUL_ECFG_BASE, PCI_EMUL_ECFG_SIZE); #define PCI_EMUL_MEMLIMIT32 PCI_EMUL_ECFG_BASE +#define PCI_EMUL_MEMSIZE64 (32 * GB) static struct pci_devemu *pci_emul_finddev(const char *name); static void pci_lintr_route(struct pci_devinst *pi); @@ -512,6 +529,11 @@ struct mem_range mr; pe = pi->pi_d; + if (pe->pe_baraddr != NULL && + (*pe->pe_baraddr)( + pi->pi_vmctx, pi, idx, registration, pi->pi_bar[idx].addr) == 0) + return; + switch (pi->pi_bar[idx].type) { case PCIBAR_IO: bzero(&iop, sizeof(struct inout_port)); @@ -525,9 +547,6 @@ error = register_inout(&iop); } else error = unregister_inout(&iop); - if (pe->pe_baraddr != NULL) - (*pe->pe_baraddr)(pi->pi_vmctx, pi, idx, registration, - pi->pi_bar[idx].addr); break; case PCIBAR_MEM32: case PCIBAR_MEM64: @@ -543,9 +562,13 @@ error = register_mem(&mr); } else error = unregister_mem(&mr); - if (pe->pe_baraddr != NULL) - (*pe->pe_baraddr)(pi->pi_vmctx, pi, idx, registration, - pi->pi_bar[idx].addr); + break; + case PCIBAR_ROM: + /* ROM emulation should be handled by pe_baraddr */ + if (pi->pi_bar[idx].addr != 0) + error = EFAULT; + else + error = 0; break; default: error = EINVAL; @@ -597,8 +620,9 @@ * the address range decoded by the BAR register. */ static void -update_bar_address(struct pci_devinst *pi, uint64_t addr, int idx, int type) +update_bar_address(struct pci_devinst *pi, int idx, uint32_t val) { + int update_idx = idx; int decode; if (pi->pi_bar[idx].type == PCIBAR_IO) @@ -606,51 +630,211 @@ else decode = memen(pi); - if (decode) - unregister_bar(pi, idx); - - switch (type) { + switch (pi->pi_bar[idx].type) { + case PCIBAR_MEMHI64: + --update_idx; case PCIBAR_IO: case PCIBAR_MEM32: - pi->pi_bar[idx].addr = addr; + case PCIBAR_MEM64: { + struct pcibar *bar = &pi->pi_bar[update_idx]; + + if (decode && bar->addr) + unregister_bar(pi, update_idx); + + if (val == ~0U) { + /* guest wants to read size of BAR */ + pci_set_cfgdata32(pi, PCIR_BAR(idx), ~0U); + bar->addr = 0; + break; + } + + /* guest sets address of BAR */ + uint64_t mask; + uint32_t bar_val; + mask = ~(bar->size - 1UL); + if (pi->pi_bar[idx].type == PCIBAR_MEMHI64) + mask >>= 32UL; + bar_val = val & mask; + bar_val |= pi->pi_bar[idx].lobits; + pci_set_cfgdata32(pi, PCIR_BAR(idx), bar_val); + + /* Only register BAR if it contains a valid address */ + uint32_t lo, hi; + lo = pci_get_cfgdata32(pi, PCIR_BAR(update_idx)); + hi = 0; + if (bar->type == PCIBAR_MEM64) + hi = pci_get_cfgdata32(pi, PCIR_BAR(update_idx + 1)); + if (lo == ~0U || hi == ~0U) { + bar->addr = 0; + break; + } + + if (bar->type == PCIBAR_IO) + lo &= PCIM_BAR_IO_BASE; + else + lo &= PCIM_BAR_MEM_BASE; + bar->addr = (uint64_t)lo | ((uint64_t)hi << 32UL); + if (decode) + register_bar(pi, update_idx); + break; - case PCIBAR_MEM64: - pi->pi_bar[idx].addr &= ~0xffffffffUL; - pi->pi_bar[idx].addr |= addr; + } + case PCIBAR_ROM: { + struct pcibar *bar = &pi->pi_bar[update_idx]; + + if (decode && bar->lobits && bar->addr) + unregister_bar(pi, idx); + + pci_set_cfgdata32(pi, PCIR_BIOS, val); + + /* Update enable bit */ + bar->lobits = val & PCIM_BIOS_ENABLE; + + /* Update ROM location */ + if ((val & PCIM_BIOS_ADDR_MASK) == PCIM_BIOS_ADDR_MASK) { + /* guest wants to read size of ROM */ + bar->addr = 0; + } else { + bar->addr = val & PCIM_BIOS_ADDR_MASK; + } + + if (decode && bar->lobits && bar->addr) + register_bar(pi, idx); + + break; + } + case PCIBAR_NONE: break; + default: + assert(0); + } +} + +static uint32_t +read_bar_value(struct pci_devinst *pi, int coff, int bytes) +{ + uint8_t idx; + if (coff >= PCIR_BAR(0) && coff < PCIR_BAR(PCI_BARMAX + 1)) { + idx = (coff - PCIR_BAR(0)) / 4; + } else if (coff >= PCIR_BIOS && coff < PCIR_BIOS + 4) { + idx = PCI_BARMAX + 1; + } else { + warnx("%02x is no BAR", coff); + return 0; + } + + uint8_t update_idx = idx; + uint64_t val; + + switch (pi->pi_bar[idx].type) { case PCIBAR_MEMHI64: - pi->pi_bar[idx].addr &= 0xffffffff; - pi->pi_bar[idx].addr |= addr; + --update_idx; + /* fallthrough */ + case PCIBAR_IO: + case PCIBAR_MEM32: + case PCIBAR_MEM64: + val = pci_get_cfgdata32(pi, PCIR_BAR(idx)); + break; + case PCIBAR_ROM: + val = pci_get_cfgdata32(pi, PCIR_BIOS); + /* check if size should be returned instead of address of ROM */ + if ((val & PCIM_BIOS_ADDR_MASK) == PCIM_BIOS_ADDR_MASK) + val = ~0U; + break; + case PCIBAR_NONE: + return 0; + default: + warnx("%x is no valid BAR type", pi->pi_bar[idx].type); + return 0; + } + + /* return size of BAR */ + if (val == ~0U) { + val = ~(pi->pi_bar[update_idx].size - 1); + val |= pi->pi_bar[update_idx].lobits; + if (pi->pi_bar[idx].type == PCIBAR_MEMHI64) + val >>= 32; + } + + switch (bytes) { + case 1: + val = (val >> (8 * (coff & 0x03))) & 0xFF; + break; + case 2: + assert((coff & 0x01) == 0); + val = (val >> (8 * (coff & 0x02))) & 0xFFFF; + break; + case 4: + assert((coff & 0x03) == 0); + val = (uint32_t)val; break; default: assert(0); } - if (decode) - register_bar(pi, idx); + return val; } int pci_emul_alloc_bar(struct pci_devinst *pdi, int idx, enum pcibar_type type, uint64_t size) { - int error; - uint64_t *baseptr, limit, addr, mask, lobits, bar; - uint16_t cmd, enbit; - - assert(idx >= 0 && idx <= PCI_BARMAX); + if ((type != PCIBAR_ROM) && (idx < 0 || idx > PCI_BARMAX)) { + errx(4, "Illegal BAR idx"); + } else if ((type == PCIBAR_ROM) && (idx != PCI_ROM_IDX)) { + errx(4, "Illegal ROM idx"); + } if ((size & (size - 1)) != 0) size = 1UL << flsl(size); /* round up to a power of 2 */ /* Enforce minimum BAR sizes required by the PCI standard */ if (type == PCIBAR_IO) { - if (size < 4) - size = 4; + size = max(size, 4); + } else if (type == PCIBAR_ROM) { + size = max(size, ~PCIM_BIOS_ADDR_MASK); } else { - if (size < 16) - size = 16; + size = max(size, 16); + } + + struct pcibarlist *newBar = malloc(sizeof(struct pcibarlist)); + memset(newBar, 0, sizeof(struct pcibarlist)); + newBar->pdi = pdi; + newBar->idx = idx; + newBar->type = type; + newBar->size = size; + if (pci_bars == NULL) { + /* first BAR */ + pci_bars = newBar; + } else { + struct pcibarlist *bar = pci_bars; + struct pcibarlist *lastBar = NULL; + do { + if (bar->size < size) + break; + lastBar = bar; + bar = bar->next; + } while (bar != NULL); + newBar->next = bar; + if (lastBar != NULL) + lastBar->next = newBar; + else + pci_bars = newBar; } + return (0); +} + +static int +pci_emul_assign_bar(struct pcibarlist *pci_bar) +{ + struct pci_devinst *pdi = pci_bar->pdi; + int idx = pci_bar->idx; + enum pcibar_type type = pci_bar->type; + uint64_t size = pci_bar->size; + + int error; + uint64_t *baseptr, limit, addr, mask, lobits; + uint16_t cmd, enbit; switch (type) { case PCIBAR_NONE: @@ -659,7 +843,7 @@ break; case PCIBAR_IO: baseptr = &pci_emul_iobase; - limit = PCI_EMUL_IOLIMIT; + limit = pci_emul_iolim; mask = PCIM_BAR_IO_BASE; lobits = PCIM_BAR_IO_SPACE; enbit = PCIM_CMD_PORTEN; @@ -670,29 +854,41 @@ * Some drivers do not work well if the 64-bit BAR is allocated * above 4GB. Allow for this by allocating small requests under * 4GB unless then allocation size is larger than some arbitrary - * number (128MB currently). + * number (256MB currently). */ - if (size > 128 * 1024 * 1024) { + if (size > 256 * 1024 * 1024) { baseptr = &pci_emul_membase64; limit = pci_emul_memlim64; mask = PCIM_BAR_MEM_BASE; lobits = PCIM_BAR_MEM_SPACE | PCIM_BAR_MEM_64 | PCIM_BAR_MEM_PREFETCH; - } else { - baseptr = &pci_emul_membase32; - limit = PCI_EMUL_MEMLIMIT32; - mask = PCIM_BAR_MEM_BASE; - lobits = PCIM_BAR_MEM_SPACE | PCIM_BAR_MEM_64; + enbit = PCIM_CMD_MEMEN; + break; } - enbit = PCIM_CMD_MEMEN; - break; + /* + * Use 32 bit BARs for small requests: + * Fallthrough into MEM32 case + */ + type = PCIBAR_MEM32; + pdi->pi_bar[idx + 1].type = PCIBAR_NONE; + /* clear 64-bit flag */ + pdi->pi_bar[idx].lobits &= ~PCIM_BAR_MEM_64; + /* [fallthrough] */ case PCIBAR_MEM32: baseptr = &pci_emul_membase32; - limit = PCI_EMUL_MEMLIMIT32; + limit = pci_emul_memlim32; mask = PCIM_BAR_MEM_BASE; lobits = PCIM_BAR_MEM_SPACE | PCIM_BAR_MEM_32; enbit = PCIM_CMD_MEMEN; break; + case PCIBAR_ROM: + /* do not claim memory for ROM. OVMF will do it for us. */ + baseptr = NULL; + limit = 0; + mask = PCIM_BIOS_ADDR_MASK; + lobits = 0; + enbit = PCIM_CMD_MEMEN; + break; default: printf("pci_emul_alloc_base: invalid bar type %d\n", type); assert(0); @@ -705,23 +901,29 @@ } pdi->pi_bar[idx].type = type; - pdi->pi_bar[idx].addr = addr; + pdi->pi_bar[idx].addr = 0; pdi->pi_bar[idx].size = size; + /* passthru devices are using same lobits as physical device + * they set this property + */ + if (pdi->pi_bar[idx].lobits != 0) + lobits = pdi->pi_bar[idx].lobits; + else + pdi->pi_bar[idx].lobits = lobits; - /* Initialize the BAR register in config space */ - bar = (addr & mask) | lobits; - pci_set_cfgdata32(pdi, PCIR_BAR(idx), bar); + /* Initialize CMD register in config space */ + cmd = pci_get_cfgdata16(pdi, PCIR_COMMAND); + if ((cmd & enbit) != enbit) + pci_set_cfgdata16(pdi, PCIR_COMMAND, cmd | enbit); + /* Initialize the BAR register in config space */ if (type == PCIBAR_MEM64) { assert(idx + 1 <= PCI_BARMAX); pdi->pi_bar[idx + 1].type = PCIBAR_MEMHI64; - pci_set_cfgdata32(pdi, PCIR_BAR(idx + 1), bar >> 32); + update_bar_address(pdi, idx + 1, addr); } - cmd = pci_get_cfgdata16(pdi, PCIR_COMMAND); - if ((cmd & enbit) != enbit) - pci_set_cfgdata16(pdi, PCIR_COMMAND, cmd | enbit); - register_bar(pdi, idx); + update_bar_address(pdi, idx, addr); return (0); } @@ -1134,6 +1336,9 @@ #define BUSIO_ROUNDUP 32 #define BUSMEM_ROUNDUP (1024 * 1024) +#define ALIGN_VALUE(Value, Alignment) \ + ((Value) + (((Alignment) - (Value)) & ((Alignment)-1))) + int init_pci(struct vmctx *ctx) { @@ -1146,25 +1351,18 @@ nvlist_t *nvl; const char *emul; size_t lowmem; - uint64_t cpu_maxphysaddr, pci_emul_memresv64; - u_int regs[4]; int bus, slot, func, error; pci_emul_iobase = PCI_EMUL_IOBASE; + pci_emul_iolim = PCI_EMUL_IOLIMIT; + pci_emul_membase32 = vm_get_lowmem_limit(ctx); + pci_emul_memlim32 = PCI_EMUL_MEMLIMIT32; - do_cpuid(0x80000008, regs); - cpu_maxphysaddr = 1ULL << (regs[0] & 0xff); - if (cpu_maxphysaddr > VM_MAXUSER_ADDRESS_LA48) - cpu_maxphysaddr = VM_MAXUSER_ADDRESS_LA48; - pci_emul_memresv64 = cpu_maxphysaddr / 4; - /* - * Max power of 2 that is less then - * cpu_maxphysaddr - pci_emul_memresv64. - */ - pci_emul_membase64 = 1ULL << (flsl(cpu_maxphysaddr - - pci_emul_memresv64) - 1); - pci_emul_memlim64 = cpu_maxphysaddr; + pci_emul_membase64 = 4 * GB + vm_get_highmem_size(ctx); + pci_emul_membase64 = ALIGN_VALUE( + pci_emul_membase64, PCI_EMUL_MEMSIZE64); + pci_emul_memlim64 = pci_emul_membase64 + PCI_EMUL_MEMSIZE64; for (bus = 0; bus < MAXBUSES; bus++) { snprintf(node_name, sizeof(node_name), "pci.%d", bus); @@ -1182,6 +1380,7 @@ bi->membase32 = pci_emul_membase32; bi->membase64 = pci_emul_membase64; + /* first run: init devices */ for (slot = 0; slot < MAXSLOTS; slot++) { si = &bi->slotinfo[slot]; for (func = 0; func < MAXFUNCS; func++) { @@ -1221,6 +1420,15 @@ } } + /* second run: assign BARs and free BAR list */ + struct pcibarlist *bar = pci_bars; + while (bar != NULL) { + pci_emul_assign_bar(bar); + struct pcibarlist *old = bar; + bar = bar->next; + free(old); + } + /* * Add some slop to the I/O and memory resources decoded by * this bus to give a guest some flexibility if it wants to @@ -1794,7 +2002,7 @@ * If the MMIO or I/O address space decoding has changed then * register/unregister all BARs that decode that address space. */ - for (i = 0; i <= PCI_BARMAX; i++) { + for (i = 0; i <= PCI_BARMAX_WITH_ROM; i++) { switch (pi->pi_bar[i].type) { case PCIBAR_NONE: case PCIBAR_MEMHI64: @@ -1808,6 +2016,11 @@ unregister_bar(pi, i); } break; + case PCIBAR_ROM: + /* skip (un-)register of ROM if it not enabled + */ + if (pi->pi_bar[i].lobits == 0) + break; case PCIBAR_MEM32: case PCIBAR_MEM64: /* MMIO address space decoding changed? */ @@ -1865,7 +2078,6 @@ struct pci_devinst *pi; struct pci_devemu *pe; int idx, needcfg; - uint64_t addr, bar, mask; if ((bi = pci_businfo[bus]) != NULL) { si = &bi->slotinfo[slot]; @@ -1917,8 +2129,15 @@ needcfg = 1; } - if (needcfg) - *eax = CFGREAD(pi, coff, bytes); + if (needcfg) { + if ((coff >= PCIR_BAR(0) && + coff <= PCIR_BAR(PCI_BARMAX)) || + (coff >= PCIR_BIOS && coff < PCIR_BIOS + 4)) { + *eax = read_bar_value(pi, coff, bytes); + } else { + *eax = CFGREAD(pi, coff, bytes); + } + } pci_emul_hdrtype_fixup(bus, slot, coff, bytes, eax); } else { @@ -1928,64 +2147,25 @@ return; /* - * Special handling for write to BAR registers + * Special handling for write to BAR and ROM registers */ - if (coff >= PCIR_BAR(0) && coff < PCIR_BAR(PCI_BARMAX + 1)) { + if ((coff >= PCIR_BAR(0) && coff <= PCIR_BAR(PCI_BARMAX)) || + (coff >= PCIR_BIOS && coff < PCIR_BIOS + 4)) { /* * Ignore writes to BAR registers that are not * 4-byte aligned. */ if (bytes != 4 || (coff & 0x3) != 0) return; - idx = (coff - PCIR_BAR(0)) / 4; - mask = ~(pi->pi_bar[idx].size - 1); - switch (pi->pi_bar[idx].type) { - case PCIBAR_NONE: - pi->pi_bar[idx].addr = bar = 0; - break; - case PCIBAR_IO: - addr = *eax & mask; - addr &= 0xffff; - bar = addr | PCIM_BAR_IO_SPACE; - /* - * Register the new BAR value for interception - */ - if (addr != pi->pi_bar[idx].addr) { - update_bar_address(pi, addr, idx, - PCIBAR_IO); - } - break; - case PCIBAR_MEM32: - addr = bar = *eax & mask; - bar |= PCIM_BAR_MEM_SPACE | PCIM_BAR_MEM_32; - if (addr != pi->pi_bar[idx].addr) { - update_bar_address(pi, addr, idx, - PCIBAR_MEM32); - } - break; - case PCIBAR_MEM64: - addr = bar = *eax & mask; - bar |= PCIM_BAR_MEM_SPACE | PCIM_BAR_MEM_64 | - PCIM_BAR_MEM_PREFETCH; - if (addr != (uint32_t)pi->pi_bar[idx].addr) { - update_bar_address(pi, addr, idx, - PCIBAR_MEM64); - } - break; - case PCIBAR_MEMHI64: - mask = ~(pi->pi_bar[idx - 1].size - 1); - addr = ((uint64_t)*eax << 32) & mask; - bar = addr >> 32; - if (bar != pi->pi_bar[idx - 1].addr >> 32) { - update_bar_address(pi, addr, idx - 1, - PCIBAR_MEMHI64); - } - break; - default: - assert(0); - } - pci_set_cfgdata32(pi, coff, bar); - + /* + * coff is equal to PCIR_BIOS on ROM writes because + * it's 4-byte aligned + */ + if (coff == PCIR_BIOS) + idx = PCI_ROM_IDX; + else + idx = (coff - PCIR_BAR(0)) / 4; + update_bar_address(pi, idx, *eax); } else if (pci_emul_iscap(pi, coff)) { pci_emul_capwrite(pi, coff, bytes, *eax, 0, 0); } else if (coff >= PCIR_COMMAND && coff < PCIR_REVID) { diff --git a/usr.sbin/bhyve/pci_fbuf.c b/usr.sbin/bhyve/pci_fbuf.c --- a/usr.sbin/bhyve/pci_fbuf.c +++ b/usr.sbin/bhyve/pci_fbuf.c @@ -216,15 +216,15 @@ return (value); } -static void +static int pci_fbuf_baraddr(struct vmctx *ctx, struct pci_devinst *pi, int baridx, - int enabled, uint64_t address) + int enabled, uint64_t address) { struct pci_fbuf_softc *sc; int prot; if (baridx != 1) - return; + return (-1); sc = pi->pi_arg; if (!enabled && sc->fbaddr != 0) { @@ -237,6 +237,8 @@ EPRINTLN("pci_fbuf: mmap_memseg failed"); sc->fbaddr = address; } + + return (0); } @@ -375,7 +377,7 @@ static int pci_fbuf_init(struct vmctx *ctx, struct pci_devinst *pi, nvlist_t *nvl) { - int error, prot; + int error; struct pci_fbuf_softc *sc; if (fbuf_sc != NULL) { @@ -393,6 +395,13 @@ pci_set_cfgdata8(pi, PCIR_CLASS, PCIC_DISPLAY); pci_set_cfgdata8(pi, PCIR_SUBCLASS, PCIS_DISPLAY_VGA); + sc->fb_base = vm_create_devmem( + ctx, VM_FRAMEBUFFER, "framebuffer", FB_SIZE); + if (sc->fb_base == MAP_FAILED) { + error = -1; + goto done; + } + error = pci_emul_alloc_bar(pi, 0, PCIBAR_MEM32, DMEMSZ); assert(error == 0); @@ -402,7 +411,6 @@ error = pci_emul_add_msicap(pi, PCI_FBUF_MSI_MSGS); assert(error == 0); - sc->fbaddr = pi->pi_bar[1].addr; sc->memregs.fbsize = FB_SIZE; sc->memregs.width = COLS_DEFAULT; sc->memregs.height = ROWS_DEFAULT; @@ -423,27 +431,9 @@ goto done; } - sc->fb_base = vm_create_devmem(ctx, VM_FRAMEBUFFER, "framebuffer", FB_SIZE); - if (sc->fb_base == MAP_FAILED) { - error = -1; - goto done; - } DPRINTF(DEBUG_INFO, ("fbuf frame buffer base: %p [sz %lu]", sc->fb_base, FB_SIZE)); - /* - * Map the framebuffer into the guest address space. - * XXX This may fail if the BAR is different than a prior - * run. In this case flag the error. This will be fixed - * when a change_memseg api is available. - */ - prot = PROT_READ | PROT_WRITE; - if (vm_mmap_memseg(ctx, sc->fbaddr, VM_FRAMEBUFFER, 0, FB_SIZE, prot) != 0) { - EPRINTLN("pci_fbuf: mapseg failed - try deleting VM and restarting"); - error = -1; - goto done; - } - console_init(sc->memregs.width, sc->memregs.height, sc->fb_base); console_fb_register(pci_fbuf_render, sc); diff --git a/usr.sbin/bhyve/pci_gvt-d_amd.c b/usr.sbin/bhyve/pci_gvt-d_amd.c new file mode 100644 --- /dev/null +++ b/usr.sbin/bhyve/pci_gvt-d_amd.c @@ -0,0 +1,190 @@ +/*- + * 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 + +#include "config.h" +#include "debug.h" +#include "pci_passthru.h" + +#define max(a, b) (((a) > (b)) ? (a) : (b)) + +static void +gvt_d_amd_usage(char *opt) { + EPRINTLN("Invalid gvt-d amd passthru option \"%s\"", opt); + EPRINTLN("passthru: {rom=rom_file}"); +} + +static int +gvt_d_amd_parse_opts(struct passthru_softc *sc, nvlist_t *nvl) +{ + const char *value; + /* parse rom file */ + value = get_config_value_node(nvl, "rom"); + if (value != NULL) { + const int fd = open(value, O_RDONLY); + if (fd < 0) { + return (-1); + } + /* determine file size */ + uint64_t bios_size = lseek(fd, 0, SEEK_END); + lseek(fd, 0, SEEK_SET); + /* read bios */ + void *bios_addr = malloc(bios_size); + if (bios_addr == NULL) { + close(fd); + return (-ENOMEM); + } + bios_size = read(fd, bios_addr, bios_size); + close(fd); + + /* save physical values of ROM */ + sc->psc_bar[PCI_ROM_IDX].type = PCIBAR_ROM; + sc->psc_bar[PCI_ROM_IDX].addr = (uint64_t)bios_addr; + sc->psc_bar[PCI_ROM_IDX].size = bios_size; + } + + return (0); +} + +int +gvt_d_amd_init(struct vmctx *ctx, struct pci_devinst *pi, nvlist_t *nvl) +{ + int error = 0; + + struct passthru_softc *sc = pi->pi_arg; + + if ((error = gvt_d_amd_parse_opts(sc, nvl))) + goto done; + + const uint16_t vendor = read_config(&sc->psc_sel, PCIR_VENDOR, 0x02); + const uint16_t dev_id = read_config(&sc->psc_sel, PCIR_DEVICE, 0x02); + + uint64_t bios_size; + if (sc->psc_bar[PCI_ROM_IDX].size == 0) { + /* get VBIOS size */ + if ((error = vm_get_vbios(ctx, sc->psc_sel.pc_bus, + sc->psc_sel.pc_dev, sc->psc_sel.pc_func, vendor, + dev_id, NULL, &bios_size)) != 0) { + warnx("vm_get_vbios: %x", errno); + goto done; + } + } else { + bios_size = sc->psc_bar[PCI_ROM_IDX].size; + } + if (bios_size == 0) { + error = ESRCH; + warnx("Could not determine VBIOS size"); + goto done; + } + + /* + * round up size to a power of two + * check in descendig order to avoid endless loop + */ + uint64_t rom_size = 1ULL << 63; + while (rom_size >= bios_size) { + rom_size >>= 1; + } + rom_size <<= 1; + /* ROM size should be greater than 2 KB */ + rom_size = max(rom_size, (~PCIM_BIOS_ADDR_MASK) + 1); + + /* Allocate VM Memory for VBIOS */ + const uint64_t rom_addr = (uint64_t)vm_create_devmem( + ctx, VM_VIDEOBIOS, "videobios", rom_size); + if ((void *)rom_addr == MAP_FAILED) { + error = ENOMEM; + warnx("vm_create_devmem: %x", errno); + goto done; + } + + /* get VBIOS */ + if (sc->psc_bar[PCI_ROM_IDX].addr == 0) { + if ((error = vm_get_vbios(ctx, sc->psc_sel.pc_bus, + sc->psc_sel.pc_dev, sc->psc_sel.pc_func, vendor, + dev_id, (void *)rom_addr, &bios_size)) != 0) { + warnx("vm_get_vbios: %x", errno); + goto done; + } + } else { + memcpy((void *)rom_addr, (void *)sc->psc_bar[PCI_ROM_IDX].addr, + bios_size); + free((void *)sc->psc_bar[PCI_ROM_IDX].addr); + } + + /* assign a ROM to this device */ + if ((error = pci_emul_alloc_bar( + pi, PCI_ROM_IDX, PCIBAR_ROM, rom_size)) != 0) { + warnx("pci_emul_alloc_rom: %x", error); + goto done; + } + + /* save physical values of ROM */ + sc->psc_bar[PCI_ROM_IDX].type = PCIBAR_ROM; + sc->psc_bar[PCI_ROM_IDX].addr = rom_addr; + sc->psc_bar[PCI_ROM_IDX].size = bios_size; + +done: + return (error); +} + +int +gvt_d_amd_addr_rom(struct pci_devinst *pi, int idx, int enabled) +{ + int error; + + if (!enabled) + error = vm_munmap_memseg( + pi->pi_vmctx, pi->pi_bar[idx].addr, pi->pi_bar[idx].size); + else + error = vm_mmap_memseg(pi->pi_vmctx, pi->pi_bar[idx].addr, + VM_VIDEOBIOS, 0, pi->pi_bar[idx].size, + PROT_READ | PROT_EXEC); + + return error; +} diff --git a/usr.sbin/bhyve/pci_passthru.h b/usr.sbin/bhyve/pci_passthru.h new file mode 100644 --- /dev/null +++ b/usr.sbin/bhyve/pci_passthru.h @@ -0,0 +1,81 @@ +/*- + * 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$ + */ + +#pragma once + +#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; + /* ROM is handled like a BAR */ + struct pcibar psc_bar[PCI_BARMAX_WITH_ROM + 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]; + uint8_t psc_pcir_prot_map[(PCI_REGMAX + 1) / 4]; +}; + +#define PT_MAP_PPTDEV_MMIO 1 +#define PT_UNMAP_PPTDEV_MMIO 0 + +#define PPT_PCIR_PROT_NA 0 /* No Access to physical values */ +#define PPT_PCIR_PROT_RO 1 /* Read Only access to physical values */ +#define PPT_PCIR_PROT_WO 2 /* Write Only access to physical values */ +#define PPT_PCIR_PROT_RW \ + (PPT_PCIR_PROT_RO | \ + PPT_PCIR_PROT_WO) /* Read/Write access to physical values */ +#define PPT_PCIR_PROT_MASK 0x03 + +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 set_pcir_prot( + struct passthru_softc *sc, uint32_t reg, uint32_t len, uint8_t prot); +int gvt_d_amd_init(struct vmctx *ctx, struct pci_devinst *pi, nvlist_t *nvl); +int gvt_d_amd_addr_rom(struct pci_devinst *pi, int idx, int enabled); diff --git a/usr.sbin/bhyve/pci_passthru.c b/usr.sbin/bhyve/pci_passthru.c --- a/usr.sbin/bhyve/pci_passthru.c +++ b/usr.sbin/bhyve/pci_passthru.c @@ -48,22 +48,23 @@ #ifndef WITHOUT_CAPSICUM #include #endif -#include -#include -#include +#include + #include #include #include +#include +#include +#include #include #include #include -#include #include "config.h" #include "debug.h" -#include "pci_emul.h" #include "mem.h" +#include "pci_passthru.h" #ifndef _PATH_DEVPCI #define _PATH_DEVPCI "/dev/pci" @@ -86,20 +87,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) { @@ -122,7 +109,7 @@ return (len); } -static uint32_t +uint32_t read_config(const struct pcisel *sel, long reg, int width) { struct pci_io pi; @@ -138,7 +125,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; @@ -152,6 +139,20 @@ (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); +} + #ifdef LEGACY_SUPPORT static int passthru_add_msicap(struct pci_devinst *pi, int msgnum, int nextptr) @@ -559,12 +560,23 @@ 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_bar(pi, i, bartype, size); if (error) return (-1); + /* Use same lobits as physical bar */ + uint8_t lobits = read_config(&sc->psc_sel, PCIR_BAR(i), 0x01); + if (bartype == PCIBAR_MEM32 || bartype == PCIBAR_MEM64) { + lobits &= ~PCIM_BAR_MEM_BASE; + } else { + 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); @@ -610,24 +622,109 @@ goto done; } - pci_set_cfgdata16(pi, PCIR_COMMAND, read_config(&sc->psc_sel, - PCIR_COMMAND, 2)); + write_config( + &sc->psc_sel, PCIR_COMMAND, 2, pci_get_cfgdata16(pi, PCIR_COMMAND)); error = 0; /* success */ done: return (error); } +#define PPT_PCIR_PROT(reg) \ + ((sc->psc_pcir_prot_map[reg / 4] >> (reg & 0x03)) & PPT_PCIR_PROT_MASK) + +int +set_pcir_prot( + struct passthru_softc *sc, uint32_t reg, uint32_t len, uint8_t prot) +{ + if (reg > PCI_REGMAX || reg + len > PCI_REGMAX + 1) + return (-1); + + prot &= PPT_PCIR_PROT_MASK; + + for (int i = reg; i < reg + len; ++i) { + /* delete old prot value */ + sc->psc_pcir_prot_map[i / 4] &= ~( + PPT_PCIR_PROT_MASK << (i & 0x03)); + /* set new prot value */ + sc->psc_pcir_prot_map[i / 4] |= prot << (i & 0x03); + } + + return (0); +} + +static int +is_pcir_writable(struct passthru_softc *sc, uint32_t reg) +{ + if (reg > PCI_REGMAX) + return (0); + + return ((PPT_PCIR_PROT(reg) & PPT_PCIR_PROT_WO) != 0); +} + +static int +is_pcir_readable(struct passthru_softc *sc, uint32_t reg) +{ + if (reg > PCI_REGMAX) + return (0); + + return ((PPT_PCIR_PROT(reg) & PPT_PCIR_PROT_RO) != 0); +} + +static int +passthru_init_quirks(struct vmctx *ctx, struct pci_devinst *pi, nvlist_t *nvl) +{ + struct passthru_softc *sc = pi->pi_arg; + + uint16_t vendor = read_config(&sc->psc_sel, PCIR_VENDOR, 0x02); + uint8_t class = read_config(&sc->psc_sel, PCIR_CLASS, 0x01); + + /* currently only display devices have quirks */ + if (class != PCIC_DISPLAY) + return (0); + + if (vendor == PCI_VENDOR_AMD) + return gvt_d_amd_init(ctx, pi, nvl); + + return (0); +} + +static void +passthru_deinit_quirks(struct vmctx *ctx, struct pci_devinst *pi) +{ + struct passthru_softc *sc = pi->pi_arg; + + if (sc == NULL) + return; + + //uint16_t vendor = read_config(&sc->psc_sel, PCIR_VENDOR, 0x02); + uint8_t class = read_config(&sc->psc_sel, PCIR_CLASS, 0x01); + + /* currently only display devices have quirks */ + if (class != PCIC_DISPLAY) + return; + + return; +} + static int passthru_legacy_config(nvlist_t *nvl, const char *opts) { - char value[16]; + char value[PATH_MAX]; int bus, slot, func; if (opts == NULL) return (0); - if (sscanf(opts, "%d/%d/%d", &bus, &slot, &func) != 3) { + const char *bdf = opts; + + char *xopts = strchr(opts, ','); + if (xopts != NULL) { + *xopts = '\0'; + ++xopts; + } + + if (sscanf(bdf, "%d/%d/%d", &bus, &slot, &func) != 3) { EPRINTLN("passthru: invalid options \"%s\"", opts); return (-1); } @@ -638,6 +735,32 @@ set_config_value_node(nvl, "slot", value); snprintf(value, sizeof(value), "%d", func); set_config_value_node(nvl, "func", value); + + if (xopts == NULL) { + return (0); + } + + char *xopt = xopts; + do { + char *xopt_val = strchr(xopt, '='); + char *xopt_end = strchr(xopt, ','); + if (xopt_val != NULL) { + *xopt_val = '\0'; + ++xopt_val; + } + if (xopt_end != NULL) { + *xopt_end = '\0'; + ++xopt_end; + } + if (strcmp(xopt, "rom") == 0) { + snprintf(value, sizeof(value), "%s", xopt_val); + set_config_value_node(nvl, "rom", value); + } else { + return (-1); + } + xopt = xopt_end; + } while (xopt != NULL); + return (0); } @@ -736,9 +859,21 @@ 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; + + /* allow access to all PCI registers */ + if ((error = set_pcir_prot(sc, 0, PCI_REGMAX + 1, PPT_PCIR_PROT_RW)) != + 0) + goto done; + + if ((error = passthru_init_quirks(ctx, pi, nvl)) != 0) + goto done; + + error = 0; /* success */ done: if (error) { + passthru_deinit_quirks(ctx, pi); free(sc); vm_unassign_pptdev(ctx, bus, slot, func); } @@ -788,12 +923,22 @@ sc = pi->pi_arg; + /* skip for protected PCI registers */ + if (!is_pcir_readable(sc, coff)) + return (-1); + /* * PCI BARs and MSI capability is emulated. */ if (bar_access(coff) || msicap_access(sc, coff)) return (-1); + /* + * PCI ROM is emulated + */ + if (coff >= PCIR_BIOS && coff < PCIR_BIOS + 4) + return (-1); + #ifdef LEGACY_SUPPORT /* * Emulate PCIR_CAP_PTR if this device does not support MSI capability @@ -834,12 +979,22 @@ sc = pi->pi_arg; + /* skip for protected PCI registers */ + if (!is_pcir_writable(sc, coff)) + return (-1); + /* * PCI BARs are emulated */ if (bar_access(coff)) return (-1); + /* + * PCI ROM is emulated + */ + if (coff >= PCIR_BIOS && coff < PCIR_BIOS + 4) + return (-1); + /* * MSI capability is emulated */ @@ -958,7 +1113,7 @@ static void passthru_msix_addr(struct vmctx *ctx, struct pci_devinst *pi, int baridx, - int enabled, uint64_t address) + int enabled, uint64_t address) { struct passthru_softc *sc; size_t remaining; @@ -966,21 +1121,15 @@ sc = pi->pi_arg; table_offset = rounddown2(pi->pi_msix.table_offset, 4096); + + struct passthru_mmio_mapping map; + if (table_offset > 0) { - if (!enabled) { - if (vm_unmap_pptdev_mmio(ctx, sc->psc_sel.pc_bus, - sc->psc_sel.pc_dev, - sc->psc_sel.pc_func, address, - table_offset) != 0) - warnx("pci_passthru: unmap_pptdev_mmio failed"); - } else { - if (vm_map_pptdev_mmio(ctx, sc->psc_sel.pc_bus, - sc->psc_sel.pc_dev, - sc->psc_sel.pc_func, address, - table_offset, - sc->psc_bar[baridx].addr) != 0) - warnx("pci_passthru: map_pptdev_mmio failed"); - } + map.gpa = address; + map.len = table_offset; + map.hpa = sc->psc_bar[baridx].addr; + if (passthru_modify_pptdev_mmio(ctx, sc, &map, enabled) != 0) + warnx("pci_passthru: modify_pptdev_mmio failed"); } table_size = pi->pi_msix.table_offset - table_offset; table_size += pi->pi_msix.table_count * MSIX_TABLE_ENTRY_SIZE; @@ -988,58 +1137,84 @@ remaining = pi->pi_bar[baridx].size - table_offset - table_size; if (remaining > 0) { address += table_offset + table_size; - if (!enabled) { - if (vm_unmap_pptdev_mmio(ctx, sc->psc_sel.pc_bus, - sc->psc_sel.pc_dev, - sc->psc_sel.pc_func, address, - remaining) != 0) - warnx("pci_passthru: unmap_pptdev_mmio failed"); - } else { - if (vm_map_pptdev_mmio(ctx, sc->psc_sel.pc_bus, - sc->psc_sel.pc_dev, - sc->psc_sel.pc_func, address, - remaining, - sc->psc_bar[baridx].addr + - table_offset + table_size) != 0) - warnx("pci_passthru: map_pptdev_mmio failed"); - } + map.gpa = address; + map.len = remaining; + map.hpa = sc->psc_bar[baridx].addr + table_offset + table_size; + if (passthru_modify_pptdev_mmio(ctx, sc, &map, enabled) != 0) + warnx("pci_passthru: modify_pptdev_mmio failed"); } } static void passthru_mmio_addr(struct vmctx *ctx, struct pci_devinst *pi, int baridx, - int enabled, uint64_t address) + int enabled, uint64_t address) { struct passthru_softc *sc; sc = pi->pi_arg; - if (!enabled) { - if (vm_unmap_pptdev_mmio(ctx, sc->psc_sel.pc_bus, - sc->psc_sel.pc_dev, - sc->psc_sel.pc_func, address, - sc->psc_bar[baridx].size) != 0) - warnx("pci_passthru: unmap_pptdev_mmio failed"); - } else { - if (vm_map_pptdev_mmio(ctx, sc->psc_sel.pc_bus, - sc->psc_sel.pc_dev, - sc->psc_sel.pc_func, address, - sc->psc_bar[baridx].size, - sc->psc_bar[baridx].addr) != 0) - warnx("pci_passthru: map_pptdev_mmio failed"); + + struct passthru_mmio_mapping map; + map.gpa = address; + map.len = sc->psc_bar[baridx].size; + map.hpa = sc->psc_bar[baridx].addr; + + if (passthru_modify_pptdev_mmio(ctx, sc, &map, enabled) != 0) + warnx("pci_passthru: modify_pptdev_mmio failed"); +} + +static int +passthru_addr_rom(struct pci_devinst *pi, int idx, int enabled) +{ + struct passthru_softc *sc = pi->pi_arg; + + const uint8_t class = read_config(&sc->psc_sel, PCIR_CLASS, 0x01); + if (class != PCIC_DISPLAY) { + warnx( + "%d/%d/%d is no display device; only display devices have a ROM", + pi->pi_bus, pi->pi_slot, pi->pi_func); + return (-1); + } + + const uint16_t vendor = read_config(&sc->psc_sel, PCIR_VENDOR, 0x02); + switch (vendor) { + case PCI_VENDOR_AMD: + return gvt_d_amd_addr_rom(pi, idx, enabled); + default: + warnx("%d/%d/%d has no ROM", pi->pi_bus, pi->pi_slot, + pi->pi_func); + return (-1); } } -static void +static int passthru_addr(struct vmctx *ctx, struct pci_devinst *pi, int baridx, - int enabled, uint64_t address) + int enabled, uint64_t address) { + int error = 0; - if (pi->pi_bar[baridx].type == PCIBAR_IO) - return; - if (baridx == pci_msix_table_bar(pi)) - passthru_msix_addr(ctx, pi, baridx, enabled, address); - else - passthru_mmio_addr(ctx, pi, baridx, enabled, address); + switch (pi->pi_bar[baridx].type) { + case PCIBAR_IO: + /* IO BARs are emulated */ + return (-1); + case PCIBAR_ROM: + /* Only quirk devices have a ROM */ + error = passthru_addr_rom(pi, baridx, enabled); + break; + case PCIBAR_MEM32: + case PCIBAR_MEM64: + if (baridx == pci_msix_table_bar(pi)) + passthru_msix_addr(ctx, pi, baridx, enabled, address); + else + passthru_mmio_addr(ctx, pi, baridx, enabled, address); + break; + default: + error = EINVAL; + break; + } + if (error) { + errx(4, "Failed to modify BAR addr: %d", error); + } + return 0; } struct pci_devemu passthru = {