summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--drivers/hv/channel.c267
-rw-r--r--drivers/hv/ring_buffer.c1
-rw-r--r--include/linux/hyperv.h9
3 files changed, 162 insertions, 115 deletions
diff --git a/drivers/hv/channel.c b/drivers/hv/channel.c
index 56ec0d96d876..ddadb7efd1cc 100644
--- a/drivers/hv/channel.c
+++ b/drivers/hv/channel.c
@@ -79,84 +79,96 @@ void vmbus_setevent(struct vmbus_channel *channel)
}
EXPORT_SYMBOL_GPL(vmbus_setevent);
-/*
- * vmbus_open - Open the specified channel.
- */
-int vmbus_open(struct vmbus_channel *newchannel, u32 send_ringbuffer_size,
- u32 recv_ringbuffer_size, void *userdata, u32 userdatalen,
- void (*onchannelcallback)(void *context), void *context)
+/* vmbus_free_ring - drop mapping of ring buffer */
+void vmbus_free_ring(struct vmbus_channel *channel)
{
- struct vmbus_channel_open_channel *open_msg;
- struct vmbus_channel_msginfo *open_info = NULL;
- unsigned long flags;
- int ret, err = 0;
- struct page *page;
- unsigned int order;
+ hv_ringbuffer_cleanup(&channel->outbound);
+ hv_ringbuffer_cleanup(&channel->inbound);
- if (send_ringbuffer_size % PAGE_SIZE ||
- recv_ringbuffer_size % PAGE_SIZE)
- return -EINVAL;
+ if (channel->ringbuffer_page) {
+ __free_pages(channel->ringbuffer_page,
+ get_order(channel->ringbuffer_pagecount
+ << PAGE_SHIFT));
+ channel->ringbuffer_page = NULL;
+ }
+}
+EXPORT_SYMBOL_GPL(vmbus_free_ring);
- order = get_order(send_ringbuffer_size + recv_ringbuffer_size);
+/* vmbus_alloc_ring - allocate and map pages for ring buffer */
+int vmbus_alloc_ring(struct vmbus_channel *newchannel,
+ u32 send_size, u32 recv_size)
+{
+ struct page *page;
+ int order;
- spin_lock_irqsave(&newchannel->lock, flags);
- if (newchannel->state == CHANNEL_OPEN_STATE) {
- newchannel->state = CHANNEL_OPENING_STATE;
- } else {
- spin_unlock_irqrestore(&newchannel->lock, flags);
+ if (send_size % PAGE_SIZE || recv_size % PAGE_SIZE)
return -EINVAL;
- }
- spin_unlock_irqrestore(&newchannel->lock, flags);
-
- newchannel->onchannel_callback = onchannelcallback;
- newchannel->channel_callback_context = context;
/* Allocate the ring buffer */
+ order = get_order(send_size + recv_size);
page = alloc_pages_node(cpu_to_node(newchannel->target_cpu),
GFP_KERNEL|__GFP_ZERO, order);
if (!page)
page = alloc_pages(GFP_KERNEL|__GFP_ZERO, order);
- if (!page) {
- err = -ENOMEM;
- goto error_set_chnstate;
- }
+ if (!page)
+ return -ENOMEM;
newchannel->ringbuffer_page = page;
- newchannel->ringbuffer_pagecount = (send_ringbuffer_size +
- recv_ringbuffer_size) >> PAGE_SHIFT;
+ newchannel->ringbuffer_pagecount = (send_size + recv_size) >> PAGE_SHIFT;
+ newchannel->ringbuffer_send_offset = send_size >> PAGE_SHIFT;
- ret = hv_ringbuffer_init(&newchannel->outbound, page,
- send_ringbuffer_size >> PAGE_SHIFT);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(vmbus_alloc_ring);
- if (ret != 0) {
- err = ret;
- goto error_free_pages;
- }
+static int __vmbus_open(struct vmbus_channel *newchannel,
+ void *userdata, u32 userdatalen,
+ void (*onchannelcallback)(void *context), void *context)
+{
+ struct vmbus_channel_open_channel *open_msg;
+ struct vmbus_channel_msginfo *open_info = NULL;
+ struct page *page = newchannel->ringbuffer_page;
+ u32 send_pages, recv_pages;
+ unsigned long flags;
+ int err;
- ret = hv_ringbuffer_init(&newchannel->inbound,
- &page[send_ringbuffer_size >> PAGE_SHIFT],
- recv_ringbuffer_size >> PAGE_SHIFT);
- if (ret != 0) {
- err = ret;
- goto error_free_pages;
+ if (userdatalen > MAX_USER_DEFINED_BYTES)
+ return -EINVAL;
+
+ send_pages = newchannel->ringbuffer_send_offset;
+ recv_pages = newchannel->ringbuffer_pagecount - send_pages;
+
+ spin_lock_irqsave(&newchannel->lock, flags);
+ if (newchannel->state != CHANNEL_OPEN_STATE) {
+ spin_unlock_irqrestore(&newchannel->lock, flags);
+ return -EINVAL;
}
+ spin_unlock_irqrestore(&newchannel->lock, flags);
+ newchannel->state = CHANNEL_OPENING_STATE;
+ newchannel->onchannel_callback = onchannelcallback;
+ newchannel->channel_callback_context = context;
+
+ err = hv_ringbuffer_init(&newchannel->outbound, page, send_pages);
+ if (err)
+ goto error_clean_ring;
+
+ err = hv_ringbuffer_init(&newchannel->inbound,
+ &page[send_pages], recv_pages);
+ if (err)
+ goto error_clean_ring;
/* Establish the gpadl for the ring buffer */
newchannel->ringbuffer_gpadlhandle = 0;
- ret = vmbus_establish_gpadl(newchannel,
- page_address(page),
- send_ringbuffer_size +
- recv_ringbuffer_size,
+ err = vmbus_establish_gpadl(newchannel,
+ page_address(newchannel->ringbuffer_page),
+ (send_pages + recv_pages) << PAGE_SHIFT,
&newchannel->ringbuffer_gpadlhandle);
-
- if (ret != 0) {
- err = ret;
- goto error_free_pages;
- }
+ if (err)
+ goto error_clean_ring;
/* Create and init the channel open message */
open_info = kmalloc(sizeof(*open_info) +
@@ -175,15 +187,9 @@ int vmbus_open(struct vmbus_channel *newchannel, u32 send_ringbuffer_size,
open_msg->openid = newchannel->offermsg.child_relid;
open_msg->child_relid = newchannel->offermsg.child_relid;
open_msg->ringbuffer_gpadlhandle = newchannel->ringbuffer_gpadlhandle;
- open_msg->downstream_ringbuffer_pageoffset = send_ringbuffer_size >>
- PAGE_SHIFT;
+ open_msg->downstream_ringbuffer_pageoffset = newchannel->ringbuffer_send_offset;
open_msg->target_vp = newchannel->target_vp;
- if (userdatalen > MAX_USER_DEFINED_BYTES) {
- err = -EINVAL;
- goto error_free_gpadl;
- }
-
if (userdatalen)
memcpy(open_msg->userdata, userdata, userdatalen);
@@ -194,18 +200,16 @@ int vmbus_open(struct vmbus_channel *newchannel, u32 send_ringbuffer_size,
if (newchannel->rescind) {
err = -ENODEV;
- goto error_free_gpadl;
+ goto error_free_info;
}
- ret = vmbus_post_msg(open_msg,
+ err = vmbus_post_msg(open_msg,
sizeof(struct vmbus_channel_open_channel), true);
- trace_vmbus_open(open_msg, ret);
+ trace_vmbus_open(open_msg, err);
- if (ret != 0) {
- err = ret;
+ if (err != 0)
goto error_clean_msglist;
- }
wait_for_completion(&open_info->waitevent);
@@ -215,12 +219,12 @@ int vmbus_open(struct vmbus_channel *newchannel, u32 send_ringbuffer_size,
if (newchannel->rescind) {
err = -ENODEV;
- goto error_free_gpadl;
+ goto error_free_info;
}
if (open_info->response.open_result.status) {
err = -EAGAIN;
- goto error_free_gpadl;
+ goto error_free_info;
}
newchannel->state = CHANNEL_OPENED_STATE;
@@ -231,18 +235,50 @@ error_clean_msglist:
spin_lock_irqsave(&vmbus_connection.channelmsg_lock, flags);
list_del(&open_info->msglistentry);
spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock, flags);
-
+error_free_info:
+ kfree(open_info);
error_free_gpadl:
vmbus_teardown_gpadl(newchannel, newchannel->ringbuffer_gpadlhandle);
- kfree(open_info);
-error_free_pages:
+ newchannel->ringbuffer_gpadlhandle = 0;
+error_clean_ring:
hv_ringbuffer_cleanup(&newchannel->outbound);
hv_ringbuffer_cleanup(&newchannel->inbound);
- __free_pages(page, order);
-error_set_chnstate:
newchannel->state = CHANNEL_OPEN_STATE;
return err;
}
+
+/*
+ * vmbus_connect_ring - Open the channel but reuse ring buffer
+ */
+int vmbus_connect_ring(struct vmbus_channel *newchannel,
+ void (*onchannelcallback)(void *context), void *context)
+{
+ return __vmbus_open(newchannel, NULL, 0, onchannelcallback, context);
+}
+EXPORT_SYMBOL_GPL(vmbus_connect_ring);
+
+/*
+ * vmbus_open - Open the specified channel.
+ */
+int vmbus_open(struct vmbus_channel *newchannel,
+ u32 send_ringbuffer_size, u32 recv_ringbuffer_size,
+ void *userdata, u32 userdatalen,
+ void (*onchannelcallback)(void *context), void *context)
+{
+ int err;
+
+ err = vmbus_alloc_ring(newchannel, send_ringbuffer_size,
+ recv_ringbuffer_size);
+ if (err)
+ return err;
+
+ err = __vmbus_open(newchannel, userdata, userdatalen,
+ onchannelcallback, context);
+ if (err)
+ vmbus_free_ring(newchannel);
+
+ return err;
+}
EXPORT_SYMBOL_GPL(vmbus_open);
/* Used for Hyper-V Socket: a guest client's connect() to the host */
@@ -610,10 +646,8 @@ static int vmbus_close_internal(struct vmbus_channel *channel)
* in Hyper-V Manager), the driver's remove() invokes vmbus_close():
* here we should skip most of the below cleanup work.
*/
- if (channel->state != CHANNEL_OPENED_STATE) {
- ret = -EINVAL;
- goto out;
- }
+ if (channel->state != CHANNEL_OPENED_STATE)
+ return -EINVAL;
channel->state = CHANNEL_OPEN_STATE;
@@ -635,11 +669,10 @@ static int vmbus_close_internal(struct vmbus_channel *channel)
* If we failed to post the close msg,
* it is perhaps better to leak memory.
*/
- goto out;
}
/* Tear down the gpadl for the channel's ring buffer */
- if (channel->ringbuffer_gpadlhandle) {
+ else if (channel->ringbuffer_gpadlhandle) {
ret = vmbus_teardown_gpadl(channel,
channel->ringbuffer_gpadlhandle);
if (ret) {
@@ -648,59 +681,63 @@ static int vmbus_close_internal(struct vmbus_channel *channel)
* If we failed to teardown gpadl,
* it is perhaps better to leak memory.
*/
- goto out;
}
- }
-
- /* Cleanup the ring buffers for this channel */
- hv_ringbuffer_cleanup(&channel->outbound);
- hv_ringbuffer_cleanup(&channel->inbound);
- __free_pages(channel->ringbuffer_page,
- get_order(channel->ringbuffer_pagecount << PAGE_SHIFT));
+ channel->ringbuffer_gpadlhandle = 0;
+ }
-out:
return ret;
}
-/*
- * vmbus_close - Close the specified channel
- */
-void vmbus_close(struct vmbus_channel *channel)
+/* disconnect ring - close all channels */
+int vmbus_disconnect_ring(struct vmbus_channel *channel)
{
- struct list_head *cur, *tmp;
- struct vmbus_channel *cur_channel;
+ struct vmbus_channel *cur_channel, *tmp;
+ unsigned long flags;
+ LIST_HEAD(list);
+ int ret;
- if (channel->primary_channel != NULL) {
- /*
- * We will only close sub-channels when
- * the primary is closed.
- */
- return;
- }
- /*
- * Close all the sub-channels first and then close the
- * primary channel.
- */
- list_for_each_safe(cur, tmp, &channel->sc_list) {
- cur_channel = list_entry(cur, struct vmbus_channel, sc_list);
- if (cur_channel->rescind) {
+ if (channel->primary_channel != NULL)
+ return -EINVAL;
+
+ /* Snapshot the list of subchannels */
+ spin_lock_irqsave(&channel->lock, flags);
+ list_splice_init(&channel->sc_list, &list);
+ channel->num_sc = 0;
+ spin_unlock_irqrestore(&channel->lock, flags);
+
+ list_for_each_entry_safe(cur_channel, tmp, &list, sc_list) {
+ if (cur_channel->rescind)
wait_for_completion(&cur_channel->rescind_event);
- mutex_lock(&vmbus_connection.channel_mutex);
- vmbus_close_internal(cur_channel);
- hv_process_channel_removal(cur_channel);
- } else {
- mutex_lock(&vmbus_connection.channel_mutex);
- vmbus_close_internal(cur_channel);
+
+ mutex_lock(&vmbus_connection.channel_mutex);
+ if (vmbus_close_internal(cur_channel) == 0) {
+ vmbus_free_ring(cur_channel);
+
+ if (cur_channel->rescind)
+ hv_process_channel_removal(cur_channel);
}
mutex_unlock(&vmbus_connection.channel_mutex);
}
+
/*
* Now close the primary.
*/
mutex_lock(&vmbus_connection.channel_mutex);
- vmbus_close_internal(channel);
+ ret = vmbus_close_internal(channel);
mutex_unlock(&vmbus_connection.channel_mutex);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(vmbus_disconnect_ring);
+
+/*
+ * vmbus_close - Close the specified channel
+ */
+void vmbus_close(struct vmbus_channel *channel)
+{
+ if (vmbus_disconnect_ring(channel) == 0)
+ vmbus_free_ring(channel);
}
EXPORT_SYMBOL_GPL(vmbus_close);
diff --git a/drivers/hv/ring_buffer.c b/drivers/hv/ring_buffer.c
index 3e90eb91db45..64d0c85d5161 100644
--- a/drivers/hv/ring_buffer.c
+++ b/drivers/hv/ring_buffer.c
@@ -241,6 +241,7 @@ int hv_ringbuffer_init(struct hv_ring_buffer_info *ring_info,
void hv_ringbuffer_cleanup(struct hv_ring_buffer_info *ring_info)
{
vunmap(ring_info->ring_buffer);
+ ring_info->ring_buffer = NULL;
}
/* Write to the ring buffer. */
diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h
index a6c32d2d090b..b3e24368930a 100644
--- a/include/linux/hyperv.h
+++ b/include/linux/hyperv.h
@@ -741,6 +741,7 @@ struct vmbus_channel {
/* Allocated memory for ring buffer */
struct page *ringbuffer_page;
u32 ringbuffer_pagecount;
+ u32 ringbuffer_send_offset;
struct hv_ring_buffer_info outbound; /* send to parent */
struct hv_ring_buffer_info inbound; /* receive from parent */
@@ -1021,6 +1022,14 @@ struct vmbus_packet_mpb_array {
struct hv_mpb_array range;
} __packed;
+int vmbus_alloc_ring(struct vmbus_channel *channel,
+ u32 send_size, u32 recv_size);
+void vmbus_free_ring(struct vmbus_channel *channel);
+
+int vmbus_connect_ring(struct vmbus_channel *channel,
+ void (*onchannel_callback)(void *context),
+ void *context);
+int vmbus_disconnect_ring(struct vmbus_channel *channel);
extern int vmbus_open(struct vmbus_channel *channel,
u32 send_ringbuffersize,