QEMU pcie_host 如何将物理地址转换为 pcie 地址

如何解决QEMU pcie_host 如何将物理地址转换为 pcie 地址

我正在学习 QEMU 的实现。这里我有一个问题:众所周知,在实际硬件中,当cpu读取虚拟地址即pci设备的地址时,pci主机会负责将其转换为pci的地址。而QEMU,提供了pcie_host.c来模仿pcie主机。在这个文件中,实现了pcie_mmcfg_data_write,但没有关于物理地址到pci地址的转换。

我在 QEMU 中使用 gdb 进行了测试:

  • 首先,我在 qemu 中添加了 edu device,这是一个非常简单的 pci 设备。
  • 当我尝试打开 Memory Space Enable,(Mem- to Mem+):septic -s 00:02.0 04.b=2 时,qemu 在函数 pcie_mmcfg_data_write 中停止。
static void pcie_mmcfg_data_write(void *opaque,hwaddr mmcfg_addr,uint64_t val,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;
    }
    addr = PCIE_MMCFG_CONFOFFSET(mmcfg_addr);
    limit = pci_config_size(pci_dev);
    pci_host_config_write_common(pci_dev,addr,limit,val,len);
}

很明显,pcie host就是用这个函数来找设备的。 使用bt可以获得:

#0  pcie_mmcfg_data_write
    (opaque=0xaaaaac573f10,mmcfg_addr=65540,val=2,len=1)
    at hw/pci/pcie_host.c:39
#1  0x0000aaaaaae4e8a8 in memory_region_write_accessor
    (mr=0xaaaaac574520,addr=65540,value=0xffffe14703e8,size=1,shift=0,mask=255,attrs=...) 
    at /home/mrzleo/Desktop/qemu/memory.c:483
#2  0x0000aaaaaae4eb14 in access_with_adjusted_size
    (addr=65540,access_size_min=1,access_size_max=4,access_fn=
    0xaaaaaae4e7c0 <memory_region_write_accessor>,mr=0xaaaaac574520,attrs=...) at /home/mrzleo/Desktop/qemu/memory.c:544
#3  0x0000aaaaaae51898 in memory_region_dispatch_write
    (mr=0xaaaaac574520,data=2,op=MO_8,attrs=...)
    at /home/mrzleo/Desktop/qemu/memory.c:1465
#4  0x0000aaaaaae72410 in io_writex
    (env=0xaaaaac6924e0,iotlbentry=0xffff000e9b00,mmu_idx=2,addr=18446603336758132740,retaddr=281473269319356,op=MO_8)
    at /home/mrzleo/Desktop/qemu/accel/tcg/cputlb.c:1084
#5  0x0000aaaaaae74854 in store_helper
    (env=0xaaaaac6924e0,oi=2,op=MO_8) 
    at /home/mrzleo/Desktop/qemu/accel/tcg/cputlb.c:1954
#6  0x0000aaaaaae74d78 in helper_ret_stb_mmu
    (env=0xaaaaac6924e0,val=2 '\002',retaddr=281473269319356) 
    at /home/mrzleo/Desktop/qemu/accel/tcg/cputlb.c:2056
#7  0x0000ffff9a3b47cc in code_gen_buffer ()
#8  0x0000aaaaaae8d484 in cpu_tb_exec
    (cpu=0xaaaaac688c00,itb=0xffff945691c0 <code_gen_buffer+5673332>)
    at /home/mrzleo/Desktop/qemu/accel/tcg/cpu-exec.c:172
#9  0x0000aaaaaae8e4ec in cpu_loop_exec_tb
    (cpu=0xaaaaac688c00,tb=0xffff945691c0 <code_gen_buffer+5673332>,last_tb=0xffffe1470b78,tb_exit=0xffffe1470b70)
    at /home/mrzleo/Desktop/qemu/accel/tcg/cpu-exec.c:619
#10 0x0000aaaaaae8e830 in cpu_exec (cpu=0xaaaaac688c00)
    at /home/mrzleo/Desktop/qemu/accel/tcg/cpu-exec.c:732
