当前位置:文档之家› 第11章 嵌入式Linux设备驱动开发

第11章 嵌入式Linux设备驱动开发

“黑色经典”系列之《嵌入式Linux应用程序开发详解》

第11章嵌入式Linux设备驱动开发

本章目标

本书从第6章到第10章详细讲解了嵌入式Linux应用程序的开发,这些都是处于用户空间的内容。本章将进入到Linux的内核空间,

初步介绍嵌入式Linux设备驱动的开发。驱动的开发流程相对于应用

程序的开发是全新的,与读者以前的编程习惯完全不同,希望读者能

尽快地熟悉现在环境。经过本章的学习,读者将会掌握以下内容。

Linux设备驱动的基本概念

Linux设备驱动的运作过程

掌握LCD设备驱动程序编写步骤

能够独立定制Linux服务

华清远见<嵌入式Linux应用开发班>培训教材

11.1 设备驱动概述

11.1.1 设备驱动简介及驱动模块

操作系统是通过各种驱动程序来驾驭硬件设备的,它为用户屏蔽了各种各样的设备,驱动硬件是操作系统最基本的功能,并且提供统一的操作方式。设备驱动程序是内核的一部分,硬件驱动程序是操作系统最基本的组成部分,在Linux内核源程序中也占有60%以上。因此,熟悉驱动的编写是很重要的。

在第2章中已经提到过,Linux内核中采用可加载的模块化设计(LKMs,Loadable Kernel Modules),一般情况下编译的Linux内核是支持可插入式模块的,也就是将最基本的核心代码编译在内核中,其他的代码可以选择在内核中,或者编译为内核的模块文件。

常见的驱动程序也是作为内核模块动态加载的,比如声卡驱动和网卡驱动等,而Linux 最基础的驱动,如CPU、PCI总线、TCP/IP协议、APM(高级电源管理)、VFS等驱动程序则直接编译在内核文件中。有时也把内核模块叫做驱动程序,只不过驱动的内容不一定是硬

?rmmod是用于将当前模块卸载。

?insmod和modprobe是用于加载当前模块,但insmod不会自动解决依存关系,而modprobe则可以根据模块间依存关系以及/etc/modules.conf文件中的内容自动插入模块。

?mknod是用于创建相关模块。

华清远见<嵌入式Linux应用开发班>培训教材

>培训教材

11.1.2 设备文件分类

本书在前面也提到过,Linux 的一个重要特点就是将所有的设备都当做文件进行处理,这一类特殊文件就是设备文件,它们可以使用前面提到的文件、I/O 相关函数进行操作,这样就大大方便了对设备的处理。它通常在/dev 下面存在一个对应的逻辑设备节点,这个节点以文件的形式存在。

Linux 系统的设备文件分为三类:块设备文件、字符设备文件和网络设备文件。

? 块设备文件通常指一些需要以块(如512字节)的方式写入的设备,如IDE 硬盘、SCSI 硬盘、光驱等。

? 字符型设备文件通常指可以直接读写,没有缓冲区的设备,如并口、虚拟控制台等。 ? 网络设备文件通常是指网络设备访问的BSD socket 接口,如网卡等。

对这三种设备文件编写驱动程序时会有一定的区别,本书在后面会有相关内容的讲解。

11.1.3 设备号

设备号是一个数字,它是设备的标志。就如前面所述,一个设备文件(也就是设备节点)可以通过mknod 命令来创建,其中指定了主设备号和次设备号。主设备号表明某一类设备,一般对应着确定的驱动程序;次设备号一般是用于区分标明不同属性,例如不同的使用方法,不同的位置,不同的操作等,它标志着某个具体的物理设备。高字节为主设备号和底字节为次设备号。例如,在系统中的块设备IDE 硬盘的主设备号是3,而多个IDE 硬盘及其各个分区分别赋予次设备号1、2、3……

11.1.4 驱动层次结构

Linux 下的设备驱动程序是内核的一部分,运行在内核模式,也就是说设备驱动程序为内核提供了一个I/O 接口,用户使用这个接口实现对设备的操作。

图11.1显示了典型的Linux 输入/输出系统中各层次结构和功能。

输/输出 输/输出 入

图11.1 Linux 输入/输出系统层次结构和功能

Linux设备驱动程序包含中断处理程序和设备服务子程序两部分。

设备服务子程序包含了所有与设备操作相关的处理代码。它从面向用户进程的设备文件系统中接受用户命令,并对设备控制器执行操作。这样,设备驱动程序屏蔽了设备的特殊性,使用户可以像对待文件一样操作设备。

