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

88 篇文章 19 订阅
本文详细解析了QEMU virtio中vp_request_msix_vectors函数的工作原理,对比了新老版本的区别,并介绍了在分配MSI-X中断向量及处理中断方面的逻辑。文章还提及了vp_find_vqs_msix函数的部分流程,以及如何根据virtio设备的特征启用中断处理。
摘要由CSDN通过智能技术生成

接前一篇文章:

上回书由init_vq s函数 中调用的virtio_find_vqs函数,跟进到virtio_find_vqs函数中调用的vdev->config->find_vqs(vdev, nvqs, vqs, callbacks, names, NULL, desc)(即virtio_config_ops的find_vqs回调),再到此回调所指向的函数vp_modern_find_vqs(),跟到其所调用的vp_find_vqs函数。调用流程如下:

init_vqs()

--->  virtio_find_vqs()

--->  vdev->config->find_vqs() ---> vp_modern_find_vqs()

--->  vp_find_vqs()

再次贴出vp_find_vqs函数代码,在 Linux 内核源码/drivers/ virtio /virtio_pci_common.c中,代码如下:

  1. /* the config->find_vqs() implementation */
  2. int vp_find_vqs(struct virtio_device *vdev, unsigned int nvqs,
  3. struct virtqueue *vqs[], vq_callback_t *callbacks[],
  4. const char * const names[], const bool *ctx,
  5. struct irq_affinity *desc)
  6. {
  7. int err;
  8. /* Try MSI-X with one vector per queue. */
  9. err = vp_find_vqs_msix(vdev, nvqs, vqs, callbacks, names, true, ctx, desc);
  10. if (!err)
  11. return 0;
  12. /* Fallback: MSI-X with one vector for config, one shared for queues. */
  13. err = vp_find_vqs_msix(vdev, nvqs, vqs, callbacks, names, false, ctx, desc);
  14. if (!err)
  15. return 0;
  16. /* Is there an interrupt? If not give up. */
  17. if (!(to_vp_device(vdev)->pci_dev->irq))
  18. return err;
  19. /* Finally fall back to regular interrupts. */
  20. return vp_find_vqs_intx(vdev, nvqs, vqs, callbacks, names, ctx);
  21. }

上回书讲到vp_find_vqs_msix函数,讲了其中的前两步。到第三步调用vp_request_msix_vectors函数的时候,又往下跟进了一层。为了便于理解和加深印象,再次贴出vp_find_vqs_msix函数的代码,在Linux内核源码/drivers/virtio/virtio_pci_common.c中,如下:

  1. static int vp_find_vqs_msix(struct virtio_device *vdev, unsigned int nvqs,
  2. struct virtqueue *vqs[], vq_callback_t *callbacks[],
  3. const char * const names[], bool per_vq_vectors,
  4. const bool *ctx,
  5. struct irq_affinity *desc)
  6. {
  7. struct virtio_pci_device *vp_dev = to_vp_device(vdev);
  8. u16 msix_vec;
  9. int i, err, nvectors, allocated_vectors, queue_idx = 0;
  10. vp_dev->vqs = kcalloc(nvqs, sizeof(*vp_dev->vqs), GFP_KERNEL);
  11. if (!vp_dev->vqs)
  12. return -ENOMEM;
  13. if (per_vq_vectors) {
  14. /* Best option: one for change interrupt, one per vq. */
  15. nvectors = 1;
  16. for (i = 0; i < nvqs; ++i)
  17. if (names[i] && callbacks[i])
  18. ++nvectors;
  19. } else {
  20. /* Second best: one for change, shared for all vqs. */
  21. nvectors = 2;
  22. }
  23. err = vp_request_msix_vectors(vdev, nvectors, per_vq_vectors,
  24. per_vq_vectors ? desc : NULL);
  25. if (err)
  26. goto error_find;
  27. vp_dev->per_vq_vectors = per_vq_vectors;
  28. allocated_vectors = vp_dev->msix_used_vectors;
  29. for (i = 0; i < nvqs; ++i) {
  30. if (!names[i]) {
  31. vqs[i] = NULL;
  32. continue;
  33. }
  34. if (!callbacks[i])
  35. msix_vec = VIRTIO_MSI_NO_VECTOR;
  36. else if (vp_dev->per_vq_vectors)
  37. msix_vec = allocated_vectors++;
  38. else
  39. msix_vec = VP_MSIX_VQ_VECTOR;
  40. vqs[i] = vp_setup_vq(vdev, queue_idx++, callbacks[i], names[i],
  41. ctx ? ctx[i] : false,
  42. msix_vec);
  43. if (IS_ERR(vqs[i])) {
  44. err = PTR_ERR(vqs[i]);
  45. goto error_find;
  46. }
  47. if (!vp_dev->per_vq_vectors || msix_vec == VIRTIO_MSI_NO_VECTOR)
  48. continue;
  49. /* allocate per-vq irq if available and necessary */
  50. snprintf(vp_dev->msix_names[msix_vec],
  51. sizeof *vp_dev->msix_names,
  52. "%s-%s",
  53. dev_name(&vp_dev->vdev.dev), names[i]);
  54. err = request_irq(pci_irq_vector(vp_dev->pci_dev, msix_vec),
  55. vring_interrupt, 0,
  56. vp_dev->msix_names[msix_vec],
  57. vqs[i]);
  58. if (err)
  59. goto error_find;
  60. }
  61. return 0;
  62. error_find:
  63. vp_del_vqs(vdev);
  64. return err;
  65. }

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

  1. static int vp_request_msix_vectors(struct virtio_device *vdev, int nvectors,
  2. bool per_vq_vectors, struct irq_affinity *desc)
  3. {
  4. struct virtio_pci_device *vp_dev = to_vp_device(vdev);
  5. const char *name = dev_name(&vp_dev->vdev.dev);
  6. unsigned int flags = PCI_IRQ_MSIX;
  7. unsigned int i, v;
  8. int err = -ENOMEM;
  9. vp_dev->msix_vectors = nvectors;
  10. vp_dev->msix_names = kmalloc_array(nvectors,
  11. sizeof(*vp_dev->msix_names),
  12. GFP_KERNEL);
  13. if (!vp_dev->msix_names)
  14. goto error;
  15. vp_dev->msix_affinity_masks
  16. = kcalloc(nvectors, sizeof(*vp_dev->msix_affinity_masks),
  17. GFP_KERNEL);
  18. if (!vp_dev->msix_affinity_masks)
  19. goto error;
  20. for (i = 0; i < nvectors; ++i)
  21. if (!alloc_cpumask_var(&vp_dev->msix_affinity_masks[i],
  22. GFP_KERNEL))
  23. goto error;
  24. if (desc) {
  25. flags |= PCI_IRQ_AFFINITY;
  26. desc->pre_vectors++; /* virtio config vector */
  27. }
  28. err = pci_alloc_irq_vectors_affinity(vp_dev->pci_dev, nvectors,
  29. nvectors, flags, desc);
  30. if (err < 0)
  31. goto error;
  32. vp_dev->msix_enabled = 1;
  33. /* Set the vector used for configuration */
  34. v = vp_dev->msix_used_vectors;
  35. snprintf(vp_dev->msix_names[v], sizeof *vp_dev->msix_names,
  36. "%s-config", name);
  37. err = request_irq(pci_irq_vector(vp_dev->pci_dev, v),
  38. vp_config_changed, 0, vp_dev->msix_names[v],
  39. vp_dev);
  40. if (err)
  41. goto error;
  42. ++vp_dev->msix_used_vectors;
  43. v = vp_dev->config_vector(vp_dev, v);
  44. /* Verify we had enough resources to assign the vector */
  45. if (v == VIRTIO_MSI_NO_VECTOR) {
  46. err = -EBUSY;
  47. goto error;
  48. }
  49. if (!per_vq_vectors) {
  50. /* Shared vector for all VQs */
  51. v = vp_dev->msix_used_vectors;
  52. snprintf(vp_dev->msix_names[v], sizeof *vp_dev->msix_names,
  53. "%s-virtqueues", name);
  54. err = request_irq(pci_irq_vector(vp_dev->pci_dev, v),
  55. vp_vring_interrupt, 0, vp_dev->msix_names[v],
  56. vp_dev);
  57. if (err)
  58. goto error;
  59. ++vp_dev->msix_used_vectors;
  60. }
  61. return 0;
  62. error:
  63. return err;
  64. }

本回就对vp_request_msix_vectors函数进行详细解析。

