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

2023-12-21 11:20
文章标签 源码 解析 19 qemu virtio

本文主要是介绍QEMU源码全解析 —— virtio(19),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

接前一篇文章:

上回书继续讲解virtio_pci_driver的probe回调函数virtio_pci_probe(),在讲到第5段代码的时候,

    if (force_legacy) {rc = virtio_pci_legacy_probe(vp_dev);/* Also try modern mode if we can't map BAR0 (no IO space). */if (rc == -ENODEV || rc == -ENOMEM)rc = virtio_pci_modern_probe(vp_dev);if (rc)goto err_probe;} else {rc = virtio_pci_modern_probe(vp_dev);if (rc == -ENODEV)rc = virtio_pci_legacy_probe(vp_dev);if (rc)goto err_probe;}

引出来两个函数:virtio_pci_legacy_probe和virtio_pci_modern_probe。本回就来对它们进行解析。当然,由于legacy已成过去,因此重点围绕virtio_pci_modern_probe函数进行解析,捎带手地也讲一下virtio_pci_legacy_probe()。为了便于理解,再次贴出两个函数的源码:

  • virtio_pci_legacy_probe

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

/* the PCI probing function */
int virtio_pci_legacy_probe(struct virtio_pci_device *vp_dev)
{struct virtio_pci_legacy_device *ldev = &vp_dev->ldev;struct pci_dev *pci_dev = vp_dev->pci_dev;int rc;ldev->pci_dev = pci_dev;rc = vp_legacy_probe(ldev);if (rc)return rc;vp_dev->isr = ldev->isr;vp_dev->vdev.id = ldev->id;vp_dev->vdev.config = &virtio_pci_config_ops;vp_dev->config_vector = vp_config_vector;vp_dev->setup_vq = setup_vq;vp_dev->del_vq = del_vq;return 0;
}
  • virtio_pci_modern_probe

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

/* the PCI probing function */
int virtio_pci_modern_probe(struct virtio_pci_device *vp_dev)
{struct virtio_pci_modern_device *mdev = &vp_dev->mdev;struct pci_dev *pci_dev = vp_dev->pci_dev;int err;mdev->pci_dev = pci_dev;err = vp_modern_probe(mdev);if (err)return err;if (mdev->device)vp_dev->vdev.config = &virtio_pci_config_ops;elsevp_dev->vdev.config = &virtio_pci_config_nodev_ops;vp_dev->config_vector = vp_config_vector;vp_dev->setup_vq = setup_vq;vp_dev->del_vq = del_vq;vp_dev->isr = mdev->isr;vp_dev->vdev.id = mdev->id;return 0;
}

virtio_pci_modern_probe函数中最主要的是调用了vp_modern_probe函数,其在Linux内核源码/drivers/virtio/virtio_pci_modern_dev.c中,代码如下:

