1.嵌入式驱动程序基本原理
设备驱动程序的任务就是控制设备的硬件完成指定的I/O 操作,所以在设备管理中驱动程序是直接和设备硬件打交道的。驱动程序包含了对设备进行各种操作的代码,在操作系统的控制下,CPU通过执行驱动程序来实现对设备底层硬件的处理和操作Linux的设备驱动程序的主要功能是:对设备进行初始化;启动或停止设备的运行;把设备上的数据传送到内存;把数据从内存传送到设备;检测设备状态。
Linux内核把驱动程序划分为三类:字符设备、块设备、网络设备驱动,对于不同的设备有不同的访问方式。
*字符设备
字符设备(char device )和普通文件系统的区别:普通文件系统可以来回读/写,而大多字符设备仅仅是数据通道,只能顺序读/写。字符设备上 Linux 最简单的设备,可以像文件一样访问。应用程序使用标准系统调用打开,读取,写和关闭,完全好像这个设备是一个普通文件一样。初始化字符设备时,它的设备驱动程序向 Linux 登记,并在字符设备向量表中增加一个 device_struct 数据结构条目,这个设备的主设备标识符(例如,对于 tty 设备的主设备标识符是 4 )用做这个向量表的索引。一个设备的主设备标识符是固定的。chrdevs 向量表维护已经登记的字符设备文件。
*块设备
块设备(block device )是文件系统的物质基础,它也支持像文件一样访问。这种为打开的块特殊文件提供正确的文件系统操作组的机制和字符设备的十分相似。Linux 用 blkdevs 向量表维护已经登记的块设备文件。它象 chrdevs 向量表一样,使用设备的主设备号作为索引。与字符设备不同,块设备进行分类,SCSI 是其中一类,而 IDE 是另一类。
*网络设备驱动
对于每一个网络接口,都用一个 device 的数据结构表示。通常,网络设备是一个物理设备,如以太网卡,但软件也可以作为网络设备,典型的是回送设备(loopback ) .在内核启动时,系统通过网络设备驱动程序登记已经存在的网络设备。设备用标准的支持网络的机制来把收到的数据转送到相应的网络层。关于网络设备驱动更详细的信息请查看相关资料。
系统调用是操作系统内核与应用程序之间的接口,驱动程序则是操作系统内核与机器硬件的接口。设备驱动程序能够直接访问硬件的代码,必须为应用程序提供系统调用。以便应用程序能访问设备。驱动程序的调用是一致的,采用统一的接口(在数据结构file _operations 中)。应用程序使用设备就像使用读写普通的文件一样方便,使用相同的 open ( ),close( ) ,read ( ) ,write ( )等,真正做到了与设备无关。
通常 Linux 驱动程序接口分为如下四层:
(1)应用程序进程与内核的接口;
(2)内核与文件系统的接口;
(3)文件系统与设备驱动程序的接口;
(4)设备驱动程序与硬件设备的接口。
每个驱动程序都有个 file_operations 的数据结构,包含了一系列的函数指针,可以指向自己所开发的接口如 open ( )等。内核中有两个表,一个用于字符设备驱动程序,一个用于块设备驱动程序。这个两个表用于保存指向 file_operations 数据结构的指针,驱动程序内部函数的地址保存在这一结构。
Linux 系统通过设备号来区分不同设备。设备号由两部分组成:主设备号和次设备号。主设备号指明对应哪些设备驱动,这种对应关系是固定不变并作为内核资源的一部分存在。需要注意的是,同一个主设备号可以对应两个不同的设备驱动,一个可以是字符设备另一个可以是块设备。
次设备号区分被一个设备驱动控制下的某个独立的设备。比如,同一个类型的 USB 设备可以在系统中有几个,它们通过次设备好加以区分,而设备驱动可以只对应一个。在/proc/ devices 中列出了系统中处于活动状态设备的主设备号,所谓的活动状态是指与该设备对应的设备驱动已经被系统内核装载。
设备入口点也可以理解为“设备文件句柄”,一个设备的入口点和磁盘上的普通文件系统一样,可以删除(rm ) ,移动(mv )和复制(cp )等。
我们可以在文件系统中使用 mknod 命令创建一个设备入口点或者通过系统调用 mknof 来创建。在文件系统中创建了设备入口点并没有代表响应的设备驱动和硬件已经准备好,只是代表了和设备驱动通信的一部分。
下面给一个创建字符设备入口点的实例:
#mknod /dev/testChar c 100 0
其中 c 代表字符设备,如果想创建块设备则用 b 代替 c 。参数 100 代表该设备的主设备号,0 代表该设备的次设备号。
对于现有 Linux 操作系统,/dev 目录是必不可少的,这个目录包含了所有 Linux 系统所知道的字符设备,块设备和网络设备。
操作字符设备的方法非常简单。打开一个字符设备就像打开一个文本文件一样,只不过读/写“文件”的操作实际上是操作设备的过程,可以使用正常的文件操作命令 cat 或者外壳复位向语法实现和设备的数据交换。
在 Linux 下加载驱动程序可以采用动态和静态两种方式。静态加载就是把驱动程序直接编译到内核里,系统启动后可以直接调用。静态加载的缺点是调试起来比较麻烦,每次修改一个地方都要重新编译下载内核,效率较低。动态加载利用了 Linux 的 module 特性,可以在系统启动后用 insmod 命令把驱动程序(.o 文件)添加上去,在不需要的时候用 rmmod 命令来卸载。在台式机上一般采用动态加载的方式。在嵌入式产品里可以先用动态加载的方式来调试,调试完毕后再编译到内核里。
设备驱动程序在加载时首先需要调用入口函数 init_module ( ) ,该函数完成设备驱动的初始化工作,比如寄存器置位,结构体附值等一系列工作。其中最重要的一个工作就是向内核注册该设备,对于字符设 备调用 register_chrdev ( )完成注册,对于块设备需要调用 register_blkdev ( )完成注册。注册成功后,该设备获得了系统分配的主设备号,自定义的次设备号,并建立起与文件系统的关联。设备在卸载的时候,需要回收响应的资源,令设备的响应寄存器值复位并从系统中注销该设备。系统调用部分则是对设备的操作过程,比如 open , read ,write , ioctl 等操作。
2.LED驱动程序举例
要写实际的驱动,就必须了解相关的硬件资源,比如用到的寄存器,物理地址,中断等,在这里,LED 是一个很简单的例子,它用到了如下硬件资源。
开发板上所用到的4 个LED 的硬件资源
LED | 对应IO |
LED1 | GPK4 |
LED2 | GPK5 |
LED3 | GPK6 |
LED4 | GPK7 |
驱动程序leds.c
#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>
#include <mach/map.h>
#include <mach/regs-clock.h>
#include <mach/regs-gpio.h>
#include <plat/gpio -cfg.h>
#include <mach/gpio-bank -e.h>
#include <mach/gpio-bank -k.h>
#define DEVICE_NAME “leds”
static long sbc2440_leds_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
switch(cmd) {
unsigned tmp;
case 0:
case 1:
if (arg > 4) {
return -EINVAL;
}
tmp = readl(S3C64XX_GPKDAT);
tmp &= ~(1 << (4 + arg));
tmp |= ( (!cmd) << (4 + arg) );
writel(tmp, S3C64XX_GPKDAT);
return 0;
default:
return -EINVAL;
}
}
static struct file_operations dev_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = sbc2440_leds_ioctl,
};
static struct miscdevice misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEVICE_NAME,
.fops = &dev_fops,
};
static int __init dev_init(void)
{
int ret;
{ unsigned tmp;
tmp = readl(S3C64XX_GPKCON);
tmp = (tmp & ~(0xffffU<<16))|(0x1111U
writel(tmp, S3C64XX_GPKCON);
tmp = readl(S3C64XX_GPKDAT);
tmp |= (0xF << 4);
writel(tmp, S3C64XX_GPKDAT);
}
ret = misc_register(&misc);
printk (DEVICE_NAME“\ tinitialized\ n“);
return ret; }
static void __exit dev_exit(void)
{ misc_deregister(&misc); }
module_init(dev_init);
module_exit(dev_exit);
MODULE_LICENSE(“GPL“);
MODULE_AUTHOR(“FriendlyARM Inc.“);
编译led驱动程序,然后下载到开发板运行它。LED测试程序参考如下: