0%

使用设备树的LED驱动框架

1. 设备树

1.1 设备树的引入

如果按照上面的方式来让我们的Linux内核支持所有的主控和开发板;也许常用的主控芯片并不是很多,但是基于主控芯片所设计出的开发板就是那就是千千万万了。如果要将所有的开发板的引脚配置编译到内核中,那内核中将充斥大量的高度重复的文件。为此,引入了设备树的概念,设备树用来给内核里的驱动程序指定硬件的信息

image-20231218155959125

1.2 设备树的语法

我们需要编写设备树文件(dts: device tree source),它需要编译为 dtb(device tree blob)文件,内核使用的是 dtb 文件。

下面是一个设备树示例:

image-20231220165324421

image-20231220170050864

1.2.1 设备树的格式

(1)DTS文件的格式
1
2
3
4
5
6
/dts-v1/;                 // 表示版本
[memory reservations] // 格式为:/memreserver <address> <length>;
/{
[property definitions]
[child nodes]
};
(2)node的格式

设备树中的基本单元,称为“node”,其格式为:

1
2
3
4
[label:] node-name[@unit-address] {
[properties definitions]
[child nodes]
};

label 是标号,可以省略。label 的作用是为了方便地引用 node,比如:

1
2
3
4
5
6
7
/dts-v1/;
/ {
uart0: uart@fe001000 {
compatible="ns16550";
reg=<0xfe001000 0x100>;
};
};

可以使用下面 2 种方法来修改 uart@fe001000 这个 node:

1
2
3
4
5
6
7
8
9
// 在根节点之外使用 label 引用 node:
&uart0 {
status = “disabled”;
};

// 或在根节点之外使用全路径
&{/uart@fe001000} {
status = “disabled”;
};
(3)properties的格式
  • 两种格式:
1
2
3
[label:] property-name;

[label:] property-name = value;
  • 三种取值:
1
2
3
arrays of cells(1 个或多个 32 位数据, 64 位数据使用 2 个 32 位数据表示), 
string(字符串),
bytestring(1 个或多个字节)
  • 示例:

arrays of cells,cell 就是一个 32 位的数据,用尖括号包围起来。而至于两个32位数据是分别表示两个数值,还是表示一个64bit数据的高低位,则由常用属性#address-cells#size-cells决定,参见后面的介绍:

1
2
interrupts = <17 0xc>;    
clock-frequency = <0x00000001 0x00000000>;

string,有结束符的字符串,用双引号包围起来:

1
compatible = "simple-bus";

bytestring,字节序列,用中括号包围起来:

1
2
local-mac-address = [00 00 12 34 56 78]; // 每个byte使用2个16进制数来表示
local-mac-address = [000012345678]; // 每个byte使用2个16进制数来表示

各种值的组合,用逗号隔开:

1
2
compatible = "ns16550", "ns8250"; 
example = <0xf00f0000 19>, "a strange property format";

1.2.2 dts 文件包含 dtsi 文件

设备树文件不需要我们从零写出来,内核支持了某款芯片比如 imx6ull,在内核的 arch/arm/boot/dts 目录下就有了能用的设备树模板,一般命名为 xxxx.dtsi。“ i”表示“include”,被别的文件引用的。

我们使用某款芯片制作出了自己的单板,所用资源跟 xxxx.dtsi 是大部分相同,小部分不同,所以需要引脚 xxxx.dtsi 并修改。

dtsi 文件跟 dts 文件的语法是完全一样的。

dts 中可以包含.h 头文件,也可以包含 dtsi 文件,在.h 头文件中可以定义 一些宏,例如:

1
2
3
4
5
6
/dts-v1/; 
#include <dt-bindings/input/input.h>
#include "imx6ull.dtsi"
/ {
......
};

