Page MenuHomeFreeBSD

D58864.id.diff
No OneTemporary

D58864.id.diff

diff --git a/lib/libvmmapi/internal.h b/lib/libvmmapi/internal.h
--- a/lib/libvmmapi/internal.h
+++ b/lib/libvmmapi/internal.h
@@ -67,7 +67,8 @@
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
--- a/lib/libvmmapi/ppt.c
+++ b/lib/libvmmapi/ppt.c
@@ -62,6 +62,19 @@
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
--- a/lib/libvmmapi/vmmapi.h
+++ b/lib/libvmmapi/vmmapi.h
@@ -203,6 +203,7 @@
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
--- a/sys/amd64/include/vmm.h
+++ b/sys/amd64/include/vmm.h
@@ -232,6 +232,7 @@
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
--- a/sys/amd64/include/vmm_dev.h
+++ b/sys/amd64/include/vmm_dev.h
@@ -306,6 +306,7 @@
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 @@
_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.h b/sys/amd64/vmm/io/ppt.h
--- a/sys/amd64/vmm/io/ppt.h
+++ b/sys/amd64/vmm/io/ppt.h
@@ -48,4 +48,5 @@
*/
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/io/ppt.c b/sys/amd64/vmm/io/ppt.c
--- a/sys/amd64/vmm/io/ppt.c
+++ b/sys/amd64/vmm/io/ppt.c
@@ -90,6 +90,7 @@
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_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 @@
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/vmm.c b/sys/amd64/vmm/vmm.c
--- a/sys/amd64/vmm/vmm.c
+++ b/sys/amd64/vmm/vmm.c
@@ -591,6 +591,13 @@
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
--- a/sys/amd64/vmm/vmm_dev_machdep.c
+++ b/sys/amd64/vmm/vmm_dev_machdep.c
@@ -129,6 +129,8 @@
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 @@
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.h b/usr.sbin/bhyve/pci_emul.h
--- a/usr.sbin/bhyve/pci_emul.h
+++ b/usr.sbin/bhyve/pci_emul.h
@@ -136,6 +136,7 @@
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_emul.c b/usr.sbin/bhyve/pci_emul.c
--- a/usr.sbin/bhyve/pci_emul.c
+++ b/usr.sbin/bhyve/pci_emul.c
@@ -1152,6 +1152,7 @@
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 @@
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 @@
}
pe = pi->pi_d;
+ pthread_mutex_lock(&pi->pi_cfg_lock);
/*
* Config read
@@ -2329,7 +2334,7 @@
/* 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 @@
* 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 @@
CFGWRITE(pi, coff, *valp, bytes);
}
}
+out:
+ pthread_mutex_unlock(&pi->pi_cfg_lock);
}
#ifdef __amd64__
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
@@ -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 {
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 @@
#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 @@
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 @@
/* 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 @@
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 @@
}
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
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 @@
}
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 @@
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)

File Metadata

Mime Type
text/plain
Expires
Mon, Aug 24, 9:34 AM (2 h, 53 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
37020670
Default Alt Text
D58864.id.diff (24 KB)

Event Timeline