/*
* Make sure all reads are done before we update the read index since
@@ -422,6 +422,10 @@ void hv_pkt_iter_close(struct vmbus_channel *channel)
/* Update the position where ring buffer has been read from */
rbi->ring_buffer->read_index = rbi->priv_read_index;

+ /* If more data is available then no need to signal */
+ if (hv_get_bytes_to_read(rbi))
+ return;
+
/*
* If the reading of the pend_sz were to be reordered and read
* before we commit the new read index.
@@ -431,22 +435,18 @@ void hv_pkt_iter_close(struct vmbus_channel *channel)
virt_wmb();

/*
* Update host ring buffer after iterating over packets.
- *
- * Avoid unnecessary signaling of the host by making sure that all
- * data is read, and the host has not masked off the interrupt.
- *
- * In addition, in Windows 8 or later there is an extension for the
- * host to indicate how much space needs to be available before
- * signaling. The hos sets pending_send_sz to the number of bytes
- * that it is waiting to send.
*/
void hv_pkt_iter_close(struct vmbus_channel *channel)
{
struct hv_ring_buffer_info *rbi = &channel->inbound;
- u32 orig_write_sz;
-
- /* Available space before read_index update */
- orig_write_sz = hv_get_bytes_to_write(rbi);
+ u32 orig_write_sz = hv_get_bytes_to_write(rbi);

/*
* Make sure all reads are done before we update the read index since
@@ -418,29 +408,29 @@ void hv_pkt_iter_close(struct vmbus_channel *channel)
* is updated.
*/
virt_rmb();
-
- /* Update the position where ring buffer has been read from */
rbi->ring_buffer->read_index = rbi->priv_read_index;

- /* If more data is available then no need to signal */
- if (hv_get_bytes_to_read(rbi))
- return;
-
/*
- * If the reading of the pend_sz were to be reordered and read
- * before we commit the new read index.
- * Then we could have if the host were to set the pending_sz
- * after we have already sampled pending_sz.
+ * 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_wmb();
+ virt_mb();

Fix bugs in signaling the Hyper-V host when freeing space in the
host->guest ring buffer:

1. The interrupt_mask must not be used to determine whether to signal
on the host->guest ring buffer
2. The ring buffer write_index must be read (via hv_get_bytes_to_write)
*after* pending_send_sz is read in order to avoid a race condition
3. Comparisons with pending_send_sz must treat the "equals" case as
not-enough-space
4. Don't signal if the pending_send_sz feature is not present. Older
versions of Hyper-V that don't implement this feature will poll.

/*
+ * Ensure the read of write_index in hv_get_bytes_to_write()
+ * happens after the read of pending_send_sz.
+ */
+ virt_rmb();
+ curr_write_sz = hv_get_bytes_to_write(rbi);
+
+ /*
* 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)
+ if (curr_write_sz - delta > pending_sz)
return;