kernel 动态修改dtb方案

本文详细介绍了如何在Linux内核中动态修改设备树二进制文件(DTB),包括删除属性、删除节点、添加属性、添加节点、修改属性及修改节点的具体实现方法。此外,还探讨了在修改过程中涉及的一些关键技术细节。

DTB是在uboot进kernel时,被load到内存中, 然后在linux中 start_kernel ->setup_arch->unflatten_device_tree 中,从内存中读取,展开成树型的数据结构,供内核驱动查找。一般来说,linux中,不会对dtb做什么改动。但如果有特殊需求,想统一几个类似产品的不同配置,修改一些驱动参数,而又不想动uboot的话(一般OTA升级可能不改动uboot), 就会有需求在kernel中修改dtb,比如,按照uboot传的bootargs,在kernel中动态修改几个dtb节点信息,就会有这个需求。
如果要在kernel中修改dtb,那就必须在start_kernel中修改了,因为在start_kernel的最后,就需要init驱动了,必须在这之前完成。而在unflatten_device_tree前,dtb是二进制文件,修改难度较大,所以在unflatten_device_tree后,直接修改node节点会相对容易。
DTB的修改,无非就是添加节点,删除节点,或者修改节点中内容参数,添加/删除/修改property的函数,在linux中有现成的of_add_property/of_remove_property/of_update_property函数,但对节点的修改貌似没有, 只能通过基本的节点操作处理。另外,如果伙伴系统还没初始化,malloc还不能用,所以涉及到内存分配需要early_init_dt_alloc_memory_arch,并且自己管理malloc的大小用下面就以如下这个简单的dts结构,针对这几个需求逐个分析一下:

这里写图片描述
1. 删除property,以删除power-led下yellow下面的default-state为例:

struct device_node *np
np = of_find_node_by_name(NULL, "power-led");
np = of_find_node_by_name(np, "yellow");
prop = of_find_property(np, "default-state", NULL);
of_remove_property(np, prop);

2. 删除node, 以删除power-led下green这个node为例:

struct device_node *np, *np1, * np2
np =  of_find_node_by_name(NULL, "power-led");
np1 =  of_find_node_by_name(np, "blue");
np2 =  of_find_node_by_name(np, "yellow");
np1->sibling = np2;
np1->next = np2;
np1->allnext = np2;

注: 关于device_node里各个指针的概念:
parent:父节点, blue/green/yellow的parent都是power-led
sibling:旁系,power-led的sibling是key,key的sibling是NULL。blue的sibling是green再接着是yellow和NULL
next:下一个的概念,power-led的next是blue接着是green和yellow,再接下来是NULL
allnext是一个总的包含所有device_node的链表,用来遍历所有node,所以这个总链表allnext的顺序是wireless-bluetooth接着power-led接着blue,green,yellow,接着key,power-key,接着NULL。

3. 添加property, 以添加yellow下enable=true为例:

np =  of_find_node_by_name(NULL, "power-led");
np =  of_find_node_by_name(np, "yellow");
prop = create_new_property("enable", "true", sizeof("true"));
of_add_property(np, prop);

由于用到了early_init_dt_alloc_memory_arch相关的接口,所以贴一下所有这些接口,包含申请device-node和property的封装接口:

static void *unflatten_dt_alloc(void **mem, unsigned long size,
    unsigned long align)
{
    void *res;

    *mem = PTR_ALIGN(*mem, align);
    res = (void *)*mem;
    *mem += size;
    return res;
}
/*we ignore type and data here for basic use.*/
static struct device_node * __init create_new_node(const char* name,
        unsigned int phandle, const char* full_name, unsigned long _flags)
{
        struct device_node *new_node;
        void *mem;
        int malloc_size = 0;
        char* new_name;
        char* new_full_name;
        /*1. calc the whole malloc memory size*/
        malloc_size = strlen(name)+1;
        malloc_size += strlen(full_name)+1;
        malloc_size += sizeof(struct device_node);

        /*2 alloc the memory*/
                    mem = early_init_dt_alloc_memory_arch(malloc_size + 4, 4);
        memset(mem, 0, malloc_size + 4);
        new_node = unflatten_dt_alloc(&mem, sizeof(struct device_node),
                                        __alignof__(struct device_node));
        new_name = unflatten_dt_alloc(&mem, strlen(name)+1, 1);
        new_full_name = unflatten_dt_alloc(&mem, strlen(full_name)+1, 1);

        /* 3. fill the data*/
        of_node_init(new_node);
        strcpy(new_name, name);
        strcpy(new_full_name, full_name);
        new_node->name = new_name;
        new_node->full_name = new_full_name;
        new_node->phandle = phandle;
        new_node->_flags = _flags;

        return new_node;
}
static struct property * __init create_new_property(char* name, void *value, int value_len)
{
        struct property *new_prop;
        void *mem;
        int malloc_size = 0;
        /* 1. calc the whole malloc memory size */
        malloc_size = strlen(name)+1;
        malloc_size += value_len;
        malloc_size += sizeof(struct property);

