virtio协议1.0 -- PCIe 类型的 Virtio 设备

84 篇文章 6 订阅
40 篇文章 9 订阅

引子

  • virtio 可以使用不同类型的总线,这里讲解PCIe类型的virtio设备
  • virtio通常实现为PCIe类型
  • virtio可以是PCI设备也可以是PCIe设备
  • 设备
    • 设备暴露给客户机的接口需要符合PCI/PCIe规范

PCI 设备发现

  • vendor id:   0x1af4
  • device id:   0x1040 + ${Virtio Device ID},确定具体设备类型,Legacy的是 0x1000 到 0x103F
  • 设备
    • PCI Vendor ID 必须是 0x1AF4
    • device id:   0x1040 + ${Virtio Device ID}(从1开始),Legacy的是 0x1000 到 0x103F
  • 区别
    • device id:   0x1040 + ${Virtio Device ID}(从1开始),Legacy的是 0x1000 到 0x103F

PCI 设备布局

  • 通过IO 和 MMIO 的方式配置 Virtio PCI Capabilities 结构
  • device configuration regions 有不同的长度
  • 64-bit; 32-bit; 16-bit 都是按照小端序处理
  • 64-bit 作为2个32-bit处理, 高32-bit 后面是 低32-bit
  • 驱动
    • 8-bit 方式访问 8-bit域
    • 16-bit 方式对齐访问 16-bit域
    • 32-bit 方式对齐访问 32-bit域 和 对齐访问 64-bit域
    • 64-bit 域的高、低32-bit访问是独立进行的
  • 设备
    • 64-bit device configuration,设备必须支持驱动高、低32-bit 进行独立访问

