QEMU pcie config空间访问机制

这篇具有很好参考价值的文章主要介绍了QEMU pcie config空间访问机制。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、PCIE config空间

pci设备的config空间只有256字节,X86架构下是通过两个IO端口访问的,0xCF8/0xCFC端口,分别用于选通地址和传输数据。当前大部分设备都是pcie设备,config空间扩展到了4KB,而对于[256-4096)的扩展config空间,X86是通过memory映射的方式访问,并非IO端口的形式。也就是X86会把pcie的config空间映射到一片memory空间,访问这片空间的时候RC就会发出config tlp报文。

这是真实的硬件设计,而对于QEMU+KVM的虚机场景,显然是要基于硬件实现和虚拟化的需求设计虚机访问config空间的完整流程。

文章对于qemu的虚机memory管理机制不单独介绍。

二、config空间(前256B)的模拟

这一部分的模拟,首先从hw/pci-host/q35.c查看。q35是一种常用的X86机型,当前qemu里的X86机型都是使用q35来模拟。

qemu-system-x86_64 -machine q35,accel=kvm -cpu host -smp sockets=2,cores=2,threads=1 -m 4096

具体的q35的machine模拟初始化接口,里面代码很多,我们只关注pci host主桥的初始化。在pc_q35_init接口里直接调用qdev_new()接口(非命令行显式创建)实例化一个host主桥,设备类型为TYPE_Q35_HOST_DEVICE,这个设备的驱动定义在hw/pci-host/q35.c里。

/* PC hardware initialisation */
static void pc_q35_init(MachineState *machine)
{
    /* create pci host bus */
    q35_host = Q35_HOST_DEVICE(qdev_new(TYPE_Q35_HOST_DEVICE));
}

在TYPE_Q35_HOST_DEVICE的realize接口中,对0xCF8/0xCFC两个端口进行了注册,注册到conf_mem和data_mem两个MemoryRegion里。

static void q35_host_realize(DeviceState *dev, Error **errp)
{
    sysbus_add_io(sbd, MCH_HOST_BRIDGE_CONFIG_ADDR, &pci->conf_mem);
    sysbus_init_ioports(sbd, MCH_HOST_BRIDGE_CONFIG_ADDR, 4);

    sysbus_add_io(sbd, MCH_HOST_BRIDGE_CONFIG_DATA, &pci->data_mem);
    sysbus_init_ioports(sbd, MCH_HOST_BRIDGE_CONFIG_DATA, 4);
}

在TYPE_Q35_HOST_DEVICE的instance_init接口中,添加了0xCF8/0xCFC端口对应的MemoryRegion的处理函数。


const MemoryRegionOps pci_host_conf_le_ops = {
    .read = pci_host_config_read,
    .write = pci_host_config_write,
    .endianness = DEVICE_LITTLE_ENDIAN,
};

const MemoryRegionOps pci_host_data_le_ops = {
    .read = pci_host_data_read,
    .write = pci_host_data_write,
    .endianness = DEVICE_LITTLE_ENDIAN,
};

    memory_region_init_io(&phb->conf_mem, obj, &pci_host_conf_le_ops, phb,
                          "pci-conf-idx", 4);
    memory_region_init_io(&phb->data_mem, obj, &pci_host_data_le_ops, phb,
                          "pci-conf-data", 4);

而pci_host_conf/data_le_ops的定义是在hw/pci/pci-host.c里,对于0xCF8端口的处理,因为是地址选通,所以只需要在write的时候记录、read的时候返回记录的值即可。

对于0xCFC端口的处理逻辑,则是实际的数据访问,访问地址就存储在0xCF8端口write操作记录的地址里。重点贴一下pci_dev_find_by_addr的代码,这个接口体现了啥呢,0xCF8端口地址的组织形式,包含了bdf号+config空间offset。所以可以根据这个地址获取到bdf号,然后根据bdf号去找到PCIDevice结构。

找到PCIDevice结构后,pci_host_config_write_common接口可以获取到config空间的内容。这里就不再赘述了。pci_default_write_config和pci_default_read_config接口是PCI_DEV层默认的config read和write接口。当然具体的设备也可以根据实际的需求设置自己的read/write接口。

