VIRTIO0.95与1.1控制面协议简析

virtio最初的设计目的就是在guest和host之间建立一个通道,达到更高效的数据传输。传统的全虚拟化实现方式,guest不会感知,依赖于qemu的模拟操作,但是guest每次向设备写入数据都需要做一次VM-exit,效率极低;virtio称为半虚拟化的方式,guest是会感知的,因为他需要专门的内核驱动(前端VIRTIO驱动),而不是原本的网卡驱动。

在guest侧,virtio模拟的设备显示出来是virtio-net/virtio-blk这样的设备类型。

所以本质上,VIRTIO就是一种协议,目的就是令guest上的virtio前端驱动和qemu里的virtio后端通过一种方式高效的通信。而共享内存就是通信数据的通道,一般称为数据面,就是virtqueue,承载了guest和host之间的数据传输;而在数据传输之前,识别virtio设备、识别上层的virtio-net/blcok设备、协商设备属性、建立共享内存等一系列的动作,称为控制面。对于virtio-pci的传输层来说,控制面的数据通道利用了pcie设备的config空间和bar空间。

VIRTIO主要在使用的有0.95、1.0和1.1版本。从VIRTIO0.95到VIRTIO1.0,config空间和bar空间的拓扑结构有所不同,增加了一些capability和feature bit,控制面有了很大的改动。VIRTIO1.1主要是在VIRTIO1.0的基础上引入了packed queue,当然也增加了一些feature bit。

1、VIRTIO0.95

首先说明,在spec和virtio驱动代码中,时常有legacy和modern的用法,virtio0.95的协议称为legacy模式,1.0之后的协议称为modern模式。

1、VENDOR_ID和DEVICE_ID

1)传统模式,DEVICE_ID采用0x1000/0x1001,分别表示net和block设备;traditional模式必须提供subsystem_device_id为0/1(分别表示net和block设备)。

2)非传统模式,DEVICE_ID采用0x1040/0x1041,分别表示net和block设备;不需要提供subsystem_device_id。

两种模式都是支持的,向下兼容。

2、配置信息——BAR0

对于VIRTIO设备,legacy模式默认使用BAR0作为VIRTIO的配置寄存器存储区域。所以驱动和设备都按协议操作BAR0的相应位置。 整个拓扑结构如下:

对于VIRTIO层的配置,包括以下内容(代码来源于qemu,内核有相同的定义):

/* A 32-bit r/o bitmask of the features supported by the host */
#define VIRTIO_PCI_HOST_FEATURES	0
/* A 32-bit r/w bitmask of features activated by the guest */
#define VIRTIO_PCI_GUEST_FEATURES	4
/* A 32-bit r/w PFN for the currently selected queue */
#define VIRTIO_PCI_QUEUE_PFN		8
/* A 16-bit r/o queue size for the currently selected queue */
#define VIRTIO_PCI_QUEUE_NUM		12
/* A 16-bit r/w queue selector */
#define VIRTIO_PCI_QUEUE_SEL		14
/* A 16-bit r/w queue notifier */
#define VIRTIO_PCI_QUEUE_NOTIFY		16
/* An 8-bit device status register.  */
#define VIRTIO_PCI_STATUS		18
/* An 8-bit r/o interrupt status register.  Reading the value will return the
 * current contents of the ISR and will also clear it.  This is effectively
 * a read-and-acknowledge. */
#define VIRTIO_PCI_ISR			19
/* MSI-X registers: only enabled if MSI-X is enabled. */
/* A 16-bit vector for configuration changes. */
#define VIRTIO_MSI_CONFIG_VECTOR        20
/* A 16-bit vector for selected queue notifications. */
#define VIRTIO_MSI_QUEUE_VECTOR         22

所以这是一片连续的地址空间,如果使能了MSI-X中断,才会有VIRTIO_MSI_CONFIG/QUEUE_VECTOR两个字段,则配置空间有24字节长度;如果没有MSI-X中断,则配置空间有20字节长度。

在通用的配置区间之后,还会有不同的net/block设备相关的配置信息,比如net设备和block设备。

net设备的配置区间如下,比较典型的是mac地址、mtu等,guest从这个字段获取mac地址和设备的最大mtu信息。