1.2.3 常用属性

  1. #address-cells、#size-cells

    #address-cells指示硬件的address要用多少个32位数表示;#size-cells表示硬件所占用的内存size要用多少个32位数来表示。

    例如:

    1
    2
    3
    4
    5
    6
    aips1: aips-bus@02000000 {
    compatible = "fsl,aips-bus", "simple-bus";
    #address-cells = <1>;
    #size-cells = <1>;
    reg = <0x02000000 0x100000>;
    ranges;

    对于需要指定大小资源的设备是有用的,比如 RAM 块或者其他可配置大小的硬件资源,需要设置 #address-cells = <1>; 以便描述该设备所使用的内存空间大小。

    AIPS 是 ARM IP Bus 的缩写,它一边与内核 AHB 总线连接,另一边与片上的各种外设连接,AIPS1~4 连接了各种各样的外设。

    对于一些固定大小的外设,如I2C控制器:

    1
    2
    3
    4
    5
    6
    i2c1: i2c@021a0000 {
    #address-cells = <1>;
    #size-cells = <0>;
    compatible = "fsl,imx6ul-i2c", "fsl,imx21-i2c";
    reg = <0x021a0000 0x4000>;
    };

    它们占用的内存映射空间通常是固定的,因此不需要在设备树中指定大小。在这种情况下,#size-cells可以被设置为<0>

    其他 #size-cells = <0> 的情况:

    • 该节点代表的硬件资源的大小是固定的,不需要通过设备树来指定。
    • 驱动程序将不期望从设备树获取大小信息,因为它已经知道资源的大小或者有其他机制来确定它。
    • 在设备树中,该节点可能仍然包含其他重要信息,比如中断、时钟或状态,但不包括大小信息。
  2. compatible

    “compatible”表示“兼容”,对于某个 LED,内核中可能有 A、B、C 三个驱动都支持它,那可以这样写:

    1
    2
    3
    led { 
    compatible = “A”, “B”, “C”;
    };

    内核启动时,就会为这个 LED 按这样的优先顺序为它找到驱动程序:A、B、C。

    根节点下也有 compatible 属性,用来选择哪一个machine desc:一个内核可以支持 machine A,也支持 machine B,内核启动后会根据根节点的 compatible 属性找到对应的 machine desc 结构体,执行其中的初始化函数。 compatible 的值,建议取这样的形式:”manufacturer,model”,即“厂家名,模块名”。

    注:machine desc 的意思就是“机器描述”,学到内核启动流程时才涉及。

  3. model

    model 属性与 compatible 属性有些类似,但是有差别。

    compatible 属性是一个字符串列表,表示可以你的硬件兼容 A、B、C 等驱动;

    model 用来准确地定义这个硬件是什么,例如:

    1
    2
    3
    4
    { 
    compatible = "samsung,smdk2440", "samsung,mini2440";
    model = "jz2440_v3";
    };

    它表示这个单板,可以兼容内核中的“smdk2440”,也兼容“mini2440”。 从 compatible 属性中可以知道它兼容哪些板,但是它到底是什么板?用 model 属性来明确

  4. status

    dtsi 文件中定义了很多设备,但是在你的板子上某些设备是没有的。这时你可以给这个设备节点添加一个 status 属性,设置为“disabled”:

    1
    2
    3
    &uart1 { 
    status = "disabled";
    };
    描述
    “okay” 设备正常运行
    “disabled” 设备不可操作,但是后面可以恢复工作
    “fail” 发生了严重错误,需要修复
    “fail-sss” 发生了严重错误,需要修复;sss表示错误信息
  5. reg

    用来描述一段空间。对于 ARM 系统,寄存器和内存是统一编址的,即访问寄存器时用某块地址,访问内存时用某块地址,在访问方法上没有区别。

    reg 属性的值,是一系列的address & size,用多少个 32 位的数来表示 address 和 size,由其父节点的#address-cells、#size-cells 决定,例如:

    1
    2
    3
    4
    5
    6
    7
    8
    /dts-v1/; 
    /{
    #address-cells = <1>;
    #size-cells = <1>;
    memory {
    reg = <0x80000000 0x20000000>;
    };
    };
  6. name(过时了,建议不用)

    它的值是字符串,用来表示节点的名字。在跟 platform_driver 匹配时, 优先级最低。 compatible 属性在匹配过程中,优先级最高。

  7. device_type(过时了,建议不用)

    它的值是字符串,用来表示节点的类型。在跟 platform_driver 匹配时, 优先级为中。 compatible 属性在匹配过程中,优先级最高。

1.2.4 常用节点(node)

  1. 根节点

    dts文件中必须有一个根节点:

    1
    2
    3
    4
    5
    6
    7
    8
    /dts-v1/;
    / {
    model = "SMDK24440";
    compatible = "samsung,smdk2440";

    #address-cells = <1>;
    #size-cells = <1>;
    };

    根节点中必须有这些属性:

    1
    2
    3
    4
    5
    6
    7
    8
    #address-cells // 在它的子节点的 reg 属性中, 使用多少个 u32 整数来描述地址(address) 
    #size-cells // 在它的子节点的 reg 属性中, 使用多少个 u32 整数来描述大小(size)
    compatible // 定义一系列的字符串, 用来指定内核中哪个 machine_desc 可以支持本设备
    // 即这个板子兼容哪些平台
    // uImage : smdk2410 smdk2440 mini2440 ==> machine_desc
    model // 咱这个板子是什么
    // 比如有 2 款板子配置基本一致, 它们的 compatible 是一样的
    // 那么就通过 model 来分辨这 2 款板子
  2. CPU节点

    一般不需要我们设置,在 dtsi 文件中都定义好了:

    1
    2
    3
    4
    5
    6
    7
    cpus { 
    #address-cells = <1>;
    #size-cells = <0>;
    cpu0: cpu@0 {
    .......
    }
    };
  3. memory节点

    芯片厂家不可能事先确定你的板子使用多大的内存,所以 memory 节点需要板厂设置,比如:

    1
    2
    3
    memory {
    reg = <0x80000000 0x20000000>;
    };
  4. chosen节点

    我们可以通过设备树文件给内核传入一些参数,这要在chosen节点中设置bootargs属性:

    1
    2
    3
    chosen { 
    bootargs = "noinitrd root=/dev/mtdblock4 rw init=/linuxrc console=ttySAC0,115200";
    };

1.3 编译、更换设备树

  1. 一般设备树文件在内核源码目录下arch/arm/boot/dts/(对于64位系统,需要将arm替换为arm64)下。

  2. 我们一般不会从零写 dts 文件,而是修改。

  3. 当我们修改完设备树文件之后需要将设备树源码文件dts编译为设备树二进制文件dtb

  4. 然后将修改后的dtb文件放到板子的/boot/目录中;重启开发板,在上电过程中进入Bootload,指定板子所使用的设备树文件,从而完成修改、编译和更换设备树的需求。

1.3.1 在内核中直接make

设置 ARCH、CROSS_COMPILE、PATH 这三个环境变量后,进入 ubuntu 上板子内核源码的根目录,在根目录下执行如下命令编译 dtb 文件:

1
make dtbs V=1   // V=1表示输出详细信息,可省略

这种编译方式首先会使用 gcc 对dts文件进行预编译,然后对预编译生成的临时文件使用 dtc 工具进行编译。

1.3.2 手工编译

除非你对设备树比较了解,否则不建议手工使用 dtc 工具直接编译。

内核目录下 scripts/dtc/dtc 是设备树的编译工具,直接使用它的话,**包含其他文件时不能使用#include,而必须使用/incldue/**。 编译、反编译的示例命令如下,-I指定输入格式,-O指定输出格式,-o指定输出文件:

1
2
./scripts/dtc/dtc -I dts -O dtb -o tmp.dtb arch/arm/boot/dts/xxx.dts // 编译 dts 为 dtb 
./scripts/dtc/dtc -I dtb -O dts -o tmp.dts arch/arm/boot/dts/xxx.dtb // 反编译 dtb 为 dts

1.3.3 板子启动后查看设备树

板子启动后执行下面的命令:

1
ls /sys/firmware/ 

在该目录下有目录 devicetree 和文件 fdt ,下面分别对二者进行介绍

(1)目录devicetree

/sys/firmware/devicetree 目录下是以目录结构呈现的dtb文件,根节点对应 base 目录,每一个节点对应一个目录,每一个属性对应一个文件。

这些属性的值如果是字符串,可以使用 cat 命令把它打印出来;对于数值, 可以用 hexdump 把它打印出来。

例如 dts中存在以下节点:

1
2
3
krocz_led_test{
led_test_resource = "LED1";
};

然后在 sys/firmware/devicetree/base/krocz_led_test目录下有:

image-20231222200900705

  • cat name 显示字符串”krocz_led_test”
  • cat led_test_resource 显示字符串”LED1”
(2)文件fdt

/sys/firmware/fdt 文件,是 dtb 格式的设备树文件,可以把它复制出来放到 ubuntu 上,使用dtc编译器,执行下面的命令反编译为 dts 文件:

1
./scripts/dtc/dtc -I dtb -O dts /目录/fdt -o tmp.dts

1.4 内核对设备树的处理

从源代码文件 dts 文件开始,设备树的处理过程为:

image-20231222202559378

  1. dts 在 PC 机上被编译为 dtb 文件;
  2. u-boot 把 dtb 文件传给内核;
  3. 内核解析 dtb 文件,把每一个节点都转换为 device_node 结构体;
  4. 对于某些 device_node 结构体,会被转换为 platform_device 结构体。

1.4.1 dtb 中的每个节点都会被转换 device_node 结构体

device_node结构体表示每一个节点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct device_node {
const char *name;
const char *type;
phandle phandle;
const char *full_name;
struct fwnode_handle fwnode;

struct property *properties;
struct property *deadprops; /* removed properties */
struct device_node *parent;
struct device_node *child;
struct device_node *sibling;
struct kobject kobj;
unsigned long _flags;
void *data;
#if defined(CONFIG_SPARC)
const char *path_component_name;
unsigned int unique_id;
struct of_irq_controller *irq_trans;
#endif
};

device_node 结构体通过以下三个成员来表示设备树中节点的关系:

1
2
3
struct device_node *parent;  /* 指向父亲节点 */
struct device_node *child; /* 指向第一个孩子节点 */
struct device_node *sibling; /* 指向同级别的下一个节点 */

例如一个设备树节点结构如下:

1
2
3
4
5
    A
/ \
B C
/ \
D E

在这个例子中,节点 A 的 child 指向 B,节点 B 的 sibling 指向 C,节点 B 的 child 指向 D,节点 D 的 sibling 指向 E。这样,就可以通过 childsibling 字段在设备树中表示节点之间的关系,包括一个节点有多个孩子的情况。

根节点被保存在全局变量 of_root 中,从 of_root 开始可以访问到任意节点。

节点中属性成员:

1
2
struct	property *properties;
struct property *deadprops; /* removed properties */

properties成员指向一个链表,表示当前节点的属性列表。这个链表中的每个节点都是一个属性,包含键(name)和值(value)

deadprops成员用于表示已被移除(removed)的属性。当设备树中的节点属性被修改或删除时,原有的属性可能会被移动到 deadprops 链表中,以便记录这些已经被移除的属性。这样设计的一个原因是为了在设备树的修改过程中能够追踪先前的属性信息,即便这些属性已经被删除或修改。通过保留这些“死去”的属性,系统可以在需要时回溯到先前的状态,或者进行一些历史记录和调试。

成员 name 和 type对应已经过时废弃的属性 name 和 device_type。节点的其他属性使用结构体property表示:

1
2
3
4
5
6
7
8
9
struct property {
char *name;
int length;
void *value;
struct property *next;
unsigned long _flags;
unsigned int unique_id;
struct bin_attribute attr;
};

1.4.2 哪些 device_node 会被转换为 platform_device

  1. 根节点下含有 compatile 属性的子节点

  2. 含有特定 compatile 属性的节点的子节点

    如果一个节点的 compatile 属性,它的值包括这 4 者之一:simple-bussimple-mfdisaarm,amba-bus,那么它的子结点( 需含 compatile 属性)也可以转换为 platform_device。

  3. 总线 I2C、SPI 节点下的子节点:不转换为 platform_device

    某个总线下到子节点,应该交给对应的总线驱动程序来处理, 它们不应该被转换为 platform_device。

例如以下dts文件,每个节点是否会转换为platform_device如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
mytest{ // √
compatile = "mytest", "simple-bus"
mytest@0{ // √
compatile = "mytest_0";
};
};

i2c{ // √
compatile = "samsung,i2c";
at24c02{ // ×
compatile = "at24c02";
};
};

spi{ // √
compatile = "samsung,spi";
flash@0{ // ×
compatible = "winbond,w25q32dw";
spi-max-frequency = <25000000>;
reg = <0>;
};
};
};