/*
 * PCI address
 * bit 16 - 24: bus number
 * bit  8 - 15: devfun number
 * bit  0 -  7: offset in configuration space of a given pci device
 */

/* the helper function to get a PCIDevice* for a given pci address */
static inline PCIDevice *pci_dev_find_by_addr(PCIBus *bus, uint32_t addr)
{
    uint8_t bus_num = addr >> 16;
    uint8_t devfn = addr >> 8;

    return pci_find_device(bus, bus_num, devfn);
}

void pci_data_write(PCIBus *s, uint32_t addr, uint32_t val, unsigned len)
{
    uint8_t bus_num = 0xff;
    uint8_t devfn = 0xff;
    uint32_t bdf = 0;
    uint8_t tlp_type = 0xff;

    PCIDevice *pci_dev = pci_dev_find_by_addr(s, addr);
    uint32_t config_addr = addr & (PCI_CONFIG_SPACE_SIZE - 1);
    if (!pci_dev) {
        return;
    }

   pci_host_config_write_common(pci_dev, config_addr, PCI_CONFIG_SPACE_SIZE,
                                 val, len);
}

static void pci_host_data_write(void *opaque, hwaddr addr,
                                uint64_t val, unsigned len)
{
    PCIHostState *s = opaque;

    if (s->config_reg & (1u << 31))
        pci_data_write(s->bus, s->config_reg | (addr & 3), val, len);
}

根据bdf号查到PCIDevice的路径也顺便记录一下。其实大概可以看出QEMU里PCIBus、PCIDevice的基于BDF号的树状组织结构。每个PCIBus都会有child链表,遍历的时候都是向下遍历,从PCIHostState里指向的root bus向下遍历,直到找到对应bus number的bus。找到准确的PCIBus之后,每个PCIBus里包含256个PCIDevice的指针,进而根据devfn索引到每一个PCIDevice。

PCIDevice *pci_find_device(PCIBus *bus, int bus_num, uint8_t devfn)
{
    bus = pci_find_bus_nr(bus, bus_num);

    if (!bus)
        return NULL;

    return bus->devices[devfn];
}

static PCIBus *pci_find_bus_nr(PCIBus *bus, int bus_num)
{
    PCIBus *sec;

    if (!bus) {
        return NULL;
    }

    if (pci_bus_num(bus) == bus_num) {
        return bus;
    }

    /* Consider all bus numbers in range for the host pci bridge. */
    if (!pci_bus_is_root(bus) &&
        !pci_secondary_bus_in_range(bus->parent_dev, bus_num)) {
        return NULL;
    }

    /* try child bus */
    for (; bus; bus = sec) {
        QLIST_FOREACH(sec, &bus->child, sibling) {
            if (pci_bus_num(sec) == bus_num) {  //PCI_SECONDARY_BUS匹配,说明设备就是挂在当前的bus下,直接返回当前的bus
                return sec;
            }
            /* PXB buses assumed to be children of bus 0 */
            if (pci_bus_is_root(sec)) {
                if (pci_root_bus_in_range(sec, bus_num)) {
                    break;
                }
            } else {
                if (pci_secondary_bus_in_range(sec->parent_dev, bus_num)) {  //secondary in range,说明在下一级child bus里,break退出内层循环QLIST_FOREACH
                    break;
                }
            }
        }
    }

    return NULL;
}

三、扩展config空间的模拟

上一节是针对前256字节使用IO端口0xCF8/CFC端口访问的config空间访问。本节是针对采用memory映射机制访问的扩展config空间访问实现。这一部分的入口是在hw/pci/pcie-host.c里了。


static const MemoryRegionOps pcie_mmcfg_ops = {
    .read = pcie_mmcfg_data_read,
    .write = pcie_mmcfg_data_write,
    .endianness = DEVICE_LITTLE_ENDIAN,
};

static void pcie_host_init(Object *obj)
{
    PCIExpressHost *e = PCIE_HOST_BRIDGE(obj);

    e->base_addr = PCIE_BASE_ADDR_UNMAPPED;
    memory_region_init_io(&e->mmio, OBJECT(e), &pcie_mmcfg_ops, e, "pcie-mmcfg-mmio",
                          PCIE_MMCFG_SIZE_MAX);
}