设备控制器需要获得系统服务时有两种方式:查询和中断。因为Linux下的设备驱动程序是内核的一部分,在设备查询期间系统不能运行其他代码,查询方式的工作效率比较低,所以只有少数设备如软盘驱动程序采取这种方式,大多设备以中断方式向设备驱动程序发出输入/输出请求。

11.1.5 设备驱动程序与外界的接口

每种类型的驱动程序,不管是字符设备还是块设备都为内核提供相同的调用接口,因此内核能以相同的方式处理不同的设备。Linux为每种不同类型的设备驱动程序维护相应的数据结构,以便定义统一的接口并实现驱动程序的可装载性和动态性。Linux设备驱动程序与外界的接口可以分为如下三个部分。

图11.2 设备驱动程序与外界的接口

华清远见<嵌入式Linux应用开发班>培训教材

11.1.6 设备驱动程序的特点

综上所述,Linux中的设备驱动程序有如下特点。

(1)内核代码:设备驱动程序是内核的一部分,如果驱动程序出错,则可能导致系统崩溃。

(2)内核接口:设备驱动程序必须为内核或者其子系统提供一个标准接口。比如,一个终端驱动程序必须为内核提供一个文件I/O接口;一个SCSI设备驱动程序应该为SCSI子系统提供一个SCSI设备接口,同时SCSI子系统也必须为内核提供文件的I/O接口及缓冲区。

(3)内核机制和服务:设备驱动程序使用一些标准的内核服务,如内存分配等。

(4)可装载:大多数的Linux操作系统设备驱动程序都可以在需要时装载进内核,在不需要时从内核中卸载。

(5)可设置:Linux操作系统设备驱动程序可以集成为内核的一部分,并可以根据需要把其中的某一部分集成到内核中,这只需要在系统编译时进行相应的设置即可。

(6)动态性:在系统启动且各个设备驱动程序初始化后,驱动程序将维护其控制的设备。如果该设备驱动程序控制的设备不存在也不影响系统的运行,那么此时的设备驱动程序只是多占用了一点系统内存罢了。

11.2 字符设备驱动编写

字符设备驱动编写流程

1.流程说明

在上一节中已经提到,设备驱动程序可以使用模块的方式动态加载到内核中去。加载模块的方式与以往的应用程序开发有很大的不同。以往在开发应用程序时都有一个main函数作为程序的入口点,而在驱动开发时却没有main函数,模块在调用insmod命令时被加载,此时的入口点是init_module函数,通常在该函数中完成设备的注册。同样,模块在调用rmmod 函数时被卸载,此时的入口点是cleanup_module函数,在该函数中完成设备的卸载。在设备完成注册加载之后,用户的应用程序就可以对该设备进行一定的操作,如read、write等,而驱动程序就是用于实现这些操作,在用户应用程序调用相应入口函数时执行相关的操作,init_module入口点函数则不需要完成其他如read、write之类功能。

上述函数之间的关系如图11.3所示。

>培训教材

图11.3 设备驱动程序流程图

2.重要数据结构

这里定义的很多函数读者在第6章中已经见到过了,当时是调用这些函数,而在这里我们将学习如何实现这些函数。当然,每个设备的驱动程序不一定要实现其中所有的函数操作,

华清远见<嵌入式Linux应用开发班>培训教材

若不需要定义实现时,则只需将其设为NULL即可。

其中,struct inode提供了关于设备文件/dev/driver(假设此设备名为driver)的信息。struct file提供关于被打开的文件信息,主要用于与文件系统对应的设备驱动程序使用。struct file 较为重要,这里列出了它的定义:

struct file {

mode_t f_mode;/*标识文件是否可读或可写,FMODE_READ或FMODE_WRITE*/

dev_t f_rdev; /* 用于/dev/tty */

off_t f_pos; /* 当前文件位移*/

unsigned short f_flags; /* 文件标志,如O_RDONLY、O_NONBLOCK和O_SYNC */

unsigned short f_count; /* 打开的文件数目*/

unsigned short f_reada;

struct inode *f_inode; /*指向inode的结构指针*/

struct file_operations *f_op;/* 文件索引指针*/

};

(2)设备解除注册

在关闭设备时,通常需要解除原先的设备注册,此时可使用函数unregister_chrdev,此后该设备就会从/proc/devices里消失。

unregister_chrdev函数格式如下表11.2所示:

表11.2 unregister_chrdev等函数语法要点

所需头文件#include

>培训教材

函数原型int unregister_chrdev(unsigned int major, const char *name)

major:设备的主设备号,必须和注册时的主设备号相同。

