virtio_pci: Add support for PCIe Function Level Reset

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>
This commit is contained in:
Israel Rukshin 2024-11-27 08:57:31 +02:00 committed by Michael S. Tsirkin
parent a3b9c053d8
commit a0ec4fb63f
3 changed files with 118 additions and 25 deletions

View 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)

View 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)

View 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)