而pcie_mmcfg_data_read/write接口里的处理形式也跟I/O端口访问的接口处理流程相似。都是先查找到PCIDevice,然后访问其config空间。

IO端口访问形式的config端口里会带有设备的BDF号。神奇的是,memory映射地址也可以直接索引到设备的BDF号,使用gdb跟踪qemu梳理了一下。看起来是X86架构会自动将一片连续的地址空间大小为0x10000000(4K*2^16个设备)分配给config空间的memory地址映射使用,每个设备分配的config空间地址是按照BDF号平铺的。根据地址在地址空间内的的offset,再除以4KB就得到了BDF号。

实际看代码,这种索引机制还给BUS号多分配了1个bit=9bit。具体还得再看看原因。

/*
 * PCI express ECAM (Enhanced Configuration Address Mapping) format.
 * AKA mmcfg address
 * bit 20 - 28: bus number
 * bit 15 - 19: device number
 * bit 12 - 14: function number
 * bit  0 - 11: offset in configuration space of a given device
 */
#define PCIE_MMCFG_SIZE_MAX             (1ULL << 29)
#define PCIE_MMCFG_SIZE_MIN             (1ULL << 20)
#define PCIE_MMCFG_BUS_BIT              20
#define PCIE_MMCFG_BUS_MASK             0x1ff
#define PCIE_MMCFG_DEVFN_BIT            12
#define PCIE_MMCFG_DEVFN_MASK           0xff
#define PCIE_MMCFG_CONFOFFSET_MASK      0xfff
#define PCIE_MMCFG_BUS(addr)            (((addr) >> PCIE_MMCFG_BUS_BIT) & \
                                         PCIE_MMCFG_BUS_MASK)
#define PCIE_MMCFG_DEVFN(addr)          (((addr) >> PCIE_MMCFG_DEVFN_BIT) & \
                                         PCIE_MMCFG_DEVFN_MASK)

/* a helper function to get a PCIDevice for a given mmconfig address */
static inline PCIDevice *pcie_dev_find_by_mmcfg_addr(PCIBus *s,
                                                     uint32_t mmcfg_addr)
{
    return pci_find_device(s, PCIE_MMCFG_BUS(mmcfg_addr),
                           PCIE_MMCFG_DEVFN(mmcfg_addr));
}

static uint64_t pcie_mmcfg_data_read(void *opaque,
                                     hwaddr mmcfg_addr,
                                     unsigned len)
{
    PCIExpressHost *e = opaque;
    PCIBus *s = e->pci.bus;
    PCIDevice *pci_dev = pcie_dev_find_by_mmcfg_addr(s, mmcfg_addr);
    uint32_t addr;
    uint32_t limit;

    if (!pci_dev) {
        return ~0x0;
    }
    limit = pci_config_size(pci_dev);

    return pci_host_config_read_common(pci_dev, addr, limit, len);
}

至于从BDF号索引到PCIDevice的过程,在前256Bconfig空间的模拟中已经介绍过了,是一个从root bus向下索引的过程。

而pci_host_config_write/read_common接口,在前256Bconfig空间的模拟中,也同样是会调用这个接口,所以后续的处理都是一样的。只是入口处路径不一致。文章来源地址https://www.toymoban.com/news/detail-764867.html

