hv_connection.c revision 301583
1/*- 2 * Copyright (c) 2009-2012,2016 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/kernel.h> 31#include <sys/malloc.h> 32#include <sys/systm.h> 33#include <sys/lock.h> 34#include <sys/mutex.h> 35#include <machine/bus.h> 36#include <machine/atomic.h> 37#include <vm/vm.h> 38#include <vm/vm_param.h> 39#include <vm/pmap.h> 40 41#include <dev/hyperv/vmbus/hv_vmbus_priv.h> 42#include <dev/hyperv/vmbus/vmbus_reg.h> 43#include <dev/hyperv/vmbus/vmbus_var.h> 44 45/* 46 * Globals 47 */ 48hv_vmbus_connection hv_vmbus_g_connection = 49 { .connect_state = HV_DISCONNECTED, 50 .next_gpadl_handle = 0xE1E10, }; 51 52uint32_t hv_vmbus_protocal_version = HV_VMBUS_VERSION_WS2008; 53 54static uint32_t 55hv_vmbus_get_next_version(uint32_t current_ver) 56{ 57 switch (current_ver) { 58 case (HV_VMBUS_VERSION_WIN7): 59 return(HV_VMBUS_VERSION_WS2008); 60 61 case (HV_VMBUS_VERSION_WIN8): 62 return(HV_VMBUS_VERSION_WIN7); 63 64 case (HV_VMBUS_VERSION_WIN8_1): 65 return(HV_VMBUS_VERSION_WIN8); 66 67 case (HV_VMBUS_VERSION_WS2008): 68 default: 69 return(HV_VMBUS_VERSION_INVALID); 70 } 71} 72 73/** 74 * Negotiate the highest supported hypervisor version. 75 */ 76static int 77hv_vmbus_negotiate_version(struct vmbus_softc *sc, 78 hv_vmbus_channel_msg_info *msg_info, uint32_t version) 79{ 80 int ret = 0; 81 hv_vmbus_channel_initiate_contact *msg; 82 83 sema_init(&msg_info->wait_sema, 0, "Msg Info Sema"); 84 msg = (hv_vmbus_channel_initiate_contact*) msg_info->msg; 85 86 msg->header.message_type = HV_CHANNEL_MESSAGE_INITIATED_CONTACT; 87 msg->vmbus_version_requested = version; 88 89 msg->interrupt_page = sc->vmbus_evtflags_dma.hv_paddr; 90 msg->monitor_page_1 = sc->vmbus_mnf1_dma.hv_paddr; 91 msg->monitor_page_2 = sc->vmbus_mnf2_dma.hv_paddr; 92 93 /** 94 * Add to list before we send the request since we may receive the 95 * response before returning from this routine 96 */ 97 mtx_lock(&hv_vmbus_g_connection.channel_msg_lock); 98 99 TAILQ_INSERT_TAIL( 100 &hv_vmbus_g_connection.channel_msg_anchor, 101 msg_info, 102 msg_list_entry); 103 104 mtx_unlock(&hv_vmbus_g_connection.channel_msg_lock); 105 106 ret = hv_vmbus_post_message( 107 msg, 108 sizeof(hv_vmbus_channel_initiate_contact)); 109 110 if (ret != 0) { 111 mtx_lock(&hv_vmbus_g_connection.channel_msg_lock); 112 TAILQ_REMOVE( 113 &hv_vmbus_g_connection.channel_msg_anchor, 114 msg_info, 115 msg_list_entry); 116 mtx_unlock(&hv_vmbus_g_connection.channel_msg_lock); 117 return (ret); 118 } 119 120 /** 121 * Wait for the connection response 122 */ 123 ret = sema_timedwait(&msg_info->wait_sema, 5 * hz); /* KYS 5 seconds */ 124 125 mtx_lock(&hv_vmbus_g_connection.channel_msg_lock); 126 TAILQ_REMOVE( 127 &hv_vmbus_g_connection.channel_msg_anchor, 128 msg_info, 129 msg_list_entry); 130 mtx_unlock(&hv_vmbus_g_connection.channel_msg_lock); 131 132 /** 133 * Check if successful 134 */ 135 if (msg_info->response.version_response.version_supported) { 136 hv_vmbus_g_connection.connect_state = HV_CONNECTED; 137 } else { 138 ret = ECONNREFUSED; 139 } 140 141 return (ret); 142} 143 144/** 145 * Send a connect request on the partition service connection 146 */ 147int 148hv_vmbus_connect(struct vmbus_softc *sc) 149{ 150 int ret = 0; 151 uint32_t version; 152 hv_vmbus_channel_msg_info* msg_info = NULL; 153 154 /** 155 * Make sure we are not connecting or connected 156 */ 157 if (hv_vmbus_g_connection.connect_state != HV_DISCONNECTED) { 158 return (-1); 159 } 160 161 /** 162 * Initialize the vmbus connection 163 */ 164 hv_vmbus_g_connection.connect_state = HV_CONNECTING; 165 166 TAILQ_INIT(&hv_vmbus_g_connection.channel_msg_anchor); 167 mtx_init(&hv_vmbus_g_connection.channel_msg_lock, "vmbus channel msg", 168 NULL, MTX_DEF); 169 170 TAILQ_INIT(&hv_vmbus_g_connection.channel_anchor); 171 mtx_init(&hv_vmbus_g_connection.channel_lock, "vmbus channel", 172 NULL, MTX_DEF); 173 174 msg_info = (hv_vmbus_channel_msg_info*) 175 malloc(sizeof(hv_vmbus_channel_msg_info) + 176 sizeof(hv_vmbus_channel_initiate_contact), 177 M_DEVBUF, M_WAITOK | M_ZERO); 178 179 hv_vmbus_g_connection.channels = malloc(sizeof(hv_vmbus_channel*) * 180 VMBUS_CHAN_MAX, M_DEVBUF, M_WAITOK | M_ZERO); 181 /* 182 * Find the highest vmbus version number we can support. 183 */ 184 version = HV_VMBUS_VERSION_CURRENT; 185 186 do { 187 ret = hv_vmbus_negotiate_version(sc, msg_info, version); 188 if (ret == EWOULDBLOCK) { 189 /* 190 * We timed out. 191 */ 192 goto cleanup; 193 } 194 195 if (hv_vmbus_g_connection.connect_state == HV_CONNECTED) 196 break; 197 198 version = hv_vmbus_get_next_version(version); 199 } while (version != HV_VMBUS_VERSION_INVALID); 200 201 hv_vmbus_protocal_version = version; 202 if (bootverbose) 203 printf("VMBUS: Protocol Version: %d.%d\n", 204 version >> 16, version & 0xFFFF); 205 206 sema_destroy(&msg_info->wait_sema); 207 free(msg_info, M_DEVBUF); 208 209 return (0); 210 211 /* 212 * Cleanup after failure! 213 */ 214 cleanup: 215 216 hv_vmbus_g_connection.connect_state = HV_DISCONNECTED; 217 218 mtx_destroy(&hv_vmbus_g_connection.channel_lock); 219 mtx_destroy(&hv_vmbus_g_connection.channel_msg_lock); 220 221 if (msg_info) { 222 sema_destroy(&msg_info->wait_sema); 223 free(msg_info, M_DEVBUF); 224 } 225 226 free(hv_vmbus_g_connection.channels, M_DEVBUF); 227 return (ret); 228} 229 230/** 231 * Send a disconnect request on the partition service connection 232 */ 233int 234hv_vmbus_disconnect(void) 235{ 236 int ret = 0; 237 hv_vmbus_channel_unload msg; 238 239 msg.message_type = HV_CHANNEL_MESSAGE_UNLOAD; 240 241 ret = hv_vmbus_post_message(&msg, sizeof(hv_vmbus_channel_unload)); 242 243 mtx_destroy(&hv_vmbus_g_connection.channel_msg_lock); 244 245 free(hv_vmbus_g_connection.channels, M_DEVBUF); 246 hv_vmbus_g_connection.connect_state = HV_DISCONNECTED; 247 248 return (ret); 249} 250 251static __inline void 252vmbus_event_flags_proc(volatile u_long *event_flags, int flag_cnt) 253{ 254 int f; 255 256 for (f = 0; f < flag_cnt; ++f) { 257 uint32_t rel_id_base; 258 u_long flags; 259 int bit; 260 261 if (event_flags[f] == 0) 262 continue; 263 264 flags = atomic_swap_long(&event_flags[f], 0); 265 rel_id_base = f << VMBUS_EVTFLAG_SHIFT; 266 267 while ((bit = ffsl(flags)) != 0) { 268 struct hv_vmbus_channel *channel; 269 uint32_t rel_id; 270 271 --bit; /* NOTE: ffsl is 1-based */ 272 flags &= ~(1UL << bit); 273 274 rel_id = rel_id_base + bit; 275 channel = hv_vmbus_g_connection.channels[rel_id]; 276 277 /* if channel is closed or closing */ 278 if (channel == NULL || channel->rxq == NULL) 279 continue; 280 281 if (channel->batched_reading) 282 hv_ring_buffer_read_begin(&channel->inbound); 283 taskqueue_enqueue(channel->rxq, &channel->channel_task); 284 } 285 } 286} 287 288void 289vmbus_event_proc(struct vmbus_softc *sc, int cpu) 290{ 291 struct vmbus_evtflags *eventf; 292 293 /* 294 * On Host with Win8 or above, the event page can be checked directly 295 * to get the id of the channel that has the pending interrupt. 296 */ 297 eventf = VMBUS_PCPU_GET(sc, event_flags, cpu) + VMBUS_SINT_MESSAGE; 298 vmbus_event_flags_proc(eventf->evt_flags, 299 VMBUS_PCPU_GET(sc, event_flags_cnt, cpu)); 300} 301 302void 303vmbus_event_proc_compat(struct vmbus_softc *sc, int cpu) 304{ 305 struct vmbus_evtflags *eventf; 306 307 eventf = VMBUS_PCPU_GET(sc, event_flags, cpu) + VMBUS_SINT_MESSAGE; 308 if (atomic_testandclear_long(&eventf->evt_flags[0], 0)) { 309 vmbus_event_flags_proc(sc->vmbus_rx_evtflags, 310 VMBUS_CHAN_MAX_COMPAT >> VMBUS_EVTFLAG_SHIFT); 311 } 312} 313 314/** 315 * Send a msg on the vmbus's message connection 316 */ 317int hv_vmbus_post_message(void *buffer, size_t bufferLen) 318{ 319 hv_vmbus_connection_id connId; 320 sbintime_t time = SBT_1MS; 321 int retries; 322 int ret; 323 324 connId.as_uint32_t = 0; 325 connId.u.id = HV_VMBUS_MESSAGE_CONNECTION_ID; 326 327 /* 328 * We retry to cope with transient failures caused by host side's 329 * insufficient resources. 20 times should suffice in practice. 330 */ 331 for (retries = 0; retries < 20; retries++) { 332 ret = hv_vmbus_post_msg_via_msg_ipc(connId, 333 VMBUS_MSGTYPE_CHANNEL, buffer, bufferLen); 334 if (ret == HV_STATUS_SUCCESS) 335 return (0); 336 337 pause_sbt("pstmsg", time, 0, C_HARDCLOCK); 338 if (time < SBT_1S * 2) 339 time *= 2; 340 } 341 342 KASSERT(ret == HV_STATUS_SUCCESS, 343 ("Error VMBUS: Message Post Failed, ret=%d\n", ret)); 344 345 return (EAGAIN); 346} 347 348/** 349 * Send an event notification to the parent 350 */ 351int 352hv_vmbus_set_event(hv_vmbus_channel *channel) 353{ 354 struct vmbus_softc *sc = vmbus_get_softc(); 355 int ret = 0; 356 uint32_t child_rel_id = channel->offer_msg.child_rel_id; 357 358 /* Each uint32_t represents 32 channels */ 359 360 synch_set_bit(child_rel_id & 31, 361 (((uint32_t *)sc->vmbus_tx_evtflags + (child_rel_id >> 5)))); 362 ret = hv_vmbus_signal_event(channel->signal_event_param); 363 364 return (ret); 365} 366 367void 368vmbus_on_channel_open(const struct hv_vmbus_channel *chan) 369{ 370 volatile int *flag_cnt_ptr; 371 int flag_cnt; 372 373 flag_cnt = (chan->offer_msg.child_rel_id / VMBUS_EVTFLAG_LEN) + 1; 374 flag_cnt_ptr = VMBUS_PCPU_PTR(vmbus_get_softc(), event_flags_cnt, 375 chan->target_cpu); 376 377 for (;;) { 378 int old_flag_cnt; 379 380 old_flag_cnt = *flag_cnt_ptr; 381 if (old_flag_cnt >= flag_cnt) 382 break; 383 if (atomic_cmpset_int(flag_cnt_ptr, old_flag_cnt, flag_cnt)) { 384 if (bootverbose) { 385 printf("VMBUS: channel%u update " 386 "cpu%d flag_cnt to %d\n", 387 chan->offer_msg.child_rel_id, 388 chan->target_cpu, flag_cnt); 389 } 390 break; 391 } 392 } 393} 394