字符设备驱动

1.驱动中如何描述一个对象

  由结构体描述

struct cdev {
            struct kobject kobj;  //kobject  kset 文件系统 ---sysfs文件系统
            struct module *owner; //THIS_MODULE
            const struct file_operations *ops; //操作方法集
            struct list_head list; //链表,用来管理字符设备
            dev_t dev; //设备号  typedef unsigned long  u_long;  32位   ------主设备号 + 次设备号
            unsigned int count;  // //隶属于同一主设备号的次设备号的个数          12            20
        };

应用层和驱动层的逻辑关系如图

在驱动中通过函数指针实现对硬件的操作

    struct file_operations {
        struct module *owner;
        loff_t (*llseek) (struct file *, loff_t, int); //定位
        ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); //
        ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); //
        long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); //ioctl 
        long (*compat_ioctl) (struct file *, unsigned int, unsigned long);      //向下兼容linux内核
        int (*mmap) (struct file *, struct vm_area_struct *);     //显示屏 - 音视频驱动的比较多
        int (*open) (struct inode *, struct file *);     //打开     
        int (*release) (struct inode *, struct file *);  //关闭 
    };    
    const struct file_operations *ops; //操作方法集

字符设备的定义和初始化:

1.定义一个字符设备   struct cdev* cdev;

2.结构体的指针并没有实际分配内存,所以需要对cdev分配内存,在驱动中使用kzalloc或者直接调用cdev_alloc分配内存空间

3.初始化字符设备void cdev_init(struct cdev *cdev, const struct file_operations *fops),第二个参数可自己定义,结构体中的方法可不全部使用,使用.xxx的方式来使用

 

设备号的获取:

  在linux中设备号是唯一用来区分设备是干什么的,相当于设备的身份证,设备好号分为主设备号和次设备号,主设备号表示一类设备,次设备号表示某个设备

  在驱动中使用dev_t dev  获取主设备号使用 MAJOR(dev)   获取次设备号MINOR(dev)  制作设备号使用MKDEV(ma,mi)

设备号的动态注册(系统分配) 

  ---------写代码的时候,以框架为主题,然后框架当中你的接口需要什么类型,你就给他什么类型。

  有注册一定要有注销

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name)
        函数的功能;动态获取设备号
        参数:
            @dev : 设备号:函数返回之后会将设备号保存到dev中
            @baseminor :最小的次设备号的基地址 0
            @count  :设备的个数 3  
            @name    :自己填充,最好见名知意
        返回值:成功返回0
            失败返回错误码 

设备号的静态注册(自己指定)

例子

静态注册设备号: 
        int major = 249;
        MKDEV(major,0);
        int register_chrdev_region(dev_t from, unsigned count, const char *name)
        函数的功能;静态获取设备号
        参数:
            @from : 指定的主设备号:
            @count  : 设备的个数 3  
            @name    :自己填充,最好见名知意
        返回值:成功返回0
            失败返回错误码 

 

设备号的注销:

void unregister_chrdev_region(dev_t from, unsigned count)
        功能:注销设备号
        参数:
        * @from:  被注销的第一个设备值,就是你指定的主设备号的值
        * @count: 设备数量

 

添加一个字符设备到系统当中去:

添加进系统是我们最初的梦想,现在想来cdev_add需要  cdev我们定义了cdev--》分配内存--》cdev初始化  ,第二个参数需要dev_t  我们动态/静态注册设备号

int cdev_add(struct cdev *p, dev_t dev, unsigned count)
    函数的功能;添加一个字符设备到系统当中去
    参数:
        @p         :字符设备 
        @dev     :设备号
        @count  :个数
    返回值:失败返回负值 
            成功返回0

 

贴上全部的代码

#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/fs.h>
#include <linux/cdev.h>

//定义一个字符设备对象
struct cdev *cdev; 
dev_t dev;
unsigned int baseminor = 0;
unsigned int count = 1;
const char * name = "demochar";



//向上层提供的接口
//向下层操作硬件----在接口里去操作硬件

const struct file_operations fops = {

};

static int __init demo_init(void)
{
    int retval;
    printk("----%s---%d.\n",__func__,__LINE__);

    //1、为字符设备分配空间
    cdev = cdev_alloc();
    if(cdev == NULL){
        printk("cdev_alloc faild.\n");
        return -ENOMEM;
    }

    //2、初始化字符设备
    cdev_init(cdev,&fops);

    //3、动态申请设备号
    retval = alloc_chrdev_region(&dev, baseminor, count, name);
    if(retval){
        printk("alloc_chrdev_region faild.\n");
        goto err0;
    }
    printk("major :%d.\n",MAJOR(dev));

    //4、添加到内核
    retval = cdev_add(cdev, dev, count);
    if(retval){
        printk("cdev_add failed.\n");
        goto err1;
    }
    
    //5、硬件初始化

    
    return 0;

err1:
    unregister_chrdev_region(dev, count);
err0:
    return retval;
    
}

static void __exit demo_exit(void)
{
    printk("----%s---%d.\n",__func__,__LINE__);

    cdev_del(cdev);
    unregister_chrdev_region(dev, count);

}

//入口函数
module_init(demo_init);
//出口函数
module_exit(demo_exit);
//GPL许可协议
MODULE_LICENSE("GPL");

 

 

重点来了  register_chrdev

我们上面说的注册流程有点啰嗦,聪明的人,将其封装成一个接口供我们使用,但是我们知其然还要知道其所以然否则以后出错都不知道怎么debug

register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)	
		功能: 创建并注册一个字符设备
		参数: 
			major :主设备号
				major == 0;      动态分配 
				0 < major < 254 ;静态分配
			name  :设备的名字
			fops  :操作方法集
		返回值:
				 * If @major == 0 返回主设备号major 
				 * If @major > 0 成功返回0
				失败返回错误码

  

13、释放设备号:
static inline void unregister_chrdev(unsigned int major, const char *name)
@major :主设备号
@name :名字
```

 

太简短了。。。

#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/fs.h>
#include <linux/cdev.h>

//定义一个字符设备对象
struct cdev *cdev; 

unsigned int major = 0;

const char * name = "demochar";

//向上层提供的接口
//向下层操作硬件----在接口里去操作硬件

const struct file_operations fops = {
    
};

static int __init demo_init(void)
{
    printk("----%s---%d.\n",__func__,__LINE__);


    major = register_chrdev(0, name, &fops);
    if(major <= 0){
        printk("register_chrdev failed.\n");
        return major;
    }
    printk("major :%d.\n",major);
    
    //5、硬件初始化
    
    return 0;

    
}

static void __exit demo_exit(void)
{
    printk("----%s---%d.\n",__func__,__LINE__);

    unregister_chrdev(major, name);
}

//入口函数
module_init(demo_init);
//出口函数
module_exit(demo_exit);
//GPL许可协议
MODULE_LICENSE("GPL");

 

posted @ 2019-03-14 20:59  竹引  阅读(215)  评论(0编辑  收藏  举报