1.4.3 device_node 怎么转为 platform_device

  • platform_device 中含有 resource 数组,设备树节点中的 reg、interrupts 属性会被转化为 resource;

    1
    struct resource *platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num)

    使用该函数可以取出这些资源:

    • 对于设备树节点中的 reg 属性,它对应 IORESOURCE_MEM 类型的资源;
    • 对于设备树节点中的 interrupts 属性,它对应 IORESOURCE_IRQ 类型的资源。
  • platform_device.dev.of_node 指向 struct device_node,可以通过该指针来获取of_node->property 从而获取设备节点中自定义的其他属性。

1.5 完善 platform_device 与 platform_driver 匹配过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);

/* When driver_override is set, only bind to the matching driver */
if (pdev->driver_override)
return !strcmp(pdev->driver_override, drv->name);

/* Attempt an OF style match first */
if (of_driver_match_device(dev, drv))
return 1;

/* Then try ACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1;

/* Then try to match against the id table */
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;

/* fall-back to driver name match */
return (strcmp(pdev->name, drv->name) == 0);
}

1.5.1 最先比较:是否强制选择某个driver

最先比较 platform_device.driver_overrideplatform_driver.driver.name

1.5.2 然后比较:设备树信息

比较 platform_device.dev.of_nodeplatform_driver.driver.of_match_table

