我的内核学习笔记14:内核设备树学习

李迟按:
上一篇内核的文章是2年半前,期间因工作转行而停止研究,最近又重新捡起。这个系列从2013年起间断地更新,本来想从系统角度逐步写的,但工作量十分庞大,现在也想通了,在适合的时间写,不带目的,不强迫自己完成什么任务。不强调什么平台/芯片、内核版本。一篇文章针对一、两个知识点即可。
本文是笔者接触设备树的第一个实例,仅仅是验证设备树简单的写法和几个函数的使用。

一、设备树

Linux内核很早就引入设备树了。引入设备树之前,arm架构代码中有大量的硬编码——即外设特性、配置全部写到代码中,不管是时钟配置、引脚复用还是MTD分区、GPIO高低电平。这样的话稍有不同的板级配置(如同一SOC,有的有1个LED,有的有2个LED,flash大小亦会有不同),会使用不同的文件或在代码中用宏区分。总之,代码比较臃肿,设备树的引入,使得代码比较简洁,引脚复用、分区等等都放到设备树文件,不同设备树文件对应不同配置的板子,而代码无须大量修改(甚至不用修改)。
关于设备树的引入历史及linus大神的风范,网上有资料,此处不展开。

二、知识点

设备树有固定的语法,本文也不展开。只说一下简单的知识点。
arm的设备树位于目录arch/arm/boot/dts,该目录有许多不同板子的设备树源文件。
设备树源文件后缀为dts(device tree source),经过编译后生成设备树二进制文件,后缀为dtb(device tree blob)。dtb文件需要传递给内核进行解析。
设备树编译器为dtc,一般在内核源码目录输入make dtbs即可。使用的Makefilearch/arm/boot/dts/Makefile。该Makefile根据make menuconfig选定的芯片类型编译多个设备树,如自行新增的,需要在Makefile对应芯片类型添加,否则不会被编译。
一般不会从头编写设备树,而是使用类似的现成的模板修改。
内核启动参数可以在设备树中使用chosen指定,示例(imx6q)如下:

chosen {
        bootargs = "earlyprintk=vga log_buf_len=25M console=ttymxc0,115200 rootfstype=ext3 root=/dev/mmcblk1 rw rootwait init=/sbin/init";
		stdout-path = &uart2;
	};

本文使用的设备树节点如下:

// 专门用于测试dts的示例,没实例用途
    // 名称可以有“,”、“-”,如“ll,i2c-enable”
    myfoo {
        compatible = "ll,jimkent-foo";
        status = "okay"; // string
        enable; // bool,无须值
        myvalue = <250>; // 默认是32位,如果使用8位读取,结果为0
        value = /bits/ 8 <88>; // 8位单独赋值
        value16 = /bits/ 16 <166>; // 16位单独赋值
        a-cell = <1 2 3 4 5>; // 数组
        // 子节点
        foo {
            label = "foo";
            note = "this is foo";
        };
        bar {
            label = "bar";
            note = "this is bar";
        };
    };
 */

其中compatible与驱动使用的名称必须一致(这样才能匹配上)。其它内容比较简单,分别是字符串、布尔类型、不同位数的数值、数组、子节点。

三、驱动实例

驱动实例如下:

/**
 * @file   foo_drv.c
 * @author Late Lee <latelee@163.com>
 * @date   Wed Jun  7 22:21:19 2019
 * 
 * @brief  测试dts示例
 * 
 * @note   读取dts的值,学习dts。代码有部分警告,不影响

设备树节点:
 // 专门用于测试dts的示例,没实例用途
    // 名称可以有“,”、“-”,如“ll,i2c-enable”
    myfoo {
        compatible = "ll,jimkent-foo";
        status = "okay"; // string
        enable; // bool,无须值
        myvalue = <250>; // 默认是32位,如果使用8位读取,结果为0
        value = /bits/ 8 <88>; // 8位单独赋值
        value16 = /bits/ 16 <166>; // 16位单独赋值
        a-cell = <1 2 3 4 5>; // 数组
        // 子节点
        foo {
            label = "foo";
            note = "this is foo";
        };
        bar {
            label = "bar";
            note = "this is bar";
        };
    };
 */

#include <linux/module.h>
#include <linux/kernel.h>       /**< printk() */
#include <linux/init.h>

