嵌入式Linux驱动程序开发

嵌入式Linux驱动程序开发node

1.设备驱动程序的概念... 2linux

2.处理器与设备间数据交换方式... 2编程

21.查询方式... 2数组

2.2.中断方式... 3缓存

2.3.直接访问内存(DMA)方式... 3数据结构

3.驱动程序结构... 3app

3.1一个设备驱动程序模块的基本框架... 4框架

3.1. file_operations结构体... 5dom

3.2.inode{}和file{}结构体... 5async

4.设备注册和初始化... 6

5.中断管理... 7

6.设备驱动程序的开发过程... 8

7.设备驱动开发的基本函数... 8

7.1.I/O口函数... 8

7.2.时钟函数... 9

7.3.内存操做函数... 10

7.4.复制函数... 10

8.模块加载与卸载... 10

9.实例剖析... 11

9.1实例一... 11

9.1.1驱动程序... 11

9.1.2用户程序... 14

9.1.3执行效果... 15

9.2实例二... 16

10块设备驱动程序的编写... 19

10.1块设备驱动编写流程... 20

10.2重要数据结构... 21

10.2.1 struct bio. 21

10.2.2 structgendisk. 22

(1)设备初始化... 24

(2)request操做... 25

(3)打开操做... 25

(4)释放设备操做... 25

(5)ioctl操做... 25

10.3中断编程... 25

10.4一个简单的块设备驱动... 26

 

系统调用是操做系统内核和应用程序之间的接口,设备驱动程序是操做系统内核和机器硬件之间的接口。设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备文件,应用程序能够象操做普通文件同样对硬件设备进行操做。设备驱动程序是内核的一部分。

Linux将设备主要分红两大类:一类是块设备,相似磁盘以记录块或扇区为单位,成块进行输入/输出的设备;另外一类是字符设备,相似键盘以字符为单位,逐个进行输入/输出的设备。网路设备是介于块设备和字符设备之间的一种特殊设备。

块设备接口仅支持面向块的I/O操做,全部I/O操做都经过在内核地址空间中的I/O缓冲区进行,它能够支持随机存取的功能。文件系统一般都创建在块设备上。

字符设备接口支持面向字符的I/O操做,因为它们不通过系统的快速缓存,因此它们负责管理本身的缓冲区结构。字符设备接口只支持顺序存取的功能,通常不能进行任意长度的I/O请求,而是限制I/O请求的长度必须是设备要求的基本块长的倍数。

1.设备驱动程序的概念

设备驱动程序实际是处理和操做硬件控制器的软件,从本质上讲,是内核中具备最高特权级的、驻留内存的、可共享的底层硬件处理例程。驱动程序是内核的一部分,是操做系统内核与硬件设备的直接接口,驱动程序屏蔽了硬件的细节,完成如下功能:

— 对设备初始化和释放;

— 对设备进行管理,包括实时参数设置,以及提供对设备的操做接口;

— 读取应用程序传送给设备文件的数据或者回送应用程序请求的数据;

— 检测和处理设备出现的错误。

Linux操做系统将全部的设备所有当作文件,并经过文件的操做界面进行操做。对用户程序而言,设备驱动程序隐藏了设备的具体细节,对各类不一样设备提供了一致的接口,通常来讲,是把设备映射为一个特殊的设备文件,用户程序能够像对其余文件同样对此设备文件进行操做。这意味着:

— 因为每个设备至少由文件系统的一个文件表明,于是都有一个“文件名”。

— 应用程序一般能够经过系统调用open()打开设备文件,创建起与目标设备的链接。

— 打开了表明着目标设备的文件,即创建起与设备的链接后,能够经过read()、write()、ioctl()等常规的文件操做对目标设备进行操做。

设备文件的属性由三部分信息组成:第一部分是文件的类型,第二部分是一个主设备号,第三部分是一个次设备号。其中类型和主设备号结合在一块儿唯一地肯定了设备文件驱动程序及其界面,而次设备号则说明目标设备是同类设备中的第几个。

因为Linux 中将设备当作文件处理,因此对设备进行操做的调用格式与对文件的操做相似,主要包括open()、read()、write()、ioctl()、close()等。应用程序发出系统调用命令后,会从用户态转到核心态,经过内核将open()这样的系统调用转换成对物理设备的操做。

2.处理器与设备间数据交换方式

处理器与外设之间传输数据的控制方式一般有3种:查询方式、中断方式和直接内存存取(DMA)方式。

21.查询方式

设备驱动程序经过设备的I/O端口空间,以及存储器空间完成数据的交换。例如,网卡通常将本身的内部寄存器映射为设备的I/O端口,而显示卡则利用大量的存储器空间做为视频信息的存储空间。利用这些地址空间,驱动程序能够向外设发送指定的操做指令。一般来说,因为外设的操做耗时较长,所以,当处理器实际执行了操做指令以后,驱动程序可采用查询方式等待外设完成操做。

2.2.中断方式

查询方式白白浪费了大量的处理器时间,而中断方式才是多任务操做系统中最有效利用处理器的方式。当CPU进行主程序操做时,外设的数据已存入端口的数据输入寄存器,或端口的数据输出寄存器已空,此时由外设经过接口电路向CPU发出中断请求信号。CPU在知足必定条件下,暂停执行当前正在执行的主程序,转入执行相应可以进行输入/输出操做的子程序,待输入/输出操做执行完毕以后,CPU再返回并继续执行原来被中断的主程序。这样,CPU就避免了把大量时间耗费在等待、查询外设状态的操做上,使其工做效率得以大大提升。中断方式的原理示意图如图6.1所示。

                                                                                                                                                   

2.3.直接访问内存(DMA)方式

