Skip to content
Shubhangi Gupta edited this page Aug 21, 2015 · 15 revisions

Continued from previous post

Each vdev will have two vrings i.e RX and TX. Each buffer of vring has a fixed size of 512 bytes. So the size of each vring is (number of buffers specified in rsc table) x 512. Both vrings (rx and tx) should have same number of buffers. This should be in power of 2 ( makes it easier to implement circular queue ).

Earlier we had said that vrings are an implementation of virtqueue. Let us see how this takes place.
The actual buffers which carry data are a part of virtqueue. After a vdev is registered by remoteproc core driver, it is passed to this probe function of virtio_rpmsg_bus which allocates memory for virtqueue buffers and adds kick callbacks (called after a vring is kicked). Keep this aside for a moment.

The vring is composed of three parts:

  • A descriptor array :
struct vring_desc  
{  
__u64 addr;  
__u32 len;  
__u16 flags;  
__u16 next;    
};  

Each descriptor has the physical address of the buffer, its length, flags to mark as Readable/Writable, next is used for chaining.

  • Available ring
struct vring_avail  
{  
__u16 flags;  
__u16 idx;  
__u16 ring[NUM];  
};  

Flags for suppressing interrupts, a free running index and array of indices into the descriptor table. This gives us all buffers in the descriptor table which are currently available.

  • Used ring
struct vring_used_elem  
{  
__u32 id;  
__u32 len;  
};  
  
struct vring_used  
{  
__u16 flags;  
__u16 idx;  
struct vring_used_elem ring[];  
};  

This is similar to the available ring.

We do not need to handle used and available vrings. However it is always good to know what things are like under the hood. This brings us back to where we left virtqueue. Now that we have both vrings and virtqueues, the only element missing is the link which ties them together and gives us the complete implementation i.e exported functions which module authors can use. All that goes inside this virtio_ring.c.

virtio_ring driver documents all functions exported for use. Some essential ones are:

  • virtqueue_get_buf - get the next used buffer
  • virtqueue_add_inbuf - expose input buffers to other end
  • virtqueue_kick - update after add_buf
  • virtqueue_get_vring_size - return the size of the virtqueue's vring

You can find these functions being used in the remoteproce core driver as well as pruss_remoteproc and get a better understanding.

A typical vring implementation to communicate between two devices would follow this pattern:
Sender's end

Loop while ( vring_desc available )  
{  da = vring_desc.da;  
   *da = data;  
}  
vring_desc.next = ~ATTACHED_TO_NEXT;  
kick(vring);  

Receiver's end

Loop while (vring_desc.ext & ATTACHED_TO_NEXT)  
{ data = virtqueue_get_buf(vq);  
  vring_add_inbuf(vq);  
}  

Hope this documentation was able to shed some light on vrings for PRU and in general. Kindly correct me if I might have interpreted something incorrectly.

Clone this wiki locally