[读书笔记]字符设备驱动程序(第三章)

一、综述

本章开始,我们就要学会写一个最基本的字符设备驱动了,麻雀虽小五脏俱全。这是本章的重点,首先要弄明白一些基本的数据结构等相关知识,在去尝试编写驱动。

知识点

1.主设备号和次设备号
a.
一个主设备 对应 一个驱动程序
b.
次设备号 确定 具体的设备
c.
dev_t(在linux/types.h中)保存设备编号,
前12位 - 主设备号
后20位 - 次设备号
MAJOR(dev_t dev):获取主设备号
MANOR(dev_t dev) : 获得次设备号
MKDEV(int major,int minor) : 主设备号和次设备号转换成dev_t
d.
分配和释放设备编号
静态分配: int register_chrdev_region(dev_t first,unsigned int count ,char *name);
first:起始编号,通常为0
count: 连续请求的设备编号个数
name:设备名称,将会出现在 /proc/devices和sysfs中
成功:返回0
失败:返回负数

动态分配
int alloc_chrdev_region(dev_t *dev,unsigned int firstminor,unsigned int count,char *name)
dev:仅用于输出的参数,在成功完成调用后将班车已分配范围的第一个编号
firstminor:第一个次设备号,通常为0
count和那么跟上面的一样
e.释放函数
释放函数:void unregister_chrdev_region(dev_t first,unsigned int count)

2.字符设备的注册
方式一:注册独立的cdev结构
struct cdev *my_cdev = cdev_alloc();
my_cdev->ops = &my_ops;
方式2:将cdev内嵌到自己的设备结构中
调用以下函数来初始化
void cdev_init(strcuct cdev *cdev,struct file_operations *fops);
指定cdev的所有者和操作函数
cdev.ower = THIS_MODULE;
cdev.ops = &my_ops;
把字符设备添加到内核
int cdev_add(struct cdev *dev,dev_t num,unsigned int count);
在适当的地方删除
void cdev_del(struct cdev *dev);
3.三个重要的数据结构
struct file_operations:保存操作字符驱动程序的方法
strcut file:表示打开一个文件
strucr inode:表示一个磁盘上的文件
大多数设备驱动程序都会用到这三个数据结构
其他知识点
#include
container_of(pointer,type,field)
一个方便使用的宏,可用于从包含在某个数据结构中的指针获得结构体本身的指针。
#include
该头文件声明了内核代码和用户空间之间移动数据的函数
unsigned long copy(void *to,const void *from,unsigned long count);
unsigned long copy(void *to,const void *from,unsigned long count);
用户空间和内核空间拷贝数据

实践环节--在安卓系统中编写字符驱动

编写一个名字为hello的字符驱动设备(参照书本和罗升阳博客),自己跟着一遍遍敲打出来,还是会遇到很多编译错误,也学习到很多东西,自己动手,丰衣足食,理解也更深!
学习的过程中,大家别跟着代码从头敲到尾,要按照思路来写,比如我先定义一些需要的变量和方法,然后先写一些空方法,把框架搭好,module_init(),module_exit()等相关方法
再去传统操作设备的方法,接着再去写devfs文件系统的方法
有思路的去写!!!
有些东西已经抛弃了
init_MUTEX(),在内核2.6版本就抛弃了,取而代之的是sema_init();

具体实现

kernel-3.18/drivers/misc/mediatek/创建hello目录
kernel-3.18/drivers/misc/mediatek/hello/
--hello.h
--hello.c
--Kconfig
--Makefile

hello.h

#include  
#include 
//节点名称
#define HELLO_DEVICE_NODE_NAME "hello"

//定义hello_dev结构体
struct hello_dev {
    int val;//变量--模拟寄存器
    struct semaphore sem;//信号量 互斥访问 防止竞态
    struct cdev dev;//字符设备
};

hello.c

引入头文件,定义相关的方法

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include "hello.h"

/*主设备号 和 从设备号 变量*/
static int hello_major = 0;
static int hello_minor = 0;

