设备的专属配置此配置空间包含了虚拟设备特殊的一些配置信息,可由 guest 读写。
比如网络设备含有一个 VIRTIO_NET_F_MAC 特征位,表明 host 想要设备包含一个特定的 MAC 地址,相应的设备专属配置空间中存有此 MAC 地址。
这种专属的配置空间和特征位的使用可以实现设备特性功能的扩展。
virtio 设备配置的操作针对 virtio 设备配置的操作主要包括四个部分——读写特征位,读写配置空间,读写状态位,重启设备,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| struct virtio_config_ops {
void (*get)(struct virtio_device *vdev, unsigned offset,void *buf, unsigned len);
void (*set)(struct virtio_device *vdev, unsigned offset, const void *buf, unsigned len);
u8 (*get_status)(struct virtio_device *vdev);
void (*set_status)(struct virtio_device *vdev, u8 status);
void (*reset)(struct virtio_device *vdev);
int (*find_vqs)(struct virtio_device *, unsigned nvqs,
struct virtqueue *vqs[],
vq_callback_t *callbacks[],
const char *names[]);
void (*del_vqs)(struct virtio_device *);
u32 (*get_features)(struct virtio_device *vdev);
void (*finalize_features)(struct virtio_device *vdev);
const char *(*bus_name)(struct virtio_device *vdev);
};
|
get():读取某配置域的值
set():设置写入某配置域
get_status():读取状态位
set_status():写入状态位
reset():重启设备主要是清除状态位并重新配置,还有清除掉所有的中断及其回调。当然也可以用于 guest 恢复驱动。
get_features():读取 feature bits
finalize_features():确认最终使用的特征并写入 Guest 特征位
Virtqueue每个设备拥有多个 virtqueue 用于大块数据的传输。virtqueue 是一个简单的队列,guest 把 buffers 插入其中,每个 buffer 都是一个分散-聚集数组。驱动调用 find_vqs()来创建一个与 queue 关联的结构体。virtqueue 的数目根据设备的不同而不同,比如 block 设备有一个 virtqueue,network 设备有 2 个 virtqueue,一个用于发送数据包,一个用于接收数据包。Balloon 设备有 3 个 virtqueue.
针对 virtqueue 的操作包括:
1)
1
2
3
4
5
6
7
| .int virtqueue_add_buf(
struct virtqueue *_vq,
struct scatterlist sg[],
unsigned int out,
unsigned int in,
void *data,
gfp_t gfp)
|
add_buf()用于向 queue 中添加一个新的 buffer,参数 data 是一个非空的令牌,用于识别 buffer,当 buffer 内容被消耗后,data 会返回。
2).virtqueue_kick():
Guest 通知 host 单个或者多个 buffer 已经添加到 queue 中,调用 virtqueue_notify(),notify 函数会向 queue notify(VIRTIO_PCI_QUEUE_NOTIFY)寄存器写入 queue index 来通知 host。
3).void *virtqueue_get_buf(struct virtqueue *_vq, unsigned int *len)
返回使用过的 buffer,len 为写入到 buffer 中数据的长度。获取数据,释放 buffer,更新 vring 描述符表格中的 index。
4).virtqueue_disable_cb()
示意 guest 不再需要再知道一个 buffer 已经使用了,也就是关闭 device 的中断。驱动会在初始化时注册一个回调函数,disable_cb()通常在这个 virtqueue 回调函数中使用,用于关闭再次的回调发生。
5).virtqueue_enable_cb()
与 disable_cb()刚好相反,用于重新开启设备中断的上报。
Vringvirtio_ring 是 virtio 传输机制的实现,vring 引入 ring buffers 来作为我们数据传输的载体。
virtio_ring 包含 3 部分:
描述符数组(descriptor table)用于存储一些关联的描述符,每个描述符都是一个对 buffer 的描述,包含一个 address/length 的配对。
可用的 ring(available ring)用于 guest 端表示那些描述符链当前是可用的。
使用过的 ring(used ring)用于表示 Host 端表示那些描述符已经使用。
Ring 的数目必须是 2 的次幂。
描述符和描述符表格vring descriptor 用于指向 guest 使用的 buffer。
addr:guest 物理地址
len:buffer 的长度
flags:flags 的值含义包括:
- VRING_DESC_F_NEXT:用于表明当前 buffer 的下一个域是否有效,也间接表明当前 buffer 是否是 buffers list 的最后一个。
- VRING_DESC_F_WRITE:当前 buffer 是 read-only 还是 write-only。
- VRING_DESC_F_INDIRECT:表明这个 buffer 中包含一个 buffer 描述符的 list
next:所有的 buffers 通过 next 串联起来组成 descriptor table
多个 buffer 组成一个 list 由 descriptor table 指向这些 list。
约定俗成,每个 list 中,read-only buffers 放置在 write-only buffers 前面。
图 2.descriptor tableIndirect Descriptors有些设备可能需要同时完成大量数据传输的大量请求,设备 VIRTIO_RING_F_INDIRECT_DESC 特性能够满足这种需求。为了增加 ring 的容量,vring 可以指向一个可以处于内存中任何位置 indirect descriptors table,而这个 table 指向一组 vring descriptors,而这些 vring descriptor 分别指向一组 buffer list(如图所示)。当然 indirect descriptors table 中的 descriptor 不能再次指向 indirect descriptors table。单个 indirect descriptor table 可以包含 read-only 和 write-only 的 descriptors,带有 write-only flag 的 descriptor 会被忽略。
图 3.indirect decriptorsAvailable RingAvailable ring 指向 guest 提供给设备的描述符,它指向一个 descriptor 链表的头。Available ring 结构如下图所示。其中标识 flags 值为 0 或者 1,1 表明 Guest 不需要 device 使用完这些 descriptor 时上报中断。idx 指向我们下一个 descriptor 入口处,idx 从 0 开始,一直增加,使用时需要取模:
idx=idx&(vring.num-1)
图 4.available ring |