Changeset View
Changeset View
Standalone View
Standalone View
sys/dev/hyperv/vmbus/hv_channel_mgmt.c
Show First 20 Lines • Show All 44 Lines • ▼ Show 20 Lines | |||||
static void vmbus_channel_on_offer(hv_vmbus_channel_msg_header* hdr); | static void vmbus_channel_on_offer(hv_vmbus_channel_msg_header* hdr); | ||||
static void vmbus_channel_on_open_result(hv_vmbus_channel_msg_header* hdr); | static void vmbus_channel_on_open_result(hv_vmbus_channel_msg_header* hdr); | ||||
static void vmbus_channel_on_offer_rescind(hv_vmbus_channel_msg_header* hdr); | static void vmbus_channel_on_offer_rescind(hv_vmbus_channel_msg_header* hdr); | ||||
static void vmbus_channel_on_gpadl_created(hv_vmbus_channel_msg_header* hdr); | static void vmbus_channel_on_gpadl_created(hv_vmbus_channel_msg_header* hdr); | ||||
static void vmbus_channel_on_gpadl_torndown(hv_vmbus_channel_msg_header* hdr); | static void vmbus_channel_on_gpadl_torndown(hv_vmbus_channel_msg_header* hdr); | ||||
static void vmbus_channel_on_offers_delivered(hv_vmbus_channel_msg_header* hdr); | static void vmbus_channel_on_offers_delivered(hv_vmbus_channel_msg_header* hdr); | ||||
static void vmbus_channel_on_version_response(hv_vmbus_channel_msg_header* hdr); | static void vmbus_channel_on_version_response(hv_vmbus_channel_msg_header* hdr); | ||||
static void vmbus_channel_process_offer(void *context); | static void vmbus_channel_process_offer(void *context); | ||||
struct hv_vmbus_channel* | |||||
vmbus_select_outgoing_channel(struct hv_vmbus_channel *promary); | |||||
/** | /** | ||||
* Channel message dispatch table | * Channel message dispatch table | ||||
*/ | */ | ||||
hv_vmbus_channel_msg_table_entry | hv_vmbus_channel_msg_table_entry | ||||
g_channel_message_table[HV_CHANNEL_MESSAGE_COUNT] = { | g_channel_message_table[HV_CHANNEL_MESSAGE_COUNT] = { | ||||
{ HV_CHANNEL_MESSAGE_INVALID, NULL }, | { HV_CHANNEL_MESSAGE_INVALID, NULL }, | ||||
{ HV_CHANNEL_MESSAGE_OFFER_CHANNEL, vmbus_channel_on_offer }, | { HV_CHANNEL_MESSAGE_OFFER_CHANNEL, vmbus_channel_on_offer }, | ||||
▲ Show 20 Lines • Show All 167 Lines • ▼ Show 20 Lines | channel = (hv_vmbus_channel*) malloc( | ||||
sizeof(hv_vmbus_channel), | sizeof(hv_vmbus_channel), | ||||
M_DEVBUF, | M_DEVBUF, | ||||
M_NOWAIT | M_ZERO); | M_NOWAIT | M_ZERO); | ||||
KASSERT(channel != NULL, ("Error VMBUS: Failed to allocate channel!")); | KASSERT(channel != NULL, ("Error VMBUS: Failed to allocate channel!")); | ||||
if (channel == NULL) | if (channel == NULL) | ||||
return (NULL); | return (NULL); | ||||
mtx_init(&channel->inbound_lock, "channel inbound", NULL, MTX_DEF); | mtx_init(&channel->inbound_lock, "channel inbound", NULL, MTX_DEF); | ||||
mtx_init(&channel->sc_lock, "vmbus multi channel", NULL, MTX_SPIN); | |||||
jhb: Why is this lock a spin mutex? Both of the places it is used it should be fine to use a… | |||||
TAILQ_INIT(&channel->sc_list_anchor); | |||||
channel->control_work_queue = hv_work_queue_create("control"); | channel->control_work_queue = hv_work_queue_create("control"); | ||||
if (channel->control_work_queue == NULL) { | if (channel->control_work_queue == NULL) { | ||||
mtx_destroy(&channel->inbound_lock); | mtx_destroy(&channel->inbound_lock); | ||||
free(channel, M_DEVBUF); | free(channel, M_DEVBUF); | ||||
return (NULL); | return (NULL); | ||||
} | } | ||||
Show All 12 Lines | |||||
} | } | ||||
/** | /** | ||||
* @brief Release the resources used by the vmbus channel object | * @brief Release the resources used by the vmbus channel object | ||||
*/ | */ | ||||
void | void | ||||
hv_vmbus_free_vmbus_channel(hv_vmbus_channel* channel) | hv_vmbus_free_vmbus_channel(hv_vmbus_channel* channel) | ||||
{ | { | ||||
mtx_destroy(&channel->sc_lock); | |||||
mtx_destroy(&channel->inbound_lock); | mtx_destroy(&channel->inbound_lock); | ||||
/* | /* | ||||
* We have to release the channel's workqueue/thread in | * We have to release the channel's workqueue/thread in | ||||
* the vmbus's workqueue/thread context | * the vmbus's workqueue/thread context | ||||
* ie we can't destroy ourselves | * ie we can't destroy ourselves | ||||
*/ | */ | ||||
hv_queue_work_item(hv_vmbus_g_connection.work_queue, | hv_queue_work_item(hv_vmbus_g_connection.work_queue, | ||||
ReleaseVmbusChannel, (void *) channel); | ReleaseVmbusChannel, (void *) channel); | ||||
} | } | ||||
/** | /** | ||||
* @brief Process the offer by creating a channel/device | * @brief Process the offer by creating a channel/device | ||||
* associated with this offer | * associated with this offer | ||||
*/ | */ | ||||
static void | static void | ||||
vmbus_channel_process_offer(void *context) | vmbus_channel_process_offer(void *context) | ||||
{ | { | ||||
int ret; | |||||
hv_vmbus_channel* new_channel; | hv_vmbus_channel* new_channel; | ||||
boolean_t f_new; | boolean_t f_new; | ||||
hv_vmbus_channel* channel; | hv_vmbus_channel* channel; | ||||
int ret; | |||||
new_channel = (hv_vmbus_channel*) context; | new_channel = (hv_vmbus_channel*) context; | ||||
f_new = TRUE; | f_new = TRUE; | ||||
channel = NULL; | channel = NULL; | ||||
/* | /* | ||||
* Make sure this is a new offer | * Make sure this is a new offer | ||||
*/ | */ | ||||
mtx_lock_spin(&hv_vmbus_g_connection.channel_lock); | mtx_lock_spin(&hv_vmbus_g_connection.channel_lock); | ||||
jhbUnsubmitted Not Done Inline ActionsLooking at the existing hyper-v code and the changes in this patch, I think this lock should also be fine to be MTX_DEF instead of MTX_SPIN. You only really need MTX_SPIN in FreeBSD if you are part of the scheduler implementation or if you use a lock inside an interrupt filter. I see that hyperv does use an interrupt filter, but it only uses it to schedule other interrupt handlers, it doesn't use any locks in that filter routine, so all of the mutexes in hyperv should be fine to be MTX_DEF instead of MTX_SPIN. jhb: Looking at the existing hyper-v code and the changes in this patch, I think this lock should… | |||||
TAILQ_FOREACH(channel, &hv_vmbus_g_connection.channel_anchor, | TAILQ_FOREACH(channel, &hv_vmbus_g_connection.channel_anchor, | ||||
list_entry) | list_entry) | ||||
{ | { | ||||
if (!memcmp( | if (!memcmp( &channel->offer_msg.offer.interface_type, | ||||
&channel->offer_msg.offer.interface_type, | |||||
&new_channel->offer_msg.offer.interface_type, | &new_channel->offer_msg.offer.interface_type, | ||||
sizeof(hv_guid)) | sizeof(hv_guid)) && | ||||
&& !memcmp( | !memcmp(&channel->offer_msg.offer.interface_instance, | ||||
Not Done Inline ActionsSince memcmp doesn't return a boolean value the comparison should be == 0. royger: Since memcmp doesn't return a boolean value the comparison should be == 0. | |||||
&channel->offer_msg.offer.interface_instance, | |||||
&new_channel->offer_msg.offer.interface_instance, | &new_channel->offer_msg.offer.interface_instance, | ||||
sizeof(hv_guid))) { | sizeof(hv_guid))) { | ||||
f_new = FALSE; | f_new = FALSE; | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
if (f_new) { | if (f_new) { | ||||
/* Insert at tail */ | /* Insert at tail */ | ||||
TAILQ_INSERT_TAIL( | TAILQ_INSERT_TAIL( | ||||
&hv_vmbus_g_connection.channel_anchor, | &hv_vmbus_g_connection.channel_anchor, | ||||
new_channel, | new_channel, | ||||
list_entry); | list_entry); | ||||
} | } | ||||
mtx_unlock_spin(&hv_vmbus_g_connection.channel_lock); | mtx_unlock_spin(&hv_vmbus_g_connection.channel_lock); | ||||
/*XXX add new channel to percpu_list */ | |||||
if (!f_new) { | if (!f_new) { | ||||
/* | |||||
* Check if this is a sub channel. | |||||
*/ | |||||
if (new_channel->offer_msg.offer.sub_channel_index != 0) { | |||||
/* | |||||
* It is a sub channel offer, process it. | |||||
*/ | |||||
new_channel->primary_channel = channel; | |||||
mtx_lock_spin(&channel->sc_lock); | |||||
TAILQ_INSERT_TAIL( | |||||
&channel->sc_list_anchor, | |||||
new_channel, | |||||
sc_list_entry); | |||||
mtx_unlock_spin(&channel->sc_lock); | |||||
/* Insert new channel into channel_anchor. */ | |||||
printf("Storvsc get multi-channel offer, rel=%u.\n", | |||||
new_channel->offer_msg.child_rel_id); | |||||
mtx_lock_spin(&hv_vmbus_g_connection.channel_lock); | |||||
TAILQ_INSERT_TAIL(&hv_vmbus_g_connection.channel_anchor, | |||||
new_channel, list_entry); | |||||
mtx_unlock_spin(&hv_vmbus_g_connection.channel_lock); | |||||
if(bootverbose) | |||||
printf("VMBUS: new multi-channel offer <%p>.\n", | |||||
new_channel); | |||||
/*XXX add it to percpu_list */ | |||||
new_channel->state = HV_CHANNEL_OPEN_STATE; | |||||
if (channel->sc_creation_callback != NULL) { | |||||
channel->sc_creation_callback(new_channel); | |||||
} | |||||
return; | |||||
} | |||||
hv_vmbus_free_vmbus_channel(new_channel); | hv_vmbus_free_vmbus_channel(new_channel); | ||||
return; | return; | ||||
} | } | ||||
new_channel->state = HV_CHANNEL_OPEN_STATE; | |||||
/* | /* | ||||
* Start the process of binding this offer to the driver | * Start the process of binding this offer to the driver | ||||
* (We need to set the device field before calling | * (We need to set the device field before calling | ||||
* hv_vmbus_child_device_add()) | * hv_vmbus_child_device_add()) | ||||
*/ | */ | ||||
new_channel->device = hv_vmbus_child_device_create( | new_channel->device = hv_vmbus_child_device_create( | ||||
new_channel->offer_msg.offer.interface_type, | new_channel->offer_msg.offer.interface_type, | ||||
new_channel->offer_msg.offer.interface_instance, new_channel); | new_channel->offer_msg.offer.interface_instance, new_channel); | ||||
/* | /* | ||||
* TODO - the HV_CHANNEL_OPEN_STATE flag should not be set below | |||||
* but in the "open" channel request. The ret != 0 logic below | |||||
* doesn't take into account that a channel | |||||
* may have been opened successfully | |||||
*/ | |||||
/* | |||||
* Add the new device to the bus. This will kick off device-driver | * Add the new device to the bus. This will kick off device-driver | ||||
* binding which eventually invokes the device driver's AddDevice() | * binding which eventually invokes the device driver's AddDevice() | ||||
* method. | * method. | ||||
*/ | */ | ||||
ret = hv_vmbus_child_device_register(new_channel->device); | ret = hv_vmbus_child_device_register(new_channel->device); | ||||
if (ret != 0) { | if (ret != 0) { | ||||
mtx_lock_spin(&hv_vmbus_g_connection.channel_lock); | mtx_lock_spin(&hv_vmbus_g_connection.channel_lock); | ||||
TAILQ_REMOVE( | TAILQ_REMOVE( | ||||
&hv_vmbus_g_connection.channel_anchor, | &hv_vmbus_g_connection.channel_anchor, | ||||
new_channel, | new_channel, | ||||
list_entry); | list_entry); | ||||
mtx_unlock_spin(&hv_vmbus_g_connection.channel_lock); | mtx_unlock_spin(&hv_vmbus_g_connection.channel_lock); | ||||
hv_vmbus_free_vmbus_channel(new_channel); | hv_vmbus_free_vmbus_channel(new_channel); | ||||
} else { | } | ||||
} | |||||
/** | |||||
* Array of device guids that are performance critical. We try to distribute | |||||
* the interrupt load for these devices across all online cpus. | |||||
*/ | |||||
static const hv_guid high_perf_devices[] = { | |||||
{HV_NIC_GUID, }, | |||||
{HV_IDE_GUID, }, | |||||
{HV_SCSI_GUID, }, | |||||
}; | |||||
enum { | |||||
PERF_CHN_NIC = 0, | |||||
PERF_CHN_IDE, | |||||
PERF_CHN_SCSI, | |||||
MAX_PERF_CHN, | |||||
}; | |||||
/* | /* | ||||
* This state is used to indicate a successful open | * We use this static number to distribute the channel interrupt load. | ||||
* so that when we do close the channel normally, | |||||
* we can clean up properly | |||||
*/ | */ | ||||
new_channel->state = HV_CHANNEL_OPEN_STATE; | static uint32_t next_vcpu; | ||||
/** | |||||
* Starting with Win8, we can statically distribute the incoming | |||||
* channel interrupt load by binding a channel to VCPU. We | |||||
* implement here a simple round robin scheme for distributing | |||||
* the interrupt load. | |||||
* We will bind channels that are not performance critical to cpu 0 and | |||||
* performance critical channels (IDE, SCSI and Network) will be uniformly | |||||
* distributed across all available CPUs. | |||||
*/ | |||||
static void | |||||
vmbus_channel_select_cpu(hv_vmbus_channel *channel, hv_guid *guid) | |||||
{ | |||||
uint32_t current_cpu; | |||||
int i; | |||||
boolean_t is_perf_channel = FALSE; | |||||
for (i = PERF_CHN_NIC; i < MAX_PERF_CHN; i++) { | |||||
if (!memcmp(guid->data, high_perf_devices[i].data, | |||||
Not Done Inline ActionsSame here. royger: Same here. | |||||
sizeof(hv_guid))) { | |||||
is_perf_channel = TRUE; | |||||
break; | |||||
} | } | ||||
} | } | ||||
if ((hv_vmbus_protocal_version == HV_VMBUS_VERSION_WS2008) || | |||||
(hv_vmbus_protocal_version == HV_VMBUS_VERSION_WIN7) || | |||||
(!is_perf_channel)) { | |||||
/* Host's view of guest cpu */ | |||||
channel->target_vcpu = 0; | |||||
/* Guest's own view of cpu */ | |||||
channel->target_cpu = 0; | |||||
return; | |||||
} | |||||
/* mp_ncpus should have the number cpus currently online */ | |||||
current_cpu = (++next_vcpu % mp_ncpus); | |||||
channel->target_cpu = current_cpu; | |||||
channel->target_vcpu = | |||||
hv_vmbus_g_context.hv_vcpu_index[current_cpu]; | |||||
if (bootverbose) | |||||
printf("VMBUS: Total online cpus %d, assign perf channel %d " | |||||
"to vcpu %d, cpu %d\n", mp_ncpus, i, channel->target_vcpu, | |||||
current_cpu); | |||||
} | |||||
/** | /** | ||||
* @brief Handler for channel offers from Hyper-V/Azure | * @brief Handler for channel offers from Hyper-V/Azure | ||||
* | * | ||||
* Handler for channel offers from vmbus in parent partition. We ignore | * Handler for channel offers from vmbus in parent partition. We ignore | ||||
* all offers except network and storage offers. For each network and storage | * all offers except network and storage offers. For each network and storage | ||||
* offers, we create a channel object and queue a work item to the channel | * offers, we create a channel object and queue a work item to the channel | ||||
* object to process the offer synchronously | * object to process the offer synchronously | ||||
*/ | */ | ||||
Show All 11 Lines | vmbus_channel_on_offer(hv_vmbus_channel_msg_header* hdr) | ||||
guidType = &offer->offer.interface_type; | guidType = &offer->offer.interface_type; | ||||
guidInstance = &offer->offer.interface_instance; | guidInstance = &offer->offer.interface_instance; | ||||
/* Allocate the channel object and save this offer */ | /* Allocate the channel object and save this offer */ | ||||
new_channel = hv_vmbus_allocate_channel(); | new_channel = hv_vmbus_allocate_channel(); | ||||
if (new_channel == NULL) | if (new_channel == NULL) | ||||
return; | return; | ||||
/* | |||||
* By default we setup state to enable batched | |||||
* reading. A specific service can choose to | |||||
* disable this prior to opening the channel. | |||||
*/ | |||||
new_channel->batched_reading = TRUE; | |||||
new_channel->signal_event_param = | |||||
(hv_vmbus_input_signal_event *) | |||||
(HV_ALIGN_UP((unsigned long) | |||||
&new_channel->signal_event_buffer, | |||||
HV_HYPERCALL_PARAM_ALIGN)); | |||||
new_channel->signal_event_param->connection_id.as_uint32_t = 0; | |||||
new_channel->signal_event_param->connection_id.u.id = | |||||
HV_VMBUS_EVENT_CONNECTION_ID; | |||||
new_channel->signal_event_param->flag_number = 0; | |||||
new_channel->signal_event_param->rsvd_z = 0; | |||||
if (hv_vmbus_protocal_version != HV_VMBUS_VERSION_WS2008) { | |||||
new_channel->is_dedicated_interrupt = | |||||
(offer->is_dedicated_interrupt != 0); | |||||
new_channel->signal_event_param->connection_id.u.id = | |||||
offer->connection_id; | |||||
} | |||||
/* | |||||
* Bind the channel to a chosen cpu. | |||||
*/ | |||||
vmbus_channel_select_cpu(new_channel, | |||||
&offer->offer.interface_type); | |||||
memcpy(&new_channel->offer_msg, offer, | memcpy(&new_channel->offer_msg, offer, | ||||
sizeof(hv_vmbus_channel_offer_channel)); | sizeof(hv_vmbus_channel_offer_channel)); | ||||
new_channel->monitor_group = (uint8_t) offer->monitor_id / 32; | new_channel->monitor_group = (uint8_t) offer->monitor_id / 32; | ||||
new_channel->monitor_bit = (uint8_t) offer->monitor_id % 32; | new_channel->monitor_bit = (uint8_t) offer->monitor_id % 32; | ||||
/* TODO: Make sure the offer comes from our parent partition */ | /* TODO: Make sure the offer comes from our parent partition */ | ||||
hv_queue_work_item( | hv_queue_work_item( | ||||
new_channel->control_work_queue, | new_channel->control_work_queue, | ||||
▲ Show 20 Lines • Show All 270 Lines • ▼ Show 20 Lines | while (!TAILQ_EMPTY(&hv_vmbus_g_connection.channel_anchor)) { | ||||
channel = TAILQ_FIRST(&hv_vmbus_g_connection.channel_anchor); | channel = TAILQ_FIRST(&hv_vmbus_g_connection.channel_anchor); | ||||
TAILQ_REMOVE(&hv_vmbus_g_connection.channel_anchor, | TAILQ_REMOVE(&hv_vmbus_g_connection.channel_anchor, | ||||
channel, list_entry); | channel, list_entry); | ||||
hv_vmbus_child_device_unregister(channel->device); | hv_vmbus_child_device_unregister(channel->device); | ||||
hv_vmbus_free_vmbus_channel(channel); | hv_vmbus_free_vmbus_channel(channel); | ||||
} | } | ||||
mtx_unlock_spin(&hv_vmbus_g_connection.channel_lock); | mtx_unlock_spin(&hv_vmbus_g_connection.channel_lock); | ||||
} | |||||
/** | |||||
* @brief Select the best outgoing channel | |||||
* | |||||
* The channel whose vcpu binding is closest to the currect vcpu will | |||||
* be selected. | |||||
* If no multi-channel, always select primary channel | |||||
* | |||||
* @param primary - primary channel | |||||
*/ | |||||
struct hv_vmbus_channel * | |||||
vmbus_select_outgoing_channel(struct hv_vmbus_channel *primary) | |||||
{ | |||||
hv_vmbus_channel *new_channel = NULL; | |||||
hv_vmbus_channel *outgoing_channel = primary; | |||||
int old_cpu_distance = 0; | |||||
int new_cpu_distance = 0; | |||||
int cur_vcpu = 0; | |||||
int smp_pro_id = PCPU_GET(cpuid); | |||||
if (TAILQ_EMPTY(&primary->sc_list_anchor)) { | |||||
return outgoing_channel; | |||||
} | |||||
if (smp_pro_id >= MAXCPU) { | |||||
return outgoing_channel; | |||||
} | |||||
cur_vcpu = hv_vmbus_g_context.hv_vcpu_index[smp_pro_id]; | |||||
TAILQ_FOREACH(new_channel, &primary->sc_list_anchor, sc_list_entry) { | |||||
if (new_channel->state != HV_CHANNEL_OPENED_STATE){ | |||||
continue; | |||||
} | |||||
if (new_channel->target_vcpu == cur_vcpu){ | |||||
return new_channel; | |||||
} | |||||
old_cpu_distance = ((outgoing_channel->target_vcpu > cur_vcpu) ? | |||||
(outgoing_channel->target_vcpu - cur_vcpu) : | |||||
(cur_vcpu - outgoing_channel->target_vcpu)); | |||||
new_cpu_distance = ((new_channel->target_vcpu > cur_vcpu) ? | |||||
(new_channel->target_vcpu - cur_vcpu) : | |||||
(cur_vcpu - new_channel->target_vcpu)); | |||||
if (old_cpu_distance < new_cpu_distance) { | |||||
continue; | |||||
} | |||||
outgoing_channel = new_channel; | |||||
} | |||||
return(outgoing_channel); | |||||
} | } |
Why is this lock a spin mutex? Both of the places it is used it should be fine to use a regular mutex (MTX_DEF) instead, and regular mutexes are preferred.