1.5设备树的引进与体验——使用设备树时对应的驱动编程

本节简单介绍怎么使用设备树,并别写对应的驱动程序,后面将会再深入学习设备树。

上节使用总线设备模型,它与设备树的不同在于平台设备的构建

使用总线设备模型,驱动程序被分为两部分:

  1. dev:主要是分配/设置/注册platform_device;
  2. drv:主要是分配/设置/注册platform_driver;

dev和drv通过bus进行匹配,当bus检测到匹配的dev和drv,将会调用drv->probe函数,在probe函数中将会分配/设置/注册file_operations结构体。

使用设备树,驱动程序同样被分为两部分:

  1. dts文件:在dts文件中构造节点,节点中含有资源
  2. drv:主要是分配/设置/注册platform_driver;

其中drv->probe函数,分配/设置/注册file_operations结构体。

在总线设备模型中,platform_device在dev.c中,dev是.c文件,每次修改需要重新编译。

设备树中,dts文件被编译成dtb文件,然后传给内核,内核会来处理解析dtb文件,得到一个一个的device_node结构体,这个device_node结构体会转换成platform_device资源。然后这个platform_device就会和drv链表中的platform_driver进行匹配,匹配成功就会调用对应的drv->probe函数来分配/设置/注册file_operations结构体。

dts->dtb->device_node->platform_device

所以,总线设备驱动模型设备树区别在于,之前的platform_device资源在dev.c文件中配置,而使用设备树,platform_device资源则来自于dts文件

可以这么说,设备树针对总线驱动模型的一种改进,使用设备树可以更方便的修改platform_device。

下面是百问网提供的设备树文件。

// SPDX-License-Identifier: GPL-2.0
/*
 * SAMSUNG SMDK2440 board device tree source
 *
 * Copyright (c) 2018 weidongshan@qq.com
 * dtc -I dtb -O dts -o jz2440.dts jz2440.dtb
 */
 
#define S3C2410_GPA(_nr)	((0<<16) + (_nr))
#define S3C2410_GPB(_nr)	((1<<16) + (_nr))
#define S3C2410_GPC(_nr)	((2<<16) + (_nr))
#define S3C2410_GPD(_nr)	((3<<16) + (_nr))
#define S3C2410_GPE(_nr)	((4<<16) + (_nr))
#define S3C2410_GPF(_nr)	((5<<16) + (_nr))
#define S3C2410_GPG(_nr)	((6<<16) + (_nr))
#define S3C2410_GPH(_nr)	((7<<16) + (_nr))
#define S3C2410_GPJ(_nr)	((8<<16) + (_nr))
#define S3C2410_GPK(_nr)	((9<<16) + (_nr))
#define S3C2410_GPL(_nr)	((10<<16) + (_nr))
#define S3C2410_GPM(_nr)	((11<<16) + (_nr))

/dts-v1/;

/ {
	model = "SMDK24440";
	compatible = "samsung,smdk2440";

	#address-cells = <1>;
	#size-cells = <1>;
		
	memory@30000000 {
		device_type = "memory";
		reg =  <0x30000000 0x4000000>;
	};
/*
	cpus {
		cpu {
			compatible = "arm,arm926ej-s";
		};
	};
*/	
	chosen {
		bootargs = "noinitrd root=/dev/mtdblock4 rw init=/linuxrc console=ttySAC0,115200";
	};

	
	led {
		compatible = "jz2440_led";
		reg = <S3C2410_GPF(5) 1>;
	};
};

可以看到,它可以使用一些C语言的语法,使用 /* */ 和 // 来添加注释,使用#define来设置宏。

这个节点设置了内核的命令行参数

查看led节点,它有两个属性,一个compatible,后面就使用它在内核中找到能够支持这个节点的驱动程序,能支持这个节点的platform_driver;还有一个reg,本意是register,寄存器,在ARM系统中,寄存器和内存是同样对待的,因为寄存器的访问空间和内存的访问空间没什么差别。

在上节中,我们曾经使用一个platform_device,在resource中将flags设置成了mem,但实际上它并不是一个mem资源,我们也没有将它作为mem资源使用,而是将它转为一个led_pin来使用。

在这个设备树文件中,也是同样的做法,reg本来是寄存器的地址,但是在这个设备树文件中,将它设置为了某个引脚(S3C2410_GPF(5))。

在驱动程序中要将这个值/引脚读出来,将它作为一个引脚。

后面还有一个1,这是size,这次并没有使用但是也需要提供一个size(对应内存的起始地址和大小,只是我们把起始地址当做引脚,不需要大小)。

介绍完dts文件,将这个文件传入/home/book/code/linux-4.19-rc3/arch/arm/boot/dts目录下,然后重新编译设备树文件

编译之前需要先设置工具链

export PATH=/home/book/code/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabi/bin/:$PATH

然后在内核目录下执行make dtbs编译设备树文件

其中arch/arm/boot/dts/jz2440.dtb就是编译出来的dtb文件,将dtb文件拷贝到nfs挂载的路径下。

重启开发板,在uboot中执行命令,下载dtb文件,从虚拟机中下载dtb到0x32000000位置。

nfs 32000000 192.168.0.103:/work/nfs_root/003_led_device_tree/jz2440.dtb

 使用nfs挂载需要先连上虚拟机,ping虚拟机失败。

将ipaddr设为192.168.0.108,确保虚拟机和开发板处于同一个网段,再ping一次可以检测到虚拟机。

 再次下载,下载成功。

 然后将device_tree分区擦除,将刚刚下载的dtb文件载入。

 分区信息如下图所示,擦除的是第1个分区,device_tree分区。

 然后重启开发板,进入/sys/devices/platform目录,执行ls命令,可以看到有一个50005.led目录。

进入该目录,执行ls。

进入of_node(open firmware node)文件夹,可以看到里面有三个文件,compatible,name和reg。

 

正好对应dts文件中的name=led,compatible=jz2440_led,reg=<0050 00 0 1>。

 

也就是说,设备树文件中的节点node确实被转换成了platform_device,那么要怎么去写对应的platform_driver。

在之前的总线设备模型中说过,有一个平台总线,里面有一个match函数,用来匹配dev和drv,所以我们需要查看一下这个match函数,看看它是怎么去匹配平台设备和平台驱动的。

传统的方法是比较name,但是对于从设备树构造的平台设备时怎么匹配的呢?

这个问题需要看源码分析,使用设备树时,在match函数中是调用of_driver_match_device函数来匹配dev和drv的。

点击进入of_driver_match_device函数,可以发现该函数直接return了of_match_device函数。

其中drv指向了结构体成员of_match_table,该变量包含了name,type,compatible。

可以猜测一下,这个compatible就会和从dts中得到的compatible属性进行比较,一样的话,就匹配成功。

了解了这些就可以开始写代码了,具体的研究下一节再展开。

在002的基础上进行修改,去掉dev相关的代码。

修改makefile,屏蔽led_dev.o。

然后修改led_drv.c,在led_drv.c中有一个led_drv结构体。

struct platform_driver led_drv = {
    .probe      = led_probe,
    .remove     = led_remove,
    .driver     = {
        .name   = "myled",
    }
};

在led_drv.driver.name中添加.of_match_table = of_match_leds。

struct platform_driver led_drv = {
    .probe      = led_probe,
    .remove     = led_remove,
    .driver     = {
        .name   = "myled",
        .of_match_table = of_match_leds, /* 能支持哪些来自dts的platform_device */
    }
};

其中 of_match_leds 如下,.compatible = "jz2440_led"对应dts文件中led节点的compatible属性的值,data没有用上,设置为NULL。

static const struct of_device_id of_match_leds[] = {
	{ .compatible = "jz2440_led", .data = NULL },
	{ }
};

.compatible = "jz2440_led"的值设置得并不符合规范,正常应该为.compatible = "jz2440,led",表示MPU为jz2440,控制的外设为led,但是目前先这样写,匹配dts文件。

代码修改完成,编译实验一下。

首先,设置环境变量。

export PATH=/home/book/code/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabi/bin/:$PATH

然后编译将编译生成的led_drv.ko文件传到nfs挂载的路径下。

在开发板上insmod这个ko文件,然后lsmod可以看到,出现了一个led_drv模块,在dev目录下也出现了一个led设备。

此时执行测试程序,./ledtest on和off,可以看到led随着指令的控制亮灭。

如果要改变使用的引脚,只需要将dts文件中led节点的reg属性的值进行修改,然后重新编译下载即可,而不用编译任何.c文件。

问:在dts文件中,使用的是reg来指定led引脚,但是reg本意是寄存器,是否有更直接的表示方法?

答:有,可以将reg改为pin,在drv代码中获取这个pin值即可。

修改dts,重新编译下载到开发板上,然后还需要修改一下drv代码。

以前是获取IORESOURCE_MEM类型的资源,现在则是要获取名为pin的属性,把这个属性转换为led引脚。

为了兼容以前的程序,可以先获取IORESOURCE_MEM资源,获取不到的时候再获取pin属性。

问:怎么获取pin属性呢?

答:这些设备节点相关的函数,可以在of.h文件中查看。

使用of_property_read_s32函数就可以从dtb中获取某个指定的属性。

static inline int of_property_read_s32(const struct device_node *np,
				       const char *propname,
				       s32 *out_value)
{
	return of_property_read_u32(np, propname, (u32*) out_value);
}

 修改后的led_probe函数如下。

static int led_probe(struct platform_device *pdev)
{
    struct resource *res;

    /* 根据platform_device的资源进行ioremap */
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (res) {
        led_pin = res->start;
    } else {
        /* 获得pin属性 */
        of_property_read_s32(pdev->dev.of_node, "pin", &led_pin);
    }

    /* 如果都没有获取到led_pin值,返回错误 */
    if (!led_pin) {
        printk("can not get pin for led!\n");
        return -EINVAL;
    }

    major = register_chrdev(0, "myled", &myled_oprs);

    led_class = class_create(THIS_MODULE, "myled");
    device_create(led_class, NULL, MKDEV(major, 0), NULL, "led"); /* /dev/led */

    return 0;
}

修改编译后试验,可以看到,小灯同样可以根据输入的指令亮灭,说明成功使用pin属性设置了led_pin。

最后,在总线设备模型中,有一个platform_device结构体,当使用设备树时,platform_device结构体的dev成员,它有一个成员叫of_node,of_node中含有属性,含有的属性则取决于设备树,如compatible和pin属性,compatible属性会最先被用来匹配对应的drv程序

  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值