字符设备驱动

1.字符设备驱动框架

先来一张网上盗来的图,整个框架一目了然
这里写图片描述

2.驱动初始化

2.1分配cdev

cdev结构体,其中的module和file_operations都是在在cdev_init中初始化
struct cdev {
    struct kobject kobj;
    struct module *owner; /*通常为THIS_MODULE*/
    struct file_operations *ops; /*在cdev_init()这个函数里面与  cdev结构联系起来*/
    struct list_head list;
    dev_t dev; /*设备号*/
    unsigned int count;
 };

分配cdev主要就是分配一个cdev结构体和设备号,以下是代码示例:

/* 定义cdev*/
 struct cdev btn_cdev;
/* 申请设备号*/
  if(major){
      //静态
      dev_id = MKDEV(major, 0);
      register_chrdev_region(dev_id, 1, "button");
 } else {
     //动态
     alloc_chardev_region(&dev_id, 0, 1, "button");
     major = MAJOR(dev_id);
}

1). linux内核中,用dev_t描述设备号

typedef u_long dev_t;

高12位表示主设备号,地20位表示次设备号,可以使用下列宏将dev_t得到主次设备号

MAJOR(dev_t dev);      //主设备号                 
MINOR(dev_t dev);      //次设备号   
MKDEV(int major,int minor);//设备号

2). 申请设备号
静态:

int register_chrdev_region(dev_t first, unsigned int count, const char *name); 

first:要分配的设备编号范围的起始值
count:连续设备的编号的个数
name:和该设备编号范围关联的设备名称,他将出现在/proc/devices和 sysfs中。此函数成功返回0,失败返回负的错误码。
适用于已经知道主设备号的情况

动态:

int alloc_chrdev_region(dev_t *dev, unsigned int  firstminor, unsigned int count, const char *name) 

dev :申请到的设备编号
firstminor:要使用的第一个此设备编号,一般为0
count:是连续设备的编号的个数,一般为1;name同上
适用于不知道主设备号的情况

3). 在不用的时候需要释放掉设备号,对应的释放函数为:

unregister_chrdev_region(dev_t dev, unsigned int count); 
/*例:*/
unregister_chrdev_region(MKDEV(major, 0), 1); 

2.2初始化cdev

void cdev_init(struct cdev *, struct file_operations *);

cdev_init()函数用于初始化 cdev 的成员,并建立 cdev 和 file_operations 之间的连接。

/*常用设备操作集合*/
 static struct file_operations btn_fops = {
.owner = THIS_MODULE,
.open = button_open,
.release = button_close,
.read = button_read
};

2.3注册cdev

int cdev_add(struct cdev *, dev_t, unsigned);

向系统注册一个cdev

3.自动创建设备节点
包含头文件#include <linux/device.h>
以前是在驱动模块加载之后,调用mknod创建设备节点,这种方式基本已经被抛弃,现在使用以下的方法创建设备节点:

1).

struct class *class_create(struct module *owner, const char *name)
owner : 一般为 THIS_MODULE
name : 创建的class类名

对应是在/sys/class/下创建类目录

2 ).

 struct class_device *device_create(struct class   *cls,
                            struct class_device *parent,
                            dev_t               devt,
                            void        *drvdata,
                            const char          *fmt, ...)
struct class : class_create()返回值,必须在本函数调用之前先被创建
parent :父节点指针
devt :设备号,如果devt不为0,创建设备文件
drvdata :被添加到该设备回调的数据
fmt :设备名称

对应是在/dev/下创建设备节点
例子:
device_create(batman_class, NULL, MKDEV(tmp_major, 0), NULL, "batman-adv");

注意:在删除模块的时候要使用以下两个函数删除创建的文件

device_destroy(struct class *cls, dev_t devt);
class_destroy(struct class *cls);

注意两个函数的调用顺序

4.创建sysfs属性节点

int device_create_file(struct device *dev,  const struct device_attribute *attr)

注意:第一个参数为device型,不是cdev,这个参数一般使用device_create()的返回值
device_attribute : 使用DEVICE_ATTR(_name, _mode, _show, _store)初始化,注意name的形式

DEVICE_ATTR(_name, _mode, _show, _store)

_name:名称,也就是将在sysfs中生成的文件名称。
_mode:上述文件的访问权限,与普通文件相同,UGO的格式。只读0444,只写0222,或者读写都行的0666。
_show:显示函数,cat该文件时,此函数被调用。
_store:写函数,echo内容到该文件时,此函数被调用

代码实例:


/*初始化并注册cdev*/  
static void cdevdemo_setup_cdev(struct cdevdemo_dev *dev, int index)  
{  
    printk(KERN_NOTICE "======== cdevdemo_setup_cdev 1");     
    int err, devno = MKDEV(cdevdemo_major, index);  
    printk(KERN_NOTICE "======== cdevdemo_setup_cdev 2");  

    /*初始化一个字符设备,设备所支持的操作在cdevdemo_fops中*/     
    cdev_init(&dev->cdev, &cdevdemo_fops);  
    printk(KERN_NOTICE "======== cdevdemo_setup_cdev 3");     
    dev->cdev.owner = THIS_MODULE;  
    dev->cdev.ops = &cdevdemo_fops;  
    printk(KERN_NOTICE "======== cdevdemo_setup_cdev 4");     
    err = cdev_add(&dev->cdev, devno, 1);  
    printk(KERN_NOTICE "======== cdevdemo_setup_cdev 5");  
    if(err)  
    {  
        printk(KERN_NOTICE "Error %d add cdevdemo %d", err, index);   
    }  
}  

static ssize_t cdevrw_show(struct device *dev, struct device_attribute *attr, char *buf)
{
    sprintf(buf,"read,demo temp is %d/n",temp);
    return 0;
}

static ssize_t cdevrw_store(struct device *dev, struct device_attribute *attr,
                                   const char *buf, size_t size)
{
    sscanf(buf, "%d", &temp);

    printk(KERN_NOTICE "write,demo temp is %d/n",temp);
    return size;
}

static DEVICE_ATTR(cdevrw, (S_IRUGO | S_IWUSR | S_IWGRP),
                               cdevrw_show,
                               cdevrw_store);

static DEVICE_ATTR(cdevrw1, (S_IRUGO | S_IWUSR | S_IWGRP),cdevrw_show,cdevrw_store);
static DEVICE_ATTR(cdevrw2, (S_IRUGO | S_IWUSR | S_IWGRP),cdevrw_show,cdevrw_store);

static struct attribute *cdev_attrs[] = {
    &dev_attr_cdevrw.attr,
    &dev_attr_cdevrw1.attr,
    &dev_attr_cdevrw2.attr,
    NULL,
};

static const struct attribute_group cdev_attr_grp = {
    .attrs = cdev_attrs,
};

int cdevdemo_init(void)  
{  
    printk(KERN_NOTICE "======== cdevdemo_init ");    
    int ret;  
    dev_t devno = MKDEV(cdevdemo_major, 0);  

    struct device *devdemo;
    /*申请设备号,如果申请失败采用动态申请方式*/  
    if(cdevdemo_major)  
    {  
        printk(KERN_NOTICE "======== cdevdemo_init 1");  
        ret = register_chrdev_region(devno, 1, "cdevdemo");  
    }else  
    {  
        printk(KERN_NOTICE "======== cdevdemo_init 2");  
        ret = alloc_chrdev_region(&devno,0,1,"cdevdemo");  
        cdevdemo_major = MAJOR(devno);  
    }  
    if(ret < 0)  
    {  
        printk(KERN_NOTICE "======== cdevdemo_init 3");  
        return ret;  
    }  
    /*动态申请设备结构体内存*/  
    cdevdemo_devp = kmalloc(sizeof(struct cdevdemo_dev), GFP_KERNEL);  
    if(!cdevdemo_devp)  /*申请失败*/  
    {  
        ret = -ENOMEM;  
        printk(KERN_NOTICE "Error add cdevdemo");     
        goto fail_malloc;  
    }  

    memset(cdevdemo_devp,0,sizeof(struct cdevdemo_dev));  
    printk(KERN_NOTICE "======== cdevdemo_init 3");  
    cdevdemo_setup_cdev(cdevdemo_devp, 0);  

    /*下面两行是创建了一个总线类型,会在/sys/class下生成cdevdemo目录 
      这里的还有一个主要作用是执行device_create后会在/dev/下自动生成 
      cdevdemo设备节点。而如果不调用此函数,如果想通过设备节点访问设备 
      需要手动mknod来创建设备节点后再访问。*/  
    cdevdemo_class = class_create(THIS_MODULE, "cdevdemo");  
    devdemo = device_create(cdevdemo_class, NULL, MKDEV(cdevdemo_major, 0), NULL, "cdevdemo");  

    //device_create_file(devdemo,&dev_attr_cdevdemo);
    sysfs_create_group(&devdemo->kobj,&cdev_attr_grp);

    printk(KERN_NOTICE "======== cdevdemo_init 4");  
    return 0;  

    fail_malloc:  
        unregister_chrdev_region(devno,1);  
}  

void cdevdemo_exit(void)    /*模块卸载*/  
{  
    printk(KERN_NOTICE "End cdevdemo");   
    device_destroy(cdevdemo_class,MKDEV(cdevdemo_major,0));
    class_destroy(cdevdemo_class);

    cdev_del(&cdevdemo_devp->cdev);  /*注销cdev*/  
    kfree(cdevdemo_devp);       /*释放设备结构体内存*/  
    unregister_chrdev_region(MKDEV(cdevdemo_major,0),1);    //释放设备号  
}  

MODULE_LICENSE("Dual BSD/GPL");  
module_param(cdevdemo_major, int, S_IRUGO);  
module_init(cdevdemo_init);  
module_exit(cdevdemo_exit);  
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值