        /* 2. alloc the memory*/
        mem = early_init_dt_alloc_memory_arch(malloc_size + 4, 4);
        memset(mem, 0, malloc_size + 4);
        new_prop = unflatten_dt_alloc(&mem, sizeof(struct property),
                                        __alignof__(struct property));
        new_prop->value = unflatten_dt_alloc(&mem, value_len, 1);
        new_prop->name = (char*)unflatten_dt_alloc(&mem, strlen(name)+1, 1);

        /* 3. fill the data*/
        memcpy(new_prop->value, value, value_len);
        new_prop->length = value_len;
        strcpy(new_prop->name, name);
        return new_prop;
}

4. 添加device_node, 以在key后,添加一个如下wireless-modem的节点为例:

/*
    add node wireless-modem after key:
    wireless-modem {
    compatible = "modem-platdata";
    status = "okay";
    };
*/
    np = of_find_node_by_name(NULL, "key");
    new_node = create_new_node("wireless-modem", 0, "/wireless-modem", 0);
    new_node->parent = np->parent;
    new_node->child = NULL;
    new_node->sibling = np->sibling;
    np->sibling = new_node;
    new_node->next = NULL;
    new_node->allnext = np->allnext;
    np->allnext = new_node;

    /*set compatible = "modem-platdata"*/
    np = of_find_node_by_name(NULL, "wireless-modem");
    prop = create_new_property("compatible", "modem-platdata", sizeof("modem-platdata"))
    of_add_property(np, prop);

    /*set status = "okay"*/
    prop = create_new_property("status", "okay", sizeof("okay"));
    of_add_property(np, prop);

5. 修改property: 以将yellow下default-state=on 改为off为例:

np =  of_find_node_by_name(NULL, "power-led");
np =  of_find_node_by_name(np, "yellow");
prop = create_new_property("default-state", "off", sizeof("off"));
of_update_property(np, new_prop);

6.修改node,参见上面添加,删除node,这里就不再赘述。

另外,需要注意的一点,如果要修改的内容是一个引用结果,比如要将blue中”gpios = <&gpio1 GPIO_A2 GPIO_ACTIVE_HIGH>;“,&gpio1改为&gpio2, 这个就比较麻烦了,因为dts中,这些都是引用,&gpio1其实是一个“linux,phandle”的值,所有被引用的值,如果没有手动设定phandle,那么系统在编译dts->dtb的过程中,会从0,1,2。。。开始自动分配,由于dts引用顺序我们不知道,所以不会知道系统分配个gpio2的是一个什么引用值(虽然这个值在dts确定后,自动生成顺序有管联性,其实是个缺定值),要解决这个问题,那么我们最好事先在gpio1,gpio2。。等可能需要作为被修改值的结构里面,人工指定phandle值,如下,这里指定了phandle,那么,我们在修改或者添加property的时候,就可以直接指定我们手动指定的phandle值了:

gpio2: gpio2@11130000 {
        compatible = "stm32,gpio-bank";
        compatible = "stm32,gpio-bank";
        reg = <0x11130000 0x100>;
        interrupts = <GIC_SPI 53 IRQ_TYPE_LEVEL_HIGH>;
        clocks = <&clk_gates9 10>;
        gpio-controller;
        interrupt-controller;
        linux,phandle = <0x00010004>; //手动设定phandle
        phandle = <0x00010004>; //手动设定phandle
};

/* gpios = <&gpio2 GPIO_A2 GPIO_ACTIVE_HIGH>;
                    gpio2 phandle: 0x00010004
*/
    gpio_data[0] = be32_to_cpu(0x00010004); //直接设定手动设定的phandle,注意大小端配置
    gpio_data[1] = be32_to_cpu(23); //GPIO_A2
    gpio_data[2] = be32_to_cpu(0); //GPIO_ACTIVE_HIGH
    prop = create_new_property("MODEM,reset_gpio", gpio_data, sizeof(gpio_data));
    of_add_property(np, prop);