#11 0x0000aaaaaae3d43c in tcg_cpu_exec (cpu=0xaaaaac688c00)
    at /home/mrzleo/Desktop/qemu/cpus.c:1405
#12 0x0000aaaaaae3dd4c in qemu_tcg_cpu_thread_fn (arg=0xaaaaac688c00)
    at /home/mrzleo/Desktop/qemu/cpus.c:1713
#13 0x0000aaaaab722c70 in qemu_thread_start (args=0xaaaaac715be0)
    at util/qemu-thread-posix.c:519
#14 0x0000fffff5af84fc in start_thread (arg=0xffffffffe3ff)
    at pthread_create.c:477
#15 0x0000fffff5a5167c in thread_start ()
    at ../sysdeps/unix/sysv/linux/aarch64/clone.S:78
  • 然后我尝试访问 edu 的地址:devmem 0x10000000 qemu 停在 edu_mmio_read。使用bt
(gdb) bt
#0  edu_mmio_read 
    (opaque=0xaaaaae71c560,addr=0,size=4) 
        at hw/misc/edu.c:187
#1  0x0000aaaaaae4e5b4 in memory_region_read_accessor
    (mr=0xaaaaae71ce50,value=0xffffe2472438,size=4,mask=4294967295,attrs=...)
    at /home/mrzleo/Desktop/qemu/memory.c:434
#2  0x0000aaaaaae4eb14 in access_with_adjusted_size
    (addr=0,access_size_min=4,access_size_max=8,access_fn=
    0xaaaaaae4e570 <memory_region_read_accessor>,mr=0xaaaaae71ce50,attrs=...) 
    at /home/mrzleo/Desktop/qemu/memory.c:544
#3  0x0000aaaaaae51524 in memory_region_dispatch_read1 
(mr=0xaaaaae71ce50,pval=0xffffe2472438,attrs=...)
    at /home/mrzleo/Desktop/qemu/memory.c:1385
#4  0x0000aaaaaae51600 in memory_region_dispatch_read 
(mr=0xaaaaae71ce50,op=MO_32,attrs=...)
    at /home/mrzleo/Desktop/qemu/memory.c:1413
#5  0x0000aaaaaae72218 in io_readx
    (env=0xaaaaac6be0f0,iotlbentry=0xffff04282ec0,mmu_idx=0,addr=281472901758976,retaddr=281473196263360,access_type=MMU_DATA_LOAD,op=MO_32) 
    at /home/mrzleo/Desktop/qemu/accel/tcg/cputlb.c:1045
#6  0x0000aaaaaae738b0 in load_helper
    (env=0xaaaaac6be0f0,oi=32,code_read=false,full_load=0xaaaaaae73c68 <full_le_ldul_mmu>) 
    at /home/mrzleo/Desktop/qemu/accel/tcg/cputlb.c:1566
#7  0x0000aaaaaae73ca4 in full_le_ldul_mmu 
(env=0xaaaaac6be0f0,retaddr=281473196263360)
    at /home/mrzleo/Desktop/qemu/accel/tcg/cputlb.c:1662
#8  0x0000aaaaaae73cd8 in helper_le_ldul_mmu 
(env=0xaaaaac6be0f0,retaddr=281473196263360)
    at /home/mrzleo/Desktop/qemu/accel/tcg/cputlb.c:1669
#9  0x0000ffff95e08824 in code_gen_buffer 
()
#10 0x0000aaaaaae8d484 in cpu_tb_exec 
(cpu=0xaaaaac6b4810,itb=0xffff95e086c0 <code_gen_buffer+31491700>)
    at /home/mrzleo/Desktop/qemu/accel/tcg/cpu-exec.c:172
#11 0x0000aaaaaae8e4ec in cpu_loop_exec_tb
    (cpu=0xaaaaac6b4810,tb=0xffff95e086c0 <code_gen_buffer+31491700>,last_tb=0xffffe2472b78,tb_exit=0xffffe2472b70)
    at /home/mrzleo/Desktop/qemu/accel/tcg/cpu-exec.c:619