Virtio PCI Capabilities 结构

  • virtio device configuration 布局结构组成
    • Common configuration
    • Notifications
    • ISR Status
    • Device-specific configuration (可选)
    • PCI configuration access
  • 结构访问方式
    • Base Address register (BAR) 映射
    • 访问 PCI configuration space 的 special VIRTIO_PCI_CAP_PCI_CFG 域
  • 结构的位置
    • PCI configuration 空间中的 capability 列表中的 vendor特定的 PCI capability 类型来指定的具体capability位置
    • virtio capability 结构使用小端序
    • 除了stated 其他所有的域都是只读的
    • virtio capability 定义,根据 cfg_type,结构后面可以跟 extra data
      • cap_vndr:0x09,标明是一个 vendor-specific capability
      • cap_next:连接到 PCI configuration space 的下一个capability
      • cap_len:capability 结构长度,包括 struct virtio_pci_cap 和 extra data,可能含有padding
      • cfg_type:标明了 结构的类型,其他值保留,每种结构可以出现2次,先出现的结构优于后出现的结构,如两个 PCI_CAP_NOTIFY_CFG
      • bar:0x0 到 0x5,指定该function使用 PCI Configuration Space 10h 处的哪个BAR,映射到 Memory 或 I/O Space
      • offset :结构相对于BAR指定地址的偏移量
      • length:结构长度
    • 驱动
      • 驱动必须忽略cfg_type为保留值的 vendor-specific capability 结构或 bar为保留值的 vendor-specific capability
      • 驱动应该使用最先匹配到的某类型的 virtio 结构
      • 驱动不能对capability结构进行写入
    • 设备

      • 如果同一个类型的capability结构出现多次,按照优先级排序
      • 如果有extra data,必须包含在 cap_len. 中,cap_len.中可能还有padding
  • Common configuration 结构布局
    • 在 VIRTIO_PCI_CAP_COMMON_CFG 类型 的capability的bar + offset指定位置
      • device_feature_select 选择那组device feature,Value 0x0 selects Feature Bits 0 to 31, 0x1 selects Feature Bits 32 to 63, etc.
      • device_feature 设备本组feature bit
      • driver_feature_select 选择哪组 driver feature
      • driver_feature 驱动在本组接受那些feature
      • config_msix_vector  Configuration Vector for MSI-X
      • num_queues 本设备支持的最大queue pair数
      • device_status 驱动写入的状态,写入0表示重启设备
      • config_generation 每次变更配置会变化,
      • queue_select 队列选择子
      • queue_size 队列深度,在reset设备时,支持驱动重新设定队列大小来调整内存使用量,设置为0意味着队列不可用
      • queue_msix_vector MSI-X.队列的MSI-X中断号
      • queue_enable 设定队列是否激活 1 - enabled; 0 - disabled.
      • queue_notify_off 计算队列的从notification结构开始地址的偏移量,作为偏移基数,后面需要乘以倍数因子
      • queue_desc DESC表的起始物理地址PA
      • queue_avail  Available Ring 的物理地址PA
      • queue_used Used Ring 的物理地址PA
      • 设备
        • offset必须是4字节对齐
        • 设备必须有至少一个common configuration capability
        • 设备必须在device_feature 中提供设备支持的特性bit,位置在 device_feature_select
          * 32 上(可以定义超过63的feature bit)
        • 任何同驱动协商完成的feature,设备必须提供功能,驱动确认的特性在driver_feature中,位于driver_feature_select ∗ 32  上,且必须是 device_feature 的子集。设备对于写入的非法feature bit进行忽略,且读取的时候自动设置为0
        • 设备通过config_generationl来保证device-specific configuration值读取的是最新的变化的值
        • 当 device_status 被写 0 的时候,设备必须重启,并且在完成reset后,读取值为0
        • 设备reset的时候,设备保证 queue_enable 为0
        • 如果 queue_select 是无效值,读取队列的queue_size必须返回0
      • 驱动
        • 驱动不能写入 device_feature, num_queues, config_generation 和 queue_notify_off
        • 写入的queue_size必须是2的幂
        • 在写入 queue_enable 前,驱动必须完成队列的配置
        • 对 device_status 写0, 驱动必须等到从 device_status 读取返回0后,才可以对设备进行再次初始化
  • Notification structure 结构布局
    • 位于 VIRTIO_PCI_CAP_NOTIFY_CFG capability,结构如下
    • notify_off_multiplier 乘以队列的 queue_notify_off 得到队列的NOTIFCATION地址在BAR开始的偏移量
    • cap.offset 和 notify_off_multiplier 来自于 notification capability 结构
    • queue_notify_off 来自于  common configuration 结构
    • 设备
      • 设备必须有至少一个 notification capability
      • cap.offset必须是2字节对齐的
      • 设备必须让notify_off_multiplier是2的幂或0
      • cap.length 至少是2,且必须足够大以便支持所有队列的notification
      • cap.length 必须满足:
  • ISR status capability 布局
    • 位于 VIRTIO_PCI_CAP_ISR_CFG capability,至少1个字节,8bit ISR status 用来 INT#x 处理使用,中断处理状态
    • offset 没有对齐要求
    • 寄存器是读清类型的,读 ISR status 之后,设备回 de-assert 中断
    • 通过ISR的bit位,可以区分是 Queue interrupt 还是 device configuration change interrupt
    • 设备
      • 设备至少含有一个 VIRTIO_PCI_CAP_ISR_CFG capability
      • 设备必须在发送 Device Configuration Interrupt 之前,设置 ISR status 的 Device Configuration Interrupt bit
      • 如果禁用MSI-X功能,设备必须在发送queue中断之前,设置 ISR status 的 Queue Interrupt bit
      • 如果禁用MSI-X功能,则设备必须将设备PCI Configuration Header 中 PCI Status 寄存器中的Interrupt Status bit 设置为设备 ISR status 中所有位的逻辑或。然后,除非根据标准PCI规则屏蔽,否则设备将ISR status INT#x中断
      • 驱动读取 ISR status 后,设备必须进行清零动作
    • 驱动
      • 如果 MSI-X capability 被使能了,驱动不应该通过 ISR status 来进行 Queue Interrupt 的探测
  • Device-specific configuration
    • 设备必须支持至少一个 VIRTIO_PCI_CAP_DEVICE_CFG capability,记录设备特定的
    • 设备
      • offset必须是4字节对齐
  • PCI configuration access capability
    • VIRTIO_PCI_CAP_PCI_CFG capability 为common configuration, notification, ISR 和device-specific configuration 区域创建了一种替代(可能是次优)访问方法
    • cap.bar, cap.length, cap.offset 和 pci_cfg_data 是驱动可读写(RW)的
    • 为了访问一个设备区域,驱动程序按照如下方式写入这个 capability
      结构(即在PCI配置空间内)
      • 驱动写入cap.bar来设置要访问的BAR
      • 驱动写入 cap.length 来设置访问长度,1,2或4
      • 驱动写入 cap.offset 来设置访问的offset
      • 通过pci_cfg_dataj将会返回给驱动访问结果
    • 设备
    • 设备至少支持一个VIRTIO_PCI_CAP_PCI_CFG capability
    • 如果发现驱动通过 pci_cfg_data 写访问,那么需要在指定内存位置,将pci_cfg_data 的值保存到内存中
    • 如果发现驱动通过pci_cfg_data 读访问,那么需要将指定为内存位置的内容,复制到pci_cfg_data中,长度为 cap.length
    • 驱动
      • 驱动必须保证 cap.offset 是 cap.length 的倍数,访问必须对齐
      • 驱动访问的内容,必须是VIRTIO_PCI_CAP_PCI_CFG以外的某个Virtio Structure PCI Capability ,并且不能超过Capability指定的范围
  • 区别
    • legacy 驱动必须通过BAR0中的configuration结构提供配置
    • 使用 legacy 接口时,驱动程序可以使用任何宽度访问访问特定于设备的配置区域,legacy 设备必须向驱动提供与使用“natural”访问方法访问时相同的结果(32位字段的32位访问等)
    • 虽然virtio common configuration structure 是PCI(即little)endian,但在使用 legacy 接口时,设备的配置区域访问使用的是guest的 native endian 进行的编码
    • 当使用legacy接口,virtio common configuration structure 如下
    • 如果使能MSI-X,后面紧跟着如下域
    • 使能了MSI-X时,device-specific configuration 从vvirtio common configuration structure
      (BLK容量,网络MAC等)的字节偏移量24开始。未启用MSI-X时,device-specific configuration 从字节偏移量20开始。
    • device-specific configuration space 在通用header后面
    • 当使用 legacy 接口访问device-specific configuration 空间时,legacy 驱动程序必须以紧跟常规标头的偏移量访问 device-specific configuration 空间
    • 当使用 legacy 接口时, legacy  设备必须在常规头之后的偏移量处显示 device-specific configuration 空间(如果有)
    • 只有 Feature Bits 0到31可通过 legacy 接口访问
    • legacy 设备没有 config_generation 域
    • 设备
    • 非兼容设备依靠如下方式防止legacy驱动来驱动设备
      • 在I/O BAR上体现BAR0
      • 在写入BAR0 offset 18(Device Status)的时候,返回0,并且其他也都是0x0,且忽略写操作