函数传入值

name:设备名

成功:0,且设备名从/proc/devices文件里消失。

函数返回值

出错:?1

(3)打开设备

打开设备的接口函数是open,根据设备的不同,open函数完成的功能也有所不同,但通常情况下在open函数中要完成如下工作。

?递增计数器。

?检查特定设备的特殊情况。

?初始化设备。

?识别次设备号。

其中递增计数器是用于设备计数的。由于设备在使用时通常会打开较多次数,也可以由不同的进程所使用,所以若有一进程想要关闭该设备,则必须保证其他设备没有使用该设备。

因此使用计数

器就可以很好地完成这项功能。

这里,实现计数器操作的是用在中定义的3个宏如下。

?MOD_INC_USE_COUNT:计数器加一。

?MOD_DEC_USE_COUNT:计数器减一。

?MOD_IN_USE:计数器非零时返回真。

另外,当有多个物理设备时,就需要识别次设备号来对各个不同的设备进行不同的操作,在有些驱动程序中并不需要用到。

注意虽然这是对设备文件执行的第一个操作,但却不是驱动程序一定要声明的操作。若这个函数的入口为NULL,那么设备的打开操作将永远成功,但系统不会通知驱动程序。

(4)释放设备

释放设备的接口函数是release。要注意释放设备和关闭设备是完全不同的。当一个进程释放设备时,其他进程还能继续使用该设备,只是该进程暂时停止对该设备的使用;而当一个进程关闭设备时,其他进程必须重新打开此设备才能使用。

释放设备时要完成的工作如下。

?递减计数器MOD_DEC_USE_COUNT。

?在最后一次释放设备操作时关闭设备。

(5)读写设备

读写设备的主要任务就是把内核空间的数据复制到用户空间,或者从用户空间复制到内核空间,也就是将内核空间缓冲区里的数据复制到用户空间的缓冲区中或者相反。这里首先解释一个read和write函数的入口函数,如表11.3所示。

华清远见<嵌入式Linux应用开发班>培训教材

表11.3 read、write函数语法要点所需头文件#include

函数原型ssize_t (*read) (struct file *filp, char *buff, size_t count, loff_t *offp) ssize_t (*write) (struct file *filp, const char *buff, size_t count, loff_t *offp) filp:文件指针

buff:指向用户缓冲区

count:传入的数据长度

函数传入值

offp:用户在文件中的位置

函数返回值成功:写入的数据长度

虽然这个过程看起来很简单,但是内核空间地址和应用空间地址是有很大区别的,其中之一就是用户空间的内存是可以被换出的,因此可能会出现页面失效等情况。所以就不能使用诸如memcpy之类的函数来完成这样的操作。在这里就要使用copy_to_user或copy_from_user函数,它们就是用来实现用户空间和内核空间的数据交换的。

要注意,这两个函数不仅实现了用户空间和内核空间的数据转换,而且还会检查用户空间指针的有效性。如果指针无效,那么就不进行复制。

(6)获取内存

在应用程序中获取内存通常使用函数malloc,但在设备驱动程序中动态开辟内存可以有基于内存地址和基于页面为单位两类。其中,基于内存地址的函数有kmalloc,注意的是,kma- lloc函数返回的是物理地址,而malloc等返回的是线性地址,因此在驱动程序中不能使用malloc函数。与malloc()不同,kmalloc()申请空间有大小限制。长度是2的整次方,并且不会对所获取的内存空间清零。

基于页为单位的内存有函数族有如下。

?get_zeroed_page:获得一个已清零页面。

?get_free_page:获得一个或几个连续页面。

?get_dma_pages:获得用于DMA传输的页面。

与之相对应的释放内存用也有kfree或free_pages族。

>培训教材

表11.5给出了kmalloc函数的语法格式。

表11.5 kmalloc函数语法要点

表11.7给出了基于页的分配函数get_free_ page族函数的语法格式。

表11.7 get_free_ page类函数语法要点

所需头文件#include

函数原型unsigned long get_zeroed_page(int flags)

unsigned long __get_free_page(int flags)

unsigned long __get_free_page(int flags,unsigned long order) unsigned long __get_dma_page(int flags,unsigned long order) flags:同kmalloc

函数传入值

order:要请求的页面数,以2为底的对数

函数返回值成功:写入的数据长度失败:-EFAULT

表11.8给出了基于页的内存释放函数free_ page族函数的语法格式。

华清远见<嵌入式Linux应用开发班>培训教材

表11.8 free_page类函数语法要点所需头文件#include

