Linux设备驱动开发---设备树的概念

文章目录

  • 1 设备树机制
    • 命名约定
    • 别名、标签和phandle
    • DT编译器
  • 2 表示和寻址设备
    • SPI和I2C寻址
    • 平台设备寻址
  • 3 处理资源
    • 提取特定应用数据
      • 文本字符串
      • 单元格和无符号的32位整数
      • 布尔
      • 提取并分析子节点
  • 4 平台驱动程序与DT
    • OF匹配风格
      • 处理非设备树平台
    • 平台数据与DT

设备树(DT)是易于阅读的硬件描述文件,它采用JSON式的格式化风格,在这种简单的树形结构中, 设备表示为带有属性的节点。属性可以为空(只有键,用来描述布尔值),也可以是键值对,其中的值可以是任意的字节流。

1 设备树机制

将选项CONFIG_OF设置为Y即可在内核中启用DT。要在驱动程序中调用DT API,必须添加以下头文件

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

DT支持一些数据类型:

  • 文本字符串用双引号表示。可以使用逗号来创建字符串列表
  • 单元格是由尖括号分隔的无符号32位整数
  • 布尔类型不过是空属性,其值是true或false取决于属性存在于否

比如:

node_lable:nodename@reg{srting-property="a string";					//一个字符串string-list="read","write";					//字符串列表onw-int-property=<197>;						//一个整型数字int-list-property=<0xbeef 123 0xabcd 4>;	//整型列表byte-array-property=[0x01 0x02 0x35 0x47];	//字节数组bool-property;								//布尔数据mixed-list-property="a string",<oxabcd 45>,<35>,[0x01 0x23 0x45];	//混合类型	
};

命名约定

每个节点都必须由<name>[@<address>]形式的名称,其中<name>是一个字符串,其最多可以为31个字符,[@<address>]是可选的,具体取决于节点代表的设备是否为可寻址的。<address>是用来访问设备的主要地址。设备命名的一个例子如下:

i2c@021a0000{compatible = "fls,imx6q-i2c","fls,ima21-i2c";reg = <0x021a0000 0x4000>;...
}

别名、标签和phandle

node_lable:nodename@reg{...
};gpio1:gpio@0209c000{...
};
aliases{...gpio1 = &gpio1;...
};

标签不过是标记节点的方法,可以用唯一的名称来标识节点。DT编译器将该名称转换为唯一的32位值。在上面的例子中,gpio1和node_label都是标签。之后可以用标签来引用节点,因为标签对于节点是唯一的。

指针句柄(point handle,简写为phandle)是与节点相关联的32位值,用于唯一标识该结点,以便可以从另一个结点的属性引用该节点。标签用于一个指向节点的指针,使用&mylabel可以指向标签为mylabel的节点,比如gpio1 = &gpio1。&gpio1被转换为phandle,以便引用gpio1节点。

为了在查找节点时不遍历整棵树,引入了别名的概念。在DT中,别名节点可以看做是快速查找表,可以使用函数find_node_by_alias()来查找指定别名的节点。别名不是直接在DT源中使用,而是由Linux内核来引用。

DT编译器

DT有两种形式:文本形式(代表源,也称作DTS)和二进制块形式(代表编译后的DT),也称作DTB。源文件的拓展名是.dts。.dtsi是SOC级定义,.dts文件代表开发板定义。就像源文件(.c)和包含头文件(.h)一样,应该把.dtsi作为头文件包含在.dts文件中。而二进制文件使用.dtb拓展名。

2 表示和寻址设备

每个设备在DT中至少有一个节点。某些属性对于设备是通用的,这些属性是reg、#address-cells和#size-cells,它们的用途是在其所在总线上进行设备寻址。主要的寻址属性是reg,其含义取决于设备所在的总线。size-cells和address-cells的前缀#可以翻译为length。
每个可寻址设备都具有reg属性,该属性是reg = <address0 size0>,<address1 size1>…形式的元组列表,其中每个元组代表设备的地址范围。#size-cells(长度单元)指示使用多少个32位单元来表示size0,如果与大小无关,则可以是0。 #address-cells(地址单元)指示用多少个32位单元来表示address0 。

可寻址设备继承它们父节点的#size-cells和#address-cells,其指定的#size-cells和#address-cells影响子设备。