利用中断,系统和设备之间能够经过设备驱动程序传送数据,可是,当传送的数据量很大时,由于中断处理上的延迟,利用中断方式的效率会大大下降。而直接内存访问(DMA)能够解决这一问题。DMA可容许设备和系统内存间在没有处理器参与的状况下传输大量数据。设备驱动程序在利用DMA以前,须要选择DMA通道并定义相关寄存器,以及数据的传输方向,即读取或写入,而后将设备设定为利用该DMA通道传输数据。设备完成设置以后,能够当即利用该DMA通道在设备和系统的内存之间传输数据,传输完毕后产生中断以便通知驱动程序进行后续处理。在利用DMA进行数据传输的同时,处理器仍然能够继续执行指令。

3.驱动程序结构 

3.1一个设备驱动程序模块的基本框架

设备驱动程序流程图

在系统内部,I/O设备的存取经过一组固定的入口点来进行,入口点也能够理解为设备的句柄,就是对设备进行操做的基本函数。字符型设备驱动程序提供以下几个入口点:

—  open入口点。打开设备准备I/O操做。对字符设备文件进行打开操做,都会调用设备的open入口点。open子程序必须对将要进行的I/O操做作好必要的准备工做,如清除缓冲区等。若是设备是独占的,即同一时刻只能有一个程序访问此设备,则open子程序必须设置一些标志以表示设备处于忙状态。

—  close入口点。关闭一个设备。当最后一次使用设备完成后,调用close子程序。独占设备必须标记设备方可再次使用。

—  read入口点。从设备上读数据。对于有缓冲区的I/O操做,通常是从缓冲区里读数据。对字符设备文件进行读操做将调用read子程序。

—  write入口点。往设备上写数据。对于有缓冲区的I/O操做,通常是把数据写入缓冲区里。对字符设备文件进行写操做将调用write子程序。

—  ioctl入口点。执行读、写以外的操做。

—       select入口点。检查设备,看数据是否可读或设备是否可用于写数据。select系统调用在检查与设备文件相关的文件描述符时使用select入口点。

3.1. file_operations结构体

