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

88 篇文章 19 订阅
本文详细解析了QEMU virtio设备的中断处理机制,包括change中断和vq中断,以及在不同中断处理方式下的实现。重点介绍了virtio_find_vqs函数、virtio_config_ops中的find_vqs回调以及现代版本Linux内核中的MSIX中断配置。通过对vp_find_vqs_msix函数的分析,阐述了如何分配和计算中断向量,为后续的中断处理打下基础。
摘要由CSDN通过智能技术生成

接前一篇文章:

上回书讲解了virtioballoon_probe函数及其中的两个重要函数init_vqs()和virtio_device_ready(),解析了init_vq s函数 的前两步,本回继续解析该函数,

(3) init_vqs函数在经过了对于各feature的初始化、检查和设置后, 接下来调用virtio_find_vqs函数 。代码片段如下:

  1. err = virtio_find_vqs(vb->vdev, VIRTIO_BALLOON_VQ_MAX, vqs,
  2. callbacks, names, NULL);
  3. if (err)
  4. return err;

virtio_find_vqs函数在 Linux 内核源码/include/linux/virtio_config.h中,代码如下:

  1. static inline
  2. int virtio_find_vqs(struct virtio_device *vdev, unsigned nvqs,
  3. struct virtqueue *vqs[], vq_callback_t *callbacks[],
  4. const char * const names[],
  5. struct irq_affinity *desc)
  6. {
  7. return vdev->config->find_vqs(vdev, nvqs, vqs, callbacks, names, NULL, desc);
  8. }

实际上,老版本代码中是直接在init_vqs函数中调用的virtio_config_ops的find_vqs回调,新版本代码将其封装到了virtio_find_vqs中。

前文书 QEMU源码全解析 —— virtio(22) 已经对于virtio_config_ops进行过讲解了,如下:

virtio_pci_config_ops的初始化有两处,分别在Linux内核源码/drivers/ virtio /virtio_pci_legacy.c和Linux内核源码/drivers/virtio/virtio_pci_modern.c中。代码分别如下:

  • legacy
  1. static const struct virtio_config_ops virtio_pci_config_ops = {
  2. .get = vp_get,
  3. .set = vp_set,
  4. .get_status = vp_get_status,
  5. .set_status = vp_set_status,
  6. .reset = vp_reset,
  7. .find_vqs = vp_find_vqs,
  8. .del_vqs = vp_del_vqs,
  9. .synchronize_cbs = vp_synchronize_vectors,
  10. .get_features = vp_get_features,
  11. .finalize_features = vp_finalize_features,
  12. .bus_name = vp_bus_name,
  13. .set_vq_affinity = vp_set_vq_affinity,
  14. .get_vq_affinity = vp_get_vq_affinity,
  15. };

  • modern
  1. static const struct virtio_config_ops virtio_pci_config_ops = {
  2. .get = vp_get,
  3. .set = vp_set,
  4. .generation = vp_generation,
  5. .get_status = vp_get_status,
  6. .set_status = vp_set_status,
  7. .reset = vp_reset,
  8. .find_vqs = vp_modern_find_vqs,
  9. .del_vqs = vp_del_vqs,
  10. .synchronize_cbs = vp_synchronize_vectors,
  11. .get_features = vp_get_features,
  12. .finalize_features = vp_finalize_features,
  13. .bus_name = vp_bus_name,
  14. .set_vq_affinity = vp_set_vq_affinity,
  15. .get_vq_affinity = vp_get_vq_affinity,
  16. .get_shm_region = vp_get_shm_region,
  17. .disable_vq_and_reset = vp_modern_disable_vq_and_reset,
  18. .enable_vq_after_reset = vp_modern_enable_vq_after_reset,
  19. };

仍以modern为例,struct virtio_config_ops virtio_pci_config_ops中find_vqs成员对应的函数是vp_modern_find_vqs()。vp_modern_find_vqs函数在Linux内核源码/drivers/virtio/virtio_pci_modern.c中,代码如下:

  1. static int vp_modern_find_vqs(struct virtio_device *vdev, unsigned int nvqs,
  2. struct virtqueue *vqs[],
  3. vq_callback_t *callbacks[],
  4. const char * const names[], const bool *ctx,
  5. struct irq_affinity *desc)
  6. {
  7. struct virtio_pci_device *vp_dev = to_vp_device(vdev);
  8. struct virtqueue *vq;
  9. int rc = vp_find_vqs(vdev, nvqs, vqs, callbacks, names, ctx, desc);
  10. if (rc)
  11. return rc;
  12. /* Select and activate all queues. Has to be done last: once we do
  13. * this, there's no way to go back except reset.
  14. */
  15. list_for_each_entry(vq, &vdev->vqs, list)
  16. vp_modern_set_queue_enable(&vp_dev->mdev, vq->index, true);
  17. return 0;
  18. }

再贴一下调用代码:

  1. static inline
  2. int virtio_find_vqs(struct virtio_device *vdev, unsigned nvqs,
  3. struct virtqueue *vqs[], vq_callback_t *callbacks[],
  4. const char * const names[],
  5. struct irq_affinity *desc)
  6. {
  7. return vdev->config->find_vqs(vdev, nvqs, vqs, callbacks, names, NULL, desc);
  8. }
  1. err = virtio_find_vqs(vb->vdev, VIRTIO_BALLOON_VQ_MAX, vqs,
  2. callbacks, names, NULL);
  3. if (err)
  4. return err;

vp_modern_find_vqs函数以相同参数调用了vp_find_vqs函数。代码片段如下:

  1. int rc = vp_find_vqs(vdev, nvqs, vqs, callbacks, names, ctx, desc);
  2. if (rc)
  3. return rc;

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

在老版的Linux内核中的KVM代码中,vp_find_vqs函数本质上只是调用了一个函数vp_try_to_find_ops()。而新版本代码则变化比较大了,主要是调用了两个函数,vp_find_vqs_msix()和vp_find_vqs_intx()。

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_find_vqs_msix函数与旧版本KVM代码的vp_try_to_find_vqs函数很相近。

(1)vp_find_vqs_msix函数首先通过kcalloc函数分配nvqs个指向struct virtio_pci_vq_info的指针,并赋值给了virtio_pct_device的vqs成员 (struct virtio_pci_vq_info **vqs;),每个virtio_pci_vq_info记录了virtqueue的信息。代码片段如下:

  1. vp_dev->vqs = kcalloc(nvqs, sizeof(*vp_dev->vqs), GFP_KERNEL);
  2. if (!vp_dev->vqs)
  3. return -ENOMEM;

nvqs的实参是VIRTIO_BALLOON_VQ_MAX,前文书讲过,该值是5。

注意,这里只是分配了指针,并没有分配具体的结构体,即每个指针指向的空间。

(2)vq_find_vqs_msix函数接着计算nvectors 。代码片段如下:

  1. if (per_vq_vectors) {
  2. /* Best option: one for change interrupt, one per vq. */
  3. nvectors = 1;
  4. for (i = 0; i < nvqs; ++i)
  5. if (names[i] && callbacks[i])
  6. ++nvectors;
  7. } else {
  8. /* Second best: one for change, shared for all vqs. */
  9. nvectors = 2;
  10. }

nvectors表示总共需要的MSIx vector 。由于vp_find_vqs函数调用了两次vp_find_vqs_msix函数,

  1. /* Try MSI-X with one vector per queue. */
  2. err = vp_find_vqs_msix(vdev, nvqs, vqs, callbacks, names, true, ctx, desc);
  3. if (!err)
  4. return 0;
  5. /* Fallback: MSI-X with one vector for config, one shared for queues. */
  6. err = vp_find_vqs_msix(vdev, nvqs, vqs, callbacks, names, false, ctx, desc);
  7. if (!err)
  8. return 0;

因此,这里前一次调用得到的nvqs的值是[2,4](视上一回所讲的有条件的feature的使能情况而定);而后一次调用得到的nvqs的值是2。

在往下继续解析代码之前,要先对virtio的中断进行一下知识补强。

参考以下博文:

virtio设备中断分析

virtio中包括 两种中断类型

  • change中断

当设备的配置信息发生改变(config changed),会产生一个中断(称为change中断),中断处理程序需要调用相应的处理函数(需要驱动定义)。

  • vq中断

当设备向队列中写入信息时,会产生一个中断(称为vq中断),中断处理函数需要调用相应的队列的回调函数(需要驱动定义)。

virtio中包括 三种中断处理方式

1)不使用msix中断,使用常规中断

change中断和所有vq中断共用一个中断irq。

中断处理函数为vp_interrupt,vp_interrupt函数中包含了对change中断和vq中断的处理。

2)使用msix中断,但只有两个vector

两个vector中,一个用来对应change中断,另一个对应所有队列的vq中断。

change中断处理函数为vp_config_changed;vq中断处理函数为vp_vring_interrupt。

对应的就是vp_find_vqs函数第二次调用vp_find_vqs_msix函数的代码:

  1. /* Fallback: MSI-X with one vector for config, one shared for queues. */
  2. err = vp_find_vqs_msix(vdev, nvqs, vqs, callbacks, names, false, ctx, desc);
  3. if (!err)
  4. return 0;
  1. else {
  2. /* Second best: one for change, shared for all vqs. */
  3. nvectors = 2;
  4. }

3)使用msix中断,有n+1个vector

n+1个vector中,一个用来对应change中断,n个分别对应n个队列的vq中断,每个vq一个vector。

change中断处理函数为vp_config_changed;vq中断处理函数为vring_interrupt。

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

对应的就是vp_find_vqs函数第一次调用vp_find_vqs_msix函数的代码:

  1. /* Try MSI-X with one vector per queue. */
  2. err = vp_find_vqs_msix(vdev, nvqs, vqs, callbacks, names, true, ctx, desc);
  3. if (!err)
  4. return 0;
  1. if (per_vq_vectors) {
  2. /* Best option: one for change interrupt, one per vq. */
  3. nvectors = 1;
  4. for (i = 0; i < nvqs; ++i)
  5. if (names[i] && callbacks[i])
  6. ++nvectors;
  7. }

根据vp_find_vqs函数的调用顺序,

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

优先使用中断处理方式3(使用msix中断,有n+1个vector),然后方式2(使用msix中断,但只有两个vector),最后方式1(不使用msix中断,使用常规中断)。

(3)vp_find_vqs_msix函数接下来调用vp_request_msix_vectors函数 。代码片段如下:

  1. err = vp_request_msix_vectors(vdev, nvectors, per_vq_vectors,
  2. per_vq_vectors ? desc : NULL);
  3. if (err)
  4. goto error_find;

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函数的解析,请看下回。

举报

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