SPI和I2C寻址

SPI和I2C设备都属于非内存映射设备,因为它们的地址对CPU不可访问。而总线控制器驱动程序将代表CPU执行间接访问。每个I2C/SPI设备都表示为在I2C/SPI总线节点上的子节点。对于非存储映射的设备,#size-cells表示为0,寻址元组中的size元素为空。这意味着这种设备的reg属性总是只有一个单元:

&i2c3{...temperature-sensor@49{...reg=<0x49>;...};pcf8523:rtc@68{...reg = <0x68>;...};...
};&ecspi{...cs-gpios = <&gpio4 17 0>,<&gpio5 17 0>,<&gpio6 17 0>;...ad7606r8_0:ad7606r8@1{...reg = <1>;...};...
};

reg属性只是一个保存地址值的单元格,I2C设备的reg属性用于指定总线上设备的地址。对于SPI,reg表示从控制器节点 所具有的芯片选择列表中 分配给设备的芯片选择线的索引。例如对于ad7606r8 ADC,芯片选择索引是1,对应于cs-gpios中的<&gpio5 17 0>,cs-gpios是控制器节点的芯片选择列表。

平台设备寻址

下面介绍的内存映射设备,其内存可由CPU访问。在这里,reg属性仍然定义设备的地址,这是可以访问设备的内存区域列表。每个区域用单元格元组表示,其中第一个单元格是内存区域的基地址,第二个单元格是该区域的大小。每个元组代表设备使用的地址范围。

这种设备应该在具有特殊值compatible= "simple-bus"的节点内声明,这意味着简单的内存映射总线,没有特定的处理和驱动程序:

soc{#address-cells = <1>;#size-cells = <1>;compatible = "simple-bus";aips-bus@02000000{...#address-cells = <1>;#size-cells = <1>;reg = <0x02000000 0x100000>;...spba-bus@02000000{...#address-cells = <1>#size-cells = <1>;reg = <0x02000000 0x400000>;...ecspi:ecspi@02008000{...#address-cells = <1>;#size-cells = <0>;reg = <0x02008000 0x400>;...};...};...};...
};

父结点compatible= “simple-bus”,其在节点将被注册为平台设备。设置#size-cells=<0>也能够看到SPI总线控制器怎样改变其子节点的寻址方式的。从内核设备树文档可以查找所有 绑定信息:Document/devicetree/binding/。

3 处理资源

当驱动程序期望某种类型的资源列表时,由于编写开发板设备树的人通常不是写驱动程序的人,因此不能保证该列表是以驱动程序期望的方式排序。例如,驱动程序可能期望其设备节点具有2条IRQ线路,一条用于索引0处的Tx事件,另一条用于索引1处的Rx。如果这种顺序得不到满足,驱动就会发生异常行为。为了避免这种不匹配,引入了命名资源的概念,它由定义资源列表命名组成,因此无论索引什么,给定的名称总将与资源相匹配。
命名资源的相关属性如下:

  • reg-names:reg属性中的内存区域列表
  • clock-names:clocks属性中命名clocks
  • interrupt-names:为interrupts属性中的每个中断指定一个名称
  • dma-names:用于dma属性

例如:

fake_device{...reg=<0x4a06400 0x800>,<0x4a064800 0x200>,<0x4a064c00 0x200>;reg-name="config","ohci","ehci";interrupters=<0 66 IRQ_TYPE_LEVEL_HIGH>,<0 67 IRQ_TYPE_LEVEL_HIGH>;interrupter-names="ohci","ehci";clocks=<&clks IMAX6QDL_CLK_UART_IPG>,<&clks IMAX6QDL_CLK_UART_SERTAL>;clock-names="ipg","per";dmas=<&sdma 25 4 0>,<&sdma 26 4 0>;dma-names="rx","tx";
};	

驱动程序中提取每个命名资源代码如下所示:

struct resource *res1,*res2;
res1 = platform_get_resource_byname(pdev,IORESOURCE_MEM,"ohci");
res2 = platform_get_resource_byname(pdev,IORESOURCE_MEM,"config");struct dma_chan *dma_chan_rx,*dma_chan_tx;
dma_chan_rx=dma_request_slave_channel(&pdev->dev,"rx");
dma_chan_tx=dma_request_slave_channel(&pdev->dev,"tx");int txirq,rxirq;
txirq = platform_get_irq_byname(pdev,"ohci");
rxirq = platform_get_irq_byname(pdev,"ehci");struct *clk_ipg,*clk_per;
clk_ipg = devm_clk_get(&pdev->dev,"ipg");
clk_per = devm_clk_get(&pdev->dev,"per");

这样,就可以确保把正确的名字映射到正确的资源上,而不用再使用索引了。

提取特定应用数据

特定应用数据时公共属性之外的数据(既不是资源,也不是IO、调度器等),可以分配给设备的任意属性和子节点,这样的属性名称通常以制造商代码作前缀。它们可以是任何字符串、布尔值或整型值。

文本字符串

下面是一个string属性:

string-property = "a string";

驱动程序使用of_property_read_string()读取字符串值,其原型定义如下:

int of_property_read_string(const struct device_node *np,const char *propname,const char **out_string);

以下代码说明如何使用它:

const char *my_string = NULL;
of_property_read_string(pdev->dev.of_node,"string-property",&my_string);

单元格和无符号的32位整数

下面是unsigned int属性:

one-int-property=<197>;
int-list-property = <1350000 0x54dae47 1250000 1200000>;

应该使用of_property_read_u32()读取单元格值。其原型定义如下:

int of_property_read_u32(const struct device_node *np,const char *propname,u32 *out_value);

驱动中使用:

unsigned int number;
of_property_read_u32(pdev->dev.of_node,"one-cell-property",&number);

可以使用of_property_read_u32_array()读取单元格列表,其原型如下:

int of_property_read_u32_array(const struct device_node *np,const char *propname,u32 *out_values, size_t sz);

sz是要读取数组元素的数量。比如:

unsigned int cells_array[4];
of_property_read_u32_array(pdev->dev.of_node,"int-list-property",cells_array,4);

布尔

使用of_property_read_bool()读取布尔属性。属性的名称在函数的第二个参数中给出:

bool my_bool = of_property_read_bool(pdev->dev.of_node,"bool-property");
if(my_bool)
{/* 布尔值为真 */
}
else{/* 布尔值为假 */
}

提取并分析子节点

使用for_each_child_of_node()遍历指定节点的子节点:

struct device_node *np = pdev->dev.of_node;
struct device_node *sub_np;
for_each_child_of_node(np,sub_np)
{...
}

4 平台驱动程序与DT

平台驱动程序也使用DT,也就是说,这是现在推荐的处理平台设备的方法,不在需要使用开发板文件,甚至不需要在设备的属性更改时重新编译内核。

OF匹配风格

OF匹配风格是平台核心执行的第一种匹配机制,以匹配设备及其驱动程序。它使用设备树的compatible属性来匹配of_match_table中的设备项,设备项是struct driver子结构的一个字段。每个设备节点都有compatible属性,它是字符串或字符串列表。任何驱动程序只要声明compatible属性中列出的字符串之一,就将触发匹配,并看到probe函数执行。

DT匹配项在内核中被描述为struct of_device_id结构的实例,该结构定义在liunx/mod_devicetable.h中,如下所示:

struct of_device_id{...char compatible[128];const void *data;
};
  • compatible:这是用来匹配DT设备节点兼容属性的字符串字符串,它们必须完全相同才可以匹配
  • data:这可以指向任何结构,这个结构可以用作每个设备类型的配置数据。

of_match_table是指针,可以传递struct of_device_id数组,使驱动程序兼容多个设备:

static const struct of_device_id imx_uart_dt_ids[] = {{ .compatible = "fsl,imx6q-uart",},{ .compatible = "fsl,imx1-uart",},{ .compatible = "fsl,imx21-uart",},....
};

填充了of_device_id 数组,就必须把它传递到平台驱动程序的of_match_table字段:

static struct platform_driver serial_imx_driver = {....driver = {.name = "imax-uart",.of_match_table = imx_uart_dt_ids,...};
};

这一步,只有驱动程序知道of_device_id数组,要使内核也或得通知(这样可以把of_device_id 数组存储到平台内核维护的设备列表中),该数组必须用MODULE_DEVICE_TABLE(设备类型,设备表)注册:

