Linux字符设备驱动框架

linux字符设备驱动编写内容:
(1)分配主设备号
(2)填充file_operations
(3)主设备号和file_operations关联
(4)创建一个类
(5)在该类下创建一个设备
(6)设置驱动入口函数和出口函数

一、字符驱动重要结构体

1.1.字符设备结构体
include/linux/cdev.h
struct cdev {
struct kobject kobj;               //kobject设备模型基础
struct module *owner;
const struct file_operations *ops;//硬件的相关操作函数
struct list_head list;
dev_t dev;                        //字符设备起始设备号
unsigned int count;               //次设备范围号大小
};
1.2.设备操作函数集
include/linux/fs.h
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 *);//设备写函数
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);//设备poll函数
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);//设备控制函数
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);//设备映射函数
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, struct dentry *, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **);
};
需要实现设备的相应功能,只要给上面的函数指针赋值即可
1.3.设备号分配记录结构体
fs/char_dev.c
static struct char_device_struct {
struct char_device_struct *next; //将主设备号相同次设备号不同的字符设备组成链表
unsigned int major;                      //主设备号
unsigned int baseminor;              //次设备号起始号
int minorct;                                      //次设备个数
char name[64];                               //设备名
struct cdev *cdev;                          //字符设备
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];
该指针数组一共255项;每项保存了一个主设备号的使用情况,相同主设备号的设备采用链表方式管理
1.4.设备号和设备关联结构体
当从chrdevs数组中选出一个可用的设备号后,进行注册的实质:将该设备号和cdev结构体相关联,然后放到一个数组中去,这个数组就是cdev_map
drivers/base/map.c
struct kobj_map {
struct probe {
struct probe *next;   //将主设备号相同次设备号不同的字符设备组成链表
dev_t dev;                  //设备号
unsigned long range;  
struct module *owner;
kobj_probe_t *get;
int (*lock)(dev_t, void *);
void *data;                //指向cdev
} *probes[255];
struct mutex *lock;
};
fs/char_dev.c
static struct kobj_map *cdev_map;
二、驱动实现函数
2.1申请设备号fs/char_dev.c
static struct char_device_struct * __register_chrdev_region(unsigned int major, unsigned int baseminor,int minorct, const char *name)
参数:major主设备号;为0就由系统自动分配
参数:baseminor起始次设备号
参数:minorct次设备号的个数
参数:name设备的名字/proc/devices可以看见
返回值:返回一个可用的char_device_struct结构体
2.2填充file_operations:cdev_init
cdev = cdev_alloc();
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
参数:cdev设备
参数:fops函数操作集
2.3设备号和设备绑定
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
参数:p指向新构建的字符设备
参数:dev申请的设备号
参数:count次设备号范围
返回值:成功返回1否则返回错误
2.4创建设备类
include/linux/device.h
#define class_create(owner, name)
参数:owner设备的持有者
参数:name类名字
返回值:新建的类
2.5类下创建设备
struct device *device_create(struct class *class, struct device *parent,dev_t devt, void *drvdata, const char *fmt, ...)
参数:class类名
参数:parent设备父对象
参数:devt设备号
参数:drvdata
参数:fmt
返回值:返回一个struct device
调用class_create和*device_create函数将使得linux文件系统/dev下自动产生新的设备节点
2.6驱动入口和出口函数
当一个函数使用module_init(函数);那么该驱动加载时它将被首先调用
当一个函数使用module_exit(函数);那么该驱动卸载时它将被最后调用
三、字符驱动实例
/*内核linux-2.6.30.4  TQ2440*/
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
static struct class  *firstdrv_class;
static struct device *firstdrv_class_dev;
volatile unsigned long *gpbcon = NULL;
volatile unsigned long *gpbdat = NULL;
//设备驱动对应的open函数
static int first_drv_open(struct inode *inode, struct file *file)
{
/*LED1,LED2,LED4对应GPB5、GPB6、GPB7、GPB8 配置GPB5,6,7,8为输出 */
*gpbcon &= ~((0x3<<(5*2)) | (0x3<<(6*2)) | (0x3<<(7*2)) | (0x3<<(8*2)));
*gpbcon |= ((0x1<<(5*2)) | (0x1<<(6*2)) | (0x1<<(7*2)) | (0x1<<(8*2)));
return 0;
}
//设备驱动对应的read函数
static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
int val;
copy_from_user(&val, buf, count);
if (val == 1)
 *gpbdat &= ~((1<<5) | (1<<6) | (1<<7) | (1<<8));//点亮LED
