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:
Kevin Bowling committed 2026-08-20 15:12:14 -07:00
1 parent 850203655b
commit c8343ba84a
12 files changed
+520 -21

No files matched your search

+2 -1
View File
@@ -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;
+13
View File
@@ -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)
+1
View File
@@ -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,
+1
View File
@@ -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);
+3
View File
@@ -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
View File
@@ -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)
{
+1
View File
@@ -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
+7
View File
@@ -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)
{
+10
View File
@@ -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;
+10 -3
View File
@@ -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__
+1
View File
@@ -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;
+347 -7
View File
@@ -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)