[PATCH v2][PATCH net] hv_netvsc: Add the support of hibernation

From: Dexuan Cui
Date: Wed Sep 25 2019 - 18:04:11 EST


The existing netvsc_detach() and netvsc_attach() APIs make it easy to
implement the suspend/resume callbacks.

Signed-off-by: Dexuan Cui <decui@xxxxxxxxxxxxx>
---

This patch is basically a pure Hyper-V specific change. I request this
patch should go through Sasha's Hyper-V tree rather than the net tree.

Sasha's Hyper-V tree is here:
https://git.kernel.org/pub/scm/linux/kernel/git/hyperv/linux.git

Previously there was a dependency on the commit 271b2224d42f ("Drivers:
hv: vmbus: Implement suspend/resume for VSC drivers for hibernation"),
which was only on Sasha Levin's Hyper-V tree's hyperv-next branch:
https://git.kernel.org/pub/scm/linux/kernel/git/hyperv/linux.git/log/?h=hyperv-next
. Now the patch has been merged into Linus's master tree, but as of now,
the patch (271b2224d42f) has not appeared in the net.git tree, so IMO
it's better for this patch to go through the Hyper-V tree. The added
code in this patch is unlikely to cause a conflict.

In v2:
Removed the superfluous "cancel_work_sync(&nvdev->subchan_work)".

Changed the [PATCH net-next] to [PATCH net] in the Subject, because
IMO this is more of a bug fix rather than a new feaure.

No other change.

drivers/net/hyperv/hyperv_net.h | 3 ++
drivers/net/hyperv/netvsc_drv.c | 57 +++++++++++++++++++++++++++++++++
2 files changed, 60 insertions(+)

diff --git a/drivers/net/hyperv/hyperv_net.h b/drivers/net/hyperv/hyperv_net.h
index ecc9af050387..b8763ee4c0d0 100644
--- a/drivers/net/hyperv/hyperv_net.h
+++ b/drivers/net/hyperv/hyperv_net.h
@@ -952,6 +952,9 @@ struct net_device_context {
u32 vf_alloc;
/* Serial number of the VF to team with */
u32 vf_serial;
+
+ /* Used to temporarily save the config info across hibernation */
+ struct netvsc_device_info *saved_netvsc_dev_info;
};

/* Per channel data */
diff --git a/drivers/net/hyperv/netvsc_drv.c b/drivers/net/hyperv/netvsc_drv.c
index afdcc5664ea6..53a9451a58a7 100644
--- a/drivers/net/hyperv/netvsc_drv.c
+++ b/drivers/net/hyperv/netvsc_drv.c
@@ -2392,6 +2392,61 @@ static int netvsc_remove(struct hv_device *dev)
return 0;
}

+static int netvsc_suspend(struct hv_device *dev)
+{
+ struct net_device_context *ndev_ctx;
+ struct net_device *vf_netdev, *net;
+ struct netvsc_device *nvdev;
+ int ret;
+
+ net = hv_get_drvdata(dev);
+
+ ndev_ctx = netdev_priv(net);
+ cancel_delayed_work_sync(&ndev_ctx->dwork);
+
+ rtnl_lock();
+
+ nvdev = rtnl_dereference(ndev_ctx->nvdev);
+ if (nvdev == NULL) {
+ ret = -ENODEV;
+ goto out;
+ }
+
+ vf_netdev = rtnl_dereference(ndev_ctx->vf_netdev);
+ if (vf_netdev)
+ netvsc_unregister_vf(vf_netdev);
+
+ /* Save the current config info */
+ ndev_ctx->saved_netvsc_dev_info = netvsc_devinfo_get(nvdev);
+
+ ret = netvsc_detach(net, nvdev);
+out:
+ rtnl_unlock();
+
+ return ret;
+}
+
+static int netvsc_resume(struct hv_device *dev)
+{
+ struct net_device *net = hv_get_drvdata(dev);
+ struct net_device_context *net_device_ctx;
+ struct netvsc_device_info *device_info;
+ int ret;
+
+ rtnl_lock();
+
+ net_device_ctx = netdev_priv(net);
+ device_info = net_device_ctx->saved_netvsc_dev_info;
+
+ ret = netvsc_attach(net, device_info);
+
+ rtnl_unlock();
+
+ kfree(device_info);
+ net_device_ctx->saved_netvsc_dev_info = NULL;
+
+ return ret;
+}
static const struct hv_vmbus_device_id id_table[] = {
/* Network guid */
{ HV_NIC_GUID, },
@@ -2406,6 +2461,8 @@ static struct hv_driver netvsc_drv = {
.id_table = id_table,
.probe = netvsc_probe,
.remove = netvsc_remove,
+ .suspend = netvsc_suspend,
+ .resume = netvsc_resume,
.driver = {
.probe_type = PROBE_FORCE_SYNCHRONOUS,
},
--
2.19.1