Virtqueues
virtqueues是一种virtio pci device的大量数据传输的机制。每个设备可有0或更多的virtqueues,例如网络设备有一个发送queue 一个接收queue,每个queue占用两个或者更多的物理地址连续的page(virtio spec定义4096byte为一个page),queue由以下部分组成:
当驱动往设备发送整个buffer的时候,填写descriptor table的一条,将descriptor index写到available ring, 然后通知设备。当设备完成buffer的传输时,它写描述符到used ring中并且发送中断。
PCI discovery
PCI设备的Vendor ID是1AF4,Device ID是0x1000~0x103F的为virtio设备。Subsystem Device ID指示当前的设备支持哪种virtio设备。
Device Configuration和Device初始化步骤
为了配置设备,我们用PCI设备的first I/O region。 其中包含了virtio header, device-specific region。
virtio header格式如下:
其中,device status由guest更新,指示当前的过程。可以接在信号灯上作为各个设备的诊断,有ACKNOWLEDGE(指示guest OS已经发现并识别设备为virtio设备),DRIVER(指示guest OS知道如何驱动设备),DRIVER_OK(驱动已经准备好) FAILED(guest出错)
其中,Feature bit指示设备支持的Feature, 例如 Feature bit0对于网络设备(Subsystem Deive ID 1)指示设备支持packet checksumming。Feature bit是协商的,设备在Device Featrue字段列出所有支持的Feature, guest在Guest Featrue字段列出子集。
device-specfic格式如下:
Guest对Device进行初始化的步骤:
Virtqueue Configuration
- 驱动需要配置queue:
- 写queue index(第一个为0)到Queue Select字段;
- 读Queue Size字段获取Size;
- 分配queue连续的物理地址空间(4K对齐)将物理地址写入到Queue Address字段;
- 若使能MSIX,选择vector,用于queue event触发中断, 写Queue Vector....
Descriptor Table: 指向guest和device收发数据的buffer,地址为物理地址,描述符可以为链表,每个描述符支持buffer是只读还是只写,链表中既可以存在只读的buffer也可以存在只写的bufer。 描述符的数量由Queue Size指定。如果协商了VIRTIO_F_INDIRECT_DESC feature则可以使用Indirect Descriptors来增加ring的容量。
Available Ring: 指向提供给设备的描述符,指向描述符链表的head。它仅由Driver写入并由Device读取,Device获取Descriptor后,Descriptor对应的缓冲区可能是可读的也可能是可写的,可读的用于Driver发送数据,可写的用于接收数据。
flag字段目前是0或1,1-表示设备从Available Ring消耗描述符的时候不需要产生中断。Guest也可以延时中断,直到一个由used ring中的“idx”已经累加到used_event指定Index值时才会产生kick。 idx域指示下一个描述符entry的位置(ring size的取模,因为ring是循环结构),idx从0开始增加。available ring结构:
Used Ring: 是为了设备处理完成buffer数据后返还buffer用的。flag字段用来指示当guest添加buffer时是否需要通知,avail_event也可以延迟,直到idx累加到avail_event时才通知。每个enty由描述符链表的head entry和写入buffer的总字节数组成。used ring结构:
Device Operation
包含两部分:提供新buffer给设备,处理used buffer。
以virtio network device为例,有两个queue: 发送queue和接收queue。驱动添加outgoing packet(只读)给发送queue,用完后释放它们。相似地,incoming buffers(只写)添加到接收queue,用完后处理。
以下内容摘自 Virtio原理简介 | Lauren·weblog
当Driver想要向设备发送数据时,它会填充Descriptor Table中的一项(或将几项链接在一起),并将描述符索引写入Available Ring中,然后它通知Device,当Device完成后,它将描述符索引写入Used Ring中并发送中断。(详见SPEC 2.4)
Driver将sk_buffer填充进scatterlist table中(只是设置地址没有数据搬移),然后通过计算得到GPA并将GPA写入Descriptor Table中,同时将Desc chain的head记录到Available Ring中,然后通过PIO的方式通知Device,Device发包并更新Used Ring。
The used ring is where the device returns buffers once it is done with them: it is only written to by the device,and read by the driver.简单来说,Used Ring的作用就是Device使用完Descriptor之后,将Descriptor放入这里,通知Driver回收。