首页 > 其他 > 详细

字符设备驱动

时间:2020-12-29 11:14:46      阅读:23      评论:0      收藏:0      [点我收藏+]

 

一、早期的注册方式

  register_chrdev 函数族 + 创建设备类、文件的函数, 这种方式在 2.4版本的内核中使用,其优点是简单,缺点是没办法指定次设备号

  registe_chrdev 函数族

 

int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)
/*
*函数作用: 为字符设备注册一个主号码
*参数1: 尝试指定的主设备号,传入0则动态分配
*参数2: 设备的标识名称
*参数3: 与此设备关联的文件操作
*/

int unregister_chrdev(const char* name, const struct file_operations *fops)
/*
*函数作用: 注销设备号
*参数1:  设备标识名
*参数2:  与此设备关联的文件操作
*/

 

 

 

二、cdev注册方式

  2.6版本以后的内核中使用这种方式来注册设备驱动,优点是用户可以自行设置主次设备号,缺点是这种注册方式相比于早期的更加复杂,函数更多

  使用一个结构体 cdev 来描述一个设备,结构体包含dev_t 类型的设备号和 与设备关联的文件操作集合 file_operations 结构体,file_operations和老版本相同

 

  设备号: 主设备号表示一个特定的驱动程序,次设备号表示使用该驱动程序的不同设备

  32位无符号整型,12位用来表示主设备号,20位用来表示次设备号

  例如:LED驱动对应一个主设备号,两个想要单独控制的LED设备对应次设备号1和2

  

struct cdev {
    struct kobject kobj;//内核对象,由内核管理
    struct module *owner;
    const struct file_operations *ops;//字符设备驱动方法结构体,需自己填充
    struct list_head list;
    dev_t dev;//设备号一般有自己指定和动态申请两种方式
    unsigned int count;
};

主从设备号在内核中有一个u32类型的变量保存

<include/linux/types.h>
---------------------------------
typedef __u32 __kernel_dev_t;
typedef __kernel_dev_t     dev_t;

 

cdev编写字符驱动框架

技术分享图片

 

 

使用到的系统函数

 

//1. 注册设备号(静态, 用户指定)
int register_chrdev_region(dev_t from, unsigned int count, conster char* name)
/*
* from: 要分配的设备编号范围的初始值(主设备号,次设备号常设置为0)
* count: 连续编号范围
* name: 编号相关联的设备名
* 返回值:成功返回0
*/ //2. 注册设备号(动态) int alloc_chrdev_region(dev_t* dev, unsigned int firstminor, unsigned int count, char * name) /* * dev:调用该函数申请下来的设备号 * firstminor: 次设备号的起始,请求使用的第一个次设备号 * count: 申请次设备号的总数 * name: 设备名 *分配失败返回0 */
可以在驱动程序中通过MAJOR和MINOR 宏,把主次设备号打印出来,不然mknod的时候, 无法知道系统自动分配的设备号

MAKDEV(major, minor); //将主设备号和此设备号转换成一个主次设备号
MAJOR(devid);      //获取主设备号
MINOR(devid);      //获取次设备号
//3. 初始化cdev cdev_init(struct cdev* cdev, const struct file_operations* fops) /* *cdev cdev结构体类型指针 *fops :与此设备关联的文件操作 */
//4. 注册cdev cdev_add(struct cdev* p, dev_t dev, unsigned count) /* *p: cdev结构体 *dev: 设备号
*count: 次设备的个数
*/ //5. 删除cdev cdev_del(struct cdev *p) //6. 注销设备号 void unregister_chrdev_region(dev_t from, unsigned int count) /* *from: 第一个设备号 *count:申请的设备数量 */

 

 

这里我们就静态注册以及动态注册单独两个申请函数做分析

静态注册:

#define LED_MAJOR 520
#define LED_MINOR 0
static dev_t devno;
struct cdev cdev;

struct file_operations fops = {
    .owner = THIS_MODULE,
    .open = led_open
};

