QEMU源码全解析 —— virtio(26)

88 篇文章 19 订阅
本文详细剖析了Linux内核中virtio_pci_device的setup_vq函数,介绍了如何初始化virtqueue,包括分配virtio_pci_vq_info结构体,设置通知函数,获取virtqueue数量和大小,以及探讨了前端驱动与后端设备的通信机制。
摘要由CSDN通过智能技术生成

接前一篇文章:

上一回讲到了vp_find_vqs_ms ix函数 的第5步:进入循环,设置每个使能的vq并申请中断。

再次贴出代码循环的代码片段:

  1. for (i = 0; i < nvqs; ++i) {
  2. if (!names[i]) {
  3. vqs[i] = NULL;
  4. continue;
  5. }
  6. if (!callbacks[i])
  7. msix_vec = VIRTIO_MSI_NO_VECTOR;
  8. else if (vp_dev->per_vq_vectors)
  9. msix_vec = allocated_vectors++;
  10. else
  11. msix_vec = VP_MSIX_VQ_VECTOR;
  12. vqs[i] = vp_setup_vq(vdev, queue_idx++, callbacks[i], names[i],
  13. ctx ? ctx[i] : false,
  14. msix_vec);
  15. if (IS_ERR(vqs[i])) {
  16. err = PTR_ERR(vqs[i]);
  17. goto error_find;
  18. }
  19. if (!vp_dev->per_vq_vectors || msix_vec == VIRTIO_MSI_NO_VECTOR)
  20. continue;
  21. /* allocate per-vq irq if available and necessary */
  22. snprintf(vp_dev->msix_names[msix_vec],
  23. sizeof *vp_dev->msix_names,
  24. "%s-%s",
  25. dev_name(&vp_dev->vdev.dev), names[i]);
  26. err = request_irq(pci_irq_vector(vp_dev->pci_dev, msix_vec),
  27. vring_interrupt, 0,
  28. vp_dev->msix_names[msix_vec],
  29. vqs[i]);
  30. if (err)
  31. goto error_find;
  32. }

上一回也讲解了循环中的前两个步骤,来到了第一个关键函数:vp_setup_vq。本回对该函数进行解析。

vp_setup_vq函数也在 Linux 内核源码/drivers/ virtio /virtio_pci_common.c中,代码如下:

  1. static struct virtqueue *vp_setup_vq(struct virtio_device *vdev, unsigned int index,
  2. void (*callback)(struct virtqueue *vq),
  3. const char *name,
  4. bool ctx,
  5. u16 msix_vec)
  6. {
  7. struct virtio_pci_device *vp_dev = to_vp_device(vdev);
  8. struct virtio_pci_vq_info *info = kmalloc(sizeof *info, GFP_KERNEL);
  9. struct virtqueue *vq;
  10. unsigned long flags;
  11. /* fill out our structure that represents an active queue */
  12. if (!info)
  13. return ERR_PTR(-ENOMEM);
  14. vq = vp_dev->setup_vq(vp_dev, info, index, callback, name, ctx,
  15. msix_vec);
  16. if (IS_ERR(vq))
  17. goto out_info;
  18. info->vq = vq;
  19. if (callback) {
  20. spin_lock_irqsave(&vp_dev->lock, flags);
  21. list_add(&info->node, &vp_dev->virtqueues);
  22. spin_unlock_irqrestore(&vp_dev->lock, flags);
  23. } else {
  24. INIT_LIST_HEAD(&info->node);
  25. }
  26. vp_dev->vqs[index] = info;
  27. return vq;
  28. out_info:
  29. kfree(info);
  30. return vq;
  31. }

vp_setup_vq函数初始化virtqueue。在该函数中会分配一个具体的virtio_pci_vq_info结构体对象,来表示一个virtqueue信息,代码片段如下:

    struct virtio_pci_vq_info *info = kmalloc(sizeof *info, GFP_KERNEL);

并且会以该对象会为参数,调用virtio_pci_device的setup_vq回调函数,代码片段如下:

  1. vq = vp_dev->setup_vq(vp_dev, info, index, callback, name, ctx,
  2. msix_vec);
  3. if (IS_ERR(vq))
  4. goto out_info;

