接前一篇文章:
本文内容参考:
特此致谢!
上回书讲到了setup_vq函数的第4步:调用vring_create_virtqueue函数实际生成virtqueue。为了便于理解了加深印象,再次贴出该函数源码,在 Linux 内核源码/drivers/virtio/virtio_ring.c中,代码如下:
- struct virtqueue *vring_create_virtqueue(
- unsigned int index,
- unsigned int num,
- unsigned int vring_align,
- struct virtio_device *vdev,
- bool weak_barriers,
- bool may_reduce_num,
- bool context,
- bool (*notify)(struct virtqueue *),
- void (*callback)(struct virtqueue *),
- const char *name)
- {
-
- if (virtio_has_feature(vdev, VIRTIO_F_RING_PACKED))
- return vring_create_virtqueue_packed(index, num, vring_align,
- vdev, weak_barriers, may_reduce_num,
- context, notify, callback, name, vdev->dev.parent);
-
- return vring_create_virtqueue_split(index, num, vring_align,
- vdev, weak_barriers, may_reduce_num,
- context, notify, callback, name, vdev->dev.parent);
- }
- EXPORT_SYMBOL_GPL(vring_create_virtqueue);
引出来两个函数:vring_create_virtqueue_packed()和vring_create_virtqueue_split()。上一回只是宏观介绍了两个函数的作用,本回分别对于两个函数的具体内容进行详细解析。
一个一个来看,先来看传统的vring_create_virtqueue_split()。再次贴出其源码,在Linux内核源码/drivers/virtio/virtio_ring.c中,代码如下:
- static struct virtqueue *vring_create_virtqueue_split(
- unsigned int index,
- unsigned int num,
- unsigned int vring_align,
- struct virtio_device *vdev,
- bool weak_barriers,
- bool may_reduce_num,
- bool context,
- bool (*notify)(struct virtqueue *),
- void (*callback)(struct virtqueue *),
- const char *name,
- struct device *dma_dev)
- {
- struct vring_virtqueue_split vring_split = {};
- struct virtqueue *vq;
- int err;
-
- err = vring_alloc_queue_split(&vring_split, vdev, num, vring_align,
- may_reduce_num, dma_dev);
- if (err)
- return NULL;
-
- vq = __vring_new_virtqueue(index, &vring_split, vdev, weak_barriers,
- context, notify, callback, name, dma_dev);
- if (!vq) {
- vring_free_split(&vring_split, vdev, dma_dev);
- return NULL;
- }
-
- to_vvq(vq)->we_own_ring = true;
-
- return vq;
- }
在讲解具体的函数功能之前,先得对于函数中用到的各结构进行详细就说明,这样才能更好地理解函数代码和功能。
(1)virtio_device结构
struct virtio_device前文提到过,其定义在Linux内核源码/include/linux/virtio.h中,代码如下:
- /**
- * struct virtio_device - representation of a device using virtio
- * @index: unique position on the virtio bus
- * @failed: saved value for VIRTIO_CONFIG_S_FAILED bit (for restore)
- * @config_enabled: configuration change reporting enabled
- * @config_change_pending: configuration change reported while disabled
- * @config_lock: protects configuration change reporting
- * @vqs_list_lock: protects @vqs.
- * @dev: underlying device.
- * @id: the device type identification (used to match it with a driver).
- * @config: the configuration ops for this device.
- * @vringh_config: configuration ops for host vrings.
- * @vqs: the list of virtqueues for this device.
- * @features: the features supported by both driver and device.
- * @priv: private pointer for the driver's use.
- */
- struct virtio_device {
- int index;
- bool failed;
- bool config_enabled;
- bool config_change_pending;
- spinlock_t config_lock;
- spinlock_t vqs_list_lock;
- struct device dev;
- struct virtio_device_id id;
- const struct virtio_config_ops *config;
- const struct vringh_config_ops *vringh_config;
- struct list_head vqs;
- u64 features;
- void *priv;
- };
struct virtio_device是Linux内核中用于表示virtio设备的数据结构,其包含了与virtio设备相关的各种信息和操作函数。如下图中间部分所示:
struct virtio_device各成员详细描述如下:
- int index:virtio总线(bus)上的唯一位置。
- bool failed:VIRTIO_CONFIG_S_FAILED位的保存值(用于恢复)。
- bool config_enabled:是否使能配置(信息)变化报告。
- bool config_change_pending:当禁用时,是否报告配置(信息)变化。
- spinlock_t config_lock:保护配置更改报告的自旋锁。
- spinlock_t vqs_list_lock:保护vqs(成员)的自旋锁。
- struct device dev:底层设备。即Linux设备模型中的struct device结构体,用于表示virtio设备在内核中的抽象。
- struct virtio_device_id id:设备类型标识(用于将其与驱动程序相匹配)。
- const struct virtio_config_ops *config:此设备的配置操作。
- const struct vringh_config_ops *vringh_config:宿主机vrings的配置操作。
- struct list_head vqs:此设备的virtqueues列表。
- u64 features:驱动程序和设备都支持的功能。
- void *priv:供驱动程序使用的专用指针。
(2)virtio_device_id结构
struct virtio_device_id的定义在Linux内核源码/include/linux/mod_devicetable.h中,代码如下:
- struct virtio_device_id {
- __u32 device;
- __u32 vendor;
- };
此结构体很简单也很好理解,只有两个成员:
- device:设备ID。
- vendor:厂商(制造商)ID。
其中的device成员标识了当前virtio_device的用途。在Linux内核源码/include/uapi/linux/virtio_ids.h中定义了各种类型:
- #define VIRTIO_ID_NET 1 /* virtio net */
- #define VIRTIO_ID_BLOCK 2 /* virtio block */
- #define VIRTIO_ID_CONSOLE 3 /* virtio console */
- #define VIRTIO_ID_RNG 4 /* virtio rng */
- #define VIRTIO_ID_BALLOON 5 /* virtio balloon */
- #define VIRTIO_ID_IOMEM 6 /* virtio ioMemory */
- #define VIRTIO_ID_RPMSG 7 /* virtio remote processor messaging */
- #define VIRTIO_ID_SCSI 8 /* virtio scsi */
- #define VIRTIO_ID_9P 9 /* 9p virtio console */
- #define VIRTIO_ID_MAC80211_WLAN 10 /* virtio WLAN MAC */
- #define VIRTIO_ID_RPROC_SERIAL 11 /* virtio remoteproc serial link */
- #define VIRTIO_ID_CAIF 12 /* Virtio caif */
- #define VIRTIO_ID_MEMORY_BALLOON 13 /* virtio memory balloon */
- #define VIRTIO_ID_GPU 16 /* virtio GPU */
- #define VIRTIO_ID_CLOCK 17 /* virtio clock/timer */
- #define VIRTIO_ID_INPUT 18 /* virtio input */
- #define VIRTIO_ID_VSOCK 19 /* virtio vsock transport */
- #define VIRTIO_ID_CRYPTO 20 /* virtio crypto */
- #define VIRTIO_ID_SIGNAL_DIST 21 /* virtio signal distribution device */
- #define VIRTIO_ID_PSTORE 22 /* virtio pstore device */
- #define VIRTIO_ID_IOMMU 23 /* virtio IOMMU */
- #define VIRTIO_ID_MEM 24 /* virtio mem */
- #define VIRTIO_ID_SOUND 25 /* virtio sound */
- #define VIRTIO_ID_FS 26 /* virtio filesystem */
- #define VIRTIO_ID_PMEM 27 /* virtio pmem */
- #define VIRTIO_ID_RPMB 28 /* virtio rpmb */
- #define VIRTIO_ID_MAC80211_HWSIM 29 /* virtio mac80211-hwsim */
- #define VIRTIO_ID_VIDEO_ENCODER 30 /* virtio video encoder */
- #define VIRTIO_ID_VIDEO_DECODER 31 /* virtio video decoder */
- #define VIRTIO_ID_SCMI 32 /* virtio SCMI */
- #define VIRTIO_ID_NITRO_SEC_MOD 33 /* virtio nitro secure module*/
- #define VIRTIO_ID_I2C_ADAPTER 34 /* virtio i2c adapter */
- #define VIRTIO_ID_WATCHDOG 35 /* virtio watchdog */
- #define VIRTIO_ID_CAN 36 /* virtio can */
- #define VIRTIO_ID_DMABUF 37 /* virtio dmabuf */
- #define VIRTIO_ID_PARAM_SERV 38 /* virtio parameter server */
- #define VIRTIO_ID_AUDIO_POLICY 39 /* virtio audio policy */
- #define VIRTIO_ID_BT 40 /* virtio bluetooth */
- #define VIRTIO_ID_GPIO 41 /* virtio gpio */
我们当前示例中的balloon设备就是其中的一种:
#define VIRTIO_ID_BALLOON 5 /* virtio balloon */
(3)virtio_config_ops结构
struct virtio_config_ops前文书也讲过,包含了配置(一个)virtio设备的操作。其定义在Linunx内核源码/include/linux/virtio_config.h中,代码如下:
- /**
- * struct virtio_config_ops - operations for configuring a virtio device
- * Note: Do not assume that a transport implements all of the operations
- * getting/setting a value as a simple read/write! Generally speaking,
- * any of @get/@set, @get_status/@set_status, or @get_features/
- * @finalize_features are NOT safe to be called from an atomic
- * context.
- * @get: read the value of a configuration field
- * vdev: the virtio_device
- * offset: the offset of the configuration field
- * buf: the buffer to write the field value into.
- * len: the length of the buffer
- * @set: write the value of a configuration field
- * vdev: the virtio_device
- * offset: the offset of the configuration field
- * buf: the buffer to read the field value from.
- * len: the length of the buffer
- * @generation: config generation counter (optional)
- * vdev: the virtio_device
- * Returns the config generation counter
- * @get_status: read the status byte
- * vdev: the virtio_device
- * Returns the status byte
- * @set_status: write the status byte
- * vdev: the virtio_device
- * status: the new status byte
- * @reset: reset the device
- * vdev: the virtio device
- * After this, status and feature negotiation must be done again
- * Device must not be reset from its vq/config callbacks, or in
- * parallel with being added/removed.
- * @find_vqs: find virtqueues and instantiate them.
- * vdev: the virtio_device
- * nvqs: the number of virtqueues to find
- * vqs: on success, includes new virtqueues
- * callbacks: array of callbacks, for each virtqueue
- * include a NULL entry for vqs that do not need a callback
- * names: array of virtqueue names (mainly for debugging)
- * include a NULL entry for vqs unused by driver
- * Returns 0 on success or error status
- * @del_vqs: free virtqueues found by find_vqs().
- * @synchronize_cbs: synchronize with the virtqueue callbacks (optional)
- * The function guarantees that all memory operations on the
- * queue before it are visible to the vring_interrupt() that is
- * called after it.
- * vdev: the virtio_device
- * @get_features: get the array of feature bits for this device.
- * vdev: the virtio_device
- * Returns the first 64 feature bits (all we currently need).
- * @finalize_features: confirm what device features we'll be using.
- * vdev: the virtio_device
- * This sends the driver feature bits to the device: it can change
- * the dev->feature bits if it wants.
- * Note that despite the name this can be called any number of
- * times.
- * Returns 0 on success or error status
- * @bus_name: return the bus name associated with the device (optional)
- * vdev: the virtio_device
- * This returns a pointer to the bus name a la pci_name from which
- * the caller can then copy.
- * @set_vq_affinity: set the affinity for a virtqueue (optional).
- * @get_vq_affinity: get the affinity for a virtqueue (optional).
- * @get_shm_region: get a shared memory region based on the index.
- * @disable_vq_and_reset: reset a queue individually (optional).
- * vq: the virtqueue
- * Returns 0 on success or error status
- * disable_vq_and_reset will guarantee that the callbacks are disabled and
- * synchronized.
- * Except for the callback, the caller should guarantee that the vring is
- * not accessed by any functions of virtqueue.
- * @enable_vq_after_reset: enable a reset queue
- * vq: the virtqueue
- * Returns 0 on success or error status
- * If disable_vq_and_reset is set, then enable_vq_after_reset must also be
- * set.
- */
- struct virtio_config_ops {
- void (*get)(struct virtio_device *vdev, unsigned offset,
- void *buf, unsigned len);
- void (*set)(struct virtio_device *vdev, unsigned offset,
- const void *buf, unsigned len);
- u32 (*generation)(struct virtio_device *vdev);
- u8 (*get_status)(struct virtio_device *vdev);
- void (*set_status)(struct virtio_device *vdev, u8 status);
- void (*reset)(struct virtio_device *vdev);
- int (*find_vqs)(struct virtio_device *, unsigned nvqs,
- struct virtqueue *vqs[], vq_callback_t *callbacks[],
- const char * const names[], const bool *ctx,
- struct irq_affinity *desc);
- void (*del_vqs)(struct virtio_device *);
- void (*synchronize_cbs)(struct virtio_device *);
- u64 (*get_features)(struct virtio_device *vdev);
- int (*finalize_features)(struct virtio_device *vdev);
- const char *(*bus_name)(struct virtio_device *vdev);
- int (*set_vq_affinity)(struct virtqueue *vq,
- const struct cpumask *cpu_mask);
- const struct cpumask *(*get_vq_affinity)(struct virtio_device *vdev,
- int index);
- bool (*get_shm_region)(struct virtio_device *vdev,
- struct virtio_shm_region *region, u8 id);
- int (*disable_vq_and_reset)(struct virtqueue *vq);
- int (*enable_vq_after_reset)(struct virtqueue *vq);
- };
struct virtio_config_ops的各成员详细描述如下:
- void (*get)(struct virtio_device *vdev, unsigned offset, void *buf, unsigned len)
回调函数(指针),读取配置字段的值。
参数:
vdev:virtio_device。
offset:配置字段的偏移量。
buf:将字段值写入的缓冲区。
len:缓冲区的长度。
- void (*set)(struct virtio_device *vdev, unsigned offset, const void *buf, unsigned len)
回调函数(指针),写入配置字段。
参数:
vdev:virtio_device。
offset:配置字段的偏移量。
buf:从中读取字段值的缓冲区。
len:缓冲区的长度。
- u32 (*generation)(struct virtio_device *vdev)
回调函数(指针),配置生成计数器(可选)。
参数:
vdev:virtio_device。
- u8 (*get_status)(struct virtio_device *vdev)
回调函数(指针),读取状态字节。
参数:
vdev:virtio_device。
返回状态字节。
- void (*set_status)(struct virtio_device *vdev, u8 status)
回调函数(指针),设置状态字节。
参数:
vdev:virtio_device。
status:新的状态字节。
- void (*reset)(struct virtio_device *vdev)
回调函数(指针),重置设备。
参数:
vdev:virtio_device。
重置设备之后,必须再次进行状态和功能协商。设备不能从其vq/config回调中重置,也不能与添加/删除并行重置。
- int (*find_vqs)(struct virtio_device *, unsigned nvqs, struct virtqueue *vqs[], vq_callback_t *callbacks[], const char * const names[], const bool *ctx, struct irq_affinity *desc)
回调函数(指针),找到virtqueues并实例化它们。
参数:
vdev:virtio_device。
nvqs:要查找的virtqueues的数量。
vqs:调用成功后,包括新的virtqueues。
callbacks:回调数组,每个virtqueue都包括一个,对于不需要回调的vqs,则置为NULL。
names:virtqueue名称数组(主要用于调试),包括驱动程序未使用的vqs的NULL条目。
- void (*del_vqs)(struct virtio_device *)
回调函数(指针),释放find_vqs()找到的空闲virtqueues。
参数:
vdev:virtio_device。
- void (*synchronize_cbs)(struct virtio_device *)
回调函数(指针),与virtqueue回调同步(可选)。
该函数保证在其之前的队列上的所有内存操作,对其后调用的vring_interrupt()可见。
参数:
vdev:virtio_device。
- u64 (*get_features)(struct virtio_device *vdev)
回调函数(指针),获取该设备的特征位数组。
参数:
vdev:virtio_device。
返回前64个特征位(我们当前所需的全部)。
- int (*finalize_features)(struct virtio_device *vdev)
回调函数(指针),确认将使用哪些设备特性。
参数:
vdev:virtio_device。
这会将驱动程序特性(特征)位发送到设备:如果需要,它可以更改dev->feature位。
注意,尽管叫这个名称,但它可以被调用任意次数。
成功返回0,失败返回错误状态。
- const char *(*bus_name)(struct virtio_device *vdev)
回调函数(指针),返回与设备关联的总线名称(可选)
参数:
vdev:virtio_device。
这将返回一个指向总线名称,一个仿pci_name的指针,然后调用方可以从该名称进行复制。
- int (*set_vq_affinity)(struct virtqueue *vq, const struct cpumask *cpu_mask)
回调函数(指针),设置virtqueue的亲和性(可选)。即设置指定的虚拟队列(Virtual Queue)与特定的CPU核心之间的亲和性(affinity)。
参数:
vq:virtqueue。
cpu_mask:CPU掩码。
- const struct cpumask *(*get_vq_affinity)(struct virtio_device *vdev, int index);
回调函数(指针),获取virtqueue的亲和性(可选)。即获取指定的虚拟队列(Virtual Queue)与特定的CPU核心之间的亲和性(affinity)
参数:
vq:virtqueue。
index:队列的索引值。
- bool (*get_shm_region)(struct virtio_device *vdev, struct virtio_shm_region *region, u8 id)
回调函数(指针),基于索引获取共享内存区域。
参数:
vdev:virtio_device。
region:共享内存区域。
id:索引值。
- int (*disable_vq_and_reset)(struct virtqueue *vq)
回调函数(指针),单独重置队列(可选)。
参数:
vq:virtqueue。
成功返回0,失败返回错误状态。
disable_vq_and_reset将确保回调被禁用并同步。
除了回调之外,调用者应该保证virtqueue的任何函数都不会访问vring。
- int (*enable_vq_after_reset)(struct virtqueue *vq)
回调函数(指针),启用重置队列。
参数:
vq:virtqueue。
成功返回0,失败返回错误状态。
如果设置了disable_vq_and_reset,则还必须设置enable_vq_after_reset。
如下图(就是上面那张图)右上角所示:
更多相关结构的介绍请看下回。