修改之前的代码使得利用设备树文件和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或1
	err = 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)>;
	};
};
  1. 根节点 /

    • / 是设备树的根节点,表示整个设备树的顶级结构。
  2. 设备节点 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)。
  3. 设备节点 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.youkuaiyun.com/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.youkuaiyun.com/wenhao_ir/article/details/145023181

由于设备文件中有两个设备的compatible都是swh_leddrv,此时,驱动部分在匹配时,对每次匹配到的驱动结构体(platform_driver)【设备文件描述的设备资源其实都会被转化为platform_driver结构体进行描述】,都会调用一次probe函数(即函数chip_demo_gpio_probe),在这里,相当于有两个platform_driver结构体去进行匹配驱动结构体(platform_driver),而不是像上一篇博文中(https://blog.youkuaiyun.com/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.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;	/* 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.youkuaiyun.com/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.88

all:
	make -C $(KERN_DIR) M=`pwd` modules 
	$(CROSS_COMPILE)gcc -o ledtest ledtest.c 

clean:
	make -C $(KERN_DIR) M=`pwd` clean
	rm -rf modules.order
	rm -f ledtest

obj-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 命令的输出中,结果由两部分组成:

  1. 数据内容部分(如 0000000 0300 0100):显示从设备树节点文件中读取的实际内容。
  2. 偏移地址部分(如 0000004):表示读取到的最后一个字节的文件偏移地址。

让我们具体分析:

数据内容部分

0000000 0300 0100
  • 0000000: 文件的起始偏移地址,表示数据从文件的第 0 字节开始。
  • 0300 0100: 文件中实际存储的数据内容。解释如下:
    • 03 00: 高 16 位(组号 3)。
    • 01 00: 低 16 位(引脚号 1)。
    • 数据以小端字节序存储,因此高位和低位的字节顺序颠倒了。

偏移地址部分

0000004
  • 这个数字表示 文件中数据读取到的最后一个字节的偏移量,以十六进制表示。
  • 在你的情况下:
    • 设备树中存储的 pin 是一个 32 位整数,占用 4 个字节。
    • 因此偏移量是 0x4,表示读取到文件的第 4 个字节位置(实际数据范围为 0x00x3)。

小结

  • 数据内容部分显示的是 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@0swh_led@1

看下相应的设备文件生成没有

ls /dev/

在这里插入图片描述
可见,生成了相应的设备文件,名字来自于:
在这里插入图片描述

运行测试程序

运行前先把设置下printfk的日志级别配置,以便能打印出相关信息:
详情见 https://blog.youkuaiyun.com/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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

昊虹AI笔记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值