]> git.karo-electronics.de Git - karo-tx-linux.git/blob - drivers/staging/hv/hv_util.c
Staging: hv: util: Adjust guest time in a process context
[karo-tx-linux.git] / drivers / staging / hv / hv_util.c
1 /*
2  * Copyright (c) 2010, Microsoft Corporation.
3  *
4  * This program is free software; you can redistribute it and/or modify it
5  * under the terms and conditions of the GNU General Public License,
6  * version 2, as published by the Free Software Foundation.
7  *
8  * This program is distributed in the hope it will be useful, but WITHOUT
9  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
11  * more details.
12  *
13  * You should have received a copy of the GNU General Public License along with
14  * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
15  * Place - Suite 330, Boston, MA 02111-1307 USA.
16  *
17  * Authors:
18  *   Haiyang Zhang <haiyangz@microsoft.com>
19  *   Hank Janssen  <hjanssen@microsoft.com>
20  */
21 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
22
23 #include <linux/kernel.h>
24 #include <linux/init.h>
25 #include <linux/module.h>
26 #include <linux/slab.h>
27 #include <linux/sysctl.h>
28 #include <linux/reboot.h>
29
30 #include "hyperv.h"
31 #include "hv_kvp.h"
32
33 static u8 *shut_txf_buf;
34 static u8 *time_txf_buf;
35 static u8 *hbeat_txf_buf;
36
37 static void shutdown_onchannelcallback(void *context)
38 {
39         struct vmbus_channel *channel = context;
40         u32 recvlen;
41         u64 requestid;
42         u8  execute_shutdown = false;
43
44         struct shutdown_msg_data *shutdown_msg;
45
46         struct icmsg_hdr *icmsghdrp;
47         struct icmsg_negotiate *negop = NULL;
48
49         vmbus_recvpacket(channel, shut_txf_buf,
50                          PAGE_SIZE, &recvlen, &requestid);
51
52         if (recvlen > 0) {
53                 icmsghdrp = (struct icmsg_hdr *)&shut_txf_buf[
54                         sizeof(struct vmbuspipe_hdr)];
55
56                 if (icmsghdrp->icmsgtype == ICMSGTYPE_NEGOTIATE) {
57                         prep_negotiate_resp(icmsghdrp, negop, shut_txf_buf);
58                 } else {
59                         shutdown_msg =
60                                 (struct shutdown_msg_data *)&shut_txf_buf[
61                                         sizeof(struct vmbuspipe_hdr) +
62                                         sizeof(struct icmsg_hdr)];
63
64                         switch (shutdown_msg->flags) {
65                         case 0:
66                         case 1:
67                                 icmsghdrp->status = HV_S_OK;
68                                 execute_shutdown = true;
69
70                                 pr_info("Shutdown request received -"
71                                             " graceful shutdown initiated\n");
72                                 break;
73                         default:
74                                 icmsghdrp->status = HV_E_FAIL;
75                                 execute_shutdown = false;
76
77                                 pr_info("Shutdown request received -"
78                                             " Invalid request\n");
79                                 break;
80                         }
81                 }
82
83                 icmsghdrp->icflags = ICMSGHDRFLAG_TRANSACTION
84                         | ICMSGHDRFLAG_RESPONSE;
85
86                 vmbus_sendpacket(channel, shut_txf_buf,
87                                        recvlen, requestid,
88                                        VM_PKT_DATA_INBAND, 0);
89         }
90
91         if (execute_shutdown == true)
92                 orderly_poweroff(true);
93 }
94
95 /*
96  * Set guest time to host UTC time.
97  */
98 static inline void do_adj_guesttime(u64 hosttime)
99 {
100         s64 host_tns;
101         struct timespec host_ts;
102
103         host_tns = (hosttime - WLTIMEDELTA) * 100;
104         host_ts = ns_to_timespec(host_tns);
105
106         do_settimeofday(&host_ts);
107 }
108
109 /*
110  * Set the host time in a process context.
111  */
112
113 struct adj_time_work {
114         struct work_struct work;
115         u64     host_time;
116 };
117
118 static void hv_set_host_time(struct work_struct *work)
119 {
120         struct adj_time_work    *wrk;
121
122         wrk = container_of(work, struct adj_time_work, work);
123         do_adj_guesttime(wrk->host_time);
124         kfree(wrk);
125 }
126
127 /*
128  * Synchronize time with host after reboot, restore, etc.
129  *
130  * ICTIMESYNCFLAG_SYNC flag bit indicates reboot, restore events of the VM.
131  * After reboot the flag ICTIMESYNCFLAG_SYNC is included in the first time
132  * message after the timesync channel is opened. Since the hv_utils module is
133  * loaded after hv_vmbus, the first message is usually missed. The other
134  * thing is, systime is automatically set to emulated hardware clock which may
135  * not be UTC time or in the same time zone. So, to override these effects, we
136  * use the first 50 time samples for initial system time setting.
137  */
138 static inline void adj_guesttime(u64 hosttime, u8 flags)
139 {
140         struct adj_time_work    *wrk;
141         static s32 scnt = 50;
142
143         wrk = kmalloc(sizeof(struct adj_time_work), GFP_ATOMIC);
144         if (wrk == NULL)
145                 return;
146
147         wrk->host_time = hosttime;
148         if ((flags & ICTIMESYNCFLAG_SYNC) != 0) {
149                 INIT_WORK(&wrk->work, hv_set_host_time);
150                 schedule_work(&wrk->work);
151                 return;
152         }
153
154         if ((flags & ICTIMESYNCFLAG_SAMPLE) != 0 && scnt > 0) {
155                 scnt--;
156                 INIT_WORK(&wrk->work, hv_set_host_time);
157                 schedule_work(&wrk->work);
158         } else
159                 kfree(wrk);
160 }
161
162 /*
163  * Time Sync Channel message handler.
164  */
165 static void timesync_onchannelcallback(void *context)
166 {
167         struct vmbus_channel *channel = context;
168         u32 recvlen;
169         u64 requestid;
170         struct icmsg_hdr *icmsghdrp;
171         struct ictimesync_data *timedatap;
172
173         vmbus_recvpacket(channel, time_txf_buf,
174                          PAGE_SIZE, &recvlen, &requestid);
175
176         if (recvlen > 0) {
177                 icmsghdrp = (struct icmsg_hdr *)&time_txf_buf[
178                                 sizeof(struct vmbuspipe_hdr)];
179
180                 if (icmsghdrp->icmsgtype == ICMSGTYPE_NEGOTIATE) {
181                         prep_negotiate_resp(icmsghdrp, NULL, time_txf_buf);
182                 } else {
183                         timedatap = (struct ictimesync_data *)&time_txf_buf[
184                                 sizeof(struct vmbuspipe_hdr) +
185                                 sizeof(struct icmsg_hdr)];
186                         adj_guesttime(timedatap->parenttime, timedatap->flags);
187                 }
188
189                 icmsghdrp->icflags = ICMSGHDRFLAG_TRANSACTION
190                         | ICMSGHDRFLAG_RESPONSE;
191
192                 vmbus_sendpacket(channel, time_txf_buf,
193                                 recvlen, requestid,
194                                 VM_PKT_DATA_INBAND, 0);
195         }
196 }
197
198 /*
199  * Heartbeat functionality.
200  * Every two seconds, Hyper-V send us a heartbeat request message.
201  * we respond to this message, and Hyper-V knows we are alive.
202  */
203 static void heartbeat_onchannelcallback(void *context)
204 {
205         struct vmbus_channel *channel = context;
206         u32 recvlen;
207         u64 requestid;
208         struct icmsg_hdr *icmsghdrp;
209         struct heartbeat_msg_data *heartbeat_msg;
210
211         vmbus_recvpacket(channel, hbeat_txf_buf,
212                          PAGE_SIZE, &recvlen, &requestid);
213
214         if (recvlen > 0) {
215                 icmsghdrp = (struct icmsg_hdr *)&hbeat_txf_buf[
216                                 sizeof(struct vmbuspipe_hdr)];
217
218                 if (icmsghdrp->icmsgtype == ICMSGTYPE_NEGOTIATE) {
219                         prep_negotiate_resp(icmsghdrp, NULL, hbeat_txf_buf);
220                 } else {
221                         heartbeat_msg =
222                                 (struct heartbeat_msg_data *)&hbeat_txf_buf[
223                                         sizeof(struct vmbuspipe_hdr) +
224                                         sizeof(struct icmsg_hdr)];
225
226                         heartbeat_msg->seq_num += 1;
227                 }
228
229                 icmsghdrp->icflags = ICMSGHDRFLAG_TRANSACTION
230                         | ICMSGHDRFLAG_RESPONSE;
231
232                 vmbus_sendpacket(channel, hbeat_txf_buf,
233                                        recvlen, requestid,
234                                        VM_PKT_DATA_INBAND, 0);
235         }
236 }
237
238 /*
239  * The devices managed by the util driver don't need any additional
240  * setup.
241  */
242 static int util_probe(struct hv_device *dev)
243 {
244         return 0;
245 }
246
247 static int util_remove(struct hv_device *dev)
248 {
249         return 0;
250 }
251
252 static const struct hv_vmbus_device_id id_table[] = {
253         /* Shutdown guid */
254         { VMBUS_DEVICE(0x31, 0x60, 0x0B, 0X0E, 0x13, 0x52, 0x34, 0x49,
255                        0x81, 0x8B, 0x38, 0XD9, 0x0C, 0xED, 0x39, 0xDB) },
256         /* Time synch guid */
257         { VMBUS_DEVICE(0x30, 0xe6, 0x27, 0x95, 0xae, 0xd0, 0x7b, 0x49,
258                        0xad, 0xce, 0xe8, 0x0a, 0xb0, 0x17, 0x5c, 0xaf) },
259         /* Heartbeat guid */
260         { VMBUS_DEVICE(0x39, 0x4f, 0x16, 0x57, 0x15, 0x91, 0x78, 0x4e,
261                        0xab, 0x55, 0x38, 0x2f, 0x3b, 0xd5, 0x42, 0x2d) },
262         /* KVP guid */
263         { VMBUS_DEVICE(0xe7, 0xf4, 0xa0, 0xa9, 0x45, 0x5a, 0x96, 0x4d,
264                        0xb8, 0x27, 0x8a, 0x84, 0x1e, 0x8c, 0x3,  0xe6) },
265         { },
266 };
267
268 MODULE_DEVICE_TABLE(vmbus, id_table);
269
270 /* The one and only one */
271 static  struct hv_driver util_drv = {
272         .name = "hv_util",
273         .id_table = id_table,
274         .probe =  util_probe,
275         .remove =  util_remove,
276 };
277
278 static int __init init_hyperv_utils(void)
279 {
280         pr_info("Registering HyperV Utility Driver\n");
281
282         if (hv_kvp_init())
283                 return -ENODEV;
284
285
286         shut_txf_buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
287         time_txf_buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
288         hbeat_txf_buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
289
290         if (!shut_txf_buf || !time_txf_buf || !hbeat_txf_buf) {
291                 pr_info("Unable to allocate memory for receive buffer\n");
292                 kfree(shut_txf_buf);
293                 kfree(time_txf_buf);
294                 kfree(hbeat_txf_buf);
295                 return -ENOMEM;
296         }
297
298         hv_cb_utils[HV_SHUTDOWN_MSG].callback = &shutdown_onchannelcallback;
299
300         hv_cb_utils[HV_TIMESYNC_MSG].callback = &timesync_onchannelcallback;
301
302         hv_cb_utils[HV_HEARTBEAT_MSG].callback = &heartbeat_onchannelcallback;
303
304         hv_cb_utils[HV_KVP_MSG].callback = &hv_kvp_onchannelcallback;
305
306         return vmbus_driver_register(&util_drv);
307 }
308
309 static void exit_hyperv_utils(void)
310 {
311         pr_info("De-Registered HyperV Utility Driver\n");
312
313         if (hv_cb_utils[HV_SHUTDOWN_MSG].channel != NULL)
314                 hv_cb_utils[HV_SHUTDOWN_MSG].channel->onchannel_callback =
315                         &chn_cb_negotiate;
316         hv_cb_utils[HV_SHUTDOWN_MSG].callback = NULL;
317
318         if (hv_cb_utils[HV_TIMESYNC_MSG].channel != NULL)
319                 hv_cb_utils[HV_TIMESYNC_MSG].channel->onchannel_callback =
320                         &chn_cb_negotiate;
321         hv_cb_utils[HV_TIMESYNC_MSG].callback = NULL;
322
323         if (hv_cb_utils[HV_HEARTBEAT_MSG].channel != NULL)
324                 hv_cb_utils[HV_HEARTBEAT_MSG].channel->onchannel_callback =
325                         &chn_cb_negotiate;
326         hv_cb_utils[HV_HEARTBEAT_MSG].callback = NULL;
327
328         if (hv_cb_utils[HV_KVP_MSG].channel != NULL)
329                 hv_cb_utils[HV_KVP_MSG].channel->onchannel_callback =
330                         &chn_cb_negotiate;
331         hv_cb_utils[HV_KVP_MSG].callback = NULL;
332
333         hv_kvp_deinit();
334
335         kfree(shut_txf_buf);
336         kfree(time_txf_buf);
337         kfree(hbeat_txf_buf);
338         vmbus_driver_unregister(&util_drv);
339 }
340
341 module_init(init_hyperv_utils);
342 module_exit(exit_hyperv_utils);
343
344 MODULE_DESCRIPTION("Hyper-V Utilities");
345 MODULE_VERSION(HV_DRV_VERSION);
346 MODULE_LICENSE("GPL");