[virtio] ring: Barriers before updating avail->idx/after reading used->idx

virtio::ring needs to have a write memory barrier with the DMA domain
after writing descriptor chains, before updating avail->idx, to ensure
the device sees up-to-date descriptor chains.

virtio::ring needs to have a read memory barrier with the DMA domain
after reading used->idx and before reading completed descriptor chains
from the device.

virtio::ring needs a write barrier after updating avail->idx but before
notifying the device, so that the device sees the newest avail->idx.
This barrier could be fused into PIO/MMIO notification writes in the
future - on some platforms UC stores may imply the required barrier.

Tested:
* Used virtio-net and virtio-blk on QEMU

Change-Id: I0bd1529022625a1509b657c443b6259c0a061d5b
diff --git a/system/dev/bus/virtio/ring.cpp b/system/dev/bus/virtio/ring.cpp
index af93772..52a6378 100644
--- a/system/dev/bus/virtio/ring.cpp
+++ b/system/dev/bus/virtio/ring.cpp
@@ -138,12 +138,17 @@
     struct vring_avail* avail = ring_.avail;
 
     avail->ring[avail->idx & ring_.num_mask] = desc_index;
-    //mb();
+    // Write memory barrier before updating avail->idx; updates to the descriptor ring must be
+    // visible before an updated avail->idx.
+    hw_wmb();
     avail->idx++;
 }
 
 void Ring::Kick() {
     LTRACE_ENTRY;
+    // Write memory barrier before notifying the device. Updates to avail->idx must be visible
+    // before the device sees the wakeup notification (so it processes the latest descriptors).
+    hw_mb();
 
     device_->RingKick(index_);
 }
diff --git a/system/dev/bus/virtio/ring.h b/system/dev/bus/virtio/ring.h
index 89d0446..e782c0e 100644
--- a/system/dev/bus/virtio/ring.h
+++ b/system/dev/bus/virtio/ring.h
@@ -4,6 +4,7 @@
 #pragma once
 
 #include <ddk/io-buffer.h>
+#include <hw/arch_ops.h>
 #include <virtio/virtio_ring.h>
 #include <zircon/types.h>
 
@@ -53,6 +54,9 @@
     // find a new free chain of descriptors
     uint16_t cur_idx = ring_.used->idx;
     uint16_t i = ring_.last_used;
+    // Read memory barrier before processing a descriptor chain. If we see an updated used->idx
+    // we must see updated descriptor chains in the used ring.
+    hw_rmb();
     for (; i != cur_idx; ++i) {
         // TRACEF("looking at idx %u\n", i);