image-20231223160427435

  1. 如果 of_match_table 中含有 compatible 值,就跟 dev 的 compatile 属性比较,若一致则成功,否则返回失败;
  2. 如果 of_match_table 中含有 type 值,就跟 dev 的 device_type 属性比较,若一致则成功,否则返回失败;
  3. 如果 of_match_table 中含有 name 值,就跟 dev 的 name 属性比较,若一致则成功,否则返回失败。

而设备树中建议不再使用 devcie_type 和 name 属性,所以基本上只使用设备节点的 compatible 属性来寻找匹配的 platform_driver。

1.5.3 接下来比较: platform_device_id

比较 platform_device.name 和 platform_driver.id_table[i].name, id_table 中可能有多项。

platform_driver.id_table 表示该 drv 支持若干个 device,它里面列出了各个 device 的{.name, .driver_data}, 其中的 name表示该 drv 支持的设备的名字,driver_data 是些提供给该 device 的私有数据。

1.5.4 最后比较:name

platform_driver.id_table 可能为空, 这时可以根据 platform_driver.driver.name 和 platform_device.name 是否匹配进行比较

1.5.5 匹配过程总结:

image-20231223153849297

2. LED驱动程序改造:设备树

上图是使用设备树的通用驱动框架:

  • 开发板的硬件资源使用 board_devicetree.dts 文件描述
  • chip_drv.c 实现针对某一款主控芯片的硬件操作结构体和平台驱动结构体
  • universal_drv.c 是兼容所有开发板的通用驱动程序
  • app_test.c 是应用层的硬件测试程序

