Skip to content

Commit

Permalink
Drivers: hv: vmbus: Suspend/resume the vmbus itself for hibernation
Browse files Browse the repository at this point in the history
Before Linux enters hibernation, it sends the CHANNELMSG_UNLOAD message to
the host so all the offers are gone. After hibernation, Linux needs to
re-negotiate with the host using the same vmbus protocol version (which
was in use before hibernation), and ask the host to re-offer the vmbus
devices.

Signed-off-by: Dexuan Cui <decui@microsoft.com>
Reviewed-by: Michael Kelley <mikelley@microsoft.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
  • Loading branch information
Dexuan Cui authored and Sasha Levin committed Sep 6, 2019
1 parent e3ede02 commit f53335e
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 2 deletions.
3 changes: 1 addition & 2 deletions drivers/hv/connection.c
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,7 @@ static __u32 vmbus_get_next_version(__u32 current_version)
}
}

static int vmbus_negotiate_version(struct vmbus_channel_msginfo *msginfo,
__u32 version)
int vmbus_negotiate_version(struct vmbus_channel_msginfo *msginfo, u32 version)
{
int ret = 0;
unsigned int cur_cpu;
Expand Down
2 changes: 2 additions & 0 deletions drivers/hv/hyperv_vmbus.h
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,8 @@ struct vmbus_msginfo {

extern struct vmbus_connection vmbus_connection;

int vmbus_negotiate_version(struct vmbus_channel_msginfo *msginfo, u32 version);

static inline void vmbus_send_interrupt(u32 relid)
{
sync_set_bit(relid, vmbus_connection.send_int_page);
Expand Down
59 changes: 59 additions & 0 deletions drivers/hv/vmbus_drv.c
Original file line number Diff line number Diff line change
Expand Up @@ -2089,20 +2089,79 @@ static int vmbus_acpi_add(struct acpi_device *device)
return ret_val;
}

static int vmbus_bus_suspend(struct device *dev)
{
vmbus_initiate_unload(false);

vmbus_connection.conn_state = DISCONNECTED;

return 0;
}

static int vmbus_bus_resume(struct device *dev)
{
struct vmbus_channel_msginfo *msginfo;
size_t msgsize;
int ret;

/*
* We only use the 'vmbus_proto_version', which was in use before
* hibernation, to re-negotiate with the host.
*/
if (vmbus_proto_version == VERSION_INVAL ||
vmbus_proto_version == 0) {
pr_err("Invalid proto version = 0x%x\n", vmbus_proto_version);
return -EINVAL;
}

msgsize = sizeof(*msginfo) +
sizeof(struct vmbus_channel_initiate_contact);

msginfo = kzalloc(msgsize, GFP_KERNEL);

if (msginfo == NULL)
return -ENOMEM;

ret = vmbus_negotiate_version(msginfo, vmbus_proto_version);

kfree(msginfo);

if (ret != 0)
return ret;

vmbus_request_offers();

return 0;
}

static const struct acpi_device_id vmbus_acpi_device_ids[] = {
{"VMBUS", 0},
{"VMBus", 0},
{"", 0},
};
MODULE_DEVICE_TABLE(acpi, vmbus_acpi_device_ids);

/*
* Note: we must use SET_NOIRQ_SYSTEM_SLEEP_PM_OPS rather than
* SET_SYSTEM_SLEEP_PM_OPS, otherwise NIC SR-IOV can not work, because the
* "pci_dev_pm_ops" uses the "noirq" callbacks: in the resume path, the
* pci "noirq" restore callback runs before "non-noirq" callbacks (see
* resume_target_kernel() -> dpm_resume_start(), and hibernation_restore() ->
* dpm_resume_end()). This means vmbus_bus_resume() and the pci-hyperv's
* resume callback must also run via the "noirq" callbacks.
*/
static const struct dev_pm_ops vmbus_bus_pm = {
SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(vmbus_bus_suspend, vmbus_bus_resume)
};

static struct acpi_driver vmbus_acpi_driver = {
.name = "vmbus",
.ids = vmbus_acpi_device_ids,
.ops = {
.add = vmbus_acpi_add,
.remove = vmbus_acpi_remove,
},
.drv.pm = &vmbus_bus_pm,
};

static void hv_kexec_handler(void)
Expand Down

0 comments on commit f53335e

Please sign in to comment.