Commit d0fa9250 authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'hyperv-next-signed' of git://git.kernel.org/pub/scm/linux/kernel/git/hyperv/linux

Pull Hyper-V updates from Sasha Levin:

 - Most of the commits here are work to enable host-initiated
   hibernation support by Dexuan Cui.

 - Fix for a warning shown when host sends non-aligned balloon requests
   by Tianyu Lan.

* tag 'hyperv-next-signed' of git://git.kernel.org/pub/scm/linux/kernel/git/hyperv/linux:
  hv_utils: Add the support of hibernation
  hv_utils: Support host-initiated hibernation request
  hv_utils: Support host-initiated restart request
  Tools: hv: Reopen the devices if read() or write() returns errors
  video: hyperv: hyperv_fb: Use physical memory for fb on HyperV Gen 1 VMs.
  Drivers: hv: vmbus: Ignore CHANNELMSG_TL_CONNECT_RESULT(23)
  video: hyperv_fb: Fix hibernation for the deferred IO feature
  Input: hyperv-keyboard: Add the support of hibernation
  hv_balloon: Balloon up according to request page number
parents 46d6b7be 54e19d34
...@@ -1351,6 +1351,8 @@ channel_message_table[CHANNELMSG_COUNT] = { ...@@ -1351,6 +1351,8 @@ channel_message_table[CHANNELMSG_COUNT] = {
{ CHANNELMSG_19, 0, NULL }, { CHANNELMSG_19, 0, NULL },
{ CHANNELMSG_20, 0, NULL }, { CHANNELMSG_20, 0, NULL },
{ CHANNELMSG_TL_CONNECT_REQUEST, 0, NULL }, { CHANNELMSG_TL_CONNECT_REQUEST, 0, NULL },
{ CHANNELMSG_22, 0, NULL },
{ CHANNELMSG_TL_CONNECT_RESULT, 0, NULL },
}; };
/* /*
...@@ -1362,25 +1364,16 @@ void vmbus_onmessage(void *context) ...@@ -1362,25 +1364,16 @@ void vmbus_onmessage(void *context)
{ {
struct hv_message *msg = context; struct hv_message *msg = context;
struct vmbus_channel_message_header *hdr; struct vmbus_channel_message_header *hdr;
int size;
hdr = (struct vmbus_channel_message_header *)msg->u.payload; hdr = (struct vmbus_channel_message_header *)msg->u.payload;
size = msg->header.payload_size;
trace_vmbus_on_message(hdr); trace_vmbus_on_message(hdr);
if (hdr->msgtype >= CHANNELMSG_COUNT) { /*
pr_err("Received invalid channel message type %d size %d\n", * vmbus_on_msg_dpc() makes sure the hdr->msgtype here can not go
hdr->msgtype, size); * out of bound and the message_handler pointer can not be NULL.
print_hex_dump_bytes("", DUMP_PREFIX_NONE, */
(unsigned char *)msg->u.payload, size);
return;
}
if (channel_message_table[hdr->msgtype].message_handler)
channel_message_table[hdr->msgtype].message_handler(hdr); channel_message_table[hdr->msgtype].message_handler(hdr);
else
pr_err("Unhandled channel message type %d\n", hdr->msgtype);
} }
/* /*
......
...@@ -1217,10 +1217,7 @@ static unsigned int alloc_balloon_pages(struct hv_dynmem_device *dm, ...@@ -1217,10 +1217,7 @@ static unsigned int alloc_balloon_pages(struct hv_dynmem_device *dm,
unsigned int i, j; unsigned int i, j;
struct page *pg; struct page *pg;
if (num_pages < alloc_unit) for (i = 0; i < num_pages / alloc_unit; i++) {
return 0;
for (i = 0; (i * alloc_unit) < num_pages; i++) {
if (bl_resp->hdr.size + sizeof(union dm_mem_page_range) > if (bl_resp->hdr.size + sizeof(union dm_mem_page_range) >
HV_HYP_PAGE_SIZE) HV_HYP_PAGE_SIZE)
return i * alloc_unit; return i * alloc_unit;
...@@ -1258,7 +1255,7 @@ static unsigned int alloc_balloon_pages(struct hv_dynmem_device *dm, ...@@ -1258,7 +1255,7 @@ static unsigned int alloc_balloon_pages(struct hv_dynmem_device *dm,
} }
return num_pages; return i * alloc_unit;
} }
static void balloon_up(struct work_struct *dummy) static void balloon_up(struct work_struct *dummy)
...@@ -1273,9 +1270,6 @@ static void balloon_up(struct work_struct *dummy) ...@@ -1273,9 +1270,6 @@ static void balloon_up(struct work_struct *dummy)
long avail_pages; long avail_pages;
unsigned long floor; unsigned long floor;
/* The host balloons pages in 2M granularity. */
WARN_ON_ONCE(num_pages % PAGES_IN_2M != 0);
/* /*
* We will attempt 2M allocations. However, if we fail to * We will attempt 2M allocations. However, if we fail to
* allocate 2M chunks, we will go back to PAGE_SIZE allocations. * allocate 2M chunks, we will go back to PAGE_SIZE allocations.
...@@ -1285,14 +1279,13 @@ static void balloon_up(struct work_struct *dummy) ...@@ -1285,14 +1279,13 @@ static void balloon_up(struct work_struct *dummy)
avail_pages = si_mem_available(); avail_pages = si_mem_available();
floor = compute_balloon_floor(); floor = compute_balloon_floor();
/* Refuse to balloon below the floor, keep the 2M granularity. */ /* Refuse to balloon below the floor. */
if (avail_pages < num_pages || avail_pages - num_pages < floor) { if (avail_pages < num_pages || avail_pages - num_pages < floor) {
pr_warn("Balloon request will be partially fulfilled. %s\n", pr_warn("Balloon request will be partially fulfilled. %s\n",
avail_pages < num_pages ? "Not enough memory." : avail_pages < num_pages ? "Not enough memory." :
"Balloon floor reached."); "Balloon floor reached.");
num_pages = avail_pages > floor ? (avail_pages - floor) : 0; num_pages = avail_pages > floor ? (avail_pages - floor) : 0;
num_pages -= num_pages % PAGES_IN_2M;
} }
while (!done) { while (!done) {
......
...@@ -346,9 +346,61 @@ int hv_fcopy_init(struct hv_util_service *srv) ...@@ -346,9 +346,61 @@ int hv_fcopy_init(struct hv_util_service *srv)
return 0; return 0;
} }
static void hv_fcopy_cancel_work(void)
{
cancel_delayed_work_sync(&fcopy_timeout_work);
cancel_work_sync(&fcopy_send_work);
}
int hv_fcopy_pre_suspend(void)
{
struct vmbus_channel *channel = fcopy_transaction.recv_channel;
struct hv_fcopy_hdr *fcopy_msg;
/*
* Fake a CANCEL_FCOPY message for the user space daemon in case the
* daemon is in the middle of copying some file. It doesn't matter if
* there is already a message pending to be delivered to the user
* space since we force fcopy_transaction.state to be HVUTIL_READY, so
* the user space daemon's write() will fail with EINVAL (see
* fcopy_on_msg()), and the daemon will reset the device by closing
* and re-opening it.
*/
fcopy_msg = kzalloc(sizeof(*fcopy_msg), GFP_KERNEL);
if (!fcopy_msg)
return -ENOMEM;
tasklet_disable(&channel->callback_event);
fcopy_msg->operation = CANCEL_FCOPY;
hv_fcopy_cancel_work();
/* We don't care about the return value. */
hvutil_transport_send(hvt, fcopy_msg, sizeof(*fcopy_msg), NULL);
kfree(fcopy_msg);
fcopy_transaction.state = HVUTIL_READY;
/* tasklet_enable() will be called in hv_fcopy_pre_resume(). */
return 0;
}
int hv_fcopy_pre_resume(void)
{
struct vmbus_channel *channel = fcopy_transaction.recv_channel;
tasklet_enable(&channel->callback_event);
return 0;
}
void hv_fcopy_deinit(void) void hv_fcopy_deinit(void)
{ {
fcopy_transaction.state = HVUTIL_DEVICE_DYING; fcopy_transaction.state = HVUTIL_DEVICE_DYING;
cancel_delayed_work_sync(&fcopy_timeout_work);
hv_fcopy_cancel_work();
hvutil_transport_destroy(hvt); hvutil_transport_destroy(hvt);
} }
...@@ -758,11 +758,50 @@ hv_kvp_init(struct hv_util_service *srv) ...@@ -758,11 +758,50 @@ hv_kvp_init(struct hv_util_service *srv)
return 0; return 0;
} }
void hv_kvp_deinit(void) static void hv_kvp_cancel_work(void)
{ {
kvp_transaction.state = HVUTIL_DEVICE_DYING;
cancel_delayed_work_sync(&kvp_host_handshake_work); cancel_delayed_work_sync(&kvp_host_handshake_work);
cancel_delayed_work_sync(&kvp_timeout_work); cancel_delayed_work_sync(&kvp_timeout_work);
cancel_work_sync(&kvp_sendkey_work); cancel_work_sync(&kvp_sendkey_work);
}
int hv_kvp_pre_suspend(void)
{
struct vmbus_channel *channel = kvp_transaction.recv_channel;
tasklet_disable(&channel->callback_event);
/*
* If there is a pending transtion, it's unnecessary to tell the host
* that the transaction will fail, because that is implied when
* util_suspend() calls vmbus_close() later.
*/
hv_kvp_cancel_work();
/*
* Forece the state to READY to handle the ICMSGTYPE_NEGOTIATE message
* later. The user space daemon may go out of order and its write()
* may fail with EINVAL: this doesn't matter since the daemon will
* reset the device by closing and re-opening it.
*/
kvp_transaction.state = HVUTIL_READY;
return 0;
}
int hv_kvp_pre_resume(void)
{
struct vmbus_channel *channel = kvp_transaction.recv_channel;
tasklet_enable(&channel->callback_event);
return 0;
}
void hv_kvp_deinit(void)
{
kvp_transaction.state = HVUTIL_DEVICE_DYING;
hv_kvp_cancel_work();
hvutil_transport_destroy(hvt); hvutil_transport_destroy(hvt);
} }
...@@ -379,10 +379,61 @@ hv_vss_init(struct hv_util_service *srv) ...@@ -379,10 +379,61 @@ hv_vss_init(struct hv_util_service *srv)
return 0; return 0;
} }
void hv_vss_deinit(void) static void hv_vss_cancel_work(void)
{ {
vss_transaction.state = HVUTIL_DEVICE_DYING;
cancel_delayed_work_sync(&vss_timeout_work); cancel_delayed_work_sync(&vss_timeout_work);
cancel_work_sync(&vss_handle_request_work); cancel_work_sync(&vss_handle_request_work);
}
int hv_vss_pre_suspend(void)
{
struct vmbus_channel *channel = vss_transaction.recv_channel;
struct hv_vss_msg *vss_msg;
/*
* Fake a THAW message for the user space daemon in case the daemon
* has frozen the file systems. It doesn't matter if there is already
* a message pending to be delivered to the user space since we force
* vss_transaction.state to be HVUTIL_READY, so the user space daemon's
* write() will fail with EINVAL (see vss_on_msg()), and the daemon
* will reset the device by closing and re-opening it.
*/
vss_msg = kzalloc(sizeof(*vss_msg), GFP_KERNEL);
if (!vss_msg)
return -ENOMEM;
tasklet_disable(&channel->callback_event);
vss_msg->vss_hdr.operation = VSS_OP_THAW;
/* Cancel any possible pending work. */
hv_vss_cancel_work();
/* We don't care about the return value. */
hvutil_transport_send(hvt, vss_msg, sizeof(*vss_msg), NULL);
kfree(vss_msg);
vss_transaction.state = HVUTIL_READY;
/* tasklet_enable() will be called in hv_vss_pre_resume(). */
return 0;
}
int hv_vss_pre_resume(void)
{
struct vmbus_channel *channel = vss_transaction.recv_channel;
tasklet_enable(&channel->callback_event);
return 0;
}
void hv_vss_deinit(void)
{
vss_transaction.state = HVUTIL_DEVICE_DYING;
hv_vss_cancel_work();
hvutil_transport_destroy(hvt); hvutil_transport_destroy(hvt);
} }
...@@ -24,6 +24,10 @@ ...@@ -24,6 +24,10 @@
#define SD_MAJOR 3 #define SD_MAJOR 3
#define SD_MINOR 0 #define SD_MINOR 0
#define SD_MINOR_1 1
#define SD_MINOR_2 2
#define SD_VERSION_3_1 (SD_MAJOR << 16 | SD_MINOR_1)
#define SD_VERSION_3_2 (SD_MAJOR << 16 | SD_MINOR_2)
#define SD_VERSION (SD_MAJOR << 16 | SD_MINOR) #define SD_VERSION (SD_MAJOR << 16 | SD_MINOR)
#define SD_MAJOR_1 1 #define SD_MAJOR_1 1
...@@ -50,8 +54,10 @@ static int sd_srv_version; ...@@ -50,8 +54,10 @@ static int sd_srv_version;
static int ts_srv_version; static int ts_srv_version;
static int hb_srv_version; static int hb_srv_version;
#define SD_VER_COUNT 2 #define SD_VER_COUNT 4
static const int sd_versions[] = { static const int sd_versions[] = {
SD_VERSION_3_2,
SD_VERSION_3_1,
SD_VERSION, SD_VERSION,
SD_VERSION_1 SD_VERSION_1
}; };
...@@ -75,18 +81,56 @@ static const int fw_versions[] = { ...@@ -75,18 +81,56 @@ static const int fw_versions[] = {
UTIL_WS2K8_FW_VERSION UTIL_WS2K8_FW_VERSION
}; };
/*
* Send the "hibernate" udev event in a thread context.
*/
struct hibernate_work_context {
struct work_struct work;
struct hv_device *dev;
};
static struct hibernate_work_context hibernate_context;
static bool hibernation_supported;
static void send_hibernate_uevent(struct work_struct *work)
{
char *uevent_env[2] = { "EVENT=hibernate", NULL };
struct hibernate_work_context *ctx;
ctx = container_of(work, struct hibernate_work_context, work);
kobject_uevent_env(&ctx->dev->device.kobj, KOBJ_CHANGE, uevent_env);
pr_info("Sent hibernation uevent\n");
}
static int hv_shutdown_init(struct hv_util_service *srv)
{
struct vmbus_channel *channel = srv->channel;
INIT_WORK(&hibernate_context.work, send_hibernate_uevent);
hibernate_context.dev = channel->device_obj;
hibernation_supported = hv_is_hibernation_supported();
return 0;
}
static void shutdown_onchannelcallback(void *context); static void shutdown_onchannelcallback(void *context);
static struct hv_util_service util_shutdown = { static struct hv_util_service util_shutdown = {
.util_cb = shutdown_onchannelcallback, .util_cb = shutdown_onchannelcallback,
.util_init = hv_shutdown_init,
}; };
static int hv_timesync_init(struct hv_util_service *srv); static int hv_timesync_init(struct hv_util_service *srv);
static int hv_timesync_pre_suspend(void);
static void hv_timesync_deinit(void); static void hv_timesync_deinit(void);
static void timesync_onchannelcallback(void *context); static void timesync_onchannelcallback(void *context);
static struct hv_util_service util_timesynch = { static struct hv_util_service util_timesynch = {
.util_cb = timesync_onchannelcallback, .util_cb = timesync_onchannelcallback,
.util_init = hv_timesync_init, .util_init = hv_timesync_init,
.util_pre_suspend = hv_timesync_pre_suspend,
.util_deinit = hv_timesync_deinit, .util_deinit = hv_timesync_deinit,
}; };
...@@ -98,18 +142,24 @@ static struct hv_util_service util_heartbeat = { ...@@ -98,18 +142,24 @@ static struct hv_util_service util_heartbeat = {
static struct hv_util_service util_kvp = { static struct hv_util_service util_kvp = {
.util_cb = hv_kvp_onchannelcallback, .util_cb = hv_kvp_onchannelcallback,
.util_init = hv_kvp_init, .util_init = hv_kvp_init,
.util_pre_suspend = hv_kvp_pre_suspend,
.util_pre_resume = hv_kvp_pre_resume,
.util_deinit = hv_kvp_deinit, .util_deinit = hv_kvp_deinit,
}; };
static struct hv_util_service util_vss = { static struct hv_util_service util_vss = {
.util_cb = hv_vss_onchannelcallback, .util_cb = hv_vss_onchannelcallback,
.util_init = hv_vss_init, .util_init = hv_vss_init,
.util_pre_suspend = hv_vss_pre_suspend,
.util_pre_resume = hv_vss_pre_resume,
.util_deinit = hv_vss_deinit, .util_deinit = hv_vss_deinit,
}; };
static struct hv_util_service util_fcopy = { static struct hv_util_service util_fcopy = {
.util_cb = hv_fcopy_onchannelcallback, .util_cb = hv_fcopy_onchannelcallback,
.util_init = hv_fcopy_init, .util_init = hv_fcopy_init,
.util_pre_suspend = hv_fcopy_pre_suspend,
.util_pre_resume = hv_fcopy_pre_resume,
.util_deinit = hv_fcopy_deinit, .util_deinit = hv_fcopy_deinit,
}; };
...@@ -118,17 +168,27 @@ static void perform_shutdown(struct work_struct *dummy) ...@@ -118,17 +168,27 @@ static void perform_shutdown(struct work_struct *dummy)
orderly_poweroff(true); orderly_poweroff(true);
} }
static void perform_restart(struct work_struct *dummy)
{
orderly_reboot();
}
/* /*
* Perform the shutdown operation in a thread context. * Perform the shutdown operation in a thread context.
*/ */
static DECLARE_WORK(shutdown_work, perform_shutdown); static DECLARE_WORK(shutdown_work, perform_shutdown);
/*
* Perform the restart operation in a thread context.
*/
static DECLARE_WORK(restart_work, perform_restart);
static void shutdown_onchannelcallback(void *context) static void shutdown_onchannelcallback(void *context)
{ {
struct vmbus_channel *channel = context; struct vmbus_channel *channel = context;
struct work_struct *work = NULL;
u32 recvlen; u32 recvlen;
u64 requestid; u64 requestid;
bool execute_shutdown = false;
u8 *shut_txf_buf = util_shutdown.recv_buffer; u8 *shut_txf_buf = util_shutdown.recv_buffer;
struct shutdown_msg_data *shutdown_msg; struct shutdown_msg_data *shutdown_msg;
...@@ -157,19 +217,37 @@ static void shutdown_onchannelcallback(void *context) ...@@ -157,19 +217,37 @@ static void shutdown_onchannelcallback(void *context)
sizeof(struct vmbuspipe_hdr) + sizeof(struct vmbuspipe_hdr) +
sizeof(struct icmsg_hdr)]; sizeof(struct icmsg_hdr)];
/*
* shutdown_msg->flags can be 0(shut down), 2(reboot),
* or 4(hibernate). It may bitwise-OR 1, which means
* performing the request by force. Linux always tries
* to perform the request by force.
*/
switch (shutdown_msg->flags) { switch (shutdown_msg->flags) {
case 0: case 0:
case 1: case 1:
icmsghdrp->status = HV_S_OK; icmsghdrp->status = HV_S_OK;
execute_shutdown = true; work = &shutdown_work;
pr_info("Shutdown request received -" pr_info("Shutdown request received -"
" graceful shutdown initiated\n"); " graceful shutdown initiated\n");
break; break;
case 2:
case 3:
icmsghdrp->status = HV_S_OK;
work = &restart_work;
pr_info("Restart request received -"
" graceful restart initiated\n");
break;
case 4:
case 5:
pr_info("Hibernation request received\n");
icmsghdrp->status = hibernation_supported ?
HV_S_OK : HV_E_FAIL;
if (hibernation_supported)
work = &hibernate_context.work;
break;
default: default:
icmsghdrp->status = HV_E_FAIL; icmsghdrp->status = HV_E_FAIL;
execute_shutdown = false;
pr_info("Shutdown request received -" pr_info("Shutdown request received -"
" Invalid request\n"); " Invalid request\n");
break; break;
...@@ -184,8 +262,8 @@ static void shutdown_onchannelcallback(void *context) ...@@ -184,8 +262,8 @@ static void shutdown_onchannelcallback(void *context)
VM_PKT_DATA_INBAND, 0); VM_PKT_DATA_INBAND, 0);
} }
if (execute_shutdown == true) if (work)
schedule_work(&shutdown_work); schedule_work(work);
} }
/* /*
...@@ -441,6 +519,44 @@ static int util_remove(struct hv_device *dev) ...@@ -441,6 +519,44 @@ static int util_remove(struct hv_device *dev)
return 0; return 0;
} }
/*
* When we're in util_suspend(), all the userspace processes have been frozen
* (refer to hibernate() -> freeze_processes()). The userspace is thawed only
* after the whole resume procedure, including util_resume(), finishes.
*/
static int util_suspend(struct hv_device *dev)
{
struct hv_util_service *srv = hv_get_drvdata(dev);
int ret = 0;
if (srv->util_pre_suspend) {
ret = srv->util_pre_suspend();
if (ret)
return ret;
}
vmbus_close(dev->channel);
return 0;
}
static int util_resume(struct hv_device *dev)
{
struct hv_util_service *srv = hv_get_drvdata(dev);
int ret = 0;
if (srv->util_pre_resume) {
ret = srv->util_pre_resume();
if (ret)
return ret;
}
ret = vmbus_open(dev->channel, 4 * HV_HYP_PAGE_SIZE,
4 * HV_HYP_PAGE_SIZE, NULL, 0, srv->util_cb,
dev->channel);
return ret;
}
static const struct hv_vmbus_device_id id_table[] = { static const struct hv_vmbus_device_id id_table[] = {
/* Shutdown guid */ /* Shutdown guid */
{ HV_SHUTDOWN_GUID, { HV_SHUTDOWN_GUID,
...@@ -477,6 +593,8 @@ static struct hv_driver util_drv = { ...@@ -477,6 +593,8 @@ static struct hv_driver util_drv = {
.id_table = id_table, .id_table = id_table,
.probe = util_probe, .probe = util_probe,
.remove = util_remove, .remove = util_remove,
.suspend = util_suspend,
.resume = util_resume,
.driver = { .driver = {
.probe_type = PROBE_PREFER_ASYNCHRONOUS, .probe_type = PROBE_PREFER_ASYNCHRONOUS,
}, },
...@@ -546,11 +664,23 @@ static int hv_timesync_init(struct hv_util_service *srv) ...@@ -546,11 +664,23 @@ static int hv_timesync_init(struct hv_util_service *srv)
return 0; return 0;
} }
static void hv_timesync_cancel_work(void)
{
cancel_work_sync(&adj_time_work);
}
static int hv_timesync_pre_suspend(void)
{
hv_timesync_cancel_work();
return 0;
}
static void hv_timesync_deinit(void) static void hv_timesync_deinit(void)
{ {
if (hv_ptp_clock) if (hv_ptp_clock)
ptp_clock_unregister(hv_ptp_clock); ptp_clock_unregister(hv_ptp_clock);
cancel_work_sync(&adj_time_work);
hv_timesync_cancel_work();
} }
static int __init init_hyperv_utils(void) static int __init init_hyperv_utils(void)
......
...@@ -352,14 +352,20 @@ void vmbus_on_msg_dpc(unsigned long data); ...@@ -352,14 +352,20 @@ void vmbus_on_msg_dpc(unsigned long data);
int hv_kvp_init(struct hv_util_service *srv); int hv_kvp_init(struct hv_util_service *srv);
void hv_kvp_deinit(void); void hv_kvp_deinit(void);
int hv_kvp_pre_suspend(void);
int hv_kvp_pre_resume(void);
void hv_kvp_onchannelcallback(void *context); void hv_kvp_onchannelcallback(void *context);
int hv_vss_init(struct hv_util_service *srv); int hv_vss_init(struct hv_util_service *srv);
void hv_vss_deinit(void); void hv_vss_deinit(void);
int hv_vss_pre_suspend(void);
int hv_vss_pre_resume(void);
void hv_vss_onchannelcallback(void *context); void hv_vss_onchannelcallback(void *context);
int hv_fcopy_init(struct hv_util_service *srv); int hv_fcopy_init(struct hv_util_service *srv);
void hv_fcopy_deinit(void); void hv_fcopy_deinit(void);
int hv_fcopy_pre_suspend(void);
int hv_fcopy_pre_resume(void);
void hv_fcopy_onchannelcallback(void *context); void hv_fcopy_onchannelcallback(void *context);
void vmbus_initiate_unload(bool crash); void vmbus_initiate_unload(bool crash);
......
...@@ -1033,6 +1033,10 @@ void vmbus_on_msg_dpc(unsigned long data) ...@@ -1033,6 +1033,10 @@ void vmbus_on_msg_dpc(unsigned long data)
} }
entry = &channel_message_table[hdr->msgtype]; entry = &channel_message_table[hdr->msgtype];
if (!entry->message_handler)
goto msg_handled;
if (entry->handler_type == VMHT_BLOCKING) { if (entry->handler_type == VMHT_BLOCKING) {
ctx = kmalloc(sizeof(*ctx), GFP_ATOMIC); ctx = kmalloc(sizeof(*ctx), GFP_ATOMIC);
if (ctx == NULL) if (ctx == NULL)
......
...@@ -259,6 +259,8 @@ static int hv_kbd_connect_to_vsp(struct hv_device *hv_dev) ...@@ -259,6 +259,8 @@ static int hv_kbd_connect_to_vsp(struct hv_device *hv_dev)
u32 proto_status; u32 proto_status;
int error; int error;
reinit_completion(&kbd_dev->wait_event);
request = &kbd_dev->protocol_req; request = &kbd_dev->protocol_req;
memset(request, 0, sizeof(struct synth_kbd_protocol_request)); memset(request, 0, sizeof(struct synth_kbd_protocol_request));
request->header.type = __cpu_to_le32(SYNTH_KBD_PROTOCOL_REQUEST); request->header.type = __cpu_to_le32(SYNTH_KBD_PROTOCOL_REQUEST);
...@@ -380,6 +382,29 @@ static int hv_kbd_remove(struct hv_device *hv_dev) ...@@ -380,6 +382,29 @@ static int hv_kbd_remove(struct hv_device *hv_dev)
return 0; return 0;
} }
static int hv_kbd_suspend(struct hv_device *hv_dev)
{
vmbus_close(hv_dev->channel);
return 0;
}
static int hv_kbd_resume(struct hv_device *hv_dev)
{
int ret;
ret = vmbus_open(hv_dev->channel,
KBD_VSC_SEND_RING_BUFFER_SIZE,
KBD_VSC_RECV_RING_BUFFER_SIZE,
NULL, 0,
hv_kbd_on_channel_callback,
hv_dev);
if (ret == 0)
ret = hv_kbd_connect_to_vsp(hv_dev);
return ret;
}
static const struct hv_vmbus_device_id id_table[] = { static const struct hv_vmbus_device_id id_table[] = {
/* Keyboard guid */ /* Keyboard guid */
{ HV_KBD_GUID, }, { HV_KBD_GUID, },
...@@ -393,6 +418,8 @@ static struct hv_driver hv_kbd_drv = { ...@@ -393,6 +418,8 @@ static struct hv_driver hv_kbd_drv = {
.id_table = id_table, .id_table = id_table,
.probe = hv_kbd_probe, .probe = hv_kbd_probe,
.remove = hv_kbd_remove, .remove = hv_kbd_remove,
.suspend = hv_kbd_suspend,
.resume = hv_kbd_resume,
.driver = { .driver = {
.probe_type = PROBE_PREFER_ASYNCHRONOUS, .probe_type = PROBE_PREFER_ASYNCHRONOUS,
}, },
......
...@@ -2215,6 +2215,7 @@ config FB_HYPERV ...@@ -2215,6 +2215,7 @@ config FB_HYPERV
select FB_CFB_COPYAREA select FB_CFB_COPYAREA
select FB_CFB_IMAGEBLIT select FB_CFB_IMAGEBLIT
select FB_DEFERRED_IO select FB_DEFERRED_IO
select DMA_CMA if HAVE_DMA_CONTIGUOUS && CMA
help help
This framebuffer driver supports Microsoft Hyper-V Synthetic Video. This framebuffer driver supports Microsoft Hyper-V Synthetic Video.
......
...@@ -31,6 +31,16 @@ ...@@ -31,6 +31,16 @@
* "set-vmvideo" command. For example * "set-vmvideo" command. For example
* set-vmvideo -vmname name -horizontalresolution:1920 \ * set-vmvideo -vmname name -horizontalresolution:1920 \
* -verticalresolution:1200 -resolutiontype single * -verticalresolution:1200 -resolutiontype single
*
* Gen 1 VMs also support direct using VM's physical memory for framebuffer.
* It could improve the efficiency and performance for framebuffer and VM.
* This requires to allocate contiguous physical memory from Linux kernel's
* CMA memory allocator. To enable this, supply a kernel parameter to give
* enough memory space to CMA allocator for framebuffer. For example:
* cma=130m
* This gives 130MB memory to CMA allocator that can be allocated to
* framebuffer. For reference, 8K resolution (7680x4320) takes about
* 127MB memory.
*/ */
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
...@@ -228,7 +238,6 @@ struct synthvid_msg { ...@@ -228,7 +238,6 @@ struct synthvid_msg {
} __packed; } __packed;
/* FB driver definitions and structures */ /* FB driver definitions and structures */
#define HVFB_WIDTH 1152 /* default screen width */ #define HVFB_WIDTH 1152 /* default screen width */
#define HVFB_HEIGHT 864 /* default screen height */ #define HVFB_HEIGHT 864 /* default screen height */
...@@ -258,12 +267,15 @@ struct hvfb_par { ...@@ -258,12 +267,15 @@ struct hvfb_par {
/* If true, the VSC notifies the VSP on every framebuffer change */ /* If true, the VSC notifies the VSP on every framebuffer change */
bool synchronous_fb; bool synchronous_fb;
/* If true, need to copy from deferred IO mem to framebuffer mem */
bool need_docopy;
struct notifier_block hvfb_panic_nb; struct notifier_block hvfb_panic_nb;
/* Memory for deferred IO and frame buffer itself */ /* Memory for deferred IO and frame buffer itself */
unsigned char *dio_vp; unsigned char *dio_vp;
unsigned char *mmio_vp; unsigned char *mmio_vp;
unsigned long mmio_pp; phys_addr_t mmio_pp;
/* Dirty rectangle, protected by delayed_refresh_lock */ /* Dirty rectangle, protected by delayed_refresh_lock */
int x1, y1, x2, y2; int x1, y1, x2, y2;
...@@ -434,7 +446,7 @@ static void synthvid_deferred_io(struct fb_info *p, ...@@ -434,7 +446,7 @@ static void synthvid_deferred_io(struct fb_info *p,
maxy = max_t(int, maxy, y2); maxy = max_t(int, maxy, y2);
/* Copy from dio space to mmio address */ /* Copy from dio space to mmio address */
if (par->fb_ready) if (par->fb_ready && par->need_docopy)
hvfb_docopy(par, start, PAGE_SIZE); hvfb_docopy(par, start, PAGE_SIZE);
} }
...@@ -751,12 +763,12 @@ static void hvfb_update_work(struct work_struct *w) ...@@ -751,12 +763,12 @@ static void hvfb_update_work(struct work_struct *w)
return; return;
/* Copy the dirty rectangle to frame buffer memory */ /* Copy the dirty rectangle to frame buffer memory */
for (j = y1; j < y2; j++) { if (par->need_docopy)
for (j = y1; j < y2; j++)
hvfb_docopy(par, hvfb_docopy(par,
j * info->fix.line_length + j * info->fix.line_length +
(x1 * screen_depth / 8), (x1 * screen_depth / 8),
(x2 - x1) * screen_depth / 8); (x2 - x1) * screen_depth / 8);
}
/* Refresh */ /* Refresh */
if (par->fb_ready && par->update) if (par->fb_ready && par->update)
...@@ -801,6 +813,7 @@ static int hvfb_on_panic(struct notifier_block *nb, ...@@ -801,6 +813,7 @@ static int hvfb_on_panic(struct notifier_block *nb,
par = container_of(nb, struct hvfb_par, hvfb_panic_nb); par = container_of(nb, struct hvfb_par, hvfb_panic_nb);
par->synchronous_fb = true; par->synchronous_fb = true;
info = par->info; info = par->info;
if (par->need_docopy)
hvfb_docopy(par, 0, dio_fb_size); hvfb_docopy(par, 0, dio_fb_size);
synthvid_update(info, 0, 0, INT_MAX, INT_MAX); synthvid_update(info, 0, 0, INT_MAX, INT_MAX);
...@@ -940,6 +953,62 @@ static void hvfb_get_option(struct fb_info *info) ...@@ -940,6 +953,62 @@ static void hvfb_get_option(struct fb_info *info)
return; return;
} }
/*
* Allocate enough contiguous physical memory.
* Return physical address if succeeded or -1 if failed.
*/
static phys_addr_t hvfb_get_phymem(struct hv_device *hdev,
unsigned int request_size)
{
struct page *page = NULL;
dma_addr_t dma_handle;
void *vmem;
phys_addr_t paddr = 0;
unsigned int order = get_order(request_size);
if (request_size == 0)
return -1;
if (order < MAX_ORDER) {
/* Call alloc_pages if the size is less than 2^MAX_ORDER */
page = alloc_pages(GFP_KERNEL | __GFP_ZERO, order);
if (!page)
return -1;
paddr = (page_to_pfn(page) << PAGE_SHIFT);
} else {
/* Allocate from CMA */
hdev->device.coherent_dma_mask = DMA_BIT_MASK(64);
vmem = dma_alloc_coherent(&hdev->device,
round_up(request_size, PAGE_SIZE),
&dma_handle,
GFP_KERNEL | __GFP_NOWARN);
if (!vmem)
return -1;
paddr = virt_to_phys(vmem);
}
return paddr;
}
/* Release contiguous physical memory */
static void hvfb_release_phymem(struct hv_device *hdev,
phys_addr_t paddr, unsigned int size)
{
unsigned int order = get_order(size);
if (order < MAX_ORDER)
__free_pages(pfn_to_page(paddr >> PAGE_SHIFT), order);
else
dma_free_coherent(&hdev->device,
round_up(size, PAGE_SIZE),
phys_to_virt(paddr),
paddr);
}
/* Get framebuffer memory from Hyper-V video pci space */ /* Get framebuffer memory from Hyper-V video pci space */
static int hvfb_getmem(struct hv_device *hdev, struct fb_info *info) static int hvfb_getmem(struct hv_device *hdev, struct fb_info *info)
...@@ -949,22 +1018,61 @@ static int hvfb_getmem(struct hv_device *hdev, struct fb_info *info) ...@@ -949,22 +1018,61 @@ static int hvfb_getmem(struct hv_device *hdev, struct fb_info *info)
void __iomem *fb_virt; void __iomem *fb_virt;
int gen2vm = efi_enabled(EFI_BOOT); int gen2vm = efi_enabled(EFI_BOOT);
resource_size_t pot_start, pot_end; resource_size_t pot_start, pot_end;
phys_addr_t paddr;
int ret; int ret;
dio_fb_size = info->apertures = alloc_apertures(1);
screen_width * screen_height * screen_depth / 8; if (!info->apertures)
return -ENOMEM;
if (gen2vm) { if (!gen2vm) {
pot_start = 0;
pot_end = -1;
} else {
pdev = pci_get_device(PCI_VENDOR_ID_MICROSOFT, pdev = pci_get_device(PCI_VENDOR_ID_MICROSOFT,
PCI_DEVICE_ID_HYPERV_VIDEO, NULL); PCI_DEVICE_ID_HYPERV_VIDEO, NULL);
if (!pdev) { if (!pdev) {
pr_err("Unable to find PCI Hyper-V video\n"); pr_err("Unable to find PCI Hyper-V video\n");
kfree(info->apertures);
return -ENODEV; return -ENODEV;
} }
info->apertures->ranges[0].base = pci_resource_start(pdev, 0);
info->apertures->ranges[0].size = pci_resource_len(pdev, 0);
/*
* For Gen 1 VM, we can directly use the contiguous memory
* from VM. If we succeed, deferred IO happens directly
* on this allocated framebuffer memory, avoiding extra
* memory copy.
*/
paddr = hvfb_get_phymem(hdev, screen_fb_size);
if (paddr != (phys_addr_t) -1) {
par->mmio_pp = paddr;
par->mmio_vp = par->dio_vp = __va(paddr);
info->fix.smem_start = paddr;
info->fix.smem_len = screen_fb_size;
info->screen_base = par->mmio_vp;
info->screen_size = screen_fb_size;
par->need_docopy = false;
goto getmem_done;
}
pr_info("Unable to allocate enough contiguous physical memory on Gen 1 VM. Using MMIO instead.\n");
} else {
info->apertures->ranges[0].base = screen_info.lfb_base;
info->apertures->ranges[0].size = screen_info.lfb_size;
}
/*
* Cannot use the contiguous physical memory.
* Allocate mmio space for framebuffer.
*/
dio_fb_size =
screen_width * screen_height * screen_depth / 8;
if (gen2vm) {
pot_start = 0;
pot_end = -1;
} else {
if (!(pci_resource_flags(pdev, 0) & IORESOURCE_MEM) || if (!(pci_resource_flags(pdev, 0) & IORESOURCE_MEM) ||
pci_resource_len(pdev, 0) < screen_fb_size) { pci_resource_len(pdev, 0) < screen_fb_size) {
pr_err("Resource not available or (0x%lx < 0x%lx)\n", pr_err("Resource not available or (0x%lx < 0x%lx)\n",
...@@ -993,20 +1101,6 @@ static int hvfb_getmem(struct hv_device *hdev, struct fb_info *info) ...@@ -993,20 +1101,6 @@ static int hvfb_getmem(struct hv_device *hdev, struct fb_info *info)
if (par->dio_vp == NULL) if (par->dio_vp == NULL)
goto err3; goto err3;
info->apertures = alloc_apertures(1);
if (!info->apertures)
goto err4;
if (gen2vm) {
info->apertures->ranges[0].base = screen_info.lfb_base;
info->apertures->ranges[0].size = screen_info.lfb_size;
remove_conflicting_framebuffers(info->apertures,
KBUILD_MODNAME, false);
} else {
info->apertures->ranges[0].base = pci_resource_start(pdev, 0);
info->apertures->ranges[0].size = pci_resource_len(pdev, 0);
}
/* Physical address of FB device */ /* Physical address of FB device */
par->mmio_pp = par->mem->start; par->mmio_pp = par->mem->start;
/* Virtual address of FB device */ /* Virtual address of FB device */
...@@ -1017,13 +1111,15 @@ static int hvfb_getmem(struct hv_device *hdev, struct fb_info *info) ...@@ -1017,13 +1111,15 @@ static int hvfb_getmem(struct hv_device *hdev, struct fb_info *info)
info->screen_base = par->dio_vp; info->screen_base = par->dio_vp;
info->screen_size = dio_fb_size; info->screen_size = dio_fb_size;
getmem_done:
remove_conflicting_framebuffers(info->apertures,
KBUILD_MODNAME, false);
if (!gen2vm) if (!gen2vm)
pci_dev_put(pdev); pci_dev_put(pdev);
kfree(info->apertures);
return 0; return 0;
err4:
vfree(par->dio_vp);
err3: err3:
iounmap(fb_virt); iounmap(fb_virt);
err2: err2:
...@@ -1032,18 +1128,25 @@ static int hvfb_getmem(struct hv_device *hdev, struct fb_info *info) ...@@ -1032,18 +1128,25 @@ static int hvfb_getmem(struct hv_device *hdev, struct fb_info *info)
err1: err1:
if (!gen2vm) if (!gen2vm)
pci_dev_put(pdev); pci_dev_put(pdev);
kfree(info->apertures);
return -ENOMEM; return -ENOMEM;
} }
/* Release the framebuffer */ /* Release the framebuffer */
static void hvfb_putmem(struct fb_info *info) static void hvfb_putmem(struct hv_device *hdev, struct fb_info *info)
{ {
struct hvfb_par *par = info->par; struct hvfb_par *par = info->par;
if (par->need_docopy) {
vfree(par->dio_vp); vfree(par->dio_vp);
iounmap(info->screen_base); iounmap(info->screen_base);
vmbus_free_mmio(par->mem->start, screen_fb_size); vmbus_free_mmio(par->mem->start, screen_fb_size);
} else {
hvfb_release_phymem(hdev, info->fix.smem_start,
screen_fb_size);
}
par->mem = NULL; par->mem = NULL;
} }
...@@ -1062,6 +1165,7 @@ static int hvfb_probe(struct hv_device *hdev, ...@@ -1062,6 +1165,7 @@ static int hvfb_probe(struct hv_device *hdev,
par = info->par; par = info->par;
par->info = info; par->info = info;
par->fb_ready = false; par->fb_ready = false;
par->need_docopy = true;
init_completion(&par->wait); init_completion(&par->wait);
INIT_DELAYED_WORK(&par->dwork, hvfb_update_work); INIT_DELAYED_WORK(&par->dwork, hvfb_update_work);
...@@ -1147,7 +1251,7 @@ static int hvfb_probe(struct hv_device *hdev, ...@@ -1147,7 +1251,7 @@ static int hvfb_probe(struct hv_device *hdev,
error: error:
fb_deferred_io_cleanup(info); fb_deferred_io_cleanup(info);
hvfb_putmem(info); hvfb_putmem(hdev, info);
error2: error2:
vmbus_close(hdev->channel); vmbus_close(hdev->channel);
error1: error1:
...@@ -1177,7 +1281,7 @@ static int hvfb_remove(struct hv_device *hdev) ...@@ -1177,7 +1281,7 @@ static int hvfb_remove(struct hv_device *hdev)
vmbus_close(hdev->channel); vmbus_close(hdev->channel);
hv_set_drvdata(hdev, NULL); hv_set_drvdata(hdev, NULL);
hvfb_putmem(info); hvfb_putmem(hdev, info);
framebuffer_release(info); framebuffer_release(info);
return 0; return 0;
...@@ -1194,6 +1298,7 @@ static int hvfb_suspend(struct hv_device *hdev) ...@@ -1194,6 +1298,7 @@ static int hvfb_suspend(struct hv_device *hdev)
fb_set_suspend(info, 1); fb_set_suspend(info, 1);
cancel_delayed_work_sync(&par->dwork); cancel_delayed_work_sync(&par->dwork);
cancel_delayed_work_sync(&info->deferred_work);
par->update_saved = par->update; par->update_saved = par->update;
par->update = false; par->update = false;
...@@ -1227,6 +1332,7 @@ static int hvfb_resume(struct hv_device *hdev) ...@@ -1227,6 +1332,7 @@ static int hvfb_resume(struct hv_device *hdev)
par->fb_ready = true; par->fb_ready = true;
par->update = par->update_saved; par->update = par->update_saved;
schedule_delayed_work(&info->deferred_work, info->fbdefio->delay);
schedule_delayed_work(&par->dwork, HVFB_UPDATE_DELAY); schedule_delayed_work(&par->dwork, HVFB_UPDATE_DELAY);
/* 0 means do resume */ /* 0 means do resume */
......
...@@ -425,6 +425,8 @@ enum vmbus_channel_message_type { ...@@ -425,6 +425,8 @@ enum vmbus_channel_message_type {
CHANNELMSG_19 = 19, CHANNELMSG_19 = 19,
CHANNELMSG_20 = 20, CHANNELMSG_20 = 20,
CHANNELMSG_TL_CONNECT_REQUEST = 21, CHANNELMSG_TL_CONNECT_REQUEST = 21,
CHANNELMSG_22 = 22,
CHANNELMSG_TL_CONNECT_RESULT = 23,
CHANNELMSG_COUNT CHANNELMSG_COUNT
}; };
...@@ -1433,6 +1435,8 @@ struct hv_util_service { ...@@ -1433,6 +1435,8 @@ struct hv_util_service {
void (*util_cb)(void *); void (*util_cb)(void *);
int (*util_init)(struct hv_util_service *); int (*util_init)(struct hv_util_service *);
void (*util_deinit)(void); void (*util_deinit)(void);
int (*util_pre_suspend)(void);
int (*util_pre_resume)(void);
}; };
struct vmbuspipe_hdr { struct vmbuspipe_hdr {
......
...@@ -80,6 +80,8 @@ static int hv_start_fcopy(struct hv_start_fcopy *smsg) ...@@ -80,6 +80,8 @@ static int hv_start_fcopy(struct hv_start_fcopy *smsg)
error = 0; error = 0;
done: done:
if (error)
target_fname[0] = '\0';
return error; return error;
} }
...@@ -108,15 +110,29 @@ static int hv_copy_data(struct hv_do_fcopy *cpmsg) ...@@ -108,15 +110,29 @@ static int hv_copy_data(struct hv_do_fcopy *cpmsg)
return ret; return ret;
} }
/*
* Reset target_fname to "" in the two below functions for hibernation: if
* the fcopy operation is aborted by hibernation, the daemon should remove the
* partially-copied file; to achieve this, the hv_utils driver always fakes a
* CANCEL_FCOPY message upon suspend, and later when the VM resumes back,
* the daemon calls hv_copy_cancel() to remove the file; if a file is copied
* successfully before suspend, hv_copy_finished() must reset target_fname to
* avoid that the file can be incorrectly removed upon resume, since the faked
* CANCEL_FCOPY message is spurious in this case.
*/
static int hv_copy_finished(void) static int hv_copy_finished(void)
{ {
close(target_fd); close(target_fd);
target_fname[0] = '\0';
return 0; return 0;
} }
static int hv_copy_cancel(void) static int hv_copy_cancel(void)
{ {
close(target_fd); close(target_fd);
if (strlen(target_fname) > 0) {
unlink(target_fname); unlink(target_fname);
target_fname[0] = '\0';
}
return 0; return 0;
} }
...@@ -131,7 +147,7 @@ void print_usage(char *argv[]) ...@@ -131,7 +147,7 @@ void print_usage(char *argv[])
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
int fcopy_fd; int fcopy_fd = -1;
int error; int error;
int daemonize = 1, long_index = 0, opt; int daemonize = 1, long_index = 0, opt;
int version = FCOPY_CURRENT_VERSION; int version = FCOPY_CURRENT_VERSION;
...@@ -141,7 +157,7 @@ int main(int argc, char *argv[]) ...@@ -141,7 +157,7 @@ int main(int argc, char *argv[])
struct hv_do_fcopy copy; struct hv_do_fcopy copy;
__u32 kernel_modver; __u32 kernel_modver;
} buffer = { }; } buffer = { };
int in_handshake = 1; int in_handshake;
static struct option long_options[] = { static struct option long_options[] = {
{"help", no_argument, 0, 'h' }, {"help", no_argument, 0, 'h' },
...@@ -170,6 +186,12 @@ int main(int argc, char *argv[]) ...@@ -170,6 +186,12 @@ int main(int argc, char *argv[])
openlog("HV_FCOPY", 0, LOG_USER); openlog("HV_FCOPY", 0, LOG_USER);
syslog(LOG_INFO, "starting; pid is:%d", getpid()); syslog(LOG_INFO, "starting; pid is:%d", getpid());
reopen_fcopy_fd:
if (fcopy_fd != -1)
close(fcopy_fd);
/* Remove any possible partially-copied file on error */
hv_copy_cancel();
in_handshake = 1;
fcopy_fd = open("/dev/vmbus/hv_fcopy", O_RDWR); fcopy_fd = open("/dev/vmbus/hv_fcopy", O_RDWR);
if (fcopy_fd < 0) { if (fcopy_fd < 0) {
...@@ -196,7 +218,7 @@ int main(int argc, char *argv[]) ...@@ -196,7 +218,7 @@ int main(int argc, char *argv[])
len = pread(fcopy_fd, &buffer, sizeof(buffer), 0); len = pread(fcopy_fd, &buffer, sizeof(buffer), 0);
if (len < 0) { if (len < 0) {
syslog(LOG_ERR, "pread failed: %s", strerror(errno)); syslog(LOG_ERR, "pread failed: %s", strerror(errno));
exit(EXIT_FAILURE); goto reopen_fcopy_fd;
} }
if (in_handshake) { if (in_handshake) {
...@@ -231,9 +253,14 @@ int main(int argc, char *argv[]) ...@@ -231,9 +253,14 @@ int main(int argc, char *argv[])
} }
/*
* pwrite() may return an error due to the faked CANCEL_FCOPY
* message upon hibernation. Ignore the error by resetting the
* dev file, i.e. closing and re-opening it.
*/
if (pwrite(fcopy_fd, &error, sizeof(int), 0) != sizeof(int)) { if (pwrite(fcopy_fd, &error, sizeof(int), 0) != sizeof(int)) {
syslog(LOG_ERR, "pwrite failed: %s", strerror(errno)); syslog(LOG_ERR, "pwrite failed: %s", strerror(errno));
exit(EXIT_FAILURE); goto reopen_fcopy_fd;
} }
} }
} }
...@@ -76,7 +76,7 @@ enum { ...@@ -76,7 +76,7 @@ enum {
DNS DNS
}; };
static int in_hand_shake = 1; static int in_hand_shake;
static char *os_name = ""; static char *os_name = "";
static char *os_major = ""; static char *os_major = "";
...@@ -1360,7 +1360,7 @@ void print_usage(char *argv[]) ...@@ -1360,7 +1360,7 @@ void print_usage(char *argv[])
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
int kvp_fd, len; int kvp_fd = -1, len;
int error; int error;
struct pollfd pfd; struct pollfd pfd;
char *p; char *p;
...@@ -1400,14 +1400,6 @@ int main(int argc, char *argv[]) ...@@ -1400,14 +1400,6 @@ int main(int argc, char *argv[])
openlog("KVP", 0, LOG_USER); openlog("KVP", 0, LOG_USER);
syslog(LOG_INFO, "KVP starting; pid is:%d", getpid()); syslog(LOG_INFO, "KVP starting; pid is:%d", getpid());
kvp_fd = open("/dev/vmbus/hv_kvp", O_RDWR | O_CLOEXEC);
if (kvp_fd < 0) {
syslog(LOG_ERR, "open /dev/vmbus/hv_kvp failed; error: %d %s",
errno, strerror(errno));
exit(EXIT_FAILURE);
}
/* /*
* Retrieve OS release information. * Retrieve OS release information.
*/ */
...@@ -1423,6 +1415,18 @@ int main(int argc, char *argv[]) ...@@ -1423,6 +1415,18 @@ int main(int argc, char *argv[])
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
reopen_kvp_fd:
if (kvp_fd != -1)
close(kvp_fd);
in_hand_shake = 1;
kvp_fd = open("/dev/vmbus/hv_kvp", O_RDWR | O_CLOEXEC);
if (kvp_fd < 0) {
syslog(LOG_ERR, "open /dev/vmbus/hv_kvp failed; error: %d %s",
errno, strerror(errno));
exit(EXIT_FAILURE);
}
/* /*
* Register ourselves with the kernel. * Register ourselves with the kernel.
*/ */
...@@ -1456,9 +1460,7 @@ int main(int argc, char *argv[]) ...@@ -1456,9 +1460,7 @@ int main(int argc, char *argv[])
if (len != sizeof(struct hv_kvp_msg)) { if (len != sizeof(struct hv_kvp_msg)) {
syslog(LOG_ERR, "read failed; error:%d %s", syslog(LOG_ERR, "read failed; error:%d %s",
errno, strerror(errno)); errno, strerror(errno));
goto reopen_kvp_fd;
close(kvp_fd);
return EXIT_FAILURE;
} }
/* /*
...@@ -1617,13 +1619,17 @@ int main(int argc, char *argv[]) ...@@ -1617,13 +1619,17 @@ int main(int argc, char *argv[])
break; break;
} }
/* Send the value back to the kernel. */ /*
* Send the value back to the kernel. Note: the write() may
* return an error due to hibernation; we can ignore the error
* by resetting the dev file, i.e. closing and re-opening it.
*/
kvp_done: kvp_done:
len = write(kvp_fd, hv_msg, sizeof(struct hv_kvp_msg)); len = write(kvp_fd, hv_msg, sizeof(struct hv_kvp_msg));
if (len != sizeof(struct hv_kvp_msg)) { if (len != sizeof(struct hv_kvp_msg)) {
syslog(LOG_ERR, "write failed; error: %d %s", errno, syslog(LOG_ERR, "write failed; error: %d %s", errno,
strerror(errno)); strerror(errno));
exit(EXIT_FAILURE); goto reopen_kvp_fd;
} }
} }
......
...@@ -28,6 +28,8 @@ ...@@ -28,6 +28,8 @@
#include <stdbool.h> #include <stdbool.h>
#include <dirent.h> #include <dirent.h>
static bool fs_frozen;
/* Don't use syslog() in the function since that can cause write to disk */ /* Don't use syslog() in the function since that can cause write to disk */
static int vss_do_freeze(char *dir, unsigned int cmd) static int vss_do_freeze(char *dir, unsigned int cmd)
{ {
...@@ -155,17 +157,26 @@ static int vss_operate(int operation) ...@@ -155,17 +157,26 @@ static int vss_operate(int operation)
continue; continue;
} }
error |= vss_do_freeze(ent->mnt_dir, cmd); error |= vss_do_freeze(ent->mnt_dir, cmd);
if (error && operation == VSS_OP_FREEZE) if (operation == VSS_OP_FREEZE) {
if (error)
goto err; goto err;
fs_frozen = true;
}
} }
endmntent(mounts); endmntent(mounts);
if (root_seen) { if (root_seen) {
error |= vss_do_freeze("/", cmd); error |= vss_do_freeze("/", cmd);
if (error && operation == VSS_OP_FREEZE) if (operation == VSS_OP_FREEZE) {
if (error)
goto err; goto err;
fs_frozen = true;
} }
}
if (operation == VSS_OP_THAW && !error)
fs_frozen = false;
goto out; goto out;
err: err:
...@@ -175,6 +186,7 @@ static int vss_operate(int operation) ...@@ -175,6 +186,7 @@ static int vss_operate(int operation)
endmntent(mounts); endmntent(mounts);
} }
vss_operate(VSS_OP_THAW); vss_operate(VSS_OP_THAW);
fs_frozen = false;
/* Call syslog after we thaw all filesystems */ /* Call syslog after we thaw all filesystems */
if (ent) if (ent)
syslog(LOG_ERR, "FREEZE of %s failed; error:%d %s", syslog(LOG_ERR, "FREEZE of %s failed; error:%d %s",
...@@ -196,13 +208,13 @@ void print_usage(char *argv[]) ...@@ -196,13 +208,13 @@ void print_usage(char *argv[])
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
int vss_fd, len; int vss_fd = -1, len;
int error; int error;
struct pollfd pfd; struct pollfd pfd;
int op; int op;
struct hv_vss_msg vss_msg[1]; struct hv_vss_msg vss_msg[1];
int daemonize = 1, long_index = 0, opt; int daemonize = 1, long_index = 0, opt;
int in_handshake = 1; int in_handshake;
__u32 kernel_modver; __u32 kernel_modver;
static struct option long_options[] = { static struct option long_options[] = {
...@@ -232,6 +244,18 @@ int main(int argc, char *argv[]) ...@@ -232,6 +244,18 @@ int main(int argc, char *argv[])
openlog("Hyper-V VSS", 0, LOG_USER); openlog("Hyper-V VSS", 0, LOG_USER);
syslog(LOG_INFO, "VSS starting; pid is:%d", getpid()); syslog(LOG_INFO, "VSS starting; pid is:%d", getpid());
reopen_vss_fd:
if (vss_fd != -1)
close(vss_fd);
if (fs_frozen) {
if (vss_operate(VSS_OP_THAW) || fs_frozen) {
syslog(LOG_ERR, "failed to thaw file system: err=%d",
errno);
exit(EXIT_FAILURE);
}
}
in_handshake = 1;
vss_fd = open("/dev/vmbus/hv_vss", O_RDWR); vss_fd = open("/dev/vmbus/hv_vss", O_RDWR);
if (vss_fd < 0) { if (vss_fd < 0) {
syslog(LOG_ERR, "open /dev/vmbus/hv_vss failed; error: %d %s", syslog(LOG_ERR, "open /dev/vmbus/hv_vss failed; error: %d %s",
...@@ -284,8 +308,7 @@ int main(int argc, char *argv[]) ...@@ -284,8 +308,7 @@ int main(int argc, char *argv[])
if (len != sizeof(struct hv_vss_msg)) { if (len != sizeof(struct hv_vss_msg)) {
syslog(LOG_ERR, "read failed; error:%d %s", syslog(LOG_ERR, "read failed; error:%d %s",
errno, strerror(errno)); errno, strerror(errno));
close(vss_fd); goto reopen_vss_fd;
return EXIT_FAILURE;
} }
op = vss_msg->vss_hdr.operation; op = vss_msg->vss_hdr.operation;
...@@ -312,14 +335,18 @@ int main(int argc, char *argv[]) ...@@ -312,14 +335,18 @@ int main(int argc, char *argv[])
default: default:
syslog(LOG_ERR, "Illegal op:%d\n", op); syslog(LOG_ERR, "Illegal op:%d\n", op);
} }
/*
* The write() may return an error due to the faked VSS_OP_THAW
* message upon hibernation. Ignore the error by resetting the
* dev file, i.e. closing and re-opening it.
*/
vss_msg->error = error; vss_msg->error = error;
len = write(vss_fd, vss_msg, sizeof(struct hv_vss_msg)); len = write(vss_fd, vss_msg, sizeof(struct hv_vss_msg));
if (len != sizeof(struct hv_vss_msg)) { if (len != sizeof(struct hv_vss_msg)) {
syslog(LOG_ERR, "write failed; error: %d %s", errno, syslog(LOG_ERR, "write failed; error: %d %s", errno,
strerror(errno)); strerror(errno));
goto reopen_vss_fd;
if (op == VSS_OP_FREEZE)
vss_operate(VSS_OP_THAW);
} }
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment