zoukankan      html  css  js  c++  java
  • Linux 设备树学习——基于i2c总线分析

    @

    博客说明

    撰写日期 2019.11.18
    完稿日期 2019.11.20
    最近维护 暂无
    本文作者 multimicro
    联系方式 multimicro@qq.com
    资料链接 本文无附件资料
    GitHub https://github.com/wifialan/drivers/tree/master/device_tree_i2c
    原文链接 https://blog.csdn.net/multimicro/article/details/103129546

    开发环境

    环境说明 详细信息 备注信息
    操作系统 Ubunut 18.04
    开发板 JZ2440-V3
    u-boot uboot-2012.04.01
    busybox busybox-1.22.1
    u-boot和busybox编译器 arm-linux-gcc (4.4.3)
    Linux内核 linux-4.19-rc3
    Linux内核编译器 arm-linux-gnueabi-gcc (4.9.4)

    1. 如何使用设备树

    设备树,顾名思义,是描述物理设备的文件,里面的节点包含特定设备下的硬件信息。使用设备树,需要做两方面工作:其一,开启u-boot支持设备树的功能;其二:使用支持设备树的内核版本。

    1.1 u-boot支持设备树

    • 在u-boot工程中,增添一行命令在板级配置文件中:
      #define CONFIG_OF_LIBFDT
      我用的是S3C2440,所以应该在include/configs/smdk2440.h中增添上述命令行,然后重新编译即可得到支持设备树的u-boot。

    • 在u-boot启动过程中,在把控制权交给内核前,会对内存进行分区,那么因此,也需要让u-boot启动时,为设备树分配一定的内存空间,具体操作是:
      继续修改include/configs/smdk2440.h文件,在里面这个位置增加图示代码:

    在这里插入图片描述
    重新编译,烧录,在u-boot命令行中输入mtdparts即可查看分区情况

    在这里插入图片描述
    这就在之前的u-boot基础上得到了一个支持设备树的u-boot,可以在对应的内存空间中烧写相应的文件。

    1.2 Linux内核支持设备树

    我使用的是韦东山提供的Linux内核以及补丁包,采用Linux-4.19.3-rc内核版本。

    打补丁:
    patch -p1<../linux-4.19-rc3_device_tree_for_jz2440.patch
    编内核:
    cp config_ok .config
    make ARCH=arm CROSS_COMPILE=/opt/FriendlyARM/toolschain/4.9.4/bin/arm-linux-gnueabi- menuconfig
    make ARCH=arm CROSS_COMPILE=/opt/FriendlyARM/toolschain/4.9.4/bin/arm-linux-gnueabi- uImage -j8
    make ARCH=arm CROSS_COMPILE=/opt/FriendlyARM/toolschain/4.9.4/bin/arm-linux-gnueabi- dtbs -j8

    执行完后,会在arch/arm/boot/文件夹下得到uImage,在arch/arm/boot/dts/文件夹下得到*.dtb

    1.3 如何在开发板中使用设备树

    为了减少学习时对开发板的FLASH擦写,这里一直使用tftp服务将内核和设备树文件的直接传递给内存中运行,而u-boot和文件系统busybox则固化在FLASH中,从Linux-3.4.2过渡过来的busybox可以直接使用,但需要提醒一下,之前的busybox是在第三分区,而增加设备树分区后的busybox被挪到了第四分区:
    在这里插入图片描述
    因此,在bootargs命令中,应该修改root挂载到/dev/mtdblock4分区下

    set bootargs console=ttySAC0,115200 root=/dev/mtdblock4 rw init=/linuxrc

    现在开始通过tftp服务加载内核和设备树,tftp服务器搭建参考:在Linux系统下通过TFTP或NFS烧写内核
    u-boot命令行中输入:

    tftp 31000000 uImage; tftp 32000000 jz2440_irq.dtb; bootm 31000000 - 32000000

    bootm 命令介绍,若ramdisk没有,则用"-"号替代
    bootm + uImage地址 + ramdisk地址 + 设备树镜像地址

    在这里插入图片描述
    可以看到,设备树已经成功被uImage加载。

    2. 设备树介绍

    • Linux uses DT data for three major purposes:
      1) platform identification,
      2) runtime configuration, and
      3) device population.

    • 我用设备树主要是方便其加载硬件设备信息,不需要在内核中增加比如spi_register_board_info这样的设备注册函数,通过重新编译内核来实现device的注册。现在在利用设备树时,若增加某个设备,那么只需要修改设备树中的相关信息,那么重新编译设备树让内核重新加载,即可便捷的注册某device。非设备树注册device可参考spi_device的注册过程: 2.2 spi_device注册

    • 在根文件系统中查看设备树(有助于调试)
      a. /sys/firmware/fdt // 原始dtb文件
      hexdump -C /sys/firmware/fdt//查看dtb文件内容
      b. /sys/firmware/devicetree // 以目录结构程现的dtb文件, 根节点对应base目录, 每一个节点对应一个目录, 每一个属性对应一个文件
      c. /sys/devices/platform // 系统中所有的platform_device, 有来自设备树的, 也有来有.c文件中注册的
       对于来自设备树的platform_device, 可以进入 /sys/devices/platform/<设备名>/of_node 查看它的设备树属性
      d. /proc/device-tree 是链接文件, 指向 /sys/firmware/devicetree/base

    2.1 设备树中的设备驱动节点

    • sys/devices/platform/
      sys/devices/platform/文件夹下面可以看到通过设备树注册进来的根设备(子设备都在根设备文件夹内的of_node文件夹下):

    在这里插入图片描述
    进入其中一个文件夹,如54000000.i2c可以看到一个文件夹of_node,里面包含了i2c节点下的子节点设备信息:

    在这里插入图片描述
    红框中of_node被链接到了/sys/firmware/devicetree/base/i2c@54000000/文件夹下(由此也验证,设备树中的所有信息都在/sys/firmware/devicetree/base文件夹里面),那么该文件夹中信息有那些呢?

    • sys/firmware/devicetree/base

    tree命令: 将tree源码交叉编译后的文件放到busybox下的/bin/文件夹下方可使用

    在这里插入图片描述
    设备树中,i2c节点下的设备子节点信息如下:

    在这里插入图片描述
    通过上述两张图,可以看出eeprom设备节点被成功加载到了内核中去,其依附于i2c设备。现在想想,树状关系是不是有点雏形了,一个根设备(i2c)下面伸出了一个枝干(eeprom)。

    • sys/bus/i2c/

    设备树注册进来的总线设备,可以在sys/bus/文件夹下找到,比如i2c、spi总线等

    sys/bus/i2c/目录下面可以看到i2c总线下面的devicedriver信息

    在这里插入图片描述
    若内核中注册了相应的i2c drivers,那么通过名字匹配成功后(具体的匹配方式下面会写),会调用相应的probe函数将driver和device连在一起

    在这里插入图片描述

    2.2 设备树匹配流程

    通过设备树注册进来的device,是如何匹配到对应的driver呢,本节仍然以i2c总线的匹配为例进行介绍。

    spi总线platform总线也是类似的,本节结尾我会贴出这两个总线的匹配程序供参考,会发现,这玩意儿,就是一通百通。

    2.2.1 以i2c匹配为例

    i2c中的匹配函数在如下文件内

    drivers/i2c/i2c-core-base.c : line 101

    static int i2c_device_match(struct device *dev, struct device_driver *drv)
    {
    	struct i2c_client	*client = i2c_verify_client(dev);
    	struct i2c_driver	*driver;
    
    	/* Attempt an OF style match */
    	if (i2c_of_match_device(drv->of_match_table, client))
    		return 1;
    
    	/* Then ACPI style match */
    	if (acpi_driver_match_device(dev, drv))
    		return 1;
    
    	driver = to_i2c_driver(drv);
    
    	/* Finally an I2C match */
    	if (i2c_match_id(driver->id_table, client))
    		return 1;
    
    	return 0;
    }
    

    从上述流程中可以得知,通过调用i2c_device_match函数实现i2c设备和驱动的匹配,匹配流程为:

    下面也是匹配的优先顺序

    • 1st: /* Attempt an OF style match */

      设备树中i2c子设备节点下的compatible属性值内核驱动程序中所定义的of_device_id结构体中的compatible属性值 是否完全相同,在linux-4.19-rc3内核版本中,采用of_compat_cmp函数(原型为strcasecmp函数,由drivers/of/base.c :line454: __of_device_is_compatible调用)来匹配两个值是否相同

    • 2nd: /* Then ACPI style match */

      设备树不涉及这种匹配方式,忽略

    • 3rd: /* Finally an I2C match */

      设备树不涉及这种匹配方式,但此种方式是在没有采用设备树时的匹配方式:platform_device结构体定义的.name 字段(至于怎么传入client先留个疑问)和 驱动程序中所定义的i2c_device_id结构体中的.name字段属性值是否完全相同,在linux-4.19-rc3内核版本中,采用i2c_match_id函数(drivers/i2c/i2c-core-base.c :line86)来匹配两个值是否相同


    下面举例介绍一下设备树中驱动的匹配

    • /* Attempt an OF style match */

    如下将at24_of_match定义为了一个of_device_id结构体,里面包含compatible属性,和设备树中子设备节点下的compatible属性值进行匹配。倘若设备树中子设备节点eeprom@50compatible = "atmel,24c256",那么就会匹配成功,调用probe函数

    driver端    内核程序

      static const struct of_device_id at24_of_match[] = {
    	{ .compatible = "atmel,24c00",		.data = &at24_data_24c00 },
    	{ .compatible = "atmel,24c01",		.data = &at24_data_24c01 },
    	{ .compatible = "atmel,24cs01",		.data = &at24_data_24cs01 },
     		...	//此处省略了一部分
    	{ .compatible = "atmel,24c256",		.data = &at24_data_24c256 },
    	{ .compatible = "atmel,24c512",		.data = &at24_data_24c512 },
    	{ .compatible = "atmel,24c1024",	.data = &at24_data_24c1024 },
    	{ /* END OF LIST */ },
    };
    

    device端    设备树程序

    在这里插入图片描述


    • /* Finally an I2C match */

      对比通过内核注册i2c device,匹配方式

    driver端    内核程序

    static const struct i2c_device_id at24_ids[] = {
    	{ "24c00",	(kernel_ulong_t)&at24_data_24c00 },
    	{ "24c01",	(kernel_ulong_t)&at24_data_24c01 },
    	{ "24cs01",	(kernel_ulong_t)&at24_data_24cs01 },
    		...	//此处省略了一部分
    	{ "24c256",	(kernel_ulong_t)&at24_data_24c256 },
    	{ "24c512",	(kernel_ulong_t)&at24_data_24c512 },
    	{ "24c1024",	(kernel_ulong_t)&at24_data_24c1024 },
    	{ "at24",	0 },
    	{ /* END OF LIST */ }
    };
    

    device端    内核程序

    static struct i2c_board_info __initdata da830_evm_i2c_devices[] = {
    	{
    		I2C_BOARD_INFO("24c256", 0x50),
    		.platform_data	= &da830_evm_i2c_eeprom_info,
    	},
    };
    

    • spi总线platform总线的匹配程序
    1. spi总线  drivers/spi/spi.c
    static int spi_match_device(struct device *dev, struct device_driver *drv)
    {
    	const struct spi_device	*spi = to_spi_device(dev);
    	const struct spi_driver	*sdrv = to_spi_driver(drv);
    
    /* Attempt an OF style match */
    if (of_driver_match_device(dev, drv))
    	return 1;
    
    /* Then try ACPI */
    if (acpi_driver_match_device(dev, drv))
    	return 1;
    
    if (sdrv->id_table)
    	return !!spi_match_id(sdrv->id_table, spi);
    
    return strcmp(spi->modalias, drv->name) == 0;
    
    }
    
    1. platform总线  drivers/base/platform.c
    static int platform_match(struct device *dev, struct device_driver *drv)
    {
    	struct platform_device *pdev = to_platform_device(dev);
    	struct platform_driver *pdrv = to_platform_driver(drv);
    
    
    	/* When driver_override is set, only bind to the matching driver */
    	if (pdev->driver_override)
    		return !strcmp(pdev->driver_override, drv->name);
    
    	/* Attempt an OF style match first */
    	if (of_driver_match_device(dev, drv))
    		return 1;
    
    	/* Then try ACPI style match */
    	if (acpi_driver_match_device(dev, drv))
    		return 1;
    
    	/* Then try to match against the id table */
    	if (pdrv->id_table)
    		return platform_match_id(pdrv->id_table, pdev) != NULL;
    
    	/* fall-back to driver name match */
    	return (strcmp(pdev->name, drv->name) == 0);
    }
    

    韦东山在设备树视频里面讲解的匹配流程就是基于platform总线的,而i2c总线spi总线的匹配优先级方式可以根据各自对应的源码分析。

    2.2.2 设备树匹配下的程序模板

    下面梳理一下i2c总线device注册driver注册以及匹配程序流程,spi总线platform总线流程也基本都是一个套路,学会一个,其他的就会很好理解了。

    • 设备树端

      代码在git上:jz2440_irq.dts

      在设备树dts文件中的i2c总线节点下面,创建一个新的子设备节点(eeprom@50),需要注意的是,通过设备树注册的i2c设备会转换为一个i2c client,不像spi设备会转换为一个spi device

    1. compatible 的值是用于和driver匹配的
    2. reg 的值是相应的寄存器
    3. status = "okay"是启用该设备

    dts文件:

    	&i2c {
        status = "okay";
        samsung,i2c-max-bus-freq = <200000>;
        eeprom@50 {
            compatible = "atmel,24c256";
            reg = <0x50>;
            pagesize = <32>;
            status = "okay";
        };  
    };
    

    下面是上述dts文件中所包含的dtsi文件里面的i2c设备节点信息,之所以可以用&i2c,就是因为在dtsi文件(见下)中将i2c@54000000这个i2c节点前记了一个标识符i2c,这种标识符的格式为:i2c: i2c@54000000采用此标识符下的属性信息可以覆盖原有的属性,并可以增加新的属性信息在其中

    dtsi文件

    i2c: i2c@54000000 {
            compatible = "samsung,s3c2440-i2c";
            clocks = <&clocks PCLK_I2C>;
            clock-names = "i2c";
            pinctrl-names = "default";
            pinctrl-0 = <&i2c0_bus>;
        }; 
    
    • 内核driver端
    static const struct of_device_id xxx_of_match[] = {
    	{ .compatible = "atmel,24c256",		.data = &xxx_data },
    	...		//还可以增加其他设备信息	.data字段暂时不知道作用,匹配是用不到这个属性了
    	{ /* END OF LIST */ },
    };
    MODULE_DEVICE_TABLE(of, xxx_of_match);
    
    static int xxx_probe(struct i2c_client * client, const struct i2c_device_id *id)
    {
    	...	//在这里可以将i2c设备注册为字符设备或者misc设备使用
    }
    
    static int xxx_remove(struct i2c_client *client)
    {
        ...
    }
    
    static struct i2c_driver xxx_driver = {
        .driver     = {
            .name   = "at24c256",
            .owner  = THIS_MODULE,
            .of_match_table = xxx_of_match,	//设备树注册i2c device匹配方式选用
        },
        .probe      = xxx_probe,
        .remove     = xxx_remove,
        .id_table   = xxx_ids,		//内核注册i2c device匹配方式选用
    };
    
    
    static int __init xxx_init(void)
    {
        return i2c_add_driver(&xxx_driver);
    }
    
    static void __exit xxx_exit(void)
    {
        i2c_del_driver(&xxx_driver);c
    }
    
    module_init(xxx_init);
    module_exit(xxx_exit);
    

    2.3 设备树中的设备节点转换

    文本内容较多,移步至github: device_tree_node_transfer

    i2c节点和spi节点是如何转换为相应的i2c clientspi device的呢?

    /i2c节点一般表示i2c控制器, 它会被转换为platform_device, 在内核中有对应的platform_driver;
    /i2c/at24c02节点不会被转换为platform_device, 它被如何处理完全由父节点的platform_driver决定, 一般是被创建为一个i2c_client。
    
    类似的也有/spi节点, 它一般也是用来表示SPI控制器, 它会被转换为platform_device, 在内核中有对应的platform_driver;
    /spi/flash@0节点不会被转换为platform_device, 它被如何处理完全由父节点的platform_driver决定, 一般是被创建为一个spi_device。
    

    详细流程参考github上文本内容

    附录

    1. GitHub:device_tree_i2c

    参考资料

    1. 让u-boot支持内核设备树dts
  • 相关阅读:
    作业2 对称密钥密码系统安全性分析(B班同学评论)
    作业1 你如何看待信息安全问题影响(B班同学评论)
    作业1 你如何看待信息安全问题影响(A班同学评论)
    jQuery火箭图标返回顶部代码
    jQuery火箭图标返回顶部代码
    jQuery火箭图标返回顶部代码
    jQuery火箭图标返回顶部代码
    jQuery火箭图标返回顶部代码
    jQuery火箭图标返回顶部代码
    jQuery火箭图标返回顶部代码
  • 原文地址:https://www.cnblogs.com/multimicro/p/11905647.html
Copyright © 2011-2022 走看看