struct virtio_net_config {
	/* The config defining mac address (if VIRTIO_NET_F_MAC) */
	__u8 mac[ETH_ALEN];
	/* See VIRTIO_NET_F_STATUS and VIRTIO_NET_S_* above */
	__virtio16 status;
	/* Maximum number of each of transmit and receive queues;
	 * see VIRTIO_NET_F_MQ and VIRTIO_NET_CTRL_MQ.
	 * Legal values are between 1 and 0x8000
	 */
	__virtio16 max_virtqueue_pairs;
	/* Default maximum transmit unit advice */
	__virtio16 mtu;
	/*
	 * speed, in units of 1Mb. All values 0 to INT_MAX are legal.
	 * Any other value stands for unknown.
	 */
	__le32 speed;

	__u8 duplex;
	/* maximum size of RSS key */
	__u8 rss_max_key_size;
	/* maximum number of indirection table entries */
	__le16 rss_max_indirection_table_length;
	/* bitmask of supported VIRTIO_NET_RSS_HASH_ types */
	__le32 supported_hash_types;
} __attribute__((packed));

block设备的配置空间如下,比较典型的是capacity、size等,guest从这个空间获取磁盘的容量、块大小等。


struct virtio_blk_config {
	/* The capacity (in 512-byte sectors). */
	__virtio64 capacity;
	/* The maximum segment size (if VIRTIO_BLK_F_SIZE_MAX) */
	__virtio32 size_max;
	/* The maximum number of segments (if VIRTIO_BLK_F_SEG_MAX) */
	__virtio32 seg_max;
	/* geometry of the device (if VIRTIO_BLK_F_GEOMETRY) */
	struct virtio_blk_geometry {
		__virtio16 cylinders;
		__u8 heads;
		__u8 sectors;
	} geometry;

	/* block size of device (if VIRTIO_BLK_F_BLK_SIZE) */
	__virtio32 blk_size;

	/* number of vqs, only available when VIRTIO_BLK_F_MQ is set */
	__virtio16 num_queues;

    /*...... */
	__u8 unused1[3];
} __attribute__((packed));

3、VIRTIO通用配置信息解析

首先是32bit宽度的host feature和guest feature,具体的feature字段可以参见协议或者代码。这些feature bit是来协商具体的属性,host feature表示设备支持的feature,guest前端驱动读取该字段记为guest_feature,然后结合前端支持的driver_feature,两者相与生成共同支持的feature,写回到设备的guest feature;guest feature才是最终协商好的;比如host feature的VIRTIO_F_VERSION_1置位,表示设备使用virtio1.0及以上的协议;VIRTIO_NET_F_MAC表示设置支持修改MAC地址。

pci status字段也是很重要的字段,用于控制面的状态配置,比较重要的是VIRTIO_DRIVER_S_OK这个bit置位的时候,表示virtio控制面协商完成,这时候设备需要做好准备,host要启动数据面的消息传输了。另外,status字段写入0是一个reset信号,这时候设备需要reset队列的idx指针。

然后就是virtqueue初始化需要的queue_sel、queue_num、queue_pfn字段,实际的使用流程,guest首先写queue_sel,这个字段是用来说明后续的操作针对哪个队列;然后操作queue_num/queue_pfn,分别读取queue_sel指定队列的size、写入指定队列vring的起始地址gpa。

queue_notify字段用于队列的中断通知,方向是主机向设备发送中断,写入的数值指明队列index。

msi_config_vector和msi_queue_vector用于设备向主机方向的中断,主机写入的数值表示相应中断在设备所有msix中断向量中的索引,只有使能了msi-x中断的设备才有这个两个字段。msi_config_vector是config changed中断的索引,通知设备config信息变化,比如net设备mac地址变化,block设备容量修改;msi_queue_vector字段是每个队列的中断向量,使用queue_select和msi_queue_vector配合写入区分不同的队列。

对于使能了msix中断的设备,默认是分配(队列个数+1)个中断向量。因为某些设备队列个数比较多,所以host首先判断最大支持的msix中断个数,如果msix_max >= (队列个数+1),则0号中断配置给config changed中断,第(1——qnum)个中断分别配置给n个队列;msix_max < (队列个数+1),则分配2个中断向量,0号中断配置给config changed中断,n个队列全都配置1号中断,也就是队列的中断时共享的。

2、VIRTIO1.1

