Linux下的驱动程序开发

Linux下的驱动程序开发
Linux下的驱动程序开发

Linux下的驱动程序开发

李伟民(1)胡炜(2) 浙江大学计算机技术专业

摘要:本文主要从学习Linux下驱动程序的基本概念入手,了解Linux下驱动程序的结构和框架。通过自己的学习深入了解在Linux环境下开发驱动程序的过程,通过学习,我们自己动手编写了一个USB的鼠标和键盘驱动程序。这对我们来说是一个从无到有的过程。

关键词:Linux、驱动程序

1 引言

1.1Linux设备驱动程序分类

Linux系统将设备分为三类:字符设备(Char Device)、块设备(Block Device)和网络设备(Network Device)三种。

字符设备(Char Device)是指存取时没有缓存的设备,典型的字符设备包括鼠标,键盘,串行口等。

块设备(Block Device)是指读写都有缓存来支持,并且块设备必须能够随机存取(random access),字符设备则没有这个要求。块设备主要包括硬盘设备,CD-ROM等。

网络设备(Network Device)在Linux里做专门处理。Linux的网络系统主要是基于BSD Unix的socket机制。在系统和驱动程序之间定义有专门的数据结构(sk_buff)进行数据的传递。系统里支持对发送数据和接收数据的缓存,提供流量控制机制,提供对多协议的支持。

1.2Linux下驱动程序的几个基本概念

在学习Linux下的驱动程序的时候,经常会遇到以下一些概念。

1.2.1 轮询与中断

内核与外设间的数据传输一般可以采用轮询(polling)或中断(interrupt)方式。

轮询方式:轮询方式的驱动程序在启动设备后会连续读取设备状态直到设备

完成操作,处于用户空间的进程进入内核开始执行设备驱动程序。当设备执行I/O 操作时,与其相应的任务周期性地轮询设备状态寄存器以决定操作何时完成。

请求

中断方式:采用中断的驱动程序在启动设备后就挂起,直到设备完成操作并发出一个中断请求(IRQ)。当IRQ产生时,中断处理程序运行(ISR)运行,他的一些代码可能会放到Bottom half中或者放到任务队列中。在这种情况下,用户进程使用驱动程序代码初始化I/O操作,然后阻塞自己直到设备完成操作。在收到IRQ后,运行与设备对应的中断处理程序,它会唤醒沉睡的进程重新执行用户空间进程。

进程继续

请求

1.2.2主设备号和次设备号

内核使用主、次设备号来唯一标识设备。

主设备号(major number)用于标识设备对应的驱动程序,主设备号相同的设备使用相同的驱动程序。例如在linux下,软驱的主设备号是2,IDE硬盘的主驱动号是3,并口的主设备号是6。

次设备号(minor number)是一个8bit数,用来区分具体设备的实例(instance)。因此同一个机器上的两个软驱具有相同的主设备号2,但是第一个软驱的次设备号是0,第二个软驱的次设备号是1。

设备号操作宏:MAJOR()和MINOR()分别用来获取主、次设备号,MKDEV()根据主、次设备号合成设备号(dev number)。

在Linux内核源码中,主、次设备号通过宏MKDEV()合成为一个变量,作为设备号(dev)保存,高位保存的是主设备号(major number)低位保存的是次设备号(minor number),需要时只需要利用MAJOR()和MINOR()两个宏定义便能够简单的将两个设备号区分出来。

1.2.3 设备文件

Linux中各种设备的输入、输出就好像是对普通文件输入、输出一样。因此,只需要将设备映射到一种特殊的文件(采用mknod进行系统调用或者直接进行devfs中的设备结点注册)就可以可达到上述目的。

系统启动的时候,内核给系统中的每个设备都创建了一个设备文件。一个设备文件是/dev目录下的一项,它用于表示设备的驱动程序。利用Linux命令mknod 可以在/dev目录下生成该设备对应的结点:

mknod /dev/

其中:参数是这个特殊文件的名字(可以在/dev目录下的特殊文件列表中看到它)。参数为c表示的是字符设备,b表示的是块设备。参数为主设备号和次设备号。

