当前位置: 首页 > news >正文

嵌入式Linux学习: 设备树实验

设备树(DeviceTree)是一种硬件描述机制,用于在嵌入式系统和操作系统中描述硬件设备的特性、连接关系和配置信息。它提供了一种与平台无关的方式来描述硬件,使得内核与硬件之间的耦合度降低,提高了系统的可移植性和可维护性。

1. 为什么需要设备树这种机制?

在Linux内核V2.6版本之前,尤其是ARM架构下,用于描述不同硬件信息的文件大多存放在arch/arm/plat-xxxarch/arm/mach-xxx文件夹下。这些文件包含了大量的板级硬件信息,而这些信息对于内核的通用性来说并无实质性帮助,反而导致了内核代码的冗余和复杂化。随着每年新推出的ARM架构芯片和基于这些芯片的板子数量不断增加,板级信息文件也呈指数级增长,这使得内核维护变得异常困难
设备树通过一种结构化的方式来描述硬件平台,包括CPU、内存、外设、总线等资源。这种描述方式使得驱动程序和内核代码能够更容易地适应不同的硬件平台,而无需对内核代码进行大量的修改。通过更换设备树文件(.dtb),即可实现不同主板的无差异支持,从而大大提高了代码的可移植性和可重用性

2. 设备树基础知识

设备树专业术语介绍:

DTS(Device Tree Source) : DTS 是设备树的源文件, 采用一种类似于文本的语法来描述硬件设备的结构、 属性和连接关系。 DTS 文件以.dts 为扩展名, 通常由开发人员编写。 它是人类可读的形式, 用于描述设备树的层次结构和属性信息。

DTSI(Device Tree Source Include) : DTSI 文件是设备树源文件的包含文件。 它扩展了 DTS文件的功能, 用于定义可重用的设备树片段。DTSI 文件以.dtsi 为扩展名, 可以在多个 DTS 文件中包含和共享。 通过使用 DTSI, 可以提高设备树的可重用性和可维护性(和 C 语言中头文件的作用相同) 。

DTB(Device Tree Blob) : DTB 是设备树的二进制表示形式。 DTB 文件是通过将 DTS 或 DTSI文件编译而成的二进制文件, 以.dtb 为扩展名。 DTB 文件包含了设备树的结构、 属性和连接信息, 被操作系统加载和解析。 在运行时, 操作系统使用 DTB 文件来动态识别和管理硬件设备。

DTC(Device Tree Compiler) : DTC 是设备树的编译器。 它是一个命令行工具, 用于将 DTS和 DTSI 文件编译成 DTB 文件。 DTC 将文本格式的设备树源代码转换为二进制的设备树表示形 式, 以便操作系统能够加载和解析。 DTC 是设备树开发中一个重要的工具。

DTS、 DTSI、 DTB 和 DTC 之间的关系:

  • 开发人员使用文本编辑器编写 DTS 和 DTSI 文件, 描述硬件设备的层次结构、 属性和连接关系。
  • DTSI 文件可以在多个 DTS 文件中包含和共享, 以提高设备树的可重用性和可维护性。
  • 使用 DTC 编译器, 开发人员将 DTS 和 DTSI 文件编译成二进制的 DTB 文件
    在这里插入图片描述
  • 操作系统在启动过程中加载和解析 DTB 文件, 以识别和管理硬件设备。

设备树文件路径
ARM体系结构: I.MX6ULL

(内核目录)/arch/arm/boot/dts

瑞芯微的RK3568

(内核目录)/arch/arm64/boot/dts/rockchip
DTC工具的使用

在 Linux 内核源码中, DTC(Device Tree Compiler) 的源代码和相关工具通常存放在(内核目录)/scripts/dtc/目录中在这里插入图片描述
在编译完源码之后 dtc 设备树编译器会默认生成, 如果没有生成相应的 dtc 可执行文件,可以查看在内核默认配置文件中((内核目录)/.config) CONFIG_DTC 是否使能。
在这里插入图片描述

① 设备树的编译

dtc -I dts -O dtb -o output.dtb input.dts

其中, input.dts是输入的设备树源文件, output.dtb是编译后的二进制设备树文件。
编译器会验证设备树源文件的语法和语义, 生成与硬件描述相对应的设备树表示形式。

②设备树的反编译

dtc -I dtb -O dts -o output.dts input.dtb

将二进制设备树文件反编译为设备树源文件
input.dtb 是输入的二进制设备树文件,output.dts是反编译后的设备树源文件

3.设备树基本语法

3.1根节点

设备树使用一种层次结构,其中的根节点(Root Node)是整个设备树的起始点和顶层节点。

/dts-v1/;   // 设备树版本信息
/{ 			// 根节点开始// 可以在里面添加描述根节点的属性和配置
};  

子节点格式如下

