本文将结合virtio spec与linux 源码,深入解析下virtio中的kick操作。

Overview

本文将详细的阐述virtio中的kick操作。根据kick操作的发展历史,按照如下顺序去介绍:

  1. legacy device kick
  2. modern device kick
  3. VIRTIO_F_NOTIFICATION_DATA feature的kick

legacy device

1
2
3
4
5
6
7
8
/* the notify function used when creating a virt queue */
bool vp_notify(struct virtqueue *vq)
{
/* we write the queue's selector into the notification register to
* signal the other end */
iowrite16(vq->index, (void __iomem *)vq->priv);
return true;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// legacy device
static struct virtqueue *setup_vq(struct virtio_pci_device *vp_dev,
struct virtio_pci_vq_info *info,
unsigned int index,
void (*callback)(struct virtqueue *vq),
const char *name,
bool ctx,
u16 msix_vec)
{
...
/* create the vring */
vq = vring_create_virtqueue(index, num,
VIRTIO_PCI_VRING_ALIGN, &vp_dev->vdev,
true, false, ctx,
vp_notify, callback, name);
...
vq->priv = (void __force *)vp_dev->ldev.ioaddr + VIRTIO_PCI_QUEUE_NOTIFY;
...
}

kick寄存器位于bar0中的VIRTIO_PCI_QUEUE_NOTIFY位置;不同vq使用同一个kick寄存器地址,往kick寄存器写入vq的index,告诉virtio后端要处理哪个vq。

modern device

在设备实现中,一般会将queue_notify_off设置为vq index;也就是说,vp_modern_get_queue_notify_off的返回值,一般会与输入变量index相同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*
* vp_modern_get_queue_notify_off - get notification offset for a virtqueue
* @mdev: the modern virtio-pci device
* @index: the queue index
*
* Returns the notification offset for a virtqueue
*/
static u16 vp_modern_get_queue_notify_off(struct virtio_pci_modern_device *mdev,
u16 index)
{
vp_iowrite16(index, &mdev->common->queue_select);

return vp_ioread16(&mdev->common->queue_notify_off);
}

  • 当notify_off_multiplier不为0时,不同vq使用不同的kick寄存器地址,往kick寄存器写入vq的index,告诉virtio后端要处理哪个vq
  • 当notify_off_multiplier为0时,不同vq使用相同的kick寄存器地址,往kick寄存器写入vq的index,告诉virtio后端要处理哪个vq
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// modern device
static struct virtqueue *setup_vq(struct virtio_pci_device *vp_dev,
struct virtio_pci_vq_info *info,
unsigned int index,
void (*callback)(struct virtqueue *vq),
const char *name,
bool ctx,
u16 msix_vec)
{
...
/* create the vring */
vq = vring_create_virtqueue(index, num,
SMP_CACHE_BYTES, &vp_dev->vdev,
true, true, ctx,
notify, callback, name);
...
vq->priv = (void __force *)vp_modern_map_vq_notify(mdev, index, NULL);
...
}

/*
* vp_modern_map_vq_notify - map notification area for a
* specific virtqueue
* @mdev: the modern virtio-pci device
* @index: the queue index
* @pa: the pointer to the physical address of the nofity area
*
* Returns the address of the notification area
*/
void __iomem *vp_modern_map_vq_notify(struct virtio_pci_modern_device *mdev,
u16 index, resource_size_t *pa)
{
u16 off = vp_modern_get_queue_notify_off(mdev, index);

if (mdev->notify_base) {
/* offset should not wrap */
if ((u64)off * mdev->notify_offset_multiplier + 2
> mdev->notify_len) {
dev_warn(&mdev->pci_dev->dev,
"bad notification offset %u (x %u) "
"for queue %u > %zd",
off, mdev->notify_offset_multiplier,
index, mdev->notify_len);
return NULL;
}
if (pa)
*pa = mdev->notify_pa +
off * mdev->notify_offset_multiplier;
return mdev->notify_base + off * mdev->notify_offset_multiplier;
} else {
...
}
}

VIRTIO_F_NOTIFICATION_DATA feature

值得注意的是,对于split vq,desc table的最大size为2^16;对于packed vq,desc table的最大size为2^15;

在kick寄存器中,不止存放了vq index:

  • 对于split vq,kick寄存器中还存放了avail_idx
  • 对于packed vq,kick寄存器中还存放了avail_idx(为了表述的方便,严格来说,packed vq已经没有了avail ring,也就不存在avail_idx了)与wrap counter

vq_notif_config_data在一般情况下,就是vq index。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// modern device
static struct virtqueue *setup_vq(struct virtio_pci_device *vp_dev,
struct virtio_pci_vq_info *info,
unsigned int index,
void (*callback)(struct virtqueue *vq),
const char *name,
bool ctx,
u16 msix_vec)
{
...
if (__virtio_test_bit(&vp_dev->vdev, VIRTIO_F_NOTIFICATION_DATA))
notify = vp_notify_with_data;
else
notify = vp_notify;
...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static bool vp_notify_with_data(struct virtqueue *vq)
{
u32 data = vring_notification_data(vq);

iowrite32(data, (void __iomem *)vq->priv);

return true;
}

u32 vring_notification_data(struct virtqueue *_vq)
{
struct vring_virtqueue *vq = to_vvq(_vq);
u16 next;

if (vq->packed_ring)
next = (vq->packed.next_avail_idx &
~(-(1 << VRING_PACKED_EVENT_F_WRAP_CTR))) |
vq->packed.avail_wrap_counter <<
VRING_PACKED_EVENT_F_WRAP_CTR;
else
next = vq->split.avail_idx_shadow;

return next << 16 | _vq->index;
}

参考资料:

  1. virtio 0.9.5 spec
  2. virtio 1.3 spec