前置知识篇
1. 进程
2. 线程
进程间通信篇
1. IPC概述
2. 信号
3. 消息传递
4. 同步
5. 共享内存区
编译相关篇
1. GCC编译
2. 静态链接与动态链接
3. makefile入门基础
设备驱动篇
1. 设备驱动概述
2. 内核模块_理论篇
3. 内核模块_实验篇
4. 字符设备_理论篇1
5. 字符设备_理论篇2
6. 字符设备_实验篇1
7. 字符设备_实验篇2
8. 设备模型
9. 平台设备驱动
10. 设备树_添加设备节点信息
11. 设备树_获取设备节点信息
本节主要介绍在得到一个设备树文件之后,如何使用代码去查找某个节点并获取该节点的各个属性。
无
《 [野火]i.MX Linux开发实战指南》
百度
在设备树中“节点”对应实际硬件中的设备,
我们在设备树中添加了一个“led”节点,正常情况下我们可以从这个节点获取编写led驱动所用到的所有信息,
例如led相关控制寄存器地址、 led时钟控制寄存器地址等等。
这一小节我们就开始学习如何从设备树的设备节点获取我们想要的数据。
内核提供了一组函数用于从设备节点获取资源(设备节点中定义的属性)的函数,这些函数以of_开头,称为OF操作函数。(“open firmware”即开放固件。)
这里介绍了7个寻找节点函数,这7个函数有一个共同特点—— 返回值类型相同 。
只要找到了节点就会返回节点对应的device_node结构体,在驱动程序中我们就是通过这个device_node获取设备节点的属性信息、顺藤摸瓜查找它的父、子节点等等。
第一函数of_find_node_by_path与后面六个不同,它是通过节点路径寻找节点的,“节点路径”是从设备树源文件(.dts)中得到的。
而中间四个函数是根据节点属性在某一个节点之后查找符合要求的设备节点,这个“某一个节点”是设备节点结构体(device_node),也就是说这个节点是 已经找到的 。
最后两个函数与中间四个类似,只不过最后两个没有使用节点属性而是根据父、子关系查找。
这个过程可以理解为**把设备树中的设备节点“获取”到驱动中**。
static inline struct device_node *of_find_node_by_path(const char *path)
struct device_node {
/* 节点中属性为name的值 */
const char *name;
/* 节点中属性为device_type的值 */
const char *type;
phandle phandle;
/* 节点的名字,在device_node结构体后面放一个字符串,full_name指向它 */
const char *full_name;
struct fwnode_handle fwnode;
/* 链表,连接该节点的所有属性 */
struct property *properties;
struct property *deadprops;
/* 指向父 / 子 / 兄弟节点 */
struct device_node *parent;
struct device_node *child;
struct device_node *sibling;
#if defined(CONFIG_OF_KOBJ)
struct kobject kobj;
#endif
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
};
得到device_node结构体之后我们就可以使用其他of 函数获取节点的详细信息。
struct device_node *of_find_node_by_name(struct device_node *from, const char *name)
struct device_node *of_find_node_by_type(struct device_node *from, const char *type)
struct device_node *of_find_compatible_node(struct device_node *from, const char *type, const char *compatible)
相比of_find_node_by_name函数增加了一个compatible属性作为筛选条件。
static inline struct device_node *of_find_matching_node_and_match(
struct device_node *from,
const struct of_device_id *matches,
const struct of_device_id **match)
可以看到,该结构体包含了更多的匹配参数,也就是说相比前三个寻找节点函数,这个函数匹配的参数更多,对节点的筛选更细。
/* 这个数组就是imx-wm8960.c这个驱动文件的匹配表 */
static const struct of_device_id imx_wm8960_dt_ids[] = {
{ .compatible = "fsl,imx-audio-wm8960", },
{ /* sentinel */ }
};
of_device_id: 结构体如下。
struct of_device_id {
/* 节点中属性为name的值 */
char name[32];
/* 节点中属性为device_type的值 */
char type[32];
/* 节点的名字,在device_node结构体后面放一个字符串,full_name指向它 */
char compatible[128];
/* 链表,连接该节点的所有属性 */
const void *data;
};
struct device_node *of_get_parent(const struct device_node *node)
struct device_node *of_get_next_child(const struct device_node *node, struct device_node *prev)
参数:
返回值:
device_node:device_node类型的结构体指针,保存获取得到的节点。同样,如果失败返回NULL。
“获取”成功后我们再通过一组of函数从设备节点结构体(device_node)中获取我们想要的设备节点属性信息。
struct property *of_find_property(const struct device_node *np,
const char *name,
int *lenp)
参数:
返回值:
property:获取得到的属性。
struct property {
char *name; //属性名
int length; //属性长度
void *value; //属性值
struct property *next; //下一个属性
#if defined(CONFIG_OF_DYNAMIC) || defined(CONFIG_SPARC)
unsigned long _flags;
#endif
#if defined(CONFIG_OF_PROMTREE)
unsigned int unique_id;
#endif
#if defined(CONFIG_OF_KOBJ)
struct bin_attribute attr;
#endif
};
读取属性函数是一组函数,分别为读取8、16、32、64位数据。(即非字符串属性)
/* 8位整数读取函数 */
static inline int of_property_read_u8_array(const struct device_node *np,
const char *propname,
u8 *out_values, size_t sz)
/* 16位整数读取函数 */
static inline int of_property_read_u16_array(const struct device_node *np,
const char *propname,
u16 *out_values, size_t sz)
/* 32位整数读取函数 */
static inline int of_property_read_u32_array(const struct device_node *np,
const char *propname,
u32 *out_values, size_t sz)
/* 64位整数读取函数 */
static inline int of_property_read_u64_array(const struct device_node *np,
const char *propname,
u64 *out_values, size_t sz)
这里的函数是对读取整型属性函数的简单封装,将读取长度设置为1。
用法与读取属性函数完全一致,这里不再赘述。
/* 8位整数读取函数 */
static inline int of_property_read_u8(const struct device_node *np,
const char *propname,
u8 *out_value)
/* 16位整数读取函数 */
static inline int of_property_read_u16(const struct device_node *np,
const char *propname,
u16 *out_value)
/* 32位整数读取函数 */
static inline int of_property_read_u32(const struct device_node *np,
const char *propname,
u32 *out_value)
/* 64位整数读取函数 */
static inline int of_property_read_u64(const struct device_node *np,
const char *propname,
u64 *out_value)
在设备节点中存在很多字符串属性,例如compatible、status、type等等,
这些属性可以使用查找节点属性函数of_find_property来获取,但是这样比较繁琐。
内核提供了一组用于读取字符串属性的函数,介绍如下:
static inline int of_property_read_string(const struct device_node *np,
const char *propname,
const char **out_string)
这个函数使用相对繁琐,推荐使用下面这个函数。
static inline int of_property_read_string_index(const struct device_node *np,
const char *propname,
int index, const char **output)
相比前面的函数增加了参数index,它用于指定读取属性值中第几个字符串,index从零开始计数。
第一个函数只能得到属性值所在地址,也就是第一个字符串的地址,其他字符串需要我们手动修改移动地址,非常麻烦,推荐使用第二个函数。
在设备节点中一些属性是BOOL型,当然内核会提供读取BOOL型属性的函数,介绍如下:
static inline bool of_property_read_bool(const struct device_node *np,
const char *propname)
在设备树的设备节点中大多会包含一些内存相关的属性,比如常用的reg属性。
通常情况下,得到寄存器地址之后我们还要通过ioremap函数将物理地址转化为虚拟地址。
现在内核提供了of函数,自动完成物理地址到虚拟地址的转换。
介绍如下:
void __iomem *of_iomap(struct device_node *np, int index)
内核也提供了常规获取地址的of函数,这些函数得到的值就是我们在设备树中设置的地址值。介绍如下:
int of_address_to_resource(struct device_node *node, int index,
struct resource *r)
struct resource {
resource_size_t start; //起始地址
resource_size_t end; //结束地址
const char *name; //属性名字
unsigned long flags;
unsigned long desc;
struct resource *parent, *sibling, *child;
};
本实验是一个简化的字符设备驱动,在驱动中没有实际操作硬件,仅在.open 函数中调用of函数获取设备树节点中的属性,获取成功后打印获取得到的内容。
程序源码如下所示,这里只列出了.open函数中的内容,其他与字符设备驱动类似,完整内容请参考本章配套源码“补充”。
/*.open 函数*/
static int led_chr_dev_open(struct inode *inode, struct file *filp)
{
int error_status = -1;
printk("\n open form device \n");
/* 获取DTS属性信息 */
/* 使用“of_find_node_by_path”函数寻找“led_test”设备节点。参数是“led_test”的设备节点路径 */
led_device_node = of_find_node_by_path("/led_test");
if(led_device_node == NULL)
{
printk(KERN_ALERT "\n get led_device_node failed ! \n");
return -1;
}
/* 根据 led_device_node 设备节点结构体输出节点的基本信息 */
/* 获取成功后得到的是一个device_node类型的结构体指针,然后我们就可以从这个结构体中获得我们想要的数据。获取完整的属性信息可能还需要使用其他of函数 */
printk(KERN_ALERT "name: %s",led_device_node->name); //输出节点名
printk(KERN_ALERT "child name: %s",led_device_node->child->name); //输出子节点的节点名
/* 获取 rgb_led_red_device_node 的子节点 */
/* 可以使用“of_get_next_child”函数获取它的子节点。当然我们也可以从“led”节点的“设备节点结构体”中直接读取得到它的第一个子节点 */
rgb_led_red_device_node = of_get_next_child(led_device_node,NULL);
if(rgb_led_red_device_node == NULL)
{
printk(KERN_ALERT "\n get rgb_led_red_device_node failed ! \n");
return -1;
}
printk(KERN_ALERT "name: %s", rgb_led_red_device_node->name); //输出节点名
printk(KERN_ALERT "parent name: %s", rgb_led_red_device_node->parent->name); //输出父节点的节点名
/* 获取 rgb_led_red_device_node 节点 的"compatible" 属性 */
/* 使用“of_find_property”函数获取“rgb_led_red”节点的“compatible”属性 */
rgb_led_red_property = of_find_property(rgb_led_red_device_node,"compatible",&size);
if(rgb_led_red_property == NULL)
{
printk(KERN_ALERT "\n get rgb_led_red_property failed ! \n");
return -1;
}
printk(KERN_ALERT "size = : %d",size); //实际读取得到的长度
printk(KERN_ALERT "name: %s",rgb_led_red_property->name); //输出属性名
printk(KERN_ALERT "length: %d",rgb_led_red_property->length); //输出属性长度
printk(KERN_ALERT "value : %s",(char*)rgb_led_red_property->value); //属性值
/* 获取 reg 地址属性 */
/* 使用“of_property_read_u32_array”函数获取reg属性 */
error_status = of_property_read_u32_array(rgb_led_red_device_node, "reg", out_values, 2);
if(error_status != 0)
{
printk(KERN_ALERT "\n get out_values failed ! \n");
return -1;
}
printk(KERN_ALERT"0x%08X ", out_values[0]);
printk(KERN_ALERT"0x%08X ", out_values[1]);
return 0;
}
编译成功后将驱动.ko拷贝到开发板,使用insmod安装驱动模块然后可以在/dev/目录下找到get_dts_info。
向驱动模块随便输入一个字符