/** vp_modern_probe: probe the modern virtio pci device, note that the* caller is required to enable PCI device before calling this function.* @mdev: the modern virtio-pci device** Return 0 on succeed otherwise fail*/
int vp_modern_probe(struct virtio_pci_modern_device *mdev)
{struct pci_dev *pci_dev = mdev->pci_dev;int err, common, isr, notify, device;u32 notify_length;u32 notify_offset;check_offsets();/* We only own devices >= 0x1000 and <= 0x107f: leave the rest. */if (pci_dev->device < 0x1000 || pci_dev->device > 0x107f)return -ENODEV;if (pci_dev->device < 0x1040) {/* Transitional devices: use the PCI subsystem device id as* virtio device id, same as legacy driver always did.*/mdev->id.device = pci_dev->subsystem_device;} else {/* Modern devices: simply use PCI device id, but start from 0x1040. */mdev->id.device = pci_dev->device - 0x1040;}mdev->id.vendor = pci_dev->subsystem_vendor;/* check for a common config: if not, use legacy mode (bar 0). */common = virtio_pci_find_capability(pci_dev, VIRTIO_PCI_CAP_COMMON_CFG,IORESOURCE_IO | IORESOURCE_MEM,&mdev->modern_bars);if (!common) {dev_info(&pci_dev->dev,"virtio_pci: leaving for legacy driver\n");return -ENODEV;}/* If common is there, these should be too... */isr = virtio_pci_find_capability(pci_dev, VIRTIO_PCI_CAP_ISR_CFG,IORESOURCE_IO | IORESOURCE_MEM,&mdev->modern_bars);notify = virtio_pci_find_capability(pci_dev, VIRTIO_PCI_CAP_NOTIFY_CFG,IORESOURCE_IO | IORESOURCE_MEM,&mdev->modern_bars);if (!isr || !notify) {dev_err(&pci_dev->dev,"virtio_pci: missing capabilities %i/%i/%i\n",common, isr, notify);return -EINVAL;}err = dma_set_mask_and_coherent(&pci_dev->dev, DMA_BIT_MASK(64));if (err)err = dma_set_mask_and_coherent(&pci_dev->dev,DMA_BIT_MASK(32));if (err)dev_warn(&pci_dev->dev, "Failed to enable 64-bit or 32-bit DMA.  Trying to continue, but this might not work.\n");/* Device capability is only mandatory for devices that have* device-specific configuration.*/device = virtio_pci_find_capability(pci_dev, VIRTIO_PCI_CAP_DEVICE_CFG,IORESOURCE_IO | IORESOURCE_MEM,&mdev->modern_bars);err = pci_request_selected_regions(pci_dev, mdev->modern_bars,"virtio-pci-modern");if (err)return err;err = -EINVAL;mdev->common = vp_modern_map_capability(mdev, common,sizeof(struct virtio_pci_common_cfg), 4,0, sizeof(struct virtio_pci_common_cfg),NULL, NULL);if (!mdev->common)goto err_map_common;mdev->isr = vp_modern_map_capability(mdev, isr, sizeof(u8), 1,0, 1,NULL, NULL);if (!mdev->isr)goto err_map_isr;/* Read notify_off_multiplier from config space. */pci_read_config_dword(pci_dev,notify + offsetof(struct virtio_pci_notify_cap,notify_off_multiplier),&mdev->notify_offset_multiplier);/* Read notify length and offset from config space. */pci_read_config_dword(pci_dev,notify + offsetof(struct virtio_pci_notify_cap,cap.length),&notify_length);pci_read_config_dword(pci_dev,notify + offsetof(struct virtio_pci_notify_cap,cap.offset),&notify_offset);/* We don't know how many VQs we'll map, ahead of the time.* If notify length is small, map it all now.* Otherwise, map each VQ individually later.*/if ((u64)notify_length + (notify_offset % PAGE_SIZE) <= PAGE_SIZE) {mdev->notify_base = vp_modern_map_capability(mdev, notify,2, 2,0, notify_length,&mdev->notify_len,&mdev->notify_pa);if (!mdev->notify_base)goto err_map_notify;} else {mdev->notify_map_cap = notify;}/* Again, we don't know how much we should map, but PAGE_SIZE* is more than enough for all existing devices.*/if (device) {mdev->device = vp_modern_map_capability(mdev, device, 0, 4,0, PAGE_SIZE,&mdev->device_len,NULL);if (!mdev->device)goto err_map_device;}return 0;err_map_device:if (mdev->notify_base)pci_iounmap(pci_dev, mdev->notify_base);
err_map_notify:pci_iounmap(pci_dev, mdev->isr);
err_map_isr:pci_iounmap(pci_dev, mdev->common);
err_map_common:pci_release_selected_regions(pci_dev, mdev->modern_bars);return err;
}
EXPORT_SYMBOL_GPL(vp_modern_probe);

实际上在老版本KVM即Linux内核代码中,vp_modern_probe函数中的内容绝大多数是直接放在virtio_pci_modern_probe函数中的,后来才单独封了这样一个函数。

(1)vp_modern_probe首先设置了virtio设备的verdor ID和device ID。代码片段如下:

    /* We only own devices >= 0x1000 and <= 0x107f: leave the rest. */if (pci_dev->device < 0x1000 || pci_dev->device > 0x107f)return -ENODEV;if (pci_dev->device < 0x1040) {/* Transitional devices: use the PCI subsystem device id as* virtio device id, same as legacy driver always did.*/mdev->id.device = pci_dev->subsystem_device;} else {/* Modern devices: simply use PCI device id, but start from 0x1040. */mdev->id.device = pci_dev->device - 0x1040;}mdev->id.vendor = pci_dev->subsystem_vendor;