[label:] node-name@[unit-address] {[properties definitions][child nodes]
};
  • 节点标签(Label) (可选) : 节点标签是一个可选的标识符, 用于在设备树中引用该节点。 标签允许其他节点直接引用此节点, 以便在设备树中建立引用关系。
  • 节点名称(Node Name) : 节点名称是一个字符串, 用于唯一标识该节点在设备树中的位置。 节点名称通常是硬件设备的名称, 但必须在设备树中是唯一的。
  • 单元地址(Unit Address)(可选):单元地址用于标识设备的实例。 它可以是一个整数、 一个十六进制值或一个字符串, 具体取决于设备的要求。 单元地址的目的是区分相同类型的设备的不同实例, 例如在下图 中名为 cpu 的节点通过它们的单元地址值 0 和 1 来区分, 名称为 ethernet 的节点通过其单元地址值 fe002000 和 fe003000 来区分。
    在这里插入图片描述
  • 属性定义(Properties Definitions) : 属性定义是一组键值对, 用于描述设备的配置和特性。 属性可以根据设备的需求进行定义, 例如寄存器地址、 中断号、 时钟频率等
  • 子节点(Child Nodes) : 子节点是当前节点的子项, 用于进一步描述硬件设备的子组件或配置。 子节点可以包含自己的属性定义和更深层次的子节点, 形成设备树的层次结构。

例子如下:

/dts-v1/;
/{
uart0: uart@fe001000 {compatible="ns16550";reg=<0xfe001000 0x100>;};
};

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

// 根节点之外使用label来引用node
&uart0 {status = “disabled”;
};
根节点之外使用全路径:
&{/uart@fe001000} {status = “disabled”;
};

address-cells 和 size-cells 属性

  • #address-cells 属性是一个位于设备树根节点的特殊属性, 它指定了设备树中地址单元的位数。 地址单元是设备树中用于表示设备地址的单个单位。 它通常是一个整数, 可以是十进制或十六进制值
    *#size-cells属性也是一个位于设备树根节点的特殊属性, 它指定了设备树中大小单元的位数。 大小单元是设备树中用于表示设备大小的单个单位。 它通常是一个整数, 可以是十进制或十六进制值。

示例1:

node1 {#address-cells = <1>;#size-cells = <1>;node1-child {reg = <0x02200000 0x4000>;// 其他属性和子节点的定义};
};
/* 解释: node1-child 节点的 reg 属性使用了 <0x02200000 0x4000> 表示地址和大小。
由于 #address-cells 的值为 <1>, 表示使用一个单元来表示地址。 #size-cells 的值也为 <1>, 表示使用一个单元来表示大小。
解释后的地址和大小值如下:
地址部分: 0x02200000 被解释为一个地址单元, 地址为 0x02200000。
大小部分: 0x4000 被解释为一个大小单元, 大小为 0x4000   
*/

示例2:

node1 {#address-cells = <2>;#size-cells = <0>;node1-child {reg = <0x0000 0x0001>;// 其他属性和子节点的定义};
};
/* 解释: node1-child 节点的 reg 属性使用了 <0x0000 0x0001> 表示地址。 由于#address-cells 的值为 <2>, 表示使用两个单元来表示地址。 #size-cells 的值为 <0>, 表示不使用单元来表示大小。
解释后的地址值如下:
地址部分: 0x0000 0x0001 被解释为两个地址单元, 其中第一个地址单元为 0x0000, 第二个地址单元为 0x0001。
*/ 

model属性

model 属性用于描述设备的型号或者名称(可选)

my_device{compatible = "device";model = "My Device";
};

status属性

status 属性用于描述设备或节点的状态

  • “okay”: 表示设备或节点正常工作, 可用。
  • “disabled”: 表示设备或节点被禁用, 不可用。
  • “reserved”: 表示设备或节点已被保留, 暂时不可用。
  • “fail”: 表示设备或节点初始化或操作失败, 不可用
my_device{compatible = "device";model = "My Device";status = "okay";
};

compatible属性

compatible 属性用于描述设备的兼容性信息,用于识别设备节点与驱动程序之间的匹配关系
compatible 属性的值是一个字符串或字符串列表
建议取这样的形式: “manufacturer,model”,即“厂家名,模块名”。

示例:

my_device {compatible = "vendor,device";  // 指定设备节点与特定厂商的特定设备兼容// 其他属性和子节点的定义
};compatible =  ["vendor,device1", "vendor,device2"]; //用于指定设备节点与多个设备兼容, 通常用于设备节点具有多种变体或配置compatible = "vendor,*"// 指定设备节点与特定厂商的所有设备兼容, 不考虑具体的设备标识。

3.2 aliases节点

aliases 节点是一个特殊的节点, 用于定义设备别名,位于设备树的根部

/dts-v1/;
/{aliases{mmc0 = &sdmmc0;serial0 = "/simple@fe000000/seria1@11c500";};
};
/*
1. mmc0 别名与设备树中的 sdmmc0 节点相关联。 通过使用别名 mmc0, 其他设备节点或客户端程序可以更方便地引用 sdmmc0 节点, 而不必直接使用其完整路径。
2. serial0 别名与设备树中的路径 /simple@fe000000/seria1@11c500 相关联。 通过使用别名 serial0, 其他设备节点或客户端程序可以更方便地引用该路径, 而不必记住整个路径字符串。
*/

注: aliases 节点中定义的别名只在设备树内部可见, 不能在设备树之外引用。它们主要用于设备树的内部组织和引用, 以提高可读性和可维护性。

