课程列表

基本问题

开源资源

好好学习天天向上

联大大纲—通信电子 ;教师介绍; 单片机课程学习经验- 学习路线图; 1.概述 -应用 -定义 -特点 -构成; 2.嵌入式处理器 —DIY CPU处理器ARM处理器Cortex-A8S5PV210讨论; 3.汇编语言 -作业 4.Bootloader -作业 5.Linux内核移植 6.嵌入式Linux程序设计 7.图形用户接口QT 8.其他框架介绍; 9.嵌入式物联网应用系统设计

Linux驱动程序设计

Linux设备驱动概述

Linux操作系统中文件类型分为普通文件、目录文件、链接文件和设备文件。Linux可以把设备当作文件一样来进行操作。在使用系统所含设备之前需要事先编写好两类程序:驱动程序和应用程序

 

针对系统中一个硬件设备编写驱动程序,实际就是实现设备文件操作方法的过程

驱动程序应具有以下功能:设备申请释放设备初始化、实现系统内核和硬件设备之间的数据交互检测和处理设备的异常以及响应用户程序提出的申请

 

设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来硬件设备只是一个设备文件。设备驱动程序实现了系统内核与系统硬件设备之间接口,基于操作系统的应用程序可以象操作普通文件一样实现对硬件设备进行操作。操作Linux系统中硬件设备的过程,就是在应用程序中调用驱动程序来完成一个文件操作的过程。

驱动程序特征

应用程序使用一个main函数,运行状态。应用程序可以和GLIBC 库连接,因此可以包含标准的头文件如“stdio.h”,“stdlib.h”。

 

驱动程序是函数集合体,没有main函数。驱动程序需要完成在内核中的加载和注册,才可以被应用程序调用。

加载方式分为内核启动时加载和内核运行过程中按需要动态加载两种方式。在驱动程序中不使用标准C库的,因此不能调用所有的C库函数。

 

当驱动程序以模块方式与内核链接时,insmod会检查模块和当前内核版本是否匹配,每个模块都定义有版本符号“_module_kernel_version”,这个符号位于模块文件ELF头的“modinfo”段中。只要在模块中包含/include/linux/module.h,编译器就会自动定义这个符号。每个内核版本都需要特定版本的编译器支持,高版本编译器并不适合低版本内核。

主设备号用来标识设备对应的驱动程序,次设备号用来标识唯一的设备。主设备号相同的设备调用相同的驱动程序。

[root@Cyb-Bot /dev]# ls –l                                              

crw-rw----    1 root     root     10, 242 Aug 10  2012 CEC              

crw-rw----    1 root     root     10, 243 Aug 10  2012 HPD              

crw-rw----    1 root     root     10,  59 Aug 10  2012 adc              

crw-rw----    1 root     root     10,  54 Aug 10  2012 alarm            

crw-rw----    1 root     root     10,  58 Aug 10  2012 android_adb      

crw-rw----    1 root     root     10,  57 Aug 10  2012 ndroid_adb_enable

crw-rw----    1 root     root     10, 134 Aug 10  2012 apm_bios         

crw-rw----    1 root     root     10,  63 Aug 10  2012 ashmem           

crw-rw----    1 root     root     10,  55 Aug 10  2012 backlight-1wire  

crw-rw----    1 root     root      5,   1 Aug 10  2012 console          

crw-rw-rw-    1 root     tty     204,  64 Aug 10 08:00 ttySAC0          

crw-rw-rw-    1 root     tty     204,  65 Aug 10  2012 ttySAC1          

crw-rw-rw-    1 root     tty     204,  66 Aug 10  2012 ttySAC2          

crw-rw-rw-    1 root     tty     204,  67 Aug 10  2012 ttySAC3    

设备驱动程序接口

通常所说的设备驱动程序接口是指file_operations{}结构。定义在/include/linux/fs.h文件中。

成 员 名                             描               述

Owner                         module 的拥有者。

Llseek                         重新定位读写位置。

Read                           从设备中读取数据。

Write                          向字符设备中写入数据。

Readdir                      只用于文件系统,对设备无用。

Ioctl                            控制设备,除读写操作外的其他控制命令。

Mmap                        将设备内存映射到进程地址空间,通常只用于块设备。

Open                          打开设备并初始化设备。

Flush                          清除内容,一般只用于网络文件系统中。

Release                      关闭设备并释放资源。

Fsync                          实现内存与设备的同步,如将内存数据写入硬盘。

Fasync                        实现内存与设备之间的异步通讯。

Lock                            文件锁定,用于文件共享时的互斥访问。

Readv                         在进行读操作前要验证地址是否可读。

Writev                        在进行写操作前要验证地址是否可写。

Open方法与Release方法

驱动程序中Open方法应完成如下工作:

(1)递增使用计数器。

(2)检查特定设备错误。

(3)如果设备是首次打开,则对其进行初始化。

(4)识别次设备号,如有必要修改f_op 指针。

(5)分配并填写filp->private_data 中的数据。

 

与open方法相反,release方法应完成如下功能:

(1)释放由open分配的filp->private_data 中所有内容。

(2)在最后一次关闭操作时关闭设备。

(3)使用计数器减一。

Read 和Write 方法

