嵌入式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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/50602.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

立创梁山派--移植开源的SFUD和FATFS实现SPI-FLASH文件系统

本文主要是在sfud的基础上进行fatfs文件系统的移植&#xff0c;并不对sfud的移植再进行过多的讲解了哦&#xff0c;所以如果想了解sfud的移植过程&#xff0c;请参考我的另外一篇文章&#xff1a;传送门 正文开始咯 首先我们需要先准备资料准备好&#xff0c;这里对于fatfs的…

【ESP32 IDF 软件模拟SPI驱动 W25Q64存储与读取数组】

目录 SPISPI介绍SPI时序代码编写&#xff08;spi&w25q64&#xff09; 代码调试 SPI SPI介绍 SPI&#xff08;Serial Peripheral Interface&#xff0c;串行外围设备接口&#xff09;是一种高速、全双工、同步的串行通信总线&#xff0c;常用于微控制器与各种外围设备&…

苍穹外卖浏览器前端界面修改

背景&#xff1a; 客户原始方案是期望做一个Spring Boot Vue的饿了么系统&#xff0c;但时间上太仓促&#xff0c;所以建议选择开源的苍穹外码目作为作业提交。 客户接受了建议的方案后&#xff0c;期望对前端页面做一些个性化的定制修改。 过程&#xff1a; 苍穹外卖简单介…

【HTML+CSS】HTML超链接:构建网页导航的基石

目录 什么是HTML超链接&#xff1f; 基本语法 示例 链接到另一个网页 链接到同一页面内的不同部分 常用属性 在Web开发的广阔世界中&#xff0c;HTML&#xff08;HyperText Markup Language&#xff09;作为网页内容的标准标记语言&#xff0c;扮演着至关重要的角色。而在…

重拾CSS,前端样式精读-函数(颜色,计算,图像和图形)

前言 本文收录于CSS系列文章中&#xff0c;欢迎阅读指正 在计算机编程中&#xff0c;函数有着重要的作用和意义&#xff0c;它可以实现封装&#xff0c;复用&#xff0c;模块化&#xff0c;参数等功能效果&#xff0c;在如何在CSS中写变量&#xff1f;一文带你了解前端样式利…

操作系统杂项(十)

目录 一、简述socket中select、epoll的使用场景和区别 1、使用场景 2、区别 二、epoll水平触发和边缘触发的区别 三、简述Reactor和Proactor模式 1、Reactor 2、Proactor 3、区别 四、简述同步和异步的区别&#xff0c;阻塞和非阻塞的区别 1、同步与异步 2、阻塞与非…

深入分析 Android ContentProvider (五)

文章目录 深入分析 Android ContentProvider (五)ContentProvider 的性能优化和实践案例1. 性能优化技巧1.1. 数据库索引优化示例&#xff1a;添加索引 1.2. 批量操作与事务管理示例&#xff1a;批量插入操作 1.3. 使用异步操作示例&#xff1a;使用 AsyncTask 进行异步查询 1.…

Linux:基础

一、安装 二、 一些组件 2.1 git管理 集中式版本控制系统:版本库是集中存放在中央服务器的,需要时要先从中央服务器取得最新的版本进行修改,修改后再推送给中央服务器。集中式版本控制系统最大的毛病就是必须联网才能工作,网速慢的话影响太大。 分布式版本控制系统:分布…

Linux网络-wget命令

作者介绍&#xff1a;简历上没有一个精通的运维工程师。希望大家多多关注我&#xff0c;我尽量把自己会的都分享给大家&#xff0c;下面的思维导图也是预计更新的内容和当前进度(不定时更新)。 Linux服务器作为一个常用的网络服务器&#xff0c;主要的作用就是向客户端提供网络…

设计模式14-享元模式

设计模式14-享元模式 由来动机定义与结构代码推导特点享元模式的应用总结优点缺点使用享元模式的注意事项 由来动机 在很多应用中&#xff0c;可能会创建大量相似对象&#xff0c;例如在文字处理器中每个字符对象。在这些场景下&#xff0c;如果每个对象都独立存在&#xff0c…