使用该驱动框架,实现了通用驱动程序和主控芯片的解耦,主控芯片和开发板的解耦,避免了大量重复的开发板硬件描述文件充斥内核

2.1 代码

2.1.1 设备树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#define GROUP_PIN(g,p) ((g<<16) | (p))

/ {
krocz_led@0 {
compatible = "krocz,leddrv";
pin = <GROUP_PIN(1, 1)>;
};

krocz_led@1 {
compatible = "krocz,leddrv";
pin = <GROUP_PIN(5, 3)>;
};

};

在开发板的设备树文件中添加两个节点,用于实验。

2.1.2 芯片层

IMX6ULL芯片操作LED的结构体

1
2
3
4
5
6
7
8
9
10
11
static int chip_led_init(int which){} /* 初始化LED, which-哪个LED */
static int chip_led_ctl(int which, char status){} /* 控制LED, which-哪个LED, status:1-亮,0-灭 */
static int chip_led_close(void) {}
static int get_board_led_num(void){}

static struct led_operations chip_imx6ll_led_opr = {
.init = chip_led_init,
.ctl = chip_led_ctl,
.close = chip_led_close,
.get_led_num = get_board_led_num,
};

操作函数具体内容和“LED驱动程序框架”中对应章节中的代码类似,这里就不放了,都是操作寄存器的繁琐代码。