如果设备文件系统devfs已经在系统中正常应用了,就不需要手工创建设备结点了。

在用户的应用程序中,当需要访问该设备时,只需要采用通常的文件操作函数即可对该设备进行访问。首先采用fopen()函数打开设备,活得文件指针。然

后利用该文件指针进行的read、write、ioctl等操作。

上述这些过程都是用虚拟文件系统VFS进行统筹管理的。根据该文件inode 中的信息,为该文件结点安装合适的file_operations{}结构,从而达到将文件操作与设备操作对应起来的效果。

同样的,此时如果采用Llinux命令cat等也可以直接从设备获得信息,其原理就是它会自动调用文件读操作函数。

2驱动程序的框架

Linux的设备驱动程序与外界的接口可以分为三部分:

●驱动程序与操作系统内核的接口:这是通过

file_operations(include/linux/fs.h)数据结构来完成的。

●驱动程序与系统引导的接口:这部分利用驱动程序对设备进行初始化。

●驱动程序与设备的接口:这部分描述了驱动程序如何与设备进行交互,

这与具体设备密切相关。

根据功能划分,设备驱动程序的代码由以下几个部分:

●设备的打开与释放;

●设备的读写操作;

●设备的控制操作;

●设备的中断和轮讯处理。

2.1 驱动程序的注册与注销

设备驱动程序可以在系统启动时初始化,也可以在需要使用时动态加载。

字符设备的初始化由chr_dev_init()完成,包括对内存、终端、打印机、鼠标等字符设备的初始化。块设备的初始化由blk_dev_init()完成,这包括对IDE硬盘、软盘、光驱等块设备的初始化。

初始化字符设备或块设备是通过devfs_register_chrdev()或devfs_register_blkdev()向内核注册。

注销字符设备或块设备是通过devfs_unregister_chrdev()或devfs_unregister_blkdev()从内核中注销。

2.2 设备的打开与释放

打开设备由open()完成,例如打印机的打开是lp_open(),硬盘的打开是hd_open()。大部分的驱动程序完成以下一些工作:

●增加设备的使用计数;

●检查设备的相关错误,如设备尚未准备好或类似的硬件问题。

●如果是首次打开,则初始化设备。

●识别次设备号,如有必要则更新f_op指针;

●如果需要,分配且设置要放在filp->private_data里的数据结构。

释放设备与打开设备正好相反,由release()完成。例如释放打印机是lp_release(),而释放终端设备是tty_release()。释放设备一般需要作以下几件事情:

●释放在filp->private_data中open分配的内存。

●如果是最后一个释放,则关闭设备。

●递减设备的使用计数。

2.3 设备的读写操作

字符设备使用各自的read()和write()来进行数据读写。例如对虚拟终端的读写是通过vcs_read()和vcs_write();块设备使用generic_file_read()和generic_file_write()进行数据读写。这两个通用函数向请求表中添加读写请求,内核可以通过ll_rw_block()优化请求顺序。由于是对内存缓冲区而不是对设备进行操作的,数据传输。这是通过数据结构request_queue()中的request_fn()来完成。

2.4 设备的控制操作

除了读写操作,有时候还需要控制设备。这可以通过设备驱动程序中的ioctl()来完成。例如IDE硬盘的控制可以用hd_ioctl(),对于光驱的控制可以使用cdrom_ioctl()。

与读写操作不同,ioctl()的用法与具体设备密切相。除了ioctl(),设备驱动程序还可能有其他的控制函数,例如llseek()等。

2.5 设备的轮讯与中断

对于不支持中断的设备,读写时需要轮讯设备状态,以决定是否需要继续进行数据传输。例如,打印机驱动程序在缺省时轮讯打印机的状态。

如果设备支持中断,则可以按照中断的方式进行。

3 实际程序一个USB的鼠标和键盘的驱动程序

程序运行环境是Linux2.4版本,主要包括usb-sysdep.h和usb.c两个程序

3.1 usb-sysdep.h

#ifndef _USB_SYSDEP_H_