3.3 chosen节点

chosen 节点是设备树中的一个特殊节点, 用于传递和存储系统引导和配置的相关信息。它位于设备树的根部。
chosen节点包含以下子节点和属性:

  • bootargs: 用于存储引导内核时传递的命令行参数。 它可以包含诸如内核参数、 设备树参数等信息。 在引导过程中, 操作系统或引导加载程序可以读取该属性来获取启动参数。
  • stdout-path:用于指定用于标准输出的设备路径。 在引导过程中, 操作系统可以使用该属性来确定将控制台输出发送到哪个设备, 例如串口或显示屏。
  • firmware-name:用于指定系统固件的名称。 它可以用于标识所使用的引导加载程序或固件的类型和版本。
  • linux,initrd-start linux,initrd-end: 这些属性用于指定 Linux 内核初始化 RAM 磁盘(initrd) 的起始地址和结束地址。 这些信息在引导过程中被引导加载程序使用, 以将 initrd 加载到内存中供内核使用。
  • 其他自定义属性: chosen 节点还可以包含其他自定义属性, 用于存储特定于系统引导和配置的信息。
chosen {bootargs = "root=/dev/nfs rw nfsroot=192.168.1.1 console=ttyS0,115200";
};
/*
通过这些命令行参数, 操作系统或引导加载程序可以配置内核在引导过程中正确地加载NFS 根文件系统, 并将控制台输出发送到指定的串口设备。
*/

通过使用 chosen 节点, 系统引导过程中的相关信息可以方便地传递给操作系统或引导加载程序。

3.3 device_type节点

device_type 节点是用于描述设备类型的节点

 1. cpu: 	  	表示中央处理器2. memory: 	表示内存设备3. display:   显示设备,液晶显示屏4. serial:    表示串行通信设备,串口5. ethernet:  表示以太网设备6. usb:		表示通用串行总线设备7. i2c:  	    表示使用I2C(Inter-Integrated Circuit)总线通信的设备8. spi:       表示使用SPI(Serial Peripheral Interface)总线通信的设备9. gpio:       表示通用输入/输出设备10.pwm:  		表示脉宽调制设备 

3.4 板子启动后查看设备树

# ls /sys/firmware/
devicetree fdt
  • /sys/firmware/devicetree 目录下是以目录结构程现的 dtb 文件, 根节点对应 base 目录,每一个节点对应一个目录, 每一个属性对应一个文件。
  • 这些属性的值如果是字符串,可以使用 cat 命令把它打印出来;对于数值,可以用hexdump 把它打印出来。

4. 内核对设备树的处理

从源代码文件 dts 文件开始,设备树的处理过程为:
在这里插入图片描述
①dts 在 PC 机上被编译为 dtb 文件;
② u-boot 把 dtb 文件传给内核;
③ 内核解析 dtb 文件,把每一个节点都转换为 device_node 结构体;
④ 对于某些 device_node 结构体,会被转换为 platform_device 结构体。

device_node 结构体定义和property结构体在内核源码的“/include/linux/of.h” 文件中

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; // 已删除的属性列表struct device_node *parent; // 父设备节点指针struct device_node *child; // 子设备节点指针struct device_node *sibling; // 兄弟设备节点指针struct kobject kobj; // 内核对象(用于 sysfs)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
};
struct property {char	*name; // 属性的名称int	length; // 属性值的长度(字节数)void	*value; // 属性值的指针struct property *next; // 下一个属性节点指针unsigned long _flags; // 属性的标志位unsigned int unique_id; // 属性的唯一标识 struct bin_attribute attr;// 内核对象二进制属性
};

哪些设备树节点会被转换为 platform_device

  • 根据规则 1, 首先遍历根节点下包含 compatible 属性的子节点, 对于每个子节点, 创建一个对应的 platform_device。
  • 根据规则 2, 遍历包含 compatible 属性为 “simple-bus”、 “simple-mfd” 、“arm、amba-bus”、“isa” 的节点以及它们的子节点。 如果子节点包含 compatible 属性值则会创建一个对应的 platform_device。
  • 根据规则 3, 总线 I2C、 SPI 节点下的子节点: 不转换为 platform_device某个总线下到子节点, 应该交给对应的总线驱动程序来处理, 它们不应该被转换为platform_device。

5. of操作函数

5.1获取设备树节点

of_find_node_by_name 函数

函数原型:
struct device_node *of_find_node_by_name(struct device_node *from, const char *nam
e);
函数参数和返回值含义如下:
from:开始查找的节点,如果为 NULL 表示从根节点开始查找整个设备树。
name:要查找的节点名字。
返回值: 找到的节点,如果为 NULL 表示查找失败。

of_find_node_by_path 函数

函数原型:
struct device_node *of_find_node_by_path(const char *path);
函数参数和返回值含义如下:
path:带有全路径的节点名,可以使用节点的别名,比如“/backlight”就是 backlight 这个
节点的全路径。
返回值: 找到的节点,如果为 NULL 表示查找失败

of_get_parent 函数