值得注意的是,virtio PCI代理设备的device iD就是前文书(参见QEMU源码全解析 —— virtio(14))在讲virtio_pci_device_plugged函数(QEMU源码中)时设置的PCI_DEVICE_ID_VIRTIO_10_BASE+VIRTIO_ID_BALLOON,即0x1040+5。

所以,这里virtio设备的device ID(mdev->id.device)就是0x1040+5-0x1040=5,也就代表了VIRTIO_ID_BALLOON。

(2)接下来,调用多次virtio_pci_find_capability函数来发现virtio PCI代理设备的pci capability。代码片段如下:

/* check for a common config: if not, use legacy mode (bar 0). */common = virtio_pci_find_capability(pci_dev, VIRTIO_PCI_CAP_COMMON_CFG,IORESOURCE_IO | IORESOURCE_MEM,&mdev->modern_bars);if (!common) {dev_info(&pci_dev->dev,"virtio_pci: leaving for legacy driver\n");return -ENODEV;}/* If common is there, these should be too... */isr = virtio_pci_find_capability(pci_dev, VIRTIO_PCI_CAP_ISR_CFG,IORESOURCE_IO | IORESOURCE_MEM,&mdev->modern_bars);notify = virtio_pci_find_capability(pci_dev, VIRTIO_PCI_CAP_NOTIFY_CFG,IORESOURCE_IO | IORESOURCE_MEM,&mdev->modern_bars);if (!isr || !notify) {dev_err(&pci_dev->dev,"virtio_pci: missing capabilities %i/%i/%i\n",common, isr, notify);return -EINVAL;}err = dma_set_mask_and_coherent(&pci_dev->dev, DMA_BIT_MASK(64));if (err)err = dma_set_mask_and_coherent(&pci_dev->dev,DMA_BIT_MASK(32));if (err)dev_warn(&pci_dev->dev, "Failed to enable 64-bit or 32-bit DMA.  Trying to continue, but this might not work.\n");/* Device capability is only mandatory for devices that have* device-specific configuration.*/device = virtio_pci_find_capability(pci_dev, VIRTIO_PCI_CAP_DEVICE_CFG,IORESOURCE_IO | IORESOURCE_MEM,&mdev->modern_bars);

这也是在(QEMU源码)virtio_pci_device_plugged函数中写入到virtio PCI代理设备的配置空间中的,参见QEMU源码全解析 —— virtio(14)和QEMU源码全解析 —— virtio(15)。

(3)virtio_pci_find_capability函数找到所属的PCI BAR,然后写入到virt_pci_device的modern_bars成员中。代码片段如下:

    /* Device capability is only mandatory for devices that have* device-specific configuration.*/device = virtio_pci_find_capability(pci_dev, VIRTIO_PCI_CAP_DEVICE_CFG,IORESOURCE_IO | IORESOURCE_MEM,&mdev->modern_bars);

从(QEMU源码)virtio_pci_realize函数中可以知道这个modern_bars是1<<4,如下图所示:

(4)接着,pci_request_selected_regions函数就将virtio PCI代理设备的BAR地址空间保留出来了。代码片段如下:

    err = pci_request_selected_regions(pci_dev, mdev->modern_bars,"virtio-pci-modern");if (err)return err;

(5)调用vp_modern_map_capability函数将对应的capability在PCI代理设备中的BAR空间映射到内核地址空间。代码片段如下:

    err = -EINVAL;mdev->common = vp_modern_map_capability(mdev, common,sizeof(struct virtio_pci_common_cfg), 4,0, sizeof(struct virtio_pci_common_cfg),NULL, NULL);if (!mdev->common)goto err_map_common;mdev->isr = vp_modern_map_capability(mdev, isr, sizeof(u8), 1,0, 1,NULL, NULL);if (!mdev->isr)goto err_map_isr;/* Read notify_off_multiplier from config space. */pci_read_config_dword(pci_dev,notify + offsetof(struct virtio_pci_notify_cap,notify_off_multiplier),&mdev->notify_offset_multiplier);/* Read notify length and offset from config space. */pci_read_config_dword(pci_dev,notify + offsetof(struct virtio_pci_notify_cap,cap.length),&notify_length);pci_read_config_dword(pci_dev,notify + offsetof(struct virtio_pci_notify_cap,cap.offset),&notify_offset);/* We don't know how many VQs we'll map, ahead of the time.* If notify length is small, map it all now.* Otherwise, map each VQ individually later.*/if ((u64)notify_length + (notify_offset % PAGE_SIZE) <= PAGE_SIZE) {mdev->notify_base = vp_modern_map_capability(mdev, notify,2, 2,0, notify_length,&mdev->notify_len,&mdev->notify_pa);if (!mdev->notify_base)goto err_map_notify;} else {mdev->notify_map_cap = notify;}/* Again, we don't know how much we should map, but PAGE_SIZE* is more than enough for all existing devices.*/if (device) {mdev->device = vp_modern_map_capability(mdev, device, 0, 4,0, PAGE_SIZE,&mdev->device_len,NULL);if (!mdev->device)goto err_map_device;}

