Drivers: hv: vmbus: Suspend/resume the vmbus itself for hibernation

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>
This commit is contained in:
Dexuan Cui 2019-09-05 23:01:19 +00:00 committed by Sasha Levin
parent e3ede02add
commit f53335e328
3 changed files with 62 additions and 2 deletions

View File

@ -59,8 +59,7 @@ static __u32 vmbus_get_next_version(__u32 current_version)
} }
} }
static int vmbus_negotiate_version(struct vmbus_channel_msginfo *msginfo, int vmbus_negotiate_version(struct vmbus_channel_msginfo *msginfo, u32 version)
__u32 version)
{ {
int ret = 0; int ret = 0;
unsigned int cur_cpu; unsigned int cur_cpu;

View File

@ -274,6 +274,8 @@ struct vmbus_msginfo {
extern struct vmbus_connection vmbus_connection; 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) static inline void vmbus_send_interrupt(u32 relid)
{ {
sync_set_bit(relid, vmbus_connection.send_int_page); sync_set_bit(relid, vmbus_connection.send_int_page);

View File

@ -2089,6 +2089,51 @@ acpi_walk_err:
return ret_val; 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[] = { static const struct acpi_device_id vmbus_acpi_device_ids[] = {
{"VMBUS", 0}, {"VMBUS", 0},
{"VMBus", 0}, {"VMBus", 0},
@ -2096,6 +2141,19 @@ static const struct acpi_device_id vmbus_acpi_device_ids[] = {
}; };
MODULE_DEVICE_TABLE(acpi, vmbus_acpi_device_ids); 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 = { static struct acpi_driver vmbus_acpi_driver = {
.name = "vmbus", .name = "vmbus",
.ids = vmbus_acpi_device_ids, .ids = vmbus_acpi_device_ids,
@ -2103,6 +2161,7 @@ static struct acpi_driver vmbus_acpi_driver = {
.add = vmbus_acpi_add, .add = vmbus_acpi_add,
.remove = vmbus_acpi_remove, .remove = vmbus_acpi_remove,
}, },
.drv.pm = &vmbus_bus_pm,
}; };
static void hv_kexec_handler(void) static void hv_kexec_handler(void)