4.IMX6ULL LINUX驱动之字符设备框架(linux4内核)

411 阅读1分钟

一、之前的驱动框架缺点

register_chrdev函数使用很浪费了很多次设备号。而且需要手动指定,如果冲突了,我们也不知道。还要手动使用mknod指令。

二、解决上述缺点

需要找到能自动向linux内核申请设备号的函数。使用alloc_chrdev_region函数。释放使用unregister_chrdev_region函数

//fs/char_dev.c
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
            const char *name);
void unregister_chrdev_region(dev_t from, unsigned count);

注册设备号

字符设备注册

cdev结构体表示字符设备,使用cdev_init函数来初始化cdev。

struct cdev {
    struct kobject kobj;
    struct module *owner;
    const struct file_operations *ops;
    struct list_head list;
    dev_t dev;
    unsigned int count;
};
void cdev_init(struct cdev *, const struct file_operations *);

自动创建设备结点

udev机制的引入在linux2.6以后。它具有对/dev的添加与删除的用户空间行为。之前使用静态的传统方法,现在udev提供热插拔管理,可以在加载驱动的时候自动创建设备结点。busybox提供了udev的简化版本mdev。

在构建根文件系统的时候,在/etc/init.d/rcS中添加了。

echo /sbin/mdev > /proc/sys/kernel/hotplug

三、新框架驱动代码

改变了创建设备号

#include<linux/module.h>
#include<linux/init.h> 
#include<linux/fs.h>
#include <asm/uaccess.h>
#include<linux/io.h>
#include<linux/cdev.h>
/*
    load module : mknod /dev/led c 200 0
*/
#define LED_NAME  "led"
/*设备结构体*/
struct led_dev{
    dev_t      devid;       /*设备号*/
    int        major;       /*主设备号*/
    int        minor;       /*次设备号*/
    int        name;        /*设备名称*/
    struct cdev cdev;       /*cdev注册设备结构体*/
    struct class *class;     /*类结构体*/
    struct device *device;   /*设备*/
};
struct led_dev led;
/*
    fileoperation的实现
*/
static const struct file_operations led_fops = {
    .owner      = THIS_MODULE,
    .open       = led_open,
    .release    = led_release,
    .read       = led_read,
    .write      = led_write,
};
/*
    注册与注销设备
*/
static int __init led_init(void){  
    int ret = 0;
    printk("-----led_init-----\n");
    Led_Init();                             //LED初始化
​
    /*  1.注册设备号  */
    if( led.major ){                        //给定主设备号
        led.devid = MKDEV(led.major , 0);
        ret = register_chrdev_region(led.devid , 1 , LED_NAME);
    }else{
        ret = alloc_chrdev_region(&led.devid, 0, 1, LED_NAME);
        led.major = MAJOR(led.devid);
        led.minor = MINOR(led.devid);
    }
​
    if(ret < 0){
        printk("led chrdev region error!\r\n");
        return -1;
    }
    printk("led major = %d , minor = %d" ,led.major , led.minor );
    
    /*  2.字符设备注册    */
    led.cdev.owner = THIS_MODULE;
    cdev_init(&led.cdev , &led_fops);
    ret = cdev_add(&led.cdev, led.devid , 1);
​
    /*  3.自动创建设备结点  */
    led.class = class_create(THIS_MODULE , LED_NAME);
    if(IS_ERR(led.class))
        return PTR_ERR(led.class);
​
    led.device = device_create(led.class , NULL , led.devid , NULL , LED_NAME);
    if(IS_ERR(led.device))
        return PTR_ERR(led.device);
    return 0;
}
static void __exit led_exit(void){
    printk("-----Led_exit-----\n");
​
    /*删除字符设备*/
    cdev_del(&led.cdev);
    /*注销设备号*/
    unregister_chrdev_region(led.devid, 1);
    /*删除设备*/
    device_destroy(led.class , led.devid);
    /*删除类*/
    class_destroy(led.class);
}

四、测试驱动设备

1.加载驱动程序

2.控制台手动创建设备结点

mknod /dev/led c 248 0  #使用动态申请设备号,还是得先加载驱动程序得到设备号

3.开启应用程序。

./led_app /dev/led 1    //1是写写入数据,来开关Led灯

五、文件私有数据

在open接口中的file结构体中的private_data成员指向我们的设备结构体,就可以将其设置为设备的私有数据,在read,write中可以直接使用private_data变量就可以读取私有数据。

static int led_open(struct inode *inode, struct file *filp){
    printk("------led_open-------\r\n");
    filp->private_data = &led;
    return 0;
}
int led_release(struct inode *inode, struct file *filp){
    printk("------led_release--------\r\n");
    struct led_dev *led = (struct led_dev *dev)filp->private_data;
    return 0;
}

\