ssize_t demo_write(struct file *filp,const char * buffer, size_t count,loff_t *ppos)

ssize_t demo_read(struct file *filp, char *buffer, size_t count, loff_t *ppos)

/*

filp:文件指针。

Count:请求传输数据的长度。

Buffer:用户空间的数据缓冲区。

ppos:文件中进行操作的偏移量,类型为64位整数。*/

由于用户空间和内核空间的内存映射方式完全不同,所以不能使用象memcpy之类的函数,必须使用如下函数:

unsigned long copy_to_user (void *to,const void *from,unsigned long count);

unsigned long copy_from_user(void *to,const void *from,unsigned long count);

返回值

read方法

write 方法

= count

请求数据传输成功

请求数据传输成功

>0且 <count

表明部分数据传输成功,根据设备的不同,导致这个问题的原因也不同,可以再次调用read。

表明部分数据传输成功,根据设备的不同,导致这个问题的原因也不同,可以再次调用write。

= 0

未读出数据。

没有写入任何数据。

< 0

表示出现错误,错误号定义参见include/linux/errno.h。

 

ioctl 方法

ioctl方法主要用于实现对设备进行读写之外的其它控制操作,如配置设备参数、进入或退出某种操作模式

1.ioctl 函数原型

2.cmd参数

为了防止向不该控制的设备发出正确命令,Linux驱动的ioctl方法中cmd 参数推荐使用唯一编号,编号分为4个字段。编号定义规则如下:

(1)type:也称为幻数,8 位宽。

(2)number:顺序数,8 位宽。

(3)direction:如果该命令有数据传输,就要定义传输方向。可使用的数值分别有_IOC_NONE、_IOC_READ、_IOC_WRITE。

(4)size:数据大小,宽度与体系结构有关,在ARM上为14 位。

3.ioctl 方法返回值

ioctl通常实现一个基于switch 语句命令处理

阻塞型IO

read调用有时会经常会出现当前没有数据可读,但是马上就会有数据到达的情况,这时内核就会使用睡眠并等待数据的方法,这就是阻塞型IO,write也使用同样的方法进行处理。在阻塞型IO过程中,一个进程会涉及到睡眠、唤醒和在阻塞情况下查看是否有数据等多个状态。

内核通过等待队列机制来处理多个进程的睡眠与唤醒。处理过程中要使用到如下几个函数和结构(函数和结构的定义在/include/linux/wait.h/文件中)。

struct __wait_queue_head {wq_lock_t lock;struct list_head task_list;};

typedef struct __wait_queue_head wait_queue_head_t;

static inline void init_waitqueue_head(wait_queue_head_t *q)

完成了队列的声明和初始化后,进程就可以睡眠。根据睡眠深浅不同,可调用sleep_on 的不同变体函数完成睡眠。一般会用到如下几个函数:

sleep_on(wait_queue_head_t *queue);

中断处理

中断是一种事件的处理方法。Linux 驱动程序需要调用以下两个函数,来实现中断处理功能。

(1)请求允许并安装某个中断源的处理程序。

函数原型(函数原型定于/include/linux/interrupt.h):

request_irq(unsigned int irq,void (*handler)(int, void *, struct pt_regs *),

unsigned long flag,const char * dev_name,void *dev_id);

/*

irq:允许中断请求的中断源所对应的中断号。

Handler:指向中断处理程序。

flag:与中断管理相关的位掩码

dev_name:用于在/proc/interrupts 中显示的中断源设备名。

dev_id:用于标识产生中断的设备号。*/

在使用中断方式处理一个中断源的申请之前,必须使用该函数完成中断处理模式的申请工作,允许相应中断源提出的申请。在设备第一次open时使用request_irq函数完成设置过程。

(2)释放中断。

extern void free_irq(unsigned int, void *);

使用该函数可以释放或禁用一个中断源,在关闭设备时使用free_irq

调试方法

1.使用printk 函数按日志级别或消息优先级打印。

例如:

printk(KERN_DEBUG “Here is :%s: %i \n”,__FILE,__LINE__);

在/include/linux/kernel.h文件中定义了8 种可用日志级别字符串。编译器在编译时会将“KERN_DEBUG”和后面的文本拼接在一起输出到显示设备。

日志级别为0~7,当优先级小于Console_loglevel这个整数时,上述使用printk打印的消息才能被显示到控制台。若printk语句没有指定日志级别,默认级别是DEFAULT_ MESSAGE_LOGLEVEL。

2.使用/proc 文件系统

Linux内核的/fs/proc/目录下的每一个文件都被绑定到一个内核函数,这个函数在此文件被读取时动态生成文件的内容。

如命令“ps”和 “top”

3.使用ioctl 方法

在编写驱动程序的同时,可以通过设置多个命名号来编写一些测试函数,使用ioctl系统调用在用户程序中调用这些函数进行调试。

4.使用strace 命令进行调试

strace命令跟踪程序执行时发生的系统调用和显示调用的参数及接收到的返回值,输出到标准输出设备或输出到通过命令参数所指定的文件。

加载方法

1.可编译进内核或独立加载

2.独立加载命令:insmod 插入  rmmod卸载

 

例子

驱动程序demo.c

驱动程序 (LED)

按键中断驱动及控制

驱动程序( ttytest)