如mp_dev(struct virtio_pci_modern_device *mdev = &vp_dev->mdev;)的common成员映射了virtio_pci_common_cfg的数据到内核中。这样,后续就可以直接通过这个内存地址空间来访问common这一capability了,其它的capability(isr、notify、device)也与此类似。

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

/** vp_modern_map_capability - map a part of virtio pci capability* @mdev: the modern virtio-pci device* @off: offset of the capability* @minlen: minimal length of the capability* @align: align requirement* @start: start from the capability* @size: map size* @len: the length that is actually mapped* @pa: physical address of the capability** Returns the io address of for the part of the capability*/
static void __iomem *
vp_modern_map_capability(struct virtio_pci_modern_device *mdev, int off,size_t minlen, u32 align, u32 start, u32 size,size_t *len, resource_size_t *pa)
{struct pci_dev *dev = mdev->pci_dev;u8 bar;u32 offset, length;void __iomem *p;pci_read_config_byte(dev, off + offsetof(struct virtio_pci_cap,bar),&bar);pci_read_config_dword(dev, off + offsetof(struct virtio_pci_cap, offset),&offset);pci_read_config_dword(dev, off + offsetof(struct virtio_pci_cap, length),&length);/* Check if the BAR may have changed since we requested the region. */if (bar >= PCI_STD_NUM_BARS || !(mdev->modern_bars & (1 << bar))) {dev_err(&dev->dev,"virtio_pci: bar unexpectedly changed to %u\n", bar);return NULL;}if (length <= start) {dev_err(&dev->dev,"virtio_pci: bad capability len %u (>%u expected)\n",length, start);return NULL;}if (length - start < minlen) {dev_err(&dev->dev,"virtio_pci: bad capability len %u (>=%zu expected)\n",length, minlen);return NULL;}length -= start;if (start + offset < offset) {dev_err(&dev->dev,"virtio_pci: map wrap-around %u+%u\n",start, offset);return NULL;}offset += start;if (offset & (align - 1)) {dev_err(&dev->dev,"virtio_pci: offset %u not aligned to %u\n",offset, align);return NULL;}if (length > size)length = size;if (len)*len = length;if (minlen + offset < minlen ||minlen + offset > pci_resource_len(dev, bar)) {dev_err(&dev->dev,"virtio_pci: map virtio %zu@%u ""out of range on bar %i length %lu\n",minlen, offset,bar, (unsigned long)pci_resource_len(dev, bar));return NULL;}p = pci_iomap_range(dev, bar, offset, length);if (!p)dev_err(&dev->dev,"virtio_pci: unable to map virtio %u@%u on bar %i\n",length, offset, bar);else if (pa)*pa = pci_resource_start(dev, bar) + offset;return p;
}

这样实际上就将virtio PCI代理设备的BAR映射到虚拟机内核地址空间了,后续直接访问这些地址即可实现对virtio PCI代理设备的配置和控制。

回到virtio_pci_modern_probe函数。

/* the PCI probing function */
int virtio_pci_modern_probe(struct virtio_pci_device *vp_dev)
{struct virtio_pci_modern_device *mdev = &vp_dev->mdev;struct pci_dev *pci_dev = vp_dev->pci_dev;int err;mdev->pci_dev = pci_dev;err = vp_modern_probe(mdev);if (err)return err;if (mdev->device)vp_dev->vdev.config = &virtio_pci_config_ops;elsevp_dev->vdev.config = &virtio_pci_config_nodev_ops;vp_dev->config_vector = vp_config_vector;vp_dev->setup_vq = setup_vq;vp_dev->del_vq = del_vq;vp_dev->isr = mdev->isr;vp_dev->vdev.id = mdev->id;return 0;
}

