Linux设备驱动开发详解学习笔记<一>
书名:《Linux设备驱动开发详解》第二版
主机环境:Linux version 2.6.25-14.fc9.i686@Fedora
arm-linux-gcc版本:version 4.4.1
开发板:mini2440-256M
版权信息:版权归作者M.Bing和博客园共同所有,可以随意转载,但请保留此条信息!
备注:由于作者水平有限,文章难免出错,欢迎大家指正,共同学习。
一、字符设备简介
在Linux2.6内核中,使用cdev结构描述一个字符设备,cdev结构如下所示
struct cdev { struct kobject kobj; struct module *owner; struct file_operations *ops; /*文件操作结构体*/ struct list_head list; dev_t dev; /*设备号 32位,其中12位主设备号,20次设备号*/ unsigned int count; };
可以从dev_t获得主设备号和次设备号:
MAJOR(dev_t dev)
MINOR(dev_t dev)
可以使用下面的宏通过主设备号和次设备号生成dev_t:
MKDEV(int major, int minor)
cdev结构内部还有一个重要的结构体:file_operations,其定义了字符设备驱动提供给虚拟文件系统的接口函数。
在Linux2.6内核中使用以下函数操作cdev结构体:
void cdev_init(struct cdev *, struct file_operationd *);/*这个函数将cdev和file_operations联合在一起*/ struct cdev *cdev_alloc(void); void cdev_put(struct cdev *,dev_t, unsigned); int cdev_add(struct cdev *, dev_t, unsigned); void cdev_del(struct cdev *);
各个函数的功能从其函数名就可以看出。
二、file_operations结构体
在上面一节说了file_operations是一个重要的结构体,下面看看它的内部构成:
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(*aio_read) (struct kiocb *, char __user *, size_t, loff_t); ssize_t(*write) (struct file *, const char __user *, size_t, loff_t *); ssize_t(*aio_write) (struct kiocb *, const char __user *, size_t, loff_t); int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_struct *); int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *); 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(*readv) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t(*writev) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t(*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void __user *); 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); };
每个函数都对应一个用户操作,但你不必实现每一个函数,一般你只需要实现以下几个函数:
struct file_operations fops = { .read = device_read, .write = device_write, .open = device_open, .release = device_release };
三、实现一个字符设备需要做哪些工作
1、填充file_operations中需要的函数,并实现这些函数
2、实现加载模块时调用的函数XXX_init();下面是一个常用的例子:
dev_t IO_MAJOR = 0; //这里是动态分配设备号和动态创建设备结点需要用到的 struct cdev dev_c; struct class *my_class; static int __init IO_init(void) { int ret,err; ret = alloc_chrdev_region(&IO_MAJOR, 0, 1,DEVICE_NAME); //动态分配设备号 if (ret) { printk("testmode register failure\n"); unregister_chrdev_region(IO_MAJOR,1); return ret; } else { printk("testmode register success\n"); } cdev_init(&dev_c, &s3c6410_IO_fops); err = cdev_add(&dev_c, IO_MAJOR, 1); if(err) { printk(KERN_NOTICE "error %d adding FC_dev\n",err); unregister_chrdev_region(IO_MAJOR, 1); return err; } my_class = class_create(THIS_MODULE, DEVICE_NAME);//动态创建设备结点 if(IS_ERR(my_class)) { printk("ERR:cannot create a cdev_class\n"); unregister_chrdev_region(IO_MAJOR, 1); return -1; } device_create(my_class,NULL, IO_MAJOR, 0, DEVICE_NAME); printk(DEVICE_NAME "initialized.\n"); return 0; }
要注意的是,在上面的函数中,没有驱动模块加载失败时的处理,所以在实际的应用过程在每一步加载失败时,跳转到相应的出错处理,在出错处理中将前面以注册的函数依次卸载。
图3-1为字符设备驱动的结构,字符设备驱动与字符设备以及字符设备驱动与用户空间访问该设备的程序之间的关系:
图3-1 字符设备内部关系
四、示例代码
/* * ===================================================================================== * Filename: testmode.c * Description: * Version: 1.0 * Created: 2013-5-29 * Revision: none * Compiler: gcc * Author: M.Bing * Company: Deviser of TinJing * ===================================================================================== */ #include#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include string.h> #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define DEVICE_NAME "io_mode" #define TESTK08K15 0 //Test GPIOK8--GPIOK15 #define TESTL00L07 1 //Test GPIOL0--GPIOL7 unsigned int UserCmd = 0; /*Set GPK12 GPK13 GPK14 GPK15 INPUT*/ /**/ static int IO_open(struct inode *inode, struct file *file) { int reg; /**************Init GPIOK8--GPIOK15******************/ reg = readl(S3C64XX_GPKCON1); reg &=(~0xffffffff);/*Use GPK8 ~GPK15 8PIN*/ /*Set GPK12 GPK13 GPK14 GPK15 INPUT*/ /*Set GPK8 GPK9 GPK10 GPK11 OUTPUT*/ reg |=(0x00001111); writel(reg,S3C64XX_GPKCON1); reg = readl(S3C64XX_GPKPUD); reg &= (~0xff0000); reg |= (0xaa0000); //GPK8 GPK9 GPK10 GPK11 pull writel(reg,S3C64XX_GPKPUD); /**************Init GPIOL0--GPIOL7******************/ reg = readl(S3C64XX_GPLCON); reg &= (~0xffffffff); reg |= (0x00001111); writel(reg,S3C64XX_GPLCON);//L0-L3 OUTPUT L4-L7 INPUT reg = readl(S3C64XX_GPLPUD); reg &=(~0x0000ffff); reg |=(0x000000aa);//L0-L3 PULL UP writel(reg,S3C64XX_GPLPUD); return 0; } static int IO_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) { if (arg > 4) { return -EINVAL; } UserCmd = cmd; switch(UserCmd) { case TESTK08K15: printk("\n-----------UserCmd = TESTK08K15------------\n"); break; case TESTL00L07: printk("\n-----------UserCmd = TESTL00L07------------\n"); break; default: printk("\n-----------Unkonw UserCmd = %d------------\n",cmd); break; } return 0; } static ssize_t IO_read( struct file *file, char __user *buff, size_t size, loff_t *loff) { unsigned int reg; if(UserCmd == TESTK08K15) { reg = readl(S3C64XX_GPKDAT); buff[0] = ((reg | 0xffff0fff) >> 12)&0x0f;//get K12 - K15 data //printk("IO_read buff[0] = %d\n",buff[0]); } if(UserCmd == TESTL00L07) { reg = readl(S3C64XX_GPLDAT); buff[0] = ((reg | 0xffffff0f) >> 4)&0x0f;//get L4 - L7 data } return 0; } static ssize_t IO_write( struct file *file, const char __user *buff, size_t size, loff_t *loff) { unsigned int reg; if(UserCmd == TESTK08K15) { //printk("IO_write buff[0] = %d\n",buff[0]); reg = readl(S3C64XX_GPKDAT); reg &= (~0xf00); reg |=( buff[0] << 8)& 0xf00; writel(reg,S3C64XX_GPKDAT); } if(UserCmd == TESTL00L07) { reg = readl(S3C64XX_GPLDAT); reg &= (~0x0f); reg |= (buff[0]) & 0x0f; writel(reg,S3C64XX_GPLDAT); } return 0; } static struct file_operations s3c6410_IO_fops = { .owner = THIS_MODULE, .open = IO_open, .ioctl = IO_ioctl, .read = IO_read, .write = IO_write }; dev_t IO_MAJOR = 0; //这里是动态分配设备号和动态创建设备结点需要用到的 struct cdev dev_c; struct class *my_class; static int __init IO_init(void) { int ret,err; ret = alloc_chrdev_region(&IO_MAJOR, 0, 1,DEVICE_NAME); //动态分配设备号 if (ret) { printk("testmode register failure\n"); unregister_chrdev_region(IO_MAJOR,1); return ret; } else { printk("testmode register success\n"); } cdev_init(&dev_c, &s3c6410_IO_fops); err = cdev_add(&dev_c, IO_MAJOR, 1); if(err) { printk(KERN_NOTICE "error %d adding FC_dev\n",err); unregister_chrdev_region(IO_MAJOR, 1); return err; } my_class = class_create(THIS_MODULE, DEVICE_NAME);//动态创建设备结点 if(IS_ERR(my_class)) { printk("ERR:cannot create a cdev_class\n"); unregister_chrdev_region(IO_MAJOR, 1); return -1; } device_create(my_class,NULL, IO_MAJOR, 0, DEVICE_NAME); printk(DEVICE_NAME "initialized.\n"); return 0; } static void __exit IO_exit(void) { device_destroy(my_class, IO_MAJOR); class_destroy(my_class); unregister_chrdev_region(IO_MAJOR,1); printk("testmode_exit \n"); } module_init(IO_init); module_exit(IO_exit); MODULE_AUTHOR("M.Bing"); MODULE_DESCRIPTION("s3c6410 IO test."); MODULE_LICENSE("GPL");