else
 *gpbdat |= (1<<5) | (1<<6) | (1<<7) | (1<<8);//熄灭LED
return 0;
}
//填充file_operations 
static struct file_operations first_drv_fops = {
    .owner  =   THIS_MODULE,    
    .open   =   first_drv_open,     
    .write  = first_drv_write,  
};

int major;
static int first_drv_init(void)
{
major = register_chrdev(250, "first_drv", &first_drv_fops); //内部调用了__register_chrdev_region;cdev_alloc;cdev_add
firstdrv_class = class_create(THIS_MODULE, "firstdrv");
firstdrv_class_dev = device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xyz"); /* /dev/xyz */
gpbcon = (volatile unsigned long *)ioremap(0x56000010, 16);
gpbdat = gpbcon + 1;
return 0;
}
static void first_drv_exit(void)
{
unregister_chrdev(major, "first_drv"); 
device_unregister(firstdrv_class_dev);
class_destroy(firstdrv_class);
iounmap(gpbcon);
}

module_init(first_drv_init);//驱动入口函数
module_exit(first_drv_exit);//驱动出口函数
MODULE_LICENSE("GPL");
四、字符设备驱动内部机理
4.1 设备驱动注册细节
//字符设备注册函数
int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)
{
struct char_device_struct *cd;
struct cdev *cdev;
char *s;
int err = -ENOMEM;
cd = __register_chrdev_region(major, 0, 256, name);//在数组chrdevs中找到可用的设备号
if (IS_ERR(cd))
return PTR_ERR(cd);
cdev = cdev_alloc();//分配一个字符设备
if (!cdev)
goto out2;
cdev->owner = fops->owner;
cdev->ops = fops;//将注册时传入的函数操作指针赋值给字符设备指针
kobject_set_name(&cdev->kobj, "%s", name);//设置cedv内嵌的kobj成员
for (s = strchr(kobject_name(&cdev->kobj),'/'); s; s = strchr(s, '/'))
*s = '!'; 
err = cdev_add(cdev, MKDEV(cd->major, 0), 256);//将设备cedv放到cdev_map对应项中
if (err)
goto out;
cd->cdev = cdev;
return major ? 0 : cd->major;
out:
kobject_put(&cdev->kobj);
out2:
kfree(__unregister_chrdev_region(cd->major, 0, 256));
return err;
}

//字符设备设备号管理(分配)函数
static struct char_device_struct *__register_chrdev_region(unsigned int major, unsigned int baseminor,int minorct, const char *name)
{
struct char_device_struct *cd, **cp;
int ret = 0;
int i;
cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);//分配一个设备号结构体
if (cd == NULL)
return ERR_PTR(-ENOMEM);
mutex_lock(&chrdevs_lock);
        //如果主设备号是0,那么就在chrdevs中找个未使用的设备号给他
if (major == 0) {
for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) {
if (chrdevs[i] == NULL)
break;
}
if (i == 0) {
ret = -EBUSY;
goto out;
}
major = i;
ret = major;
}
//填充好设备号结构体cd
cd->major = major;
cd->baseminor = baseminor;
cd->minorct = minorct;
strlcpy(cd->name, name, sizeof(cd->name));
i = major_to_index(major);
//将设备号结构体插入到主设备号相同的链表中去
for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)
if ((*cp)->major > major ||
   ((*cp)->major == major &&
    (((*cp)->baseminor >= baseminor) ||
     ((*cp)->baseminor + (*cp)->minorct > baseminor))))
break;
/* Check for overlapping minor ranges.  */
if (*cp && (*cp)->major == major) {
int old_min = (*cp)->baseminor;
int old_max = (*cp)->baseminor + (*cp)->minorct - 1;
int new_min = baseminor;
int new_max = baseminor + minorct - 1;
/* New driver overlaps from the left.  */
if (new_max >= old_min && new_max <= old_max) {
ret = -EBUSY;
goto out;
}
/* New driver overlaps from the right.  */
if (new_min <= old_max && new_min >= old_min) {
ret = -EBUSY;
goto out;
}
}
cd->next = *cp;
*cp = cd;
mutex_unlock(&chrdevs_lock);
return cd;
out:
mutex_unlock(&chrdevs_lock);
kfree(cd);
return ERR_PTR(ret);
}
图:设备号管理图
Linux字符设备驱动框架_第1张图片

