这里描述符的是virito的通用基础组件,不依赖于总线类型,如PCI,MMIO, Channel IO 等
0. virtio基本基础组件
- Device status 域
- Feature bits
- Device Configuration space
- 一个或多个 virtqueues
1. Device status 域
驱动在发现和加载设备过程中,会经历一系列加载步骤,device status 就是在底层提示加载步骤的完成状态,底层设备通过这些状态值,知道需要完成的事情
- 状态
- ACKNOWLEDGE (1) 客户系统发现了设备,并且知道是一个virtio设备
- DRIVER (2) :客户系统找到了对应的驱动
- FAILED (128): 客户系统的驱动有问题,加载失败
- FEATURES_OK (8): 驱动已经取得了所有的features bit并协商完成了
- DRIVER_OK (4) :驱动安装完成
- DEVICE_NEEDS_RESET (64) :设备有问题且问题设备无法恢复
- 驱动
- 按照初始化步骤,不断设定device status域
- 不能清除,只能写入
- 设置了FAILED,在重新初始化前,必须对设备进行reset
- 不可以依赖DEVICE_NEEDS_RESET来处理in flight数据,应在在设备重启后,重新发送
- 设备
- 设备在reset的时候,device status域必须归零
- 在 DRIVER_OK 前,不能处理数据
- 设备进入错误状态的时候,必须设置 DEVICE_NEEDS_RESET 位,然后发送 device configuration change 中断
2. Feature Bits
设备提供所有支持的 features bit,在设备初始化期间,驱动读取 features bit 并告诉设备哪些 features 被接受,一旦协商完成,再需要协商的话,需要reset设备。通过 features bit 可以完成向后兼容,驱动可以选择最新能力的feature bit或者选择不接受没有实现的feature
- feature 位分配
- 0 to 23 特定类型设备的 feature bits
- 24 to 32 queue 的 feature bits
- 33 and above 保留.
- 有些feature bit 会表示设备有新的configuration space域
- 驱动
- 不能接受没有实现的特性的feature bits,包括关联的
- 如果设备提供的feature bits比较旧,驱动应切换为老模式,或者停止初始化
- 设备
- 不能提供没有实现的特性的feature bits,包括关联的
- 接受并校验驱动提供的feature bits的子集
- 当dirver写回feature bits后,设定FEATURES_OK状态
- 区别
- 设备 VIRTIO_F_VERSION_1 bit 没有,说明是 Legacy 设备
- 驱动不识别 VIRTIO_F_VERSION_1,说明是 Legacy driver,设备需要支持 legacy接口
3. 设备配置空间
Device configuration space 用作设定设备初始化参数,每个configuration域都是根据feature bits
可选的,未来扩展的话,会在尾部增加新的 configuration space,小端序且多字节的
configuration space 都有一个 generation count,每次读写都要校验这个generation,保证修改的内容被及时读取到
- 驱动
- 驱动不能假设对大于32bit的读取是原子的
- 读取 configuration space 必须检测到 feature bit 被设置了
- 只要设备的 configuration space 比协议规定的大,driver都要接收
- 驱动不能假设对大于32bit的读取是原子的
- 设备
- 在驱动设定FEATURES_OK前,设备允许读取任何的 device-specific configuration 域,包括后面可能驱动不接受的feature bit 对应的域
- Device Configuration Space 是小端序的
- 区别
- 字节序:legacy 认为 configuration space 和客户机保持一致的字节序,而不是PCI的小端序
- Device Configuration Space:Legacy 设备没有 configuration generation 域,在配置更新的时候,很可能发生race conditions,影响的有 BLK 的 block capacity 和 NET 的 mac,需要驱动反复读取,直到两次结果是一致的
4. Virtqueues
- 定义
- 数据传输机制是 virtqueue
- 每个设备有一个或多个Virtqueues
- 每个队列 16bit queue size说明队列深度,2的幂,最大32768
- virtqueue组成
- Descriptor 表
- Available Ring
- Used Ring
- virtqueue 地址
- 每部分都使用客户机连续的物理内存
- 每部分不同的对齐要求
- 地址算法
- 发送过程
- 驱动填写buffer到desc 表中的desc(可能成链)
- 驱动把(收个)desc index写入 available ring
- 驱动发送kick到设备
- 设备处理完buffer
- 设备把(收个) desc index 写入 used ring
- 设备发送中断
- 驱动
- 驱动需要保证地址是对齐的
- 区别
- legacy interface 的 virtqueue内存布局
- 三部分是连读的,占用两个或多个物理连续页面,中间靠padding对齐
- 必须读取qsize,并一次分配足够的内存
- virqueue 布局结构
- 三部分是连读的,占用两个或多个物理连续页面,中间靠padding对齐
- 字节序
- legacy interface 使用客户机的字节序
- legacy interface 的 virtqueue内存布局
- 消息帧格式
- 格式
- 12字节virtio头,后面跟着网络包
- 可以使用12字节desc+1514 字节 desc,也可以使用 1526 字节 desc包含头和报文,或者更多个desc
- 设备可以限制所有desc的总大小
- 设备
- 不能假设desc的存放方式
- 可限制desc链的长度
- 驱动
- 设备可写desc必须放在设备只读desc之后
- 使用限制的desc链长度
- 区别
- legacy interface 使用 VIRTIO_F_ANY_LAYOUT 特性来提示帧格式是任意的
- Descriptor 表
- desc 引用了buffer,addr是物理地址,通过next形成链
- desc flag 标志位表示是设备可读还是设备可写,链里面可以包含两种
- queue size 是desc表中 desc的数量,也是desc链的最大长度
- desc 的结构是 struct vring_desc
- 设备
- 不能写入只读desc
- 不能读取只写desc(调试或诊断使用)
- 驱动
- desc链总长度不能超过2^32字节
- desc链不许打环
- 间接desc表
- 处理数量多,消息大的请求
- VIRTIO_F_INDIRECT_DESC
- 扩展ring的能力,ind desc表存在内存任意处
- ring中desc 的flag设置 VIRTQ_DESC_F_INDIRECT,addr 内存指向 ind desc表,len为间接表字节总长度
- 第一个desc在间接表开始(idx = 0),下面通过desc链接,没有设置VIRTQ_DESC_F_NEXT 标志的desc就是结尾
- 一个间接表内可以包含只读、只写两种desc
- 设备
- ring中的间接表desc的VIRTQ_DESC_F_WRITEb标志会被忽略
- 有VIRTQ_DESC_F_INDIRECTb标志的desc后面可以成链(极其特殊)
- 驱动
- VIRTIO_F_INDIRECT_DESC特性协商了,才可以有 VIRTQ_DESC_F_INDIRECT类型的desc
- desc链最大长度是queue size
- VIRTQ_DESC_F_INDIRECT and VIRTQ_DESC_F_NEXT 不能同时设置
- Available Ring
- 格式
- 驱动给设备发包
- 每个ring元素是一个desc链的头
- 驱动写,设备读
- idx 是客户机本次生产位置的下一个位置(取模queue_size),从0 开始单向增长
- legacy中结构是 vring_avail,常量是 RING_AVAIL_F_NO_INTERRUPT而不是VIRTIO_F_EVENT_IDX,其他一样
- 格式
- Interrupt 抑制
- 没有协商 VIRTIO_F_EVENT_IDX,可以通过 available ring 的 flag 域来提示设备在返回used 时候不要发送中断,比较暴力
- 协商了 VIRTIO_F_EVENT_IDX ,通过 available ring 的 used_event 来告诉设备返回多少used 才发送一个中断
- 都不可靠,但是对性能优化有帮助
- 驱动
- 不支持 VIRTIO_F_EVENT_IDX没协商
- available ring->flag为0或1
- available ring->flag 为1表示设备返回used后不要发送中断
- 支持 VIRTIO_F_EVENT_IDX 协商
- available ring->flag 必须为0
- 可能使用 available ring-> used_event 通知设备生产used_idx到used_event值,才产生一个中断(此时 used_idx = used_event + 1)
- 不支持 VIRTIO_F_EVENT_IDX没协商
- 设备
- 不支持 VIRTIO_F_EVENT_IDX
- 忽略 available ring->used_event
- 写 desc idx 到 used ring后,flag 为1 不发送中断,flag为0发送中断
- 支持 VIRTIO_F_EVENT_IDX
- 忽略 available ring->flag
- 写 desc idx 到 used ring后,used ring -> idx(写入位置) == available ring->used_event,设备发送中断,否则不发送中断
- 不支持 VIRTIO_F_EVENT_IDX
- Used Ring
- 结构
- used ring 设备处理完buffer后返回给驱动使用
- 设备写,驱动读
- 每个ring元素是一个结构,id是描述符链的开始desc idx,len是buffer的总字节数
- idx 表示设备生产到used ring的哪个位置,从0开始,单向递增
- legacy 叫做 vring_used 和 vring_used_elem,使用常量 VRING_USED_F_NO_NOTIFY 而不是 VIRTIO_F_EVENT_IDX,其他一样
- 区别
- 驱动可以忽略len,设备也可以设置len为不同的值
- legacy interface忽略len值
- len必须和可写desc总长度相等(cq)
- 设备
- 必须先写len,再更新 used idx
- 设备必须从第一个可写的desc,写入len字节,写完后才可以更新 used idx
- 设备可能写入的字节多余len
- 驱动
- 驱动应该忽略超过len字节的数据内容
- 结构
- KICK 抑制
- 抑制对设备的KICK
- 操作 used ring 的flag 和 avail_event 的方式达到抑制的目的
- 驱动
- 当分配used ring时,必须初始化 used ring->flag 为0
- 不支持 VIRTIO_F_EVENT_IDX
- 必须忽略 used ring -> avail_event
- 在驱动向available ring写入 desc->idx 后
- used ring -> flag 为1, 不发送KICK
- used ring -> flag 为 0,发送KICK
- 支持 VIRTIO_F_EVENT_IDX
- 忽略 used ring -> flag
- 在驱动向available ring写入 desc->idx 后
- 如果 available ring->idx (写入位置) == used ring -> avail_event, 发送 KICK
- 不相等,不发送
- 不支持 VIRTIO_F_EVENT_IDX
- 当分配used ring时,必须初始化 used ring->flag 为0
- 设备
- 不支持 VIRTIO_F_EVENT_IDX
- 必须设置 used ring->flag 为 0 或 1
- used ring->flag 设置为1,表示不希望驱动发送KICK
- 支持 VIRTIO_F_EVENT_IDX
- 必须设置 used ring->flag 为0
- 可以设置 used ring -> avail_event 抑制驱动的KICK,直到驱动写入的 desc 位置 available ring->idx == used ring -> avail_event,才发送KICK(之后驱动会增加 available ring->idx++,此时 available ring->idx = used ring -> avail_event + 1)
- 不支持 VIRTIO_F_EVENT_IDX
- desc 引用了buffer,addr是物理地址,通过next形成链
- 格式