vmbus_ic.c revision 250199
1/*- 2 * Copyright (c) 2009-2012 Microsoft Corp. 3 * Copyright (c) 2012 NetApp Inc. 4 * Copyright (c) 2012 Citrix Inc. 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice unmodified, this list of conditions, and the following 12 * disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29/** 30 * A common driver for all hyper-V util services. 31 */ 32 33#include <sys/param.h> 34#include <sys/kernel.h> 35#include <sys/bus.h> 36#include <sys/malloc.h> 37#include <sys/module.h> 38#include <sys/reboot.h> 39#include <sys/timetc.h> 40 41#include <dev/hyperv/include/hyperv.h> 42 43#define HV_SHUT_DOWN 0 44#define HV_TIME_SYNCH 1 45#define HV_HEART_BEAT 2 46#define HV_KVP 3 47#define HV_MAX_UTIL_SERVICES 4 48 49#define HV_NANO_SEC 1000000000L /* 10^ 9 nanosecs = 1 sec */ 50 51#define HV_WLTIMEDELTA 116444736000000000L /* in 100ns unit */ 52#define HV_ICTIMESYNCFLAG_PROBE 0 53#define HV_ICTIMESYNCFLAG_SYNC 1 54#define HV_ICTIMESYNCFLAG_SAMPLE 2 55 56typedef struct hv_vmbus_service { 57 hv_guid guid; /* Hyper-V GUID */ 58 char* name; /* name of service */ 59 boolean_t enabled; /* service enabled */ 60 hv_work_queue* work_queue; /* background work queue */ 61 /* 62 * function to initialize service 63 */ 64 int (*init)(struct hv_vmbus_service *); 65 /* 66 * function to process Hyper-V messages 67 */ 68 void (*callback)(void *); 69} hv_vmbus_service; 70 71static void hv_shutdown_cb(void *context); 72static void hv_heartbeat_cb(void *context); 73static void hv_timesync_cb(void *context); 74static void hv_kvp_cb(void *context); 75 76static int hv_timesync_init(hv_vmbus_service *serv); 77 78/** 79 * Note: GUID codes below are predefined by the host hypervisor 80 * (Hyper-V and Azure)interface and required for correct operation. 81 */ 82static hv_vmbus_service service_table[] = { 83 /* Shutdown Service */ 84 { .guid.data = {0x31, 0x60, 0x0B, 0X0E, 0x13, 0x52, 0x34, 0x49, 85 0x81, 0x8B, 0x38, 0XD9, 0x0C, 0xED, 0x39, 0xDB}, 86 .name = "Hyper-V Shutdown Service\n", 87 .enabled = TRUE, 88 .callback = hv_shutdown_cb, 89 }, 90 91 /* Time Synch Service */ 92 { .guid.data = {0x30, 0xe6, 0x27, 0x95, 0xae, 0xd0, 0x7b, 0x49, 93 0xad, 0xce, 0xe8, 0x0a, 0xb0, 0x17, 0x5c, 0xaf}, 94 .name = "Hyper-V Time Synch Service\n", 95 .enabled = TRUE, 96 .init = hv_timesync_init, 97 .callback = hv_timesync_cb, 98 }, 99 100 /* Heartbeat Service */ 101 { .guid.data = {0x39, 0x4f, 0x16, 0x57, 0x15, 0x91, 0x78, 0x4e, 102 0xab, 0x55, 0x38, 0x2f, 0x3b, 0xd5, 0x42, 0x2d}, 103 .name = "Hyper-V Heartbeat Service\n", 104 .enabled = TRUE, 105 .callback = hv_heartbeat_cb, 106 107 }, 108 109 /* KVP (Key Value Pair) Service */ 110 { .guid.data = {0xe7, 0xf4, 0xa0, 0xa9, 0x45, 0x5a, 0x96, 0x4d, 111 0xb8, 0x27, 0x8a, 0x84, 0x1e, 0x8c, 0x3, 0xe6}, 112 .name = "Hyper-V KVP Service\n", 113 .enabled = FALSE, 114 .callback = hv_kvp_cb, 115 }, 116}; 117 118/** 119 * Receive buffer pointers, there is one buffer per utility service. The 120 * buffer is allocated during attach(). 121 */ 122static uint8_t* receive_buffer[HV_MAX_UTIL_SERVICES]; 123 124struct hv_ictimesync_data { 125 uint64_t parenttime; 126 uint64_t childtime; 127 uint64_t roundtriptime; 128 uint8_t flags; 129} __packed; 130 131static int hv_timesync_init(hv_vmbus_service *serv) 132{ 133 serv->work_queue = hv_work_queue_create("Time Sync"); 134 if (serv->work_queue == NULL) 135 return (ENOMEM); 136 return (0); 137} 138 139static void 140hv_negotiate_version( 141 struct hv_vmbus_icmsg_hdr* icmsghdrp, 142 struct hv_vmbus_icmsg_negotiate* negop, 143 uint8_t* buf) 144 { 145 icmsghdrp->icmsgsize = 0x10; 146 147 negop = (struct hv_vmbus_icmsg_negotiate *)&buf[ 148 sizeof(struct hv_vmbus_pipe_hdr) + 149 sizeof(struct hv_vmbus_icmsg_hdr)]; 150 151 if (negop->icframe_vercnt == 2 && 152 negop->icversion_data[1].major == 3) { 153 negop->icversion_data[0].major = 3; 154 negop->icversion_data[0].minor = 0; 155 negop->icversion_data[1].major = 3; 156 negop->icversion_data[1].minor = 0; 157 } else { 158 negop->icversion_data[0].major = 1; 159 negop->icversion_data[0].minor = 0; 160 negop->icversion_data[1].major = 1; 161 negop->icversion_data[1].minor = 0; 162 } 163 164 negop->icframe_vercnt = 1; 165 negop->icmsg_vercnt = 1; 166} 167 168static void hv_kvp_cb(void *context) 169{ 170} 171 172/** 173 * Set host time based on time sync message from host 174 */ 175static void 176hv_set_host_time(void *context) 177{ 178 uint64_t hosttime = (uint64_t)context; 179 struct timespec ts, host_ts; 180 int64_t tns, host_tns, tmp, tsec; 181 182 nanotime(&ts); 183 tns = ts.tv_sec * HV_NANO_SEC + ts.tv_nsec; 184 host_tns = (hosttime - HV_WLTIMEDELTA) * 100; 185 186 tmp = host_tns; 187 tsec = tmp / HV_NANO_SEC; 188 host_ts.tv_nsec = (long) (tmp - (tsec * HV_NANO_SEC)); 189 host_ts.tv_sec = tsec; 190 191 /* force time sync with host after reboot, restore, etc. */ 192 mtx_lock(&Giant); 193 tc_setclock(&host_ts); 194 resettodr(); 195 mtx_unlock(&Giant); 196} 197 198/** 199 * @brief Synchronize time with host after reboot, restore, etc. 200 * 201 * ICTIMESYNCFLAG_SYNC flag bit indicates reboot, restore events of the VM. 202 * After reboot the flag ICTIMESYNCFLAG_SYNC is included in the first time 203 * message after the timesync channel is opened. Since the hv_utils module is 204 * loaded after hv_vmbus, the first message is usually missed. The other 205 * thing is, systime is automatically set to emulated hardware clock which may 206 * not be UTC time or in the same time zone. So, to override these effects, we 207 * use the first 50 time samples for initial system time setting. 208 */ 209static inline 210void hv_adj_guesttime(uint64_t hosttime, uint8_t flags) 211{ 212 static int scnt = 50; 213 214 if ((flags & HV_ICTIMESYNCFLAG_SYNC) != 0) { 215 hv_queue_work_item(service_table[HV_TIME_SYNCH].work_queue, 216 hv_set_host_time, (void *) hosttime); 217 return; 218 } 219 220 if ((flags & HV_ICTIMESYNCFLAG_SAMPLE) != 0 && scnt > 0) { 221 scnt--; 222 hv_queue_work_item(service_table[HV_TIME_SYNCH].work_queue, 223 hv_set_host_time, (void *) hosttime); 224 } 225} 226 227/** 228 * Time Sync Channel message handler 229 */ 230static void 231hv_timesync_cb(void *context) 232{ 233 hv_vmbus_channel* channel = context; 234 hv_vmbus_icmsg_hdr* icmsghdrp; 235 uint32_t recvlen; 236 uint64_t requestId; 237 int ret; 238 uint8_t* time_buf; 239 struct hv_ictimesync_data* timedatap; 240 241 time_buf = receive_buffer[HV_TIME_SYNCH]; 242 243 ret = hv_vmbus_channel_recv_packet(channel, time_buf, 244 PAGE_SIZE, &recvlen, &requestId); 245 246 if ((ret == 0) && recvlen > 0) { 247 icmsghdrp = (struct hv_vmbus_icmsg_hdr *) &time_buf[ 248 sizeof(struct hv_vmbus_pipe_hdr)]; 249 250 if (icmsghdrp->icmsgtype == HV_ICMSGTYPE_NEGOTIATE) { 251 hv_negotiate_version(icmsghdrp, NULL, time_buf); 252 } else { 253 timedatap = (struct hv_ictimesync_data *) &time_buf[ 254 sizeof(struct hv_vmbus_pipe_hdr) + 255 sizeof(struct hv_vmbus_icmsg_hdr)]; 256 hv_adj_guesttime(timedatap->parenttime, timedatap->flags); 257 } 258 259 icmsghdrp->icflags = HV_ICMSGHDRFLAG_TRANSACTION 260 | HV_ICMSGHDRFLAG_RESPONSE; 261 262 hv_vmbus_channel_send_packet(channel, time_buf, 263 recvlen, requestId, 264 HV_VMBUS_PACKET_TYPE_DATA_IN_BAND, 0); 265 } 266} 267 268/** 269 * Shutdown 270 */ 271static void 272hv_shutdown_cb(void *context) 273{ 274 uint8_t* buf; 275 hv_vmbus_channel* channel = context; 276 uint8_t execute_shutdown = 0; 277 hv_vmbus_icmsg_hdr* icmsghdrp; 278 uint32_t recv_len; 279 uint64_t request_id; 280 int ret; 281 hv_vmbus_shutdown_msg_data* shutdown_msg; 282 283 buf = receive_buffer[HV_SHUT_DOWN]; 284 285 ret = hv_vmbus_channel_recv_packet(channel, buf, PAGE_SIZE, 286 &recv_len, &request_id); 287 288 if ((ret == 0) && recv_len > 0) { 289 290 icmsghdrp = (struct hv_vmbus_icmsg_hdr *) 291 &buf[sizeof(struct hv_vmbus_pipe_hdr)]; 292 293 if (icmsghdrp->icmsgtype == HV_ICMSGTYPE_NEGOTIATE) { 294 hv_negotiate_version(icmsghdrp, NULL, buf); 295 296 } else { 297 shutdown_msg = 298 (struct hv_vmbus_shutdown_msg_data *) 299 &buf[sizeof(struct hv_vmbus_pipe_hdr) + 300 sizeof(struct hv_vmbus_icmsg_hdr)]; 301 302 switch (shutdown_msg->flags) { 303 case 0: 304 case 1: 305 icmsghdrp->status = HV_S_OK; 306 execute_shutdown = 1; 307 if(bootverbose) 308 printf("Shutdown request received -" 309 " graceful shutdown initiated\n"); 310 break; 311 default: 312 icmsghdrp->status = HV_E_FAIL; 313 execute_shutdown = 0; 314 printf("Shutdown request received -" 315 " Invalid request\n"); 316 break; 317 } 318 } 319 320 icmsghdrp->icflags = HV_ICMSGHDRFLAG_TRANSACTION | 321 HV_ICMSGHDRFLAG_RESPONSE; 322 323 hv_vmbus_channel_send_packet(channel, buf, 324 recv_len, request_id, 325 HV_VMBUS_PACKET_TYPE_DATA_IN_BAND, 0); 326 } 327 328 if (execute_shutdown) 329 shutdown_nice(RB_POWEROFF); 330} 331 332/** 333 * Process heartbeat message 334 */ 335static void 336hv_heartbeat_cb(void *context) 337{ 338 uint8_t* buf; 339 hv_vmbus_channel* channel = context; 340 uint32_t recvlen; 341 uint64_t requestid; 342 int ret; 343 344 struct hv_vmbus_heartbeat_msg_data* heartbeat_msg; 345 struct hv_vmbus_icmsg_hdr* icmsghdrp; 346 347 buf = receive_buffer[HV_HEART_BEAT]; 348 349 ret = hv_vmbus_channel_recv_packet(channel, buf, PAGE_SIZE, &recvlen, 350 &requestid); 351 352 if ((ret == 0) && recvlen > 0) { 353 354 icmsghdrp = (struct hv_vmbus_icmsg_hdr *) 355 &buf[sizeof(struct hv_vmbus_pipe_hdr)]; 356 357 if (icmsghdrp->icmsgtype == HV_ICMSGTYPE_NEGOTIATE) { 358 hv_negotiate_version(icmsghdrp, NULL, buf); 359 360 } else { 361 heartbeat_msg = 362 (struct hv_vmbus_heartbeat_msg_data *) 363 &buf[sizeof(struct hv_vmbus_pipe_hdr) + 364 sizeof(struct hv_vmbus_icmsg_hdr)]; 365 366 heartbeat_msg->seq_num += 1; 367 } 368 369 icmsghdrp->icflags = HV_ICMSGHDRFLAG_TRANSACTION | 370 HV_ICMSGHDRFLAG_RESPONSE; 371 372 hv_vmbus_channel_send_packet(channel, buf, recvlen, requestid, 373 HV_VMBUS_PACKET_TYPE_DATA_IN_BAND, 0); 374 } 375} 376 377 378static int 379hv_util_probe(device_t dev) 380{ 381 int i; 382 int rtn_value = ENXIO; 383 384 for (i = 0; i < HV_MAX_UTIL_SERVICES; i++) { 385 const char *p = vmbus_get_type(dev); 386 if (service_table[i].enabled && !memcmp(p, &service_table[i].guid, sizeof(hv_guid))) { 387 device_set_softc(dev, (void *) (&service_table[i])); 388 rtn_value = 0; 389 } 390 } 391 392 return rtn_value; 393} 394 395static int 396hv_util_attach(device_t dev) 397{ 398 struct hv_device* hv_dev; 399 struct hv_vmbus_service* service; 400 int ret; 401 size_t receive_buffer_offset; 402 403 hv_dev = vmbus_get_devctx(dev); 404 service = device_get_softc(dev); 405 receive_buffer_offset = service - &service_table[0]; 406 device_printf(dev, "Hyper-V Service attaching: %s\n", service->name); 407 receive_buffer[receive_buffer_offset] = 408 malloc(PAGE_SIZE, M_DEVBUF, M_WAITOK | M_ZERO); 409 410 if (service->init != NULL) { 411 ret = service->init(service); 412 if (ret) { 413 ret = ENODEV; 414 goto error0; 415 } 416 } 417 418 ret = hv_vmbus_channel_open(hv_dev->channel, 2 * PAGE_SIZE, 419 2 * PAGE_SIZE, NULL, 0, 420 service->callback, hv_dev->channel); 421 422 if (ret) 423 goto error0; 424 425 return (0); 426 427 error0: 428 429 free(receive_buffer[receive_buffer_offset], M_DEVBUF); 430 receive_buffer[receive_buffer_offset] = NULL; 431 432 return (ret); 433} 434 435static int 436hv_util_detach(device_t dev) 437{ 438 struct hv_device* hv_dev; 439 struct hv_vmbus_service* service; 440 size_t receive_buffer_offset; 441 442 hv_dev = vmbus_get_devctx(dev); 443 444 hv_vmbus_channel_close(hv_dev->channel); 445 service = device_get_softc(dev); 446 receive_buffer_offset = service - &service_table[0]; 447 448 if (service->work_queue != NULL) 449 hv_work_queue_close(service->work_queue); 450 451 free(receive_buffer[receive_buffer_offset], M_DEVBUF); 452 receive_buffer[receive_buffer_offset] = NULL; 453 454 return (0); 455} 456 457static void hv_util_init(void) 458{ 459} 460 461static int hv_util_modevent(module_t mod, int event, void *arg) 462{ 463 switch (event) { 464 case MOD_LOAD: 465 break; 466 case MOD_UNLOAD: 467 break; 468 default: 469 break; 470 } 471 return (0); 472} 473 474static device_method_t util_methods[] = { 475 /* Device interface */ 476 DEVMETHOD(device_probe, hv_util_probe), 477 DEVMETHOD(device_attach, hv_util_attach), 478 DEVMETHOD(device_detach, hv_util_detach), 479 DEVMETHOD(device_shutdown, bus_generic_shutdown), 480 { 0, 0 } } 481; 482 483static driver_t util_driver = { "hyperv-utils", util_methods, 0 }; 484 485static devclass_t util_devclass; 486 487DRIVER_MODULE(hv_utils, vmbus, util_driver, util_devclass, hv_util_modevent, 0); 488MODULE_VERSION(hv_utils, 1); 489MODULE_DEPEND(hv_utils, vmbus, 1, 1, 1); 490 491SYSINIT(hv_util_initx, SI_SUB_RUN_SCHEDULER, SI_ORDER_MIDDLE + 1, 492 hv_util_init, NULL); 493