Alessandro的《linux设备驱动程序》,编写驱动时,发现并不能够成功的在你的linux平台上编译通过、或不能正常执行。你的朋友会告诉你,你用的内核和书里的不一致。那该怎么办呢?
我想从两个方面去解释这个问题,一方面是如何写好linux设备驱动,另一方面是如何应对不断升级的内核。
如何写好Linux设备驱动
Linux设备驱动是linux内核的一部分,是用来封装硬件细节,为上层提供标准接口的一种方法。为了能够编写出质量比较高的驱动,要求工程师必须具备以下几个方面的知识:
熟悉处理器的性能
如:处理器的体系结构、汇编语言、工作模式、异常处理等此项对于初学者来说,重要程度:***。也就是说还不熟悉驱动编写方法的情况下,可
以先不把重心放在这一项上,因为可能因为它的枯燥、抽象而影响到你对设备驱动的兴趣。
随着你不断的熟悉驱动的编写,你会很自然的意识到此项的重要性。
掌握驱动目标的硬件工作原理及通讯协议
如:串口串口
串口是计算机上一种非常通用的设备通信协议,大多数计算机包含两个基于RS232的串口。串口同时也是仪器仪表设备的通信协议,并可用于获取远程采集设备的数据。 [全文]
控制器、显卡显卡
显卡作为电脑主机里的一个重要组成部分,承担输出显示图形的任务,它将计算机系统所需要的显示信息进行转换驱动,并向显示器提供行扫描信号,控制显示器的正确显示。 [全文]
控制器、硬件编解码、存储卡存储卡
CF卡是最早出现的存储卡,也是现在应用最广的存储卡。目前我们常见的CF卡有TypeI和TypeII两种规格,分为3.3V和5V两种工作电压。所有CF卡都可以在两种电压下工作,但是我们的设备支持TypeI和TypeII中的哪类则需要参考设备说明书。CF卡在数码相机领域应用最为广泛。有部分PDA和MP3播放器也使用CF卡作为存储介质。金士顿CF卡产品线非常完善,普通CF卡从128MB到1GB容量;专业级的高速Elite Pro CF卡则覆盖256MB-4GB的容量,并提供最高达10MB/s的存取速度。 [全文]
控制器、I2C通讯、SPI通讯、USB通讯、SDIO通讯、I2S通讯、PCI通讯等
此项的重要程度应该不用多说了,编写设备驱动的前提就是知道设备的操作方法。但不是说要把所有设备的操作方法都熟悉了以后才可以驱动,你只需要了解你要驱动的硬件就可以了。所有这一项对于初学者来说重要程度都是:*****。
掌握硬件的控制方法
如:中断、轮询、DMA 通常一个硬件控制器会有多种控制方法,你需要根据系统性能的需要合理的选择操作方法。
此项对于初学者来说:重要程度:****。初学阶段以实现功能为目的。掌握的顺序应该是,轮询->中断->DMA。随着学习的深入,需要综合考虑系统的性能需求,采取合适的方法。
良好的GNU C语言编程基础
如:C语言的指针、结构体、内存内存
内存的正式叫法是内存储器,以此来与外存储器区分开。物理上它安装在计算机内部,通常安装在主板上,所以称为内存。它的作用是供暂时存储处理器需要处理的数据或处理后的结果,可见内存是计算机处理器的工作空间。它是处理器运行的程序和数据必须驻留于其中的一个临时存储区域,是计算机十分重要的部件。 [全文]
操作、链表、队列、栈、C和汇编混合编程等。
这些编程语法是编写设备驱动的基础。
此项无论对于初学者还是熟手重要程度:*****。
良好的linux操作系统概念
如:多进程、多线程、进程调度、进程抢占、进程上下文、虚拟内存、原子操作、阻塞、睡眠、同步等概念及它们之间的关系。
这些概念及方法在设备驱动的使用是linux设备驱动区别单片机单片机
单片机是单片微型计算机(Single-Chip Microcomputer)的简称,是一种将中央处理器CPU随机存储器RAM、只读存储器ROM、多种I/O口和中断系统、定时器/计时器等功能(可能还包括显示驱动电路、脉宽调制电路、模拟多路转换器、A/D转换器等电路)采用超大规模集成电路技术集成到一块硅片上构成的微型计算机系统。 [全文]
编程的最大特点。只有理解了它们才会编写出高质量的驱动。
此项对于初学者来说:重要程度:***。开始可以以实现功能为目的,逐步完善自己的驱动。
掌握linux内核中设备驱动的编写接口
如:字符设备的cdev、块设备的gendisk、网络设备的net_device,以及基于这些基本接口的frAMEbuffer设备的fb_info、mtd设备的mtd_info、tty设备的tty_driver、usb设备的usb_driver、mmc设备的mmc_host等
Linux内核为设备驱动编写者留下了标准的接口。驱动编写者无需精通内核的各个部分,只需要明确内核留给我们的接口,并实现此接口就可以了。内核流出的接口采用的是面向对象的思路,即把目标设备看成一个对象,通常利用一个结构体来描述这个对象。驱动工程师的任务就是实现这个对象。这个结构体中会包含设备的属性(用变量表示)和操作方法(用函数指针表示)。如:字符设备的cdev
struct cdev {
struct kobject kobj;
struct module *owner;
cONst struct file_operations *ops; //操作方法结合,其它项都是属性
struct list_head list;
dev_t dev;
unsigned int count;
};
此项对于初学者来说:重要程度:****。开始阶段可以以模仿为主,即套用一些固定的模板。
如何应对不断升级的内核
内核升级对驱动的影响主要体现在,(1)驱动接口定义的变化(2)内核的一些功能函数的名称、参数、头文件、宏定义的变化(3)平台代码关于硬件操作方面封装的一些函数的变化(4)设备模型的影响。下面探讨一下,如何应对这几个方面的问题:
驱动接口定义的变化
如:2.4内核中字符设备驱动的注册接口是
int register_chrdev(unsigned int major, const char * name, struct file_operations *fops)
而2.6内核中已经不建议使用这种方法了,改为:
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
又如:2.6.27内核中网卡网卡
网卡即插在主机箱内,使计算机能够与外界局域网的连接的网络接口板。比较规范的名字应为通信适配器或网络适配器或网络接口卡,网卡是人们口语化的称呼。网卡是工作在物理层的网路组件,是局域网中连接计算机和传输介质的接口。 [全文]
接口的net_device结构成员和低版本的net_device结构成员也发生了一些变化。
这种接口定义及注册方法带来的变化,发生的并不频繁。解决方案是:参考内核中的代码。这种接口定义及注册方法在内核中非常容易找到,如:字符设备驱动的注册方法及接口定义可以参照内核driver/char/目录下的很多实例。
内核的一些功能函数的名称、参数、头文件、宏定义的变化
如:中断注册函数的格式及参数在2.4内核、2.6内核低版本和高版本之间都存在差别
在2.6.8中,中断注册函数的定义为:
int request_IRq(unsigned int irq, irqreturn_t (*handler)(int, void *, struct pt_regs *),unsigned long irq_flags, const char * devname, void *dev_id)
irq_flags的取值主要为下面的某一种或组合:
SA_INTERRUPT、SA_SAMPLE_RANDOM、SA_SHIRQ
在2.6.26中,中断注册函数的定义为:
int request_irq(unsigned int irq, irq_handler_t handler,unsigned long irqflags, const char *devname, void *dev_id)
typedef irqreturn_t (*irq_handler_t)(int, void *);
irq_flags的取值主要为下面的某一种或组合:(功能和2.6.8的对应)
IRQF_DISABLEDLED
LED(Light Emitting Diode)即发光二极管,是一种固态的半导体器件,它可以直接把电转化为光。LED 的心脏是一个半导体的晶片,当电流通过导线作用于这个晶片的时候,电子和空穴就会被推向量子阱,在量子阱内电子跟空穴复合,然后就会以光子的形式发出能量。能完成数十种不同的工作,并且在各种设备中都能找到它们的身影。例如它们可以组成电子钟表 表盘上的数字,从遥控器 传输信息,为手表表盘照明并在设备开启时向您发出提示。 如果将它们集结在一起,可以组成超大电视屏幕上的图像,或是用于点亮交通信号灯。 [全文]
、IRQF_SAMPLE_RANDOM、IRQF_SHARED
当出现这些问题时,编译过程中,编译器会给我们比较明确的错误提示,根据这些提示你可以判断出是否是缺少头问题、是否是函数参数定义有误等。解决问题的最好办法还是到你的目标内核中找信息。此时找问题的方法可以借助于搜索,如:你可以在新的内核中搜索request_irq,看新内核中的驱动是如何使用它的。这种方法非常有效。
平台代码关于硬件操作方面封装的一些函数的变化
内核中,硬件平台相关的代码在内核更新过程中变化比较频繁。和我们的设备驱动也是息息相关。所以在针对一个新内核编写设备驱动前,一定要熟悉你的平台代码的结构。有时平台虽然提供了内核要求的接口函数,但使用起来功能却并不完善。下面还是先举个例子说明平台代码更新对设备驱动的影响。
如:在linux-2.6.8内核中,调用set_irq_type(IRQ_EINT0, IRQT_FALLING);去设置S3C2410的IRQ_EINT0的中断触发信号类型,你会发现不会有什么效果。跟踪代码发现内核的set_irq_type函数需要平台提供一个针对硬件平台的实现函数
static struct irqchip s3c_irqext_chip = {
.mask = s3c_irqext_mask,
.unmask = s3c_irqext_unmask,
.ack = s3c_irqext_ack,
.type = s3c_irqext_type
};
s3c_irqext_type就是linux内核需要的实现函数,而s3c_irqext_type在2.6.8中的实现为:
static int s3c_irqext_type(unsigned int irq, unsigned int type)
{
irqdbf("s3c_irqext_type: called for irq %d, type %d\n", irq, type);
return 0;
}
原来并没有实现。而在较高版本的内核,如2.6.26内核中,这个函数是实现了的。所以你一定要小心。当平台函数不好用时,一定要查查原因,或者直接操作硬件寄存器来达到目的。
2.6内核设备模型对驱动的影响
在2.6内核中写设备驱动和在2.4内核中有着很大的不同,就是在设备驱动中融入了比设备驱动本身结构还复杂,难以理解的设备模型。初学驱动时你可以不理会设备模型,但你会发现内核里的驱动代码基本上都是融入了设备模型的了。所以很多时候你不得不面对现实,还是要弄懂它,并且它也的注册方法也会随着内核的升级而发生变化。解决此类问题的最好方法还是参考目标内核驱动代码。
总结:
开始学习设备驱动时,选择一个当前比较流行的内核版本和硬件平台。不着急追赶最新潮流。这样你可以找到的网络资源会比较多,不至于有孤军奋战的感觉。我想这个过程应该不低于1年。当过了这个过程后,尝试将你编写过的驱动移植到各个目标平台上。上面的一些建议、和应对方法是本人的一些经验总结,仅供参考。
摘自:http://www.dzsc.com/data/html/2009-8-3/78046.html