]> www.infradead.org Git - users/dwmw2/linux.git/commitdiff
virtio_pci: Add support for PCIe Function Level Reset
authorIsrael Rukshin <israelr@nvidia.com>
Wed, 27 Nov 2024 06:57:31 +0000 (08:57 +0200)
committerMichael S. Tsirkin <mst@redhat.com>
Mon, 27 Jan 2025 14:39:26 +0000 (09:39 -0500)
Implement support for Function Level Reset (FLR) in virtio_pci devices.
This change adds reset_prepare and reset_done callbacks, allowing
drivers to properly handle FLR operations.

Without this patch, performing and recovering from an FLR is not possible
for virtio_pci devices. This implementation ensures proper FLR handling
and recovery for both physical and virtual functions.

The device reset can be triggered in case of error or manually via
sysfs:
echo 1 > /sys/bus/pci/devices/$PCI_ADDR/reset

Signed-off-by: Israel Rukshin <israelr@nvidia.com>
Reviewed-by: Max Gurtovoy <mgurtovoy@nvidia.com>
Message-Id: <1732690652-3065-2-git-send-email-israelr@nvidia.com>
Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
drivers/virtio/virtio.c
drivers/virtio/virtio_pci_common.c
include/linux/virtio.h

index b9095751e43bb7db5fc991b0cc0979d2e86f7b9b..c1cc1157b38067026bf0fd3cdd1c88bbbc091dea 100644 (file)
@@ -527,29 +527,7 @@ void unregister_virtio_device(struct virtio_device *dev)
 }
 EXPORT_SYMBOL_GPL(unregister_virtio_device);
 