/*设备类别和设备变量*/
static struct class *hello_class = NULL;
static struct hello_dev* hello_dev = NULL;

/*传统的设备文件操作方法*/
static int hello_open(struct inode *inode,struct file *filp);
static int hello_release(struct inode *inode,struct file *filp);
static ssize_t hello_read(struct file *filp,char __user *buf,
size_t count,loff_t *f_ops);
static ssize_t hello_write(struct file *filp,const char __user *buf,size_t count,loff_t *f_ops);

/*设备文件操作方法表*/
static struct file_operations hello_fops = {
    .owner = THIS_MODULE,
    .open = hello_open,
    .release = hello_release,
    .write = hello_write,
    
};
/*访问设置属性方法*/
static ssize_t hello_val_show(struct device *dev,struct device_attribute *attr,char *buf);
static ssize_t hello_val_store(struct device* dev, struct device_attribute* attr, const char* buf, size_t count);

/*定义设备属性*/
static DEVICE_ATTR(val,S_IRUGO | S_IWUSR,hello_val_show,hello_val_store);

传统的设备文件操作方法
定义传统的设备文件访问方法,主要是定义hello_open、hello_release、hello_read和hello_write这四个打开、释放、读和写设备文件的方法:

/*打开设备方法*/
static int hello_open(struct inode *inode,struct file *filp) {
    struct hello_dev *dev;
    dev = container_of(inode->i_cdev,struct hello_dev,dev);
    filp->private_data = dev;

    return 0;
}
/*是否设备方法 这里空实现*/
static int hello_release(struct inode *inode,struct file * filp) {

    return 0;

}
/*读取寄存器设备 val的值*/
static ssize_t hello_read(struct file *filp,char __user *buf,
size_t count,loff_t *f_ops) {
    ssize_t err = 0;
    struct hello_dev *dev = filp->private_data;

    /*同步访问*/
    if(down_interruptible(&(dev->sem)));
        return -ERESTARTSYS;
    
    if(count < sizeof(dev->val)){
        goto out;
    }
    /*将寄存器val的值拷贝到用户提供的缓存区*/
    if(copy_to_user(buf,&(dev->val),sizeof(dev->val))){
        err = -EFAULT;
        goto out;
    }

    out:
    up(&(dev->sem));
    return err;
}

/*写设备寄存器val的值*/
    return 0;

}
/*读取寄存器设备 val的值*/
static ssize_t hello_read(struct file *filp,char __user *buf,
size_t count,loff_t *f_ops) {
    ssize_t err = 0;
    struct hello_dev *dev = filp->private_data;

    /*同步访问*/
    if(down_interruptible(&(dev->sem)));
        return -ERESTARTSYS;
    
    if(count < sizeof(dev->val)){
        goto out;
    }
    /*将寄存器val的值拷贝到用户提供的缓存区*/
    if(copy_to_user(buf,&(dev->val),sizeof(dev->val))){
        err = -EFAULT;
        goto out;
    }

    out:
    up(&(dev->sem));
    return err;
}

