git: c8343ba84af7 - main - bhyve: Manage passthrough devices across guest FLR
Kevin Bowling <[email protected]>
| Newsgroups | gmane.os.freebsd.devel.cvs.src,gmane.os.freebsd.current.scm |
|---|---|
| Message-ID | <[email protected]> |
The branch main has been updated by kbowling: URL: https://cgit.FreeBSD.org/src/commit/?id=c8343ba84af75c3710a1c587ff9bd2165fcd37a9 commit c8343ba84af75c3710a1c587ff9bd2165fcd37a9 Author: Kevin Bowling <[email protected]> AuthorDate: 2026-08-15 23:47:33 +0000 Commit: Kevin Bowling <[email protected]> CommitDate: 2026-08-20 22:12:14 +0000 bhyve: Manage passthrough devices across guest FLR bhyve emulates the guest PCI Command register so BAR sizing does not disable physical decoding. However, PCIe Device Control was passed through. A guest VFIO reset therefore performed a physical FLR, which cleared physical Command, while the guest restored only its emulated copy. The device remained assigned with bus mastering disabled and could not fetch DMA descriptors. Intercept guest FLR writes and issue a PPT-managed reset. Stop all vCPUs, verify ownership, quiesce the function, perform only an FLR, and restore the host-owned PCI configuration, decode, and bus-master state. Keep the IOMMU domain in place. bhyve removes guest BAR mappings before this ioctl; a later guest MEMEN write recreates them. Never escalate a guest FLR to a power reset. Reset the guest-owned Command, MSI, MSI-X, MSI-X table, INTx, and MRRS state. PCIe 6.2 section 6.6.2 explicitly preserves MPS across FLR. Virtualize MPS, MRRS, and Completion Timeout. Keep physical MPS and completion-timeout policy host-owned, and apply physical MRRS with MPS as its floor. Keep Phantom Functions Enable host-owned because it changes requester identities visible to the IOMMU. Serialize guest configuration transactions per function and gate trapped and direct BAR access across the reset. Handle byte, word, dword, and overlapping Device Control accesses. A guest FLR can sleep for at least 100 ms. Reserve the target function while dropping the global PPT lock so a guest cannot delay PPT lifecycle operations for other VMs. Operations on the target wait for its reset while other functions and VMs can proceed. Check pcie_flr_supported() before destructive preparation so PPT applies the generic PCI quirk policy. This includes VFs such as the 82599 which implement FLR without advertising it. Validated with two E610 VFs in a Linux 7.0 guest using VFIO no-IOMMU and DPDK testpmd with two queues per VF. Byte, word, dword, and overlapping FLR writes, 32 alternating resets, DPDK traffic, and ixgbevf reattachment all completed while the sibling VF and host PCIe remained healthy. This fixes Linux VFIO no-IOMMU with DPDK PMDs. Reviewed by: markj MFC after: 2 weeks Sponsored by: BBOX.io --- lib/libvmmapi/internal.h | 3 +- lib/libvmmapi/ppt.c | 13 ++ lib/libvmmapi/vmmapi.h | 1 + sys/amd64/include/vmm.h | 1 + sys/amd64/include/vmm_dev.h | 3 + sys/amd64/vmm/io/ppt.c | 134 +++++++++++++-- sys/amd64/vmm/io/ppt.h | 1 + sys/amd64/vmm/vmm.c | 7 + sys/amd64/vmm/vmm_dev_machdep.c | 10 ++ usr.sbin/bhyve/pci_emul.c | 13 +- usr.sbin/bhyve/pci_emul.h | 1 + usr.sbin/bhyve/pci_passthru.c | 354 +++++++++++++++++++++++++++++++++++++++- 12 files changed, 520 insertions(+), 21 deletions(-) diff --git a/lib/libvmmapi/internal.h b/lib/libvmmapi/internal.h index 4afe1cab3460..dadcdbc3974c 100644 --- a/lib/libvmmapi/internal.h +++ b/lib/libvmmapi/internal.h @@ -67,7 +67,8 @@ extern const char *vm_capstrmap[]; VM_PPTDEV_MSI, \ VM_PPTDEV_MSIX, \ VM_UNMAP_PPTDEV_MMIO, \ - VM_PPTDEV_DISABLE_MSIX + VM_PPTDEV_DISABLE_MSIX, \ + VM_RESET_PPTDEV extern const cap_ioctl_t vm_ioctl_cmds[]; extern size_t vm_ioctl_ncmds; diff --git a/lib/libvmmapi/ppt.c b/lib/libvmmapi/ppt.c index fd49f8eed168..7e318c292237 100644 --- a/lib/libvmmapi/ppt.c +++ b/lib/libvmmapi/ppt.c @@ -62,6 +62,19 @@ vm_unassign_pptdev(struct vmctx *ctx, int bus, int slot, int func) return (ioctl(ctx->fd, VM_UNBIND_PPTDEV, &pptdev)); } +int +vm_reset_pptdev(struct vmctx *ctx, int bus, int slot, int func) +{ + struct vm_pptdev pptdev; + + bzero(&pptdev, sizeof(pptdev)); + pptdev.bus = bus; + pptdev.slot = slot; + pptdev.func = func; + + return (ioctl(ctx->fd, VM_RESET_PPTDEV, &pptdev)); +} + int vm_map_pptdev_mmio(struct vmctx *ctx, int bus, int slot, int func, vm_paddr_t gpa, size_t len, vm_paddr_t hpa) diff --git a/lib/libvmmapi/vmmapi.h b/lib/libvmmapi/vmmapi.h index 5d3495a128d9..25dd5021ccdd 100644 --- a/lib/libvmmapi/vmmapi.h +++ b/lib/libvmmapi/vmmapi.h @@ -203,6 +203,7 @@ int vm_set_capability(struct vcpu *vcpu, enum vm_cap_type cap, int val); int vm_assign_pptdev(struct vmctx *ctx, int bus, int slot, int func); int vm_unassign_pptdev(struct vmctx *ctx, int bus, int slot, int func); +int vm_reset_pptdev(struct vmctx *ctx, int bus, int slot, int func); int vm_map_pptdev_mmio(struct vmctx *ctx, int bus, int slot, int func, 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, diff --git a/sys/amd64/include/vmm.h b/sys/amd64/include/vmm.h index eef8e6760fd6..4ff7e83db90c 100644 --- a/sys/amd64/include/vmm.h +++ b/sys/amd64/include/vmm.h @@ -232,6 +232,7 @@ int vm_map_mmio(struct vm *vm, vm_paddr_t gpa, size_t len, vm_paddr_t hpa); int vm_unmap_mmio(struct vm *vm, vm_paddr_t gpa, size_t len); int vm_assign_pptdev(struct vm *vm, int bus, int slot, int func); int vm_unassign_pptdev(struct vm *vm, int bus, int slot, int func); +int vm_reset_pptdev(struct vm *vm, int bus, int slot, int func); int vm_get_register(struct vcpu *vcpu, int reg, uint64_t *retval); int vm_set_register(struct vcpu *vcpu, int reg, uint64_t val); diff --git a/sys/amd64/include/vmm_dev.h b/sys/amd64/include/vmm_dev.h index f1c07a983a4b..4ed110b526ec 100644 --- a/sys/amd64/include/vmm_dev.h +++ b/sys/amd64/include/vmm_dev.h @@ -306,6 +306,7 @@ enum { IOCNUM_PPTDEV_MSIX = 44, IOCNUM_PPTDEV_DISABLE_MSIX = 45, IOCNUM_UNMAP_PPTDEV_MMIO = 46, + IOCNUM_RESET_PPTDEV = 47, /* statistics */ IOCNUM_VM_STATS = 50, @@ -420,6 +421,8 @@ enum { _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_RESET_PPTDEV \ + _IOW('v', IOCNUM_RESET_PPTDEV, struct vm_pptdev) #define VM_INJECT_NMI \ _IOW('v', IOCNUM_INJECT_NMI, struct vm_nmi) #define VM_STATS \ diff --git a/sys/amd64/vmm/io/ppt.c b/sys/amd64/vmm/io/ppt.c index 162f26796b33..961c0747b201 100644 --- a/sys/amd64/vmm/io/ppt.c +++ b/sys/amd64/vmm/io/ppt.c @@ -90,6 +90,7 @@ struct pptseg { struct pptdev { device_t dev; struct vm *vm; /* owner of this device */ + bool resetting; /* guest FLR in progress */ TAILQ_ENTRY(pptdev) next; struct pptseg mmio[MAX_MMIOSEGS]; struct { @@ -233,19 +234,30 @@ ppt_find(struct vm *vm, int bus, int slot, int func, struct pptdev **pptp) PPT_ASSERT_LOCKED(); - TAILQ_FOREACH(ppt, &pptdev_list, next) { - dev = ppt->dev; - b = pci_get_bus(dev); - s = pci_get_slot(dev); - f = pci_get_function(dev); - if (bus == b && slot == s && func == f) + for (;;) { + TAILQ_FOREACH(ppt, &pptdev_list, next) { + dev = ppt->dev; + b = pci_get_bus(dev); + s = pci_get_slot(dev); + f = pci_get_function(dev); + if (bus == b && slot == s && func == f) + break; + } + + if (ppt == NULL) + return (ENOENT); + if (ppt->vm != vm) /* Make sure we own this device. */ + return (EBUSY); + if (!ppt->resetting) break; + /* + * Once resetting is set, every exit from ppt_reset_device() + * reacquires ppt_mtx, clears resetting, and wakes us. The FLR wait + * itself is bounded. + */ + sx_sleep(ppt, &ppt_mtx, 0, "pptflr", 0); } - if (ppt == NULL) - return (ENOENT); - if (ppt->vm != vm) /* Make sure we own this device */ - return (EBUSY); *pptp = ppt; return (0); } @@ -474,6 +486,108 @@ out: return (error); } +int +ppt_reset_device(struct vm *vm, int bus, int slot, int func) +{ + struct pptdev *ppt; + uint16_t cmd, enables, original_cmd; + int error; + + PPT_LOCK(); + error = ppt_find(vm, bus, slot, func, &ppt); + if (error != 0) + goto out_locked; + + /* + * FLR takes at least 100 ms. Reserve this function, but do not let a + * guest hold the global PPT lock and delay operations on other VMs. + */ + ppt->resetting = true; + PPT_UNLOCK(); + + original_cmd = pci_read_config(ppt->dev, PCIR_COMMAND, 2); + if (original_cmd == 0xffff) { + error = ENXIO; + goto out; + } + if (!pcie_flr_supported(ppt->dev)) { + error = ENOTSUP; + goto out; + } + + /* + * Disable physical INTx before releasing its handler. This also makes + * an asserted Function send Deassert_INTx before FLR, as required by + * PCIe. Gate decoding and DMA before tearing down MSI or MSI-X. + */ + cmd = original_cmd | PCIM_CMD_INTxDIS; + cmd &= ~(PCIM_CMD_PORTEN | PCIM_CMD_MEMEN | PCIM_CMD_BUSMASTEREN); + pci_write_config(ppt->dev, PCIR_COMMAND, cmd, 2); + ppt_teardown_msi(ppt); + ppt_teardown_msix(ppt); + + /* + * Save the host-owned state after interrupt teardown, then restore BARs + * and PCIe controls after FLR. The IOMMU domain remains intact. bhyve + * removes guest BAR mappings before this ioctl; a later guest MEMEN write + * recreates them. + */ + pci_save_state(ppt->dev); + + /* + * A guest-requested FLR must not be escalated to a power reset. The + * support check above and force=true mean that pcie_flr() cannot fail + * for a stable function. If its support nevertheless disappears + * between the two checks, destructive preparation has already torn down + * host interrupt resources. Return EIO so bhyve discards the now-stale + * guest interrupt state even though the FLR was not initiated. + */ + if (!pcie_flr(ppt->dev, + max(pcie_get_max_completion_timeout(ppt->dev) / 1000, 10), true)) { + device_printf(ppt->dev, "guest FLR could not be performed\n"); + error = EIO; + goto restore; + } + error = 0; + + /* + * Restore the decode and DMA enables which were set before the FLR; + * the guest command register is intentionally virtual. Do not infer + * writable enables from the BAR resources here. For example, a VF can + * use PF-owned BAR apertures while its own MEMEN bit is RsvdP. + * + * A post-reset readback failure does not undo the guest-visible reset. + */ +restore: + pci_restore_state(ppt->dev); + enables = original_cmd & + (PCIM_CMD_PORTEN | PCIM_CMD_MEMEN | PCIM_CMD_BUSMASTEREN); + cmd = pci_read_config(ppt->dev, PCIR_COMMAND, 2); + if (cmd == 0xffff) { + device_printf(ppt->dev, + "config space unavailable after guest FLR\n"); + error = EIO; + goto out; + } + cmd &= ~(PCIM_CMD_PORTEN | PCIM_CMD_MEMEN | PCIM_CMD_BUSMASTEREN | + PCIM_CMD_INTxDIS); + cmd |= enables | (original_cmd & PCIM_CMD_INTxDIS); + pci_write_config(ppt->dev, PCIR_COMMAND, cmd, 2); + cmd = pci_read_config(ppt->dev, PCIR_COMMAND, 2); + if (cmd == 0xffff || (cmd & enables) != enables) { + device_printf(ppt->dev, + "failed to restore command register after guest FLR\n"); + error = EIO; + } +out: + PPT_LOCK(); + ppt->resetting = false; + wakeup(ppt); +out_locked: + PPT_UNLOCK(); + return (error); +} + int ppt_unassign_all(struct vm *vm) { diff --git a/sys/amd64/vmm/io/ppt.h b/sys/amd64/vmm/io/ppt.h index 9377f34d50e6..f58c1dc4cd23 100644 --- a/sys/amd64/vmm/io/ppt.h +++ b/sys/amd64/vmm/io/ppt.h @@ -48,4 +48,5 @@ bool ppt_is_mmio(struct vm *vm, vm_paddr_t gpa); */ int ppt_assign_device(struct vm *vm, int bus, int slot, int func); int ppt_unassign_device(struct vm *vm, int bus, int slot, int func); +int ppt_reset_device(struct vm *vm, int bus, int slot, int func); #endif diff --git a/sys/amd64/vmm/vmm.c b/sys/amd64/vmm/vmm.c index 4dedb5da94a3..cea8ee99609d 100644 --- a/sys/amd64/vmm/vmm.c +++ b/sys/amd64/vmm/vmm.c @@ -591,6 +591,13 @@ vm_assign_pptdev(struct vm *vm, int bus, int slot, int func) return (error); } +int +vm_reset_pptdev(struct vm *vm, int bus, int slot, int func) +{ + + return (ppt_reset_device(vm, bus, slot, func)); +} + int vm_get_register(struct vcpu *vcpu, int reg, uint64_t *retval) { diff --git a/sys/amd64/vmm/vmm_dev_machdep.c b/sys/amd64/vmm/vmm_dev_machdep.c index 55fccf8f25b2..dcc9998e754d 100644 --- a/sys/amd64/vmm/vmm_dev_machdep.c +++ b/sys/amd64/vmm/vmm_dev_machdep.c @@ -129,6 +129,8 @@ const struct vmmdev_ioctl vmmdev_machdep_ioctls[] = { VMMDEV_IOCTL(VM_UNBIND_PPTDEV, VMMDEV_IOCTL_XLOCK_MEMSEGS | VMMDEV_IOCTL_LOCK_ALL_VCPUS | VMMDEV_IOCTL_PPT), + VMMDEV_IOCTL(VM_RESET_PPTDEV, VMMDEV_IOCTL_LOCK_ALL_VCPUS | + VMMDEV_IOCTL_PPT), VMMDEV_IOCTL(VM_MAP_PPTDEV_MMIO, VMMDEV_IOCTL_LOCK_ALL_VCPUS | VMMDEV_IOCTL_PPT), @@ -316,6 +318,14 @@ vmmdev_machdep_ioctl(struct vm *vm, struct vcpu *vcpu, u_long cmd, caddr_t data, pptdev->func); break; } + case VM_RESET_PPTDEV: { + struct vm_pptdev *pptdev; + + pptdev = (struct vm_pptdev *)data; + error = vm_reset_pptdev(vm, pptdev->bus, pptdev->slot, + pptdev->func); + break; + } case VM_INJECT_EXCEPTION: { struct vm_exception *vmexc; diff --git a/usr.sbin/bhyve/pci_emul.c b/usr.sbin/bhyve/pci_emul.c index dc27402675ee..538e4f284bec 100644 --- a/usr.sbin/bhyve/pci_emul.c +++ b/usr.sbin/bhyve/pci_emul.c @@ -1152,6 +1152,7 @@ pci_emul_init(struct vmctx *ctx, struct pci_devemu *pde, int bus, int slot, pdi->pi_slot = slot; pdi->pi_func = func; pthread_mutex_init(&pdi->pi_lintr.lock, NULL); + pthread_mutex_init(&pdi->pi_cfg_lock, NULL); pdi->pi_lintr.pin = 0; pdi->pi_lintr.state = IDLE; pci_irq_init_irq(&pdi->pi_lintr.irq); @@ -1169,8 +1170,11 @@ pci_emul_init(struct vmctx *ctx, struct pci_devemu *pde, int bus, int slot, err = (*pde->pe_init)(pdi, fi->fi_config); if (err == 0) fi->fi_devi = pdi; - else + else { + pthread_mutex_destroy(&pdi->pi_cfg_lock); + pthread_mutex_destroy(&pdi->pi_lintr.lock); free(pdi); + } return (err); } @@ -2309,6 +2313,7 @@ pci_cfgrw(int in, int bus, int slot, int func, int coff, int bytes, } pe = pi->pi_d; + pthread_mutex_lock(&pi->pi_cfg_lock); /* * Config read @@ -2329,7 +2334,7 @@ pci_cfgrw(int in, int bus, int slot, int func, int coff, int bytes, /* Let the device emulation override the default handler */ if (pe->pe_cfgwrite != NULL && (*pe->pe_cfgwrite)(pi, coff, bytes, *valp) == 0) - return; + goto out; /* * Special handling for write to BAR and ROM registers @@ -2340,7 +2345,7 @@ pci_cfgrw(int in, int bus, int slot, int func, int coff, int bytes, * 4-byte aligned. */ if (bytes != 4 || (coff & 0x3) != 0) - return; + goto out; if (is_pcir_bar(coff)) { idx = (coff - PCIR_BAR(0)) / 4; @@ -2422,6 +2427,8 @@ pci_cfgrw(int in, int bus, int slot, int func, int coff, int bytes, CFGWRITE(pi, coff, *valp, bytes); } } +out: + pthread_mutex_unlock(&pi->pi_cfg_lock); } #ifdef __amd64__ diff --git a/usr.sbin/bhyve/pci_emul.h b/usr.sbin/bhyve/pci_emul.h index 29e9717c1593..4bf617dc3219 100644 --- a/usr.sbin/bhyve/pci_emul.h +++ b/usr.sbin/bhyve/pci_emul.h @@ -136,6 +136,7 @@ struct pci_devinst { struct pci_irq irq; pthread_mutex_t lock; } pi_lintr; + pthread_mutex_t pi_cfg_lock; struct { int enabled; diff --git a/usr.sbin/bhyve/pci_passthru.c b/usr.sbin/bhyve/pci_passthru.c index e38dc327fdff..a78ca06c9d83 100644 --- a/usr.sbin/bhyve/pci_passthru.c +++ b/usr.sbin/bhyve/pci_passthru.c @@ -55,6 +55,7 @@ #include <err.h> #include <errno.h> #include <fcntl.h> +#include <pthread.h> #include <sysexits.h> #include <unistd.h> @@ -99,7 +100,17 @@ struct passthru_softc { struct { int capoff; } psc_msix; + struct { + int capoff; + uint16_t devctl; + uint16_t reset_devctl; + uint16_t devctl2; + uint16_t reset_devctl2; + bool has_devctl2; + } psc_pcie; struct pcisel psc_sel; + pthread_mutex_t psc_io_mtx; + bool psc_resetting; struct passthru_mmio_mapping psc_mmio_map[PASSTHRU_MMIO_MAX]; cfgread_handler psc_pcir_rhandler[PCI_REGMAX + 1]; @@ -263,9 +274,10 @@ passthru_add_msicap(struct pci_devinst *pi, int msgnum, int nextptr) #endif /* LEGACY_SUPPORT */ static int -cfginitmsi(struct passthru_softc *sc) +cfginitcaps(struct passthru_softc *sc) { int i, ptr, capptr, cap, sts, caplen, table_size; + uint16_t flags; uint32_t u32; struct pcisel sel; struct pci_devinst *pi; @@ -319,11 +331,32 @@ cfginitmsi(struct passthru_softc *sc) capptr += 4; msixcap_ptr += 4; } + } else if (cap == PCIY_EXPRESS) { + sc->psc_pcie.capoff = ptr; } ptr = passthru_read_config(&sel, ptr + PCICAP_NEXTPTR, 1); } } + if (sc->psc_pcie.capoff != 0) { + sc->psc_pcie.devctl = passthru_read_config(&sel, + sc->psc_pcie.capoff + PCIER_DEVICE_CTL, 2); + /* + * Use the assignment-time guest view as the virtual reset baseline. + * Host firmware and the PCI bus may already have tuned Device Control, + * so restoring the hardware reset defaults would expose a different + * configuration after the first guest FLR. + */ + sc->psc_pcie.reset_devctl = sc->psc_pcie.devctl; + flags = passthru_read_config(&sel, + sc->psc_pcie.capoff + PCIER_FLAGS, 2); + if ((flags & PCIEM_FLAGS_VERSION) >= 2) { + sc->psc_pcie.has_devctl2 = true; + sc->psc_pcie.devctl2 = passthru_read_config(&sel, + sc->psc_pcie.capoff + PCIER_DEVICE_CTL2, 2); + sc->psc_pcie.reset_devctl2 = sc->psc_pcie.devctl2; + } + } if (sc->psc_msix.capoff != 0) { pi->pi_msix.pba_bar = @@ -340,6 +373,8 @@ cfginitmsi(struct passthru_softc *sc) /* Allocate the emulated MSI-X table array */ table_size = pi->pi_msix.table_count * MSIX_TABLE_ENTRY_SIZE; pi->pi_msix.table = calloc(1, table_size); + if (pi->pi_msix.table == NULL) + return (-1); /* Mask all table entries */ for (i = 0; i < pi->pi_msix.table_count; i++) { @@ -691,8 +726,8 @@ cfginit(struct pci_devinst *pi, int bus, int slot, int func) pci_set_cfgdata8(pi, PCIR_INTLINE, intline); pci_set_cfgdata8(pi, PCIR_INTPIN, intpin); - if (cfginitmsi(sc) != 0) { - warnx("failed to initialize MSI for PCI %d/%d/%d", + if (cfginitcaps(sc) != 0) { + warnx("failed to initialize PCI capabilities for %d/%d/%d", bus, slot, func); goto done; } @@ -998,6 +1033,9 @@ passthru_init(struct pci_devinst *pi, nvlist_t *nvl) } sc = calloc(1, sizeof(struct passthru_softc)); + if (sc == NULL) + goto done; + pthread_mutex_init(&sc->psc_io_mtx, NULL); pi->pi_arg = sc; sc->psc_pi = pi; @@ -1043,6 +1081,8 @@ done: if (error) { if (dev != NULL) dev->deinit(pi); + if (sc != NULL) + pthread_mutex_destroy(&sc->psc_io_mtx); free(sc); vm_unassign_pptdev(pi->pi_vmctx, bus, slot, func); } @@ -1075,6 +1115,184 @@ msixcap_access(struct passthru_softc *sc, int coff) coff < sc->psc_msix.capoff + MSIX_CAPLEN); } +#define PASSTHRU_DEVCTL_VIRT (PCIEM_CTL_MAX_PAYLOAD | \ + PCIEM_CTL_MAX_READ_REQUEST) +#define PASSTHRU_DEVCTL_NO_WRITE PCIEM_CTL_PHANTHOM_FUNCS +#define PASSTHRU_DEVCTL2_VIRT (PCIEM_CTL2_COMP_TIMO_VAL | \ + PCIEM_CTL2_COMP_TIMO_DISABLE) + +static uint32_t +passthru_cfg_field_mask(int coff, int bytes, int fieldoff, uint16_t mask) +{ + uint32_t access_mask; + int i, pos; + + access_mask = 0; + for (i = 0; i < bytes; i++) { + pos = coff + i; + if (pos >= fieldoff && pos < fieldoff + 2) + access_mask |= ((mask >> ((pos - fieldoff) * NBBY)) & + 0xff) << (i * NBBY); + } + return (access_mask); +} + +static uint32_t +passthru_cfg_field_value(int coff, int bytes, int fieldoff, uint16_t value) +{ + uint32_t access_value; + int i, pos; + + access_value = 0; + for (i = 0; i < bytes; i++) { + pos = coff + i; + if (pos >= fieldoff && pos < fieldoff + 2) + access_value |= ((value >> ((pos - fieldoff) * NBBY)) & + 0xff) << (i * NBBY); + } + return (access_value); +} + +/* + * Replace selected bits of a 16-bit field within an arbitrarily aligned + * configuration-space access. Preserve every byte and field bit outside + * the supplied mask. + */ +static uint32_t +passthru_cfg_overlay_field(int coff, int bytes, uint32_t access, int fieldoff, + uint16_t field, uint16_t field_mask) +{ + uint32_t access_mask; + + access_mask = passthru_cfg_field_mask(coff, bytes, fieldoff, + field_mask); + return ((access & ~access_mask) | + (passthru_cfg_field_value(coff, bytes, fieldoff, field) & + access_mask)); +} + +static void +passthru_cfg_update_field(int coff, int bytes, uint32_t value, int fieldoff, + uint16_t mask, uint16_t *field) +{ + uint16_t byte_mask, byte_value; + int i, pos, shift; + + for (i = 0; i < bytes; i++) { + pos = coff + i; + if (pos < fieldoff || pos >= fieldoff + 2) + continue; + shift = (pos - fieldoff) * NBBY; + byte_mask = mask & (0xff << shift); + byte_value = ((value >> (i * NBBY)) & 0xff) << shift; + *field = (*field & ~byte_mask) | (byte_value & byte_mask); + } +} + +static void +passthru_reset_interrupt_state(struct passthru_softc *sc) +{ + struct pci_devinst *pi; + uint16_t msgctrl; + int caplen, i; + + pi = sc->psc_pi; + if (sc->psc_msi.capoff != 0) { + caplen = msi_caplen(sc->psc_msi.msgctrl); + msgctrl = pci_get_cfgdata16(pi, sc->psc_msi.capoff + 2); + msgctrl &= ~(PCIM_MSICTRL_MME_MASK | PCIM_MSICTRL_MSI_ENABLE); + memset(pi->pi_cfgdata + sc->psc_msi.capoff + 4, 0, caplen - 4); + pci_set_cfgdata16(pi, sc->psc_msi.capoff + 2, msgctrl); + pi->pi_msi.enabled = 0; + pi->pi_msi.addr = 0; + pi->pi_msi.msg_data = 0; + pi->pi_msi.maxmsgnum = 0; + } + if (sc->psc_msix.capoff != 0) { + msgctrl = pci_get_cfgdata16(pi, sc->psc_msix.capoff + 2); + msgctrl &= ~(PCIM_MSIXCTRL_MSIX_ENABLE | + PCIM_MSIXCTRL_FUNCTION_MASK); + pci_set_cfgdata16(pi, sc->psc_msix.capoff + 2, msgctrl); + pi->pi_msix.enabled = 0; + pi->pi_msix.function_mask = 0; + bzero(pi->pi_msix.table, pi->pi_msix.table_count * + sizeof(pi->pi_msix.table[0])); + for (i = 0; i < pi->pi_msix.table_count; i++) + pi->pi_msix.table[i].vector_control = + PCIM_MSIX_VCTRL_MASK; + } +} + +static void +passthru_reset_capability_state(struct passthru_softc *sc) +{ + uint16_t devctl, guest_mps; + int offset; + + /* MPS is explicitly preserved across FLR by the PCIe specification. */ + guest_mps = sc->psc_pcie.devctl & PCIEM_CTL_MAX_PAYLOAD; + offset = sc->psc_pcie.capoff + PCIER_DEVICE_CTL; + devctl = passthru_read_config(&sc->psc_sel, offset, 2); + if (devctl != 0xffff) { + devctl &= ~PCIEM_CTL_MAX_READ_REQUEST; + devctl |= sc->psc_pcie.reset_devctl & + PCIEM_CTL_MAX_READ_REQUEST; + passthru_write_config(&sc->psc_sel, offset, 2, devctl); + } + sc->psc_pcie.devctl = + (sc->psc_pcie.reset_devctl & ~PCIEM_CTL_MAX_PAYLOAD) | guest_mps; + if (sc->psc_pcie.has_devctl2) + sc->psc_pcie.devctl2 = sc->psc_pcie.reset_devctl2; +} + +static int +passthru_reset(struct passthru_softc *sc) +{ + struct pci_devinst *pi; + uint16_t command; + int error; + + pi = sc->psc_pi; + command = pci_get_cfgdata16(pi, PCIR_COMMAND); + + /* + * Stop trapped BAR accesses and drain any handler already touching the + * device. pci_cfgrw() holds pi_cfg_lock for this entire transaction, + * so another vCPU cannot re-enable or move a BAR around the reset. + */ + pthread_mutex_lock(&sc->psc_io_mtx); + sc->psc_resetting = true; + pthread_mutex_unlock(&sc->psc_io_mtx); + + if (pi->pi_lintr.pin != 0) + pci_lintr_deassert(pi); + pci_set_cfgdata16(pi, PCIR_COMMAND, 0); + pci_emul_cmd_changed(pi, command); + + if (vm_reset_pptdev(pi->pi_vmctx, sc->psc_sel.pc_bus, + sc->psc_sel.pc_dev, sc->psc_sel.pc_func) == 0) + error = 0; + else + error = errno; + + /* + * EIO means PPT crossed the destructive preparation boundary. The FLR + * either ran or failed after host interrupt resources were torn down, so + * the corresponding guest state must be discarded in either case. + */ + if (error != 0 && error != EIO) { + pci_set_cfgdata16(pi, PCIR_COMMAND, command); + pci_emul_cmd_changed(pi, 0); + } else { + passthru_reset_interrupt_state(sc); + passthru_reset_capability_state(sc); + } + pthread_mutex_lock(&sc->psc_io_mtx); + sc->psc_resetting = false; + pthread_mutex_unlock(&sc->psc_io_mtx); + return (error); +} + static int passthru_cfgread_default(struct passthru_softc *sc, struct pci_devinst *pi __unused, int coff, int bytes, uint32_t *rv) @@ -1100,8 +1318,20 @@ passthru_cfgread_default(struct passthru_softc *sc, return (0); } - /* Everything else just read from the device's config space */ + /* Everything else just read from the device's config space. */ *rv = passthru_read_config(&sc->psc_sel, coff, bytes); + if (sc->psc_pcie.capoff != 0) { + int devctl; + + devctl = sc->psc_pcie.capoff + PCIER_DEVICE_CTL; + *rv = passthru_cfg_overlay_field(coff, bytes, *rv, devctl, + sc->psc_pcie.devctl, PASSTHRU_DEVCTL_VIRT); + if (sc->psc_pcie.has_devctl2) { + devctl = sc->psc_pcie.capoff + PCIER_DEVICE_CTL2; + *rv = passthru_cfg_overlay_field(coff, bytes, *rv, devctl, + sc->psc_pcie.devctl2, PASSTHRU_DEVCTL2_VIRT); + } + } return (0); } @@ -1131,6 +1361,9 @@ static int passthru_cfgwrite_default(struct passthru_softc *sc, struct pci_devinst *pi, int coff, int bytes, uint32_t val) { + uint32_t flr_mask, transport_mask; + uint16_t physical_devctl; + int devctl, devctl2, host_mps, guest_mrrs; int error, msix_table_entries, i; uint16_t cmd_old; @@ -1175,6 +1408,83 @@ passthru_cfgwrite_default(struct passthru_softc *sc, struct pci_devinst *pi, return (0); } + /* + * A direct FLR would clear physical Command while the guest sees its + * emulated copy remain enabled. Route FLR through ppt so it restores + * host-owned state. MPS is shared-path policy, and Phantom Functions + * Enable changes IOMMU-visible requester IDs, so keep both host-owned. + * Floor physical MRRS at physical MPS because the guest lacks the + * hierarchy view. ppt also applies FLR quirks for VFs such as 82599. + */ + devctl = sc->psc_pcie.capoff + PCIER_DEVICE_CTL; + if (sc->psc_pcie.capoff != 0 && coff < devctl + 2 && + coff + bytes > devctl) { + transport_mask = passthru_cfg_field_mask(coff, bytes, devctl, + PASSTHRU_DEVCTL_VIRT); + physical_devctl = passthru_read_config(&sc->psc_sel, devctl, 2); + if (physical_devctl == 0xffff) { + warnx("configuration space unavailable for passthru " + "device %d/%d/%d", sc->psc_sel.pc_bus, + sc->psc_sel.pc_dev, sc->psc_sel.pc_func); + return (0); + } + passthru_cfg_update_field(coff, bytes, val, devctl, + PASSTHRU_DEVCTL_VIRT, &sc->psc_pcie.devctl); + if ((transport_mask & passthru_cfg_field_mask(coff, bytes, + devctl, PCIEM_CTL_MAX_READ_REQUEST)) != 0) { + host_mps = (physical_devctl & PCIEM_CTL_MAX_PAYLOAD) >> 5; + guest_mrrs = (sc->psc_pcie.devctl & + PCIEM_CTL_MAX_READ_REQUEST) >> 12; + if (guest_mrrs <= 5) { + if (guest_mrrs < host_mps) + guest_mrrs = host_mps; + physical_devctl &= ~PCIEM_CTL_MAX_READ_REQUEST; + physical_devctl |= guest_mrrs << 12; + } + } + val = passthru_cfg_overlay_field(coff, bytes, val, devctl, + physical_devctl, + PASSTHRU_DEVCTL_VIRT | PASSTHRU_DEVCTL_NO_WRITE); + + flr_mask = passthru_cfg_field_mask(coff, bytes, devctl, + PCIEM_CTL_INITIATE_FLR); + passthru_write_config(&sc->psc_sel, coff, bytes, + val & ~flr_mask); + if ((val & flr_mask) != 0) { + error = passthru_reset(sc); + if (error != 0) + warnx("failed to reset passthru device " + "%d/%d/%d: %s", sc->psc_sel.pc_bus, + sc->psc_sel.pc_dev, sc->psc_sel.pc_func, + strerror(error)); + } + return (0); + } + + /* + * Completion Timeout controls how long the host must protect against an + * in-flight completion after a forced FLR. Keep the physical policy + * host-owned so a guest cannot extend the reset ioctl for tens of + * seconds, but retain a guest-visible value for normal PCI semantics. + */ + devctl2 = sc->psc_pcie.capoff + PCIER_DEVICE_CTL2; + if (sc->psc_pcie.has_devctl2 && coff < devctl2 + 2 && + coff + bytes > devctl2) { + physical_devctl = passthru_read_config(&sc->psc_sel, devctl2, 2); + if (physical_devctl == 0xffff) { + warnx("configuration space unavailable for passthru " + "device %d/%d/%d", sc->psc_sel.pc_bus, + sc->psc_sel.pc_dev, sc->psc_sel.pc_func); + return (0); + } + passthru_cfg_update_field(coff, bytes, val, devctl2, + PASSTHRU_DEVCTL2_VIRT, &sc->psc_pcie.devctl2); + val = passthru_cfg_overlay_field(coff, bytes, val, devctl2, + physical_devctl, PASSTHRU_DEVCTL2_VIRT); + passthru_write_config(&sc->psc_sel, coff, bytes, val); + return (0); + } + /* * The command register is emulated, but the status register * is passed through. @@ -1220,8 +1530,8 @@ passthru_cfgwrite(struct pci_devinst *pi, int coff, int bytes, uint32_t val) } static void -passthru_write(struct pci_devinst *pi, int baridx, uint64_t offset, int size, - uint64_t value) +passthru_write_locked(struct pci_devinst *pi, int baridx, uint64_t offset, + int size, uint64_t value) { struct passthru_softc *sc; struct passthru_bar_handler *handler; @@ -1267,7 +1577,8 @@ passthru_write(struct pci_devinst *pi, int baridx, uint64_t offset, int size, } static uint64_t -passthru_read(struct pci_devinst *pi, int baridx, uint64_t offset, int size) +passthru_read_locked(struct pci_devinst *pi, int baridx, uint64_t offset, + int size) { struct passthru_softc *sc; struct passthru_bar_handler *handler; @@ -1315,6 +1626,35 @@ passthru_read(struct pci_devinst *pi, int baridx, uint64_t offset, int size) return (val); } +static void +passthru_write(struct pci_devinst *pi, int baridx, uint64_t offset, int size, + uint64_t value) +{ + struct passthru_softc *sc; + + sc = pi->pi_arg; + pthread_mutex_lock(&sc->psc_io_mtx); + if (!sc->psc_resetting) + passthru_write_locked(pi, baridx, offset, size, value); + pthread_mutex_unlock(&sc->psc_io_mtx); +} + +static uint64_t +passthru_read(struct pci_devinst *pi, int baridx, uint64_t offset, int size) +{ + struct passthru_softc *sc; + uint64_t value; + + sc = pi->pi_arg; + pthread_mutex_lock(&sc->psc_io_mtx); + if (sc->psc_resetting) + value = UINT64_MAX; + else + value = passthru_read_locked(pi, baridx, offset, size); + pthread_mutex_unlock(&sc->psc_io_mtx); + return (value); +} + static int passthru_mmio_map(struct pci_devinst *pi, int baridx, int enabled, uint64_t address, uint64_t off, uint64_t size)