diff options
author | Stephen Hemminger <stephen@networkplumber.org> | 2018-09-14 18:10:17 +0200 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2018-09-25 20:33:24 +0200 |
commit | ae6935ed7d424ffa74d634da00767e7b03c98fd3 (patch) | |
tree | d26a4096301a8555277b50eb9741bcb978a308b2 /drivers/hv/channel.c | |
parent | vmbus: keep pointer to ring buffer page (diff) | |
download | linux-ae6935ed7d424ffa74d634da00767e7b03c98fd3.tar.xz linux-ae6935ed7d424ffa74d634da00767e7b03c98fd3.zip |
vmbus: split ring buffer allocation from open
The UIO driver needs the ring buffer to be persistent(reused)
across open/close. Split the allocation and setup of ring buffer
out of vmbus_open. For normal usage vmbus_open/vmbus_close there
are no changes; only impacts uio_hv_generic which needs to keep
ring buffer memory and reuse when application restarts.
Signed-off-by: Stephen Hemminger <sthemmin@microsoft.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/hv/channel.c')
-rw-r--r-- | drivers/hv/channel.c | 267 |
1 files changed, 152 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); |