PCI 设备初始化和设备操作

  • 设备初始化
    • 定义了设备初始化需要执行的步骤
    • Virtio Device Configuration Layout探测
      • 驱动扫描 PCI capability 链表,确定使用Virtio Structure PCI capability 的 virtio 配置布局
      • 区别
      • Legacy 驱动没有layout探测这一步,假设了BAR0结构是固定的
      • Legacy 设备没有 Virtio PCI Capability list
      • Legacy 使用IIO读写访问 BAR0 的IO空间
      • 兼容驱动会查看是否有 Virtio PCI Capabilities list,如果没有,那么回落到使用 legacy 接口
    • MSI-X 中断向量配置
      • 设备使能了 MSI-X capability,config_msix_vector 和 queue_msix_vector用来配置映射 configuration change interrupt 和 queue interrupts,且不适用 ISR Status
      • 写入MSI-X号0到0x7FF 到 config_msix_vector/queue_msix_vector 将配置更改/所选队列中断号和configuration change 中断号
      • 要禁中断,驱动程序将通过写入NO_VECTOR 来取消映射
      • 分配MSI VECTOR,是需要消耗设备资源的,可能失败
      • 设备
        • 设备必须支持2~0x800数量的 MSI-X 中断向量
        • 报告中断向量数量到PCI MSI-X Capability 的 Table Size 中
        • MSIX中断向量数是依赖于系统的,选择最优数量
        • 设备必须支持映射任何类型的 event 到任意的有效vector(0 到 MSI-X Table Size
          ),也必须支持unmap任何的event类型
        • 在读取config_msix_vector/queue_msix_vector时,设备必须能返回给定event的vector(NO_VECTOR 表示没有映射)
        • 在设备reset时候,所有的 queue event 和 configuration change event,必须保持中断未映射状态
        • 除非设备无法满足映射请求,否则设备不应导致事件到向量的映射失败
        • 如果event中断映射失败,当读取 config_msix_vector/queue_msix_vector 时候,设备必须返回 NO_VECTOR 
      • 驱动
        • 驱动必须支持设备的中断向量数量是 0 到 0x7FF的 MSI-X Table Size
        • 如果设备只支持一个MSIX中断,驱动回落为使用INT#x中断
        • 驱动读取Table Size,并解释为设备建议的使用的MSI-X vectors数量
        • 驱动不能映射MSI-X Capability的Table Size 之外的vector到特定event
        • 在映射中断vector到event后,驱动必须通过驱动来进行设定是否成功的校验,如果失败,会读取到NO_VECTOR
        • 如果映射中断失败,那么驱动将试着使用更少的VECTOR或者关闭MSIX使用INT#X或者返回失败
    • 队列配置
      • 设备可以有0到多个 virtqueue 进行数据传输,驱动需要对队列进行设定
      • 驱动会对每个 virtqueue 进行如下配置
        • 写 virtqueue index(从 0 开始) 到 queue_select
        • 从 queue_size 读取 virtqueue 深度,0 代表 virtqueue 不存在
        • 驱动可能使用更小的 virtqueue 深度,并将新的值写入queue_size
        • 为 virtqueue 分配并用0x0初始化 Descriptor Table ,Available Ring 和 Used Ring,使用的是连续的物理内存
        • 如果使能了MSI-X,选择一个vector映射到 virtqueue 的中断事件,将 MSI-X Table entry number 写入 queue_msix_vector,然后再读取 queue_msix_vector,查看是否设定成功,如果设置错误,读取返回 NO_VECTOR
    • 区别
      • 当使用 legacy 接口时,queue layout 必须按照下面的排布,且必须是4K对齐的
      • 驱动把 4K 对齐的PA写入 Queue Address 域
      • 并且没有机制来协商队列深度 queue size,设备说了算
  • KICK 设备
    • 队列通过写入16bit的virtqueue 的index到 Queue Notify 来通知queue
  • 设备发送队列中断
    • 发送中断的流程
      • 如果禁用MSIX
        • 设定ISR Status的最低位
        • 发送PCI中断到设备
      • 如果使能MSIX
        • 如果queue_msix_vector不是 NO_VECTOR,那么发送 MSI-X interrupt message,queue_msix_vector 是 MSI-X Table entry number
    • 设备
      • 如果启用了MSI-X,并且queue_msix_vector 不是NO_VECTOR,则设备不会为该virtqueue传递中断
  • 设备发送Configuration Changes中断
    • 某些virtio PCI设备可以更改设备配置状态,这反映在设备的device-specific configuration
      区域中,在这种情况下
    • 如果MSIX禁用
      • 设定 ISR Status 的低位的第二bit
      • 发送PCI中断
    • 如果MSIX使能
      • 如果 config_msix_vector 不是 NO_VECTOR, 发送 MSI-X interrupt message,config_msix_vector 中是 MSI-X Table entry number
    • 一个中断可能既代表队列virtqueue事件,又有  configuration space change事件
    • 设备
      • 如果MSIX使能,且config_msix_vector是NO_VECTOR,那么设备不能传递device configuration space change 事件
    • 驱动
      • 驱动必须能处理一个中断既代表队列virtqueue事件,又有  configuration space change事件
  • 驱动中断响应
    • 驱动中断处理一般流程
    • 如果没有使能 MSI-X
      • 读取 ISR Status,读取后设备会进行清零
      • 如果最低 bit 设定了,查看设备所有virtqueue的used ring,来查看处理哪个队列
      • 如果第二低 bit 设定了,需要重亲查看设备的配置空间
    • 如果使能了 MSI-X
      • 查看所有MAP到当前 MSI-X vector的队列virtqueue的 used ring,来查看需要处理哪个队列
      • 如果 MSI-X vector 等于 config_msix_vector,需要重亲查看设备的配置空间
  • 收包一个MSIX VECTOR 发包一个MSIX VECTOR
  • 5
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值