上一篇文章学习了字符设备的注册,操作过的小伙伴都知道上一篇文章中测试驱动时是通过手动创建设备节点的,现在开始学习怎么自动挂载设备节点和设备树信息的获取,这篇文章中的源码将会是我以后编写字符驱动的模板。

一、准备材料

开发环境: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

版权声明:本文为jzcn原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/jzcn/p/15016959.html