驱动必须首先请求IRQ并将一个中断处理函数与其绑定:
#define ROLLER_IRQ 7
static irqreturn_t roller_interrupt(int irq, void *dev_id);
if (request_irq(ROLLER_IRQ, roller_interrupt, IRQF_DISABLED |
IRQF_TRIGGER_RISING, "roll", NULL)) {
printk(KERN_ERR "Roll: Can't register IRQ %d\n", ROLLER_IRQ);
return -EIO;
}
我们看一下传递给request_irq()的参数,本例中没有查询或探测IRQ号,而是直接硬编码为ROLLER_IRQ。第2个参数roller_interrupt()是中断处理函数。中断处理函数的原型的返回值类型为irqreturn_t,如果中断处理成功,则返回IRQ_HANDLED,否则,返回IRQ_NONE。对于PCI等I/O而言,该返回值的意义更重要,因为多个设备可能共享同一IRQ。
IRQF_DISABLED标志意味着这个中断处理为快中断,因此,在调用该处理函数的时候,内核将禁止所有的中断。IRQF_TRIGGER_RISING暗示辊轮将在中断线上产生一个上升沿以发出中断。换句话说,辊轮是一个边沿触发的设备。有一些设备是电平触发的,在CPU服务其中断之前,它一直将中断线保持在一个电平上。使用IRQF_TRIGGER_HIGH或IRQF_TRIGGER_LOW可以标识一个中断为高/低电平触发。该参数其他的可能值包括IRQF_SAMPLE_RANDOM(第5章《字符设备驱动》的《伪字符设备驱动》一节会用到)、IRQF_SHARED(定义这个IRQ被多个设备共享)。
下一个参数"roll",用于标识这个设备,在/proc/interrupts等文件中也会利用它产生数据。最后一个参数(本例中为NULL),仅在共享中断的时候有用,用于区分共享同一IRQ线的每个设备。
从2.6.19内核开始,中断处理接口发生了一些变化。以前的中断处理函数的第3个参数为struct pt_regs *,它指向存放CPU寄存器的地址,在2.6.19中已经移除。另外,IRQF_xxx型中断标志取代了SA_xxx型中断标志。例如,在较早的内核中,你应该使用SA_INTERRUPT而不是IRQF_DISABLED来将中断处理标识为快中断处理。
驱动初始化的时候申请IRQ并不是太好,因为这样会导致甚至设备未被使用的时候,有价值的资源也被占用。因此,设备驱动通常在设备被应用打开的时候申请IRQ。类似地,IRQ也在应用关闭设备的时候释放IRQ,而不是在退出驱动模块的时候进行。使用下面的方法可以释放一个IRQ:
free_irq(int irq, void *dev_id);
清单4.1给出了辊轮中断处理的实现。
roller_interrupt()有2个参数,IRQ和设备标识符(传递给request_irq()的最后一个参数)。请对照图4.3查看清单4.1。
清单4.1 辊轮中断处理
spinlock_t roller_lock = SPIN_LOCK_UNLOCKED;
static DECLARE_WAIT_QUEUE_HEAD(roller_poll);
static irqreturn_t
roller_interrupt(int irq, void *dev_id)
{
int i, PA_t, PA_delta_t, movement = 0;
/* Get the waveforms from bits 0, 1 and 2
of Port D as shown in Figure 4.3 */
PA_t = PORTD & 0x07;
/* Wait until the state of the pins change.
(Add some timeout to the loop) */
for (i=0; (PA_t==PA_delta_t); i++){
PA_delta_t = PORTD & 0x07;
}
movement = determine_movement(PA_t, PA_delta_t); /* See below */
spin_lock(&roller_lock);
/* Store the wheel movement in a buffer for
later access by the read()/poll() entry points */
store_movements(movement);
spin_unlock(&roller_lock);
/* Wake up the poll entry point that might have
gone to sleep, waiting for a wheel movement */
wake_up_interruptible(&roller_poll);
return IRQ_HANDLED;
}
int
determine_movement(int PA_t, int PA_delta_t)
{
switch (PA_t){
case 0:
switch (PA_delta_t){
case 1:
movement = ANTICLOCKWISE;
break;
case 2:
movement = CLOCKWISE;
break;
case 4:
movement = KEYPRESSED;
break;
}
break;
case 1:
switch (PA_delta_t){
case 3:
movement = ANTICLOCKWISE;
break;
case 0:
movement = CLOCKWISE;
break;
}
break;
case 2:
switch (PA_delta_t){
case 0:
movement = ANTICLOCKWISE;
break;
case 3:
movement = CLOCKWISE;
break;
}
break;
case 3:
switch (PA_delta_t){
case 2:
movement = ANTICLOCKWISE;
break;
case 1:
movement = CLOCKWISE;
break;
}
case 4:
movement = KEYPRESSED;
break;
}
}
驱动入口点(如read()和poll())尾随roller_interrupt()进行操作。例如,当中断处理函数解析完一个辊轮运动后,它唤醒正在等待的poll()线程(可能已经因为X Windows等应用发起的select()系统调用而睡眠)。请在学习完第5章字符设备驱动的知识后,重新查看清单4.1并实现辊轮设备的完整驱动。
第7章《输入设备驱动》的清单7.3利用了内核的输入接口,将辊轮转化为辊鼠标。
在本节结束前,我们介绍一下使能和禁止特定IRQ的函数。enable_irq(ROLLER_IRQ)用于使能辊轮运动的中断发生,disable_irq(ROLLER_IRQ)则进行相反的工作。disable_irq_nosync(ROLLER_IRQ)禁止辊轮中断,并且不等待任何正在执行的roller_interrupt()实例的返回。disable_irq()的非同步变体执行地更快,但是可能导致潜在的竞态。只有在你确认没有竞争的尽快下,才可以这样使用。drivers/ide/ide-io.c由一个使用disable_irq_nosync()的例子,在初始化过程中,它阻止了一些中断,因为一些系统中可能在此方面存在问题。
软中断(Softirq)和Tasklet
正如以前讨论的那样,中断处理有2个矛盾的要求:它们需要完成大量的设备数据处理,但是又不得不尽可能快地退出。为了摆脱这一困境,中断处理过程被分成2部分:一个急切的且抢占的与硬件交互的顶半部,和一个在所有中断都使能情况下并非十分急切的处理大量工作的底半部。如顶半部不一样,底半部是同步的,因为内核决定了它什么时候会执行它们。如下机制都可用于内核中延后一个工作到底半部执行:softirq、tasklet和工作队列(work queue)。
Softirq是一种基本的底半部机制,有较强的加锁需求。仅仅在一些对性能敏感的子系统(如网络层、SCSI层和内核定时器)中才会使用softirq。Tasklet建立在softirq之上,使用起来更简单。除非有严格的可扩展性和速度要求,都建议使用Tasklet。Softirq和Tasklet的主要不同是前者是可重用的而后者则不需要。Softirq的不同实例可运行在不同的处理器上,而tasklet则不允许。
为了论证Softirq和Tasklet的用法,假定前例中的辊轮由存在由于运动部件导致的潜在问题(如旋轮偶尔被卡住)从而导致不同于spec的波形。一个被卡住的旋轮会不停地产生假的中断,并可能使系统冻结。为了解决这个问题,可以捕获波形,进行一些分析,并在发现卡住的情况下动态地从中断模式切换到轮询模式,如果旋轮恢复正常,软件也恢复到正常模式。我们在中断处理函数中捕获波形,并在底半部分析它。清单4.2和4.3分别用Softirq和Tasklet对此进行了实现。
它们都是清单4.1的简化的变体,它们将中断处理简化为2个函数:从GPIO端口D捕获波形的roller_capture()和对波形进行算术分析并按需切换到轮询模式的roller_analyze()。
清单4.2 使用Softirq 分担中断处理的负载
void __init
roller_init()
{
/* ... */
/* Open the softirq. Add an entry for ROLLER_SOFT_IRQ in
the enum list in include/linux/interrupt.h */
open_softirq(ROLLER_SOFT_IRQ, roller_analyze, NULL);
}
/* The bottom half */
void
roller_analyze()
{
/* Analyze the waveforms and switch to polled mode if required */
}
/* The interrupt handler */
static irqreturn_t
roller_interrupt(int irq, void *dev_id)
{
/* Capture the wave stream */
roller_capture();
/* Mark softirq as pending */
raise_softirq(ROLLER_SOFT_IRQ);
return IRQ_HANDLED;
}
为了定义一个softirq,你必须在include/linux/interrupt.h中静态地添加一个入口。你不能动态地定义softirq。raise_softirq()用于宣布相应的softirq需要被执行。内核会在下一个可获得的机会里执行它。可能发生在退出硬中断处理函数的时候,也可能在ksoftirqd内核线程中。
清单4.3使用tasklet分担中断处理的负载
struct roller_device_struct { /* Device-specific structure */
/* ... */
struct tasklet_struct tsklt;
/* ... */
}
void __init roller_init()
{
struct roller_device_struct *dev_struct;
/* ... */
/* Initialize tasklet */
tasklet_init(&dev_struct->tsklt, roller_analyze, dev);
}
/* The bottom half */
void
roller_analyze()
{
/* Analyze the waveforms and switch to
polled mode if required */
}
/* The interrupt handler */
static irqreturn_t
roller_interrupt(int irq, void *dev_id)
{
struct roller_device_struct *dev_struct;
/* Capture the wave stream */
roller_capture();
/* Mark tasklet as pending */
tasklet_schedule(&dev_struct->tsklt);
return IRQ_HANDLED;
}
tasklet_init()用于动态地初始化一个tasklet,该函数不会为tasklet_struct分配内存,相反地,你必须将已经分配好的地址传递给它。tasklet_schedule()用于宣布相应的tasklet需要被执行。和中断类似,内核提供了一系列用于控制在多处理器系统中tasklet执行状态的函数:
(1)tasklet_enable()使能tasklet;
(2)tasklet_disable()禁止tasklet,并等待正在执行的tasklet退出;
你已经看到了中断处理函数和底半部的不同,但是,也有几个相似点。中断处理函数和tasklet都不需要可重用。而且,二者都不能睡眠。另外,中断处理函数、tasklet和softirq都不能被抢占。
工作队列是中断处理延后执行的第3种方式。它们在进程上下文执行,允许睡眠,因此可以使用mutex这类可能导致睡眠的函数。在前一章分析内核辅助接口的时候,我们已经讨论了工作队列。表4.1对softirq、tasklet和工作队列进行了对比分析。
表4.1 softirq、tasklet和工作队列对比
|
Softirq |
Tasklet |
Work Queue |
执行上下文 |
延后的工作,运行于中断上下文 |
延后的工作,运行于中断上下文 |
延后的工作,运行于进程上下文 |
可重用 |
可以在不同的CPU上同时运行 |
不能在不同的CPU上同时运行,但是不同的CPU可以运行不同的tasklet |
可以在不同的CPU上同时运行 |
睡眠 |
不能睡眠 |
不能睡眠 |
可以睡眠 |
抢占 |
不能抢占/调度 |
不能抢占/调度 |
可以抢占/调度 |
易用性 |
不容易使用 |
容易使用 |
容易使用 |
何时使用 |
如果延后的工作不会睡眠,而且有严格的可扩展性或速度要求 |
如果延后的工作不会睡眠 |
如果延后的工作会睡眠 |
在LKML正在进行一项去除tasklet的可行性的讨论。Tasklet比进程上下文的代码优先级更高,因此它们可能存在延迟问题。另外,你已经学习到,它们不允许睡眠,且只能在同一CPU上执行。因此,有人提议将现存的tasklet基于其场景随机应变地转换为softirq或工作队列。
第2章讨论的–rt补丁集将中断处理移到了内核线程执行,以实现更广泛的抢占支持。
Linux设备模型
新的Linux设备模型引入了类似于C++的抽象机制,它总结出设备驱动的共性,并提取出了总线和核心层。接下来,我们分析一下设备模型中的udev、sysfs、kobject和设备类(device class)等组件,以及这些组件对/dev结点管理、热插拔、固件下载和模块自动加载等关键内核子系统的影响。Udev是分析设备模型优点的最佳入口点,我们先从它开始讲解。
Udev
几年前,Linux操作系统还很年轻,管理设备节点的工作一点都不好玩。所有需要的结点(达到数千个)都不得不在/dev目录下静态创建。该问题实际起源于原始的UNIX系统。在2.4内核中,引入了devfs,它支持设备结点的动态创建。Devfs提供在了位于内存的文件系统中创建设备结点的能力,而命名结点的负担还是落在了设备驱动头上。但是,设备命名策略是可管理的,不应与内核混在一起。策略可位于头文件、模块参数或用户空间中。而Udev将成功地设备管理的任务彻底推向了用户空间。
Udev的工作依赖于:
1.内核中的sysfs支持,sysfs是Linux设备模型的一个重要组成部分。Sysfs位于内存中,在启动时被挂载在了/sys目录(见/etc/fstab)。下一节我们会分析sysfs,你可以认为访问sysfs是理所当然的。
2.一套用户空间守护程序和实用工具,如udevd和udevinfo。
3.用户自定义的规则,位于/etc/udev/rules.d/目录。你可以根据对应设备的特点设置规则。
为了理解udev的用法,我们先看一个例子。假定你有一个USB DVD驱动器或一个USB CD-RW驱动器。根据你热插拔设备顺序的不同,一个被命名为/dev/sr0,另一个被命名为/dev/sr1。在没有udev的情况下,你必须执行区分这些名字对应的设备。但是,有了udev以后,不管你以什么顺序热插拔它们,你都能分辨出二者,如DVD命名为/dev/usbdvd,CD-RW命名为/dev/usbcdrw。
首先,从sysfs相应的文件中提取产品信息。假定Targus DVD驱动器被分配的设备结点为/dev/sr0,Addonics CD-RW驱动器为/dev/sr1,使用udevinfo可收集设备信息:
bash> udevinfo -a -p /sys/block/sr0
...
looking at the device chain at
'/sys/devices/pci0000:00/0000:00:1d.7/usb1/1-4':
BUS=»usb»
ID=»1-4»
SYSFS{bConfigurationValue}=»1»
...
SYSFS{idProduct}=»0701»
SYSFS{idVendor}=»05e3»
SYSFS{manufacturer}=»Genesyslogic»
SYSFS{maxchild}=»0»
SYSFS{product}=»USB Mass Storage Device»
...
bash> udevinfo -a -p /sys/block/sr1
...
looking at the device chain at
'/sys/devices/pci0000:00/0000:00:1d.7/usb1/1-3':
BUS=»usb»
ID=»1-3»
SYSFS{bConfigurationValue}=»2»
...
SYSFS{idProduct}=»0302»
SYSFS{idVendor}=»0dbf»
SYSFS{manufacturer}=»Addonics»
SYSFS{maxchild}=»0»
SYSFS{product}=»USB to IDE Cable»
...
接下来,使用搜集到的产品信息标识设备并且添加udev命名规则。创建/etc/udev/rules.d/40-cdvd.rules文件并添加如下规则信息:
BUS="usb", SYSFS{idProduct}="0701", SYSFS{idVendor}="05e3",
KERNEL="sr[0-9]*", NAME="%k", SYMLINK="usbdvd"
BUS="usb", SYSFS{idProduct}="0302", SYSFS{idVendor}="0dbf",
KERNEL="sr[0-9]*", NAME="%k", SYMLINK="usbcdrw"
第1条规则告诉udev,一旦它发现一个USB设备的产品ID为0x0701,厂商ID为0x05e3,就增加一个以sr开始的名称,udev将在/dev创建一个同名的结点并为之创建一个名为usbdvd的符号链接。类似地,第2条规则为CD-RW驱动器创建一个名为usbcdrw的符号链接。
为了测试新创建规则的语法错误,可以对/sys/block/sr*运行udevtest。为了打开/var/log/messages中的相关提示信息,可以将/etc/udev/udev.conf文件中的udev_log设置为“yes”。为了在运行过程中对/dev目录应用新增规则,可以运行udevstart重启udev。此后,你的DVD驱动器在系统中将始终为/dev/usbdvd,而你的CD-RW驱动器将总是为/dev/usbcdrw。
你可以自主地通过在shell的脚本中通过如下命令来挂载设备:
mount /dev/usbdvd /mnt/dvd
为设备结点(以及网络接口)进行恒定的命名并非udev的唯一功能。实际上,udev已经演变为Linux热插拔管理器。Udev也承担了按需自动加载模块和为设备下载微码的任务。在挖掘这些能力之前,让我们首先对设备模型的内在机理有一个基本的认识。
Sysfs、Kobject和设备类(Device Class)
Sysfs、Kobjects和设备类(Device Classes)是设备模型的组成模块,但是它们羞于在公众面前露面,一直深藏于幕后。它们主要在总线和核心层的实现中使用,隐藏在为设备驱动提供服务的API中。
Sysfs是内核中结构化的设备模型在用户空间的印证。它与procfs类型,二者都是位于内存的文件系统,而且包含内核数据结构的信息。但是,procfs是查看内核内部的一个通用视窗,而sysfs则特定地对应于设备模型。因而,Sysfs并非procfs的替代品。进程描述符、sysctl参数等信息属于procfs而非sysfs。很快,我们会发现udev的大多数功能都依赖于sysfs。
Kobject封装了一些公用的对象属性,如引用计数。它们通常被嵌在更大的数据结构中。Kobject的主要字段如下(定义于include/linux/kobject.h文件):
1. kref对象,用于引用计数管理。kref_init()接口用于初始化kref接口,kref_get()用户增加引用计数,而kref_put()则用于减少引用计数,当没有剩下的引用后,对象会被释放。例如,URB结构体(见第11章《通用串行总线》)就包含一个kref,用于跟踪对它的引用的数量[2]。
[2]usb_alloc_urb()接口调用kref_init(),usb_submit_urb()调用kref_get(),而usb_free_urb()则调用kref_put()。
2. kset的指针,表征kobject归属的对象集(object set)。
3. kobj_type,用于描述对象的类型。
Kobject与sysfs紧密关联。内核中的每个对象实例对有一个sysfs的代表。
设备类概念的引入是设备模型的另一个特点,在驱动中,我们更有可能用到此接口。类接口抽象了这一理念,即每个设备都属于某一个总括性的分类。例如,USB鼠标、PS/2键盘和操纵杆对属于输入设备类,对在/sys/class/input/拥有入口。
图4.4显示了一个连接了外部USB鼠标的笔记本电脑的sysfs结构。顶层的bus、class和device目录被展开,以便显示sysfs是怎样基于设备类型和物理连接提供USB鼠标的视图的。鼠标是一个输入类设备,但是在物理上是一个USB设备,包含2个端点:1个控制端点ep00和一个中断断点ep81。上述B端口位于BUS 2上的USB主机控制器,而USB主机控制器自身则通过PCI总线被桥接给CPU。如果到目前为止,你还是理不清的话,不必担心。在读完讲解输入设备驱动的第7章、PCI驱动的第10章和USB驱动的第11章后,请回过头来阅读本节。
[/sys]
+[block]
-[bus]—[usb]—[devices]—[usb2]—[2-2]—[2-2:1.0]-[usbendpoint:usbdev2.2-ep81]
-[class]-[input]—[mouse2]—[device]—[bus]—[usbendpoint:usbdev2.2-ep81]
-[usb_device]—[usbdev2.2]—[device]—[bus]
-[usb_endpoint]—[usbdev2.2-ep00]—[device]
—[usbdev2.2-ep81]—[device]
-[devices]—[pci0000:00]—[0000:00:1d:1]—[usb2]—[2-2]—[2-2:1.0]
+[firmware]
+[fs]
+[kernel]
+[module]
+[power]
读者可以浏览/sys并查看与其他设备关联的入口(如你的网卡)以便对sysfs的层次结构组织有更好的理解。第10章的《Addressing and Identification》一节论证了sysfs怎样为笔记本电脑上CardBus以太网Modem卡的物理连接建立镜像的例子。
class编程接口则建立在kobject和sysfszhishang,因此它是深入理解设备模型中各种组件进行端到端交换的很好的着入点。我们以RTC驱动为例,RTC驱动(drivers/char/rtc.c)是一个混杂设备(misc)驱动。在第5章分析字符设备驱动的时候,我们会对misc驱动进行讨论。
加载RTC驱动模块,查看/sys和/dev下载的结点:
bash> modprobe rtc
bash> ls -lR /sys/class/misc
drwr-xr-x 2 root root 0 Jan 15 01:23 rtc
/sys/class/misc/rtc:
total 0
-r--r--r-- 1 root root 4096 Jan 15 01:23 dev
--w------- 1 root root 4096 Jan 15 01:23 uevent
bash> ls -l /dev/rtc
crw-r--r-- 1 root root 10, 135 Jan 15 01:23 /dev/rtc
/sys/class/misc/rtc/dev包含了分配给该设备的主次设备号(下一章讨论),/sys/class/misc/rtc/uevent用于冷插拔(下一节讨论),/dev/rtc是应用程序访问RTC驱动的入口。
下面分析一下设备模型的代码流程。在初始化过程中,Misc驱动利用了misc_register()服务,抽取一些代码片段:
/* ... */
dev = MKDEV(MISC_MAJOR, misc->minor);
misc->class = class_device_create(misc_class, NULL, dev,
misc->dev,
"%s", misc->name);
if (IS_ERR(misc->class)) {
err = PTR_ERR(misc->class);
goto out;
}
/* ... */
图4.5继续剥离了更多层以便深入到设备迷信的底层。它论证了class、kobject、sysfs以及udev之间的交互,这些交互会在/sys和/dev中产生相应的文件。
并口LED驱动(第5章《Talking to the Parallel Port》一节)和虚拟鼠标输入设备驱动(第7章《Device Example: Virtual Mouse》一节)可以作为创建sysfs中设备控制文件的例子。
总线—设备—驱动编程结构也是设备模型抽象的另一个部分。内核设备支持被清晰地结构化为总线、设备和驱动。这使得单独的驱动更容易实现也更通用。例如,总线能用于寻找操作某一设备的驱动。
以内核的I2C子系统(第8章《The Inter-Integrated Circuit Protocol》)为例。I2C层包含一个核心设施、总线适配器的设备驱动以及client设备驱动。I2C核心层使用bus_register()注册每个被侦测到的I2C总线适配器。当一个I2C client设备(例如,一个电可擦除的可编程只读存储EEPROM芯片)被探测到后,device_register()将表征它的存在。最后,I2C EEPROM client驱动通过driver_register()注册自身。实际上,这些注册是由I2C核心提供的API间接执行的。
bus_register()会在/sys/bus/增加一个相应的入口,而device_register()会在/sys/devices/增加相应的入口。bus_type、device、device_driver这3个结构体分别是与总线、设备和驱动对应的主要数据结构。include/linux/device.h中包含了它们的定义。
热插拔和冷插拔
在运行过程中往系统中插入设备称为热插拔,而在系统启动前就连接设备则称为冷插拔。以前,内核通过调用由/proc文件系统注册的辅助程序来向用户空间通知热插拔事件。而在当前的内核里,侦测到热插拔事件后,它们会通过netlink套接字向用户空间派生uevent。Netlink一种在内核空间和用户空间透过套接字API进行通信的足够强大的机制。用户空间的udevd(管理设备结点创建和移除的守护程序)会接收uevent并管理热插拔。
为了查看热插拔处理机制最新的进展,查看一下2.6内核各个不同版本情况下udev的逐步变迁:
1. udev-039和2.6.9内核,当内核侦测到一个热插拔事件后,它激活/proc/sys/kernel/hotplug中注册的用户空间辅助程序。用户空间辅助程序默认为/sbin/hotplug ,它接收热插拔设备的属性信息,在执行完/etc/hotplug/目录中的其它脚本之后,/sbin/hotplug查看热插拔配置目录(通常为/etc/hotplug.d/default/)并且运行相应的程序(如 /etc/hotplug.d/default/10-udev.hotplug)。
bash> ls -l /etc/hotplug.d/default/
...
lrwcrwxrwx 1 root root 14 May 11 2005 10-udev.hotplug -> /sbin/udevsend
...
当/sbin/udevsend执行后,它将热插拔设备的信息传递给udevd。
2. udev-058和2.6.11内核,情况发生了一些变化。Udevsend程序代替了/sbin/hotplug:
bash> cat /proc/sys/kernel/hotplug
/sbin/udevsend
3. 对于最新的udev和内核而言,udevd承担了管理热插拔的全部责任,不再依赖于udevsend。它现在通过netlink套接字直接从内核提取热插拔事件。/proc/sys/kernel/hotplug什么都不包含:
bash> cat /proc/sys/kernel/hotplug
bash>
Udev也处理冷插拔。由于udev是用户空间的一部分,仅仅在内核启动后才开始运行,需要一种特殊的机制针对冷插拔设备模拟热插拔事件。启动时,内核为所有设备在sysfs下创建了一个名为uevent的文件,并将冷插拔事件记录于这些文件。当udev开始运行后,它读取/sys下所有的uevent文件,并为每个冷插拔设备产生热插拔uevent。
微码下载
一些设备在开始运行前,必须下载微码。微码会在片上的微控制器中执行,过去,设备驱动通常将微码存放于头文件的静态数组中。但是,这种途径并不安全,这是因为微码通常是设备制造商的具有产权的映像文件,它们不宜进入GPL的内核。另一个原因导致不宜将二者混在一起的原因是内核和固件发布的时间线不同。显而易见,更好的解决方法是在用户空间维护微码,并在内核需要的时候将其下载进去。Sysfs和udev为此提供了支持。
以一些笔记本电脑上的Intel PRO/Wireless 2100无线mini PCI卡为例,该卡建立在一个需要执行外部提供微码的微控制器之上。下面分析一下Linux驱动下载微码到该卡的步骤。假定用户已经从