在这里必须也提一下老版本中该函数的代码,如下所示:

  1. static int vp_request_msix_vectors(struct virtio_device *vdev, int nvectors,
  2. bool per_vq_vectors)
  3. {
  4. struct virtio_pci_device *vp_dev = to_vp_device(vdev);
  5. const char *name = dev_name(&vp_dev->vdev.dev);
  6. unsigned i, v;
  7. int err = -ENOMEM;
  8. vp_dev->msix_vectors = nvectors;
  9. vp_dev->msix_entries = kmalloc(nvectors * sizeof *vp_dev->msix_entries,
  10. GFP_KERNEL);
  11. if (!vp_dev->msix_entries)
  12. goto error;
  13. vp_dev->msix_names = kmalloc(nvectors * sizeof *vp_dev->msix_names,
  14. GFP_KERNEL);
  15. if (!vp_dev->msix_names)
  16. goto error;
  17. vp_dev->msix_affinity_masks
  18. = kzalloc(nvectors * sizeof *vp_dev->msix_affinity_masks,
  19. GFP_KERNEL);
  20. if (!vp_dev->msix_affinity_masks)
  21. goto error;
  22. for (i = 0; i < nvectors; ++i)
  23. if (!alloc_cpumask_var(&vp_dev->msix_affinity_masks[i],
  24. GFP_KERNEL))
  25. goto error;
  26. for (i = 0; i < nvectors; ++i)
  27. vp_dev->msix_entries[i].entry = i;
  28. err = pci_enable_msix_exact(vp_dev->pci_dev,
  29. vp_dev->msix_entries, nvectors);
  30. if (err)
  31. goto error;
  32. vp_dev->msix_enabled = 1;
  33. /* Set the vector used for configuration */
  34. v = vp_dev->msix_used_vectors;
  35. snprintf(vp_dev->msix_names[v], sizeof *vp_dev->msix_names,
  36. "%s-config", name);
  37. err = request_irq(vp_dev->msix_entries[v].vector,
  38. vp_config_changed, 0, vp_dev->msix_names[v],
  39. vp_dev);
  40. if (err)
  41. goto error;
  42. ++vp_dev->msix_used_vectors;
  43. v = vp_dev->config_vector(vp_dev, v);
  44. /* Verify we had enough resources to assign the vector */
  45. if (v == VIRTIO_MSI_NO_VECTOR) {
  46. err = -EBUSY;
  47. goto error;
  48. }
  49. if (!per_vq_vectors) {
  50. /* Shared vector for all VQs */
  51. v = vp_dev->msix_used_vectors;
  52. snprintf(vp_dev->msix_names[v], sizeof *vp_dev->msix_names,
  53. "%s-virtqueues", name);
  54. err = request_irq(vp_dev->msix_entries[v].vector,
  55. vp_vring_interrupt, 0, vp_dev->msix_names[v],
  56. vp_dev);
  57. if (err)
  58. goto error;
  59. ++vp_dev->msix_used_vectors;
  60. }
  61. return 0;
  62. error:
  63. vp_free_vectors(vdev);
  64. return err;
  65. }

老版本和新版本vp_request_msix_vectors函数最大的区别是新版本中没有了vp_dev->msix_entries相关的代码,这是因为在新版本的virtio_pci_device结构中,不再有msix_entries这个成员所导致的。

老版本中的vp_request_msix_vectors函数完成了以下工作(仍然参考 virtio设备中断分析 ):

1)分配nvectors个msix中断用 vector ,并使用1个vector来指定vp_config_changed为change中断处理函数。
2)如果per_vq_vectors为false,则nvectors就是2,再用掉另一个vector来指定n个队列共用的vq中断处理函数vp_vring_interrupt。
3)如果per_vq_vectors为true,则在下面代码中为每个队列指定一个vector,vq中断处理函数为vring_interrupt。

由于新老版本中的代码差异与功能并没有太大联系,因此新版本vp_request_msix_vectors函数的功能与上面所述基本一致。

(4) vp_find_vqs_msix函数 接下来是两个赋值语句 。代码片段如下:

  1. vp_dev->per_vq_vectors = per_vq_vectors;
  2. allocated_vectors = vp_dev->msix_used_vectors;

第一句代码的意思是将vp_find_vqs_msix函数的参数bool per_vq_vectors赋值给vp_dev(virtio PCI设备)的per_vq_vectors成员。

第二句代码是将vp_dev->msix_used_vectors即使用的中断向量个数赋给allocated_vectors。vp_dev->msix_used_vectors在上边的vp_request_msix_vectors函数中生成,代码片段如下:

  1. /* Set the vector used for configuration */
  2. v = vp_dev->msix_used_vectors;
  3. snprintf(vp_dev->msix_names[v], sizeof *vp_dev->msix_names,
  4. "%s-config", name);
  5. err = request_irq(pci_irq_vector(vp_dev->pci_dev, v),
  6. vp_config_changed, 0, vp_dev->msix_names[v],
  7. vp_dev);
  8. if (err)
  9. goto error;
  10. ++vp_dev->msix_used_vectors;
  11. v = vp_dev->config_vector(vp_dev, v);
  12. /* Verify we had enough resources to assign the vector */
  13. if (v == VIRTIO_MSI_NO_VECTOR) {
  14. err = -EBUSY;
  15. goto error;
  16. }
  17. if (!per_vq_vectors) {
  18. /* Shared vector for all VQs */
  19. v = vp_dev->msix_used_vectors;
  20. snprintf(vp_dev->msix_names[v], sizeof *vp_dev->msix_names,
  21. "%s-virtqueues", name);
  22. err = request_irq(pci_irq_vector(vp_dev->pci_dev, v),
  23. vp_vring_interrupt, 0, vp_dev->msix_names[v],
  24. vp_dev);
  25. if (err)
  26. goto error;
  27. ++vp_dev->msix_used_vectors;
  28. }

