Virtio文档阅读笔记(一)基础设备
备注
- 文档中的特殊说明比如驱动程序必须要干啥, 设备必须要干啥没总结了, 按着规矩来就好, 如果发现出问题了可以上文档里翻翻
- 有一些自认为多余的没加上来, 防止看的有点绕.
Virtio基本设施
每个virtio设备通过一个特定的方法被发现(具体为PCI, MMIO, 通道IO), 每个设备包含以下的部分
- 设备状态字段(Device status field)
- 特征位 (Feature bits)
- 通知 (Notification)
- 设备配置空间 (Device Configuration space)
- 一个或多个虚拟队列 (one or more virtqueues)
设备状态字段
包含该设备的状态:
- ACKNOWLEDGE(1): 操作系统发现了这个设备, 认为这是个有效的virtio设备
- DRIVER(2): 操作系统知道怎么驱动该设备
- FAILED(128): 由于某种原因, 操作系统放弃驱动该设备
- FEATURES_OK(8): 驱动程序确认了它理解的所有特征, 特征协商达成一致
- DRIVER_OK(4): 驱动程序加载完成, 可以驱动该设备了
- DEVICE_NEEDS_RESET(64): 设备出错, 需要重置
特征位
每个Virtio设备需提供他所支持的特征, 在设备初始化阶段, 驱动程序读取这个数据并且要告诉设备它接受的特征子集(设备支持的特征集子集), 重新进行特征协商(上面的这个过程)的唯一方法就是重置设备. 特征位具体意义如下:
- 0-23: 特定设备可以使用的特征位
- 24-37: 预留给队列和特征协商机制的特征位
- 38-…: 给未来其他用途
手册给了个例子: 对于一个网络设备来讲, 如果feature bit为0, 则代表这个设备支持包的checksum检验
通知
通知分为三种类型:
- 配置空间修改通知 (configuration change notification)
- 可用缓冲区通知 (available buffer notification)
- 已用缓冲区通知 (used buffer notification)
其中第一个和第三个由设备发出, 驱动程序接受, 配置空间修改通知代表设备配置空间被修改过了, 已用缓冲区通知代表该通知指定的一个virtqueue中的缓冲区可能已经被使用, 可用缓冲区通知代表该通知指定的一个virtqueue中的缓冲区可能可以使用. (这一部分理解有点绕..不一定是对的)
不同的通知的语义需要依赖于传输的实现以及其他方面, 大多数使用中断实现设备发送给驱动程序的通知.
设备配置空间
设备配置空间通常用于很少更改或初始化时需要设置的设备参数, 设备的特征位包含配置空间是否存在的bit, 可以通过在特征位的末尾添加新的bit来扩展配置空间, 设备配置空间对于多个byte的配置采用的是小端模式, 驱动程序不能假设大于32bit的读取是原子性的.
Virtio的大数据传输机制被称为虚拟队列(Virtqueue), 每个设备可以有零个或者多个虚拟队列. 每个队列占用2或多个4K的物理页, 驱动程序与设备之间通信的流程如下:
- 驱动程序通过向队列中添加可用缓冲区, 即向设备发送可用缓冲区通知
- 设备处理请求, 完成时向队列中添加已用缓冲区, 即向驱动程序发送已用缓冲区通知
每个虚拟队列包含最多三个部分:
- 描述符区域, 用于描述缓冲
- 驱动程序区域, 驱动程序提供给设备的额外数据
- 设备区域, 设备提供给驱动程序的额外数据
一共支持两种类型的虚拟队列, 分别为Split Virtqueue以及 Packed Virtqueue
Split Virtqueue
这个为1.0版本以及更早版本支持的的虚拟队列, 它将虚拟队列拆分成不同的部分, 每个部分要么驱动程序可写, 要么设备可写, 不能同时两边可写, 如果缓冲区被标记为可用或者已被使用, 则需要更新一个部分中的多个部分和/或者位置. 每个队列包含一个16-bit大小的队列大小参数, 表示队列的总长度, 每个虚拟队列包含以下三个部分:
- 描述符表(Descriptor Table): IO传输请求信息(描述符)的数组,每个描述符都是对某buffer 的address/length描述。buffer中包含IO传输请求的命令/数据/返回结果等。
- 可用环(Available Ring): 记录了描述符表中被驱动程序更新的描述符的索引集合,需要后端的设备进行读取并完成相关I/O操作
- 已用环(Used Ring): 记录了描述符表中被设备更新的描述符的索引集合,需要前端的驱动程序进行读取并完成对I/O操作结果的响应
每个部分都由连续的物理内存块组成, 并且有不同的对齐需求, 驱动程序必须保证每一个部分的第一个byte的物理地址是下述表中对齐值的倍数:
虚拟队列部分 | 对齐(bytes) | 大小 |
---|---|---|
描述符表 | 16 | 16*队列大小 |
可用环 | 2 | (6+2)*队列大小 |
已用环 | 4 | (6+8)*队列大小 |
队列大小代表的是支持的最大缓冲区数量, 并且总是2^n, 最大的大小为32768(2^15). 这三者使用方法如下: 当驱动程序需要发送一个缓冲区到设备时, 它会填充描述符表中的一个插槽, 并将描述符索引写入可用环中, 然后通知设备. 当设备处理完缓冲区内容时, 设备将描述符索引写入已用环, 并发送已用缓冲区通知.
描述符表
描述符表用来存指向IO传输请求的信息, 即是驱动程序与设备进行交互的缓冲区, 每一个描述符由以下部分组成:
- addr 小端64bit, 存储buffer的物理地址
- len 小端32bit, 存储buffer的长度
- flag 小端16bit, 存储为flag, 具体有(括弧中为值, 应该转化为bit, 比如4代表是bit2(0起始)):
- NEXT(1): 有下一个描述符表
- WRITE(2): 是否为可写, 不然就为可读(可写或可读,不能两者都有)
- INDIRECT(4): 这个buffer包含一列表的buffer描述符
- next 小端16bit, 存储下一个描述符表的索引, 如果flag为next
buffer所在的物理地址空间需要操作系统在初始化或者运行时分配好, 后续由驱动程序在里面填写IO传输相关的命令/数据, 或者是设备返回IO操作的结果. 多个描述符 (IO操作命令, IO操作数据块, IO操作的返回结果)形成的描述符链可以表示一个完整的IO操作请求.
INDIRECT代表的意思是buffer中存的是一堆描述符表索引, addr同样代表的是该addr的地址, len同样是长度, 只不过里面的内容为(len/16)个描述符索引.
可用环
每个可用环由以下部分组成:
- flags 小端16bit, 包含一个NO_INTERRUPT bit(bit0)
- idx 小端16bit
- ring 小端16bit, 总共有queue_size个ring
- used_event 小端16bit(只有当VIRTOIO_F_EVNET_IDX启用时才会有,特征EVENT_IDX)
驱动程序用可用环来输入缓存到设备中, 每一个ring代表一个描述符的头, 只可以被驱动程序写入, 被设备读出, idx代表驱动程序将在环中放置下一个描述符条目的位置(队列大小的模), 这一位从0开始, 逐步增加 (相当于一个环形队列,尾部后又跳到头部)
看手册中, used_event使用方法为:
- 没有EVENT_IDX, 则flag必须为0或者1(启用或者不启用中断, 在这里又称为通知notification), 如果为1, 则不启用中断, 如果为0, 则启动中断
- 有EVENT_IDX, 则flag可以忽略, 驱动必须要设置flag为0, 之后驱动用used_event来告诉设备是否要通知(中断), 如果used_event等于idx, 则设备会发送通知(中断), 如果不相等, 则不会发送中断
已用环
每个已用环由以下部分组成:
- flags 小端16bit, 包含一个NO_NOTIFY(bit0)
- idx 小端16bit
- ring struct virtq_used_elem 内部包含一个小端32bit的id, 小端32bit的len, 分别代表已用描述符索引的起始位置以及描述符链的总长度, 采用32bit的id文档中说明是因为填充原因, 总共有queue_size个ring
- avail_event 小端16bit, 只有当EVNET_IDX特征开启时才会有
其中flags和idx以及avail_event和可用环差不多功能, ring部分将16bit扩展成了64bit, 包含一个id(跟可用环的ring差不多意思)以及一个len(代表buffer大小), 文档中对于len的描述为, 方便驱动程序将要写入的位置清0, 防止数据泄露, 比如上一个程序在这里写了些数据, 有一些没有清零直接设为了buffer内容.
对于Virtqueue这一部分文档中给了似乎是linux的代码, 链接.
Packed Virtqueue
这一部分等有时间了写一下
设备初始化以及操作
设备初始化
驱动程序要遵循以下的步骤初始化一个设备:
- 重置设备
- 设置ACKNOWLEDGE状态
- 设置DRIVER状态
- 读取设备特征bit, 写入操作系统支持的特征bit, 在这一步驱动可能要读取(不能写入) 设备特定的配置空间区查看驱动自己能不能支持这个设备
- 设置FEATURES_OK, 这一步之后, 驱动程序不能接受新的特征bit
- 一直重复读取设备状态去确保FEATURES_OK被设置成功了, 要不然设备可能不能支驱动程序设置的特征(很简单的意思, 问他接受不接受)
- 进行特定设备的配置, 包括发现设备的virtqueue, 读取或者写入设备的Virtio配置空间
- 设置DRIVER_OK状态, 这时候代表设备已经可以使用了
如果任何步骤除了不可恢复的问题, 则驱动程序需要设置FAILED状态, 代表驱动放弃了这个设备, 驱动不能发送可用缓冲区通知直到状态为DRIVER_OK.
设备操作
操作设备时, 设备配置空间中的每个字段可以由驱动程序或者设备更改, 当设备更改配置时, 会以通知的形式告诉驱动程序. 设备通过设置DEVICE_NEEDS_RESET来触发配置空间修改通知