函数原型:
struct device_node *of_get_parent(const struct device_node *node);
头文件:
#include <linux/of.h>
函数作用:
该函数接收一个指向设备树节点的指针 node, 并返回该节点的父节点的指针。
参数含义:
node: 要获取父节点的设备树节点指针。
返回值:
如果找到匹配的节点, 则返回对应的 struct device_node 指针。
如果未找到匹配的节点, 则返回 NULL

of_get_next_child 函数

函数原型:
struct device_node *of_get_next_child(const struct device_node *node, struct device_node *prev);
头文件:
#include <linux/of.h>
函数作用:
该函数接收两个参数: node 是当前节点, prev 是上一个子节点。 它返回下一个子节点的指针。
参数含义:
node: 当前节点, 用于指定要获取子节点的起始节点。
prev: 上一个子节点, 用于指定从哪个子节点开始获取下一个子节点。 如果为 NULL, 则从起始节点的第一个子节点开始。
返回值:
如果找到匹配的节点, 则返回对应的 struct device_node 指针。
如果未找到匹配的节点, 则返回 NULL

5.2提取属性值

of_find_property函数

函数原型:
property *of_find_property(const struct device_node *np,
const char *name,int *lenp)
函数参数和返回值含义如下:
np:设备节点。
name: 属性名字。
lenp:属性值的字节数
返回值: 找到的属性。

of_property_count_elems_of_size 函数

int of_property_count_elems_of_size(const struct device_node *np,
const char *propname, int elem_size)
函数参数和返回值含义如下:
np:设备节点。
proname: 需要统计元素数量的属性名字。
elem_size:元素长度。
返回值: 得到的属性元素数量。

of_property_read_u32_index 函数

int of_property_read_u32_index(const struct device_node *np,
const char *propname,
u32 index,
u32 *out_value)
函数参数和返回值含义如下:
np:设备节点。
proname: 要读取的属性名字。
index:要读取的值标号。
out_value:读取到的值
返回值: 0 读取成功,负值,读取失败, -EINVAL 表示属性不存在, -ENODATA 表示没有要读取的数据, -EOVERFLOW 表示属性值列表太小。

of_property_read_string 函数

int of_property_read_string(struct device_node *np,
const char *propname,const char **out_string)
函数参数和返回值含义如下:
np:设备节点。
proname: 要读取的属性名字。
out_string:读取到的字符串值。
返回值: 0,读取成功,负值,读取失败。

of_n_addr_cells 函数

int of_n_addr_cells(struct device_node *np)
函数参数和返回值含义如下:
np:设备节点。
返回值: 获取到的#address-cells 属性值。

of_n_size_cells 函数

int of_n_size_cells(struct device_node *np)
函数参数和返回值含义如下:
np:设备节点。
返回值: 获取到的#size-cells 属性值。

6.pinctrl和gpio子系统

在设备树实验开始之前,我们先了解一下pinctrl和gpio子系统

Pinctrl子系统

pinctrl(引脚控制) 用于描述和配置硬件设备上的引脚功能和连接方式。 它是设备树的一部分, 用于在启动过程中传递引脚配置信息给操作系统和设备驱动程序, 以便正确地初始化和控制引脚。
在设备树中, pinctrl(引脚控制) 使用了客户端和服务端的概念来描述引脚控制的关系和配置。

现在的芯片动辄几百个引脚,在使用到 GPIO 功能时,让你一个引脚一个引脚去找对应的寄存器,配置过程烦躁且费劲。
通过把引脚的复用、配置分离出来,做成Pinctrl子系统,给GPIO、I2C等使用。

    my_device_100ask_imx6ull{pinctrl-names = "default";pinctrl-0 = <&myled_for_gpio_subsys>;status = "okay";};

解释如下:
pinctrl-names 属性:

pinctrl-names = "default";
这一行定义了一个或多个引脚配置状态的名称。在这个例子中,只定义了一个名为 "default" 的状态。这意味着当设备启动或需要配置引脚时,会查找名为 "default" 的引脚配置。

pinctrl-0 属性:

pinctrl-0 = <&myled_for_gpio_subsys>;
这一行指定了对应于 "default" 状态(由 pinctrl-names 属性定义)的引脚配置引用。<&myled_for_gpio_subsys> 是一个对设备树中另一个节点的引用,这个节点应该包含了具体的引脚配置信息,比如哪些引脚被选中、它们的电气特性(如驱动能力、上拉/下拉等)以及它们被配置为什么功能(如GPIO、I2C等)。

GPIO子系统
GPIO(General Purpose Input/Output,通用输入输出)子系统是Linux内核中用于管理通用输入输出引脚的一个子系统。GPIO引脚是芯片上的一种常见资源,可以用于实现各种简单的输入输出功能。

以前我们通过寄存器来操作 GPIO 引脚,即使 LED 驱动程序,对于不同的板子它的代码也完全不同。
当 BSP 工程师实现了 GPIO 子系统后,我们就可以:

  • 在设备树里指定 GPIO 引脚
  • 在驱动代码中:使用 GPIO 子系统的标准函数获得 GPIO、设置 GPIO 方向、读取/设置 GPIO 值
