linux驱动之获取设备树信息
上一篇文章学习了字符设备的注册,操作过的小伙伴都知道上一篇文章中测试驱动时是通过手动创建设备节点的,现在开始学习怎么自动挂载设备节点和设备树信息的获取,这篇文章中的源码将会是我以后编写字符驱动的模板。
一、准备材料
开发环境:VMware
操作系统:ubuntu
开发版:湃兔i2S-6UB
库文件:linux开发板或ubuntu的内核源码
二、自动创建设备节点
需要用到的头文件 #include <linux/device.h>,需要用到的函数如下所示
struct class *class_create(owner, name)
void class_destroy(struct class *class)
struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...)
void device_destroy(struct class *class, dev_t devt);
owner: THIS_MODULE
name: 设备节点的名称(也就是/dev目录下的文件名)
class:类
parent:NULL
devt:设备号
drvdata:NULL
fmt:设备节点的名称
三、获取设备树信息
为了帮助像我一样才接触linux驱动,对设备树不是很理解的小伙伴,所系这里就不对设备树进行详细的介绍。可以将设备树简单的理解为,设备树的存在是方便linux内核研究人员专心的研究内核的功能,通过设备树将板载的描述文件和内核分开,使得内核文件不在臃肿。有需要的小伙伴可以了解Device Tree。
设备树文件在内核源码的“arch/arm/boot/dts”目录下,设备树的描述文件是'.dtsi',每个开发板对应的文件不同,比如我的开发板的描述文件是i2c6ulxb-i2s6ull-emmc.dtsi,打开可以看到的信息如图所示:

在这里我就不对设备进行更改了,我对backlight节点信息进行读取,有需要了解设备树语法的小伙伴可以了解Linux设备树语法详解。
我在驱动中读取设备树的主要函数有以下几个,想了解更多of函数的小伙伴可以了解linux设备树常用of操作函数。
inline struct device_node *of_find_node_by_path(const char *path)
int of_property_read_string(struct device_node *nd, const char *propname, const char *out_string);
int of_property_read_u32_array(struct device_node *nd, const char *propname, u32 *out_value)
path:带有全路径的节点名,可以使用节点的别名
np:设备节点
proname 要读取的属性名字
out_string:读取到的字符串值
out_value:读取到的数组值
通过这几个函数,就可以将设备树种的信息的读取出来了,接下载看源码
四、程序源码
驱动文件chrdevtemp.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#define CHRDEVTEMP_NAME "chrdevtemp"
#define CHRDEVTEMP_COUNT 1
/* 设备结构体 */
struct chrtemp_dev{
dev_t devid; /* 设备号 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
struct cdev cdev; /* 字符设备 */
struct class *class; /* 类结构体 */
struct device *device; /* 设备 */
struct device_node *nd; /* 设备节点 */
int gpio_number; /*gpio的编号*/
};
struct chrtemp_dev chrdevtemp;
static char readbuf[100];
static char writebuf[100];
static char kerneldata[] = {"hello This is the kernel data"};
static int chrdevtemp_open(struct inode *inode, struct file *filp)
{
filp->private_data = &chrdevtemp;
return 0;
}
static int chrdevtemp_release(struct inode *inode, struct file *filp)
{
return 0;
}
static ssize_t chrdevtemp_read(struct file *filp, __user char *buf, size_t count, loff_t *ppos)
{
int ret = 0;
memcpy(readbuf, kerneldata, sizeof(kerneldata));
ret = copy_to_user(buf, readbuf, count);
if (ret == 0) {
} else {
}
return 0;
}
static ssize_t chrdevtemp_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
int ret = 0;
ret = copy_from_user(writebuf, buf, count);
if (ret == 0) {
printk("kernel recevdata:%s\r\n", writebuf);
} else {
}
return 0;
}
/*
* 字符设备操作集合
*/
static const struct file_operations chrdevtemp_fops = {
.owner = THIS_MODULE,
.open = chrdevtemp_open,
.release = chrdevtemp_release,
.read = chrdevtemp_read,
.write = chrdevtemp_write,
};
/*
* 模块入口
*/
static int __init chrdevtemp_init(void)
{
int ret = 0;
const char *str;
u32 brightness[8];
u8 i;
printk("chrdevtemp_init\r\n");
/* 申请设备号 */
chrdevtemp.major = 0; /* 设置设备号由内存分配 */
if (chrdevtemp.major){
chrdevtemp.devid = MKDEV(chrdevtemp.major, 0);
ret = register_chrdev_region(chrdevtemp.devid, CHRDEVTEMP_COUNT, CHRDEVTEMP_NAME);
} else {
ret = alloc_chrdev_region(&chrdevtemp.devid, 0, CHRDEVTEMP_COUNT, CHRDEVTEMP_NAME);
chrdevtemp.major = MAJOR(chrdevtemp.devid);
chrdevtemp.minor = MINOR(chrdevtemp.devid);
}
if (ret < 0) {
printk("chrdevtemp chrdev_region err!\r\n");
goto fail_devid;
}
/* 注册字符设备 */
chrdevtemp.cdev.owner = chrdevtemp_fops.owner;
cdev_init(&chrdevtemp.cdev, &chrdevtemp_fops);
ret = cdev_add(&chrdevtemp.cdev, chrdevtemp.devid, CHRDEVTEMP_COUNT);
if (ret < 0) {
goto fail_cdev;
}
/* 自动创建设备节点 */
chrdevtemp.class = class_create(THIS_MODULE, CHRDEVTEMP_NAME);
if (IS_ERR(chrdevtemp.class)) {
ret = PTR_ERR(chrdevtemp.class);
goto fail_class;
}
chrdevtemp.device = device_create(chrdevtemp.class, NULL, chrdevtemp.devid, NULL, CHRDEVTEMP_NAME);
if (IS_ERR(chrdevtemp.device)) {
ret = PTR_ERR(chrdevtemp.device);
goto fail_device;
}
/* 获取设备树的属性内容 */
chrdevtemp.nd = of_find_node_by_path("/backlight");
if (chrdevtemp.nd == NULL) {
ret = -EINVAL;
goto fail_findnd;
}
/* 获取字符串属性 */
ret = of_property_read_string(chrdevtemp.nd, "compatible", &str);
if (ret < 0) {
goto fail_rs;
} else {
printk("status is: %s\r\n", str);
}
/* 获取数组 */
ret = of_property_read_u32_array(chrdevtemp.nd, "brightness-levels", brightness, 8);
if (ret < 0) {
goto fail_rs;
} else {
printk("brightness-levels: ");
for(i = 0; i < 8; i++){
printk("%d ", brightness[i]);
}
printk("\r\n");
}
return 0;
fail_rs:
fail_findnd:
device_destroy(chrdevtemp.class, chrdevtemp.devid);
fail_device:
class_destroy(chrdevtemp.class);
fail_class:
cdev_del(&chrdevtemp.cdev);
fail_cdev:
unregister_chrdev_region(chrdevtemp.devid, CHRDEVTEMP_COUNT);
fail_devid:
return ret;
}
/*
* 模块出口
*/
static void __exit chrdevtemp_exit(void)
{
printk("chrdevtemp_exit\r\n");
/* 删除字符设备 */
cdev_del(&chrdevtemp.cdev);
/* 释放字符设号 */
unregister_chrdev_region(chrdevtemp.devid, CHRDEVTEMP_COUNT);
/* 摧毁设备 */
device_destroy(chrdevtemp.class, chrdevtemp.devid);
/* 摧毁类 */
class_destroy(chrdevtemp.class);
}
/*
* 模块注册入口
*/
module_init(chrdevtemp_init);
/*
* 模块注册出口
*/
module_exit(chrdevtemp_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("jiaozhu");
Makefile文件
KERNELDIR := /home/xfg/linux/imx_7ull/i2x_6ub/system_file/i2SOM-iMX-Linux
CURRENT_PATH := $(shell pwd)
obj-m := chrdevtemp.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
测试程序需就使用之前编写的hello2App.c文件
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
/*
*argc:应用程序参数个数
*argv[]:具体的参数内容,字符串形式
*./hello2App <filename> <1:2> 1表示读,2表示写
*./hello2App /dev/hello2 1 表示从驱动里面读数据
*./hello2App /dev/hello2 2 表示向驱动里面写数据
* */
int main(int argc, char *argv[])
{
int ret = 0;
int fd = 0;
char *filename;
char readbuf[100], writebuf[100];
static char usrdata[] = {"hell0 This is user data!"};
if(argc !=3) {
printf("Instruction usage error!!!\r\n");
printf("./helle2App <filename> <1:2> 1表示读,2表示写\r\n");
printf("./hello2App ./dev/hello2 1 \r\n");
return -1;
}
filename = argv[1];
fd = open(filename, O_RDWR);
if(fd < 0) {
}
if(atoi(argv[2]) ==1){
ret = read(fd, readbuf, 50);
if(ret <0) {
printf("read file %s failed!\r\n", filename);
} else {
printf("App read data:%s\r\n", readbuf);
}
}
if(atoi(argv[2]) == 2) {
memcpy(writebuf, usrdata, sizeof(usrdata));
ret = write(fd,writebuf, 50);
if(ret <0) {
printf("write file %s failed\r\n", filename);
} else {
}
}
ret =close(fd);
if(ret <0) {
printf("close file %s falied!\r\n", filename);
}
return 0;
}
五、测试
将驱动文件和应用文件进行编译
make
arm-linux-gnueabihf-gcc
将编译后的驱动文件可应用文件拷贝到开发板中,然后加载驱动,结果如下图所示:

将读取的信息和设备树文件中的信息对比,说明读取成功。
通过应用读取设备节点,测试设备节点是否加载成功,结果如下图所示:

可知通过自动创界设备节点成功。
六、问题
1.自动创建设备节点时出现如下错误

解决办法:此错误是因为我运行的内核版本和写驱动是用的内核版本不一致导致,只需要控制版本一致后从新烧写内核文件即可。
参考文献
Device Tree:http://www.wowotech.net/device_model/why-dt.html
Linux设备树语法详解:https://www.cnblogs.com/xiaojiang1025/p/6131381.html
linux设备树常用of操作函数:https://blog.csdn.net/wang_518/article/details/108923399
linux驱动之获取设备树信息的更多相关文章
- Linux 获取设备树源文件(DTS)里描述的资源
Linux 获取设备树源文件(DTS)里的资源 韩大卫@吉林师范大学 在linux使用platform_driver_register() 注册 platform_driver 时, 需要在 plat ...
- Linux 获取设备树源文件(DTS)里的资源【转】
本文转载自:http://blog.csdn.net/keleming1/article/details/51036000 http://www.cnblogs.com/dyllove98/archi ...
- Linux kernel 有关 spi 设备树参数解析
一.最近做了一个 spi 设备驱动从板级设备驱动升级到设备树设备驱动,这其中要了解 spi 设备树代码的解析. 二. 设备树配置如下: 503 &spi0 { 504 status = &qu ...
- I.MX6 linux eGalaxTouch 自动获取设备节点
I.MX6 linux eGalaxTouch 自动获取设备节点 \\\\\\\\\\\\\\-*- 目录 -*-///////////// | 一. 需求: | 二. /proc/bus/input ...
- Linux 获取设备树源文件(DTS)里描述的资源【转】
转自:http://www.linuxidc.com/Linux/2013-07/86839.htm 转自:http://blog.sina.com.cn/s/blog_636a55070101mce ...
- Linux 总线、设备、驱动模型 与 设备树
1.总线.设备.驱动模型 本着高内聚.低耦合的原则,Linux 把设备驱动模型分为了总线.设备和驱动三个实体,这三个实体在内核里的职责分别如下: 设备和驱动向总线进行注册,总线负责把设备和对应的驱动绑 ...
- ARM Linux 3.x的设备树(Device Tree)
http://blog.csdn.net/21cnbao/article/details/8457546 宋宝华 Barry Song <21cnbao@gmail.com> 1. ...
- ARM Linux 3.x的设备树(Device Tree)
1. ARM Device Tree起源 Linus Torvalds在2011年3月17日的ARM Linux邮件列表宣称“this whole ARM thing is a f*cking pai ...
- Linux驱动设计——字符设备驱动(一)
Linux字符设别驱动结构 cdev结构体 struct cdev { struct kobject kobj; struct module *owner; const struct file_ope ...
随机推荐
- .NET平台系列13 .NET5 统一平台
系列目录 [已更新最新开发文章,点击查看详细] 时机决定一切,对于 .NET5 也是如此.实际上微软.NET团队在开始开发 .NET Core 时,对 .NET Framework 的全面重写 ...
- Qt开发笔记:OpenSSL库介绍、windows上mingw32版本的OpenSSL编译模块化
前言 Windows上mingw32版本的openssl的编译是属于比较棘手的,OpenSSL本身不提供支持.. OpenSSL 介绍 OpenSSL是一个开放源代码的软件库包,应用程序可 ...
- 如何让Android 支持HEIF 图片解码和加载(免费的方法)
字节跳动火山引擎ImageX提供了一种能力,可以支持客户端android 直接解码HEIF 和HEIC图片,经过测试发现,可以免费使用: 一.阅前准备 HEIF图片格式是什么? 高效率图像格式(Hig ...
- Jmeter- 笔记8 - 分布式
分布式: 原因:在实际工作中,jmeter可能需要虚拟上千并发用户,而每台实际能虚拟的线程数时有限的,一般一台电脑小于2000个,1.5k - 2k可能就出现无法虚拟. 多台机器,一起虚拟并发用户数, ...
- 使用Auto TensorCore CodeGen优化Matmul
使用Auto TensorCore CodeGen优化Matmul 本文将演示如何使用TVM Auto TensorCore CodeGen在Volta / Turing GPU上编写高性能matmu ...
- 【NX二次开发】不健全的双击按钮。
为什么说不健全,是因为 双击按钮时会先运行单击事件,这个后面再解决.但是模仿某公司的图层操作工具是没有问题了,因为这个工具运行双击事件时本来就需要运行单击事件,不仔细看容易被唬住. 图层操作工具(双击 ...
- 【题解】覆盖问题 BZOJ1052 HAOI2007 二分
题目描述 某 人在山上种了N棵小树苗.冬天来了,温度急速下降,小树苗脆弱得不堪一击,于是树主人想用一些塑料薄膜把这些小树遮盖起来,经过一番长久的思考,他决定用 3个LL的正方形塑料薄膜将小树遮起来.我 ...
- 使用axios模拟表单提交
1.需求背景 最近在实验室写一个Spring前后端分离的项目,项目中使用Spring Security组件实现系统的认证和授权,当Security的认证模式设置为FormLogin时(如下代码),前端 ...
- Spring Boot 2.x基础教程:使用Redis的发布订阅功能
通过前面一篇集中式缓存的使用教程,我们已经了解了Redis的核心功能:作为K.V存储的高性能缓存. 接下来我们会分几篇来继续讲讲Redis的一些其他强大用法!如果你对此感兴趣,一定要关注收藏我哦! 发 ...
- canvas介绍和用途
canvas介绍和用途 canvas(画布)主要是位图 svg(矢量图) canvas标签,必须要写的3个属性 id width height 为什么不在style中设置width和height呢? ...