#define _USB_SYSDEP_H_

struct usb_device_id {

int class;

int subclass;

int protocol;

unsigned long driver_info;

};

#define USB_INTERFACE_INFO(cl,sc,pr)

class: (cl), subclass: (sc), protocol: (pr)

#endif /* _USB_SYSDEP_H_ */

3.2 usb.c

#ifndef __KERNEL__

#define __KERNEL__

#endif

#ifndef MODULE

#define MODULE

#endif

#include

#include

#include

#include

#include

#include

/* Note: If you write a backward-portable driver with both USB and something-else support, you need to separate the USB stuff in order not to rely on sysdep.h in USB-related files*/

#if 0

#include "sysdep.h"

#else

#include "usb-sysdep.h"

#endif

/*need local data structure, as it must be allocated for new mouse device plugged in the USB bus*/

struct sample_device {

unsigned char data[8]; /* enough for keyboard and mouse protocols */

char *name; /* either "kdb" or "mouse" */

struct urb urb; /* USB Request block, to get USB data*/

int maxp; /* packet len */

char output[80]; /* used for printk at irq time */

};

/* Handler for data sent in by the device The function is called by the USB kernel subsystem whenever a device spits out new data */

static void sample_irq(struct urb *urb)

{

struct sample_device *sample = urb->context;

char *pos = sample->output;

int i;

if (urb->status != USB_ST_NOERROR) return;

pos += sprintf(pos, "usbsample: data from %-8s =", sample->name);

for (i=0; imaxp; i++) {

pos += sprintf(pos, " %02x", sample->data[i]);

}

printk(KERN_INFO "%s\n", sample->output);

}

/*Two callbacks are invoked when an USB device is detached or attached to the bus*/ static void sample_disconnect(struct usb_device *udev, void *clientdata)

{

/* the clientdata is the sample_device we passed originally */

struct sample_device *sample = clientdata;

/* remove the URB, remove the input device, free memory */

usb_unlink_urb(&sample->urb);

kfree(sample);

printk(KERN_INFO "sample: USB %s disconnected\n", sample->name);

/*MOD_DEC_USE_COUNT, but only if you increment the count in sample_probe() below */

return;

}

static void *sample_probe(struct usb_device *udev, unsigned int ifnum,

const struct usb_device_id *id)

{

/*The probe procedure is pretty standard. Device matching has already been performed based on the id_table structure (defined later) */

struct usb_interface *iface;

struct usb_interface_descriptor *interface;

struct usb_endpoint_descriptor *endpoint;

struct sample_device *sample;

printk(KERN_INFO "usbsample: probe called for %s device\n", (char *)id->driver_info /* "mouse" or "keyboard" */ );

iface = &udev->actconfig->interface[ifnum];

interface = &iface->altsetting[iface->act_altsetting];

if (interface->bNumEndpoints != 1) return NULL;

endpoint = interface->endpoint + 0;

if (!(endpoint->bEndpointAddress & 0x80)) return NULL;

if ((endpoint->bmAttributes & 3) != 3) return NULL;

usb_set_protocol(udev, interface->bInterfaceNumber, 0);

usb_set_idle(udev, interface->bInterfaceNumber, 0, 0);

/* allocate and zero a new data structure for the new device */

sample = kmalloc(sizeof(struct sample_device), GFP_KERNEL);

if (!sample) return NULL; /* failure */

memset(sample, 0, sizeof(*sample));

sample->name = (char *)id->driver_info;

/* fill the URB data structure using the FILL_INT_URB macro */

{

int pipe = usb_rcvintpipe(udev, endpoint->bEndpointAddress);

int maxp = usb_maxpacket(udev, pipe, usb_pipeout(pipe));

if (maxp > 8) maxp = 8; sample->maxp = maxp; /* remember for later */

FILL_INT_URB(&sample->urb, udev, pipe, sample->data, maxp,

sample_irq, sample, endpoint->bInterval);

}

/* register the URB within the USB subsystem */

if (usb_submit_urb(&sample->urb)) {

kfree(sample);

return NULL;

}

/* announce yourself */

printk(KERN_INFO "usbsample: probe successful for %s (maxp is %i)\n", sample->name, sample->maxp);

/* MOD_INC_USE_COUNT; if you do, you'll need to unplug the device or the devices before being able to unload the module and return the new structure */ return sample;

}

