hv_connection.c revision 294886
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#include <sys/param.h> 30#include <sys/malloc.h> 31#include <sys/systm.h> 32#include <sys/lock.h> 33#include <sys/mutex.h> 34#include <machine/bus.h> 35#include <vm/vm.h> 36#include <vm/vm_param.h> 37#include <vm/pmap.h> 38 39#include "hv_vmbus_priv.h" 40 41/* 42 * Globals 43 */ 44hv_vmbus_connection hv_vmbus_g_connection = 45 { .connect_state = HV_DISCONNECTED, 46 .next_gpadl_handle = 0xE1E10, }; 47 48uint32_t hv_vmbus_protocal_version = HV_VMBUS_VERSION_WS2008; 49 50static uint32_t 51hv_vmbus_get_next_version(uint32_t current_ver) 52{ 53 switch (current_ver) { 54 case (HV_VMBUS_VERSION_WIN7): 55 return(HV_VMBUS_VERSION_WS2008); 56 57 case (HV_VMBUS_VERSION_WIN8): 58 return(HV_VMBUS_VERSION_WIN7); 59 60 case (HV_VMBUS_VERSION_WIN8_1): 61 return(HV_VMBUS_VERSION_WIN8); 62 63 case (HV_VMBUS_VERSION_WS2008): 64 default: 65 return(HV_VMBUS_VERSION_INVALID); 66 } 67} 68 69/** 70 * Negotiate the highest supported hypervisor version. 71 */ 72static int 73hv_vmbus_negotiate_version(hv_vmbus_channel_msg_info *msg_info, 74 uint32_t version) 75{ 76 int ret = 0; 77 hv_vmbus_channel_initiate_contact *msg; 78 79 sema_init(&msg_info->wait_sema, 0, "Msg Info Sema"); 80 msg = (hv_vmbus_channel_initiate_contact*) msg_info->msg; 81 82 msg->header.message_type = HV_CHANNEL_MESSAGE_INITIATED_CONTACT; 83 msg->vmbus_version_requested = version; 84 85 msg->interrupt_page = hv_get_phys_addr( 86 hv_vmbus_g_connection.interrupt_page); 87 88 msg->monitor_page_1 = hv_get_phys_addr( 89 hv_vmbus_g_connection.monitor_pages); 90 91 msg->monitor_page_2 = 92 hv_get_phys_addr( 93 ((uint8_t *) hv_vmbus_g_connection.monitor_pages 94 + PAGE_SIZE)); 95 96 /** 97 * Add to list before we send the request since we may receive the 98 * response before returning from this routine 99 */ 100 mtx_lock_spin(&hv_vmbus_g_connection.channel_msg_lock); 101 102 TAILQ_INSERT_TAIL( 103 &hv_vmbus_g_connection.channel_msg_anchor, 104 msg_info, 105 msg_list_entry); 106 107 mtx_unlock_spin(&hv_vmbus_g_connection.channel_msg_lock); 108 109 ret = hv_vmbus_post_message( 110 msg, 111 sizeof(hv_vmbus_channel_initiate_contact)); 112 113 if (ret != 0) { 114 mtx_lock_spin(&hv_vmbus_g_connection.channel_msg_lock); 115 TAILQ_REMOVE( 116 &hv_vmbus_g_connection.channel_msg_anchor, 117 msg_info, 118 msg_list_entry); 119 mtx_unlock_spin(&hv_vmbus_g_connection.channel_msg_lock); 120 return (ret); 121 } 122 123 /** 124 * Wait for the connection response 125 */ 126 ret = sema_timedwait(&msg_info->wait_sema, 500); /* KYS 5 seconds */ 127 128 mtx_lock_spin(&hv_vmbus_g_connection.channel_msg_lock); 129 TAILQ_REMOVE( 130 &hv_vmbus_g_connection.channel_msg_anchor, 131 msg_info, 132 msg_list_entry); 133 mtx_unlock_spin(&hv_vmbus_g_connection.channel_msg_lock); 134 135 /** 136 * Check if successful 137 */ 138 if (msg_info->response.version_response.version_supported) { 139 hv_vmbus_g_connection.connect_state = HV_CONNECTED; 140 } else { 141 ret = ECONNREFUSED; 142 } 143 144 return (ret); 145} 146 147/** 148 * Send a connect request on the partition service connection 149 */ 150int 151hv_vmbus_connect(void) { 152 int ret = 0; 153 uint32_t version; 154 hv_vmbus_channel_msg_info* msg_info = NULL; 155 156 /** 157 * Make sure we are not connecting or connected 158 */ 159 if (hv_vmbus_g_connection.connect_state != HV_DISCONNECTED) { 160 return (-1); 161 } 162 163 /** 164 * Initialize the vmbus connection 165 */ 166 hv_vmbus_g_connection.connect_state = HV_CONNECTING; 167 hv_vmbus_g_connection.work_queue = hv_work_queue_create("vmbusQ"); 168 sema_init(&hv_vmbus_g_connection.control_sema, 1, "control_sema"); 169 170 TAILQ_INIT(&hv_vmbus_g_connection.channel_msg_anchor); 171 mtx_init(&hv_vmbus_g_connection.channel_msg_lock, "vmbus channel msg", 172 NULL, MTX_SPIN); 173 174 TAILQ_INIT(&hv_vmbus_g_connection.channel_anchor); 175 mtx_init(&hv_vmbus_g_connection.channel_lock, "vmbus channel", 176 NULL, MTX_DEF); 177 178 /** 179 * Setup the vmbus event connection for channel interrupt abstraction 180 * stuff 181 */ 182 hv_vmbus_g_connection.interrupt_page = contigmalloc( 183 PAGE_SIZE, M_DEVBUF, 184 M_NOWAIT | M_ZERO, 0UL, 185 BUS_SPACE_MAXADDR, 186 PAGE_SIZE, 0); 187 KASSERT(hv_vmbus_g_connection.interrupt_page != NULL, 188 ("Error VMBUS: malloc failed to allocate Channel" 189 " Request Event message!")); 190 if (hv_vmbus_g_connection.interrupt_page == NULL) { 191 ret = ENOMEM; 192 goto cleanup; 193 } 194 195 hv_vmbus_g_connection.recv_interrupt_page = 196 hv_vmbus_g_connection.interrupt_page; 197 198 hv_vmbus_g_connection.send_interrupt_page = 199 ((uint8_t *) hv_vmbus_g_connection.interrupt_page + 200 (PAGE_SIZE >> 1)); 201 202 /** 203 * Set up the monitor notification facility. The 1st page for 204 * parent->child and the 2nd page for child->parent 205 */ 206 hv_vmbus_g_connection.monitor_pages = contigmalloc( 207 2 * PAGE_SIZE, 208 M_DEVBUF, 209 M_NOWAIT | M_ZERO, 210 0UL, 211 BUS_SPACE_MAXADDR, 212 PAGE_SIZE, 213 0); 214 KASSERT(hv_vmbus_g_connection.monitor_pages != NULL, 215 ("Error VMBUS: malloc failed to allocate Monitor Pages!")); 216 if (hv_vmbus_g_connection.monitor_pages == NULL) { 217 ret = ENOMEM; 218 goto cleanup; 219 } 220 221 msg_info = (hv_vmbus_channel_msg_info*) 222 malloc(sizeof(hv_vmbus_channel_msg_info) + 223 sizeof(hv_vmbus_channel_initiate_contact), 224 M_DEVBUF, M_NOWAIT | M_ZERO); 225 KASSERT(msg_info != NULL, 226 ("Error VMBUS: malloc failed for Initiate Contact message!")); 227 if (msg_info == NULL) { 228 ret = ENOMEM; 229 goto cleanup; 230 } 231 232 hv_vmbus_g_connection.channels = malloc(sizeof(hv_vmbus_channel*) * 233 HV_CHANNEL_MAX_COUNT, 234 M_DEVBUF, M_WAITOK | M_ZERO); 235 /* 236 * Find the highest vmbus version number we can support. 237 */ 238 version = HV_VMBUS_VERSION_CURRENT; 239 240 do { 241 ret = hv_vmbus_negotiate_version(msg_info, version); 242 if (ret == EWOULDBLOCK) { 243 /* 244 * We timed out. 245 */ 246 goto cleanup; 247 } 248 249 if (hv_vmbus_g_connection.connect_state == HV_CONNECTED) 250 break; 251 252 version = hv_vmbus_get_next_version(version); 253 } while (version != HV_VMBUS_VERSION_INVALID); 254 255 hv_vmbus_protocal_version = version; 256 if (bootverbose) 257 printf("VMBUS: Protocol Version: %d.%d\n", 258 version >> 16, version & 0xFFFF); 259 260 sema_destroy(&msg_info->wait_sema); 261 free(msg_info, M_DEVBUF); 262 263 return (0); 264 265 /* 266 * Cleanup after failure! 267 */ 268 cleanup: 269 270 hv_vmbus_g_connection.connect_state = HV_DISCONNECTED; 271 272 hv_work_queue_close(hv_vmbus_g_connection.work_queue); 273 sema_destroy(&hv_vmbus_g_connection.control_sema); 274 mtx_destroy(&hv_vmbus_g_connection.channel_lock); 275 mtx_destroy(&hv_vmbus_g_connection.channel_msg_lock); 276 277 if (hv_vmbus_g_connection.interrupt_page != NULL) { 278 contigfree( 279 hv_vmbus_g_connection.interrupt_page, 280 PAGE_SIZE, 281 M_DEVBUF); 282 hv_vmbus_g_connection.interrupt_page = NULL; 283 } 284 285 if (hv_vmbus_g_connection.monitor_pages != NULL) { 286 contigfree( 287 hv_vmbus_g_connection.monitor_pages, 288 2 * PAGE_SIZE, 289 M_DEVBUF); 290 hv_vmbus_g_connection.monitor_pages = NULL; 291 } 292 293 if (msg_info) { 294 sema_destroy(&msg_info->wait_sema); 295 free(msg_info, M_DEVBUF); 296 } 297 298 free(hv_vmbus_g_connection.channels, M_DEVBUF); 299 return (ret); 300} 301 302/** 303 * Send a disconnect request on the partition service connection 304 */ 305int 306hv_vmbus_disconnect(void) { 307 int ret = 0; 308 hv_vmbus_channel_unload* msg; 309 310 msg = malloc(sizeof(hv_vmbus_channel_unload), 311 M_DEVBUF, M_NOWAIT | M_ZERO); 312 KASSERT(msg != NULL, 313 ("Error VMBUS: malloc failed to allocate Channel Unload Msg!")); 314 if (msg == NULL) 315 return (ENOMEM); 316 317 msg->message_type = HV_CHANNEL_MESSAGE_UNLOAD; 318 319 ret = hv_vmbus_post_message(msg, sizeof(hv_vmbus_channel_unload)); 320 321 322 contigfree(hv_vmbus_g_connection.interrupt_page, PAGE_SIZE, M_DEVBUF); 323 324 mtx_destroy(&hv_vmbus_g_connection.channel_msg_lock); 325 326 hv_work_queue_close(hv_vmbus_g_connection.work_queue); 327 sema_destroy(&hv_vmbus_g_connection.control_sema); 328 329 free(hv_vmbus_g_connection.channels, M_DEVBUF); 330 hv_vmbus_g_connection.connect_state = HV_DISCONNECTED; 331 332 free(msg, M_DEVBUF); 333 334 return (ret); 335} 336 337/** 338 * Handler for events 339 */ 340void 341hv_vmbus_on_events(int cpu) 342{ 343 int bit; 344 int dword; 345 void *page_addr; 346 uint32_t* recv_interrupt_page = NULL; 347 int rel_id; 348 int maxdword; 349 hv_vmbus_synic_event_flags *event; 350 /* int maxdword = PAGE_SIZE >> 3; */ 351 352 KASSERT(cpu <= mp_maxid, ("VMBUS: hv_vmbus_on_events: " 353 "cpu out of range!")); 354 355 if ((hv_vmbus_protocal_version == HV_VMBUS_VERSION_WS2008) || 356 (hv_vmbus_protocal_version == HV_VMBUS_VERSION_WIN7)) { 357 maxdword = HV_MAX_NUM_CHANNELS_SUPPORTED >> 5; 358 /* 359 * receive size is 1/2 page and divide that by 4 bytes 360 */ 361 recv_interrupt_page = 362 hv_vmbus_g_connection.recv_interrupt_page; 363 } else { 364 /* 365 * On Host with Win8 or above, the event page can be 366 * checked directly to get the id of the channel 367 * that has the pending interrupt. 368 */ 369 maxdword = HV_EVENT_FLAGS_DWORD_COUNT; 370 page_addr = hv_vmbus_g_context.syn_ic_event_page[cpu]; 371 event = (hv_vmbus_synic_event_flags *) 372 page_addr + HV_VMBUS_MESSAGE_SINT; 373 recv_interrupt_page = event->flags32; 374 } 375 376 /* 377 * Check events 378 */ 379 if (recv_interrupt_page != NULL) { 380 for (dword = 0; dword < maxdword; dword++) { 381 if (recv_interrupt_page[dword]) { 382 for (bit = 0; bit < HV_CHANNEL_DWORD_LEN; bit++) { 383 if (synch_test_and_clear_bit(bit, 384 (uint32_t *) &recv_interrupt_page[dword])) { 385 rel_id = (dword << 5) + bit; 386 if (rel_id == 0) { 387 /* 388 * Special case - 389 * vmbus channel protocol msg. 390 */ 391 continue; 392 } else { 393 hv_vmbus_channel * channel = hv_vmbus_g_connection.channels[rel_id]; 394 /* if channel is closed or closing */ 395 if (channel == NULL || channel->rxq == NULL) 396 continue; 397 398 if (channel->batched_reading) 399 hv_ring_buffer_read_begin(&channel->inbound); 400 taskqueue_enqueue_fast(channel->rxq, &channel->channel_task); 401 } 402 } 403 } 404 } 405 } 406 } 407 408 return; 409} 410 411/** 412 * Send a msg on the vmbus's message connection 413 */ 414int hv_vmbus_post_message(void *buffer, size_t bufferLen) { 415 int ret = 0; 416 hv_vmbus_connection_id connId; 417 unsigned retries = 0; 418 419 /* NetScaler delays from previous code were consolidated here */ 420 static int delayAmount[] = {100, 100, 100, 500, 500, 5000, 5000, 5000}; 421 422 /* for(each entry in delayAmount) try to post message, 423 * delay a little bit before retrying 424 */ 425 for (retries = 0; 426 retries < sizeof(delayAmount)/sizeof(delayAmount[0]); retries++) { 427 connId.as_uint32_t = 0; 428 connId.u.id = HV_VMBUS_MESSAGE_CONNECTION_ID; 429 ret = hv_vmbus_post_msg_via_msg_ipc(connId, 1, buffer, bufferLen); 430 if (ret != HV_STATUS_INSUFFICIENT_BUFFERS) 431 break; 432 /* TODO: KYS We should use a blocking wait call */ 433 DELAY(delayAmount[retries]); 434 } 435 436 KASSERT(ret == 0, ("Error VMBUS: Message Post Failed\n")); 437 438 return (ret); 439} 440 441/** 442 * Send an event notification to the parent 443 */ 444int 445hv_vmbus_set_event(hv_vmbus_channel *channel) { 446 int ret = 0; 447 uint32_t child_rel_id = channel->offer_msg.child_rel_id; 448 449 /* Each uint32_t represents 32 channels */ 450 451 synch_set_bit(child_rel_id & 31, 452 (((uint32_t *)hv_vmbus_g_connection.send_interrupt_page 453 + (child_rel_id >> 5)))); 454 ret = hv_vmbus_signal_event(channel->signal_event_param); 455 456 return (ret); 457} 458