-#ifdef CONFIG_PM_SLEEP
-int virtio_device_freeze(struct virtio_device *dev)
-{
-       struct virtio_driver *drv = drv_to_virtio(dev->dev.driver);
-       int ret;
-
-       virtio_config_core_disable(dev);
-
-       dev->failed = dev->config->get_status(dev) & VIRTIO_CONFIG_S_FAILED;
-
-       if (drv && drv->freeze) {
-               ret = drv->freeze(dev);
-               if (ret) {
-                       virtio_config_core_enable(dev);
-                       return ret;
-               }
-       }
-
-       return 0;
-}
-EXPORT_SYMBOL_GPL(virtio_device_freeze);
-
-int virtio_device_restore(struct virtio_device *dev)
+static int virtio_device_restore_priv(struct virtio_device *dev, bool restore)
 {
        struct virtio_driver *drv = drv_to_virtio(dev->dev.driver);
        int ret;
@@ -580,8 +558,14 @@ int virtio_device_restore(struct virtio_device *dev)
        if (ret)
                goto err;
 
-       if (drv->restore) {
-               ret = drv->restore(dev);
+       if (restore) {
+               if (drv->restore) {
+                       ret = drv->restore(dev);
+                       if (ret)
+                               goto err;
+               }
+       } else {
+               ret = drv->reset_done(dev);
                if (ret)
                        goto err;
        }
@@ -598,9 +582,69 @@ err:
        virtio_add_status(dev, VIRTIO_CONFIG_S_FAILED);
        return ret;
 }
+
+#ifdef CONFIG_PM_SLEEP
+int virtio_device_freeze(struct virtio_device *dev)
+{
+       struct virtio_driver *drv = drv_to_virtio(dev->dev.driver);
+       int ret;
+
+       virtio_config_core_disable(dev);
+
+       dev->failed = dev->config->get_status(dev) & VIRTIO_CONFIG_S_FAILED;
+
+       if (drv && drv->freeze) {
+               ret = drv->freeze(dev);
+               if (ret) {
+                       virtio_config_core_enable(dev);
+                       return ret;
+               }
+       }
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(virtio_device_freeze);
+
+int virtio_device_restore(struct virtio_device *dev)
+{
+       return virtio_device_restore_priv(dev, true);
+}
 EXPORT_SYMBOL_GPL(virtio_device_restore);
 #endif
 
+int virtio_device_reset_prepare(struct virtio_device *dev)
+{
+       struct virtio_driver *drv = drv_to_virtio(dev->dev.driver);
+       int ret;
+
+       if (!drv || !drv->reset_prepare)
+               return -EOPNOTSUPP;
+
+       virtio_config_core_disable(dev);
+
+       dev->failed = dev->config->get_status(dev) & VIRTIO_CONFIG_S_FAILED;
+
+       ret = drv->reset_prepare(dev);
+       if (ret) {
+               virtio_config_core_enable(dev);
+               return ret;
+       }
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(virtio_device_reset_prepare);
+
+int virtio_device_reset_done(struct virtio_device *dev)
+{
+       struct virtio_driver *drv = drv_to_virtio(dev->dev.driver);
+
+       if (!drv || !drv->reset_done)
+               return -EOPNOTSUPP;
+
+       return virtio_device_restore_priv(dev, false);
+}
+EXPORT_SYMBOL_GPL(virtio_device_reset_done);
+
 static int virtio_init(void)
 {
        if (bus_register(&virtio_bus) != 0)
index 88074451dd6151f03cfa384ff0893852f302eda4..d6d79af44569b8276ec378d9cd4899be436979e9 100644 (file)
@@ -794,6 +794,46 @@ static int virtio_pci_sriov_configure(struct pci_dev *pci_dev, int num_vfs)
        return num_vfs;
 }
 
+static void virtio_pci_reset_prepare(struct pci_dev *pci_dev)
+{
+       struct virtio_pci_device *vp_dev = pci_get_drvdata(pci_dev);
+       int ret = 0;
+
+       ret = virtio_device_reset_prepare(&vp_dev->vdev);
+       if (ret) {
+               if (ret != -EOPNOTSUPP)
+                       dev_warn(&pci_dev->dev, "Reset prepare failure: %d",
+                                ret);
+               return;
+       }
+
+       if (pci_is_enabled(pci_dev))
+               pci_disable_device(pci_dev);
+}
+
+static void virtio_pci_reset_done(struct pci_dev *pci_dev)
+{
+       struct virtio_pci_device *vp_dev = pci_get_drvdata(pci_dev);
+       int ret;
+
+       if (pci_is_enabled(pci_dev))
+               return;
+
+       ret = pci_enable_device(pci_dev);
+       if (!ret) {
+               pci_set_master(pci_dev);
+               ret = virtio_device_reset_done(&vp_dev->vdev);
+       }
+
+       if (ret && ret != -EOPNOTSUPP)
+               dev_warn(&pci_dev->dev, "Reset done failure: %d", ret);
+}
+
+static const struct pci_error_handlers virtio_pci_err_handler = {
+       .reset_prepare  = virtio_pci_reset_prepare,
+       .reset_done     = virtio_pci_reset_done,
+};
+
 static struct pci_driver virtio_pci_driver = {
        .name           = "virtio-pci",
        .id_table       = virtio_pci_id_table,
@@ -803,6 +843,7 @@ static struct pci_driver virtio_pci_driver = {
        .driver.pm      = &virtio_pci_pm_ops,
 #endif
        .sriov_configure = virtio_pci_sriov_configure,
+       .err_handler    = &virtio_pci_err_handler,
 };
 
 struct virtio_device *virtio_pci_vf_get_pf_dev(struct pci_dev *pdev)
index dd88682e27e319c0f133af2de8b81c3dc8d45086..4d16c13d0df580b69d740aff13939e9ea99062e8 100644 (file)
@@ -190,6 +190,8 @@ int virtio_device_freeze(struct virtio_device *dev);
 int virtio_device_restore(struct virtio_device *dev);
 #endif
 void virtio_reset_device(struct virtio_device *dev);
+int virtio_device_reset_prepare(struct virtio_device *dev);
+int virtio_device_reset_done(struct virtio_device *dev);
 
 size_t virtio_max_dma_size(const struct virtio_device *vdev);
 
@@ -214,6 +216,10 @@ size_t virtio_max_dma_size(const struct virtio_device *vdev);
  *    changes; may be called in interrupt context.
  * @freeze: optional function to call during suspend/hibernation.
  * @restore: optional function to call on resume.
+ * @reset_prepare: optional function to call when a transport specific reset
+ *    occurs.
+ * @reset_done: optional function to call after transport specific reset
+ *    operation has finished.
  */
 struct virtio_driver {
        struct device_driver driver;
@@ -229,6 +235,8 @@ struct virtio_driver {
        void (*config_changed)(struct virtio_device *dev);
        int (*freeze)(struct virtio_device *dev);
        int (*restore)(struct virtio_device *dev);
+       int (*reset_prepare)(struct virtio_device *dev);
+       int (*reset_done)(struct virtio_device *dev);
 };
 
 #define drv_to_virtio(__drv)   container_of_const(__drv, struct virtio_driver, driver)