struct file_operations {

structmodule *owner;

loff_t(*llseek) (struct file *, loff_t, int);

ssize_t(*read) (struct file *, char *, size_t, loff_t *);

ssize_t(*write) (struct file *, const char *, size_t, loff_t *);

int(*readdir) (struct file *, void *, filldir_t);

unsignedint (*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(*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(*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);

unsignedlong (*get_unmapped_area)(

struct file*,

unsignedlong,

unsignedlong,

unsignedlong,

unsignedlong

);

};

—  lseek,移动文件指针的位置,只能用于能够随机存取的设备。

—  read,进行读操做,buf为存放读取结果的缓冲区,count为所要读取的数据长度。

—  write,进行写操做,与read相似。

—  select,进行选择操做。

—  ioctl,进行读、写之外的其余操做。

—  mmap,用于把设备的内容映射到地址空间,通常只有块设备驱动程序使用。

—  open,打开设备进行I/O操做。返回0表示成功,返回负数表示失败。

—  release,即close操做

3.2.inode{}和file{}结构体

inode数据结构体提供关于特别设备文件的信息。file结构体主要是与文件系统对应的设备驱动程序使用。

struct file主要用于与文件系统相关的设备驱动程序,可提供关于被打开的文件的信息,定义以下:

struct file {

struct list_head   f_list;

struct dentry              *f_dentry;

struct vfsmount        *f_vfsmnt;

struct file_operations  *f_op;

atomic_t            f_count;

unsigned int              f_flags;

mode_t                     f_mode;

loff_t                 f_pos;

unsigned long           f_reada,f_ramax, f_raend, f_ralen, f_rawin;

struct fown_struct      f_owner;

unsigned int              f_uid,f_gid;

int                     f_error;

unsigned long            f_version;

/* needed for tty driver, and maybe others */

void                  *private_data;

/* preallocated helper kiobuf to speedup O_DIRECT */

struct kiobuf              *f_iobuf;

long                  f_iobuf_lock;

};

在用户本身的驱动程序中,首先要根据驱动程序的功能,完成file_operations结构中函数的实现。不须要的函数接口能够直接在file_operations结构中初始化为NULL。file_operations中的变量会在驱动程序初始化时,注册到系统内部。每一个进程对设备的操做,都会根据主次设备号,转换成对file_operations结构的访问。

4.设备注册和初始化

设备的驱动程序在加载的时候首先须要调用入口函数init_module(),该函数最重要的一个工做就是向内核注册该设备,对于字符设备调用register_chrdev()完成注册。register_chrdev 的定义为:int register_chrdev(unsignedint major, const char *name, struct file_ operations *fops);

其中,major是为设备驱动程序向系统申请的主设备号,若是为0,则系统为此驱动程序动态分配一个主设备号。name是设备名,fops是对各个调用的入口点说明。此函数返回0时表示成功;返回-EINVAL,表示申请的主设备号非法,主要缘由是主设备号大于系统所容许的最大设备号;返回-EBUSY,表示所申请的主设备号正在被其余设备程序使用。若是动态分配主设备号成功,此函数将返回所分配的主设备号。若是register_chrdev()操做成功,设备名就会出如今/proc/dvices文件中。

Linux在/dev目录中为每一个设备创建一个文件,用ls –l命令列出函数返回值,若小于0,则表示注册失败;返回0或者大于0的值表示注册成功。注册之后,Linux将设备名与主、次设备号联系起来。当有对此设备名的访问时,Linux经过请求访问的设备名获得主、次设备号,而后把此访问分发到对应的设备驱动,设备驱动再根据次设备号调用不一样的函数。

当设备驱动模块从Linux内核中卸载,对应的主设备号必须被释放。字符设备在cleanup_module()函数中调用unregister_chrdev()来完成设备的注销。unregister_chrdev()的定义为:int unregister_chrdev(unsignedint major, const char *name);

此函数的参数为主设备号major和设备名name。Linux内核把name和major在内核注册的名称对比,若是不相等,卸载失败,并返回-EINVAL;若是major大于最大的设备号,也返回-EINVAL。

包括设备注册在内,设备驱动的初始化函数主要完成的功能是有如下5项。

(1)对驱动程序管理的硬件进行必要的初始化。

对硬件寄存器进行设置。好比,设置中断掩码,设置串口的工做方式、并口的数据方向等。

(2)初始化设备驱动相关的参数。

通常说来,每一个设备都要定义一个设备变量,用以保存设备相关的参数。在这一步骤里对设备变量中的项进行初始化。

(3)在内核注册设备。

调用register_chrdev()函数来注册设备。

(4)注册中断。

若是设备须要IRQ支持,则要使用request_irq()函数注册中断。

(5)其余初始化工做。

初始化部分通常还负责给设备驱动程序申请包括内存、时钟、I/O端口等在内的系统资源,这些资源也能够在open子程序或者其余地方申请。这些资源不用时,应该释放,以利于资源的共享。

若驱动程序是内核的一部分,初始化函数则要按以下方式声明:

int __init chr_driver_init(void);

其中__init是必不可少的,在系统启动时会由内核调用chr_driver_init,完成驱动程序的初始化。

当驱动程序是以模块的形式编写时,则要按照以下方式声明:

int init_module(void)

当运行后面介绍的insmod命令插入模块时,会调用init_module函数完成初始化工做。

5.中断管理

设备驱动程序经过调用request_irq函数来申请中断,经过free_irq来释放中断。它们在linux/sched.h中的定义以下:

int request_irq(

unsigned int irq,

void (*handler)(int irq,void dev_id,structpt_regs *regs),

unsigned long flags,

const char *device,

void *dev_id

);

void free_irq(unsigned int irq, void*dev_id);

一般从request_irq函数返回的值为0时,表示申请成功;负值表示出现错误。

— irq表示所要申请的硬件中断号。

— handler为向系统登记的中断处理子程序,中断产生时由系统来调用,调用时所带参数irq为中断号,dev_id为申请时告诉系统的设备标识,regs为中断发生时寄存器内容。

— device为设备名,将会出如今/proc/interrupts文件里。

— flag是申请时的选项,它决定中断处理程序的一些特性,其中最重要的是决定中断处理程序是快速处理程序(flag里设置了SA_INTERRUPT)仍是慢速处理程序(不设置SA_INTERRUPT)。

下面的代码将在SBC-2410X的Linux中注册外部中断2。

eint_irq = IRQ_EINT2;

set_external_irq (eint_irq, EXT_FALLING_EDGE,GPIO_PULLUP_DIS);

ret_val =request_irq(eint_irq,eint2_handler, “S3C2410Xeint2”,0);

if(ret_val < 0){

return ret_val;

}

用来打开和关闭中断的函数以下:

#define cli() _asm_ _volatile_("cli"::)

#define sli() _asm_ _volatile_("sli"::) 。

6.设备驱动程序的开发过程

因为嵌入式设备因为硬件种类很是丰富,在默认的内核发布版中不必定包括全部驱动程序。因此进行嵌入式Linux系统的开发,很大的工做量是为各类设备编写驱动程序。除非系统不使用操做系统,程序直接操纵硬件。嵌入式Linux系统驱动程序开发与普通Linux开发没有区别。能够在硬件生产厂家或者Internet上寻找驱动程序,也能够根据相近的硬件驱动程序来改写,这样能够加快开发速度。实现一个嵌入式Linux设备驱动的大体流程以下。

(1)查看原理图,理解设备的工做原理。通常嵌入式处理器的生产商提供参考电路,也能够根据须要自行设计。

(2)定义设备号。设备由一个主设备号和一个次设备号来标识。主设备号唯一标识了设备类型,即设备驱动程序类型,它是块设备表或字符设备表中设备表项的索引。次设备号仅由设备驱动程序解释,区分被一个设备驱动控制下的某个独立的设备。

(3)实现初始化函数。在驱动程序中实现驱动的注册和卸载。

(4)设计所要实现的文件操做,定义file_operations结构。

(5)实现所需的文件操做调用,如read、write等。

(6)实现中断服务,并用request_irq向内核注册,中断并非每一个设备驱动所必需的。

(7)编译该驱动程序到内核中,或者用insmod命令加载模块。

(8)测试该设备,编写应用程序,对驱动程序进行测试。

7.设备驱动开发的基本函数

7.1.I/O口函数

不管驱动程序多么复杂,归根结底,无非仍是向某个端口或者某个寄存器位赋值,这个值只能是0或1。接收值的就是I/O口。与中断和内存不一样,使用一个没有申请的I/O端口不会使处理器产生异常,也就不会致使诸如“segmentationfault”一类的错误发生。因为任何进程均可以访问任何一个I/O端口,此时系统没法保证对I/O端口的操做不会发生冲突,甚至所以而使系统崩溃。所以,在使用I/O端口前,也应该检查此I/O端口是否已有别的程序在使用,若没有,再把此端口标记为正在使用,在使用完之后释放它。

这样须要用到以下几个函数:

int check_region(unsigned int from,unsigned int extent);

void request_region(unsigned int from,unsigned int extent,const char *name);

void release_region(unsigned int from, unsignedint extent);

调用这些函数时的参数为:

— from表示所申请的I/O端口的起始地址;

— extent为所要申请的从from开始的端口数;

— name为设备名,将会出如今/proc/ioports文件里;

— check_region返回0表示I/O端口空闲,不然为正在被使用。

在申请了I/O端口以后,能够借助asm/io.h中的以下几个函数来访问I/O端口:

inline unsigned int inb(unsigned shortport);

inline unsigned int inb_p(unsigned shortport);

inline void outb(char value, unsigned shortport);

inline void outb_p(char value,unsigned short port);

其中inb_p和outb_p插入了必定的延时以适应某些低速的I/O端口。

7.2.时钟函数

在设备驱动程序中,通常都须要用到计时机制。在Linux系统中,时钟是由系统接管的,设备驱动程序能够向系统申请时钟。与时钟有关的系统调用有:

#include <asm/param.h>

#include <linux/timer.h>

void add_timer(struct timer_list * timer);

int del_timer(struct timer_list * timer);

inline void init_timer(struct timer_list *timer);

struct timer_list的定义为:

struct timer_list {

struct timer_list *next;

struct timer_list *prev;

unsigned long expires;

unsigned long data;

void (*function)(unsigned long d);

};

其中,expires是要执行function的时间。系统核心有一个全局变量jiffies表示当前时间,通常在调用add_timer时jiffies=JIFFIES+num,表示在num个系统最小时间间隔后执行function函数。系统最小时间间隔与所用的硬件平台有关,在核内心定义了常数HZ表示一秒内最小时间间隔的数目,则num*HZ表示num秒。系统计时到预约时间就调用function,并把此子程序从定时队列里删除,可见,若是想要每隔必定时间间隔执行一次的话,就必须在function里再一次调用add_timer。function的参数d即为timer里面的data项。

7.3.内存操做函数

做为系统核心的一部分,设备驱动程序在申请和释放内存时不是调用malloc和free,而代之以调用kmalloc和kfree,它们在linux/kernel.h中被定义为:

void * kmalloc(unsigned int len, intpriority);

void kfree(void * obj);

参数len为但愿申请的字节数,obj为要释放的内存指针。priority为分配内存操做的优先级,即在没有足够空闲内存时如何操做,通常由取值GFP_KERNEL解决便可。

7.4.复制函数

在用户程序调用read、write时,由于进程的运行状态由用户态变为核心态,地址空间也变为核心地址空间。因为read、write中参数buf是指向用户程序的私有地址空间的,因此不能直接访问,必须经过下面两个系统函数来访问用户程序的私有地址空间。

#include <asm/segment.h>

void memcpy_fromfs(void * to,const void *from,unsigned long n);

void memcpy_tofs(void * to,const void *from,unsigned long n);

memcpy_fromfs由用户程序地址空间往核心地址空间复制,memcpy_tofs则反之。参数to为复制的目的指针,from为源指针,n为要复制的字节数。

在设备驱动程序里,能够调用printk来打印一些调试信息,printk的用法与printf相似。printk打印的信息不只出如今屏幕上,同时还记录在文件syslog里。

8.模块加载与卸载

虽然模块做为内核的一部分,但并未被编译到内核中,它们被分别编译和连接成目标文件。Linux中模块能够用C语言编写,用gcc命令编译成模块*.o,在命令行里加上-c的参数和“-D__KERNEL__-DMODULE”参数。而后用depmod -a 使此模块成为可加载模块。模块用insmod命令加载,用rmmod命令来卸载,这两个命令分别调用init_module()和cleanup_ module()函数,还能够用lsmod命令来查看全部已加载的模块的状态。

insmod命令可将编译好的模块调入内存。内核模块与系统中其余程序同样是已连接的目标文件,但不一样的是它们被连接成可重定位映像。insmod将执行一个特权级系统调用get_kernel_sysms()函数以找到内核的输出内容,insmod修改模块对内核符号的引用后,将再次使用特权级系统调用create_module()函数来申请足够的物理内存空间,以保存新的模块。内核将为其分配一个新的module结构,以及足够的内核内存,并将新模块添加在内核模块链表的尾部,而后将新模块标记为uninitialized。

利用rmmod命令能够卸载模块。若是内核中还在使用此模块,这个模块就不能被卸载。缘由是若是设备文件正被一个进程打开就卸载还在使用的内核模块,并致使对内核模块的读/写函数所在内存区域的调用。若是幸运,没有其余代码被加载到那个内存区域,将获得一个错误提示;不然,另外一个内核模块被加载到同一区域,这就意味着程序跳到内核中另外一个函数的中间,结果是不可预见的。

9.实例剖析

9.1实例一

9.1.1驱动程序

/******************************
 *   LED_Driver    2007/09/20        
 *****************************/
#include<linux/config.h>
#include<linux/kernel.h>
#include<linux/sched.h>
#include<linux/timer.h>
#include<linux/init.h>
#include<linux/module.h>
#include<asm/hardware.h>
#defineGPIO_LED_MAJOR   97
#defineARM_GPIO_LED_DEBUG
#defineARM_GPIO_LED (GPIO96)
#defineLED_ON  0
#defineLED_OFF 1
 
#definectl_GPIO_LED1 1
 
#defineVERSION         "ARM_GPIO_LED_2007/09/20"
 
voidshowversion(void)
{
        printk("*********************************************\n");
        printk("\t %s \t\n",VERSION);
        printk("********************************************\n\n");
}
//------------------- READ ------------------------
ssize_tGPIO_LED_read (struct file * file ,char * buf, size_t count, loff_t * f_ops)
{
    #ifdef ARM_GPIO_LED_DEBUG
       printk ("GPIO_LED_read [--kernel--]\n");
    #endif             
    return count;
}  
//------------------- WRITE -----------------------
ssize_tGPIO_LED_write (struct file * file ,const char * buf, size_t count, loff_t *f_ops)
{
    #ifdef ARM_GPIO_LED_DEBUG
           printk("GPIO_LED_write [ --kernel--]\n");
        #endif
    return count;
}  
//------------------- IOCTL -----------------------
ssize_tGPIO_LED_ioctl (struct inode * inode ,struct file * file, unsigned int cmd,long data)
{
    #ifdef ARM_GPIO_LED_DEBUG
           printk("GPIO_LED_ioctl [ --kernel--]\n");
        #endif
    switch (cmd)
        {
       case LED_ON : {GPCR3 |= 0x1;break;}
       case LED_OFF: {GPSR3 |= 0x1;break;}
                default :
                        {printk ("lcdcontrol : no cmd run  [ --kernel--]\n"); return (-EINVAL);}
        }
    return 0;
    }
//------------------- OPEN ------------------------
ssize_tGPIO_LED_open (struct inode * inode ,struct file * file)
{
    #ifdef ARM_GPIO_LED_DEBUG
           printk("GPIO_LED_open [ --kernel--]\n");
        #endif
    MOD_INC_USE_COUNT;
    return 0;
}  
 
//------------------- RELEASE/CLOSE ---------------
ssize_tGPIO_LED_release (struct inode  * inode,struct file * file)
{
    #ifdef ARM_GPIO_LED_DEBUG
           printk ("GPIO_LED_release [ --kernel--]\n");
        #endif
    MOD_DEC_USE_COUNT;
    return 0;
}
//-------------------------------------------------
structfile_operations GPIO_LED_ctl_ops ={
    open:      GPIO_LED_open,
    read:      GPIO_LED_read,
    write:     GPIO_LED_write,
    ioctl:     GPIO_LED_ioctl,
    release:           GPIO_LED_release,
};
//------------------- INIT ------------------------
staticint __init HW_GPIO_LED_CTL_init(void)
{
    int ret = -ENODEV;
    printk("Driver Loding.....................\n\n");
    showversion();
    // init GPIO
    GPDR3 |= 0x00000001; // SET GPIO96 OUTPUTMODE
    GPSR3 |= 0x00000001; // OFF THE LED
    #ifdef ARM_GPIO_LED_DEBUG
        printk (" GPLR3=%x\n",GPLR3);
       printk (" GPDR3=%x \n",GPDR3);
        #endif
    ret = devfs_register_chrdev(GPIO_LED_MAJOR,"led_drv", &GPIO_LED_ctl_ops);
    if( ret < 0 )
    {
       printk (" ARM: init_module failedwith %d\n [ --kernel--]", ret); 
       return ret;
    }
    else
    {
       printk(" ARM gpio_led_driverregister success!!! [ --kernel--]\n");
    }
    return ret;
}
staticint __init ARM_GPIO_LED_CTL_init(void)
{
    int ret = -ENODEV;
 
    #ifdef ARM_GPIO_LED_DEBUG
                printk("ARM_GPIO_LED_CTL_init [ --kernel--]\n");
        #endif
 
 
    ret = HW_GPIO_LED_CTL_init();
    if (ret)
      return ret;
    return 0;
}
 
staticvoid __exit cleanup_GPIO_LED_ctl(void)
{
    #ifdef ARM_GPIO_LED_DEBUG
           printk("cleanup_GPIO_LED_ctl [ --kernel--]\n");
        #endif
   
    devfs_unregister_chrdev (GPIO_LED_MAJOR,"gpio_led_ctl" );
   
}
MODULE_DESCRIPTION("GPIO_led driver module");
MODULE_AUTHOR("zsm");
MODULE_LICENSE("GPL");//GPL协议证书信息
module_init(ARM_GPIO_LED_CTL_init);
module_exit(cleanup_GPIO_LED_ctl);


9.1.2用户程序

在编写用户应用程序过程当中,考虑经过接口open()函数打开设备,再经过接口ioctl()函数来实现对LED的控制功能。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>      // open() close()
#include <unistd.h>     // read() write()
#define DEVICE_NAME "/dev/led_drv"
#define LED_ON 0
#define LED_OFF 1
int main(void)
{
        intfd;
    int ret;
    char *i;
       printf("\nstart GPIO_led_driver test\n\n");
        fd= open(DEVICE_NAME, O_RDWR);
    printf("fd= %d\n",fd);
        if(fd == -1)
        {
               printf("open device %s error\n",DEVICE_NAME);
        }
        else
        {
       while(1)
       {   ioctl(fd,LED_OFF);
           sleep(1);
           ioctl(fd,LED_ON);
           sleep(1);
       }
       ret =close(fd);
       printf("ret=%d\n",ret);
       printf("close led_driver test\n");
        }
       return 0;
}


9.1.3执行效果

(1)Create:

[root@OURSELEC usb]# mknod /dev/led_drv c 97 0

(2)view:

[root@OURSELEC usb]# ls -l /dev/led_drv

crw-r--r--   1 root     root      97,  0 Jan  1 01:29 /dev/led_drv

(3)insmod:

[root@OURSELEC usb]# insmod led_drv.o

Using led_drv.o

ARM_GPIO_LED_CTL_init [ --kernel--]

Driver Loding .....................                                                                               

*********************************************

        ARM_GPIO_LED_2007/09/20

*********************************************

                                                                               

 GPLR3=73e7fd

 GPDR3=1efffc3

 ARMgpio_led_driver register success!!! [ --kernel--]

(4)./test_led:

[root@OURSELEC usb]# ./test_led

                                                                             startGPIO_led_driver test

                                                                                 GPIO_LED_open[ --kernel--]

fd = 3

GPIO_LED_ioctl [ --kernel--]

GPIO_LED_ioctl [ --kernel--]

GPIO_LED_ioctl [ --kernel--]

GPIO_LED_ioctl [ --kernel--]

GPIO_LED_ioctl [ --kernel--]

GPIO_LED_ioctl [ --kernel--]

GPIO_LED_ioctl [ --kernel--]

GPIO_LED_ioctl [ --kernel--]

GPIO_LED_ioctl [ --kernel--]

GPIO_LED_ioctl [ --kernel--]

GPIO_LED_ioctl [ --kernel--]

GPIO_LED_release [ --kernel--]                                                                                 

(5) remove led_drv mode:

[root@OURSELEC usb]# rmmod led_drv

cleanup_GPIO_LED_ctl [ --kernel--]

9.2实例二

/*************************************
NAME:gt2440_leds.c
COPYRIGHT:www.e-online.cc
*************************************/
#include <linux/miscdevice.h>
#include <linux/delay.h>
#include <asm/irq.h>
#include <mach/regs-gpio.h>
#include <mach/hardware.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/mm.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/delay.h>
#include <linux/moduleparam.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/ioctl.h>
#include <linux/cdev.h>
#include <linux/string.h>
#include <linux/list.h>
#include <linux/pci.h>
#include <asm/uaccess.h>
#include <asm/atomic.h>
#include <asm/unistd.h>
 
#define DEVICE_NAME "leds"
 
/* 应用程序执行ioctl(fd, cmd, arg)时的第2个参数 */
#define IOCTL_LED_ON     1
#define IOCTL_LED_OFF    0
 
/* 用来指定LED所用的GPIO引脚 */
static unsigned long led_table [] =
{
       S3C2410_GPB5,
       S3C2410_GPB6,
       S3C2410_GPB7,
       S3C2410_GPB8,
};
 
/* 用来指定GPIO引脚的功能:输出 */
static unsigned int led_cfg_table [] =
{
       S3C2410_GPB5_OUTP,
       S3C2410_GPB6_OUTP,
       S3C2410_GPB7_OUTP,
       S3C2410_GPB8_OUTP,
};
 
static int gt2440_leds_ioctl(
       structinode *inode,
       structfile *file,
       unsignedint cmd,
       unsignedlong arg)
{
       if(arg > 4)
       {
              return-EINVAL;
       }
 
       switch(cmd)
       {
              caseIOCTL_LED_ON:
                     //设置指定引脚的输出电平为0
                     s3c2410_gpio_setpin(led_table[arg], 0);
                     return0;
 
              caseIOCTL_LED_OFF:
                     //设置指定引脚的输出电平为1
                     s3c2410_gpio_setpin(led_table[arg], 1);
                     return0;
 
              default:
                     return-EINVAL;
       }
}
 
static struct file_operations dev_fops = {
       .owner    =     THIS_MODULE,
       .ioctl       =     gt2440_leds_ioctl,
};
 
static struct miscdevice misc = {
       .minor= MISC_DYNAMIC_MINOR,
       .name= DEVICE_NAME,
       .fops= &dev_fops,
};
 
static int __init dev_init(void)
{
       intret;
 
       inti;
      
       for(i = 0; i < 4; i++)
       {
              s3c2410_gpio_cfgpin(led_table[i],led_cfg_table[i]);
              s3c2410_gpio_setpin(led_table[i], 0);
       }
 
       ret= misc_register(&misc);
 
       printk(DEVICE_NAME" initialized\n");
 
       returnret;
}
 
static void __exit dev_exit(void)
{
       misc_deregister(&misc);
}
 
module_init(dev_init);
module_exit(dev_exit);
 
MODULE_LICENSE("GPL");
MODULE_AUTHOR("www.e-online.cc");
MODULE_DESCRIPTION("LEDS control forGT2440 Board");


10块设备驱动程序的编写

块设备文件一般指一些须要以块(如512字节)的方式写入的设备,如IDE硬盘、SCSI硬盘、光驱等。它的驱动程序的编写过程与字符型设备驱动程序的编写有很大的区别。

为了把各类块设备的操做请求队列有效地组织起来,内核中设置了一个结构数组blk_dev,该数组中的元素类型是blk_dev_struct结构。这个结构由三个成分组成,其主体是执行操做的请求队列request_queue,还有一个函数指针queue。当这个指针不为0时,就调用这个函数来找到具体设备的请求队列。块设备驱动程序描述符是一个包含在<linux/blkdev.h>中的blk_dev_struct类型的数据结构,其定义以下所示:

struct blk_dev_struct {

request_queue_t request_queue;

queue_proc *queue;

void *date;

};

在这个结构中,请求队列request_queue是主体,包含了初始化以后的I/O 请求队列。

全部块设备的描述符都存放在blk_dev表struct blk_dev_structblk_dev[MAX_BLKDEV]中;每一个块设备都对应着数组中的一项,可使用主设备号进行检索。每当用户进程对一个块设备发出一个读写请求时,首先调用块设备所公用的函数generic_file_read(),generic_file_write()。若是数据存在在缓冲区中或缓冲区还能够存放数据,那么就同缓冲区进行数据交换。不然,系统会将相应的请求队列结构添加到其对应项的blk_dev_struct中,以下图所示:

10.1块设备驱动编写流程

块设备驱动程序的编写流程同字符设备驱动程序的编写流程很相似,也包括了注册和使

用两部分。但与字符驱动设备所不一样的是,块设备驱动程序包括一个request请求队列。它是

当内核安排一次数据传输时在列表中的一个请求队列,用以最大化系统性能为原则进行排序。

块设备驱动程序流程图

 

10.2重要数据结构

Linux系统中有一个名为blkdevs的结构数组,它描述了一系列在系统中登记的块设备。数组blkdevs也使用设备的主设备号做为索引,其元素类型是device_struct结构。该结构中包括指向已登记的设备驱动程序名的指针和指向block_device_operations结构的指针。在block_device_operations结构中包含指向有关操做的函数指针。因此,该结构就是链接抽象的块设备操做与具体块设备类型的操做之间的枢纽。

10.2.1 struct bio

一个bio结构体是在通用块层或更底层对块设备i/o操做的的表示单位。一般1个bio对应1个I/O请求.

struct bio {
     sector_t        bi_sector;      /* device address in 512 byte
                                    sectors */
      structbio      *bi_next; /*request queue link */
     structblock_device *bi_bdev;
     unsignedlong         bi_flags;   /* status, command, etc */
     unsignedlong         bi_rw;      /* bottom bits READ/WRITE,
                                 * top bits priority
                                  */
 
     unsignedshort        bi_vcnt;   /*how many bio_vec's */
     unsignedshort        bi_idx;          /* current index into bvl_vec */
 
      /* Numberofsegments in this BIO after
       *physical address coalescing is performed.
       */
     unsignedint       bi_phys_segments;
 
     unsignedint       bi_size;    /*residual I/O count */
 
      /*
       * Tokeep track of the max segment size, weaccount for the
       *sizes of the first and last mergeablesegments in this bio.
       */
     unsignedint       bi_seg_front_size;
     unsignedint       bi_seg_back_size;
 
      unsignedint      bi_max_vecs;     /* max bvl_vecs we can hold */
 
      unsignedint      bi_comp_cpu;    /* completion CPU */
 
     atomic_t      bi_cnt;           /*pin count*/
 
     structbio_vec         *bi_io_vec;   /* the actual vec list */
 
     bio_end_io_t          *bi_end_io;
 
     void              *bi_private;
#if defined(CONFIG_BLK_DEV_INTEGRITY)
     structbio_integrity_payload *bi_integrity;  /*data integrity */
#endif
 
     bio_destructor_t     *bi_destructor;  /* destructor */
 
      /*
       * Wecan inline a number of vecs at the end ofthe bio, to avoid
       *double allocations for a small number ofbio_vecs. This member
       * MUSTobviously be kept at the very end ofthe bio.
       */
     structbio_vec          bi_inline_vecs[0];
};


10.2.2 struct gendisk


struct gendisk {   //表示一个独立的磁盘设备或分区
  intmajor;                /* major number of driver */
  intfirst_minor;       /*starting minor number*/
  intminors;       /* maximumnumber ofminors, =1 for
                 *disks that can't be partitioned. 每个分区都有一个minor号*/
 
  chardisk_name[DISK_NAME_LEN];  /* name ofmajor driver */
 
  structdisk_part_tbl *part_tbl;
  structhd_struct part0;
 
  structblock_device_operations *fops;
  structrequest_queue*queue;
  void*private_data;
 
  int flags;
  struct device*driverfs_dev;  // FIXME: remove
  struct kobject*slave_dir;
 
  structtimer_rand_state *random;
 
 atomic_tsync_io;         /* RAID */
  structwork_struct async_notify;
#ifdef CONFIG_BLK_DEV_INTEGRITY
  structblk_integrity *integrity;
#endif
  int node_id;
};
 
struct device_struct {
const char *name;
struct file_operations *chops;
};
static struct device_structblkdevs[MAX_BLKDEV];
struct sbull_dev {
void **data;
int quantum;// thecurrent quantum size
int qset;// the current array size
unsigned long size;
unsigned int access_key;// used by sbulluid and sbullpriv
unsigned int usage;// lock the device while using it
unsigned int new_msg;
struct sbull_dev *next;// next listitem
};
与字符设备驱动程序同样,块设备驱动程序也包含一个file_operation结构,其结构定义通常以下所示:
struct file_operation blk_fops = {
NULL,//seek
block_read,//内核函数
block_write,//内核函数
NULL,//readdir
NULL,//poll
sbull_ioctl,// ioctl
NULL,//mmap
sbull_open,//open
NULL,//flush
sbull_release,//release
block_fsync,//内核函数
NULL,//fasync
sbull_check_media_change,//check media change
NULL,//revalidate
NULL,//lock
};


全部的块驱动程序都调用内核函数block_read()、block_write(),block_fsync()函数,因此在块设备驱动程序入口中不包含这些函数,只需包括ioctl()、open()

和release()函数便可。

(1)设备初始化

块设备的初始化过程要比字符设备复杂,它既须要像字符设备同样在引导内核时完成必定的

工做,还须要在内核编译时增长一些内容。块设备驱动程序初始化时,由驱动程序的init()完成。

块设备驱动程序初始化的工做主要包括:

· 检查硬件是否存在;

· 登记主设备号;

· 将fops结构的指针传递给内核;

· 利用register_blkdev()函数对设备进行注册:

if(register_blkdev(sbull_MAJOR,“sbull”,&sbull_fops)) {

printk(“Registering block device major:%d failed\n”,sbull_MAJOR);

return-EIO;

};

· 将request()函数的地址传递给内核:

blk_dev[sbull_MAJOR].request_fn= DEVICE_REQUEST;

· 将块设备驱动程序的数据容量传递给缓冲区:

#define sbull_HARDS_SIZE 512

#define sbull_BLOCK_SIZE 1024

static int sbull_hard = sbull_HARDS_SIZE;

static int sbull_soft = sbull_BLOCK_SIZE;

hardsect_size[sbull_MAJOR] = &sbull_hard;

blksize_size[sbull_MAJOR] = &sbull_soft;

在块设备驱动程序内核编译时,应把下列宏加到blk.h文件中:

#define MAJOR_NR sbull_MAJOR

#define DEVICE_NAME “sbull”

#define DEVICE_REQUEST sbull_request

#define DEVICE_NR(device) (MINOR(device))

#define DEVICE_ON(device)

#define DEVICE_OFF(device)

(2)request操做

Request操做涉及一个重要的数据结构以下。

struct request {

kdev_t rq_dev;

int cmd; // 读或写

int errors;

unsigned long sector;

char *buffer;

struct request *next;

};

对于具体的块设备,函数指针request_fn固然是不一样的。块设备的读写操做都是由request()函数完成。全部的读写请求都存储在request结构的链表中。request()函数利用CURRENT宏

检查当前的请求。request()函数从INIT_REQUEST宏命令开始(它也在blk.h中定义),它对请求队列进行检查,保证请求队列中至少有一个请求在等待处理。若是没有请求(即CURRENT = 0),则INIT_REQUEST宏命令将使request()函数返回,任务结束。

    假定队列中至少有一个请求,request()函数如今应处理队列中的第一个请求,当处理完

请求后,request()函数将调用end_request()函数。若是成功地完成了读写操做,那么应该用参数值1 调用end_request()函数;若是读写操做不成功,那么以参数值0 调用end_request()函数。若是队列中还有其余请求,那么将CURRENT 指针设为指向下一个请求。执行end_request()函数后,request()函数回到循环的起点,对下一个请求重复上面的处理过程。

(3)打开操做

(4)释放设备操做

(5)ioctl操做

10.3中断编程

不少Linux 的驱动都是经过中断的方式来进行内核和硬件的交互。

这是驱动程序申请中断和释放中断的调用。在include/linux/sched.h里声明。

request_irq()调用的定义:

int request_irq(unsigned int irq,

void (*handler)(int irq, void*dev_id, struct pt_regs *regs),

unsigned long irqflags,const char* devname,oid *dev_id);

irq 是要申请的硬件中断号。在Intel平台,范围是0~15。handler 是向系统登记的中断处理函数。这是一个回调函数,中断发生时,系统调用这个函数,传入的参数包括硬件中断号,device id,寄存器值。dev_id就是下面的request_irq时传递给系统的参数dev_id。irqflags是中断处理的一些属性。比较重要的有SA_INTERRUPT,标明中断处理程序是快速处理程序(设置SA_INTERRUPT)仍是慢速处理程序(不设置SA_INTERRUPT)。快速处理程序

被调用时屏蔽全部中断。慢速处理程序不屏蔽。还有一个SA_SHIRQ 属性,设置了之后运行多个设备共享中断。dev_id在中断共享时会用到。通常设置为这个设备的device结构自己或者NULL。中断处理程序能够用dev_id找到相应的控制这个中断的设备,或者用irq2dev_map

找到中断对应的设备。void free_irq(unsigned int irq,void *dev_id);

10.4一个简单的块设备驱动

经过写一个创建在内存中的块设备驱动,来学习linux内核和相关设备驱动知识

#defineSIMP_BLKDEV_DISKNAME        "simp_blkdev"

#defineSIMP_BLKDEV_BYTES        (16*1024*1024)// 使用宏定义了块设备的大小,定为16M

#defineSIMP_BLKDEV_DEVICEMAJOR        COMPAQ_SMART2_MAJOR

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

structblock_device_operations simp_blkdev_fops = {
          .owner               = THIS_MODULE,
  };// gendisk结构须要设置fops指针,虽然咱们用不到,但该设仍是要设的

static structgendisk *simp_blkdev_disk;

static structrequest_queue *simp_blkdev_queue;// 指向块设备须要的请求队列

unsigned charsimp_blkdev_data[SIMP_BLKDEV_BYTES];

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

module_init(simp_blkdev_init);//而后申明模块的入口和出口

 module_exit(simp_blkdev_exit);

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

static int __initsimp_blkdev_init(void) //在入口处添加这个设备、出口处删除这个设备
  {

simp_blkdev_disk = alloc_disk(1); //在添加设备以前咱们须要申请这个设备的资源,这用到了alloc_disk()函数

strcpy(simp_blkdev_disk->disk_name,SIMP_BLKDEV_DISKNAME);  //设备有关的属性也是须要设置
simp_blkdev_disk->major = SIMP_BLKDEV_DEVICEMAJOR;
simp_blkdev_disk->first_minor = 0;

simp_blkdev_disk->fops =&simp_blkdev_fops;
simp_blkdev_disk->queue = simp_blkdev_queue;
set_capacity(simp_blkdev_disk, SIMP_BLKDEV_BYTES>>9);

 add_disk(simp_blkdev_disk);

if (!simp_blkdev_disk) {
ret = -ENOMEM;

goto err_alloc_disk;
        }

 simp_blkdev_queue =blk_init_queue(simp_blkdev_do_request, NULL);//初始化请求队列
 if (!simp_blkdev_queue) {
             ret = -ENOMEM;
             goto err_init_queue;
  }//在加载模块时用simp_blkdev_do_request()函数的地址做参数

调用blk_init_queue()初始化一个请求队列

//用来从一个请求队列中拿出一条请求(其实严格来讲,拿出的多是请求中的一段)。
随后的处理请求本质上是根据rq_data_dir(req)返回的该请求的方向(读/写),把块设备中的数据装入req->buffer、或是把req->buffer中的数据写入块设备。

static voidsimp_blkdev_do_request(struct request_queue *q) //请求队列的处理函数。
{
        struct request *req;
        while ((req = elv_next_request(q)) != NULL) {
                if ((req->sector +req->current_nr_sectors) << 9
                       > SIMP_BLKDEV_BYTES) {
                       printk(KERN_ERR SIMP_BLKDEV_DISKNAME
                               ": bad request: block=%llu,count=%u\n",
                               (unsigned long long)req->sector,
                               req->current_nr_sectors);
                       end_request(req, 0);
                       continue;
                }

                switch (rq_data_dir(req)){
                case READ:
                       memcpy(req->buffer,
                               simp_blkdev_data + (req->sector <<9),
                               req->current_nr_sectors << 9);
                       end_request(req, 1);
                       break;
                case WRITE:
                       memcpy(simp_blkdev_data + (req->sector << 9),
                               req->buffer, req->current_nr_sectors<< 9);
                       end_request(req, 1);
                       break;
                default:
                       /* No default because rq_data_dir(req) is 1 bit */
                       break;
                }
        }
}
 return 0;
err_alloc_disk:
        blk_cleanup_queue(simp_blkdev_queue);
err_init_queue:
        return ret;}
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
static void __exit simp_blkdev_exit(void)
{
      del_gendisk(simp_blkdev_disk);
put_disk(simp_blkdev_disk);  
blk_cleanup_queue(simp_blkdev_queue);
}

转自: http://blog.csdn.net/ce123/article/details/6581260#_Toc289386898