Merge remote-tracking branch 'regmap/fix/header' into regmap-linus
[cascardo/linux.git] / drivers / hv / channel_mgmt.c
index 21ef689..0df7590 100644 (file)
@@ -115,6 +115,9 @@ static struct vmbus_channel *alloc_channel(void)
                return NULL;
 
        spin_lock_init(&channel->inbound_lock);
+       spin_lock_init(&channel->sc_lock);
+
+       INIT_LIST_HEAD(&channel->sc_list);
 
        channel->controlwq = create_workqueue("hv_vmbus_ctl");
        if (!channel->controlwq) {
@@ -166,6 +169,7 @@ static void vmbus_process_rescind_offer(struct work_struct *work)
                                                     struct vmbus_channel,
                                                     work);
        unsigned long flags;
+       struct vmbus_channel *primary_channel;
        struct vmbus_channel_relid_released msg;
 
        vmbus_device_unregister(channel->device_obj);
@@ -174,9 +178,16 @@ static void vmbus_process_rescind_offer(struct work_struct *work)
        msg.header.msgtype = CHANNELMSG_RELID_RELEASED;
        vmbus_post_msg(&msg, sizeof(struct vmbus_channel_relid_released));
 
-       spin_lock_irqsave(&vmbus_connection.channel_lock, flags);
-       list_del(&channel->listentry);
-       spin_unlock_irqrestore(&vmbus_connection.channel_lock, flags);
+       if (channel->primary_channel == NULL) {
+               spin_lock_irqsave(&vmbus_connection.channel_lock, flags);
+               list_del(&channel->listentry);
+               spin_unlock_irqrestore(&vmbus_connection.channel_lock, flags);
+       } else {
+               primary_channel = channel->primary_channel;
+               spin_lock_irqsave(&primary_channel->sc_lock, flags);
+               list_del(&channel->listentry);
+               spin_unlock_irqrestore(&primary_channel->sc_lock, flags);
+       }
        free_channel(channel);
 }
 
@@ -228,6 +239,24 @@ static void vmbus_process_offer(struct work_struct *work)
        spin_unlock_irqrestore(&vmbus_connection.channel_lock, flags);
 
        if (!fnew) {
+               /*
+                * Check to see if this is a sub-channel.
+                */
+               if (newchannel->offermsg.offer.sub_channel_index != 0) {
+                       /*
+                        * Process the sub-channel.
+                        */
+                       newchannel->primary_channel = channel;
+                       spin_lock_irqsave(&channel->sc_lock, flags);
+                       list_add_tail(&newchannel->sc_list, &channel->sc_list);
+                       spin_unlock_irqrestore(&channel->sc_lock, flags);
+                       newchannel->state = CHANNEL_OPEN_STATE;
+                       if (channel->sc_creation_callback != NULL)
+                               channel->sc_creation_callback(newchannel);
+
+                       return;
+               }
+
                free_channel(newchannel);
                return;
        }
@@ -685,4 +714,86 @@ cleanup:
        return ret;
 }
 
-/* eof */
+/*
+ * Retrieve the (sub) channel on which to send an outgoing request.
+ * When a primary channel has multiple sub-channels, we choose a
+ * channel whose VCPU binding is closest to the VCPU on which
+ * this call is being made.
+ */
+struct vmbus_channel *vmbus_get_outgoing_channel(struct vmbus_channel *primary)
+{
+       struct list_head *cur, *tmp;
+       int cur_cpu = hv_context.vp_index[smp_processor_id()];
+       struct vmbus_channel *cur_channel;
+       struct vmbus_channel *outgoing_channel = primary;
+       int cpu_distance, new_cpu_distance;
+
+       if (list_empty(&primary->sc_list))
+               return outgoing_channel;
+
+       list_for_each_safe(cur, tmp, &primary->sc_list) {
+               cur_channel = list_entry(cur, struct vmbus_channel, sc_list);
+               if (cur_channel->state != CHANNEL_OPENED_STATE)
+                       continue;
+
+               if (cur_channel->target_vp == cur_cpu)
+                       return cur_channel;
+
+               cpu_distance = ((outgoing_channel->target_vp > cur_cpu) ?
+                               (outgoing_channel->target_vp - cur_cpu) :
+                               (cur_cpu - outgoing_channel->target_vp));
+
+               new_cpu_distance = ((cur_channel->target_vp > cur_cpu) ?
+                               (cur_channel->target_vp - cur_cpu) :
+                               (cur_cpu - cur_channel->target_vp));
+
+               if (cpu_distance < new_cpu_distance)
+                       continue;
+
+               outgoing_channel = cur_channel;
+       }
+
+       return outgoing_channel;
+}
+EXPORT_SYMBOL_GPL(vmbus_get_outgoing_channel);
+
+static void invoke_sc_cb(struct vmbus_channel *primary_channel)
+{
+       struct list_head *cur, *tmp;
+       struct vmbus_channel *cur_channel;
+
+       if (primary_channel->sc_creation_callback == NULL)
+               return;
+
+       list_for_each_safe(cur, tmp, &primary_channel->sc_list) {
+               cur_channel = list_entry(cur, struct vmbus_channel, sc_list);
+
+               primary_channel->sc_creation_callback(cur_channel);
+       }
+}
+
+void vmbus_set_sc_create_callback(struct vmbus_channel *primary_channel,
+                               void (*sc_cr_cb)(struct vmbus_channel *new_sc))
+{
+       primary_channel->sc_creation_callback = sc_cr_cb;
+}
+EXPORT_SYMBOL_GPL(vmbus_set_sc_create_callback);
+
+bool vmbus_are_subchannels_present(struct vmbus_channel *primary)
+{
+       bool ret;
+
+       ret = !list_empty(&primary->sc_list);
+
+       if (ret) {
+               /*
+                * Invoke the callback on sub-channel creation.
+                * This will present a uniform interface to the
+                * clients.
+                */
+               invoke_sc_cb(primary);
+       }
+
+       return ret;
+}
+EXPORT_SYMBOL_GPL(vmbus_are_subchannels_present);