//cdev设备和设备号管理函数
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
p->dev = dev;
p->count = count;
return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);//将cdev类型的p保存到cdev_map中
}
int kobj_map(struct kobj_map *domain, dev_t dev, unsigned long range,struct module *module, kobj_probe_t *probe,int (*lock)(dev_t, void *), void *data)
{
unsigned n = MAJOR(dev + range - 1) - MAJOR(dev) + 1;
unsigned index = MAJOR(dev);
unsigned i;
struct probe *p;
if (n > 255)
n = 255;
p = kmalloc(sizeof(struct probe) * n, GFP_KERNEL);
if (p == NULL)
return -ENOMEM;
for (i = 0; i < n; i++, p++) {
p->owner = module;
p->get = probe;
p->lock = lock;
p->dev = dev;
p->range = range;
p->data = data;
}
mutex_lock(domain->lock);//将struct probe插入到链表的方式和上面的方法一样
for (i = 0, p -= n; i < n; i++, p++, index++) {
struct probe **s = &domain->probes[index % 255];
while (*s && (*s)->range < range)
s = &(*s)->next;
p->next = *s;
*s = p;
}
mutex_unlock(domain->lock);
return 0;
}
Linux字符设备驱动框架_第2张图片
4.2 设备驱动调用细节
当应用层调用open函数打开一个设备时将进入内核态:
执行的函数有:sys_open->do_sys_open一系列函数,最后会调用到chrdev_open,在该函数内部将调用kobj_lookup在cdev_map中根据设备号找到它相关联的那个cdev设备,
然后通过这个设备的里面的fops就可以找到操作设备的函数了。
static int chrdev_open(struct inode *inode, struct file *filp)
{
struct cdev *p;
struct cdev *new = NULL;
int ret = 0;
spin_lock(&cdev_lock);
p = inode->i_cdev;
if (!p) {
struct kobject *kobj;
int idx;
spin_unlock(&cdev_lock);
kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx); //根据设备的设备号来在cdev_map中查找
if (!kobj)
return -ENXIO;
new = container_of(kobj, struct cdev, kobj);//取得设备cdev
spin_lock(&cdev_lock);
/* Check i_cdev again in case somebody beat us to it while
  we dropped the lock. */
p = inode->i_cdev;
if (!p) {
inode->i_cdev = p = new;
inode->i_cindex = idx;
list_add(&inode->i_devices, &p->list);
new = NULL;
} else if (!cdev_get(p))
ret = -ENXIO;
} else if (!cdev_get(p))
ret = -ENXIO;
spin_unlock(&cdev_lock);
cdev_put(new);
if (ret)
return ret;


ret = -ENXIO;
filp->f_op = fops_get(p->ops);//将文件描述符的f_op指针赋值为cdev里面的fops
if (!filp->f_op)
goto out_cdev_put;
if (filp->f_op->open) {
ret = filp->f_op->open(inode,filp);//调用设备的open函数
if (ret)
goto out_cdev_put;
}
return 0;
 out_cdev_put:
cdev_put(p);
return ret;
}
struct kobject *kobj_lookup(struct kobj_map *domain, dev_t dev, int *index) 
{
struct kobject *kobj;
struct probe *p;
unsigned long best = ~0UL;
retry:
mutex_lock(domain->lock);
for (p = domain->probes[MAJOR(dev) % 255]; p; p = p->next) {
struct kobject *(*probe)(dev_t, int *, void *);
struct module *owner;
void *data;
if (p->dev > dev || p->dev + p->range - 1 < dev)
continue;
if (p->range - 1 >= best)
break;
if (!try_module_get(p->owner))
continue;
owner = p->owner;
data = p->data;
probe = p->get;
best = p->range - 1;
*index = dev - p->dev;
if (p->lock && p->lock(dev, data) < 0) {
module_put(owner);
continue;
}
mutex_unlock(domain->lock);
kobj = probe(dev, index, data);
/* Currently ->owner protects _only_ ->probe() itself. */
module_put(owner);
if (kobj)
return kobj;
goto retry;
}
mutex_unlock(domain->lock);
return NULL;
}
参考文章:
http://www.cnblogs.com/mr-raptor/archive/2011/03/22/2347683.html
http://blog.chinaunix.net/uid-26921272-id-3422993.html
http://blog.chinaunix.net/uid-27106528-id-3321715.html
http://www.cnblogs.com/armlinux/archive/2010/09/12/2396919.html
http://liu1227787871.blog.163.com/blog/static/20536319720128280344914/

你可能感兴趣的:(Linux驱动)