You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 04:42:16