MODULE_DEVICE_TABLE(of,imx_uart_dt_ids);

这样,驱动程序就兼容DT,接下来声明与驱动程序兼容的设备:

uart1:serial@02020000{compatible = "fsl,imx6q-uart","fls,imx21-uart";reg = <0x02020000 0x4000>;...
};

这里提供了两个兼容的字符串,如果第一个与任何驱动程序都不匹配,则平台核心将执行第二个匹配。
当发生匹配时,将以struct platform_device 结构作为参数调用驱动程序的probe函数。

处理非设备树平台

使用CONFIG_OF选项在内核中启用DT支持。如果内核没有启用DT支持,则可能会希望避免使用DT API。其实现的方法是检查CONFIG_OF是否设置。过去常常这样做:

#ifdef CONFIG_OFstatic const struct of_device_id imx_uart_dt_ids[] = {{ .compatible = "fsl,imx6q-uart",},{ .compatible = "fsl,imx1-uart",},{ .compatible = "fsl,imx21-uart",},....};....
#endif

这不是唯一的选择,还可以使用of_match_ptr宏,当OF被禁用时它简单地返回NULL,查看源代码,在include/linux/of.h里面。

#ifdef CONFIG_OF
...
#define of_match_ptr(_ptr)	(_ptr)
...
#else /* CONFIG_OF */
...
#define of_match_ptr(_ptr)	NULL
...
#endif /* CONFIG_OF */

平台数据与DT

如果驱动程序需要平台数据,则应该检查dev.platfrom_data指针。非空值表示驱动程序已经在开发板配置文件中以旧的方法实例化,并且DT不会处理它。对于从DT实例化的驱动程序,dev.platform_data将为NULL,平台设备将在DT项上获得一个指针,该指针对应于dev.of_node指针中的设备,可以从该指针中提取资源并使用OF API分析和提取应用数据

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/379491.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【转】C#中数组复制的4种方法

C#中数组复制的4种方法 from&#xff1a;http://blog.csdn.net/burningcpu/article/details/1434167今天旁边的同事MM叫我调了一段程序&#xff0c;她想复制一个数组&#xff0c;int[] pins {9,3,4,9};int [] alias pins;这里出了错误&#xff0c;也是错误的根源&#xff0c…

Java StringBuilder codePointAt()方法与示例

StringBuilder类codePointAt()方法 (StringBuilder Class codePointAt() method) codePointAt() method is available in java.lang package. codePointAt()方法在java.lang包中可用。 codePointAt() method is used to return the Unicode code point at the given indices an…

用户虚拟地址转化成物理地址,物理地址转换成内核虚拟地址,内核虚拟地址转换成物理地址,虚拟地址和对应页的关系

