一、linux中设备驱动的类型字符设备:GPIO、EINT、ADC、WDT、LCD、CAMERA、块设备:nand flash、SD卡、硬盘网络设备:有线网卡、无线网卡=================================================================================== 二、字符设备驱动的描述1、cdevcdev是一个描述一个字符设备的结构体,我们要设计一个字符设备,就必须定义一个cdev的结构体,然后对cdev结构进行初始化,初始化结束后,将cdev注册到内核,这样,内核中就添加了一个字符设备驱动。#include <linux/cdev.h>struct cdev {struct kobject kobj;struct module *owner;const struct file_operations *ops;struct list_head list;dev_t dev;unsigned int count;};1)struct kobject kobj; linux在做字符设备管理的时候,使用的驱动模型,利用kobject生成/sys下的驱动信息。2)struct module *owner;这个字符设备输入那一个module,一般设备为THIS_MODULE3)const struct file_operations *ops;文件操作集,给应用程序提供API接口,应用程序通过系统调用,来访问file_operations中的接口函数。struct file_operations {struct module *owner;ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);unsigned int (*poll) (struct file *, struct poll_table_struct *);int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);int (*mmap) (struct file *, struct vm_area_struct *);int (*open) (struct inode *, struct file *);int (*release) (struct inode *, struct file *);};以上的接口函数,是应用程序中,文件IO的系统调用接口。open、read、write、lseek、ioctl、mmap、close提示1:文件IO函数原型中的参数,与文件操作集中对应函数的参数是否一致???是一个系统调用过程,中间是经过vfs和linux内核,所以参数是不一致的,与函数调用不同。提示2: ssize_t是什么类型?typedef __kernel_ssize_t ssize_t;#ifndef __kernel_size_t#if __BITS_PER_LONG != 64typedef unsigned int __kernel_size_t;typedef int __kernel_ssize_t;typedef int __kernel_ptrdiff_t;#elsetypedef unsigned long __kernel_size_t;typedef long __kernel_ssize_t;typedef long __kernel_ptrdiff_t;#endif#endif4) struct list_head list;内核双向链表,向内核中注册一个字符设备时,链表增加一项5)dev_t dev;设备号6)unsigned int count;次设备号的数量-----------------------------------------------------------------------------------------------------------2、设备号1)设备号的定义dev_t dev;在linux内核中,每一个设备驱动都有一个设备号,设备号有主设备号和次设备号组成主设备号:描述设备驱动的具体应用类型次设备号:描述该具体应用类型下的一个应用实例例如:4个uart的驱动[root@YueQian /]# ls /dev/s3c2410_serial* -lcrw-rw---- 1 root root 204, 64 Jan 2 11:21 /dev/s3c2410_serial0crw-rw---- 1 root root 204, 65 Jan 2 11:21 /dev/s3c2410_serial1crw-rw---- 1 root root 204, 66 Jan 2 11:21 /dev/s3c2410_serial2crw-rw---- 1 root root 204, 67 Jan 2 11:21 /dev/s3c2410_serial3例如:nand flash的5个分区[root@YueQian /]# ls /dev/mtdblock* -lbrw-rw---- 1 root root 31, 0 Jan 2 11:21 /dev/mtdblock0brw-rw---- 1 root root 31, 1 Jan 2 11:21 /dev/mtdblock1brw-rw---- 1 root root 31, 2 Jan 2 11:21 /dev/mtdblock2brw-rw---- 1 root root 31, 3 Jan 2 11:21 /dev/mtdblock3brw-rw---- 1 root root 31, 4 Jan 2 11:21 /dev/mtdblock4typedef unsigned int __u32;typedef __u32 __kernel_dev_t;typedef __kernel_dev_t dev_t;设备号是一个无符号32位的整型值,其中高12bits是主设备号,低20bits是次设备号。-----------------------------------------------------------------------------------------------------------2)主次设备号之间的函数#define MINORBITS 20#define MINORMASK ((1U << MINORBITS) - 1) //0x000 fffff#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS)) //MINORBITS = 20#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))-----------------------------------------------------------------------------------------------------------3)如何得到一个设备号我们设置一个设备驱动,需要从内核中,申请一个设备号。(1)静态注册设备号:我们来指定设备号,不要使用系统已经使用的设备号/** * register_chrdev_region() - register a range of device numbers * @from: the first in the desired range of device numbers; must include * the major number. * @count: the number of consecutive device numbers required * @name: the name of the device or driver. * * Return value is zero on success, a negative error code on failure. */int register_chrdev_region(dev_t from, unsigned count, const char *name)参数说明: dev_t from:注册的一个设备号的开始值。如果是gec210 uart,from=(204<<20 + 64) unsigned count: 设备实例的数量,即次设备号的数目。如果是gec210 uart,count=4 const char *name:字符设备的名称。如果是gec210 uart,name=s3c2410_serial返回值:注册成功,返回0注册失败,返回一个负数的错误码(2)动态分配设备号:让系统自动分配空闲的设备号/** * alloc_chrdev_region() - register a range of char device numbers * @dev: output parameter for first assigned number * @baseminor: first of the requested range of minor numbers * @count: the number of minor numbers required * @name: the name of the associated device or driver * * Allocates a range of char device numbers. The major number will be * chosen dynamically, and returned (along with the first minor number) * in @dev. Returns zero or a negative error code. */int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)参数说明: dev_t *dev:动态分配后的设备号。 unsigned baseminor:我们使用的第一个次设备号,次设备号的开始值,如果是gec210 uart,baseminor=64 unsigned count: 设备实例的数量,即次设备号的数目。如果是gec210 uart,count=4 const char *name:字符设备的名称。如果是gec210 uart,name=s3c2410_serial返回值:注册成功,返回0注册失败,返回一个负数的错误码===================================================================================三、字符设备驱动的设计思路1、定义一个字符设备struct cdev chrdev_test;------------------------------------------------------------------------------------2、定义一个文件操作集,并对文件操作集进行初始化struct file_operations chdev_fops = {.open = test_open,.read = test_read,.write = test_write,.ioctl = test_ioctl,.release = test_release,};------------------------------------------------------------------------------------3、字符设备的初始化/** * cdev_init() - initialize a cdev structure * @cdev: the structure to initialize * @fops: the file_operations for this device * * Initializes @cdev, remembering @fops, making it ready to add to the * system with cdev_add(). */void cdev_init(struct cdev *cdev, const struct file_operations *fops)参数: struct cdev *cdev:定义的字符设备 const struct file_operations *fops:已经完成定义和初始化的一个文件操作集------------------------------------------------------------------------------------4、申请设备号 dev_t ndev;//设备号 int TestMajor = 0;//主设备号int TestMinor = 0;//次设备号char drv_name[]="chrtest1";int ret;if(TestMajor){ndev=MKDEV(TestMajor,TestMinor);//由主次设备号,得到设备号ret=register_chrdev_region(ndev,1,drv_name);//注册字符设备编号}else{ret=alloc_chrdev_region(&ndev,TestMinor,1,drv_name);//动态分配一个字符设备号,&ndev存放返回的设备号TestMajor=MAJOR(ndev);}if(ret<0){printk(KERN_WARNING"cannot get major %d n",TestMajor);goto fail_chrdev_region; //出错处理 goto}------------------------------------------------------------------------------------5、将字符设备加入到内核/** * cdev_add() - add a char device to the system * @p: the cdev structure for the device * @dev: the first device number for which this device is responsible * @count: the number of consecutive minor numbers corresponding to this * device * * cdev_add() adds the device represented by @p to the system, making it * live immediately. A negative error code is returned on failure. */int cdev_add(struct cdev *p, dev_t dev, unsigned count)参数:struct cdev *p :定义并初始化的字符设备cdevdev_t dev:已经申请到的设备号unsigned count:次设备的数量返回值:注册成功,返回0注册失败,返回一个负数的错误码------------------------------------------------------------------------------------6、注销一个设备号/** * unregister_chrdev_region() - return a range of device numbers * @from: the first in the range of numbers to unregister * @count: the number of device numbers to unregister * * This function will unregister a range of @count device numbers, * starting with @from. The caller should normally be the one who * allocated those numbers in the */void unregister_chrdev_region(dev_t from, unsigned count)参数说明: dev_t from:注册的一个设备号的开始值。如果是gec210 uart,from=(204<<20 + 64) unsigned count: 设备实例的数量,即次设备号的数目。如果是gec210 uart,count=4------------------------------------------------------------------------------------7、注销一个字符设备/** * cdev_del() - remove a cdev from the system * @p: the cdev structure to be removed * * cdev_del() removes @p from the system, possibly freeing the structure * itself. */void cdev_del(struct cdev *p)===================================================================================四、用户空间与内核空间交互数据的函数1、将用户空间的数据拷贝到内核空间static inline unsigned long __must_check copy_from_user(void *to, const void __user *from, unsigned long n)2、将内核空间的数据拷贝到用户空间static inline unsigned long __must_check copy_to_user(void __user *to, const void *from, unsigned long n)返回值:如果数据拷贝成功,返回是0;如果数据拷贝失败,返回是尚未拷贝的字节数。提示1:__must_check必须检查该函数的返回值,如果不检查,会有警告。提示2:inline --内联函数。===================================================================================五、驱动程序的设计见demo1===================================================================================六、驱动的调试写一个应用程序,利用应用程序调试驱动程序。1、安装驱动[root@YueQian /test]# insmod demo1.ko[24273.778642][24273.779733] this is the first demo of driver, inserting successful !2、查看内核模块[root@YueQian /test]# lsmoddemo1 2589 0 - Live 0xbf0000003、查看字符设备的主设备号和设备名称char drv_name[ ]="chrtest";int TestMajor = 0;//主设备号int TestMinor = 0;//次设备号[root@YueQian /test]# cat /proc/devicesCharacter devices:250 chrtest --->主设备号和设备名称4、手动创建设备文件(后面讲如何自动创建设备文件)[root@YueQian /test]# mknod /dev/chr_test1 c 250 05、查看设备文件[root@YueQian /test]# ls /dev/chr_test1 -lcrw-r--r-- 1 root root 250, 0 Jan 2 18:14 /dev/chr_test16、执行应用程序 7、驱动的卸载[root@YueQian /]# rmmod demo1[25293.152108] the driver is exiting===================================================================================七、字符设备驱动的老方法老的方法更简洁,更直观;新的方法比较灵活。1、注册字符设备static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)参数说明: unsigned int major 主设备号,如果major=0,系统会自动分配一个主设备号,如果major!=0,静态注册主设备号。 const char *name 设备名称 const struct file_operations *fops 文件操作集。返回值: 1)如果unsigned int major 是大于0的值,静态注册字符设备:注册成功,返回0注册失败,返回一个负数的错误码2)如果unsigned int major 是等于0的值,动态注册字符设备分配成功,返回分配后的主设备号注册失败,返回一个负数的错误码将设备号申请、字符设备的初始化、字符设备的注册使用一个函数实现。2、注销字符设备static inline void unregister_chrdev(unsigned int major, const char *name)