diff options
Diffstat (limited to 'drivers/hv/channel.c')
-rw-r--r-- | drivers/hv/channel.c | 174 |
1 files changed, 97 insertions, 77 deletions
diff --git a/drivers/hv/channel.c b/drivers/hv/channel.c index c4dcab048cb8..56dd261f7142 100644 --- a/drivers/hv/channel.c +++ b/drivers/hv/channel.c @@ -28,6 +28,7 @@ #include <linux/module.h> #include <linux/hyperv.h> #include <linux/uio.h> +#include <linux/interrupt.h> #include "hyperv_vmbus.h" @@ -218,6 +219,21 @@ error0: } EXPORT_SYMBOL_GPL(vmbus_open); +/* Used for Hyper-V Socket: a guest client's connect() to the host */ +int vmbus_send_tl_connect_request(const uuid_le *shv_guest_servie_id, + const uuid_le *shv_host_servie_id) +{ + struct vmbus_channel_tl_connect_request conn_msg; + + memset(&conn_msg, 0, sizeof(conn_msg)); + conn_msg.header.msgtype = CHANNELMSG_TL_CONNECT_REQUEST; + conn_msg.guest_endpoint_id = *shv_guest_servie_id; + conn_msg.host_service_id = *shv_host_servie_id; + + return vmbus_post_msg(&conn_msg, sizeof(conn_msg)); +} +EXPORT_SYMBOL_GPL(vmbus_send_tl_connect_request); + /* * create_gpadl_header - Creates a gpadl for the specified buffer */ @@ -496,8 +512,33 @@ static void reset_channel_cb(void *arg) static int vmbus_close_internal(struct vmbus_channel *channel) { struct vmbus_channel_close_channel *msg; + struct tasklet_struct *tasklet; int ret; + /* + * process_chn_event(), running in the tasklet, can race + * with vmbus_close_internal() in the case of SMP guest, e.g., when + * the former is accessing channel->inbound.ring_buffer, the latter + * could be freeing the ring_buffer pages. + * + * To resolve the race, we can serialize them by disabling the + * tasklet when the latter is running here. + */ + tasklet = hv_context.event_dpc[channel->target_cpu]; + tasklet_disable(tasklet); + + /* + * In case a device driver's probe() fails (e.g., + * util_probe() -> vmbus_open() returns -ENOMEM) and the device is + * rescinded later (e.g., we dynamically disble an Integrated Service + * 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; + } + channel->state = CHANNEL_OPEN_STATE; channel->sc_creation_callback = NULL; /* Stop callback and cancel the timer asap */ @@ -525,7 +566,7 @@ static int vmbus_close_internal(struct vmbus_channel *channel) * If we failed to post the close msg, * it is perhaps better to leak memory. */ - return ret; + goto out; } /* Tear down the gpadl for the channel's ring buffer */ @@ -538,7 +579,7 @@ static int vmbus_close_internal(struct vmbus_channel *channel) * If we failed to teardown gpadl, * it is perhaps better to leak memory. */ - return ret; + goto out; } } @@ -549,12 +590,9 @@ static int vmbus_close_internal(struct vmbus_channel *channel) free_pages((unsigned long)channel->ringbuffer_pages, get_order(channel->ringbuffer_pagecount * PAGE_SIZE)); - /* - * If the channel has been rescinded; process device removal. - */ - if (channel->rescind) - hv_process_channel_removal(channel, - channel->offermsg.child_relid); +out: + tasklet_enable(tasklet); + return ret; } @@ -601,6 +639,7 @@ int vmbus_sendpacket_ctl(struct vmbus_channel *channel, void *buffer, u64 aligned_data = 0; int ret; bool signal = false; + bool lock = channel->acquire_ring_lock; int num_vecs = ((bufferlen != 0) ? 3 : 1); @@ -620,7 +659,7 @@ int vmbus_sendpacket_ctl(struct vmbus_channel *channel, void *buffer, bufferlist[2].iov_len = (packetlen_aligned - packetlen); ret = hv_ringbuffer_write(&channel->outbound, bufferlist, num_vecs, - &signal); + &signal, lock); /* * Signalling the host is conditional on many factors: @@ -630,11 +669,24 @@ int vmbus_sendpacket_ctl(struct vmbus_channel *channel, void *buffer, * on the ring. We will not signal if more data is * to be placed. * + * Based on the channel signal state, we will decide + * which signaling policy will be applied. + * * If we cannot write to the ring-buffer; signal the host * even if we may not have written anything. This is a rare * enough condition that it should not matter. + * NOTE: in this case, the hvsock channel is an exception, because + * it looks the host side's hvsock implementation has a throttling + * mechanism which can hurt the performance otherwise. */ - if (((ret == 0) && kick_q && signal) || (ret)) + + if (channel->signal_policy) + signal = true; + else + kick_q = true; + + if (((ret == 0) && kick_q && signal) || + (ret && !is_hvsock_channel(channel))) vmbus_setevent(channel); return ret; @@ -687,6 +739,7 @@ int vmbus_sendpacket_pagebuffer_ctl(struct vmbus_channel *channel, struct kvec bufferlist[3]; u64 aligned_data = 0; bool signal = false; + bool lock = channel->acquire_ring_lock; if (pagecount > MAX_PAGE_BUFFER_COUNT) return -EINVAL; @@ -723,7 +776,8 @@ int vmbus_sendpacket_pagebuffer_ctl(struct vmbus_channel *channel, bufferlist[2].iov_base = &aligned_data; bufferlist[2].iov_len = (packetlen_aligned - packetlen); - ret = hv_ringbuffer_write(&channel->outbound, bufferlist, 3, &signal); + ret = hv_ringbuffer_write(&channel->outbound, bufferlist, 3, + &signal, lock); /* * Signalling the host is conditional on many factors: @@ -733,10 +787,19 @@ int vmbus_sendpacket_pagebuffer_ctl(struct vmbus_channel *channel, * on the ring. We will not signal if more data is * to be placed. * + * Based on the channel signal state, we will decide + * which signaling policy will be applied. + * * If we cannot write to the ring-buffer; signal the host * even if we may not have written anything. This is a rare * enough condition that it should not matter. */ + + if (channel->signal_policy) + signal = true; + else + kick_q = true; + if (((ret == 0) && kick_q && signal) || (ret)) vmbus_setevent(channel); @@ -777,6 +840,7 @@ int vmbus_sendpacket_mpb_desc(struct vmbus_channel *channel, struct kvec bufferlist[3]; u64 aligned_data = 0; bool signal = false; + bool lock = channel->acquire_ring_lock; packetlen = desc_size + bufferlen; packetlen_aligned = ALIGN(packetlen, sizeof(u64)); @@ -796,7 +860,8 @@ int vmbus_sendpacket_mpb_desc(struct vmbus_channel *channel, bufferlist[2].iov_base = &aligned_data; bufferlist[2].iov_len = (packetlen_aligned - packetlen); - ret = hv_ringbuffer_write(&channel->outbound, bufferlist, 3, &signal); + ret = hv_ringbuffer_write(&channel->outbound, bufferlist, 3, + &signal, lock); if (ret == 0 && signal) vmbus_setevent(channel); @@ -821,6 +886,7 @@ int vmbus_sendpacket_multipagebuffer(struct vmbus_channel *channel, struct kvec bufferlist[3]; u64 aligned_data = 0; bool signal = false; + bool lock = channel->acquire_ring_lock; u32 pfncount = NUM_PAGES_SPANNED(multi_pagebuffer->offset, multi_pagebuffer->len); @@ -859,7 +925,8 @@ int vmbus_sendpacket_multipagebuffer(struct vmbus_channel *channel, bufferlist[2].iov_base = &aligned_data; bufferlist[2].iov_len = (packetlen_aligned - packetlen); - ret = hv_ringbuffer_write(&channel->outbound, bufferlist, 3, &signal); + ret = hv_ringbuffer_write(&channel->outbound, bufferlist, 3, + &signal, lock); if (ret == 0 && signal) vmbus_setevent(channel); @@ -881,46 +948,29 @@ EXPORT_SYMBOL_GPL(vmbus_sendpacket_multipagebuffer); * * Mainly used by Hyper-V drivers. */ -int vmbus_recvpacket(struct vmbus_channel *channel, void *buffer, - u32 bufferlen, u32 *buffer_actual_len, u64 *requestid) +static inline int +__vmbus_recvpacket(struct vmbus_channel *channel, void *buffer, + u32 bufferlen, u32 *buffer_actual_len, u64 *requestid, + bool raw) { - struct vmpacket_descriptor desc; - u32 packetlen; - u32 userlen; int ret; bool signal = false; - *buffer_actual_len = 0; - *requestid = 0; - - - ret = hv_ringbuffer_peek(&channel->inbound, &desc, - sizeof(struct vmpacket_descriptor)); - if (ret != 0) - return 0; - - packetlen = desc.len8 << 3; - userlen = packetlen - (desc.offset8 << 3); - - *buffer_actual_len = userlen; - - if (userlen > bufferlen) { - - pr_err("Buffer too small - got %d needs %d\n", - bufferlen, userlen); - return -ETOOSMALL; - } - - *requestid = desc.trans_id; - - /* Copy over the packet to the user buffer */ - ret = hv_ringbuffer_read(&channel->inbound, buffer, userlen, - (desc.offset8 << 3), &signal); + ret = hv_ringbuffer_read(&channel->inbound, buffer, bufferlen, + buffer_actual_len, requestid, &signal, raw); if (signal) vmbus_setevent(channel); - return 0; + return ret; +} + +int vmbus_recvpacket(struct vmbus_channel *channel, void *buffer, + u32 bufferlen, u32 *buffer_actual_len, + u64 *requestid) +{ + return __vmbus_recvpacket(channel, buffer, bufferlen, + buffer_actual_len, requestid, false); } EXPORT_SYMBOL(vmbus_recvpacket); @@ -931,37 +981,7 @@ int vmbus_recvpacket_raw(struct vmbus_channel *channel, void *buffer, u32 bufferlen, u32 *buffer_actual_len, u64 *requestid) { - struct vmpacket_descriptor desc; - u32 packetlen; - int ret; - bool signal = false; - - *buffer_actual_len = 0; - *requestid = 0; - - - ret = hv_ringbuffer_peek(&channel->inbound, &desc, - sizeof(struct vmpacket_descriptor)); - if (ret != 0) - return 0; - - - packetlen = desc.len8 << 3; - - *buffer_actual_len = packetlen; - - if (packetlen > bufferlen) - return -ENOBUFS; - - *requestid = desc.trans_id; - - /* Copy over the entire packet to the user buffer */ - ret = hv_ringbuffer_read(&channel->inbound, buffer, packetlen, 0, - &signal); - - if (signal) - vmbus_setevent(channel); - - return ret; + return __vmbus_recvpacket(channel, buffer, bufferlen, + buffer_actual_len, requestid, true); } EXPORT_SYMBOL_GPL(vmbus_recvpacket_raw); |