(5) 接下来,vp_find_vqs_msix函数进入循环, 设置每个使能的vq并申请中断

  1. enum virtio_balloon_vq {
  2. VIRTIO_BALLOON_VQ_INFLATE,
  3. VIRTIO_BALLOON_VQ_DEFLATE,
  4. VIRTIO_BALLOON_VQ_STATS,
  5. VIRTIO_BALLOON_VQ_FREE_PAGE,
  6. VIRTIO_BALLOON_VQ_REPORTING,
  7. VIRTIO_BALLOON_VQ_MAX
  8. };

代码片段如下:

  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. }

names[]前文已经讲过了,参见 QEMU源码全解析 —— virtio(23)

有2个无条件的feature,如下所示:

  1. struct virtqueue *vqs[VIRTIO_BALLOON_VQ_MAX];
  2.     vq_callback_t *callbacks[VIRTIO_BALLOON_VQ_MAX];
  3.     const char *names[VIRTIO_BALLOON_VQ_MAX];
  4.     int err;
  5.  
  6.     /*
  7.      * Inflateq and deflateq are used unconditionally. The names[]
  8.      * will be NULL if the related feature is not enabled, which will
  9.      * cause no allocation for the corresponding virtqueue in find_vqs.
  10.      */
  11.     callbacks[VIRTIO_BALLOON_VQ_INFLATE] = balloon_ack;
  12.     names[VIRTIO_BALLOON_VQ_INFLATE] = "inflate";
  13.     callbacks[VIRTIO_BALLOON_VQ_DEFLATE] = balloon_ack;
  14.     names[VIRTIO_BALLOON_VQ_DEFLATE] = "deflate";
  15.     callbacks[VIRTIO_BALLOON_VQ_STATS] = NULL;
  16.     names[VIRTIO_BALLOON_VQ_STATS] = NULL;
  17.     callbacks[VIRTIO_BALLOON_VQ_FREE_PAGE] = NULL;
  18.     names[VIRTIO_BALLOON_VQ_FREE_PAGE] = NULL;
  19.     names[VIRTIO_BALLOON_VQ_REPORTING] = NULL;

还有3个有条件的feature,如下所示:

  1. if (virtio_has_feature(vb->vdev, VIRTIO_BALLOON_F_STATS_VQ)) {
  2. names[VIRTIO_BALLOON_VQ_STATS] = "stats";
  3. callbacks[VIRTIO_BALLOON_VQ_STATS] = stats_request;
  4. }
  5. if (virtio_has_feature(vb->vdev, VIRTIO_BALLOON_F_FREE_PAGE_HINT)) {
  6. names[VIRTIO_BALLOON_VQ_FREE_PAGE] = "free_page_vq";
  7. callbacks[VIRTIO_BALLOON_VQ_FREE_PAGE] = NULL;
  8. }
  9. if (virtio_has_feature(vb->vdev, VIRTIO_BALLOON_F_REPORTING)) {
  10. names[VIRTIO_BALLOON_VQ_REPORTING] = "reporting_vq";
  11. callbacks[VIRTIO_BALLOON_VQ_REPORTING] = balloon_ack;
  12. }

如果某个feature未被使能,则其对应的names[i]就为NULL,就把其对应的vqs[i]也置为NULL,跳过该特性。代码片段如下:

  1. if (!names[i]) {
  2. vqs[i] = NULL;
  3. continue;
  4. }

能再往下走的都是使能的feature了。这里又分为两种情况:一种是其对应的 callbacks [i]不为NULL,这是对于大多数feature来说的;另一种是虽然names[i]不为空,但callbacks[i]为NULL,目前这种情况只是对于VIRTIO_BALLOON_VQ_FREE_PAGE才会出现。对于后一种情况,将msix_vec设置为VIRTIO_MSI_NO_VECTOR;对于前一种情况,又要看根据上一回所讲的virtio的中断处理方式进行区别对待。对于“使用msix中断,有n+1个vector”的情况,每循环至此一次,allocated_vectors自增1,同时赋给msix_vec;否则就将msix_vec设置为VP_MSIX_VQ_VECTOR。代码片段如下:

  1. if (!callbacks[i])
  2. msix_vec = VIRTIO_MSI_NO_VECTOR;
  3. else if (vp_dev->per_vq_vectors)
  4. msix_vec = allocated_vectors++;
  5. else
  6. msix_vec = VP_MSIX_VQ_VECTOR;

接下来,循环中就来到了第一个核心函数:vp_setup_vq。对于它的详细解析,请看下回。

举报

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