一、概述
virtio设备可以基于不同总线来实现,本文介绍基于pci实现的virtio-pci设备。以virtio-blk为例,首先介绍PCI配置空间内容,virtio-pci实现的硬件基础——capability,最后分析PIC设备的初始化以及virtio-pci设备的初始化。
再后面不详细介绍PCI太多的知识, 已virtio 相关为主。
二、PCI 配置空间
virtio设备作为pci设备,必须实现pci local bus spec规定的配置空间(最大256字节,现在有的最大是1K,看芯片自己设置, 只要前64字节是PCI标准)。前64字节是spec中定义好的,称预定义空间。其中前16字节对所有类型的PCI设备都相同,之后的空间格式因类型而不同,对前16字节空间,我称其为通用配置空间。
2.1 通用配置空间
通用配置空间如下图所示:
(1)vendor id
厂商ID,用来标识PCI设备出自哪个厂商。这里是0x1af4,来自Red Hat。
(2)device id
厂商下的产品ID。传统virtio-blk设备,这里是0x1001。
(3)revision id
设备版本ID。厂商决定是否使用,这里未使用。
(4)header type
pci设备类型。0x00表示普通设备、0x01表示pci bridge、0x02表示CardBus bridge。virtio是普通设备,这里是0x00。
通用配置空间即PCI配置空间前16字节中的以上4个地方用来识别virtio设备。
(5)command
command字段用来控制pci设备,打开某些功能的开关。对于virtio-blk设备来说,是(0x0507 = 0b01010111)。command的各字段含义如下图所示:
其中,低三位的含义如下:
- I/O Space位
如果PCI设备实现了IO空间,该字段用来控制是否接收总线上对IO空间的访问;如果PCI设备没有IO空间,该字段不可写。
- Memory Space位
如果PCI设备实现了内存空间,该字段用来控制是否接收总线上对内存空间的访问;如果PCI设备没有内存空间,该字段不可写。
- Bus Master位
控制PCI设备是否具有作为Master角色的权限。
(6)status
status字段用来记录pci设备的状态信息。对于virtio-blk设备,是(0x10 = 0b00010000)。status各字段含义如下图所示:
其中,Capabilities List位的含义如下:
- Capabilities List位
Capabilities List是pci规范定义的附加空间标志位,其意义是允许在pci设备配置空间之后加上额外的寄存器。这些寄存器由Capability List组织起来,用来实现特定的功能,附加空间在64字节配置空间之后,最大不能超过256字节。以virtio-blk设备为例,它标记了这个位,因此在virtio-blk设备的配置空间之后,还有一段空间用来实现virtio-blk的一些特有功能。此处,Capabilities List位为1表示Capabilities Pointer
字段(0x34)存放了附加寄存器组的起始地址。这里的地址表示附加空间在PCI设备空间内的偏移。
virtio-blk设备配置空间的内容可以通过lspci命令查看到,如下图所示:
2.2 virtio 配置空间
virtio-pci设备实现pci spec规定的通用配置空间后,设计了自己的配置空间,用来实现virtio-pci的功能。
上面已提到,PCI规范通过status
字段的capabilities list
位标记自己在64字节预定义配置空间之后有附加的寄存器组,capabilities pointer
字段会存放寄存器组链表的头部指针,这里的指针代表寄存器在配置空间内的偏移。如下图所示(仍然是上图中的信息):
pci spec中描述的capabilities list
格式如下:第1个字节存放capability ID,标识后面配置空间实现的是哪种capability,第2个字节存放下一个capability的地址。capability ID查阅参见pci spec3.0
virtio-blk实现的capability有两种:一种是MSI-X( Message Signaled Interrupts - Extension),ID为0x11;另一种是Vendor Specific,ID为0x9。后面一种capability设计目的就是让厂商实现自己的功能。virtio-blk的实现以此为基础。
virtio-pci根据自己的功能需求,设计了如下的capabilities布局:
上图中右侧是6个capability,其中5个用做描述virtio-pci的capability,1个用作描述MSI-X的capability。这里我们只介绍用作virtio-pci的capability。右侧描述了virtio-blk的capability
布局,左边是每个capability
指向的物理地址空间布局。
根据virtio spec的规范,要实现virtio-pci的capabilty,其布局应该如下:
- cap_vndr
表示capability类型。
- cap_next
表示下一个capability在pci配置空间的位置。
- cap_len
表示capability这个数据结构的长度。
- cfg_type
表示配置类型。cfg_type通过如下取值,将virtio-pci的capability又细分成几类:
2.3 virtio通用配置空间
capability中最核心的内容是virtio_pci_common_cfg,它是virtio前后端沟通的主要桥梁。common config分两部分:第一部分用于设备配置;第二部分用于virtqueue使用。virtio驱动初始化利用第一部分来和后端进行沟通协商,比如支持的特性(guest_feature),初始化时设备的状态(device_status),设备的virtqueue个数(num_queues)。第二部分用来实现前后端数据传输。后面会详细提到两部分在virtio初始化和数据传输中的作用。virtio_pci_common_cfg
数据结构如下:
三、pci 的枚举和配置
这部分百度上很多, 在之前的文章也写到了, 在这不赘述了。
注意三点就行:
- 枚举过程中访问pci配置空间寄存器,软件接口是往特定寄存器写东西,真正发起读写请求的,是host bridge,它在pci总线上传输的是command type为configuration wirte/read的transaction。配置完成后,软件对pci bar空间关联的内存进行读写,可以直接使用mov内存访问指令,这时候host bridge在pci总线上传输的是command type为IO Read/Write或者Memory Read/Write的transaction。
- pci设备被配置之后,bar寄存器中存放了关联的内存起始地址,这个地址是pci总线域的物理地址,不是cpu域的物理地址,虽然这两个值一样,但这只是x86这种架构的特殊情况,因为host bridge转换的时候是一一映射的,才造成这种假象。在别的架构上,比如power pc,cpu域的地址想要访问pci总线域的地址,需要通过host bridge进行地址转换才能进行,两种地址并不相同。
- pci设备被配置之后,虽然看上去软件可以像访问内存一样访问pci设备的bar的空间,但cpu真正访问的时候,是要把地址交给host bridge,让它去访问才可以。可以说,访问pci设备bar空间的不是cpu,而是host bridge。至始至终,cpu都不能直接管理pci设备的bar空间,它只能通过host bridge来间接管理。