diff options
Diffstat (limited to 'drivers/hv/ring_buffer.c')
-rw-r--r-- | drivers/hv/ring_buffer.c | 169 |
1 files changed, 60 insertions, 109 deletions
diff --git a/drivers/hv/ring_buffer.c b/drivers/hv/ring_buffer.c index 1f450c39a9b0..12eb8caa4263 100644 --- a/drivers/hv/ring_buffer.c +++ b/drivers/hv/ring_buffer.c @@ -29,6 +29,7 @@ #include <linux/uio.h> #include <linux/vmalloc.h> #include <linux/slab.h> +#include <linux/prefetch.h> #include "hyperv_vmbus.h" @@ -94,30 +95,6 @@ hv_set_next_write_location(struct hv_ring_buffer_info *ring_info, ring_info->ring_buffer->write_index = next_write_location; } -/* Get the next read location for the specified ring buffer. */ -static inline u32 -hv_get_next_read_location(const struct hv_ring_buffer_info *ring_info) -{ - return ring_info->ring_buffer->read_index; -} - -/* - * Get the next read location + offset for the specified ring buffer. - * This allows the caller to skip. - */ -static inline u32 -hv_get_next_readlocation_withoffset(const struct hv_ring_buffer_info *ring_info, - u32 offset) -{ - u32 next = ring_info->ring_buffer->read_index; - - next += offset; - if (next >= ring_info->ring_datasize) - next -= ring_info->ring_datasize; - - return next; -} - /* Set the next read location for the specified ring buffer. */ static inline void hv_set_next_read_location(struct hv_ring_buffer_info *ring_info, @@ -142,29 +119,6 @@ hv_get_ring_bufferindices(struct hv_ring_buffer_info *ring_info) } /* - * Helper routine to copy to source from ring buffer. - * Assume there is enough room. Handles wrap-around in src case only!! - */ -static u32 hv_copyfrom_ringbuffer( - const struct hv_ring_buffer_info *ring_info, - void *dest, - u32 destlen, - u32 start_read_offset) -{ - void *ring_buffer = hv_get_ring_buffer(ring_info); - u32 ring_buffer_size = hv_get_ring_buffersize(ring_info); - - memcpy(dest, ring_buffer + start_read_offset, destlen); - - start_read_offset += destlen; - if (start_read_offset >= ring_buffer_size) - start_read_offset -= ring_buffer_size; - - return start_read_offset; -} - - -/* * Helper routine to copy from source to ring buffer. * Assume there is enough room. Handles wrap-around in dest case only!! */ @@ -334,33 +288,22 @@ int hv_ringbuffer_write(struct vmbus_channel *channel, return 0; } -static inline void -init_cached_read_index(struct hv_ring_buffer_info *rbi) -{ - rbi->cached_read_index = rbi->ring_buffer->read_index; -} - int hv_ringbuffer_read(struct vmbus_channel *channel, void *buffer, u32 buflen, u32 *buffer_actual_len, u64 *requestid, bool raw) { - u32 bytes_avail_toread; - u32 next_read_location; - u64 prev_indices = 0; - struct vmpacket_descriptor desc; - u32 offset; - u32 packetlen; - struct hv_ring_buffer_info *inring_info = &channel->inbound; - - if (buflen <= 0) + struct vmpacket_descriptor *desc; + u32 packetlen, offset; + + if (unlikely(buflen == 0)) return -EINVAL; *buffer_actual_len = 0; *requestid = 0; - bytes_avail_toread = hv_get_bytes_to_read(inring_info); /* Make sure there is something to read */ - if (bytes_avail_toread < sizeof(desc)) { + desc = hv_pkt_iter_first(channel); + if (desc == NULL) { /* * No error is set when there is even no header, drivers are * supposed to analyze buffer_actual_len. @@ -368,48 +311,22 @@ int hv_ringbuffer_read(struct vmbus_channel *channel, return 0; } - init_cached_read_index(inring_info); - - next_read_location = hv_get_next_read_location(inring_info); - next_read_location = hv_copyfrom_ringbuffer(inring_info, &desc, - sizeof(desc), - next_read_location); - - offset = raw ? 0 : (desc.offset8 << 3); - packetlen = (desc.len8 << 3) - offset; + offset = raw ? 0 : (desc->offset8 << 3); + packetlen = (desc->len8 << 3) - offset; *buffer_actual_len = packetlen; - *requestid = desc.trans_id; - - if (bytes_avail_toread < packetlen + offset) - return -EAGAIN; + *requestid = desc->trans_id; - if (packetlen > buflen) + if (unlikely(packetlen > buflen)) return -ENOBUFS; - next_read_location = - hv_get_next_readlocation_withoffset(inring_info, offset); + /* since ring is double mapped, only one copy is necessary */ + memcpy(buffer, (const char *)desc + offset, packetlen); - next_read_location = hv_copyfrom_ringbuffer(inring_info, - buffer, - packetlen, - next_read_location); + /* Advance ring index to next packet descriptor */ + __hv_pkt_iter_next(channel, desc); - next_read_location = hv_copyfrom_ringbuffer(inring_info, - &prev_indices, - sizeof(u64), - next_read_location); - - /* - * Make sure all reads are done before we update the read index since - * the writer may start writing to the read area once the read index - * is updated. - */ - virt_mb(); - - /* Update the read index */ - hv_set_next_read_location(inring_info, next_read_location); - - hv_signal_on_read(channel); + /* Notify host of update */ + hv_pkt_iter_close(channel); return 0; } @@ -440,14 +357,16 @@ static u32 hv_pkt_iter_avail(const struct hv_ring_buffer_info *rbi) struct vmpacket_descriptor *hv_pkt_iter_first(struct vmbus_channel *channel) { struct hv_ring_buffer_info *rbi = &channel->inbound; - - /* set state for later hv_signal_on_read() */ - init_cached_read_index(rbi); + struct vmpacket_descriptor *desc; if (hv_pkt_iter_avail(rbi) < sizeof(struct vmpacket_descriptor)) return NULL; - return hv_get_ring_buffer(rbi) + rbi->priv_read_index; + desc = hv_get_ring_buffer(rbi) + rbi->priv_read_index; + if (desc) + prefetch((char *)desc + (desc->len8 << 3)); + + return desc; } EXPORT_SYMBOL_GPL(hv_pkt_iter_first); @@ -471,10 +390,7 @@ __hv_pkt_iter_next(struct vmbus_channel *channel, rbi->priv_read_index -= dsize; /* more data? */ - if (hv_pkt_iter_avail(rbi) < sizeof(struct vmpacket_descriptor)) - return NULL; - else - return hv_get_ring_buffer(rbi) + rbi->priv_read_index; + return hv_pkt_iter_first(channel); } EXPORT_SYMBOL_GPL(__hv_pkt_iter_next); @@ -484,6 +400,7 @@ EXPORT_SYMBOL_GPL(__hv_pkt_iter_next); void hv_pkt_iter_close(struct vmbus_channel *channel) { struct hv_ring_buffer_info *rbi = &channel->inbound; + u32 orig_write_sz = hv_get_bytes_to_write(rbi); /* * Make sure all reads are done before we update the read index since @@ -493,6 +410,40 @@ void hv_pkt_iter_close(struct vmbus_channel *channel) virt_rmb(); rbi->ring_buffer->read_index = rbi->priv_read_index; - hv_signal_on_read(channel); + /* + * Issue a full memory barrier before making the signaling decision. + * Here is the reason for having this barrier: + * If the reading of the pend_sz (in this function) + * were to be reordered and read before we commit the new read + * index (in the calling function) we could + * have a problem. If the host were to set the pending_sz after we + * have sampled pending_sz and go to sleep before we commit the + * read index, we could miss sending the interrupt. Issue a full + * memory barrier to address this. + */ + virt_mb(); + + /* If host has disabled notifications then skip */ + if (rbi->ring_buffer->interrupt_mask) + return; + + if (rbi->ring_buffer->feature_bits.feat_pending_send_sz) { + u32 pending_sz = READ_ONCE(rbi->ring_buffer->pending_send_sz); + + /* + * If there was space before we began iteration, + * then host was not blocked. Also handles case where + * pending_sz is zero then host has nothing pending + * and does not need to be signaled. + */ + if (orig_write_sz > pending_sz) + return; + + /* If pending write will not fit, don't give false hope. */ + if (hv_get_bytes_to_write(rbi) < pending_sz) + return; + } + + vmbus_setevent(channel); } EXPORT_SYMBOL_GPL(hv_pkt_iter_close); |