1、1、字符设备驱动框架2、字符设备驱动开发3、GPIO驱动概述4、串行总线概述5、I2C总线驱动开发1、字符字符设备驱动设备驱动框架框架 Linux的一个重要特点就是将所有的设备都当作文件来处理,其中就包括设备文件,它们可以使用和操作文件相同的、标准的系统调用接口来完成打开、关闭、读写和I/O控制操作,而驱动程序的主要任务也就是要实现这些系统调用函数。设备驱动程序为应用程序屏蔽了硬件的细节。字符设备驱动程序是嵌入式Linux最基本、也是最常用的驱动程序。它的功能非常强大,几乎可以描述不涉及挂载文件系统的所有硬件设备。字符设备驱动程序的实现方式分为两种:一种是直接编译进内核,另一种是以模块方式加载
2、,然后在需要使用驱动时加载。2、字符设备驱动开发对字符设备的访问是通过文件系统内的设备文件进行的,或者称为设备节点。它们通常位于/dev目录。表示字符设备的设备文件可以通过“ls -l”命令输出的第一列中的“c”来识别,而块设备则用“b”标识。主设备号用来标识该设备的种类,也标识了该设备所使用的驱动程序;次设备号由内核使用,标识使用同一设备驱动程序的不同硬件设备。设备设备号号在Linux内核中,使用dev_t类型来表示设备号,这个类型在头文件中定义。typedef _u32 _kernel_dev_t;typedef _kernel_dev_tdev_t;dev_t是一个32位的无符号数,其高
3、12位用来表示主设备号,低20位用来表示次设备号。在2.6内核中,可以容纳大量的设备,而不像先前的内核版本最多只能使用255个主设备号和255个次设备号。内核主要提供了三个操作dev_t类型的函数,它们分别是: MAJOR(dev)、MINOR(dev)和MKDEV(ma, mi)。 其中MAJOR(dev)用于获取主设备号。 MINOR(dev)则用于获取次设备号。 MKDEV(ma, mi) 根据主设备号ma和次设备号mi构造dev_t设备号。在头文件中给出了这些宏的定义#define MINORBITS20#define MINORMASK(1U MINORBITS)#define MI
4、NOR(dev)(unsigned int) (dev) & MINORMASK)#define MKDEV(ma,mi) (ma) MINORBITS) | (mi)注册和注销设备号在建立一个字符设备之前,驱动程序首先要做的一件事是向内核请求分配一个或多个设备号。内核专门提供了字符设备号管理的函数接口:int register_chrdev_region(dev_t first, unsigned int count, const char *name);int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned
5、 int count, const char *name);void unregister_chrdev_region(dev_t first, unsigned int count);register_chrdev_region函数和alloc_chrdev_region函数用于分配设备号,它们的区别是后者是以动态的方式分配的。unregister_chrdev_region函数则用于释放设备号。关键关键数据结构数据结构多数情况下,基本的驱动程序操作都会涉及到内核提供的三个关键数据结构,分别是file_operations、file和inode,它们都在头文件中定义。 struct file
6、_operations struct file struct inodefile_operationsfile_operations结构体描述了一个文件操作所需要的所有函数。这组函数是以函数指针的形式给出的,它们是字符设备驱动程序设计的主要内容。每个打开的文件,在内核里都用file结构体表示,这个结构体中有一个成员为f_op,它是指向一个file_operations结构体的指针。通过这种形式将一个文件同它自身的操作函数关联起来,这些函数实际上是系统调用的底层实现。在用户空间的应用程序调用内核提供的open、close、read、write等系统调用时,实际上最终会调用这些函数。file_op
7、erations编写驱动程序的主要工作就是实现这些函数中的一部分,具体实现哪些函数因实际需要而定。对于一个字符设备来说,一般只要实现open、release、read、write、mmap、ioctl这几个函数。随着内核版本的不断改进,file_operations结构体的规模也越来越大,它的定义如下所示:struct file_operations /指向拥有该结构的模块的指针,一般初始化为THIS_MODULE struct module *owner; /用来改变文件中的当前读/写位置 loff_t (*llseek) (struct file *, loff_t, int); /用来从
8、设备中读取数据 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
9、*, const struct iovec *, unsigned long, loff_t); /用来读取目录,对于设备文件,该成员应当为NULL int (*readdir) (struct file *, void *, filldir_t); /轮询函数,查询对一个或多个文件描述符的读或写是否会阻塞 unsigned int (*poll) (struct file *, struct poll_table_struct *); /用来执行设备I/O操作命令 int (*ioctl) (struct inode *, struct file *, unsigned int, unsig
10、ned long); /不使用BKL文件系统,将使用此函数代替ioctl long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); /在64位系统上,使用32位的ioctl调用将使用此函数代替long (*compat_ioctl) (struct file *, unsigned int, unsigned long);/用来将设备内存映射到进程的地址空间int (*mmap) (struct file *, struct vm_area_struct *);/用来打开设备int (*open) (struct
11、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);/fsync的异步版本int (*aio_fsync) (struct kiocb *, int datasync);/通知设备FASYNC标志的改变int (*fasync) (
12、int, struct file *, int);/用来实现文件加锁,通常设备文件不需要实现此函数int (*lock) (struct file *, int, struct file_lock *);file_operations主要的函数 llseek()函数用于改变文件中的读写位置,并将新位置返回。 open()函数负责打开设备和初始化I/O。 release()函数负责释放设备占用的内存并关闭设备。 read()函数用来从设备中读取数据。 write()函数用来向设备上写入数据。 ioctl()函数实现对设备的控制。 mmap()函数将设备内存映射到进程的地址空间。若 aio_rea
13、d()和aio_write()函数分别实现对设备进程异步的读写操作。fileLinux中的所有设备都是文件,在内核中使用file结构体来表示一个打开的文件。file结构体中的重要成员 fmode_t f_mode loff_t f_pos unsigned int f_flags const struct file_operations *f_op void *private_datainodeinode结构体是一个内核文件系统索引节点对象,它包含了内核在操作文件或目录时所需要的全部信息。在内核中inode结构体用来表示文件,它与表示打开文件的file结构体的区别是,同个文件可能会有多个打开文
14、件,因此一个inode结构体可能会对应着多个file结构体。inode对于字符设备驱动来说,需要关心的是如何从inode结构体中获取设备号。与此相关的两个成员分别是: dev_t i_rdev 对于设备文件而言,此成员包含实际的设备号。 struct cdev *i_cdev 字符设备在内核中是用cdev结构来表示的。此成员是指向cdev结构的指针。inode内核开发者提供了两个函数来从inode对象中获取设备号,它们的定义如下:static inline unsigned iminor(const struct inode *inode) return MINOR(inode-i_rdev)
15、;static inline unsigned imajor(const struct inode *inode) return MAJOR(inode-i_rdev); 字符字符设备注册和注销设备注册和注销字符设备初始化 void cdev_init(struct cdev *, const struct file_operations *);字符设备注册 struct cdev *cdev_alloc(void);字符设备添加 void cdev_add(struct cdev *, dev_t, unsigned);字符设备删除 void cdev_del(struct cdev *);
16、方法一:struct cdev *my_cdev = cdev_alloc();my_cdev-owner = THIS_MODULE;my_cdev-ops = &fops;方法二:struct cdev my_cdev;cdev_init(&my_cdev, &fops);my_cdev.owner = THIS_MODULE;3、GPIO概述概述GPIO接口利用工业标准I22C、SMBus或SPI接口简化了I/O接口的扩展。当微控制器或芯片组没有足够的I/O端口,或当系统需要使用远程串行通信或控制时,GPIO接口能够提供额外的控制和监视功能。GPIO接口一般至少会有两个寄存器,即控制寄存
17、器和数据寄存器。数据寄存器的各位都直接引到芯片外部,而针对该寄存器的每一位的功能,则可以通过控制寄存器中相应的位来设置。GPIO接口的优点是低功耗、小封装、低成本、较好的灵活性。14、串行串行总线概述总线概述目前流行的通信一般采用串行或并行模式,而串行模式应用更广泛。目前,大多数微控制器都提供SPI 和I2C接口,用于发送、接收数据。微处理器通过几条总线控制周边的设备。串行相比于并行的主要优点是要求的线数较少,通常只需要使用2条、3条或4条数据/时钟总线连续传输数据。SPI总线总线同步外设接口(Serial Peripheral Interface,SPI)是由摩托罗拉公司推出的一种高速的、全
18、双工、同步的串行总线。它主要应用在EEPROM、Flash、实时时钟、AD转换器以及数字信号处理器和数字信号解码器之间。SPI 的工作模式有两种:主模式和从模式,无论那种模式,都支持3Mbit/s的速率,并且还具有传输完成标志和写冲突保护标志。I2C总线总线部集成电路(Internal Integrated Circuit),通常也被称为I22C或者IIC,这种总线主要用于连接微控制器和外围设备。它是由Philips公司开发的二线式串行总线标准,最初被应用于音频和视频领域的设备开发,现今已在各种电子设备中得到了广泛的使用。I2C总线最主要的特点是它的简单性和高效性。SMBus总线总线系统管理总
19、线(System Management Bus,SMBus)最初由Intel提出,应用于移动PC和桌面PC系统中的低速通讯。SMBus总线同I2C总线一样也是一种二线式串行总线,它使用一条数据线和一条时钟线进行通信。虽然SMBus的数据传输率较慢,只有大约100kbit/s,却以其结构简单、造价低的特点,受到业界的普遍欢迎。5、I2C总线驱动总线驱动开发开发I2C驱动架构驱动架构 Linux下的I2C驱动架构有相当的复杂度,主要由I2C核心、I2C总线驱动以及I2C设备驱动三个部分组成。I2C核心 主要提供了以下几个功能:I2C总线驱动和设备驱动的注册及注销函数,I2C algorithm的上
20、层代码实现,探测设备、检测设备地址的上层代码实现。I2C总线驱动 I2C总线驱动是对I2C硬件架构中适配器的具体实现。 I2C总线驱动负责实现I2C适配器数据结构(i2c_adapter)、I2C适配器的algorithm数据结构(i2c_algorithm)以及控制适配器产生通信信号的函数。I2C设备驱动 I2C设备驱动是对I2C硬件架构中设备的具体实现。 I2C设备驱动负责实现i2c_driver和i2c_client两个数据结构。关键关键数据结构数据结构i2c_adapter是对硬件上的适配器的抽象,相当于整个I2C驱动的控制器,它的作用是产生总线时序,例如开始位、停止位、读写周期等,用
21、以读写I2C从设备。I2c_adapter结构体定义如下:struct i2c_adapter struct module *owner; /*所属模块*/unsigned int id;unsigned int class; /*用来允许探测的类*/const struct i2c_algorithm *algo; /*I22C algorithm结构体指针*/void *algo_data; /*algorithm所需数据*/ /*client注册和注销时调用*/int (*client_register)(struct i2c_client *) _deprecated;int (*cl
22、ient_unregister)(struct i2c_client *) _deprecated;int timeout;/*超时限制*/int retries; /*重试次数*/struct device dev;/*适配器设备*/int nr;struct list_head clients;/* client链表头*/char name48; /*适配器名称*/struct completion dev_released;i2c_alogorithm是提供控制适配器产生总线时序的函数。i2c_algorithm结构体定义如下:struct i2c_algorithm /I22C传输函数
23、指针 int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs, int num); /SMBus传输函数指针 int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr, unsigned short flags, char read_write, u8 command, int size, union i2c_smbus_data *data); /确定适配器所支持的功能 u32 (*functionality) (struct i2c_adapter *);与适配器对
24、应的是从设备,表示它的数据结构为i2c_client。每个I2C设备都需要一个i2c_client来描述。该结构体的定义如下:struct i2c_client unsigned short flags; /*标志*/ unsigned short addr; /*芯片地址,注意:7位地址存储在低7位*/ char nameI2C_NAME_SIZE; /*设备名字*/ struct i2c_adapter *adapter; /*依附的i2c_adapter指针*/ struct i2c_driver *driver; /*依附的i2c_driver指针*/ struct device de
25、v; /*设备结构体*/ int irq; struct list_head list; /*链表头*/ struct list_head detected; struct completion released; /*用于同步*/;i2c_driver并不是任何真实物理设备的对应,它只是一套驱动函数。在I2C驱动架构中的设备驱动部分,i2c_driver是辅助类型的数据结构。它的定义如下所示:struct i2c_driver int id; /*唯一的驱动id*/ unsigned int class; /*适配器添加函数(旧式)*/ int (*attach_adapter)(struc
26、t i2c_adapter *); /*适配器删除函数(旧式)*/ int (*detach_adapter)(struct i2c_adapter *); /*设备删除函数(旧式)*/ int (*detach_client)(struct i2c_client *) _deprecated; /*设备添加函数(新式)*/ int (*probe)(struct i2c_client *, const struct i2c_device_id *); /*设备删除函数(新式)*/ int (*remove)(struct i2c_client *); void (*shutdown)(str
27、uct i2c_client *); /*设备关闭函数*/ /*设备挂起函数*/ int (*suspend)(struct i2c_client *, pm_message_t mesg); int (*resume)(struct i2c_client *); /*设备恢复函数*/ int (*command)(struct i2c_client *client, unsigned int cmd, void *arg); /*类似ioctl*/ struct device_driver driver; /*设备驱动结构体*/ /*此驱动支持的I22C设备列表*/ const struct
28、 i2c_device_id *id_table; /*检测函数*/ int (*detect)(struct i2c_client *, int kind, struct i2c_board_info *); const struct i2c_client_address_data *address_data; struct list_head clients; /*链表头*/;I2C核心核心I2C Core用于维护Linux的I2C核心部分,它主要提供了一套接口函数,允许一个I2C adapter、I2C driver和I2C client在初始化时在I2C Core中进行注册,以及在退出
29、时进行注销。这些接口函数都是与具体硬件平台无关的函数,它们在drivers/i2c-core.c文件中定义,在linux/ic.h文件中声明。i2c_adapter的注册和注销 int i2c_add_adapter(struct i2c_adapter *adapter); int i2c_del_adapter(struct i2c_adapter *adapter)i2c_driver的注册和注销 int i2c_register_driver(struct module *owner, struct i2c_driver *driver); void i2c_del_driver(st
30、ruct i2c_driver *driver); static inline int i2c_add_driver(struct i2c_driver *driver);I2C核心核心接口函数I2C核心核心接口函数i2c_client的注册和注销 int i2c_attach_client(struct i2c_client *); int i2c_detach_client(struct i2c_client *);传输、发送和接收 int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num); int
31、i2c_master_send(struct i2c_client *client, const char *buf, int count); int i2c_master_recv(struct i2c_client *client, char *buf, int count);I2C总线总线驱动驱动任务 为系统中各个I2C总线增加相应的读写方法。 一个总线驱动用于支持一条特定的I2C总线的读写。组成 一个总线驱动通常需要两个模块,分别用一个i2c_adapter结构体和i2c_algorithm结构体来描述。具体实现 加载函数 卸载函数 通信方法加载函数负责初始化I2C适配器所要使用的硬件
32、资源。例如申请I/O地址、中断号等,然后通过i2c_add_adapter() 函数注册i2c_adapter结构体,此结构体的成员函数指针已经被相应的具体实现函数初始化。卸载函数需要释放I2C适配器所占用的硬件资源,然后通过i2c_del_adapter() 函数注销i2c_adapter结构体。针对特定的I22C适配器,还需要实现适合其硬件特性的通信方法,即实现i2c_algorithm结构体。5、I2C设备设备驱动驱动I2C设备驱动与与I2C总线驱动对应组成 I2C设备驱动也分成两个模块,它们分别是i2c_driver和i2c_client结构体。I2C设备驱动要使用这两个结构体,并且负责填充其中的成员函数。Linux内核中提供了一个通用的方法来实现I2C设备驱动,这个通用的I2C设备驱动由i2c-dev.c文件实现。