Changeset View
Changeset View
Standalone View
Standalone View
sys/dev/hyperv/vmbus/hv_connection.c
Show All 39 Lines | |||||
/* | /* | ||||
* Globals | * Globals | ||||
*/ | */ | ||||
hv_vmbus_connection hv_vmbus_g_connection = | hv_vmbus_connection hv_vmbus_g_connection = | ||||
{ .connect_state = HV_DISCONNECTED, | { .connect_state = HV_DISCONNECTED, | ||||
.next_gpadl_handle = 0xE1E10, }; | .next_gpadl_handle = 0xE1E10, }; | ||||
uint32_t hv_vmbus_protocal_version = HV_VMBUS_VERSION_WS2008; | |||||
static uint32_t | |||||
hv_vmbus_get_next_version(uint32_t current_ver) | |||||
{ | |||||
switch (current_ver) { | |||||
case (HV_VMBUS_VERSION_WIN7): | |||||
return(HV_VMBUS_VERSION_WS2008); | |||||
case (HV_VMBUS_VERSION_WIN8): | |||||
return(HV_VMBUS_VERSION_WIN7); | |||||
case (HV_VMBUS_VERSION_WIN8_1): | |||||
return(HV_VMBUS_VERSION_WIN8); | |||||
case (HV_VMBUS_VERSION_WS2008): | |||||
default: | |||||
return(HV_VMBUS_VERSION_INVALID); | |||||
} | |||||
} | |||||
/** | /** | ||||
* Negotiate the highest supported hypervisor version. | |||||
*/ | |||||
static int | |||||
hv_vmbus_negotiate_version(hv_vmbus_channel_msg_info *msg_info, | |||||
uint32_t version) | |||||
{ | |||||
int ret = 0; | |||||
hv_vmbus_channel_initiate_contact *msg; | |||||
sema_init(&msg_info->wait_sema, 0, "Msg Info Sema"); | |||||
msg = (hv_vmbus_channel_initiate_contact*) msg_info->msg; | |||||
msg->header.message_type = HV_CHANNEL_MESSAGE_INITIATED_CONTACT; | |||||
msg->vmbus_version_requested = version; | |||||
msg->interrupt_page = hv_get_phys_addr( | |||||
hv_vmbus_g_connection.interrupt_page); | |||||
msg->monitor_page_1 = hv_get_phys_addr( | |||||
hv_vmbus_g_connection.monitor_pages); | |||||
msg->monitor_page_2 = | |||||
hv_get_phys_addr( | |||||
((uint8_t *) hv_vmbus_g_connection.monitor_pages | |||||
+ PAGE_SIZE)); | |||||
/** | |||||
* Add to list before we send the request since we may receive the | |||||
* response before returning from this routine | |||||
*/ | |||||
mtx_lock_spin(&hv_vmbus_g_connection.channel_msg_lock); | |||||
TAILQ_INSERT_TAIL( | |||||
&hv_vmbus_g_connection.channel_msg_anchor, | |||||
msg_info, | |||||
msg_list_entry); | |||||
mtx_unlock_spin(&hv_vmbus_g_connection.channel_msg_lock); | |||||
ret = hv_vmbus_post_message( | |||||
msg, | |||||
sizeof(hv_vmbus_channel_initiate_contact)); | |||||
if (ret != 0) { | |||||
mtx_lock_spin(&hv_vmbus_g_connection.channel_msg_lock); | |||||
TAILQ_REMOVE( | |||||
&hv_vmbus_g_connection.channel_msg_anchor, | |||||
msg_info, | |||||
msg_list_entry); | |||||
mtx_unlock_spin(&hv_vmbus_g_connection.channel_msg_lock); | |||||
return (ret); | |||||
} | |||||
/** | |||||
* Wait for the connection response | |||||
*/ | |||||
ret = sema_timedwait(&msg_info->wait_sema, 500); /* KYS 5 seconds */ | |||||
mtx_lock_spin(&hv_vmbus_g_connection.channel_msg_lock); | |||||
TAILQ_REMOVE( | |||||
&hv_vmbus_g_connection.channel_msg_anchor, | |||||
msg_info, | |||||
msg_list_entry); | |||||
mtx_unlock_spin(&hv_vmbus_g_connection.channel_msg_lock); | |||||
/** | |||||
* Check if successful | |||||
*/ | |||||
if (msg_info->response.version_response.version_supported) { | |||||
hv_vmbus_g_connection.connect_state = HV_CONNECTED; | |||||
} else { | |||||
ret = ECONNREFUSED; | |||||
} | |||||
return (ret); | |||||
} | |||||
/** | |||||
* Send a connect request on the partition service connection | * Send a connect request on the partition service connection | ||||
*/ | */ | ||||
int | int | ||||
hv_vmbus_connect(void) { | hv_vmbus_connect(void) { | ||||
int ret = 0; | int ret = 0; | ||||
uint32_t version; | |||||
hv_vmbus_channel_msg_info* msg_info = NULL; | hv_vmbus_channel_msg_info* msg_info = NULL; | ||||
hv_vmbus_channel_initiate_contact* msg; | |||||
/** | /** | ||||
* Make sure we are not connecting or connected | * Make sure we are not connecting or connected | ||||
*/ | */ | ||||
if (hv_vmbus_g_connection.connect_state != HV_DISCONNECTED) { | if (hv_vmbus_g_connection.connect_state != HV_DISCONNECTED) { | ||||
return (-1); | return (-1); | ||||
} | } | ||||
/** | /** | ||||
* Initialize the vmbus connection | * Initialize the vmbus connection | ||||
*/ | */ | ||||
hv_vmbus_g_connection.connect_state = HV_CONNECTING; | hv_vmbus_g_connection.connect_state = HV_CONNECTING; | ||||
hv_vmbus_g_connection.work_queue = hv_work_queue_create("vmbusQ"); | hv_vmbus_g_connection.work_queue = hv_work_queue_create("vmbusQ"); | ||||
sema_init(&hv_vmbus_g_connection.control_sema, 1, "control_sema"); | sema_init(&hv_vmbus_g_connection.control_sema, 1, "control_sema"); | ||||
TAILQ_INIT(&hv_vmbus_g_connection.channel_msg_anchor); | TAILQ_INIT(&hv_vmbus_g_connection.channel_msg_anchor); | ||||
mtx_init(&hv_vmbus_g_connection.channel_msg_lock, "vmbus channel msg", | mtx_init(&hv_vmbus_g_connection.channel_msg_lock, "vmbus channel msg", | ||||
NULL, MTX_SPIN); | NULL, MTX_SPIN); | ||||
TAILQ_INIT(&hv_vmbus_g_connection.channel_anchor); | TAILQ_INIT(&hv_vmbus_g_connection.channel_anchor); | ||||
mtx_init(&hv_vmbus_g_connection.channel_lock, "vmbus channel", | mtx_init(&hv_vmbus_g_connection.channel_lock, "vmbus channel", | ||||
NULL, MTX_SPIN); | NULL, MTX_DEF); | ||||
/** | /** | ||||
* Setup the vmbus event connection for channel interrupt abstraction | * Setup the vmbus event connection for channel interrupt abstraction | ||||
* stuff | * stuff | ||||
*/ | */ | ||||
hv_vmbus_g_connection.interrupt_page = contigmalloc( | hv_vmbus_g_connection.interrupt_page = contigmalloc( | ||||
PAGE_SIZE, M_DEVBUF, | PAGE_SIZE, M_DEVBUF, | ||||
M_NOWAIT | M_ZERO, 0UL, | M_NOWAIT | M_ZERO, 0UL, | ||||
Show All 39 Lines | malloc(sizeof(hv_vmbus_channel_msg_info) + | ||||
M_DEVBUF, M_NOWAIT | M_ZERO); | M_DEVBUF, M_NOWAIT | M_ZERO); | ||||
KASSERT(msg_info != NULL, | KASSERT(msg_info != NULL, | ||||
("Error VMBUS: malloc failed for Initiate Contact message!")); | ("Error VMBUS: malloc failed for Initiate Contact message!")); | ||||
if (msg_info == NULL) { | if (msg_info == NULL) { | ||||
ret = ENOMEM; | ret = ENOMEM; | ||||
goto cleanup; | goto cleanup; | ||||
} | } | ||||
sema_init(&msg_info->wait_sema, 0, "Msg Info Sema"); | /* | ||||
msg = (hv_vmbus_channel_initiate_contact*) msg_info->msg; | * Find the highest vmbus version number we can support. | ||||
msg->header.message_type = HV_CHANNEL_MESSAGE_INITIATED_CONTACT; | |||||
msg->vmbus_version_requested = HV_VMBUS_REVISION_NUMBER; | |||||
msg->interrupt_page = hv_get_phys_addr( | |||||
hv_vmbus_g_connection.interrupt_page); | |||||
msg->monitor_page_1 = hv_get_phys_addr( | |||||
hv_vmbus_g_connection.monitor_pages); | |||||
msg->monitor_page_2 = | |||||
hv_get_phys_addr( | |||||
((uint8_t *) hv_vmbus_g_connection.monitor_pages | |||||
+ PAGE_SIZE)); | |||||
/** | |||||
* Add to list before we send the request since we may receive the | |||||
* response before returning from this routine | |||||
*/ | */ | ||||
mtx_lock_spin(&hv_vmbus_g_connection.channel_msg_lock); | version = HV_VMBUS_VERSION_CURRENT; | ||||
TAILQ_INSERT_TAIL( | do { | ||||
&hv_vmbus_g_connection.channel_msg_anchor, | ret = hv_vmbus_negotiate_version(msg_info, version); | ||||
msg_info, | if (ret == EWOULDBLOCK) { | ||||
msg_list_entry); | /* | ||||
* We timed out. | |||||
mtx_unlock_spin(&hv_vmbus_g_connection.channel_msg_lock); | */ | ||||
ret = hv_vmbus_post_message( | |||||
msg, | |||||
sizeof(hv_vmbus_channel_initiate_contact)); | |||||
if (ret != 0) { | |||||
mtx_lock_spin(&hv_vmbus_g_connection.channel_msg_lock); | |||||
TAILQ_REMOVE( | |||||
&hv_vmbus_g_connection.channel_msg_anchor, | |||||
msg_info, | |||||
msg_list_entry); | |||||
mtx_unlock_spin(&hv_vmbus_g_connection.channel_msg_lock); | |||||
goto cleanup; | goto cleanup; | ||||
} | } | ||||
/** | if (hv_vmbus_g_connection.connect_state == HV_CONNECTED) | ||||
* Wait for the connection response | break; | ||||
*/ | |||||
ret = sema_timedwait(&msg_info->wait_sema, 500); /* KYS 5 seconds */ | |||||
mtx_lock_spin(&hv_vmbus_g_connection.channel_msg_lock); | version = hv_vmbus_get_next_version(version); | ||||
TAILQ_REMOVE( | } while (version != HV_VMBUS_VERSION_INVALID); | ||||
&hv_vmbus_g_connection.channel_msg_anchor, | |||||
msg_info, | |||||
msg_list_entry); | |||||
mtx_unlock_spin(&hv_vmbus_g_connection.channel_msg_lock); | |||||
/** | hv_vmbus_protocal_version = version; | ||||
* Check if successful | if (bootverbose) | ||||
*/ | printf("VMBUS: Portocal Version: %d.%d\n", | ||||
if (msg_info->response.version_response.version_supported) { | version >> 16, version & 0xFFFF); | ||||
hv_vmbus_g_connection.connect_state = HV_CONNECTED; | |||||
} else { | |||||
ret = ECONNREFUSED; | |||||
goto cleanup; | |||||
} | |||||
sema_destroy(&msg_info->wait_sema); | sema_destroy(&msg_info->wait_sema); | ||||
free(msg_info, M_DEVBUF); | free(msg_info, M_DEVBUF); | ||||
return (0); | return (0); | ||||
/* | /* | ||||
* Cleanup after failure! | * Cleanup after failure! | ||||
▲ Show 20 Lines • Show All 75 Lines • ▼ Show 20 Lines | hv_vmbus_get_channel_from_rel_id(uint32_t rel_id) { | ||||
hv_vmbus_channel* foundChannel = NULL; | hv_vmbus_channel* foundChannel = NULL; | ||||
/* | /* | ||||
* TODO: | * TODO: | ||||
* Consider optimization where relids are stored in a fixed size array | * Consider optimization where relids are stored in a fixed size array | ||||
* and channels are accessed without the need to take this lock or search | * and channels are accessed without the need to take this lock or search | ||||
* the list. | * the list. | ||||
*/ | */ | ||||
mtx_lock_spin(&hv_vmbus_g_connection.channel_lock); | mtx_lock(&hv_vmbus_g_connection.channel_lock); | ||||
TAILQ_FOREACH(channel, | TAILQ_FOREACH(channel, | ||||
&hv_vmbus_g_connection.channel_anchor, list_entry) { | &hv_vmbus_g_connection.channel_anchor, list_entry) { | ||||
if (channel->offer_msg.child_rel_id == rel_id) { | if (channel->offer_msg.child_rel_id == rel_id) { | ||||
foundChannel = channel; | foundChannel = channel; | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
mtx_unlock_spin(&hv_vmbus_g_connection.channel_lock); | mtx_unlock(&hv_vmbus_g_connection.channel_lock); | ||||
return (foundChannel); | return (foundChannel); | ||||
} | } | ||||
/** | /** | ||||
* Process a channel event notification | * Process a channel event notification | ||||
*/ | */ | ||||
static void | static void | ||||
VmbusProcessChannelEvent(uint32_t relid) | VmbusProcessChannelEvent(uint32_t relid) | ||||
{ | { | ||||
void* arg; | |||||
uint32_t bytes_to_read; | |||||
hv_vmbus_channel* channel; | hv_vmbus_channel* channel; | ||||
boolean_t is_batched_reading; | |||||
/** | /** | ||||
* Find the channel based on this relid and invokes | * Find the channel based on this relid and invokes | ||||
* the channel callback to process the event | * the channel callback to process the event | ||||
*/ | */ | ||||
channel = hv_vmbus_get_channel_from_rel_id(relid); | channel = hv_vmbus_get_channel_from_rel_id(relid); | ||||
if (channel == NULL) { | if (channel == NULL) { | ||||
return; | return; | ||||
} | } | ||||
/** | /** | ||||
* To deal with the race condition where we might | * To deal with the race condition where we might | ||||
* receive a packet while the relevant driver is | * receive a packet while the relevant driver is | ||||
* being unloaded, dispatch the callback while | * being unloaded, dispatch the callback while | ||||
* holding the channel lock. The unloading driver | * holding the channel lock. The unloading driver | ||||
* will acquire the same channel lock to set the | * will acquire the same channel lock to set the | ||||
* callback to NULL. This closes the window. | * callback to NULL. This closes the window. | ||||
*/ | */ | ||||
mtx_lock(&channel->inbound_lock); | mtx_lock(&channel->inbound_lock); | ||||
if (channel->on_channel_callback != NULL) { | if (channel->on_channel_callback != NULL) { | ||||
channel->on_channel_callback(channel->channel_callback_context); | arg = channel->channel_callback_context; | ||||
is_batched_reading = channel->batched_reading; | |||||
/* | |||||
* Optimize host to guest signaling by ensuring: | |||||
* 1. While reading the channel, we disable interrupts from | |||||
* host. | |||||
* 2. Ensure that we process all posted messages from the host | |||||
* before returning from this callback. | |||||
* 3. Once we return, enable signaling from the host. Once this | |||||
* state is set we check to see if additional packets are | |||||
* available to read. In this case we repeat the process. | |||||
*/ | |||||
do { | |||||
if (is_batched_reading) | |||||
hv_ring_buffer_read_begin(&channel->inbound); | |||||
channel->on_channel_callback(arg); | |||||
if (is_batched_reading) | |||||
bytes_to_read = | |||||
hv_ring_buffer_read_end(&channel->inbound); | |||||
else | |||||
bytes_to_read = 0; | |||||
} while (is_batched_reading && (bytes_to_read != 0)); | |||||
} | } | ||||
mtx_unlock(&channel->inbound_lock); | mtx_unlock(&channel->inbound_lock); | ||||
} | } | ||||
#ifdef HV_DEBUG_INTR | |||||
extern uint32_t hv_intr_count; | |||||
extern uint32_t hv_vmbus_swintr_event_cpu[MAXCPU]; | |||||
extern uint32_t hv_vmbus_intr_cpu[MAXCPU]; | |||||
#endif | |||||
/** | /** | ||||
* Handler for events | * Handler for events | ||||
*/ | */ | ||||
void | void | ||||
hv_vmbus_on_events(void *arg) | hv_vmbus_on_events(void *arg) | ||||
{ | { | ||||
int dword; | |||||
int bit; | int bit; | ||||
int cpu; | |||||
int dword; | |||||
void *page_addr; | |||||
uint32_t* recv_interrupt_page = NULL; | |||||
int rel_id; | int rel_id; | ||||
int maxdword = HV_MAX_NUM_CHANNELS_SUPPORTED >> 5; | int maxdword; | ||||
hv_vmbus_synic_event_flags *event; | |||||
/* int maxdword = PAGE_SIZE >> 3; */ | /* int maxdword = PAGE_SIZE >> 3; */ | ||||
cpu = (int)(long)arg; | |||||
KASSERT(cpu <= mp_maxid, ("VMBUS: hv_vmbus_on_events: " | |||||
"cpu out of range!")); | |||||
#ifdef HV_DEBUG_INTR | |||||
int i; | |||||
hv_vmbus_swintr_event_cpu[cpu]++; | |||||
if (hv_intr_count % 10000 == 0) { | |||||
printf("VMBUS: Total interrupt %d\n", hv_intr_count); | |||||
for (i = 0; i < mp_ncpus; i++) | |||||
printf("VMBUS: hw cpu[%d]: %d, event sw intr cpu[%d]: %d\n", | |||||
i, hv_vmbus_intr_cpu[i], i, hv_vmbus_swintr_event_cpu[i]); | |||||
} | |||||
#endif | |||||
if ((hv_vmbus_protocal_version == HV_VMBUS_VERSION_WS2008) || | |||||
(hv_vmbus_protocal_version == HV_VMBUS_VERSION_WIN7)) { | |||||
maxdword = HV_MAX_NUM_CHANNELS_SUPPORTED >> 5; | |||||
/* | /* | ||||
* receive size is 1/2 page and divide that by 4 bytes | * receive size is 1/2 page and divide that by 4 bytes | ||||
*/ | */ | ||||
recv_interrupt_page = | |||||
uint32_t* recv_interrupt_page = | |||||
hv_vmbus_g_connection.recv_interrupt_page; | hv_vmbus_g_connection.recv_interrupt_page; | ||||
} else { | |||||
/* | |||||
* On Host with Win8 or above, the event page can be | |||||
* checked directly to get the id of the channel | |||||
* that has the pending interrupt. | |||||
*/ | |||||
maxdword = HV_EVENT_FLAGS_DWORD_COUNT; | |||||
page_addr = hv_vmbus_g_context.syn_ic_event_page[cpu]; | |||||
event = (hv_vmbus_synic_event_flags *) | |||||
page_addr + HV_VMBUS_MESSAGE_SINT; | |||||
recv_interrupt_page = event->flags32; | |||||
} | |||||
/* | /* | ||||
* Check events | * Check events | ||||
*/ | */ | ||||
if (recv_interrupt_page != NULL) { | if (recv_interrupt_page != NULL) { | ||||
for (dword = 0; dword < maxdword; dword++) { | for (dword = 0; dword < maxdword; dword++) { | ||||
if (recv_interrupt_page[dword]) { | if (recv_interrupt_page[dword]) { | ||||
for (bit = 0; bit < 32; bit++) { | for (bit = 0; bit < 32; bit++) { | ||||
▲ Show 20 Lines • Show All 48 Lines • ▼ Show 20 Lines | int hv_vmbus_post_message(void *buffer, size_t bufferLen) { | ||||
return (ret); | return (ret); | ||||
} | } | ||||
/** | /** | ||||
* Send an event notification to the parent | * Send an event notification to the parent | ||||
*/ | */ | ||||
int | int | ||||
hv_vmbus_set_event(uint32_t child_rel_id) { | hv_vmbus_set_event(hv_vmbus_channel *channel) { | ||||
int ret = 0; | int ret = 0; | ||||
uint32_t child_rel_id = channel->offer_msg.child_rel_id; | |||||
/* Each uint32_t represents 32 channels */ | /* Each uint32_t represents 32 channels */ | ||||
synch_set_bit(child_rel_id & 31, | synch_set_bit(child_rel_id & 31, | ||||
(((uint32_t *)hv_vmbus_g_connection.send_interrupt_page | (((uint32_t *)hv_vmbus_g_connection.send_interrupt_page | ||||
+ (child_rel_id >> 5)))); | + (child_rel_id >> 5)))); | ||||
ret = hv_vmbus_signal_event(); | ret = hv_vmbus_signal_event(channel->signal_event_param); | ||||
return (ret); | return (ret); | ||||
} | } | ||||