/dts-v1/;
/{...
my_device_100ask_imx6ull{compatible = "100ask,leddrv";pinctrl-names = "default";pinctrl-0 = <&myled_for_gpio_subsys>;led-gpios = <&gpio5 3 GPIO_ACTIVE_LOW>;status = "okay";};
};&iomuxc_snvs {...imx6ul-evk {... /* 省略其他代码 */myled_for_gpio_subsys: myled_for_gpio_subsys {        /*!< Function assigned for the core: Cortex-A7[ca7] */fsl,pins = <MX6ULL_PAD_SNVS_TAMPER3__GPIO5_IO03        0x000110A0/*  数值定义在IMX6ULLRM.pdf文档中的1529页*/>;};};
};

解释如下:
led-gpios 属性:

&gpio5:这是一个对GPIO控制器的引用,指向设备树中定义的GPIO控制器节点之一。在这个例子中,它指的是编号为5的GPIO控制器。这个引用告诉系统LED灯连接到了哪个GPIO控制器上。3:这个数字指定了GPIO控制器上的具体引脚编号。在这个例子中,LED灯连接到了GPIO控制器5的第3个引脚上。GPIO_ACTIVE_LOW:这是一个标志,用于指定GPIO引脚的活动状态。GPIO_ACTIVE_LOW意味着当引脚处于低电平时(即0V或接近0V),LED灯被认为是激活的(即点亮)。相反,如果引脚处于高电平(通常是3.3V或5V,取决于系统的电源电压),LED灯则被认为是非激活的(即熄灭)。

pinctrl-names 和 pinctrl-0 属性(虽然它们本身不直接属于GPIO子系统,但与GPIO引脚的使用密切相关)

总的来说,这些代码片段共同描述了如何将一个GPIO引脚(GPIO5的引脚3)配置为LED灯的控制引脚,并指定了该引脚在低电平时激活LED灯的行为。同时,它们还引用了一个引脚配置(通过myled_for_gpio_subsys)

这里我们使用"Pins_Tool_for_i.MX_Processors_v6_x64"工具打IMX6ULL 的配置文件“MCIMX6Y2xxx08.mex”,就可以在 GUI 界面中选择引脚,
配置它的功能,这就可以自动生成 Pinctrl 的子节点信息。
在这里插入图片描述

7. 设备树下的 LED 驱动实验

注:本实验使用的是韦东山I.MX6U开发板

100ask_imx6ull-14x14.dts 完成编写后,在(内核目录)编译dts文件

book@100ask:~/100ask_imx6ull-sdk/Linux-4.9.88$ make dtbs

将编译好的dtb文件拷贝到网络文件系统中

cp /home/book/100ask_imx6ull-sdk/Linux-4.9.88/arch/arm/boot/dts/100ask_imx6ull-14x14.dtb ~/nfs_rootfs/

7.1编写驱动文件

led_driver.c

#include "asm-generic/errno-base.h"
#include "asm-generic/int-ll64.h"
#include "asm/gpio.h"
#include "linux/compiler.h"
#include "linux/err.h"
#include "linux/export.h"
#include "linux/gpio/consumer.h"
#include "linux/gpio/driver.h"
#include "linux/ioport.h"
#include "linux/kdev_t.h"
#include "linux/leds.h"
#include "linux/printk.h"
#include "linux/stddef.h"
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>#define LED_DEV_COUNT 1
#define LED_DEV_NAME "dts_plat_led"/* led_dev结构体 */
struct led_dev {dev_t dev_id; /* 设备id */struct cdev cdev; /* cdev*/struct class *class; /* class*/struct device *device; /* device*/int major; /* major*/struct device_node *node; /* led_node*/struct gpio_desc    *led_gpio; /* led_gpio*/
};static struct led_dev led_dev;static int led_open (struct inode *node, struct file *filp){printk("%s %s line:%d\n", __FILE__,__FUNCTION__,__LINE__);/* 设置GPIO引脚为输出模式,输出低电平 */gpiod_direction_output(led_dev.led_gpio, 0);return 0;
}static ssize_t led_write (struct file *filp, const char __user *buf, size_t size, loff_t *offset){int status;int err;printk("%s %s line:%d\n", __FILE__,__FUNCTION__,__LINE__);err = copy_from_user(&status, buf, 1);/* 设置GPIO的值 */gpiod_set_value(led_dev.led_gpio,status);return 1;
}static const struct file_operations led_fops = {.owner	= THIS_MODULE,.open   = led_open,.write  = led_write,
};/*  当驱动与设备匹配成功后执行此函数   */
int led_probe(struct platform_device *led_device){printk("%s %s line:%d\n", __FILE__,__FUNCTION__,__LINE__);// 1.gpio/*  led-gpios = <&gpio5 3 GPIO_ACTIVE_LOW>; */led_dev.led_gpio = gpiod_get(&led_device->dev, "led", 0);if(IS_ERR(led_dev.led_gpio)){dev_err(&led_device->dev, "Failed to get GPIO for led");return PTR_ERR(led_dev.led_gpio);}// 2.注册file_operations结构体// 2.1注册设备号if(led_dev.major){led_dev.dev_id = MKDEV(led_dev.major, 0);register_chrdev_region(led_dev.dev_id, LED_DEV_COUNT, LED_DEV_NAME);}else{alloc_chrdev_region(&led_dev.dev_id,0, LED_DEV_COUNT, LED_DEV_NAME);led_dev.major = MAJOR(led_dev.dev_id);}// 2.2添加cdev结构体cdev_init(&led_dev.cdev, &led_fops);cdev_add(&led_dev.cdev, led_dev.dev_id, LED_DEV_COUNT);// 2.3创建classled_dev.class = class_create(THIS_MODULE, LED_DEV_NAME);if(IS_ERR(led_dev.class)){return PTR_ERR(led_dev.class);}// 2.4 创建设备 /dev/dts_plat_ledled_dev.device = device_create(led_dev.class, NULL, led_dev.dev_id, NULL, LED_DEV_NAME);if(IS_ERR(led_dev.device)){return PTR_ERR(led_dev.device);}return 0;
}/*  remove 函数,移除 platform 驱动的时候此函数会执行    */
int led_remove(struct platform_device *led_device){gpio_set_value((unsigned int)led_dev.led_gpio,1); // turn off LEDcdev_del(&led_dev.cdev);unregister_chrdev_region(led_dev.dev_id, LED_DEV_COUNT);device_destroy(led_dev.class, led_dev.dev_id);class_destroy(led_dev.class);gpiod_put(led_dev.led_gpio); // release gpioreturn 0;}static const struct of_device_id leds_table[]   = { {.compatible = "100ask,leddrv"}, /* 100ask_imx6ull-14x14.dts中定义,compatible相同时才匹配成功 */{}
};static struct platform_driver led_driver = {.driver = {.name = "imx6ul-led", /* driver name */.owner = THIS_MODULE,.of_match_table = leds_table, /* device tree match table */   },.probe = led_probe, /* device probe function */    .remove = led_remove, /* device remove function */
} ;static int __init led_init(void){printk("%s %s line %d\n", __FILE__,__FUNCTION__,__LINE__);return platform_driver_register(&led_driver);
}
static void __exit led_exit(void){printk("%s %s line %d\n", __FILE__,__FUNCTION__,__LINE__);platform_driver_unregister(&led_driver);
}module_init(led_init); 
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Pumpk1n");