#include <linux/types.h>        /**< size_t */
#include <linux/errno.h>        /**< error codes */
#include <linux/string.h>

#include <linux/of.h>
#include <linux/of_device.h>

static int foo_remove(struct platform_device *dev)
{
    printk(KERN_NOTICE "remove...\n");

    return 0;
}

static int foo_probe(struct platform_device *dev)
{
    int ret = 0;
    struct device_node* np = dev->dev.of_node;
    struct device_node* child = NULL;
    const char* str = NULL;
    bool enable = false;
    u8 value = 0;
    u16 value16 = 0;
    u32 value32 = 0;
    
    // 测试dts读取API
    if(np == NULL)
    {
        pr_info("of_node is NULL\n");
        return 0;
    }
    
    of_property_read_string(np, "status", &str); // 读字符串

    enable = of_property_read_bool(np, "enable"); // bool类型,可判断某字段存在不存在
    of_property_read_u32(np, "myvalue", &value32); // 一般地,都使用u32读取数值
    of_property_read_u8(np, "value", &value);
    of_property_read_u16(np, "value16", &value16);
    
    u32 data[3] = {0};
	u32 tag = 0;
    // a-cell是一个数组,默认读第1个。
    of_property_read_u32(np, "a-cell", &tag);
    // 也可以读取指定大小的数组(不一定是全部的)
    of_property_read_u32_array(np, "a-cell", data, ARRAY_SIZE(data));
    
    printk("of read status: %s enable: %d value: %d %d %d\n", str, enable, value, value16, value32);
    printk("of read tag: %d data: %d %d %d\n", tag, data[0], data[1], data[2]);
    
    // 获取子节点个数
    int count = of_get_available_child_count(np);
    
    // 遍历所有子节点,按格式读取属性
    int index = 0;
    for_each_available_child_of_node(np,child)
    {
        const char* label = of_get_property(child,"label",NULL) ? : child->name;
        const char* note = of_get_property(child,"note",NULL) ? : child->name;
        printk("of read: label: %s note: %s\n", label, note);
    }
    return ret;
}

static struct of_device_id foo_of_match[] = {
	{ .compatible = "ll,jimkent-foo", },
	{ /* sentinel */ }
};

static struct platform_driver foo_driver = {
	.driver = {
		.name = "foo",
		.of_match_table = of_match_ptr(foo_of_match),
	},
	.probe  = foo_probe,
    .remove = foo_remove,
};

static int __init foo_drv_init(void)
{
    int ret = 0;

    ret = platform_driver_register(&foo_driver);
    if (ret)
    {
        pr_info("platform_driver_register failed!\n");
        return ret;
    }
    
    pr_info("Init OK!\n");
    
    return ret;
}

static void __exit foo_drv_exit(void)
{
    platform_driver_unregister(&foo_driver);
}

module_init(foo_drv_init);
module_exit(foo_drv_exit);

MODULE_AUTHOR("Late Lee");
MODULE_DESCRIPTION("Simple platform driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:foo");

示例的代码是一个简单的模板,除了学习dts外,没什么用处。但是可以以此展开复杂的、有实际用途的驱动。
与以前的platform驱动不同,platform_driver中指定of_match_tablefoo_of_match结构体的.compatible必须与设备树的compatible一致。
本驱动涉及到的读取设备树节点信息的函数如下,更多函数,参考内核源码的include/linux/of.h头文件:

of_property_read_string // 读取字符串
of_property_read_bool // 判断某个字段是否存在,无须赋值
of_property_read_u8 // 读取8比特
of_property_read_u16 // 读取16比特
of_property_read_u32 // 读取32比特

如果存在多个子节点,用of_get_available_child_count获取个数(可用于开辟内存),然后调用for_each_available_child_of_node遍历所有子节点,注意,of_get_propertyof_property_read_string有相同效果。只是用法不同而已。

四、小结

设备树有许多用法和功能,本文只是初窥门径而已。从实践角度,笔者建议先用实例来了解设备树的基本用法,熟悉后,再看其它与外设有关的(如时钟、引脚复用)的例子。
网上关于设备树的测试驱动资料比较少,笔者写此文,仅抛砖引玉。
本驱动在qemu的imx6q下测试通过。设备树文件名为imx6q-sabrelite.dts

五、资源

4.15版本内核在线查询
笔者cst仓库4.15版本代码

李迟 2019.6.12 周三 晚

  • 0
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值