VIRTIO1.0+协议的控制数据拓扑结构与VIRTIO0.95有很大不同,所以有了legacy和modern的区别。modern模式同样是利用pcie传输层的config空间和bar空间,但它的数据拓扑结构要更加复杂,而且使用了pcie的capability,capablity中并不存储真实的配置数据,而是提供指向配置数据的指针信息(bar index及offset)。

1、设备ID

UEST如何识别VIRTIO设备以及具体的VIRTIO-NET或者VIRTIO-BLK设备呢,是通过PCI设备config空间的VENDOR_ID和DEVICE_ID来实现的,对于virtio-pci设备,VENDOR_ID采用0x1af4,modern模式中,DEVICE_ID采用0x1000/0x1001+0x40,分别表示net和block设备。

2、配置信息格式

modern模式一共开放了4种类型的通用配置信息,每种配置信息都对应一个capabiility,如果设备不需要某种类型的配置信息,则不提供相应的capability即可。配置信息分别是:

1)common config,通用的配置信息,包括队列的初始化、feature bit、status,对应legacy模式下的common config,必须支持;

2)isr config,中断配置信息;

3)notify config,也是中断相关的配置信息,isr和notify至少需要支持一种;

4)device config,设备自有的配置信息,对应virtio0.95中提到的net/block设备配置信息,有些设备不需要单独的配置信息,则不提供相应的capability;

下面是qemu相关的实现代码,注册四种配置信息的mem_region并创建相应的capability。

virtio_pci_modern_mem_region_map(proxy, &proxy->common, &cap);
virtio_pci_modern_mem_region_map(proxy, &proxy->isr, &cap);
virtio_pci_modern_mem_region_map(proxy, &proxy->device, &cap);
virtio_pci_modern_mem_region_map(proxy, &proxy->notify, &notify.cap);

相应的capability和memory region的内存空间分布图如下,

 3、virtio common配置信息

截取QEMU里的common配置结构体,如下:

/* Fields in VIRTIO_PCI_CAP_COMMON_CFG: */
struct virtio_pci_common_cfg {
	/* About the whole device. */
	uint32_t device_feature_select;	/* read-write */
	uint32_t device_feature;		/* read-only */
	uint32_t guest_feature_select;	/* read-write */
	uint32_t guest_feature;		/* read-write */
	uint16_t msix_config;		/* read-write */
	uint16_t num_queues;		/* read-only */
	uint8_t device_status;		/* read-write */
	uint8_t config_generation;		/* read-only */

	/* About a specific virtqueue. */
	uint16_t queue_select;		/* read-write */
	uint16_t queue_size;		/* read-write, power of 2. */
	uint16_t queue_msix_vector;	/* read-write */
	uint16_t queue_enable;		/* read-write */
	uint16_t queue_notify_off;	/* read-only */
	uint32_t queue_desc_lo;		/* read-write */
	uint32_t queue_desc_hi;		/* read-write */
	uint32_t queue_avail_lo;		/* read-write */
	uint32_t queue_avail_hi;		/* read-write */
	uint32_t queue_used_lo;		/* read-write */
	uint32_t queue_used_hi;		/* read-write */
};

首先是feature bit的处理,因为modern模式新增了feature bit,所以feature bit扩展到了64位;应该是考虑到后续的扩展,可能有>64bit的feature需求,所以采用了类似于queue配置的方式,增加了device_feature_select和guest_feature_select字段,配合device_feature和guest_feature使用。

msix_config字段,同legacy模式的msix_config_vector;

num_queues表示现在最多支持多少个队列;

device_status字段同legacy模式;

config_generation字段,device每次config空间变化通知主机的时候,config_generation会同步自加1;

queue_msix_vector,同legacy模式;

队列ring空间的设置分为3个部分,descriptor、avail ring和used ring;

增加了queue_enable字段,用于使能队列;

queue_notify_off用于主机向设备发送中断,是结合notify配置信息使用的。在legacy模式下,guest向设备发送中断是向queue_notify字段写入队列index实现的。在modern模式下,是有专门的notify区间,通过notify capability定义这部分空间的地址;notify空间里对每个队列都对应一个elem,而queue_notify_off是表示当前队列对应elem的索引值,当guest向设备发送某个队列的中断时,就写入相应队列的elem空间。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值