PyCharm 2024.1.4:一站式教程与新特性解析

简介 PyCharm是由JetBrains开发的一款Python集成开发环境&#xff08;IDE&#xff09;&#xff0c;自发布以来&#xff0c;凭借其强大的功能、智能的代码补全、广泛的插件支持和用户友好的界面&#xff0c;成为了Python开发者的首选工具之一。无论是数据科学、Web开发还是其他…

Redis - SpringDataRedis - RedisTemplate

目录 概述 创建项目 引入依赖 配置文件 测试代码 测试结果 数据序列化器 自定义RedisTemplate的序列化方式 测试报错 添加依赖后测试 存入一个 String 类型的数据 测试存入一个对象 优化 -- 手动序列化 测试存入一个Hash 总结&#xff1a; 概述 SpringData 是 S…

在 ArchLinux 上编译运行 axmol 引擎

本文将在 Windows 10 上安装 Arch WSL 中编译 axmol 请确保 WSL2 已正确安装 1. 在微软应用商店安装 ArchLinux 2. 打开 Arch&#xff0c;按照提示输入用户名和密码&#xff0c;尽量简单 3. 配置清华源&#xff0c;速度快的起飞&#xff0c;否则&#xff0c;各种包会安装失败…

光伏电站气象站:现代光伏系统的重要组成部分

光伏电站气象站&#xff0c;作为现代光伏系统的重要组成部分&#xff0c;集成了气象学、电子信息技术、数据处理与分析等多学科技术于一体&#xff0c;能够实时监测并记录包括温度、湿度、风速、风向、太阳辐射强度、降雨量在内的多种气象参数。这些数据不仅是评估光伏板发电效…

GLSL教程 第8章:几何着色器

目录 8.1 几何着色器的介绍 几何着色器的主要功能&#xff1a; 几何着色器的工作流程&#xff1a; 8.2 实现基本的几何变换 示例&#xff1a;将三角形扩展成多个三角形 8.3 几何着色器的高级应用 1. 粒子系统 2. 光晕效果 3. 线框模型 小结 几何着色器是图形管线中的一…

应用层自定义协议以及序列化和反序列化

文章目录 应用层自定义协议以及序列化和反序列化1、应用层自定义协议1.1、应用层1.2、协议 2、序列化和反序列化3、TCP 为什么支持全双工4、jsoncpp基础4.1、序列化4.2、反序列化 5、实现网络版计算器6、手写序列化和反序列化 应用层自定义协议以及序列化和反序列化 1、应用层…

爬取贴吧的标题和链接

免责声明 感谢您学习本爬虫学习Demo。在使用本Demo之前&#xff0c;请仔细阅读以下免责声明&#xff1a; 学习和研究目的&#xff1a;本爬虫Demo仅供学习和研究使用。用户不得将其用于任何商业用途或其他未经授权的行为。合法性&#xff1a;用户在使用本Demo时&#xff0c;应确…

智能算法驱动的爬虫平台:解锁网络数据的无限潜力

摘要 在信息爆炸的时代&#xff0c;网络数据如同深海宝藏&#xff0c;等待着有识之士发掘其无尽价值。本文将探索智能算法驱动的爬虫平台如何成为解锁这一宝库的关键&#xff0c;不仅剖析其技术优势&#xff0c;还通过实例展示它如何助力企业与开发者高效、稳定地采集数据&…

C语言 ——— 数组指针的定义 数组指针的使用

目录 前言 数组指针的定义 数组指针的使用 前言 之前有编写过关于 指针数组 的相关知识 C语言 ——— 指针数组 & 指针数组模拟二维整型数组-CSDN博客 指针数组 顾名思义就是 存放指针的数组 那什么是数组指针呢&#xff1f; 数组指针的定义 何为数组指针&#xf…

【QT】UDP

目录 核心API 示例&#xff1a;回显服务器 服务器端编写&#xff1a; 第一步&#xff1a;创建出socket对象 第二步&#xff1a; 连接信号槽 第三步&#xff1a;绑定端口号 第四步&#xff1a;编写信号槽所绑定方法 第五步&#xff1a;编写第四步中处理请求的方法 客户端…