/*写设备寄存器val的值*/
static ssize_t hello_write(struct file *filp,const char __user *buf,
size_t count,loff_t * f_ops){
    ssize_t err = 0;
    struct hello_dev *dev = filp->private_data;

    /*同步访问*/
    if(down_interruptible(&(dev->sem))){
        return -ERESTARTSYS;
    }

    if(count != sizeof(dev->val)) {
        goto out;
    }
    /*将用户提供的缓存区的值写到设备寄存器中去*/
    if(copy_from_user(&(dev->val),buf,count)){
        err = EFAULT;
        goto out;
    }
    
    err = sizeof(dev->val);//成功写入的数据大小

out:
    up(&(dev->sem));
    return err;

}
/*
字符设备注册和初始化的方式
方式2:将cdev内嵌到自己的设备结构中
调用以下函数来初始化
void cdev_init(strcuct cdev *cdev,struct file_operations *fops);
指定cdev的所有者和操作函数
cdev.ower = THIS_MODULE;
cdev.ops = &my_ops;
把字符设备添加到内核
} 
/*写设备寄存器val的值*/
static ssize_t hello_write(struct file *filp,const char __user *buf,
size_t count,loff_t * f_ops){
    ssize_t err = 0;
    struct hello_dev *dev = filp->private_data;

    /*同步访问*/
    if(down_interruptible(&(dev->sem))){
        return -ERESTARTSYS;
    }

    if(count != sizeof(dev->val)) {
        goto out;
    }
    /*将用户提供的缓存区的值写到设备寄存器中去*/
    if(copy_from_user(&(dev->val),buf,count)){
        err = EFAULT;
        goto out;
    }
    
    err = sizeof(dev->val);//成功写入的数据大小

out:
    up(&(dev->sem));
    return err;

}

devfs文件系统访问方法
定义通过devfs文件系统访问方法,这里把设备的寄存器val看成是设备的一个属性,通过读写这个属性来对设备进行访问,主要是实现hello_val_show和hello_val_store两个方法,同时定义了两个内部使用的访问val值的方法__hello_get_val和__hello_set_val

//---------------------------方式2----------------------------
/*读取寄存器val的值到缓存区buf中 内部使用*/
static ssize_t __hello_get_val(struct hello_dev *dev,char *buf) {
    int val = 0;

    /*同步访问*/
    if(down_interruptible(&(dev->sem))){
        return -ERESTARTSYS;
    }

    val = dev->val;
    up(&(dev->sem));

    return snprintf(buf,PAGE_SIZE,"%d\n",val);
}
/*把缓冲区buf的值写到设备寄存器val中去 内部使用*/
static ssize_t __hello_set_val(struct hello_dev *dev,const char *buf,size_t count){
    int val = 0;
    //把字符串转换成数字
    val = simple_strtol(buf,NULL,10);
    //同步访问
    if(down_interruptible(&(dev->sem))){
        return -ERESTARTSYS;
    }
    
    dev->val = val;
    up(&(dev->sem));

    return count;

}

/*读取设备属性 val*/
static ssize_t hello_val_show(struct device *dev,struct device_attribute *attr,char *buf){
    struct hello_dev *hdev = (struct hello_dev*)dev_get_drvdata(dev);
    
    return __hello_get_val(hdev,buf);
}
/*写设备属性val*/
static ssize_t hello_val_store(struct device* dev, struct device_attribute* attr, const char* buf, size_t count){
    struct hello_dev *hdev = (struct hello_dev*)dev_get_drvdata(dev);
    
    return __hello_set_val(hdev,buf,count);
}

定义模块加载和卸载方法,这里只要是执行设备注册和初始化操作

/*初始化设备*/
static int hello_setup_dev(struct hello_dev *dev){
    int err;
    dev_t devno = MKDEV(hello_major,hello_minor);

    //先清空dev,书上没有调用
    memset(dev,0,sizeof(struct hello_dev));
    //调用cdev_init初始化,接着绑定操作设备的方法
    cdev_init(&(dev->dev),&hello_fops);
    dev->dev.owner = THIS_MODULE;
    dev->dev.ops = &hello_fops;
    //注册字符设备
    err = cdev_add(&(dev->dev),devno,1);
    if(err)
        return err;
    //初始化信号量和寄存器的值
    sema_init(&(dev->sem),1);
    dev->val = 0;
    
    return 0;
}

