修改之前的代码使得利用设备树文件和Platform总线设备驱动实现对多个LED的驱动【只是假想对LED进行驱动,并没有实际的硬件操作】
引言
在下面这篇博文中:
利用Linux的Platform总线设备驱动实现对多个LED的驱动【只是假想对LED进行驱动,并没有实际的硬件操作】
我们利用Platform总线设备驱动的思想实现了对多个LED的驱动。
Platform总线设备驱动以及其它的总线设备驱动都将驱动分成了三个部分,即总线 (Bus)、设备 (Device) 和 驱动 (Driver)。
- 设备(硬件资源描述): 代表具体的硬件资源,例如一个 LED,同时描述了它的相关硬件信息(如寄存器地址、IRQ 等)。
- 驱动: 实现了操作该设备的具体逻辑代码。
- 总线: 负责在设备和驱动之间建立匹配关系,并调用驱动程序对设备进行初始化。
在下面这篇博文中:
利用Linux的Platform总线设备驱动实现对多个LED的驱动【只是假想对LED进行驱动,并没有实际的硬件操作】
总线部分就是Platform总线,Linux内核源码已经为我们写好了,我们只需写设备(硬件资源描述)和驱动部分。
对于设备(硬件资源描述)部分,我们是写在C文件board_A_led.c
中的,并将其信息存储在结构体platform_device
中的,驱动程序部分根据结构体platform_device
来获取硬件资源信息,进行相应的设备类、设备文件的生成,当然还有具体对硬件的初始化和实际操作。但这样描述硬件资源的办法很麻烦,也很不统一,不利于驱动程序的标准化。
有没有一种方法能把系统中用到的设备资源进行集中且规范的描述呢?
有…那就是设备树文件。
本篇博文我们就使用设备树文件来把之前在博文“利用Linux的Platform总线设备驱动实现对多个LED的驱动【只是假想对LED进行驱动,并没有实际的硬件操作】”的驱动程序进行改进。
显然改进的重点在于对驱动总线中硬件资源描述的部分用设备树进行替换,当然由于硬件资源描述的方式变了,相应的,驱动部分获取硬件资源信息的方式也要改变。
阅读下面内容前建议先行阅读的博文
Platform总线设备驱动是如何把设备资源描述结构体(platform_device)与驱动结构体(platform_driver)匹配起来的
利用Linux的Platform总线设备驱动实现对多个LED的驱动【只是假想对LED进行驱动,并没有实际的硬件操作】
Linux的设备树文件(dts)的基础知识
完整源代码
设备树文件swh_led.dts
#define GROUP_PIN(g,p) ((g<<16) | (p))/ {swh_led@0 {compatible = "swh_leddrv";pin = <GROUP_PIN(3, 1)>;};swh_led@1 {compatible = "swh_leddrv";pin = <GROUP_PIN(5, 8)>;};};
Platform总线设备驱动的驱动部分(chip_demo_gpio.c)
#include <linux/module.h>#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/platform_device.h>
#include <linux/of.h>#include "led_opr.h"
#include "leddrv.h"
#include "led_resource.h"static int g_ledpins[100];
static int g_ledcnt = 0;static int board_demo_led_init (int which) /* 初始化LED, which-哪个LED */
{ //printk("%s %s line %d, led %d\n", __FILE__, __FUNCTION__, __LINE__, which);printk("init gpio: group %d, pin %d\n", GROUP(g_ledpins[which]), PIN(g_ledpins[which]));switch(GROUP(g_ledpins[which])){case 0:{printk("init pin of group 0 ...\n");break;}case 1:{printk("init pin of group 1 ...\n");break;}case 2:{printk("init pin of group 2 ...\n");break;}case 3:{printk("init pin of group 3 ...\n");break;}}return 0;
}static int board_demo_led_ctl (int which, char status) /* 控制LED, which-哪个LED, status:1-亮,0-灭 */
{//printk("%s %s line %d, led %d, %s\n", __FILE__, __FUNCTION__, __LINE__, which, status ? "on" : "off");printk("set led %s: group %d, pin %d\n", status ? "on" : "off", GROUP(g_ledpins[which]), PIN(g_ledpins[which]));switch(GROUP(g_ledpins[which])){case 0:{printk("set pin of group 0 ...\n");break;}case 1:{printk("set pin of group 1 ...\n");break;}case 2:{printk("set pin of group 2 ...\n");break;}case 3:{printk("set pin of group 3 ...\n");break;}}return 0;
}static struct led_operations board_demo_led_opr = {.init = board_demo_led_init,.ctl = board_demo_led_ctl,
};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;err = of_property_read_u32(np, "pin", &led_pin);g_ledpins[g_ledcnt] = led_pin;device_file_create(g_ledcnt);g_ledcnt++;return 0;}static int chip_demo_gpio_remove(struct platform_device *pdev)
{int i;/* 销毁所有已创建的设备文件 */for (i = 0; i < g_ledcnt; i++) {device_file_destroy(i);}/* 重置全局变量以清理状态 */g_ledcnt = 0;printk("%s: Platform device removed.\n", __FUNCTION__);return 0;
}static const struct of_device_id swh_led_drv_table[] = {{ .compatible = "swh_leddrv" },{ },
};static struct platform_driver chip_demo_gpio_driver = {.probe = chip_demo_gpio_probe,.remove = chip_demo_gpio_remove,.driver = {.name = "gpio_led_suwenhao",.of_match_table = swh_led_drv_table,},
};static int __init chip_demo_gpio_drv_init(void)
{int err;err = platform_driver_register(&chip_demo_gpio_driver); register_led_operations(&board_demo_led_opr);return 0;
}static void __exit lchip_demo_gpio_drv_exit(void)
{platform_driver_unregister(&chip_demo_gpio_driver);
}module_init(chip_demo_gpio_drv_init);
module_exit(lchip_demo_gpio_drv_exit);MODULE_LICENSE("GPL");
Linux系统的驱动注册部分(leddrv.c)
#include <linux/module.h>#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>#include "led_opr.h"/* 1. 确定主设备号 */
static int major = 0;
static struct class *led_class;
struct led_operations *p_led_opr;#define MIN(a, b) (a < b ? a : b)void device_file_create(int minor)
{device_create(led_class, NULL, MKDEV(major, minor), NULL, "swh_led%d", minor); /* /dev/swh_led0,1,... */
}
void device_file_destroy(int minor)
{device_destroy(led_class, MKDEV(major, minor));
}
void register_led_operations(struct led_operations *opr)
{p_led_opr = opr;
}EXPORT_SYMBOL(device_file_create);
EXPORT_SYMBOL(device_file_destroy);
EXPORT_SYMBOL(register_led_operations);/* 3. 实现对应的open/read/write等函数,填入file_operations结构体 */
static ssize_t led_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}/* write(fd, &val, 1); */
static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{int err;char status;struct inode *inode = file_inode(file);int minor = iminor(inode);printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);//让位于内核空间的变量status接收来自用户空间传来的值,status的值为0或1err = copy_from_user(&status, buf, 1); /* 根据次设备号和status控制LED */p_led_opr->ctl(minor, status);return 1;
}static int led_drv_open (struct inode *node, struct file *file)
{int minor = iminor(node);printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);/* 根据次设备号初始化LED */p_led_opr->init(minor);return 0;
}static int led_drv_close (struct inode *node, struct file *file)
{printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}/* 2. 定义自己的file_operations结构体 */
static struct file_operations led_drv = {.owner = THIS_MODULE,.open = led_drv_open,.read = led_drv_read,.write = led_drv_write,.release = led_drv_close,
};/* 4. 把file_operations结构体告诉内核:注册驱动程序 */
/* 5. 谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数 */
static int __init led_init(void)
{int err;printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);major = register_chrdev(0, "swh_led_driver", &led_drv); led_class = class_create(THIS_MODULE, "swh_led_class");err = PTR_ERR(led_class);if (IS_ERR(led_class)) {printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);unregister_chrdev(major, "swh_led_driver");return -1;}return 0;
}/* 6. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数 */
static void __exit led_exit(void)
{printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);class_destroy(led_class);unregister_chrdev(major, "swh_led_driver");
}/* 7. 其他完善:提供设备信息,自动创建设备节点 */module_init(led_init);
module_exit(led_exit);MODULE_LICENSE("GPL");
设备树文件swh_led.dts
的分析
设备树文件就代替了博文 利用Linux的Platform总线设备驱动实现对多个LED的驱动 中的设备资源描述文件board_A_led.c
的作用。
其内容如下:
#define GROUP_PIN(g,p) ((g<<16) | (p))/ {swh_led@0 {compatible = "swh_leddrv";pin = <GROUP_PIN(3, 1)>;};swh_led@1 {compatible = "swh_leddrv";pin = <GROUP_PIN(5, 8)>;};};
这段代码是一段 Linux 设备树的内容,定义了一个假设的 LED 驱动器设备节点,主要涉及两个 LED 设备。以下是代码的逐行解析:
代码解析
宏定义部分
#define GROUP_PIN(g,p) ((g<<16) | (p))
GROUP_PIN
是一个宏,用于计算某个引脚的编号。- 参数:
g
表示引脚组编号(group)。p
表示组内的引脚编号(pin)。
- 逻辑:
(g << 16)
将组号左移 16 位,将其放在 32 位整数的高 16 位。| (p)
将组内引脚编号放在低 16 位。
- 结果:
- 宏返回一个 32 位的整数,高 16 位表示组号,低 16 位表示组内引脚号。
- 例如,
GROUP_PIN(3, 1)
的结果是(3 << 16) | 1 = 0x00030001
。
设备树定义部分
/ {swh_led@0 {compatible = "swh_leddrv";pin = <GROUP_PIN(3, 1)>;};swh_led@1 {compatible = "swh_leddrv";pin = <GROUP_PIN(5, 8)>;};
};
-
根节点
/
/
是设备树的根节点,表示整个设备树的顶级结构。
-
设备节点
swh_led@0
swh_led@0 {compatible = "swh_leddrv";pin = <GROUP_PIN(3, 1)>; };
swh_led@0
:- 表示第一个 LED 设备节点。
@0
通常是节点地址或标识符,在这里可能是硬件逻辑编号。
compatible = "swh_leddrv";
:- 定义设备兼容字符串,表示该设备节点适配
swh_leddrv
驱动。 - 驱动程序会根据这个字符串来匹配并加载对应的驱动。
- 定义设备兼容字符串,表示该设备节点适配
pin = <GROUP_PIN(3, 1)>;
:- 定义该设备所用引脚,通过
GROUP_PIN(3, 1)
宏计算出的值。 - 对应的值为
0x00030001
(组号 3,引脚 1)。
- 定义该设备所用引脚,通过
-
设备节点
swh_led@1
swh_led@1 {compatible = "swh_leddrv";pin = <GROUP_PIN(5, 8)>; };
- 类似
swh_led@0
,但表示另一个 LED 设备节点。 pin = <GROUP_PIN(5, 8)>;
:- 定义设备所用引脚,对应的值为
0x00050008
(组号 5,引脚 8)。
- 定义设备所用引脚,对应的值为
- 类似
小结
这段设备树代码定义了两个 LED 驱动设备节点,分别使用了以下硬件引脚:
swh_led@0
:使用引脚组 3 的引脚 1。swh_led@1
:使用引脚组 5 的引脚 8。
驱动程序的作用
- 驱动程序部分会通过解析
compatible
字符串(例如swh_leddrv
)找到这些设备。 - 然后解析
pin
属性,根据具体硬件配置(如 GPIO 控制器),初始化和控制对应引脚。
总线驱动部分代码的修改(chip_demo_gpio.c)
对结构体platform_driver
实例的修改
之前在博文 https://blog.csdn.net/wenhao_ir/article/details/145013236 中,
结构体struct platform_driver chip_demo_gpio_driver
是下面这样的:
static struct platform_driver chip_demo_gpio_driver = {.probe = chip_demo_gpio_probe,.remove = chip_demo_gpio_remove,.driver = {.name = "gpio_led_suwenhao",},
};
现在修改成下面这样:
static const struct of_device_id swh_led_drv_table[] = {{ .compatible = "swh_leddrv" },{ },
};static struct platform_driver chip_demo_gpio_driver = {.probe = chip_demo_gpio_probe,.remove = chip_demo_gpio_remove,.driver = {.name = "gpio_led_suwenhao",.of_match_table = swh_led_drv_table,},
};
从这个修改中我们可以发现,我们为platform_driver
结构体实例chip_demo_gpio_driver
的成员driver
的成员of_match_table
添加名字为swh_led_drv_table
的结构体of_device_id
数组。
结构体of_device_id
数组swh_led_drv_table
中的每一个成员就代表一个设备具体的匹配信息,结构体of_device_id
的定义如下:
位置:include\linux\mod_devicetable.h/** Struct used for matching a device*/
struct of_device_id {char name[32];char type[32];char compatible[128];const void *data;
};
由于之前我们在设置树文件中都给每个设备设置了compatible
信息,如下:
,所以我们这里将swh_led_drv_table中的每一个成员的compatible填为
swh_leddrv
。
对probe
函数的修改
注:下面这段话提到匹配机制的详情请参考我的另一篇博文 https://blog.csdn.net/wenhao_ir/article/details/145023181
由于设备文件中有两个设备的compatible
都是swh_leddrv
,此时,驱动部分在匹配时,对每次匹配到的驱动结构体(platform_driver)【设备文件描述的设备资源其实都会被转化为platform_driver结构体进行描述】,都会调用一次probe
函数(即函数chip_demo_gpio_probe
),在这里,相当于有两个platform_driver结构体去进行匹配驱动结构体(platform_driver),而不是像上一篇博文中(https://blog.csdn.net/wenhao_ir/article/details/145013236)只有一个platform_driver结构体,这里就是最大的区别之一,所以我们要对probe
函数的处理机制进行修改,这里的probe
函数便不需要去循环遍历platform_driver结构体里面的所有资源了,因为在这里一个platform_driver结构体就相当于只对应了一个设备。
修改前的代码如下:
static int chip_demo_gpio_probe(struct platform_device *pdev)
{struct resource *res;int i = 0;while (1){res = platform_get_resource(pdev, IORESOURCE_IRQ, i);if (!res)break;g_ledpins[g_ledcnt] = res->start;device_file_create(g_ledcnt);g_ledcnt++;i++;}return 0;}
修改后的代码如下:
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;err = of_property_read_u32(np, "pin", &led_pin);g_ledpins[g_ledcnt] = led_pin;device_file_create(g_ledcnt);g_ledcnt++;return 0;}
这个逻辑也很简单,就是从platform_device
结构体中取出其成员dev
中的成员of_node
,然后利用内核函数of_property_read_u32
去取出设备树文件中自定义的pin信息。获得设置的编号信息后存储到数组g_ledpins
中,然后调用位于leddrv.c
中的函数device_file_create
去创建对应的设备文件。
of_node
是结构体struct device_node
的一个指针,结构体struct device_node
用于存储设备树文件中每一个设备节点的信息,其定义如下:
位置:include\linux\of.hstruct 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
};
具体各字段的含义和用法这里暂不展开,以后要进阶使用的时候再去研究。
对remove
函数的修改
remove
函数在一定程序上是对probe
函数的反向操作,probe
函数中创建相应的设备文件,remove
函数中就要对创建的设备文件进行销毁操作,所以我们也需要修改remove
函数,在这里就是实际上就是函数chip_demo_gpio_remove()
当在模块退出函数中调用函数platform_driver_unregister()
时,就会调用结构体platform_driver
中的remove
函数。
原来的函数chip_demo_gpio_remove()
的代码如下:
static int chip_demo_gpio_remove(struct platform_device *pdev)
{struct resource *res;int i = 0;while (1){res = platform_get_resource(pdev, IORESOURCE_IRQ, i);if (!res)break;device_file_destroy(i);i++;}return 0;
}
修改后的如下:
static int chip_demo_gpio_remove(struct platform_device *pdev)
{int i;/* 销毁所有已创建的设备文件 */for (i = 0; i < g_ledcnt; i++) {device_file_destroy(i);}/* 重置全局变量以清理状态 */g_ledcnt = 0;printk("%s: Platform device removed.\n", __FUNCTION__);return 0;
}
这个代码很简单,官方教程提供的这段代码冗余又啰嗦,上面是我进行了精简的代码…
编译源码
修改内核源码中的设备树dts文件并进行重新编译
先把之前的设备树dts文件和编译生成的dtb文件进行备份
之前已经在博文 https://blog.csdn.net/wenhao_ir/article/details/144421577 中把dtb文件编译生成了:
位置:/arm/boot/dts/100ask_imx6ull-14x14.dtb
绝对路径:/home/book/100ask_imx6ull-sdk/Linux-4.9.88/arch/arm/boot/dts
由于文件100ask_imx6ull-14x14.dtb
已经存放于/home/book/nfs_rootfs/已使用/001-内核和设备树文件
中了,如下图所示:
所以这里只需要把设备树文件源码100ask_imx6ull-14x14.dts
进行备份即可。
备份好设备树文件源码100ask_imx6ull-14x14.dts
之后,就开始修改它,即把下面的内容:
#define GROUP_PIN(g,p) ((g<<16) | (p))/ {swh_led@0 {compatible = "swh_leddrv";pin = <GROUP_PIN(3, 1)>;};swh_led@1 {compatible = "swh_leddrv";pin = <GROUP_PIN(5, 8)>;};};
加进去…
修改后之后,把之前的dtb文件删除掉…
之前的dtb文件删除掉后,重新make dtbs
cd /home/book/100ask_imx6ull-sdk/Linux-4.9.88
make dtbs
下图中的100ask_imx6ull-14x14.dtb
就是重新生成的dtb文件。
复制到网络文件系统的文件中备用。
编译咱们写的Platform总线设备驱动程序
先修改Makefile文件,因为不再需要board_A_led.c
,所以得去掉对这个文件的编译。
修改后的Makefile文件如下:
# 使用不同的Linux内核时, 一定要修改KERN_DIR,KERN_DIR代表已经配置、编译好的Linux源码的根目录KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88all:make -C $(KERN_DIR) M=`pwd` modules $(CROSS_COMPILE)gcc -o ledtest ledtest.c clean:make -C $(KERN_DIR) M=`pwd` cleanrm -rf modules.orderrm -f ledtestobj-m += leddrv.o chip_demo_gpio.o
工程复制到Ubuntu中:
然后make
make
把生成的模块文件和可执行文件一并复制到NFS文件中:
更新系统中的设备树文件(dtb文件)
打开串口终端→打开开发板→挂载网络文件系统
mount -t nfs -o nolock,vers=3 192.168.5.11:/home/book/nfs_rootfs /mnt
cd /boot
可以用ls查看下现有的设备树文件:
把刚才编译出的设备树文件复制进去:
cp /mnt/device_tree_and_Platform_bus/100ask_imx6ull-14x14.dtb /boot/
然后reboot重启系统就行了:
reboot
重启系统后在系统中查看设备树节点的属性
系统重启后用下面的命令查看是否有相应的设备树节点对应的目录是否存在:
ls /sys/firmware/devicetree/base/
从截图中可以看出, 两个设备节点对应在系统中的文件已经有了。
还可以进入相应的目录查看下设备节点的属性。
cd /sys/firmware/devicetree/base/swh_led@0
ls
可以对着咱们的设备文件源码来理解三个属性文件:
从上图中可以看出,name
这个文件的名字是系统为我们自动加上的,我们在源码中定义设备文件时是不需加上的。
compatible
文件的具体内容查看命令如下:
cat compatible
pin
文件的具体内容查看命令如下【因为它是一个十六进制数,所以要用特殊的命令】:
hexdump pin
以上面这个结果的解释如下:
在 hexdump
命令的输出中,结果由两部分组成:
- 数据内容部分(如
0000000 0300 0100
):显示从设备树节点文件中读取的实际内容。 - 偏移地址部分(如
0000004
):表示读取到的最后一个字节的文件偏移地址。
让我们具体分析:
数据内容部分
0000000 0300 0100
0000000
: 文件的起始偏移地址,表示数据从文件的第 0 字节开始。0300 0100
: 文件中实际存储的数据内容。解释如下:03 00
: 高 16 位(组号3
)。01 00
: 低 16 位(引脚号1
)。- 数据以小端字节序存储,因此高位和低位的字节顺序颠倒了。
偏移地址部分
0000004
- 这个数字表示 文件中数据读取到的最后一个字节的偏移量,以十六进制表示。
- 在你的情况下:
- 设备树中存储的
pin
是一个 32 位整数,占用 4 个字节。 - 因此偏移量是
0x4
,表示读取到文件的第 4 个字节位置(实际数据范围为0x0
到0x3
)。
- 设备树中存储的
小结
- 数据内容部分显示的是
pin
属性的值(即GROUP_PIN(3, 1)
的 32 位表示)。 - 偏移地址部分仅是
hexdump
的输出格式,显示文件读取的结束位置,用于帮助定位文件中的数据,不是数据内容本身的一部分。
查看设备树节点生成的platform_device
结构体的属性文件
cd /sys/bus/platform/devices/
ls
可见是有对应的platform_device
结构体的记录文件目录了。
然后可以进入目录查看对应的文件属性:
cd swh_led@0
ls
这些文件就记录着结构体platform_device
的方方面面的信息。
如果某个结构体platform_device
已经匹配到某个驱动程序结构体platform_driver
了,那上面这个目录下还会有一个名叫driver
的目录的软链接,比如下面这图所示:
加载驱动程序模块并查看系统中相应的platform_driver
的结构体记录文件、以及生成的设备文件
加载驱动程序模块
先挂载网络文件系统:
mount -t nfs -o nolock,vers=3 192.168.5.11:/home/book/nfs_rootfs /mnt
先加载模块leddrv.ko
insmod /mnt/device_tree_and_Platform_bus/leddrv.ko
再加载模块chip_demo_gpio.ko
insmod /mnt/device_tree_and_Platform_bus/chip_demo_gpio.ko
查看系统中相应的platform_driver
的结构体记录文件
加载成功后,我们查看目录/sys/bus/platform/devices/wh_led@0
下的文件和目录:
cd /sys/bus/platform/devices/swh_led@0
下面应该有由于结构体platform_device
和结构体platform_driver
匹配到之后产生的driver目录的软链接,如下图所示:
我们可以查看下这个软链接的指向:
ls -l driver
我们用下面的命令进入上面列出的路径,看下绝对路径:
cd ../../../bus/platform/drivers/gpio_led_suwenhao
pwd
结果如下:
/sys/bus/platform/drivers/gpio_led_suwenhao
可见,其实系统中存放platform_driver
结构体的记录文件的路径是:
/sys/bus/platform/drivers/
为什么这个目录名叫做gpio_led_suwenhao
,这是因为下面的代码:
我们可以查看下这个目录下的文件:
可见它绑定着两个platform_device
结构体,名字分别为swh_led@0
和 swh_led@1
。
看下相应的设备文件生成没有
ls /dev/
可见,生成了相应的设备文件,名字来自于:
运行测试程序
运行前先把设置下printfk的日志级别配置,以便能打印出相关信息:
详情见 https://blog.csdn.net/wenhao_ir/article/details/145006286
echo "7 4 1 7" > /proc/sys/kernel/printk
然后运行测试命令:
cd /mnt/device_tree_and_Platform_bus/
./ledtest /dev/swh_led0 on
可见是按我们的设想打印了相关的信息。
再运行测试命令:
./ledtest /dev/swh_led0 off
也是没问题的,我们再试下打开另一个设备文件/dev/swh_led1
./ledtest /dev/swh_led1 on
因为测试程序里只有写设备的分支,没有读设备的分支,所以这里就不测试读部分了。
卸载模块
用命令lsmod
查看下依赖关系,结果如下:
所以先卸载chip_demo_gpio
,再卸载leddrv
。
所以三条卸载命令如下:
rmmod chip_demo_gpio
rmmod leddrv
先运行:
rmmod chip_demo_gpio
这个模块的退出函数会调用结构体chip_demo_gpio_driver
的成员函数chip_demo_gpio_remove
,在函数数chip_demo_gpio_remove
里会注销由这个驱动结构体platform_driver创建的设备文件,前面也有函数chip_demo_gpio_remove
的源码,这个函数是由我自己写的,官方教程提供的太啰嗦了。
所以,接下来,我们要看下相应的设备文件还有没有:
ls /dev/
可见,已经没有了…
再看下相应的驱动结构体目录还有没有
ls /sys/bus/platform/drivers/
可见,名称为gpio_led_suwenhao
的目录也没有了…
继续卸载另一个模块:
rmmod leddrv
没问题~测试完毕!
附完整工程文件和内核源码中的设备树dts文件
完整工程文件:
https://pan.baidu.com/s/1oi7fvmLwqdfoSPMBfBf6NQ?pwd=wte2
内核源码中添加了自己设备节点的设备树dts文件:
https://pan.baidu.com/s/1vclrJSR04A7qjd8qZNLMig?pwd=a8tj
相关文章:
修改之前的代码使得利用设备树文件和Platform总线设备驱动实现对多个LED的驱动【只是假想对LED进行驱动,并没有实际的硬件操作】
引言 在下面这篇博文中: 利用Linux的Platform总线设备驱动实现对多个LED的驱动【只是假想对LED进行驱动,并没有实际的硬件操作】 我们利用Platform总线设备驱动的思想实现了对多个LED的驱动。 Platform总线设备驱动以及其它的总线设备驱动都将驱动分成了三个部分…...
从CentOS到龙蜥:企业级Linux迁移实践记录(龙蜥开局)
引言: 在我们之前的文章中,我们详细探讨了从CentOS迁移到龙蜥操作系统的基本过程和考虑因素。今天,我们将继续这个系列,重点关注龙蜥系统的实际应用——特别是常用软件的安装和配置。 龙蜥操作系统(OpenAnolis&#…...
多云架构,JuiceFS 如何实现一致性与低延迟的数据分发
随着大模型的普及,GPU 算力成为稀缺资源,单一数据中心或云区域的 GPU 资源常常难以满足用户的全面需求。同时,跨地域团队的协作需求也推动了企业在不同云平台之间调度数据和计算任务。多云架构正逐渐成为一种趋势,然而该架构下的数…...
Jenkins持续集成与交付安装配置
Jenkins 是一款开源的持续集成(CI)和持续交付(CD)工具,它主要用于自动化软件的构建、测试和部署流程。为项目持续集成与交付功能强大的应用。下面我们来介绍下它的安装与配置。 环境准备 更新系统组件(这…...
十大排序简介
十大排序简介 一、排序分类二、排序思路1.冒泡排序(Bubble Sort)2.选择排序(Selection Sort)3.插入排序(Insertion Sort)4.希尔排序(Shell Sort&a…...
uniapp小程序中隐藏顶部导航栏和指定某页面去掉顶部导航栏小程序
uniappvue3开发小程序过程中隐藏顶部导航栏和指定某页面去掉顶部导航栏方法 在page.json中 "globalStyle": {"navigationStyle":"custom",}, 如果是指定某个页面关闭顶部导航栏,在style中添加"navigationStyle": "cus…...
echarts:dataZoom属性横向滚动条拖拽不生效
问: 拖拽的过程中,第一次向右拖拽正常,然后就报错: echarts报错: var pointerOption pointerShapeBuilder[axisPointerType](axis,pixeValue,otherExtent),(axis,pixeValue,otherExtent)下划线红色报错:…...
【Leetcode 热题 100】739. 每日温度
问题背景 给定一个整数数组 t e m p e r a t u r e s temperatures temperatures,表示每天的温度,返回一个数组 a n s w e r answer answer,其中 a n s w e r [ i ] answer[i] answer[i] 是指对于第 i i i 天,下一个更高温度…...
R数据分析:多分类问题预测模型的ROC做法及解释
有同学做了个多分类的预测模型,结局有三个类别,做的模型包括多分类逻辑回归、随机森林和决策树,多分类逻辑回归是用ROC曲线并报告AUC作为模型评估的,后面两种模型报告了混淆矩阵,审稿人就提出要统一模型评估指标。那么肯定是统一成ROC了,刚好借这个机会给大家讲讲ROC在多…...
如何用 SSH 访问 QNX 虚拟机
QNX 虚拟机默认是开启 SSH 服务的,如果要用 SSH 访问 QNX 虚拟机,就需要知道虚拟机的 IP 地址,用户和密码。本文我们来看看如何获取这些参数。 1. 启动虚拟机 启动过程很慢,请耐心等待。 2. 查看 IP 地址 等待 IDE 连接到虚拟机。…...
交响曲-24-3-单细胞CNV分析及聚类
CNV概述 小于1kb是常见的插入、移位、缺失等的变异 人体内包含<10% 的正常CNV,我们的染色体数是两倍体,正常情况下,只有一条染色体表达,另一条沉默,当表达的那条染色体发生CNV之后,表达数量就会成倍增加…...
java远程调试debug
文章目录 首先被调试的服务配置idea 中配置远程调试连接上被调试服务打断点开始调试 首先被调试的服务配置 被调试的 java 服务需要开启允许被远程调试的配置,具体就是启动脚本中,加上允许被远程调试以及相应端口 # 针对JDK15.-1.8 -agentlib:jdwptran…...
操作系统之系统调用
系统调用 从上文简介得知,操作系统是计算机硬件和软件之间的桥梁,通过管理计算机软件和硬件资源,最终为我们用户提供服务。就如同一个管家帮助我们对CPU(进程)的管理、内存的管理、设备的管理、文件的管理。而我们如何…...
【docker】exec /entrypoint.sh: no such file or directory
dockerfile生成的image 报错内容: exec /entrypoint.sh: no such file or directory查看文件正常在此路径,但是就是报错没找到。 可能是因为sh文件的换行符使用了win的。...
CAPL概述与环境搭建
CAPL概述与环境搭建 目录 CAPL概述与环境搭建1. CAPL简介与应用领域1.1 CAPL简介1.2 CAPL的应用领域 2. CANoe/CANalyzer 安装与配置2.1 CANoe/CANalyzer 简介2.2 安装CANoe/CANalyzer2.2.1 系统要求2.2.2 安装步骤 2.3 配置CANoe/CANalyzer2.3.1 配置CAN通道2.3.2 配置CAPL节点…...
ML-Agents:智能体(三)
注:本文章为官方文档翻译,如有侵权行为请联系作者删除 Agent - Unity ML-Agents Toolkit–原文链接> ML-Agents:智能体(一) ML-Agents:智能体(二) ML-Agents:智能体&a…...
【harbor】离线安装2.9.0-arm64架构服务制作和升级部署
执行: .prepare 【作用就是产生一些配置信息 和docker-compose.yaml文件,然后docker-compose发布docker】 harbor官网地址:Harbor 参考文档可以看这里:部署 harbor 2.10.1 arm64 - 简书。 前提环境准备: 安装docker 和 docker…...
可视化-Visualization
可视化-Visualization 1.Introduction Visualization in Open CASCADE Technology is based on the separation of: on the one hand – the data which stores the geometry and topology of the entities you want to display and select, andon the other hand – its pr…...
完整化安装kubesphere,ks-jenkins的状态一直为init
错误描述: 打印日志: kubectl describe pod ks-jenkins-7fcff7857b-gh4g5 -n kubesphere-devops-system 日志描述如下: Events: Type Reason Age From Message ---- ------ ---- …...
halcon三维点云数据处理(十)locate_cylinder_3d
目录 一、locate_cylinder_3d例程代码二、gen_binocular_rectification_map函数三、binocular_disparity函数四、自定义函数select_best_candidates五、自定义函数remove_shadowed_regions 一、locate_cylinder_3d例程代码 1、读取或者创建3D形状模型, 2、根据双目…...
【CSS】设置滚动条样式
文章目录 基本语法用法案例 基本语法 在CSS中,可以使用 ::-webkit-scrollbar 和相关伪元素来为滚动条设置样式,但请注意这些伪元素是非标准的,主要用于WebKit内核浏览器(如Chrome、Safari)。 ::-webkit-scrollbar CSS …...
一个运行在浏览器中的开源Web操作系统Puter本地部署与远程访问
文章目录 前言1.关于Puter2.本地部署Puter3.Puter简单使用4. 安装内网穿透5.配置puter公网地址6. 配置固定公网地址 💡 推荐 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。【点击跳转到网站…...
支持selenium的chrome driver更新到131.0.6778.264
最近chrome释放新版本:131.0.6778.264 如果运行selenium自动化测试出现以下问题,是需要升级chromedriver才可以解决的。 selenium.common.exceptions.SessionNotCreatedException: Message: session not created: This version of ChromeDriver only s…...
conda+jupyter+pycharm:如何在Windows conda环境下运行jupyter并使用浏览器或者pycharm运行.ipynb
1 安装conda 2 conda环境下安装jupyter pip install jupyter3 设置jupyter配置文件 1)创建 jupyter_notebook_config.py文件 jupyter notebook --generate-config 2)设置密码 3)设置参数 直接将以下参数修改为自己的配置后复制到配置文件…...
生成式数据增强在大语言模型中的应用与实践
引言 近年来,大语言模型(Large Language Models, LLMs)如GPT、BERT等在自然语言处理(NLP)领域取得了巨大突破。然而,这些模型的性能往往依赖于大量高质量的训练数据,而在许多实际应用场景中&am…...
深度学习笔记11-优化器对比实验(Tensorflow)
🍨 本文为🔗365天深度学习训练营中的学习记录博客🍖 原作者:K同学啊 目录 一、导入数据并检查 二、配置数据集 三、数据可视化 四、构建模型 五、训练模型 六、模型对比评估 七、总结 一、导入数据并检查 import pathlib,…...
汽车免拆诊断 | 2007款保时捷Carrera S车行驶中发动机冷却液温度报警灯异常点亮
故障现象 一辆2007款保时捷Carrera S车,搭载3.8 L自然吸气发动机,累计行驶里程约为7.8万km。车主反映,车辆行驶一段距离后,组合仪表上的发动机冷却液温度报警灯异常点亮。为此,在其他维修厂已更换过节温器、发动机冷却…...
【蓝牙】win11 笔记本电脑连接 hc-06
文章目录 前言步骤 前言 使用电脑通过蓝牙添加串口 步骤 设置 -> 蓝牙和其他设备 点击 显示更多设备 更多蓝牙设置 COM 端口 -> 添加 有可能出现卡顿,等待一会 传出 -> 浏览 点击添加 hc-06,如果没有则点击 再次搜索 确定 添加成…...
what?ngify 比 axios 更好用,更强大?
文章目录 前言一、什么是ngify?二、npm安装三、发起请求3.1 获取 JSON 数据3.2 获取其他类型的数据3.3 改变服务器状态3.4 设置 URL 参数3.5 设置请求标头3.6 与服务器响应事件交互3.7 接收原始进度事件3.8 处理请求失败3.9 Http Observables 四、更换 HTTP 请求实现…...
EFCore HasDefaultValueSql (续2 HasComputedColumnSql)
前情:EFCore HasDefaultValueSql EFCore HasDefaultValueSql (续1 ValueGeneratedOnAdd)-CSDN博客 小伙伴在使用 HasDefaultValueSql 时,对相关的 ValueGeneratedOnAdd, HasComputedColumnSql 也有了疑问: HasComputedColumnSql 对于计算…...
springboot整合h2
在 Spring Boot 中整合 H2 数据库非常简单。H2 是一个轻量级的嵌入式数据库,非常适合开发和测试环境。以下是整合 H2 数据库的步骤: 1. 添加依赖 首先,在你的 pom.xml 文件中添加 H2 数据库的依赖: <dependency><grou…...
Unity自带的真车模拟系统,速度不够大r时如何以匀速上桥
在 Unity 中,如果你使用自带的真车模拟系统(如 Wheel Collider)时,发现车辆上桥时速度不够,导致无法顺利上坡,可以通过以下方法调整车辆的行为,使其能够以匀速上桥: 1. 调整 Wheel C…...
HarmonyOS鸿蒙-@State@Prop装饰器限制条件
一、组件Components级别的状态管理: State组件内状态限制条件 1.State装饰的变量必须初始化,否则编译期会报错。 // 错误写法,编译报错 State count: number;// 正确写法 State count: number 10; 2.嵌套属性的赋值观察不到。 // 嵌套的…...
C# 中的 Task 和 Async/Await
理解 C# 中的 Task 和 Async/Await:提升程序性能的利器 前言:在现代应用程序开发中,特别是在设计用户界面(UI)和进行网络请求等 I/O 操作时,异步编程变得尤为重要。C# 提供了一套强大的异步编程模型&#…...
vue.js 基于VueCli自定义创建项目
在使用Vue.js进行项目开发时,我们可以使用Vue CLI来快速创建项目。Vue CLI是一个基于Vue.js的命令行工具,它提供了一套完整的项目脚手架,可以帮助开发者快速搭建Vue.js项目的开发环境。 下面我们来详细解析如何使用Vue CLI自定义创建项目&am…...
Java中的反射机制及其应用场景
目录 什么是Java反射机制? 工作原理 主要应用场景 注意事项 总结 什么是Java反射机制? Java反射机制是一种强大的工具,它允许程序在运行时访问、检查和修改其本身的类和对象的信息。通过反射,开发者可以在不知道类的具体实现…...
金融项目实战 03|JMeter脚本实现手工接口测试
目录 一、环境说明 1、项目环境搭建 2、Mock说明 二、构造测试数据 1、通过系统页面构造 2、通过接口构造 3、通过数据库构造【推荐】 4、案例:构造借款业务数据 三、JMeter执行接口测试用例 1、获取图片验证码、获取短信验证码 2、注册脚本 3、登录脚本…...
前端工具汇总
1. vscode 下载地址:https://code.visualstudio.com/ vscode扩展汇总: 1.1 Code Spell Checker(必须安装) 代码拼写检查器 1.2 Auto Close Tag 自动添加HTML/XML的关闭标签 3. Auto Import 自动查找、解析并为所有可用导入…...
【学习路线】Python数据分析(数据科学) 详细知识点学习路径(附学习资源)
学习本路线内容之前,请先学习Python的基础知识 其他路线: Python基础 >> Python进阶 >> Python爬虫 >> Python数据分析(数据科学) >> Python 算法(人工智能) >> Pyth…...
Flutter 实现验证码输入框学习
学习flutter代码 实现一个用于输入验证码的自定义组件,它允许用户输入固定长度的验证码,并在输入完成时触发回调。 前置知识点学习 TextStyle TextStyle 是 Flutter 中用于定义文本样式的类。它提供了一组属性来控制文本的外观,如字体大小、…...
hutool糊涂工具通过注解设置excel宽度
import java.lang.annotation.*;Documented Retention(RetentionPolicy.RUNTIME) Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER}) public interface ExcelStyle {int width() default 0; }/*** 聊天记录*/ Data public class DialogContentInfo {/**…...
汽车基础软件AutoSAR自学攻略(四)-AutoSAR CP分层架构(3) (万字长文-配21张彩图)
汽车基础软件AutoSAR自学攻略(四)-AutoSAR CP分层架构(3) (万字长文-配21张彩图) 前面的两篇博文简述了AutoSAR CP分层架构的概念,下面我们来具体到每一层的具体内容进行讲解,每一层的每一个功能块力求用一个总览图,外加一个例子的图给大家进…...
有收到腾讯委托律师事务所向AppStore投诉带有【水印相机】主标题名称App的开发者吗
近期,有多名开发者反馈,收到来自腾讯科技 (深圳) 有限公司委托北京的一家**诚律师事务所卞,写给AppStore的投诉邮件。 邮件内容主要说的是,腾讯注册了【水印相机】这四个字的商标,所以你们这些在AppStore上的app&…...
SpringBoot操作spark处理hdfs文件
SpringBoot操作spark处理hdfs文件 1、导入依赖 <!-- spark依赖--><dependency><groupId>org.apache.spark</groupId><artifactId>spark-core_2.12</artifactId><version>3.2.2</version></dependency><depend…...
Spring Boot中的依赖注入是如何工作
Spring Boot 中的依赖注入(Dependency Injection,简称 DI)是通过 Spring 框架的核心机制——控制反转(Inversion of Control,IOC)容器来实现的。Spring Boot 基于 Spring Framework,在应用中自动…...
算法面试1
简述yolov1的网络架构 YOLOv1网络结构包括24层卷积层用来提取图像的特征,2层全连接层回归得到7730(141420)的张量。 网络结构大概如下:输入的是4484483通道的图像,就是RGB图像,然后用64个卷积核大小是…...
Android车机DIY开发之软件篇(八)单独编译
Android车机DIY开发之软件篇(八)单独编译 1.CarLauncher单独编译 CarLauncher源码位于 packages/apps/Car/Launcher 用Eclipse ADT 谷歌定制版编译而成,.mk .bp编译 Android13目录如下: alientekalientek:~/packages/apps/Car$ ls Calendar …...
保证Mysql数据库到ES的数据一致性的解决方案
文章目录 1.业务场景介绍1.1 需求分析1.2 技术实现方案 2.业界常用数据一致性方案分析2.1 同步双写方案2.2 MQ异步双写方案2.3 扫表定期同步方案2.4 监听binlog同步方案 1.业务场景介绍 1.1 需求分析 某知名的在线旅游平台,在即将到来的春季促销活动之前ÿ…...
Cursor实现go项目配置并实现仓库Gin项目运行
✅作者简介:大家好,我是 Meteors., 向往着更加简洁高效的代码写法与编程方式,持续分享Java技术内容。 🍎个人主页:Meteors.的博客 💞当前专栏:知识备份 ✨特色专栏:知识分享 &#x…...
【网络云SRE运维开发】2025第2周-每日【2025/01/11】小测-【第11章NAT理论和实操考试】解析
文章目录 一、选择题二、理论题三、实操题 【网络云SRE运维开发】2025第2周-每日【2025/01/11】小测-【第11章NAT理论和实操考试】解析 一、选择题 在H3C设备上,NAT技术主要用于( ) A. 提高网络安全性 B. 实现不同网段的通信 C. 将内部私有IP…...