Add support for virtualizing the PCIe TPH (Transaction Processing Hints) control register. TPH may break platform isolation, so add a module parameter "enable_unsafe_tph" to allow administrators to globally control this feature. TPH control register writes are mediated to only allow valid mode settings, and TPH is automatically disabled when VFIO takes ownership of the device or when userspace closes the device file descriptor. Signed-off-by: Chengwen Feng --- drivers/vfio/pci/vfio_pci.c | 13 +++++++++++- drivers/vfio/pci/vfio_pci_config.c | 33 ++++++++++++++++++++++++++++++ drivers/vfio/pci/vfio_pci_core.c | 12 ++++++++++- include/linux/vfio_pci_core.h | 3 ++- 4 files changed, 58 insertions(+), 3 deletions(-) diff --git a/drivers/vfio/pci/vfio_pci.c b/drivers/vfio/pci/vfio_pci.c index 0c771064c0b8..6d73668459cf 100644 --- a/drivers/vfio/pci/vfio_pci.c +++ b/drivers/vfio/pci/vfio_pci.c @@ -60,6 +60,12 @@ static bool disable_denylist; module_param(disable_denylist, bool, 0444); MODULE_PARM_DESC(disable_denylist, "Disable use of device denylist. Disabling the denylist allows binding to devices with known errata that may lead to exploitable stability or security issues when accessed by untrusted users."); +#ifdef CONFIG_PCIE_TPH +static bool enable_unsafe_tph; +module_param(enable_unsafe_tph, bool, 0444); +MODULE_PARM_DESC(enable_unsafe_tph, "Enable PCIe TPH (Transaction Processing Hints) support. It may break platform isolation. If you do not know what this is for, step away. (default: false)"); +#endif + static bool vfio_pci_dev_in_denylist(struct pci_dev *pdev) { switch (pdev->vendor) { @@ -257,12 +263,17 @@ static int __init vfio_pci_init(void) { int ret; bool is_disable_vga = true; + bool is_enable_unsafe_tph = false; #ifdef CONFIG_VFIO_PCI_VGA is_disable_vga = disable_vga; #endif +#ifdef CONFIG_PCIE_TPH + is_enable_unsafe_tph = enable_unsafe_tph; +#endif - vfio_pci_core_set_params(nointxmask, is_disable_vga, disable_idle_d3); + vfio_pci_core_set_params(nointxmask, is_disable_vga, disable_idle_d3, + is_enable_unsafe_tph); /* Register and scan for devices */ ret = pci_register_driver(&vfio_pci_driver); diff --git a/drivers/vfio/pci/vfio_pci_config.c b/drivers/vfio/pci/vfio_pci_config.c index a10ed733f0e3..efb413ce7817 100644 --- a/drivers/vfio/pci/vfio_pci_config.c +++ b/drivers/vfio/pci/vfio_pci_config.c @@ -22,6 +22,7 @@ #include #include +#include #include #include #include @@ -35,6 +36,8 @@ ((offset >= PCI_BASE_ADDRESS_0 && offset < PCI_BASE_ADDRESS_5 + 4) || \ (offset >= PCI_ROM_ADDRESS && offset < PCI_ROM_ADDRESS + 4)) +extern bool enable_unsafe_tph; + /* * Lengths of PCI Config Capabilities * 0: Removed from the user visible capability list @@ -313,6 +316,35 @@ static int vfio_virt_config_read(struct vfio_pci_core_device *vdev, int pos, return count; } +static int vfio_pci_tph_config_write(struct vfio_pci_core_device *vdev, int pos, + int count, struct perm_bits *perm, + int offset, __le32 val) +{ + u32 data = le32_to_cpu(val); + int ret; + + if (!enable_unsafe_tph) + return -EOPNOTSUPP; + + if (count != 4 || offset != PCI_TPH_CTRL) + return -EINVAL; + + /* Only permit write TPH mode. */ + data &= PCI_TPH_CTRL_MODE_SEL_MASK; + if (data > PCI_TPH_ST_DS_MODE) + return -EINVAL; + + if (data == PCI_TPH_ST_NS_MODE) { + pcie_disable_tph(vdev->pdev); + return 4; + } + + ret = pcie_enable_tph(vdev->pdev, data); + if (ret) + return -EIO; + return 4; +} + static struct perm_bits direct_ro_perms = { .readfn = vfio_direct_config_read, }; @@ -1121,6 +1153,7 @@ int __init vfio_pci_init_perm_bits(void) ret |= init_pci_ext_cap_err_perm(&ecap_perms[PCI_EXT_CAP_ID_ERR]); ret |= init_pci_ext_cap_pwr_perm(&ecap_perms[PCI_EXT_CAP_ID_PWR]); ecap_perms[PCI_EXT_CAP_ID_VNDR].writefn = vfio_raw_config_write; + ecap_perms[PCI_EXT_CAP_ID_TPH].writefn = vfio_pci_tph_config_write; ecap_perms[PCI_EXT_CAP_ID_DVSEC].writefn = vfio_raw_config_write; if (ret) diff --git a/drivers/vfio/pci/vfio_pci_core.c b/drivers/vfio/pci/vfio_pci_core.c index 3f8d093aacf8..cc13fc8eea9d 100644 --- a/drivers/vfio/pci/vfio_pci_core.c +++ b/drivers/vfio/pci/vfio_pci_core.c @@ -29,6 +29,7 @@ #include #include #include +#include #if IS_ENABLED(CONFIG_EEH) #include #endif @@ -41,6 +42,7 @@ static bool nointxmask; static bool disable_vga; static bool disable_idle_d3; +bool enable_unsafe_tph; static void vfio_pci_eventfd_rcu_free(struct rcu_head *rcu) { @@ -736,6 +738,9 @@ void vfio_pci_core_close_device(struct vfio_device *core_vdev) #endif vfio_pci_dma_buf_cleanup(vdev); + /* Disable TPH when userspace closes the device FD */ + pcie_disable_tph(vdev->pdev); + vfio_pci_core_disable(vdev); mutex_lock(&vdev->igate); @@ -2205,6 +2210,9 @@ int vfio_pci_core_register_device(struct vfio_pci_core_device *vdev) if (!disable_idle_d3) pm_runtime_put(dev); + /* Disable TPH when taking over ownership of the device */ + pcie_disable_tph(pdev); + ret = vfio_register_group_dev(&vdev->vdev); if (ret) goto out_power; @@ -2570,11 +2578,13 @@ static void vfio_pci_dev_set_try_reset(struct vfio_device_set *dev_set) } void vfio_pci_core_set_params(bool is_nointxmask, bool is_disable_vga, - bool is_disable_idle_d3) + bool is_disable_idle_d3, + bool is_enable_unsafe_tph) { nointxmask = is_nointxmask; disable_vga = is_disable_vga; disable_idle_d3 = is_disable_idle_d3; + enable_unsafe_tph = is_enable_unsafe_tph; } EXPORT_SYMBOL_GPL(vfio_pci_core_set_params); diff --git a/include/linux/vfio_pci_core.h b/include/linux/vfio_pci_core.h index 2ebba746c18f..33e7cd1dae87 100644 --- a/include/linux/vfio_pci_core.h +++ b/include/linux/vfio_pci_core.h @@ -157,7 +157,8 @@ int vfio_pci_core_register_dev_region(struct vfio_pci_core_device *vdev, const struct vfio_pci_regops *ops, size_t size, u32 flags, void *data); void vfio_pci_core_set_params(bool nointxmask, bool is_disable_vga, - bool is_disable_idle_d3); + bool is_disable_idle_d3, + bool is_enable_unsafe_tph); void vfio_pci_core_close_device(struct vfio_device *core_vdev); int vfio_pci_core_init_dev(struct vfio_device *core_vdev); void vfio_pci_core_release_dev(struct vfio_device *core_vdev); -- 2.17.1