函数原型unsigned long free_page(unsigned long addr) unsigned long free_page(unsigned long addr) flags:同kmalloc

函数传入值

order:要请求的页面数,以2为底的对数

函数返回值成功:写入的数据长度失败:-EFAULT

(7)打印信息

就如同在编写用户空间的应用程序,打印信息有时是很好的调试手段,也是在代码中很常用的组成部分。但是与用户空间不同,在内核空间要用函数printk而不能用平常的函数printf。printk和printf很类似,都可以按照一定的格式打印消息,所不同的是,printk还可以定义打印消息的优先级。

表11.9给出了printk函数的语法格式。

这些不同优先级的信息可以输出到控制台上、/var/log/messages里。其中,对输出给控制台的信息有一个特定的优先级console_loglevel。若优先级小于这个整数值时,则消息才能显示到控制台上,否则,消息会显示在/var/log/messages里。若不加任何优先级选项,则消息默认输出到/var/log/messages文件中。

注意

要开启klogd和syslogd服务,消息才能正常输出。

4.proc文件系统

/proc文件系统是一个伪文件系统,它是一种内核和内核模块用来向进程发送信息的机

>培训教材

制。这个伪文件系统让用户可以和内核内部数据结构进行交互,获取有关进程的有用信息,在运行时通过改变内核参数改变设置。与其他文件系统不同,/proc存在于内存之中而不是硬盘上。读者可以通过“ls”查看/proc文件系统的内容。

表11.10列出了/proc文件系统的主要目录内容。

表11.10 /proc文件系统主要目录内容

用户可以使用cat命令来查看其中的内容。

可以看到,/proc文件系统体现了内核及进程运行的内容,在加载模块成功后,读者可以使用查看/proc/device文件获得相关设备的主设备号。

11.3 LCD驱动编写实例

11.3.1 LCD工作原理

S3C2410LCD控制器用于传输视频数据和产生必要的控制信号,如VFRAME、VLINE、

华清远见<嵌入式Linux应用开发班>培训教材

VCLK、VM等。除了控制信号,S3C2410还有输出视频数据的端口VD[23:0],如图11.4所示。

图11.4 S3C2410 LCD控制器

(1)寄存器介绍

LCD的寄存器主要有:LCDCON1寄存器、LCDCON2寄存器、LCDCON3寄存器、LCDC- ON4寄存器和LCDCON5寄存器。

(2)控制流程

LCD控制器由REGBANK、LCDCDMA、VIDPRCS和LPC3600组成(如图11.5所示)。

图11.5 S3C2410 LCD控制器内部方框图

(3)数据流描述

FIFO存储器位于LCDCDMA。当FIFO空或部分空时,LCDCDMA要求从基于突发传输模式的帧存储器中取来数据,存入要显示的图像数据,而这个帧存储器是LCD控制器在RAM中开辟的一片缓冲区。当这个传送请求被存储控制器中的总线仲裁器接收到后,从系统存储器到内部FIFO

>培训教材

就会成功传送4个字。FIFO的总大小是28个字,其中低位FIFOL是12个字,高位FIFOH是16个字。S3C2410有两个FIFO来支持双扫描显示模式。在单扫描模式下,只使用一个FIFO(FIFOH)。

(4)TFT控制器操作

S3C2410支持STN-LCD和TFT-LCD。TIMEGEN产生LCD驱动器的控制信号,如VSYNC、HSYNC、VCLK、VDEN和LEND等。这些控制信号与REGBANK寄存器组中的LCDCONl/2/3/4/5寄存器的配置关系相当密切,基于LCD控制寄存器中的这些可编程配置,TIMEGEN产生可编程控制信号来支持不同类型的LCD驱动器。

VSYNC和HSYNC脉冲的产生依赖于LCDCON2/3寄存器的HOZV Al域和LINEV AL域的配置。HOZV AL和LINEV AL的值由LCD屏的尺寸决定,如下公式:

HOZV AL=水平显示尺寸?1 (1)

LINEV AL=垂直显示尺寸?1 (2)VCLK信号的频率取决于LCDCONl寄存器中的CLKV AL域。VCLK和CLKV AL的关系如下,其中CLKV Al的最小值是0:

VCLK(Hz)=HCLK/L(CLKV AL+1)×2] (3)

华清远见<嵌入式Linux应用开发班>培训教材

>培训教材

华清远见<嵌入式Linux应用开发班>培训教材

>培训教材

华清远见<嵌入式Linux应用开发班>培训教材

>培训教材

华清远见<嵌入式Linux应用开发班>培训教材

相关主题
文本预览
相关文档 最新文档