mirror of
https://github.com/freebsd/freebsd-src.git
synced 2026-10-28 11:25:21 +01:00
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
This commit is contained in:
1 parent
850203655b
commit
c8343ba84a
12 files changed
+520
-21
No files matched your search
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 \
|
||||
|
||||
+124
-10
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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__
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in new issue
Block a user