ARM64设备内核模块加载固件到保留内存时崩溃求助
问题:ARM64设备固件加载至保留内存时随机内核崩溃
环境配置(设备树)
已通过no-map定义保留内存区域,避免Linux映射虚拟内存:
reserved-memory { #address-cells = <2>; #size-cells = <2>; ranges; reserved: memory@8000000 { no-map; reg = <0x0 0x08000000 0x0 0x01000000>; }; }; xlnx-com2@8000000 { compatible = "xlnx,reserved-memory"; memory-region = <&reserved>; };
驱动实现
内核驱动通过模块参数传入固件路径,读取比特流后写入保留内存,关键代码如下:
#include <linux/module.h> #include <linux/kernel.h> #include <linux/io.h> #include <linux/of.h> #include <linux/firmware.h> #include <linux/memremap.h> #include <linux/of_address.h> #include <linux/platform_device.h> #include <linux/init.h> #include <linux/delay.h> #define DRV_NAME "com2" static char *fpgaupdate; module_param(fpgaupdate, charp, 0444); static int com2_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct device_node *np; struct resource res; phys_addr_t mem_paddr; void __iomem *mem_vaddr; size_t mem_size; int ret = 0; const struct firmware *fw; size_t offset = 0; size_t chunk_size; if (fpgaupdate == NULL) { dev_err(dev, "fpgaupdate parameter is missing\n"); return -EINVAL; } ret = request_firmware(&fw, fpgaupdate, dev); if (ret) { dev_err(dev, "failed to request firmware. error: %d\n", ret); return ret; } dev_info(dev, "firmware size: %ld bytes\n", fw->size); np = of_parse_phandle(dev->of_node, "memory-region", 0); if (!np) { dev_err(dev, "No %s specified\n", "memory-region"); ret = -EINVAL; goto rel_fw; } ret = of_address_to_resource(np, 0, &res); if (ret) { dev_err(dev, "No memory address assigned to the region\n"); goto rel_fw; } mem_paddr = res.start; mem_size = resource_size(&res); if (fw->size > mem_size) { dev_err(dev, "firmware size exceeds reserved memory size\n"); ret = -ENOMEM; goto rel_fw; } while (offset < fw->size) { dev_info(dev, "fw_size 0x%lx, offset 0x%lx\n", fw->size, offset); chunk_size = min((size_t)PAGE_SIZE, fw->size - offset); mem_vaddr = memremap(mem_paddr + offset, chunk_size, MEMREMAP_WB); if (!mem_vaddr) { dev_err(dev, "failed to remap reserved memory region\n"); ret = -ENOMEM; goto rel_fw; } dev_info(dev, "vaddr received 0x%p, \n", mem_vaddr); memcpy_toio(mem_vaddr, fw->data + offset, chunk_size); dev_info(dev, "Copied %ld bytes to reserved memory at offset 0x%lx\n", chunk_size, offset); memunmap(mem_vaddr); offset += chunk_size; } rel_fw: release_firmware(fw); return ret; } static int com2_remove(struct platform_device *pdev) { dev_info(&pdev->dev, "firmware loader module removed\n"); return 0; } static const struct of_device_id com2_of_match[] = { { .compatible = "xlnx,reserved-memory", }, {}, }; MODULE_DEVICE_TABLE(of, com2_of_match); static struct platform_driver com2_driver = { .probe = com2_probe, .remove = com2_remove, .driver = { .name = DRV_NAME, .of_match_table = com2_of_match, }, }; module_platform_driver(com2_driver); MODULE_ALIAS("platform:" DRV_NAME); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("com2 driver");
问题现象
固件大小约3MB,保留内存为16MB。最初尝试一次性映射整个区域并复制固件时触发内核崩溃,改为4KB分块复制后,仍会随机崩溃,崩溃位置不固定(有时复制1/3固件,有时仅几KB),错误日志显示为页错误或访问不存在的虚拟页。已尝试移除no-map属性,问题依旧。
排查思路与解决建议
- 调整memremap映射类型:当前使用
MEMREMAP_WB(回写缓存),若该保留内存是FPGA配置专用RAM,建议尝试MEMREMAP_IO或MEMREMAP_WT(写通)。WB模式可能因缓存一致性问题导致内核操作异常,IO/WT模式更适合这类非系统内存的访问场景。 - 验证物理地址范围合法性:在每次映射前,添加检查确保
mem_paddr + offset + chunk_size不超过保留内存的结束地址res.end + 1,避免地址溢出导致非法访问。 - 减少映射操作次数:改为一次性映射整个保留内存区域,再通过
memcpy_toio分块复制数据。之前一次性映射崩溃大概率是误用了普通memcpy而非memcpy_toio——针对IO类型的内存映射,必须使用IO专用拷贝函数。 - 处理缓存一致性:ARM64架构下,若使用WB映射,每次写入后需调用
dma_wmb()或flush_cache_vmap()确保数据刷入物理内存;若用IO映射,memcpy_toio会自动处理缓存,但需确认映射类型匹配硬件要求。 - 确认保留内存未被占用:查看
/proc/iomem文件,确认该内存区域确实被标记为reserved,未被其他硬件或内核组件占用。 - 检查内核配置:确保内核开启
CONFIG_MEMREMAP、CONFIG_RESERVED_MEM等相关选项,保证保留内存和内存映射功能正常工作。 - 增强调试信息:在崩溃前打印每次映射的物理地址、虚拟地址及块大小,同时在
memremap失败或崩溃时调用dump_stack()输出栈回溯,精确定位问题点。 - 尝试替换映射函数:若
memremap存在兼容性问题,可改用ioremap()直接映射保留内存,同样使用memcpy_toio进行数据写入。
内容的提问来源于stack exchange,提问作者szs
相关产品推荐
相关产品推荐

