2 * Copyright (c) 2009-2012 Microsoft Corp.
3 * Copyright (c) 2012 NetApp Inc.
4 * Copyright (c) 2012 Citrix Inc.
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
10 * 1. Redistributions of source code must retain the above copyright
11 * notice unmodified, this list of conditions, and the following
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.
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.
29 #include <sys/cdefs.h>
30 __FBSDID("$FreeBSD$");
32 #include <sys/param.h>
33 #include <sys/kernel.h>
34 #include <sys/malloc.h>
35 #include <sys/systm.h>
37 #include <sys/mutex.h>
38 #include <machine/bus.h>
40 #include <vm/vm_param.h>
43 #include "hv_vmbus_priv.h"
48 hv_vmbus_connection hv_vmbus_g_connection =
49 { .connect_state = HV_DISCONNECTED,
50 .next_gpadl_handle = 0xE1E10, };
52 uint32_t hv_vmbus_protocal_version = HV_VMBUS_VERSION_WS2008;
55 hv_vmbus_get_next_version(uint32_t current_ver)
57 switch (current_ver) {
58 case (HV_VMBUS_VERSION_WIN7):
59 return(HV_VMBUS_VERSION_WS2008);
61 case (HV_VMBUS_VERSION_WIN8):
62 return(HV_VMBUS_VERSION_WIN7);
64 case (HV_VMBUS_VERSION_WIN8_1):
65 return(HV_VMBUS_VERSION_WIN8);
67 case (HV_VMBUS_VERSION_WS2008):
69 return(HV_VMBUS_VERSION_INVALID);
74 * Negotiate the highest supported hypervisor version.
77 hv_vmbus_negotiate_version(hv_vmbus_channel_msg_info *msg_info,
81 hv_vmbus_channel_initiate_contact *msg;
83 sema_init(&msg_info->wait_sema, 0, "Msg Info Sema");
84 msg = (hv_vmbus_channel_initiate_contact*) msg_info->msg;
86 msg->header.message_type = HV_CHANNEL_MESSAGE_INITIATED_CONTACT;
87 msg->vmbus_version_requested = version;
89 msg->interrupt_page = hv_get_phys_addr(
90 hv_vmbus_g_connection.interrupt_page);
92 msg->monitor_page_1 = hv_get_phys_addr(
93 hv_vmbus_g_connection.monitor_pages);
97 ((uint8_t *) hv_vmbus_g_connection.monitor_pages
101 * Add to list before we send the request since we may receive the
102 * response before returning from this routine
104 mtx_lock(&hv_vmbus_g_connection.channel_msg_lock);
107 &hv_vmbus_g_connection.channel_msg_anchor,
111 mtx_unlock(&hv_vmbus_g_connection.channel_msg_lock);
113 ret = hv_vmbus_post_message(
115 sizeof(hv_vmbus_channel_initiate_contact));
118 mtx_lock(&hv_vmbus_g_connection.channel_msg_lock);
120 &hv_vmbus_g_connection.channel_msg_anchor,
123 mtx_unlock(&hv_vmbus_g_connection.channel_msg_lock);
128 * Wait for the connection response
130 ret = sema_timedwait(&msg_info->wait_sema, 5 * hz); /* KYS 5 seconds */
132 mtx_lock(&hv_vmbus_g_connection.channel_msg_lock);
134 &hv_vmbus_g_connection.channel_msg_anchor,
137 mtx_unlock(&hv_vmbus_g_connection.channel_msg_lock);
140 * Check if successful
142 if (msg_info->response.version_response.version_supported) {
143 hv_vmbus_g_connection.connect_state = HV_CONNECTED;
152 * Send a connect request on the partition service connection
155 hv_vmbus_connect(void) {
158 hv_vmbus_channel_msg_info* msg_info = NULL;
161 * Make sure we are not connecting or connected
163 if (hv_vmbus_g_connection.connect_state != HV_DISCONNECTED) {
168 * Initialize the vmbus connection
170 hv_vmbus_g_connection.connect_state = HV_CONNECTING;
171 hv_vmbus_g_connection.work_queue = hv_work_queue_create("vmbusQ");
172 sema_init(&hv_vmbus_g_connection.control_sema, 1, "control_sema");
174 TAILQ_INIT(&hv_vmbus_g_connection.channel_msg_anchor);
175 mtx_init(&hv_vmbus_g_connection.channel_msg_lock, "vmbus channel msg",
178 TAILQ_INIT(&hv_vmbus_g_connection.channel_anchor);
179 mtx_init(&hv_vmbus_g_connection.channel_lock, "vmbus channel",
183 * Setup the vmbus event connection for channel interrupt abstraction
186 hv_vmbus_g_connection.interrupt_page = contigmalloc(
188 M_NOWAIT | M_ZERO, 0UL,
191 KASSERT(hv_vmbus_g_connection.interrupt_page != NULL,
192 ("Error VMBUS: malloc failed to allocate Channel"
193 " Request Event message!"));
194 if (hv_vmbus_g_connection.interrupt_page == NULL) {
199 hv_vmbus_g_connection.recv_interrupt_page =
200 hv_vmbus_g_connection.interrupt_page;
202 hv_vmbus_g_connection.send_interrupt_page =
203 ((uint8_t *) hv_vmbus_g_connection.interrupt_page +
207 * Set up the monitor notification facility. The 1st page for
208 * parent->child and the 2nd page for child->parent
210 hv_vmbus_g_connection.monitor_pages = contigmalloc(
218 KASSERT(hv_vmbus_g_connection.monitor_pages != NULL,
219 ("Error VMBUS: malloc failed to allocate Monitor Pages!"));
220 if (hv_vmbus_g_connection.monitor_pages == NULL) {
225 msg_info = (hv_vmbus_channel_msg_info*)
226 malloc(sizeof(hv_vmbus_channel_msg_info) +
227 sizeof(hv_vmbus_channel_initiate_contact),
228 M_DEVBUF, M_NOWAIT | M_ZERO);
229 KASSERT(msg_info != NULL,
230 ("Error VMBUS: malloc failed for Initiate Contact message!"));
231 if (msg_info == NULL) {
236 hv_vmbus_g_connection.channels = malloc(sizeof(hv_vmbus_channel*) *
237 HV_CHANNEL_MAX_COUNT,
238 M_DEVBUF, M_WAITOK | M_ZERO);
240 * Find the highest vmbus version number we can support.
242 version = HV_VMBUS_VERSION_CURRENT;
245 ret = hv_vmbus_negotiate_version(msg_info, version);
246 if (ret == EWOULDBLOCK) {
253 if (hv_vmbus_g_connection.connect_state == HV_CONNECTED)
256 version = hv_vmbus_get_next_version(version);
257 } while (version != HV_VMBUS_VERSION_INVALID);
259 hv_vmbus_protocal_version = version;
261 printf("VMBUS: Protocol Version: %d.%d\n",
262 version >> 16, version & 0xFFFF);
264 sema_destroy(&msg_info->wait_sema);
265 free(msg_info, M_DEVBUF);
270 * Cleanup after failure!
274 hv_vmbus_g_connection.connect_state = HV_DISCONNECTED;
276 hv_work_queue_close(hv_vmbus_g_connection.work_queue);
277 sema_destroy(&hv_vmbus_g_connection.control_sema);
278 mtx_destroy(&hv_vmbus_g_connection.channel_lock);
279 mtx_destroy(&hv_vmbus_g_connection.channel_msg_lock);
281 if (hv_vmbus_g_connection.interrupt_page != NULL) {
283 hv_vmbus_g_connection.interrupt_page,
286 hv_vmbus_g_connection.interrupt_page = NULL;
289 if (hv_vmbus_g_connection.monitor_pages != NULL) {
291 hv_vmbus_g_connection.monitor_pages,
294 hv_vmbus_g_connection.monitor_pages = NULL;
298 sema_destroy(&msg_info->wait_sema);
299 free(msg_info, M_DEVBUF);
302 free(hv_vmbus_g_connection.channels, M_DEVBUF);
307 * Send a disconnect request on the partition service connection
310 hv_vmbus_disconnect(void) {
312 hv_vmbus_channel_unload* msg;
314 msg = malloc(sizeof(hv_vmbus_channel_unload),
315 M_DEVBUF, M_NOWAIT | M_ZERO);
317 ("Error VMBUS: malloc failed to allocate Channel Unload Msg!"));
321 msg->message_type = HV_CHANNEL_MESSAGE_UNLOAD;
323 ret = hv_vmbus_post_message(msg, sizeof(hv_vmbus_channel_unload));
326 contigfree(hv_vmbus_g_connection.interrupt_page, PAGE_SIZE, M_DEVBUF);
328 mtx_destroy(&hv_vmbus_g_connection.channel_msg_lock);
330 hv_work_queue_close(hv_vmbus_g_connection.work_queue);
331 sema_destroy(&hv_vmbus_g_connection.control_sema);
333 free(hv_vmbus_g_connection.channels, M_DEVBUF);
334 hv_vmbus_g_connection.connect_state = HV_DISCONNECTED;
342 * Process a channel event notification
345 VmbusProcessChannelEvent(uint32_t relid)
348 uint32_t bytes_to_read;
349 hv_vmbus_channel* channel;
350 boolean_t is_batched_reading;
353 * Find the channel based on this relid and invokes
354 * the channel callback to process the event
357 channel = hv_vmbus_g_connection.channels[relid];
359 if (channel == NULL) {
363 * To deal with the race condition where we might
364 * receive a packet while the relevant driver is
365 * being unloaded, dispatch the callback while
366 * holding the channel lock. The unloading driver
367 * will acquire the same channel lock to set the
368 * callback to NULL. This closes the window.
372 * Disable the lock due to newly added WITNESS check in r277723.
373 * Will seek other way to avoid race condition.
376 // mtx_lock(&channel->inbound_lock);
377 if (channel->on_channel_callback != NULL) {
378 arg = channel->channel_callback_context;
379 is_batched_reading = channel->batched_reading;
381 * Optimize host to guest signaling by ensuring:
382 * 1. While reading the channel, we disable interrupts from
384 * 2. Ensure that we process all posted messages from the host
385 * before returning from this callback.
386 * 3. Once we return, enable signaling from the host. Once this
387 * state is set we check to see if additional packets are
388 * available to read. In this case we repeat the process.
391 if (is_batched_reading)
392 hv_ring_buffer_read_begin(&channel->inbound);
394 channel->on_channel_callback(arg);
396 if (is_batched_reading)
398 hv_ring_buffer_read_end(&channel->inbound);
401 } while (is_batched_reading && (bytes_to_read != 0));
403 // mtx_unlock(&channel->inbound_lock);
410 hv_vmbus_on_events(void *arg)
416 uint32_t* recv_interrupt_page = NULL;
419 hv_vmbus_synic_event_flags *event;
420 /* int maxdword = PAGE_SIZE >> 3; */
422 cpu = (int)(long)arg;
423 KASSERT(cpu <= mp_maxid, ("VMBUS: hv_vmbus_on_events: "
424 "cpu out of range!"));
426 if ((hv_vmbus_protocal_version == HV_VMBUS_VERSION_WS2008) ||
427 (hv_vmbus_protocal_version == HV_VMBUS_VERSION_WIN7)) {
428 maxdword = HV_MAX_NUM_CHANNELS_SUPPORTED >> 5;
430 * receive size is 1/2 page and divide that by 4 bytes
432 recv_interrupt_page =
433 hv_vmbus_g_connection.recv_interrupt_page;
436 * On Host with Win8 or above, the event page can be
437 * checked directly to get the id of the channel
438 * that has the pending interrupt.
440 maxdword = HV_EVENT_FLAGS_DWORD_COUNT;
441 page_addr = hv_vmbus_g_context.syn_ic_event_page[cpu];
442 event = (hv_vmbus_synic_event_flags *)
443 page_addr + HV_VMBUS_MESSAGE_SINT;
444 recv_interrupt_page = event->flags32;
450 if (recv_interrupt_page != NULL) {
451 for (dword = 0; dword < maxdword; dword++) {
452 if (recv_interrupt_page[dword]) {
453 for (bit = 0; bit < HV_CHANNEL_DWORD_LEN; bit++) {
454 if (synch_test_and_clear_bit(bit,
455 (uint32_t *) &recv_interrupt_page[dword])) {
456 rel_id = (dword << 5) + bit;
460 * vmbus channel protocol msg.
464 VmbusProcessChannelEvent(rel_id);
477 * Send a msg on the vmbus's message connection
479 int hv_vmbus_post_message(void *buffer, size_t bufferLen)
481 hv_vmbus_connection_id connId;
482 sbintime_t time = SBT_1MS;
486 connId.as_uint32_t = 0;
487 connId.u.id = HV_VMBUS_MESSAGE_CONNECTION_ID;
490 * We retry to cope with transient failures caused by host side's
491 * insufficient resources. 20 times should suffice in practice.
493 for (retries = 0; retries < 20; retries++) {
494 ret = hv_vmbus_post_msg_via_msg_ipc(connId, 1, buffer,
496 if (ret == HV_STATUS_SUCCESS)
499 pause_sbt("pstmsg", time, 0, C_HARDCLOCK);
500 if (time < SBT_1S * 2)
504 KASSERT(ret == HV_STATUS_SUCCESS,
505 ("Error VMBUS: Message Post Failed, ret=%d\n", ret));
511 * Send an event notification to the parent
514 hv_vmbus_set_event(hv_vmbus_channel *channel) {
516 uint32_t child_rel_id = channel->offer_msg.child_rel_id;
518 /* Each uint32_t represents 32 channels */
520 synch_set_bit(child_rel_id & 31,
521 (((uint32_t *)hv_vmbus_g_connection.send_interrupt_page
522 + (child_rel_id >> 5))));
523 ret = hv_vmbus_signal_event(channel->signal_event_param);