在调用完vp_modern_probe函数之后,virtio_pci_modern_probe函数接着设置virtio_pci_device中virtio_device的成员vdev的config成员。如果有device这一capability,则设置为virtio_pci_config_ops,否则设置为virtio_pci_config_nodev_ops。

之后设置vpdev即struct virtio_pci_device的几个回调函数:config_vector与MSI中断有关,设置为vp_config_vector;setup_vq用来配置virtio设备virt queue,设置为setup_vq;del_vq用来删除virt queue,设置为del_vq。

至此,virtio_pci_modern_probe函数就解析完了。

欲知后事如何,且看下回分解。

这篇关于QEMU源码全解析 —— virtio(19)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/519787

相关文章

网页解析 lxml 库--实战

lxml库使用流程 lxml 是 Python 的第三方解析库,完全使用 Python 语言编写,它对 XPath表达式提供了良好的支 持,因此能够了高效地解析 HTML/XML 文档。本节讲解如何通过 lxml 库解析 HTML 文档。 pip install lxml lxm| 库提供了一个 etree 模块,该模块专门用来解析 HTML/XML 文档,下面来介绍一下 lxml 库

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL

如何在Visual Studio中调试.NET源码

今天偶然在看别人代码时,发现在他的代码里使用了Any判断List<T>是否为空。 我一般的做法是先判断是否为null,再判断Count。 看了一下Count的源码如下: 1 [__DynamicallyInvokable]2 public int Count3 {4 [__DynamicallyInvokable]5 get

工厂ERP管理系统实现源码(JAVA)

工厂进销存管理系统是一个集采购管理、仓库管理、生产管理和销售管理于一体的综合解决方案。该系统旨在帮助企业优化流程、提高效率、降低成本,并实时掌握各环节的运营状况。 在采购管理方面,系统能够处理采购订单、供应商管理和采购入库等流程,确保采购过程的透明和高效。仓库管理方面,实现库存的精准管理,包括入库、出库、盘点等操作,确保库存数据的准确性和实时性。 生产管理模块则涵盖了生产计划制定、物料需求计划、

OWASP十大安全漏洞解析

OWASP(开放式Web应用程序安全项目)发布的“十大安全漏洞”列表是Web应用程序安全领域的权威指南,它总结了Web应用程序中最常见、最危险的安全隐患。以下是对OWASP十大安全漏洞的详细解析: 1. 注入漏洞(Injection) 描述:攻击者通过在应用程序的输入数据中插入恶意代码,从而控制应用程序的行为。常见的注入类型包括SQL注入、OS命令注入、LDAP注入等。 影响:可能导致数据泄

从状态管理到性能优化:全面解析 Android Compose

文章目录 引言一、Android Compose基本概念1.1 什么是Android Compose?1.2 Compose的优势1.3 如何在项目中使用Compose 二、Compose中的状态管理2.1 状态管理的重要性2.2 Compose中的状态和数据流2.3 使用State和MutableState处理状态2.4 通过ViewModel进行状态管理 三、Compose中的列表和滚动

Spring 源码解读:自定义实现Bean定义的注册与解析

引言 在Spring框架中,Bean的注册与解析是整个依赖注入流程的核心步骤。通过Bean定义,Spring容器知道如何创建、配置和管理每个Bean实例。本篇文章将通过实现一个简化版的Bean定义注册与解析机制,帮助你理解Spring框架背后的设计逻辑。我们还将对比Spring中的BeanDefinition和BeanDefinitionRegistry,以全面掌握Bean注册和解析的核心原理。

CSP 2023 提高级第一轮 CSP-S 2023初试题 完善程序第二题解析 未完

一、题目阅读 (最大值之和)给定整数序列 a0,⋯,an−1,求该序列所有非空连续子序列的最大值之和。上述参数满足 1≤n≤105 和 1≤ai≤108。 一个序列的非空连续子序列可以用两个下标 ll 和 rr(其中0≤l≤r<n0≤l≤r<n)表示,对应的序列为 al,al+1,⋯,ar​。两个非空连续子序列不同,当且仅当下标不同。 例如,当原序列为 [1,2,1,2] 时,要计算子序列 [