IMX6ULL芯片的LED驱动结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
static int chip_demo_gpio_probe(struct platform_device *pdev)
{
struct device_node *np;
int err = 0;
int led_pin;

np = pdev->dev.of_node;
if (!np) return -1; // platform_device 不一定是由device_node转换而来的,of_node可能为空因此需要判断

err = of_property_read_u32(np, "pin", &led_pin);
g_ledpins[g_ledcnt] = led_pin;
led_class_create_device(g_ledcnt); // 创建设备文件
g_ledcnt++;

return 0;

}

static int chip_demo_gpio_remove(struct platform_device *pdev)
{
int i = 0, j;
int err;
struct device_node *np;
int led_pin;

np = pdev->dev.of_node;
if (!np) return -1;

err = of_property_read_u32(np, "pin", &led_pin);

for (i = 0; i < g_ledcnt; i++)
{
if (g_ledpins[i] == led_pin)
{
led_class_destroy_device(i); //销毁设备文件
for(j = i + 1; j < g_ledcnt; j++)
g_ledpins[j - 1] = g_ledpins[j];
g_ledcnt --;
break;
};
}
return 0;
}

static const struct of_device_id krocz_leds[] = {
{ .compatible = "krocz,leddrv" }, //通过该属性与platform_device进行匹配
{ },
};

static struct platform_driver chip_imx6ull_gpio_driver = {
.probe = chip_demo_gpio_probe,
.remove = chip_demo_gpio_remove,
.driver = {
.name = "krocz_led_drv",
.of_match_table = krocz_leds,
},
};

在 platform_driver 结构体中制定了和 platform_device匹配时的初始化函数(probe),以及解绑时的去初始化函数(remove);通过 of_device_id 结构体指定了该驱动所支持的设备。

最后就是必须的内核模块安装/卸载函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static int __init chip_demo_gpio_drv_init(void)
{
int err;

err = platform_driver_register(&chip_imx6ull_gpio_driver);
register_led_operations(&chip_imx6ll_led_opr);

return 0;
}

static void __exit lchip_demo_gpio_drv_exit(void)
{
platform_driver_unregister(&chip_imx6ull_gpio_driver);
}

module_init(chip_demo_gpio_drv_init);
module_exit(lchip_demo_gpio_drv_exit);
MODULE_LICENSE("GPL");

主要执行 platform_driver 的总线注册和删除

2.1.3 通用LED驱动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void led_class_create_device(int minor)
{
device_create(led_class, NULL, MKDEV(major, minor), NULL, "100ask_led%d", minor); /* /dev/krocz_led0,1,... */
}
void led_class_destroy_device(int minor)
{
device_destroy(led_class, MKDEV(major, minor));
}
void register_led_operations(struct led_operations *opr)
{
p_led_opr = opr;
}

EXPORT_SYMBOL(led_class_create_device);
EXPORT_SYMBOL(led_class_destroy_device);
EXPORT_SYMBOL(register_led_operations);

该程序和”LED驱动程序框架“对应章节中的程序相比变化不大。由于设备文件应该在 platform_driver 和 platform_deivce 匹配/删除时动态的创建或销毁,所以将设备文件的创建和销毁作为函数提供给下层驱动程序进行调用(使用EXPORT_SYMBOL宏修饰函数,使其在其他内核模块中可以被调用)。

2.2 测试

2.2.1 编译、安装

  1. 参考1.3节“编译、更换设备树”,为开发板更换设备树文件

更换安装完成之后,可以在/sys/firmware/devicetree/base目录下查看设备树节点(device_node):

image-20240102112542082

还可以在/sys/bus/platform/devices/目录下查看所有的 platform_device 节点,例如:

image-20240102113358690

程序中 platform_device 和 platform_driver 使用 compatible 属性进行匹配,当匹配成功之后,platform_device 节点中就会出现指向对应驱动节点(platform_driver)的链接文件。

  1. 编译内核模块并安装内核模块。

/sys/bus/platform/drivers/目录下查看所有的 platform_driver 节点,例如:

image-20240102114920931

虽然我也不知道为什么 driver 匹配的 device 会链接到 /sys/devices/soc0/krocz_led@0

同时,在安装内核模块chip_imx6ull_gpio.ko时,会在和 platform_device 匹配之后,创建一个设备文件,在目录/dev/下:

image-20240102115755062

  1. 运行APP进行测试