<think>我们正在讨论的是动态加载bit设备树的修改方法。根据之前的上下文,我们知道在Zynq平台上动态加载bit文件需要修改设备树,以避免启动时自动加载PL,并启用fpga-manager驱动。现在用户明确询问如何修改设备树以支持动态加载。 参考引用: [^1] 提到了两种主要方式:设备树覆盖(Device Tree Overlay)和使用Petalinux工具链。这里我们关注设备树覆盖的方法,因为它允许动态修改设备树。 [^2] 提供了设备树语法和加载过程的详细信息,这对于我们理解如何正确修改设备树非常重要。 [^3] 说明了设备树与驱动的匹配机制,这对于确保我们修改后的设备树能够被正确加载和识别至关重要。 [^4] 提到了生成PL设备树及动态加载PL程序和设备树的过程,其中提到了一个具体的pl.dtsi文件,这可能是我们修改设备树的一个起点。 因此,我们的回答应该围绕如何修改设备树(可能通过覆盖)来支持动态加载bit文件。 修改步骤: 1. 禁用启动时自动加载PL:在设备树中移除固化bit流的配置。 2. 配置fpga-manager节点:在设备树中添加或修改devcfg节点,并设置firmware-name为空。 3. 可选:添加PL状态检测节点。 但是,用户的问题是关于“动态加载bit设备树”的修改方法。这里需要明确:动态加载bit文件通常不需要修改PL的设备树节点(即描述PL外设的节点),因为动态加载bit文件后,PL部分的外设可能会改变,所以我们需要动态地加载描述这些外设的设备树片段(overlay)。因此,修改方法包括两个部分: A. 修改基础设备树以支持fpga-manager(如上所述)。 B. 创建并加载设备树覆盖(overlay)来描述动态加载bit文件后PL部分的外设。 因此,回答将分为两部分: 第一部分:修改基础设备树以支持fpga-manager(即动态加载bit文件的能力)。 第二部分:创建设备树覆盖(overlay)并动态加载,以描述PL部分的外设。 注意:用户的问题中“动态加载bit设备树”可能是指动态加载bit文件后,如何动态加载对应的设备树描述。所以,我们重点在第二部分。 具体步骤: 第一部分:基础设备树修改(支持fpga-manager) 1. 在基础设备树(system-top.dts或pl.dtsi)中,确保没有在chosen节点中指定固化bit文件。 2. 添加fpga-manager节点(如之前所述)。 第二部分:动态加载设备树覆盖(用于描述PL外设) 1. 为每次动态加载的bit文件生成对应的设备树片段(overlay)。 2. 编译该设备树片段为.dtbo文件。 3. 在Linux运行时加载该.dtbo文件。 详细步骤: 一、基础设备树修改(以Zynq-7000为例): 在基础设备树中(通常是system.dts或pl.dtsi),修改或添加如下节点: ```dts /* 确保chosen节点没有固化bit */ / { chosen { /* 删除类似这样的行:xlnx,fpga = "path/to/static.bit"; */ }; }; /* 添加fpga-manager节点 */ &amba { fpga: fpga@f8007000 { compatible = "xlnx,zynq-devcfg-1.0"; reg = <0xf8007000 0x100>; interrupt-parent = <&intc>; interrupts = <0 8 4>; clocks = <&clkc 12>; clock-names = "ref_clk"; firmware-name = ""; /* 重要:清空固化路径 */ }; }; ``` 二、创建设备树覆盖(overlay): 假设我们有一个PL设计,其中包含一个AXI GPIO外设,我们需要为它创建设备树覆盖。 1. 编写设备树片段(例如:axi_gpio_overlay.dts): ```dts /dts-v1/; /plugin/; &amba { /* 这里添加PL部分的外设节点 */ axi_gpio_0: gpio@41200000 { #gpio-cells = <2>; compatible = "xlnx,xps-gpio-1.00.a"; gpio-controller; reg = <0x41200000 0x10000>; xlnx,all-inputs = <0>; xlnx,all-outputs = <1>; xlnx,dout-default = <0x0>; xlnx,gpio-width = <8>; xlnx,tri-default = <0xFFFFFFFF>; }; }; ``` 2. 编译设备树覆盖: ```bash dtc -O dtb -o axi_gpio_overlay.dtbo -b 0 -@ axi_gpio_overlay.dts ``` 注意:使用`-@`选项来支持符号(symbols),这样可以在覆盖中引用基础设备树中的节点。 三、动态加载设备树覆盖: 将编译好的.dtbo文件放到文件系统中(例如/lib/firmware),然后使用以下命令加载: ```bash echo axi_gpio_overlay.dtbo > /sys/kernel/config/device-tree/overlays/load ``` 四、卸载设备树覆盖(如果需要): ```bash echo 0 > /sys/kernel/config/device-tree/overlays/axi_gpio_overlay/status ``` 注意:在动态加载bit文件后,再加载对应的设备树覆盖。顺序如下: 1. 通过fpga-manager加载bit文件(.bin或.bit)到PL。 2. 加载对应的设备树覆盖。 这样,PL部分的外设就可以被Linux正确识别了。 相关问题: 1. 如何生成PL外设的设备树节点? 2. 设备树覆盖加载失败如何调试? 3. 动态加载bit文件后,如何确保设备树覆盖与硬件匹配? 4. 如何卸载设备树覆盖? 5. 设备树覆盖的编译工具有哪些? 引用说明: [^1] 提到设备树覆盖是动态关联PL bit文件的一种方式,我们采用了这种方式。 [^2][^3] 提供了设备树语法和匹配机制,确保我们正确编写设备树覆盖。 [^4] 提到了生成PL设备树文件(pl.dtsi),我们可以将其作为设备树覆盖的基础。 因此,回答将按照上述步骤进行。</think>在Zynq平台上动态加载bit设备树需要进行针对性修改,以下是具体步骤和原理,结合设备树机制与动态加载流程: --- ### 一、核心修改步骤(设备树覆盖法) #### 1. **基础设备树修改(禁用固化加载)** ```dts /* 修改基础设备树 */ / { chosen { /* 删除固化bit配置 */ // bootargs = "... xlnx,fpga=static.bit"; }; }; &amba { /* 启用fpga-manager */ fpga: fpga@f8007000 { compatible = "xlnx,zynq-devcfg-1.0"; firmware-name = ""; /* 关键:清空固化路径 */ clocks = <&clkc 12>; }; }; ``` **作用**:禁止启动时自动加载固化bit,释放PL控制权[^1][^4] #### 2. **创建动态设备树覆盖(.dtsi文件)** ```dts // pl_overlay.dtsi(PL外设描述) /dts-v1/; /plugin/; &amba { axi_gpio_0: gpio@41200000 { compatible = "xlnx,xps-gpio-1.00.a"; reg = <0x41200000 0x10000>; // PL外设物理地址 #gpio-cells = <2>; gpio-controller; }; }; ``` **关键点**: - 使用`/plugin/`声明为覆盖层 - 通过`&amba`挂接到总线 - 描述PL外设寄存器地址和驱动兼容性[^2] #### 3. **编译与加载流程** ```bash # 编译设备树覆盖 dtc -O dtb -o pl_overlay.dtbo -b 0 -@ pl_overlay.dtsi # 动态加载 echo pl_overlay.dtbo > /sys/kernel/config/device-tree/overlays/load ``` **验证加载**: ```bash ls /proc/device-tree/amba/ # 应显示axi_gpio_0节点 ``` --- ### 二、动态加载全流程 ```mermaid sequenceDiagram participant User as 用户空间 participant DT_Overlay as 设备树覆盖层 participant FPGA_Mgr as fpga-manager participant Driver as PL外设驱动 User->>FPGA_Mgr: echo bitstream.bin > firmware FPGA_Mgr->>FPGA_Mgr: 配置PL逻辑 User->>DT_Overlay: echo overlay.dtbo > load DT_Overlay->>Kernel: 创建platform_device Kernel->>Driver: 触发probe()函数 Driver->>PL: 初始化外设寄存器 ``` --- ### 三、设备树与驱动匹配机制 当覆盖层加载后: 1. 内核将`axi_gpio_0`节点转换为`platform_device` 2. 驱动通过`of_match_table`匹配设备: ```c static const struct of_device_id axi_gpio_ids[] = { { .compatible = "xlnx,xps-gpio-1.00.a" }, {} }; static struct platform_driver axi_gpio_driver = { .driver = { .of_match_table = axi_gpio_ids } }; ``` 3. 匹配成功后调用驱动的`probe()`初始化硬件[^3] --- ### 四、调试技巧 1. **检查覆盖层状态**: ```bash cat /sys/kernel/config/device-tree/overlays/pl_overlay/status # 正常显示"applied" ``` 2. **查看设备树节点**: ```bash dtc -I fs /proc/device-tree | grep axi_gpio ``` 3. **驱动加载验证**: ```bash dmesg | grep gpio # 成功输出: xlnx-gpio 41200000.gpio: registered ``` --- ### 五、常见问题解决 | 问题现象 | 原因 | 解决方案 | |---------------------------|-------------------------------|------------------------------| | 覆盖层加载失败 | 基础设备树未禁用固化bit | 确认`firmware-name=""` | | 驱动未触发probe() | 设备树compatible值不匹配 | 检查驱动of_match_table | | PL外设无法访问 | 寄存器地址冲突 | 核对Vivado地址映射 | | 卸载覆盖层后资源未释放 | 驱动未实现remove() | 添加资源释放代码 | > **重要原则**:动态加载的bit文件必须与设备树覆盖层描述的硬件严格匹配,否则会导致访问冲突或系统崩溃[^4]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值