这个回调函数同样是在virtio_pci_modern_probe函数中设置的,参考前文所讲的virtio_pci_modern_probe函数代码( Linux内核源码 /drivers/virtio/virtio_pci_modern.c中):

  1. /* the PCI probing function */
  2. int virtio_pci_modern_probe(struct virtio_pci_device *vp_dev)
  3. {
  4. struct virtio_pci_modern_device *mdev = &vp_dev->mdev;
  5. struct pci_dev *pci_dev = vp_dev->pci_dev;
  6. int err;
  7. mdev->pci_dev = pci_dev;
  8. err = vp_modern_probe(mdev);
  9. if (err)
  10. return err;
  11. if (mdev->device)
  12. vp_dev->vdev.config = &virtio_pci_config_ops;
  13. else
  14. vp_dev->vdev.config = &virtio_pci_config_nodev_ops;
  15. vp_dev->config_vector = vp_config_vector;
  16. vp_dev->setup_vq = setup_vq;
  17. vp_dev->del_vq = del_vq;
  18. vp_dev->isr = mdev->isr;
  19. vp_dev->vdev.id = mdev->id;
  20. return 0;
  21. }

可见,回调函数指向的是同文件(Linux内核源码/drivers/virtio/virtio_pci_modern.c)中的setup_vq函数。该函数代码如下:

  1. static struct virtqueue *setup_vq(struct virtio_pci_device *vp_dev,
  2. struct virtio_pci_vq_info *info,
  3. unsigned int index,
  4. void (*callback)(struct virtqueue *vq),
  5. const char *name,
  6. bool ctx,
  7. u16 msix_vec)
  8. {
  9. struct virtio_pci_modern_device *mdev = &vp_dev->mdev;
  10. bool (*notify)(struct virtqueue *vq);
  11. struct virtqueue *vq;
  12. u16 num;
  13. int err;
  14. if (__virtio_test_bit(&vp_dev->vdev, VIRTIO_F_NOTIFICATION_DATA))
  15. notify = vp_notify_with_data;
  16. else
  17. notify = vp_notify;
  18. if (index >= vp_modern_get_num_queues(mdev))
  19. return ERR_PTR(-EINVAL);
  20. /* Check if queue is either not available or already active. */
  21. num = vp_modern_get_queue_size(mdev, index);
  22. if (!num || vp_modern_get_queue_enable(mdev, index))
  23. return ERR_PTR(-ENOENT);
  24. info->msix_vector = msix_vec;
  25. /* create the vring */
  26. vq = vring_create_virtqueue(index, num,
  27. SMP_CACHE_BYTES, &vp_dev->vdev,
  28. true, true, ctx,
  29. notify, callback, name);
  30. if (!vq)
  31. return ERR_PTR(-ENOMEM);
  32. vq->num_max = num;
  33. err = vp_active_vq(vq, msix_vec);
  34. if (err)
  35. goto err;
  36. vq->priv = (void __force *)vp_modern_map_vq_notify(mdev, index, NULL);
  37. if (!vq->priv) {
  38. err = -ENOMEM;
  39. goto err;
  40. }
  41. return vq;
  42. err:
  43. vring_del_virtqueue(vq);
  44. return ERR_PTR(err);
  45. }

(1) setup_vq函数 首先检测virtio PCI设备是否具有VIRTIO_F_NOTIFICATION_DATA特性 。代码片段如下:

  1. if (__virtio_test_bit(&vp_dev->vdev, VIRTIO_F_NOTIFICATION_DATA))
  2. notify = vp_notify_with_data;
  3. else
  4. notify = vp_notify;

VIRTIO_F_NOTIFICATION_DATA宏在Linux内核源码/include/uapi/linux/virtio_config.h中定义,如下:

  1. /*
  2. * This feature indicates that the driver passes extra data (besides
  3. * identifying the virtqueue) in its device notifications.
  4. */
  5. #define VIRTIO_F_NOTIFICATION_DATA 38

如果设备支持 VIRTIO_F_NOTIFICATION_DATA,则说明notify时携带(额外)数据,就将notify函数指针设置为vp_notify_with_data,即指向vp_notify_with_data函数;否则指向notify函数。

vp_notify_with_data函数也在Linux内核源码/drivers/virtio/virtio_pci_modern.c中(就在上边),代码如下:

  1. static bool vp_notify_with_data(struct virtqueue *vq)
  2. {
  3. u32 data = vring_notification_data(vq);
  4. iowrite32(data, (void __iomem *)vq->priv);
  5. return true;
  6. }

而vp_notify函数则是在Linux内核源码/drivers/virtio/virtio_pci_common.c中,代码如下:

  1. /* the notify function used when creating a virt queue */
  2. bool vp_notify(struct virtqueue *vq)
  3. {
  4. /* we write the queue's selector into the notification register to
  5. * signal the other end */
  6. iowrite16(vq->index, (void __iomem *)vq->priv);
  7. return true;
  8. }

(2) 接下来, 调用vp_modern_get_num_queues函数获取virtqueues的长度(个数) 。代码片段如下:

  1. if (index >= vp_modern_get_num_queues(mdev))
  2. return ERR_PTR(-EINVAL);