led_test.c

#include "asm-generic/fcntl.h"
#include "linux/string.h"
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <unistd.h>int main(int argc, char **argv) {int fd;int status;if(argc != 3){printf("Usage: %s /dev/dts_plat_led on\n", argv[0]); //  turn on ledprintf("Usage: %s /dev/dts_plat_led off\n", argv[0]);// turn off ledreturn -1;}fd = open(argv[1],O_RDWR);if(fd < 0){printf("Error opening /dev/dts_plat_led\n");return -1;}if(strcmp(argv[2], "on") == 0){// turn on the LEDstatus = 1;write(fd,&status,1);}else if(strcmp(argv[2], "off") == 0){// turn off the LEDstatus = 0;write(fd,&status,1);}return 0;
}

Makefile文件

KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88obj-m += led_driver.oall: $(MAKE) -C $(KERN_DIR) M=`pwd` modules$(CROSS_COMPILE)gcc -o led_test led_test.c clean:make -C $(KERN_DIR) M=`pwd` modules cleanrm -rf modules.orderrm -f led_test

执行命令: make

编译 led_driver.c文件为 led_driver.ko内核模块文件
编译led_test.c文件为led_test可执行文件

拷贝这两个文件到网络文件系统中

cp led_test led_driver.ko ~/nfs_rootfs/

串口连接开发板

挂载到网络文件系统中

[root@100ask:~]# mount -t nfs -o nolock,vers=3 192.168.5.11:/home/book/nfs_rootfs /mnt

将/mnt/100ask_imx6ull-14x14.dtb文件复制到/boot/下面

[root@100ask:~]# cp /mnt/100ask_imx6ull-14x14.dtb /boot/

重启开发板,等待重启完成后重新挂载网络文件系统中

[root@100ask:~]# reboot
[root@100ask:~]# mount -t nfs -o nolock,vers=3 192.168.5.11:/home/book/nfs_rootfs /mnt

进入到/mnt/目录中,并向内核加载模块文件led_driver.ko

[root@100ask:~]# cd /mnt
[root@100ask:mnt]# insmod led_driver.ko

运行测试文件测试实验结果

[root@100ask:/mnt]# ./led_test /dev/dts_plat_led on
[root@100ask:/mnt]# ./led_test /dev/dts_plat_led off

相关文章:

嵌入式Linux学习: 设备树实验

设备树&#xff08;DeviceTree&#xff09;是一种硬件描述机制&#xff0c;用于在嵌入式系统和操作系统中描述硬件设备的特性、连接关系和配置信息。它提供了一种与平台无关的方式来描述硬件&#xff0c;使得内核与硬件之间的耦合度降低&#xff0c;提高了系统的可移植性和可维…...

eqmx上读取数据处理以后添加到数据库中

目录 定义一些静态变量 定时器事件的处理器 订阅数据的执行器 处理json格式数据和将处理好的数据添加到数据库中 要求和最终效果 总结一下 定义一些静态变量 // 在这里都定义成全局的 一般都定义成静态的private static MqttClient mqttClient; // mqtt客户端 private s…...

【中项】系统集成项目管理工程师-第5章 软件工程-5.3软件设计

前言&#xff1a;系统集成项目管理工程师专业&#xff0c;现分享一些教材知识点。觉得文章还不错的喜欢点赞收藏的同时帮忙点点关注。 软考同样是国家人社部和工信部组织的国家级考试&#xff0c;全称为“全国计算机与软件专业技术资格&#xff08;水平&#xff09;考试”&…...

C++学习笔记-内联函数使用和含义

引言 内联函数是C为了优化在函数的调用带来的性能开销而设计的&#xff0c;特别是当函数体很小且频繁调用时&#xff0c;内联函数可以让编译器在调用点直接展开函数体&#xff0c;从而避免了函数调用的开销。 一、内联函数的定义与含义 1.1 定义 内联函数是通过在函数声明或…...

数据库(MySQL)-视图、存储过程、触发器

一、视图 视图的定义、作用 视图是从一个或者几个基本表&#xff08;或视图&#xff09;导出的表。它与基本表不同&#xff0c;是一个虚表。但是视图只能用来查看表&#xff0c;不能做增删改查。 视图的作用&#xff1a;①简化查询 ②重写格式化数据 ③频繁访问数据库 ④过…...

js 优雅的实现模板方法设计模式

在JavaScript中&#xff0c;优雅地实现模板方法设计模式通常意味着我们要遵循一些最佳实践&#xff0c;如清晰地定义算法的骨架&#xff08;模板方法&#xff09;&#xff0c;并确保子类能够灵活地扩展或修改这些算法中的特定步骤。由于JavaScript是一种动态语言&#xff0c;我…...

C语言——输入输出

C语言——输入输出 输入输出函数的类型getcharputcharprintf占位符的分类 scanf 什么是输入输出呢&#xff1f; 所谓输入输出是以计算机为主机而言的&#xff0c;往内存中输入数据为输入&#xff0c;反之从内存中输出数据为输出。 输入输出的功能 C语言本身是不提供输入输出功能…...

【微软蓝屏】微软Windows蓝屏问题汇总与应对解决策略

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…...

OpenCV图像滤波(2)均值平滑处理函数blur()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 在OpenCV中&#xff0c;blur()函数用于对图像应用简单的均值模糊&#xff08;mean blur&#xff09;。这种模糊效果可以通过将图像中的每个像素替…...

Android lmkd机制详解

目录 一、lmkd介绍 二、lmkd实现原理 2.1 工作原理图 2.2 初始化 2.3 oom_adj获取 2.4 监听psi事件及处理 2.5 进程选取与查杀 2.5.1 进程选取 2.5.2 进程查杀 三、关键系统属性 四、核心数据结构 五、代码时序 一、lmkd介绍 Android lmkd采用epoll方式监听linux内…...

linux shell(中)

结构化命令 if语句 if-then 最基本的结构化命令是 if-then 语句。if-then 语句的格式如下&#xff1a; if command thencommands ifif command; then # 通过把分号&#xff08;;&#xff09;放在待求值的命令尾部&#xff0c;可以将 then 语句写在同一行commands ifbash sh…...

VMware三种网络模式---巨细

文章目录 目录 ‘一.网络模式概述 二.桥接模式 二.NAT模式 三.仅主机模式 四.案例演示 防火墙配置&#xff1a; 虚拟电脑配置 前言 本文主要介绍VMware的三种网络模式 ‘一.网络模式概述 VMware中分为三种网络模式&#xff1a; 桥接模式&#xff1a;默认与宿主机VMnet0绑…...

力扣高频SQL 50 题(基础版)第一题

文章目录 力扣高频SQL 50 题&#xff08;基础版&#xff09;第一题1757.可回收且低脂的产品题目说明思路分析实现过程准备数据&#xff1a;实现方式&#xff1a;结果截图&#xff1a; 力扣高频SQL 50 题&#xff08;基础版&#xff09;第一题 1757.可回收且低脂的产品 题目说…...

2.1.卷积层

卷积 ​ 用MLP处理图片的问题&#xff1a;假设一张图片有12M像素&#xff0c;那么RGB图片就有36M元素&#xff0c;使用大小为100的单隐藏层&#xff0c;模型有3.6B元素&#xff0c;这个数量非常大。 识别模式的两个原则&#xff1a; 平移不变性&#xff08;translation inva…...

网易《永劫无间》手游上线,掀起游戏界狂潮

原标题&#xff1a;网易《永劫无间》手游上线&#xff0c;网友&#xff1a;发烧严重 易采游戏网7月26日消息&#xff1a;自网易宣布《永劫无间》手游即将上线以来&#xff0c;广大游戏玩家的期待值就不断攀升。作为一款拥有丰富内容和极高自由度的游戏&#xff0c;《永劫无间》…...

RNN(一)——循环神经网络的实现

文章目录 一、循环神经网络RNN1.RNN是什么2.RNN的语言模型3.RNN的结构形式 二、完整代码三、代码解读1.参数return_sequences2.调参过程 一、循环神经网络RNN 1.RNN是什么 循环神经网络RNN主要体现在上下文对理解的重要性&#xff0c;他比传统的神经网络&#xff08;传统的神…...

php 根据位置的经纬度计算距离

在开发中,我们要经常和位置打交道,要计算附近的位置、距离什么的。如下: 一.sql语句 SELECT houseID,title,location,chamber,room,toward,area,rent,is_verify,look_type,look_time, traffic,block_name,images,tag,create_time,update_time, location->&g…...

17 Python常用内置函数——基本输入输出

input() 和 print() 是 Python 的基本输入输出函数&#xff0c;前者用来接收用户的键盘输入&#xff0c;后者用来把数据以指定的格式输出到标准控制台或指定的文件对象。无论用户输入什么内容&#xff0c;input() 一律作为字符串对待&#xff0c;必要时可以使用内置函数 int()、…...

【Web】LitCTF 2024 题解(全)

目录 浏览器也能套娃&#xff1f; 一个....池子&#xff1f; 高亮主题(划掉)背景查看器 百万美元的诱惑 SAS - Serializing Authentication exx 浏览器也能套娃&#xff1f; 随便试一试&#xff0c;一眼ssrf file:///flag直接读本地文件 一个....池子&#xff1f; {…...

家政项目小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;家政人员管理&#xff0c;家政服务管理&#xff0c;咨询信息管理&#xff0c;咨询服务管理&#xff0c;家政预约管理&#xff0c;留言板管理&#xff0c;系统管理 微信端账号功能…...

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…...

多模态2025:技术路线“神仙打架”,视频生成冲上云霄

文&#xff5c;魏琳华 编&#xff5c;王一粟 一场大会&#xff0c;聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中&#xff0c;汇集了学界、创业公司和大厂等三方的热门选手&#xff0c;关于多模态的集中讨论达到了前所未有的热度。其中&#xff0c;…...

Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动

一、前言说明 在2011版本的gb28181协议中&#xff0c;拉取视频流只要求udp方式&#xff0c;从2016开始要求新增支持tcp被动和tcp主动两种方式&#xff0c;udp理论上会丢包的&#xff0c;所以实际使用过程可能会出现画面花屏的情况&#xff0c;而tcp肯定不丢包&#xff0c;起码…...

Zustand 状态管理库:极简而强大的解决方案

Zustand 是一个轻量级、快速和可扩展的状态管理库&#xff0c;特别适合 React 应用。它以简洁的 API 和高效的性能解决了 Redux 等状态管理方案中的繁琐问题。 核心优势对比 基本使用指南 1. 创建 Store // store.js import create from zustandconst useStore create((set)…...

Linux云原生安全:零信任架构与机密计算

Linux云原生安全&#xff1a;零信任架构与机密计算 构建坚不可摧的云原生防御体系 引言&#xff1a;云原生安全的范式革命 随着云原生技术的普及&#xff0c;安全边界正在从传统的网络边界向工作负载内部转移。Gartner预测&#xff0c;到2025年&#xff0c;零信任架构将成为超…...

unix/linux,sudo,其发展历程详细时间线、由来、历史背景

sudo 的诞生和演化,本身就是一部 Unix/Linux 系统管理哲学变迁的微缩史。来,让我们拨开时间的迷雾,一同探寻 sudo 那波澜壮阔(也颇为实用主义)的发展历程。 历史背景:su的时代与困境 ( 20 世纪 70 年代 - 80 年代初) 在 sudo 出现之前,Unix 系统管理员和需要特权操作的…...

鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个生活电费的缴纳和查询小程序

一、项目初始化与配置 1. 创建项目 ohpm init harmony/utility-payment-app 2. 配置权限 // module.json5 {"requestPermissions": [{"name": "ohos.permission.INTERNET"},{"name": "ohos.permission.GET_NETWORK_INFO"…...

Ascend NPU上适配Step-Audio模型

1 概述 1.1 简述 Step-Audio 是业界首个集语音理解与生成控制一体化的产品级开源实时语音对话系统&#xff0c;支持多语言对话&#xff08;如 中文&#xff0c;英文&#xff0c;日语&#xff09;&#xff0c;语音情感&#xff08;如 开心&#xff0c;悲伤&#xff09;&#x…...

JVM暂停(Stop-The-World,STW)的原因分类及对应排查方案

JVM暂停(Stop-The-World,STW)的完整原因分类及对应排查方案,结合JVM运行机制和常见故障场景整理而成: 一、GC相关暂停​​ 1. ​​安全点(Safepoint)阻塞​​ ​​现象​​:JVM暂停但无GC日志,日志显示No GCs detected。​​原因​​:JVM等待所有线程进入安全点(如…...

【碎碎念】宝可梦 Mesh GO : 基于MESH网络的口袋妖怪 宝可梦GO游戏自组网系统

目录 游戏说明《宝可梦 Mesh GO》 —— 局域宝可梦探索Pokmon GO 类游戏核心理念应用场景Mesh 特性 宝可梦玩法融合设计游戏构想要素1. 地图探索&#xff08;基于物理空间 广播范围&#xff09;2. 野生宝可梦生成与广播3. 对战系统4. 道具与通信5. 延伸玩法 安全性设计 技术选…...