virtio_blk 通过写virtqueue 出发中断通知qemu后端

在virtblk_probe->init_vq中会通过下面的code来找到virtqueue
    err = vdev->config->find_vqs(vdev, num_vqs, vqs, callbacks, names);
    if (err)
        goto out;

    for (i = 0; i < num_vqs; i++) {
        spin_lock_init(&vblk->vqs[i].lock);
        vblk->vqs[i].vq = vqs[i];
    }
这样当virtblk往virtqueue 中写东西的时候就通知qemu后端。具体flow如下:
find_vqs 是在virtio_config_ops 中赋值的,我们以下为例
static const struct virtio_config_ops virtio_pci_config_ops = {
    .get        = vp_get,
    .set        = vp_set,
    .generation    = vp_generation,
    .get_status    = vp_get_status,
    .set_status    = vp_set_status,
    .reset        = vp_reset,
    .find_vqs    = vp_modern_find_vqs,
    .del_vqs    = vp_del_vqs,
    .get_features    = vp_get_features,
    .finalize_features = vp_finalize_features,
    .bus_name    = vp_bus_name,
    .set_vq_affinity = vp_set_vq_affinity,
};

可见这里赋值的是vp_modern_find_vqs,在
static int vp_modern_find_vqs(struct virtio_device *vdev, unsigned nvqs,
                  struct virtqueue *vqs[],
                  vq_callback_t *callbacks[],
                  const char * const names[])
{
    struct virtio_pci_device *vp_dev = to_vp_device(vdev);
    struct virtqueue *vq;
    int rc = vp_find_vqs(vdev, nvqs, vqs, callbacks, names);

    if (rc)
        return rc;

    /* Select and activate all queues. Has to be done last: once we do
     * this, there's no way to go back except reset.
     */
    list_for_each_entry(vq, &vdev->vqs, list) {
        vp_iowrite16(vq->index, &vp_dev->common->queue_select);
        vp_iowrite16(1, &vp_dev->common->queue_enable);
    }

    return 0;
}
中调用vp_find_vqs 来找到virtqueue
/* the config->find_vqs() implementation */
int vp_find_vqs(struct virtio_device *vdev, unsigned nvqs,
        struct virtqueue *vqs[],
        vq_callback_t *callbacks[],
        const char * const names[])
{
    int err;

    /* Try MSI-X with one vector per queue. */
    err = vp_try_to_find_vqs(vdev, nvqs, vqs, callbacks, names, true, true);
    if (!err)
        return 0;
    /* Fallback: MSI-X with one vector for config, one shared for queues. */
    err = vp_try_to_find_vqs(vdev, nvqs, vqs, callbacks, names,
                 true, false);
    if (!err)
        return 0;
    /* Finally fall back to regular interrupts. */
    return vp_try_to_find_vqs(vdev, nvqs, vqs, callbacks, names,
                  false, false);
}
可见都是调用vp_try_to_find_vqs->vp_setup_vq->setup_vq
而setup_vq实在Virtio_pci_modern.c中赋值
static struct virtqueue *setup_vq(struct virtio_pci_device *vp_dev,
                  struct virtio_pci_vq_info *info,
                  unsigned index,
                  void (*callback)(struct virtqueue *vq),
                  const char *name,
                  u16 msix_vec)
{
    struct virtio_pci_common_cfg __iomem *cfg = vp_dev->common;
    struct virtqueue *vq;
    u16 num, off;
    int err;

    if (index >= vp_ioread16(&cfg->num_queues))
        return ERR_PTR(-ENOENT);

    /* Select the queue we're interested in */
    vp_iowrite16(index, &cfg->queue_select);

    /* Check if queue is either not available or already active. */
    num = vp_ioread16(&cfg->queue_size);
    if (!num || vp_ioread16(&cfg->queue_enable))
        return ERR_PTR(-ENOENT);

    if (num & (num - 1)) {
        dev_warn(&vp_dev->pci_dev->dev, "bad queue size %u", num);
        return ERR_PTR(-EINVAL);
    }

    /* get offset of notification word for this vq */
    off = vp_ioread16(&cfg->queue_notify_off);

    info->msix_vector = msix_vec;

    /* create the vring */
    vq = vring_create_virtqueue(index, num,
                    SMP_CACHE_BYTES, &vp_dev->vdev,
                    true, true, vp_notify, callback, name);
    if (!vq)
        return ERR_PTR(-ENOMEM);
}
在setup_vq 中通过vring_create_virtqueue 申请一个virtqueue,其回调函数是vp_notify。也就是再往vq中写的时候就会调用vp_notify。我们看看vp_notify 是如何通知qemu后端的呢?
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;
}
原来是通过写寄存器通知qemu的啊
这样在virto_blk.c 中的virtio_queue_rq中通过__virtblk_add_req 往vq中写数据的时候就会自动通过qemu。所以正常情绪下err==0,及不用主动调用virtqueue_kick 来通知qemu后端。
    err = __virtblk_add_req(vblk->vqs[qid].vq, vbr, vbr->sg, num);
    if (err) {
        virtqueue_kick(vblk->vqs[qid].vq);
        blk_mq_stop_hw_queue(hctx);
        spin_unlock_irqrestore(&vblk->vqs[qid].lock, flags);
        /* Out of mem doesn't actually happen, since we fall back
         * to direct descriptors */
        if (err == -ENOMEM || err == -ENOSPC)
            return BLK_MQ_RQ_QUEUE_BUSY;
        return BLK_MQ_RQ_QUEUE_ERROR;
    }


你可能感兴趣的:(Linux,源码分析)