vp_modern_get_num_queues函数在Linux内核源代码/drivers/virtio/virtio_pci_modern_dev.c中,代码如下:

  1. /*
  2. * vp_modern_get_num_queues - get the number of virtqueues
  3. * @mdev: the modern virtio-pci device
  4. *
  5. * Returns the number of virtqueues
  6. */
  7. u16 vp_modern_get_num_queues(struct virtio_pci_modern_device *mdev)
  8. {
  9. return vp_ioread16(&mdev->common->num_queues);
  10. }
  11. EXPORT_SYMBOL_GPL(vp_modern_get_num_queues);

(3) 接下来, 调用vp_modern_get_queue_size函数获得一个virtqueue的大小 。代码片段如下:

  1. /* Check if queue is either not available or already active. */
  2. num = vp_modern_get_queue_size(mdev, index);
  3. if (!num || vp_modern_get_queue_enable(mdev, index))
  4. return ERR_PTR(-ENOENT);

vp_modern_get_queue_size函数在Linux内核源码/drivers/virtio/virtio_pci_modern_dev.c中,代码如下:

  1. /*
  2. * vp_modern_get_queue_size - get size for a virtqueue
  3. * @mdev: the modern virtio-pci device
  4. * @index: the queue index
  5. *
  6. * Returns the size of the virtqueue
  7. */
  8. u16 vp_modern_get_queue_size(struct virtio_pci_modern_device *mdev,
  9. u16 index)
  10. {
  11. vp_iowrite16(index, &mdev->common->queue_select);
  12. return vp_ioread16(&mdev->common->queue_size);
  13. }
  14. EXPORT_SYMBOL_GPL(vp_modern_get_queue_size);

vp_modern_get_queue_size函数先选择某个virtqueue,然后得到其大小。

vp_modern_get_queue_enable函数也在Linux内核源码/drivers/virtio/virtio_pci_modern_dev.c中,代码如下:

  1. /*
  2. * vp_modern_get_queue_enable - enable a virtqueue
  3. * @mdev: the modern virtio-pci device
  4. * @index: the queue index
  5. *
  6. * Returns whether a virtqueue is enabled or not
  7. */
  8. bool vp_modern_get_queue_enable(struct virtio_pci_modern_device *mdev,
  9. u16 index)
  10. {
  11. vp_iowrite16(index, &mdev->common->queue_select);
  12. return vp_ioread16(&mdev->common->queue_enable);
  13. }
  14. EXPORT_SYMBOL_GPL(vp_modern_get_queue_enable);

vp_modern_get_queue_enable函数获得所选中的virtqueue是否使能。

这里要特别说明一下,在老版代码的setup_vq函数中,一上来首先得到virtio_pci_device的common成员,代码片段如下:

    struct virtio_pci_common_cfg __iomem *cfg = vp_dev->common;

在新版本中,虽然是直接使用具体的common->x,并未单独使用一个中间变量专门保存virtio_pci_device的common成员,但意思一样。

struct virtio_pci_common_cfg的定义在Linux内核源码/include/uapi/linux/virtio_pci.h中,代码如下:

  1. /* Fields in VIRTIO_PCI_CAP_COMMON_CFG: */
  2. struct virtio_pci_common_cfg {
  3. /* About the whole device. */
  4. __le32 device_feature_select; /* read-write */
  5. __le32 device_feature; /* read-only */
  6. __le32 guest_feature_select; /* read-write */
  7. __le32 guest_feature; /* read-write */
  8. __le16 msix_config; /* read-write */
  9. __le16 num_queues; /* read-only */
  10. __u8 device_status; /* read-write */
  11. __u8 config_generation; /* read-only */
  12. /* About a specific virtqueue. */
  13. __le16 queue_select; /* read-write */
  14. __le16 queue_size; /* read-write, power of 2. */
  15. __le16 queue_msix_vector; /* read-write */
  16. __le16 queue_enable; /* read-write */
  17. __le16 queue_notify_off; /* read-only */
  18. __le32 queue_desc_lo; /* read-write */
  19. __le32 queue_desc_hi; /* read-write */
  20. __le32 queue_avail_lo; /* read-write */
  21. __le32 queue_avail_hi; /* read-write */
  22. __le32 queue_used_lo; /* read-write */
  23. __le32 queue_used_hi; /* read-write */
  24. };

这是virtio PCI代理设备中用来配置的一段MMIO,如下图中间部分所示:

直接读写这些地址会陷入到QEMU的virtio_pci_common_read/write函数。这里将common的各个偏移和对应的寄存器名列出来以方便对照,在Linux内核源码/include/uapi/linux/virtio_pci.h中,如下:

  1. /* Macro versions of offsets for the Old Timers! */
  2. #define VIRTIO_PCI_CAP_VNDR 0
  3. #define VIRTIO_PCI_CAP_NEXT 1
  4. #define VIRTIO_PCI_CAP_LEN 2
  5. #define VIRTIO_PCI_CAP_CFG_TYPE 3
  6. #define VIRTIO_PCI_CAP_BAR 4
  7. #define VIRTIO_PCI_CAP_OFFSET 8
  8. #define VIRTIO_PCI_CAP_LENGTH 12
  9. #define VIRTIO_PCI_NOTIFY_CAP_MULT 16
  10. #define VIRTIO_PCI_COMMON_DFSELECT 0
  11. #define VIRTIO_PCI_COMMON_DF 4
  12. #define VIRTIO_PCI_COMMON_GFSELECT 8
  13. #define VIRTIO_PCI_COMMON_GF 12
  14. #define VIRTIO_PCI_COMMON_MSIX 16
  15. #define VIRTIO_PCI_COMMON_NUMQ 18
  16. #define VIRTIO_PCI_COMMON_STATUS 20
  17. #define VIRTIO_PCI_COMMON_CFGGENERATION 21
  18. #define VIRTIO_PCI_COMMON_Q_SELECT 22
  19. #define VIRTIO_PCI_COMMON_Q_SIZE 24
  20. #define VIRTIO_PCI_COMMON_Q_MSIX 26
  21. #define VIRTIO_PCI_COMMON_Q_ENABLE 28
  22. #define VIRTIO_PCI_COMMON_Q_NOFF 30
  23. #define VIRTIO_PCI_COMMON_Q_DESCLO 32
  24. #define VIRTIO_PCI_COMMON_Q_DESCHI 36
  25. #define VIRTIO_PCI_COMMON_Q_AVAILLO 40
  26. #define VIRTIO_PCI_COMMON_Q_AVAILHI 44
  27. #define VIRTIO_PCI_COMMON_Q_USEDLO 48
  28. #define VIRTIO_PCI_COMMON_Q_USEDHI 52
  29. #define VIRTIO_PCI_COMMON_Q_NDATA 56
  30. #define VIRTIO_PCI_COMMON_Q_RESET 58

对照上边的struct virtio_pci_common_cfg的定义,一目了然。

  1. /* Fields in VIRTIO_PCI_CAP_COMMON_CFG: */
  2. struct virtio_pci_common_cfg {
  3. /* About the whole device. */
  4. __le32 device_feature_select; /* read-write */
  5. __le32 device_feature; /* read-only */
  6. __le32 guest_feature_select; /* read-write */
  7. __le32 guest_feature; /* read-write */
  8. __le16 msix_config; /* read-write */
  9. __le16 num_queues; /* read-only */
  10. __u8 device_status; /* read-write */
  11. __u8 config_generation; /* read-only */
  12. /* About a specific virtqueue. */
  13. __le16 queue_select; /* read-write */
  14. __le16 queue_size; /* read-write, power of 2. */
  15. __le16 queue_msix_vector; /* read-write */
  16. __le16 queue_enable; /* read-write */
  17. __le16 queue_notify_off; /* read-only */
  18. __le32 queue_desc_lo; /* read-write */
  19. __le32 queue_desc_hi; /* read-write */
  20. __le32 queue_avail_lo; /* read-write */
  21. __le32 queue_avail_hi; /* read-write */
  22. __le32 queue_used_lo; /* read-write */
  23. __le32 queue_used_hi; /* read-write */
  24. };

这里特别说明以上内容,是因为这是前不久(2024年春节后)笔者参加地平线面试时被问到的一个问题:

问:

virtio的前端驱动和后端设备是怎样进行通信的?

答:

使用virtqueue,vring。

问:前端驱动即Guest写完vring后,怎样通知后端设备?

答:Guest添加完buffer是否通知Host,有两种机制:一是判断Ring中的flags,二是Event_idx。

问:底层的机制是什么?

答:……需要再研究一下。

面试官(问者)解答:开辟一段空间,当Guest中操作这段地空间时,会陷入到VMX root中。

当前位于virtqueue创建流程的以下阶段(红色矩形框中)(图片引自 https://note.youdao.com/ynoteshare/index.html?id=f247acce8c21eb4ca4a7e37403f065f9&type=note&_time=1633000040495 ):

回到setup_vq函数来,setup_vq函数的余下部分,将在下一回中进行解析。

举报

选择你想要举报的内容(必选)
  • 内容涉黄
  • 政治相关
  • 内容抄袭
  • 涉嫌广告
  • 内容侵权
  • 侮辱谩骂
  • 样式问题
  • 其他