#12 0x0000aaaaaae8e830 in cpu_exec 
(cpu=0xaaaaac6b4810) at /home/mrzleo/Desktop/qemu/accel/tcg/cpu-exec.c:732
#13 0x0000aaaaaae3d43c in tcg_cpu_exec 
(cpu=0xaaaaac6b4810) at /home/mrzleo/Desktop/qemu/cpus.c:1405
#14 0x0000aaaaaae3dd4c in qemu_tcg_cpu_thread_fn 
(arg=0xaaaaac6b4810) 
    at /home/mrzleo/Desktop/qemu/cpus.c:1713
#15 0x0000aaaaab722c70 in qemu_thread_start (args=0xaaaaac541610) at util/qemu-thread-posix.c:519
#16 0x0000fffff5af84fc in start_thread (arg=0xffffffffe36f) at pthread_create.c:477
#17 0x0000fffff5a5167c in thread_start () at ../sysdeps/unix/sysv/linux/aarch64/clone.S:78

好像qemu只是直接定位到edu设备,pcie host在这个过程中什么都不做。不知道是不是qemu这里没有实现转换,只是用memoryRegion来实现多态?如果不是,QEMU的pcie主机在这个过程中是怎么做的?

解决方法

QEMU 使用一组称为 MemoryRegions 的数据结构来模拟 CPU 看到的地址空间(详细的 API 记录在 in part in the developer docs 中)。

MemoryRegions 可以构建成一棵树,其中在“根”处有一个“容器”MR,它覆盖了来宾 CPU 可以看到的整个 64 位地址空间,然后是 RAM 块、设备、等以适当的偏移量放置到该根 MR 中。子 MR 也可以是容器,而容器又包含更多 MR。然后,您可以通过遍历 MR 树找到与给定访客物理地址对应的 MR。

MemoryRegions 树主要是在 QEMU 启动时静态构建的(因为大多数设备不会四处移动),但它也可以动态更改以响应客户软件操作。特别是,PCI 以这种方式工作。当客户操作系统写入 PCI 设备 BAR(在 PCI 配置空间中)时,这会导致 QEMU 的 PCI 主机控制器仿真代码将与设备寄存器对应的 MR 放入 MemoryRegion 层次结构中的正确位置和偏移量(取决于什么地址)客人写信给 BAR,即它要求映射它的地方)。完成此操作后,PCI 设备的 MR 就像树中的任何其他设备一样,并且 PCI 主机控制器代码不需要参与访客对其的访问。

作为性能优化,QEMU 实际上并没有为每次访问都沿着一棵 MR 树向下走。相反,我们首先将树“展平”成一个数据结构(一个 FlatView),它直接说“对于这个地址范围,它将是这个 MR;对于这个范围;这个 MR”,等等。其次,QEMU 的 TLB 结构可以直接缓存从“来宾虚拟地址”到“特定内存区域”的映射。在第一次访问时,它将执行模拟的访客 MMU 页表遍历以从访客虚拟地址获取访客物理地址,然后它将在 FlatView 中查找该物理地址以找到真正的主机 RAM 或 MemoryRegion映射到那里,它会将“guest VA -> this MR”映射添加到 TLB 缓存中。未来的访问将在 TLB 中命中,并且不需要重复转换为 physaddr 然后在 flatmap 中找到 MR 的工作。这就是你的回溯中发生的事情——io_readx() 函数被传递来宾虚拟地址以及 TLB 数据结构的相关部分,然后它可以直接找到目标 MR 和其中的偏移量,因此它可以调用 memory_region_dispatch_read() 将读取请求分派到该 MR 的读取回调函数。 (如果这是第一次访问,那么在 load_helper() 调用 io_readx() 之前,最初的“MMU walk + FlatView 查找”工作将刚刚完成。)

显然,所有这些缓存也意味着 QEMU 跟踪事件,这意味着缓存的数据不再有效,因此我们可以将其丢弃(例如,如果来宾再次写入 BAR 以取消映射或将其映射到其他地方;或如果更改了 MMU 设置或页表以更改来宾虚拟到物理映射)。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-