文章目录1. 用户虚拟地址转换成物理地址2. 内核虚拟地址转换成物理地址3. 物理地址转换成内核虚拟地址4 内核虚拟地址和对应页5 根据进程号获取进程描述符1. 用户虚拟地址转换成物理地址 static void get_pgtable_macro(void) {printk("PAGE_OFFSET 0x%lx\n", PAGE…

简单三层架构(登录)

1&#xff0c;首先导包 dao //获取数据String username request.getParameter("username");String password request.getParameter("password");//传递到Service层UserService service new UserService();//这里的UserService 需要创建到service包下Use…

通过隐藏option实现select的联动效果

开始的时候需求是根据一定条件隐藏一部分<option>标签&#xff0c;类似联动效果&#xff0c;但是目前的html规范并没有为<option>提供隐藏的效果&#xff0c;因此常用的设置display或者visibility无效。网上大部分解决方案是删除<option>节点或<option>…

Java SimpleTimeZone setEndRule()方法与示例

SimpleTimeZone类setEndRule()方法 (SimpleTimeZone Class setEndRule() method) Syntax: 句法&#xff1a; public void setEndRule(int en_mm, int en_dd, int en_time);public void setEndRule(int en_mm, int en_dd, int en_dow, int en_time);public void setEndRule(int…

Linux设备驱动开发--- DMA

文章目录1 设置DMA映射缓存一致性和DMADMA映射一致映射流式DMA映射2 完成的概念3 DMA引擎API分配DMA从通道设置从设备和控制器指定参数获取事务描述符提交事务发布待处理DMA请求并等待回调通知4 程序单缓冲区映射分散聚集映射DMA是计算机系统的一项功能&#xff0c;它允许设备在…

类加载器

一、类加载器 1&#xff0c;什么是类加载器&#xff1f; 类加载器就是用来加载字节码文件 2&#xff0c;类加载器的种类有哪些&#xff1f; 1&#xff09;BootStrap&#xff1a;引导类加载器&#xff1a;加载都是最基础的文件 2&#xff09;ExtClassLoader&#xff1a;扩展类加…

一个用java读取XML文件的简单方法(转)

XML文件 book.xml <book> <person> <first>Kiran</first> <last>Pai</last> <age>22</age> </person> <person> <first>Bill</first> <last>Gates</last> <age>46</age&g…

Java ObjectStreamField getName()方法与示例

ObjectStreamField类的getName()方法 (ObjectStreamField Class getName() method) getName() method is available in java.io package. getName()方法在java.io包中可用。 getName() method is used to get the name of this ObjectStreamField field. getName()方法用于获取…

【css】CSS中折叠margin的问题

为什么要翻译这篇说明&#xff1f;css2本有人已翻译过&#xff0c;但看一下&#xff0c;很粗糙&#xff08;不是说自己就怎么怎么样啊&#xff0c;翻译者真的是很值得敬佩的&#xff01;&#xff09;&#xff0c;近来跟css与xhtml接触得越来越多&#xff0c;但接触得越多&#…

算法---链表

文章目录反转链表合并两个有序链表删除重复元素反转链表 反转链表包括两种&#xff0c;反转全部元素或者反转部分元素。在这里&#xff0c;我们约定&#xff1a;数据元素类型是struct LinkNode&#xff0c;要反转链表的第一个节点是head&#xff0c;head的前面一个节点是pre&a…

SSM

二、环境设置&#xff08;MyEclipse&#xff09; 1&#xff0c;字体设置 window–>Preference->General->Appearance->Colors and Fonts->Basic Text->Font 2&#xff0c;workspace字符集设置 window–>Preference->General->Appearance->W…

IOS NSArray,NSDictionary

小结&#xff1a; NSArray有序的集合&#xff1b; NSDictionary无序的集合&#xff0c;可排序&#xff1b; 增删改查 ------NSArray----------- create : 1)NSArray *array [NSArray arrayWithObjects:"Henry","Jones", "Susan", "Smith&q…

Java PropertyPermission equals()方法与示例

PropertyPermission类equals()方法 (PropertyPermission Class equals() method) equals() method is available in java.util package. equals()方法在java.util包中可用。 equals() method is used to check whether this object and the given object (ob) are equal or not…

c#配合oracle快速导入excel方法--原创(6万条记录5分钟左右)

原理&#xff1a;用c#采用读取Excel数据源方式将数据读入c#的datatable,循环datatable,将datatable中的数据用stringbuilder拼成insert into (字段名) valus (值);每5条插入一个符号&#xff08;作用是将sql字符串限制在4000字符以内&#xff09;&#xff0c;然后将拼成的字符串…

English最俗语法大全

一、先分析两个长难句 1,It is a truth universally acknowledged that a single man in possession of a good fortune must be in want of a wife. 人们公认这样一个事实&#xff0c;一个有钱的单身男人一定想要娶一个妻子。 in want of want 想要 university widely 广泛的…

tfs 内网和外网切换的方法。

C:\Windows\System32\drivers\etc的hosts文件配置一个123.67.128.109 geo-dept-3转载于:https://www.cnblogs.com/lwflt/archive/2012/07/23/2604731.html

observable_Java Observable countObservers()方法与示例

observable可观察的类countObservers()方法 (Observable Class countObservers() method) countObservers() method is available in java.util package. countObservers()方法在java.util包中可用。 countObservers() method is used to count the number of observers exists…

设计模式--Strategy 策略模式

所谓策略模式(Strategy Pattern)&#xff0c;就是将策略 (算法) 封装为一个对象&#xff0c;易于相互替换&#xff0c;如同 USB 设备一样可即插即用&#xff1b;如果将策略、具体的算法和行为&#xff0c;编码在某个类或客户程序内部&#xff0c;将导至事后的修改和扩展不易。 …