/*模块加载方法*/
static int hello_init(void){
    int err = -1;
    dev_t dev = 0;
    struct device* temp = NULL;

    printk("[%s] is init\n",__func__);
    
    //动态分配主设备号和从设备好
    err = alloc_chrdev_region(&dev,0,1,HELLO_DEVICE_NODE_NAME);
    if(err < 0){
        printk("faile to alloc_chrdev_region()\n");
        goto fail;
    }
    hello_major = MAJOR(dev);
    hello_minor = MINOR(dev);

    //分配内存
    hello_dev = kmalloc(sizeof(struct hello_dev),GFP_KERNEL);
    if(!hello_dev){
        err = -ENOMEM;
        printk("faile to alooc hello_dev\n");
        goto unregister;
    }
    
    //初始化设备
    err = hello_setup_dev(hello_dev);
    if(err){
        printk("failed to set up hello device\n");
        goto cleanup;
    }

    //在 /sys/class目录下创建设备类别目录hello
    hello_class = class_create(THIS_MODULE,"hello");
    if(IS_ERR(hello_class)){
        err = -1;
        printk(KERN_ALERT"Failed to create hello class.\n");
        goto destroy_cdev;
    }
    //在dev目录 和 sys/class/hello目录下分别创建设备文件hello
    temp = device_create(hello_class,NULL,dev,NULL,"hello");
    if(IS_ERR(temp)){
        err = -2;
        printk(KERN_ALERT"Failed to create hello device.");
        goto destroy_class;   
    }

    //在 sys/class/hello/hello目录下创建属性文件val
    
    err = device_create_file(temp,&dev_attr_val);
    if(err < 0){
        printk(KERN_ALERT"Failed to create attribute val.");
        goto destroy_device;
    }
    
    dev_set_drvdata(temp,hello_dev);

    printk("success to init hello device\n");
    return 0;

destroy_device:
    device_destroy(hello_class, dev); 
destroy_class:
    class_destroy(hello_class);
destroy_cdev:
    cdev_del(&(hello_dev->dev));
cleanup:
    kfree(hello_dev);
unregister:
    unregister_chrdev_region(MKDEV(hello_major,hello_minor),1);
fail:
    return err;
     
}

/*模块卸载方法*/
static void hello_exit(void){
    dev_t devno = MKDEV(hello_major,hello_minor);

    printk("destroy hello device \n");
    
    //销毁设备类别和设备
    if(hello_class){
        device_destroy(hello_class,MKDEV(hello_major,hello_minor));
        class_destroy(hello_class);
    }

    //删除字符设备和释放设备内存
    if(hello_dev){
        cdev_del(&(hello_dev->dev));
        kfree(hello_dev);
    }

    //释放设备号
    unregister_chrdev_region(devno,1);
}

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("First Andorid Driver");

module_init(hello_init);
module_exit(hello_exit);

Kconfig

config HELLO        
    tristate "First Android Driver"
    default n
    help
    This is my first android driver.

Makefile

obj-y += hello.o

接着
kernel-3.18/drivers/misc/mediatek/Kconfig添加一句
source "drivers/misc/mediatek/hello/Kconfig"
kernel-3.18/drivers/misc/mediatek/Makefile添加一句
obj-y += hello/

最后 使用编译命令 ./mk bootimage
把生成的bootimage刷机即可


[读书笔记]字符设备驱动程序(第三章)_第1张图片
out

结果

进入到dev目录,可以看到hello设备文件:


[读书笔记]字符设备驱动程序(第三章)_第2张图片
adb

进入到sys/class目录,可以看到hello目录,hello文件,
进入到下一层hello目录,可以看到val文件:


[读书笔记]字符设备驱动程序(第三章)_第3张图片
adb

重启问题

[读书笔记]字符设备驱动程序(第三章)_第4张图片
log
static int hello_init(void){
    int err = -1; 
    dev_t dev = 0;
    
    printk("[%s] is init\n",__func__);
    
    //动态分配主设备号和从设备好
    err = alloc_chrdev_region(dev,0,1,HELLO_DEVICE_NODE_NAME); 
//省略代码
}

问题出在alloc_chrdev_region()这里,原来发现第一个参数dev需要的是地址,导致了重启
修改:alloc_chrdev_region(&dev,0,1,HELLO_DEVICE_NODE_NAME);

你可能感兴趣的:([读书笔记]字符设备驱动程序(第三章))