/*The id_table, lists all devices that can be handled by this driver. The three numbers are class, subclass, protocol. has more details about interface matches and vendor/device matches. This feature is not there in version 2.2, see below, sample_probe_22() for details. Here we use a fake usb_device_id structure defined in /usb-sysdep.h */

static struct usb_device_id sample_id_table [] = {

{

USB_INTERFACE_INFO(3, 1, 1),

driver_info: (unsigned long)"keyboard"

},

{

USB_INTERFACE_INFO(3, 1, 2),

driver_info: (unsigned long)"mouse"

},

{

0, /* no more matches */

}

};

/* The callbacks are registered within the USB subsystem using the usb_driver data structure */

#ifdef LINUX_24

static struct usb_driver sample_usb_driver = {

name: "sample",

probe: sample_probe,

disconnect: sample_disconnect,

id_table: sample_id_table,

};

#else /* 2.2 */

/* With version 2.2, there is no device_id support: the probe function is called for every device being plugged, and it must select whether the device is going to be handled or not. Here we extract the identification phase and rely on sample_probe() above for the interesting part of the game. Note that a 2.4 driver can use this approach as well, by not defining an id table.We think the id_table way is much cleaner, so we chose to exploit it where available*/

static void *sample_probe_22(struct usb_device *udev, unsigned int ifnum)

{

struct usb_device_id *id;

struct usb_interface_descriptor *interface;

printk(KERN_INFO "sample_probe_22 called\n");

if (udev->descriptor.bNumConfigurations != 1) return NULL;

interface = udev->config[0].interface[ifnum].altsetting;

for (id = sample_id_table; id->driver_info; id++) {

if (interface->bInterfaceClass != id->class) continue;

if (interface->bInterfaceSubClass != id->subclass) continue;

if (interface->bInterfaceProtocol != id->protocol) continue;

break; /* found */

}

if (!id->driver_info)

return NULL; /* not ours */

return sample_probe(udev, ifnum, id);

}

static struct usb_driver sample_usb_driver = {

name: "sample",

probe: sample_probe_22,

disconnect: sample_disconnect,

/* no id_table field here */

};

#endif /* 2.2 */

/*Functions call at module load and unload time: only register and unregister the USB callbacks*/

int sample_init(void)

{

/* just register it, returns 0 or error code */

return usb_register(&sample_usb_driver);

}

void sample_exit(void)

{

usb_deregister(&sample_usb_driver);

}

module_init(sample_init);

module_exit(sample_exit);

#endif /* no 2.0 */

4 设备的安装

下面编译

$ gcc -O2 -DMODULE -D__KERNEL__ -c usb.c

得到文件usb.o就是一个设备驱动程序。

如果设备驱动程序有多个文件,把每个文件按上面的命令行编译,然后

ld -r file1.o file2.o -o modulename.

驱动程序已经编译好了,现在把它安装到系统中去。

$ insmod -f usb.o

如果安装成功,在/proc/devices文件中就可以看到设备usb,并可以看到它的主设备号。

要卸载的话,运行

$ rmmod usb

下一步要创建设备文件。

mknod /dev/usb.c major minor

c 是指字符设备,major是主设备号,就是在/proc/devices里看到的。

用shell命令

$ cat /proc/devices | awk \\$2==\usb\" {print \\$1}"

就可以获得主设备号,可以把上面的命令行加入你的shell script中去。

minor是从设备号,设置成0就可以了。

5 参考文献

[1]Linux Device Drivers by Alessandro Rubini and Jonathan Corbet,Published by O’reilly & Associates.

[2] 《边学边干-Linux 内核指导》李善平陈文智编著浙江大学出版社出版

[3] Kernel Projects for Linux by Gary Nutt Addison Weley Longman,Inc.

相关文档
最新文档