int __init led_init(void)
{
    //主次设备号转换成设备号
     devno = MKDEV(LED_MAJOR,LED_MINOR);
     //申请设备号
    int ret = register_chrdev_region(devno, 0, "led");
    if(ret < 0){
        printk("result devno error\n");
        return -1;
    }
    
    //初始化cdev结构体
    cdev_init(devno, &fops);
    cdev.owner = THIS_MODULE;
    cdev.ops = &fops
    
    //注册字符设备
    cdev_add(&cdev, devno, 1);
    
    return 0;         
}   

void __exit led_exit(void)
{
    cdev_del(&cdev);
    unregister_chrdev_region(devno, 1);
}

 

动态注册:

static dev_t devno;
struct cdev cdev;

struct file_operations fops = {
    .owner = THIS_MODULE,
    .open = led_open
};

int __init led_init(void)
{
     //申请设备号
    int ret = alloc_chrdev_region(&devno, 0, 1, "led");
printk("Kernel info : LED major number is %d\n", MAJOR(devno));
printk("Kernel info : LED minor number is %d\n", MINOR(devno));
if(ret < 0){ printk("result devno error\n"); return -1; } //初始化cdev结构体 cdev_init(devno, &fops); cdev.owner = THIS_MODULE; cdev.ops = &fops //注册字符设备 cdev_add(&cdev, devno, 1); return 0; } void __exit led_exit(void) { cdev_del(&cdev); unregister_chrdev_region(devno, 1); }

 

调用系统API,生成设备节点

class_create   class_device_create

用动态申请设备号的做示例

static dev_t devno;
struct cdev cdev;

struct file_operations fops = {
    .owner = THIS_MODULE,
    .open = led_open
};

int __init led_init(void)
{
     //申请设备号
    int ret = alloc_chrdev_region(&devno, 0, 1, "led");
    printk("Kernel info : LED major number is %d\n", MAJOR(devno));
    printk("Kernel info : LED minor number is %d\n", MINOR(devno));
    if(ret < 0){
        printk("result devno error\n");
        return -1;
    }
    
    //初始化cdev结构体
    cdev_init(devno, &fops);
    cdev.owner = THIS_MODULE;
    cdev.ops = &fops
    
    //注册字符设备
    cdev_add(&cdev, devno, 1);
    
    struct class *led_class  = class_create(THIS_MODULE, "led");
    class_device_create(led_class, NULL, devno, NULL, "led0");

    return 0;         
}   

void __exit led_exit(void)
{
    class_device_destroy(led_class, devno);
    cdev_del(&cdev);
    unregister_chrdev_region(devno, 1);
}

 

三、ioremap 映射物理地址

 

#define GPIO_CON_REG  0x12345
#define GPIO_DATA_REG 0x23456

unsigned long *led_con;
unsigned long *led_data;

static dev_t devno;
struct cdev cdev;

struct file_operations fops = {
    .owner = THIS_MODULE,
    .open = led_open
};

int __init led_init(void)
{
     //申请设备号
    int ret = alloc_chrdev_region(&devno, 0, 1, "led");
    printk("Kernel info : LED major number is %d\n", MAJOR(devno));
    printk("Kernel info : LED minor number is %d\n", MINOR(devno));
    if(ret < 0){
        printk("result devno error\n");
        return -1;
    }
    
    //初始化cdev结构体
    cdev_init(devno, &fops);
    cdev.owner = THIS_MODULE;
    cdev.ops = &fops
    
    //注册字符设备
    cdev_add(&cdev, devno, 1);
    
    struct class *led_class  = class_create(THIS_MODULE, "led");
    class_device_create(led_class, NULL, devno, NULL, "led0");

    led_con = ioremap(GPIO_CON_REG, 8);
    led_data = ioremap(GPIO_DATA_REG, 8);

    return 0;         
}   

void __exit led_exit(void)
{
    iounremap(led_con);
    iounremap(led_data);
    class_device_destroy(led_class, devno);
    cdev_del(&cdev);
    unregister_chrdev_region(devno, 1);
}

 

字符设备驱动

原文:https://www.cnblogs.com/chenxinblog/p/14200268.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!