kernel 动态修改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结构,针对这几个需求逐个分析一下:

kernel 动态修改dtb方案_第1张图片
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 = 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);

你可能感兴趣的:(linux内核)