使用uint64_t指针通过mmap读取PCI CSR失败,求排查原因
问题分析与解决方案
你的问题核心是PCI配置空间的MMIO访问不支持原子64位读取,导致CPU发起64位读指令时硬件返回错误,内核将错误转化为全0xffffffffffffffff的返回值;而gdb的内存查看命令是通过非原子的多字节读取拼接结果,因此能规避硬件限制,显示正确值。
具体原因
PCI传统配置空间本身是32位粒度设计,即便通过MMIO映射到内存地址空间,绝大多数硬件也仅响应32位读请求。当你用*(uint64_t *)map_address发起64位原子读操作时,硬件无法处理该请求,触发总线错误,内核为标记这种错误,返回全1的数值。
而gdb的x/1xg命令并非生成64位CPU读指令,而是分两次读取32位数据再拼接成64位结果,因此能绕过硬件对64位读的限制,得到正确的拼接值。
修复方案
不要直接使用64位指针读取,改为手动拼接两次32位读取的结果,修改后的核心代码如下:
static uint64_t _mmio_read_worker(uint64_t address) { int fd; void *map_base = NULL; void *map_address = NULL; uint64_t result = 0UL; if((fd = open("/dev/mem", O_RDONLY | O_SYNC)) < 0) FATAL; if((map_base = mmap(NULL, PAGE_SIZE, PROT_READ, MAP_SHARED, fd, (address & ~PAGE_MASK))) == MAP_FAILED) FATAL; map_address = map_base + (address & PAGE_MASK); // 手动拼接两个32位读取结果,替代原子64位读 uint32_t low = *(uint32_t *)map_address; uint32_t high = *(uint32_t *)((uint8_t *)map_address + 4); result = ((uint64_t)high << 32) | low; printf("uint32_t 0x%016x, uint64_t 0x%016lx\n", low, result); close(fd); return result; }
额外说明
如果你的设备是PCIe设备且支持扩展配置空间的64位访问,需要先确认设备能力寄存器是否允许64位MMIO访问。但对于绝大多数传统PCI设备,32位粒度的访问是唯一可行的方式。
内容的提问来源于stack exchange,提问作者imotfnir
相关产品推荐
相关产品推荐