到了这里,关于QEMU pcie config空间访问机制的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Qemu 逃逸基础知识

    QEMU 与 KVM 的完整架构如下图所示。 QEMU 与 KVM 架构整体上分为 3 个部分: VMX root 模式的应用层,即图中左上部分,属于 qemu 进程。 VMX root 模式的内核层,即图中下半部分,属于 kvm 驱动。 VMX non-root模式下的虚拟机,即图中右上部分,属于运行中的客户机。 其中 VMX root 和 V

    2024年02月08日
    浏览(27)
  • 使用QEMU运行虚拟机

    1,get kernel source from here 2,将当前guest os的内核配置文件 /boot/config-5.10.0 拷贝至内核源码目录并命名为.config 3,执行命令“make rpm-pkg -j10” 将内核各个组件编译为RPM包 4,生成的RPM包位于“/root/rpmbuild/RPMS/aarch64/”: kernel-headers-5.10.0-1.aarch64.rpm, kernel-devel-5.10.0-1.aarch64.rpm, kernel-5

    2024年02月05日
    浏览(38)
  • QEMU运行openBMC

    在Ubuntu 编译服务器自己的目录中执行命令: wget https://jenkins.openbmc.org/job/latest-qemu-x86/lastSuccessfulBuild/artifact/qemu/build/qemu-system-arm 给QEMU 执行权限: chmod u+x qemu-system-arm cp build/evb-ast2500/tmp/deploy/images/evb-ast2500/obmc-phosphor-image-evb-ast2500-20230831101603.static.mtd ./ ./qemu-system-arm -m 256 -M ro

    2024年02月10日
    浏览(49)
  • qemu源码解析一

    QEMU是一个开源的虚拟化软件,它能够模拟各种硬件设备,支持多种虚拟化技术,如TCG、Xen、KVM等 TCG 是 QEMU 中的一个组件,它可以将高级语言编写的代码(例如 C 代码)转换为可在虚拟机中执行的低级代码(例如 x86 机器指令)。TCG 生成的代码通常比直接使用 CPU 指令更简单

    2024年04月12日
    浏览(31)
  • QEMU tap数据接收流程

    QEMU tap数据接收步骤: qemu从tun取数据包 qemu将数据包放入virtio硬件网卡。 qemu触发中断。 虚拟机收到中断,从virtio读取数据。 在qemu中步骤1(tap_read_packet)和步骤2(qemu_send_packet_async)都是在tap_send中完成的,其中步骤2是异步流程。 qemu通过qemu_net_queue_deliver将数据包发送到v

    2024年02月11日
    浏览(35)
  • 使用QEMU模拟启动uboot

    uboot的相关知识,可以参考:uboot基本概念。 WSL: ubutu20.04 模拟开发板:vexpress-a9 uboot版本:u-boot-2023.10 2.1、安装 2.2、查看支持哪些开发板 结果如下: 注:此步非必须,也可自己从Arm GNU Toolchain下载,解压后添加到环境变量即可。 4.1、解压 4.2、编译 4.3、生成的u-boot.bin文件

    2024年02月06日
    浏览(43)
  • Qemu搭建arm版麒麟系统

    博主 默语带您 Go to New World. ✍ 个人主页—— 默语 的博客👦🏻 《java 面试题大全》 🍩惟余辈才疏学浅,临摹之作或有不妥之处,还请读者海涵指正。☕🍭 《MYSQL从入门到精通》数据库是开发者必会基础之一~ 🪁 吾期望此文有资助于尔,即使粗浅难及深广,亦备添少许微薄

    2024年02月02日
    浏览(33)
  • QEMU源码全解析 —— virtio(20)

    接前一篇文章: 上回书重点解析了virtio_pci_modern_probe函数。再来回顾一下其中相关的数据结构: struct virtio_pci_device struct virtio_pci_device的定义在Linux内核源码/drivers/virtio/virtio_pci_common.h中,如下: virtio_pci_modern_probe执行完成后,相关数据结构如下图所示: 回到virtio_pci_probe函数

    2024年02月21日
    浏览(32)
  • 【QEMU系统分析之启动篇(二十)】

    本文以 QEMU 8.2.2 为例,分析其作为系统仿真工具的启动过程,并为读者展示各种 QEMU 系统仿真的启动配置实例。 本文读者需要具备一定的 QEMU 系统仿真使用经验,并对 C 语言编程有一定了解。 QEMU 是一个通用且开源的机器模拟器和虚拟机。 其官方主页是:https://www.qemu.org/

    2024年04月27日
    浏览(32)
  • QEMU源码全解析38 —— Machine(8)

    接前一篇文章:QEMU源码全解析37 —— Machine(7) 本文内容参考: 《趣谈Linux操作系统》 —— 刘超,极客时间 《QEMU/KVM》源码解析与应用 —— 李强,机械工业出版社 特此致谢! 上一回经过了重重周折,终于找到了MACHINE的定义所在: 本回就详细解析一下这段代码。别看代码

    2024年02月12日
    浏览(28)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包