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设备号。在头文件中给出了这种宏的定义#defineMINORBITS20#defineMINORMASK(1UMINORBITS)#defineMI
4、NOR(dev)(unsignedint)(dev)&MINORMASK)#defineMKDEV(ma,mi)(ma)MINORBITS)|(mi)注册和注销设备号在构建一个字符设备之前,驱动程序首先要做的一件事是向内核恳求分配一个或多个设备号。内核专门提供了字符设备号管理的函数插口:intregister_chrdev_region(dev_tfirst,unsignedintcount,constchar*name);intalloc_chrdev_region(dev_t*dev,unsignedintfirstminor,unsigned
5、intcount,constchar*name);voidunregister_chrdev_region(dev_tfirst,unsignedintcount);register_chrdev_region函数和alloc_chrdev_region函数用于分配设备号,它们的区别是前者是以动态的方法分配的。unregister_chrdev_region函数则用于释放设备号。关键关键数据结构数据结构多数情况下,基本的驱动程序操作还会涉及到内核提供的三个关键数据结构,分别是file_operations、file和inode,它们都在头文件中定义。structfile
6、_operationsstructfilestructinodefile_operationsfile_operations结构体描述了一个文件操作所须要的所有函数。这组函数是以函数表针的方式给出的,它们是字符设备驱动程序设计的主要内容。每位打开的文件,在内核里都用file结构体表示嵌入式系统接口设计与linux驱动程序开发,这个结构体中有一个成员为f_op,它是指向一个file_operations结构体的表针。通过这些方式将一个文件同它自身的操作函数关联上去,这种函数实际上是系统调用的底层实现。在用户空间的应用程序调用内核提供的open、close、read、write等系统调用时,实际上最终会调用那些函数。file_op
7、erations编撰驱动程序的主要工作就是实现这种函数中的一部份,具体实现什么函数因实际须要而定。对于一个字符设备来说,通常只要实现open、release、read、write、mmap、ioctl这几个函数。随着内核版本的不断改进,file_operations结构体的规模也越来越大,它的定义如下所示:structfile_operations/指向拥有该结构的模块的表针,通常初始化为THIS_MODULEstructmodule*owner;/拿来改变文件中的当前读/写位置loff_t(*llseek)(structfile*,loff_t,int);/拿来从
8、设备中读取数据ssize_t(*read)(structfile*,char_user*,size_t,loff_t*);/拿来向设备写入数据ssize_t(*write)(structfile*,constchar_user*,size_t,loff_t*);/初始化一个异步读取操作ssize_t(*aio_read)(structkiocb*,conststructiovec*,unsignedlong,loff_t);/初始化一个异步写入操作ssize_t(*aio_write)(structkiocb
9、*,conststructiovec*,unsignedlong,loff_t);/拿来读取目录,对于设备文件,该成员应该为NULLint(*readdir)(structfile*,void*,filldir_t);/寻址函数,查询对一个或多个文件描述符的读或写是否会阻塞unsignedint(*poll)(structfile*,structpoll_table_struct*);/拿来执行设备I/O操作命令int(*ioctl)(structinode*,structfile*,unsignedint,unsig
10、nedlong);/不使用BKL文件系统,将使用此函数替代ioctllong(*unlocked_ioctl)(structfile*,unsignedint,unsignedlong);/在64位系统上,使用32位的ioctl调用将使用此函数替代long(*compat_ioctl)(structfile*,unsignedint,unsignedlong);/拿来将设备显存映射到进程的地址空间int(*mmap)(structfile*,structvm_area_struct*);/拿来打开设备int(*open)(struct
11、inode*,structfile*);/执行并等待设备的任何未完成的操作int(*flush)(structfile*,fl_owner_tid);/拿来关掉设备int(*release)(structinode*,structfile*);/拿来刷新待处理的数据int(*fsync)(structfile*,structdentry*,intdatasync);/fsync的异步版本int(*aio_fsync)(structkiocb*,intdatasync);/通知设备FASYNC标志的改变int(*fasync)(
12、int,structfile*,int);/拿来实现文件加锁,一般设备文件不须要实现此函数int(*lock)(structfile*,int,structfile_lock*);file_operations主要的函数llseek()函数用于改变文件中的读写位置,并将新位置返回。open()函数负责打开设备和初始化I/O。release()函数负责释放设备占用的显存并关掉设备。read()函数拿来从设备中读取数据。write()函数拿来向设备上写入数据。ioctl()函数实现对设备的控制。mmap()函数将设备显存映射到进程的地址空间。若aio_rea
13、d()和aio_write()函数分别实现对设备进程异步的读写操作。fileLinux中的所有设备都是文件,在内核中使用file结构体来表示一个打开的文件。file结构体中的重要成员fmode_tf_modeloff_tf_posunsignedintf_flagsconststructfile_operations*f_opvoid*private_datainodeinode结构体是一个内核文件系统索引节点对象,它包含了内核在操作文件或目录时所须要的全部信息。在内核中inode结构体拿来表示文件,它与表示打开文件的file结构体的区别是,同个文件可能会有多个打开文
14、件,因而一个inode结构体可能会对应着多个file结构体。inode对于字符设备驱动来说,须要关心的是怎样从inode结构体中获取设备号。与此相关的两个成员分别是:dev_ti_rdev对于设备文件而言linux开发培训,此成员包含实际的设备号。structcdev*i_cdev字符设备在内核中是用cdev结构来表示的。此成员是指向cdev结构的表针。inode内核开发者提供了两个函数来从inode对象中获取设备号,它们的定义如下:staticinlineunsignediminor(conststructinode*inode)returnMINOR(inode-i_rdev)
15、;staticinlineunsignedimajor(conststructinode*inode)returnMAJOR(inode-i_rdev);字符字符设备注册和注销设备注册和注销字符设备初始化voidcdev_init(structcdev*,conststructfile_operations*);字符设备注册structcdev*cdev_alloc(void);字符设备添加voidcdev_add(structcdev*,dev_t,unsigned);字符设备删掉voidcdev_del(structcdev*);
16、方法一:structcdev*my_cdev=cdev_alloc();my_cdev-owner=THIS_MODULE;my_cdev-ops=&fops;技巧二:structcdevmy_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总线总线同步外设插口(SerialPeripheralInterface,SPI)是由诺基亚公司推出的一种高速的、全
18、双工、同步的串行总线。它主要应用在EEPROM、Flash、实时时钟、AD转换器以及数字讯号处理器和数字讯号解码器之间。SPI的工作模式有两种:主模式和从模式,无论那个模式,都支持3Mbit/s的速度,而且还具有传输完成标志和写冲突保护标志。I2C总线总线部集成电路(InternalIntegratedCircuit),一般也被称为I22C或则IIC,这些总线主要用于联接微控制器和外围设备。它是由Philips公司开发的二线式串行总线标准,最初被应用于音频和视频领域的设备开发,现在已在各类电子设备中得到了广泛的使用。I2C总线最主要的特征是它的简单性和高效性。SMBus总线总线系统管理总
19、线(SystemManagementBus,SMBus)最初由Intel提出,应用于联通PC和桌面PC系统中的低速通信。SMBus总线同I2C总线一样也是一种二线式串行总线,它使用一条数据线和一条时钟线进行通讯。其实SMBus的数据传输率较慢,只有大概100kbit/s,却以其结构简单、造价低的特性,深受业界的普遍欢迎。5、I2C总线驱动总线驱动开发开发I2C驱动构架驱动构架Linux下的I2C驱动构架有相当的复杂度,主要由I2C核心、I2C总线驱动以及I2C设备驱动三个部份组成。I2C核心主要提供了以下几个功能:I2C总线驱动和设备驱动的注册及注销函数,I2Calgorithm的上
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结构体定义如下:structi2c_adapterstructmodule*owner;/*所属模块*/unsignedintid;unsignedintclass;/*拿来容许侦测的类*/conststructi2c_algorithm*algo;/*I22Calgorithm结构体表针*/void*algo_data;/*algorithm所需数据*//*client注册和注销时调用*/int(*client_register)(structi2c_client*)_deprecated;int(*cl
22、ient_unregister)(structi2c_client*)_deprecated;inttimeout;/*超时限制*/intretries;/*重试次数*/structdevicedev;/*适配器设备*/intnr;structlist_headclients;/*client数组头*/charname48;/*适配器名称*/structcompletiondev_released;i2c_alogorithm是提供控制适配器形成总线时序的函数。i2c_algorithm结构体定义如下:structi2c_algorithm/I22C传输函数
23、指针int(*master_xfer)(structi2c_adapter*adap,structi2c_msg*msgs,intnum);/SMBus传输函数表针int(*smbus_xfer)(structi2c_adapter*adap,u16addr,unsignedshortflags,charread_write,u8command,intsize,unioni2c_smbus_data*data);/确定适配器所支持的功能u32(*functionality)(structi2c_adapter*);与适配器对
24、应的是从设备,表示它的数据结构为i2c_client。每位I2C设备都须要一个i2c_client来描述。该结构体的定义如下:structi2c_clientunsignedshortflags;/*标志*/unsignedshortaddr;/*芯片地址,注意:7位地址储存在低7位*/charnameI2C_NAME_SIZE;/*设备名子*/structi2c_adapter*adapter;/*屈从的i2c_adapter表针*/structi2c_driver*driver;/*屈从的i2c_driver表针*/structdevicede
25、v;/*设备结构体*/intirq;structlist_headlist;/*数组头*/structlist_headdetected;structcompletionreleased;/*用于同步*/;i2c_driver并不是任何真实化学设备的对应,它只是一套驱动函数。在I2C驱动构架中的设备驱动部份,i2c_driver是辅助类型的数据结构。它的定义如下所示:structi2c_driverintid;/*惟一的驱动id*/unsignedintclass;/*适配器添加函数(新式)*/int(*attach_adapter)(struc
26、ti2c_adapter*);/*适配器删掉函数(新式)*/int(*detach_adapter)(structi2c_adapter*);/*设备删掉函数(新式)*/int(*detach_client)(structi2c_client*)_deprecated;/*设备添加函数(旧式)*/int(*probe)(structi2c_client*,conststructi2c_device_id*);/*设备删掉函数(旧式)*/int(*remove)(structi2c_client*);void(*shutdown)(str
27、ucti2c_client*);/*设备关掉函数*//*设备挂起函数*/int(*suspend)(structi2c_client*,pm_message_tmesg);int(*resume)(structi2c_client*);/*设备恢复函数*/int(*command)(structi2c_client*client,unsignedintcmd,void*arg);/*类似ioctl*/structdevice_driverdriver;/*设备驱动结构体*//*此驱动支持的I22C设备列表*/conststruct
28、i2c_device_id*id_table;/*检查函数*/int(*detect)(structi2c_client*,intkind,structi2c_board_info*);conststructi2c_client_address_data*address_data;structlist_headclients;/*数组头*/;I2C核心核心I2CCore用于维护Linux的I2C核心部份,它主要提供了一套插口函数,容许一个I2Cadapter、I2Cdriver和I2Cclient在初始化时在I2CCore中进行注册,以及在退出
29、时进行注销。这种插口函数都是与具体硬件平台无关的函数,它们在drivers/i2c-core.c文件中定义,在linux/ic.h文件中申明。i2c_adapter的注册和注销inti2c_add_adapter(structi2c_adapter*adapter);inti2c_del_adapter(structi2c_adapter*adapter)i2c_driver的注册和注销inti2c_register_driver(structmodule*owner,structi2c_driver*driver);voidi2c_del_driver(st
30、ructi2c_driver*driver);staticinlineinti2c_add_driver(structi2c_driver*driver);I2C核心核心插口函数I2C核心核心插口函数i2c_client的注册和注销inti2c_attach_client(structi2c_client*);inti2c_detach_client(structi2c_client*);传输、发送和接收inti2c_transfer(structi2c_adapter*adap,structi2c_msg*msgs,intnum);int
31、i2c_master_send(structi2c_client*client,constchar*buf,intcount);inti2c_master_recv(structi2c_client*client,char*buf,intcount);I2C总线总线驱动驱动任务为系统中各个I2C总线降低相应的读写方式。一个总线驱动用于支持一条特定的I2C总线的读写。组成一个总线驱动一般须要两个模块,分别用一个i2c_adapter结构体和i2c_algorithm结构体来描述。具体实现加载函数卸载函数通讯方式加载函数负责初始化I2C适配器所要使用的硬件
32、资源。诸如申请I/O地址、中断号等,之后通过i2c_add_adapter()函数注册i2c_adapter结构体,此结构体的成员函数表针早已被相应的具体实现函数初始化。卸载函数须要释放I2C适配器所占用的硬件资源,之后通过i2c_del_adapter()函数注销i2c_adapter结构体。针对特定的I22C适配器,还须要实现适宜其硬件特点的通讯方式嵌入式系统接口设计与linux驱动程序开发,即实现i2c_algorithm结构体。5、I2C设备设备驱动驱动I2C设备驱动与与I2C总线驱动对应组成I2C设备驱动也分成两个模块,它们分别是i2c_driver和i2c_client结构体。I2C设备驱动要使用这两个结构体linux系统装win7,但是负责填充其中的成员函数。Linux内核中提供了一个通用的方式来实现I2C设备驱动,这个通用的I2C设备驱动由i2c-dev.c文件实现。