Linux设备模型(热插拔、mdev 与 firmware)

热插拔
有 2 个不同角度来看待热插拔:
   从内核角度看,热插拔是在硬件、内核和内核驱动之间的交互。
   从用户角度看,热插拔是内核和用户空间之间,通过调用用户空间程序(如hotplug、udev 和 mdev)的交互。 当需要通知用户内核发生了某种热插拔事件时,内核才调用这个用户空间程序。
现在的计算机系统,要求 Linux 内核能够在硬件从系统中增删时,可靠稳定地运行。这就对设备驱动作者增加了压力,因为在他们必须处理一个毫无征兆地突然出现或消失的设备。

热插拔工具
当用户向系统添加或删除设备时,内核会产生一个热插拔事件,并在 /proc/sys/kernel/hotplug 文件里查找处理设备连接的用户空间程序。这个用户空间程序主要有

hotplug:这个程序是一个典型的 bash 脚本,只传递执行权给一系列位于 /etc/hot-plug.d/ 目录树的程序。hotplug 脚本搜索所有的有 .hotplug 后缀的可能对这个事件进行处理的程序并调用它们, 并传递给它们许多不同的已经被内核设置的环境变量。(基本已被淘汰,具体内容请参阅《LDD3》)

udev :用于linux2.6.13或更高版本的内核上,为用户空间提供使用固定设备名的动态/dev目录的解决方案。它通过在 sysfs 的 /class/ 和/block/ 目录树中查找一个称为 dev 的文件,以确定所创建的设备节点文件的主次设备号。所以要使用udev,驱动必须为设备在sysfs中创建类接口及其dev属性文件,方法和sculld模块中创建dev属性相同。 udev的资料网上十分丰富,我就不在这废话了,给出以下链接有兴趣的自己研究:
 
 
mdev:一个简化版的udev,是busybox所带的程序,十分适合嵌入式系统。
 

因为hotplug现在也在被慢慢地淘汰,udev不再依赖hotplug了,所以这里不再介绍;

udev较mdev复杂,不太适合嵌入式使用。(本人也有做udev的实验,交叉编译是通过了,但是使用上有问题,没有实现其功能。也许是我的文件系统没做好,以后有时间再研究和写记录。有成功高人的通知一声,交流一下经验。^_^谢谢!);

mdev简单易用,比较适合嵌入式系统,实验成功。以下详细介绍mdev的使用。


mdev
 
在一开始建立根文件系统时,开始使用mdev,但是当时只是启动时mdev -s 一下,并没有深究。现在在学习了Linux设备模型之后,对于Linux中/dev目录的动态管理有了更深的认识,并认真的看了一下busybox中的mdev.txt文档并翻译了一下,做成了PDF,在看下面的内容时请先看看这篇文档。


下载busybox中的mdev.pdf

 
先声明一个要点:要实现设备节点文件的自动、动态的增删,必须在你自己的驱动源码中实现 类 接口,并在类设备的目录中添加包含设备号的名为“dev”的属性文件。
 
mdev原理及bug
 
要使用mdev,适当知道一下原理是必不可少的(能完整地研究mdev源码是最好的)。说实话起初我并没有想看mdev的源码,是在使用时发现了问题后才去研究了一下mdev的源码。现在简单介绍一下mdev的原理:
 
执行mdev -s :以‘-s’为参数调用位于 /sbin目录写的mdev(其实是个链接,作用是传递参数给/bin目录下的busybox程序并调用它),mdev扫描 /sys/class 和 /sys/block 中所有的类设备目录,如果在目录中含有名为“dev”的文件,且文件中包含的是设备号,则mdev就利用这些信息为这个设备在/dev 下创建设备节点文件。一般只在启动时才执行一次 “mdev -s”。
 
 热插拔事件:由于启动时运行了命令:echo /sbin/mdev > /proc/sys/kernel/hotplug ,那么当有热插拔事件产生时,内核就会调用位于 /sbin目录的mdev。这时mdev通过环境变量中的 ACTION 和 DEVPATH,来确定此次热插拔事件的动作以及影响了/sys中的那个目录。接着会看看这个目录中是否有“dev”的属性文件,如果有就利用这些信息为这个设备在/dev 下创建设备节点文件。
 

源码的bug(个人意见):由于mdev是通过判断“dev”属性文件的路径字符串中的第6个字符是否为‘c’,来决定设备是字符设备还是块设备【type = (path[5] == 'c' ? S_IFCHR : S_IFBLK);例如path = "/sys/class/ldd/sculld*/"为字符设备,而/sys/devices/ldd0/sculld*/ 就会被误判为块设备】,那么如果你在非 /sys/class 和 /sys/block 目录下建立了“dev”属性文件且内容是设备号(像sculld中就这样做了),那么mdev也会在/dev 下创建设备节点文件。这样可能所创建的设备节点文件是错的。

以我实验为例,我以上一篇的文章中的sculld为基础,加上了类接口(这样在/sys/devices/ldd0/sculld*/和 /sys/class/ldd/sculld* 中都有内容为设备号的“dev”属性文件)。在运行时发现一直会将有的sculld*创建为块设备节点文件。郁闷死了,难道我的驱动有错???最后研究了mdev源码之后发现,只要在 /sys中建立了“dev”属性文件且内容是设备号,mdev就会以所在的目录为名在/dev 下创建设备节点文件。像sculld模块,mdev会为一个设备创建两次设备文件,由于文件名一样,第二次的文件会覆盖第一次的。如果第二次是因为/sys/devices/ldd0/sculld*/dev 产生的设备节点文件,那么设备节点文件就会被错误地创建为块设备。

我认为这个bug的解决办法有如下两种:

(1)在你写驱动的时候,只在/sys/class 和 /sys/block 中的类设备目录中存在包含设备号的“dev”属性文件。(你无法保证被人的驱动会这么做)

(2)修正mdev源码:

修改/busybox-1.9.0/util-linux/mdev.c文件的第328行:

<span style="color:#000000;"><font face="Courier New"><span style="color:#0000ff;">if</span> <span style="color:#0000cc;">(</span><span style="color:#0000cc;">!</span><span style="color:#ff0000;">strcmp</span><span style="color:#0000cc;">(</span>action<span style="color:#0000cc;">,</span> <span style="color:#ff00ff;">"remove"</span><span style="color:#0000cc;">)</span><span style="color:#0000cc;">)</span><br />       make_device<span style="color:#0000cc;">(</span>temp<span style="color:#0000cc;">,</span> 1<span style="color:#0000cc;">)</span><span style="color:#0000cc;">;</span></font></span>

<span style="color:#000000;"><font face="Courier New"><span style="color:#0000ff;">else</span> <span style="color:#0000ff;">if</span> <span style="color:#0000cc;">(</span><span style="color:#0000cc;">!</span><span style="color:#ff0000;">strcmp</span><span style="color:#0000cc;">(</span>action<span style="color:#0000cc;">,</span> <span style="color:#ff00ff;">"add"</span><span style="color:#0000cc;">)</span><span style="color:#0000cc;">)</span> <span style="color:#0000cc;">{</span><br />         <span style="color:#ff0102;">if (env_path[2]=='l') make_device(temp,0);  //tekkamanninja</span><br />            <span style="color:#0000ff;">if</span> <span style="color:#0000cc;">(</span>ENABLE_FEATURE_MDEV_LOAD_FIRMWARE<span style="color:#0000cc;">)</span><br />                load_firmware<span style="color:#0000cc;">(</span><span style="color:#ff0000;">getenv</span><span style="color:#0000cc;">(</span><span style="color:#ff00ff;">"FIRMWARE"</span><span style="color:#0000cc;">)</span><span style="color:#0000cc;">,</span> temp<span style="color:#0000cc;">)</span><span style="color:#0000cc;">;</span><br />        <span style="color:#0000cc;">}</span></font></span>

也就是在增加设备节点文件之前检查/sys/目录下的路径是否为/class和/block(通过检查路径字符串的第3个字符是否为‘l’)。

本人推荐第二种做法!

 
mdev使用
mdev的使用在busybox中的mdev.txt文档已经将得很详细了。但作为例子,我简单讲讲我的使用过程:
 
(1)在编译时加上对mdev的支持(我是使用的是busybox1.9.0):
    Linux System Utilities  --->   
           [*] mdev      
           [*]   Support /etc/mdev.conf
           [*]     Support command execution at device addition/removal
 
(2)在启动时加上使用mdev的命令:
我在自己创建的根文件系统(nfs)中的/linuxrc文件中添加了如下指令:

<span style="color:#000000;"><font face="Courier New"><span style="color:#0000cc;">#</span>挂载<span style="color:#0000cc;">/</span>sys为sysfs文件系统<br />    echo <span style="color:#ff00ff;">"----------mount /sys as sysfs"</span><br />    <span style="color:#0000cc;">/</span>bin<span style="color:#0000cc;">/</span>mount <span style="color:#0000cc;">-</span>t tmpfs mdev <span style="color:#0000cc;">/</span>dev <br />    <span style="color:#0000cc;">/</span>bin<span style="color:#0000cc;">/</span>mount <span style="color:#0000cc;">-</span>t sysfs sysfs <span style="color:#0000cc;">/</span>sys<br />    echo <span style="color:#ff00ff;">"----------Starting mdev......"</span><br />    <span style="color:#0000cc;">/</span>bin<span style="color:#0000cc;">/</span>echo <span style="color:#0000cc;">/</span><span style="color:#ff0102;">sbin</span><span style="color:#0000cc;">/</span>mdev <span style="color:#0000cc;">></span> <span style="color:#0000cc;">/</span>proc<span style="color:#0000cc;">/</span>sys<span style="color:#0000cc;">/</span>kernel<span style="color:#0000cc;">/</span>hotplug<br />    mdev <span style="color:#0000cc;">-</span>s </font></span>

注意:是<span style="color:#000000;"><span style="color:#0000cc;">/</span>bin<span style="color:#0000cc;">/</span>echo <span style="color:#0000cc;">/</span><span style="color:#ff0102;">sbin</span><span style="color:#0000cc;">/</span>mdev <span style="color:#0000cc;">></span> <span style="color:#0000cc;">/</span>proc<span style="color:#0000cc;">/</span>sys<span style="color:#0000cc;">/</span>kernel<span style="color:#0000cc;">/</span>hotplug,<span style="color:#ff0102;">并非</span></span><span style="color:#000000;"><span style="color:#0000cc;">/</span>bin<span style="color:#0000cc;">/</span>echo <span style="color:#0000cc;">/</span><span style="color:#ff0102;">bin</span><span style="color:#0000cc;">/</span>mdev <span style="color:#0000cc;">></span> <span style="color:#0000cc;">/</span>proc<span style="color:#0000cc;">/</span>sys<span style="color:#0000cc;">/</span>kernel<span style="color:#0000cc;">/</span>hotplug。<span style="color:#0001ff;">busybox的文档有错!!</span></span>

 

(3)在你的驱动中加上对类设备接口的支持,并在类设备目录下添加包含设备号的名为“dev”的属性文件。
 
(4)至于/etc/mdev.conf文件,可有可无,不影响使用,只是添加了些功能。
     为了实验我在/etc创建了mdev.conf文件并输入了:

<span style="color:#000000;"><font face="Courier New">    sculld<span style="color:#0000cc;">[</span>0<span style="color:#0000cc;">-</span>1<span style="color:#0000cc;">]</span> 0<span style="color:#0000cc;">:</span>0 666 <span style="color:#0000cc;">*</span> echo tekkaman <span style="color:#0000cc;">></span> <span style="color:#0000cc;">/</span>tmp<span style="color:#0000cc;">/</span>mdev</font></span>

     这样,在挂载和卸载sculld.ko时,在/tmp/下会出现mdev文件,里面字符为tekkaman

具体的实验源码和现象在文章后面有。
 

firmware

硬件市场的激烈竞争, 使得制造商连一点用于设备控制固件的 EEPROM 的成本都不愿意花费。因此固件一般发布在和硬件配套的驱动包中,由操作系统(其实是驱动程序)负责传送固件到设备。

内核固件接口

获取固件的正确方法是当需要时从用户空间获取它。一定不要试图从内核空间直接打开包含固件的文件,那是一个易出错的操作, 因为它把策略(以文件名的形式)包含进了内核。正确的方法是使用固件接口:

<span style="color:#000000;"><span style="font-family:'新宋体';"><span style="color:#0000cc;">#</span><span style="color:#ff0000;">include</span> <span style="color:#0000cc;"><</span>linux<span style="color:#0000cc;">/</span>firmware<span style="color:#0000cc;">.</span>h<span style="color:#0000cc;">></span><br /><span style="color:#0000ff;">int</span> request_firmware<span style="color:#0000cc;">(</span><span style="color:#0000ff;">const</span> <span style="color:#0000ff;">struct</span> firmware <span style="color:#0000cc;">*</span><span style="color:#0000cc;">*</span>fw<span style="color:#0000cc;">,</span> </span></span>

<span style="color:#000000;"><span style="font-family:'新宋体';"><span style="color:#0000ff;"><span style="color:#000000;">                     </span>const</span> <span style="color:#0000ff;">char</span> <span style="color:#0000cc;">*</span>name<span style="color:#0000cc;">, <span style="color:#ff9900;">/* name 为固件文件名*/</span></span></span></span>

<span style="color:#000000;"><span style="font-family:'新宋体';">                     <span style="color:#0000ff;">struct</span> device <span style="color:#0000cc;">*</span>device<span style="color:#0000cc;">)</span><span style="color:#0000cc;">;</span><br /><span style="color:#ff9900;">/*要求用户空间定位并提供一个固件映象给内核;若成功加载, 返回值是 0(否则返回错误码)*/</span><br /><br /><span style="color:#ff9900;">/*因为 request_firmware 需要用户空间的操作, 所以返回前将保持休眠。若驱动必须使用固件而不能进入休眠时,可使用以下异步函数:*/</span><br /><span style="color:#0000ff;">int</span> request_firmware_nowait<span style="color:#0000cc;">(</span><br />    <span style="color:#0000ff;">struct</span> module <span style="color:#0000cc;">*</span>module<span style="color:#0000cc;">,</span> <span style="color:#ff9900;">/* = THIS_MODULE*/</span><br />    <span style="color:#0000ff;">int</span> uevent<span style="color:#0000cc;">,</span><br />    <span style="color:#0000ff;">const</span> <span style="color:#0000ff;">char</span> <span style="color:#0000cc;">*</span>name<span style="color:#0000cc;">,</span> <br />    <span style="color:#0000ff;">struct</span> device <span style="color:#0000cc;">*</span>device<span style="color:#0000cc;">,</span> <br />    <span style="color:#0000ff;">void</span> <span style="color:#0000cc;">*</span>context<span style="color:#0000cc;">,</span><span style="color:#ff9900;">/*不由固件子系统使用的私有数据指针*/</span><br />    <span style="color:#0000ff;">void</span> <span style="color:#0000cc;">(</span><span style="color:#0000cc;">*</span>cont<span style="color:#0000cc;">)</span><span style="color:#0000cc;">(</span><span style="color:#0000ff;">const</span> <span style="color:#0000ff;">struct</span> firmware <span style="color:#0000cc;">*</span>fw<span style="color:#0000cc;">,</span> <span style="color:#0000ff;">void</span> <span style="color:#0000cc;">*</span>context<span style="color:#0000cc;">)</span><span style="color:#0000cc;">)</span><span style="color:#0000cc;">;</span><br /><span style="color:#ff9900;">/*如果一切正常,request_firmware_nowait 开始固件加载过程并返回 0. 过了一段时间后(默认10秒),将用加载的结果(若加载失败, fw 为 NULL)作为参数调用 cont。*/</span><br /><br /><span style="color:#ff9900;">/* fw 参数指向以下结构体:*/</span><br /><span style="color:#0000ff;">struct</span> firmware <span style="color:#0000cc;">{</span><br />    <span style="color:#ff0000;">size_t</span> size<span style="color:#0000cc;">;</span><br />    u8 <span style="color:#0000cc;">*</span>data<span style="color:#0000cc;">;</span><br /><span style="color:#0000cc;">}</span><span style="color:#0000cc;">;</span><br /><span style="color:#ff9900;">/*那个结构包含实际的固件, 它现在可被下载到设备中.但是请注意:在发送它到硬件之前,必须检查这个文件以确保它是正确的固件映象(设备固件常常包含标识字符串、 校验和等等)*/</span><br /><br /><span style="color:#ff9900;">/*当固件已经发送到设备后,应当释放 firmware 结构体, 使用:*/</span><br /><span style="color:#0000ff;">void</span> release_firmware<span style="color:#0000cc;">(</span><span style="color:#0000ff;">struct</span> firmware <span style="color:#0000cc;">*</span>fw<span style="color:#0000cc;">)</span><span style="color:#0000cc;">;</span> </span></span>

注意:要使用<span style="color:#000000;"><span style="font-family:'新宋体';">firmware<span style="color:#000102;">,必须要在配置内核时选上:</span></span></span>

<span style="color:#000000;"><span style="font-family:'新宋体';"><span style="color:#000102;">   Device Drivers  --->    <br /></span></span></span>

<span style="color:#000000;"><span style="font-family:'新宋体';"><span style="color:#000102;">          Generic Driver Options  --->      <br /></span></span></span>

<span style="color:#000000;"><span style="font-family:'新宋体';"><span style="color:#000102;">              <span style="color:#0001ff;"><*> Userspace firmware loading support </span><br /></span></span></span>

<span style="color:#000000;"><span style="font-family:'新宋体';"><span style="color:#000102;">否则会出现: Unknown symbol release_firmware 和: Unknown symbol request_firmware 的错误。<br />               <br /></span></span></span>

固件接口工作原理

固件子系统使用 sysfs 和热插拔机制工作。当调用 request_firmware时, 函数将在 /sys/class/firmware 下创建一个以设备名为目录名的新目录,其中包含 3 个属性:

loading :这个属性应当被加载固件的用户空间进程设置为 1。当加载完毕, 它将被设为 0。被设为 -1 时,将中止固件加载。
data :一个用来接收固件数据的二进制属性。在设置 loading 为1后, 用户空间进程将固件写入这个属性。
device :一个链接到 /sys/devices 下相关入口项的符号链接。

一旦创建了 sysfs 入口项, 内核将为设备产生一个热插拔事件,并传递包括变量 FIRMWARE 的环境变量给处理热插拔的用户空间程序。FIRMWARE 被设置为提供给 request_firmware 的固件文件名。

用户空间程序定位固件文件, 并将其拷贝到内核提供的二进制属性;若无法定位文件, 用户空间程序设置 loading 属性为 -1。

若固件请求在 10 秒内没有被服务, 内核就放弃并返回一个失败状态给驱动。超时周期可通过 sysfs 属性 /sys/class/firmware/timeout 属性改变。

 request_firmware 接口允许使用驱动发布设备固件。当正确地集成进热插拔机制后, 固件加载子系统允许设备不受干扰地工作。显然这是处理问题的最好方法,但固件受版权保护,小心违反版权法。


ARM9开发板实验
 

实验源码下载点击这里
 
实验现象:

<span style="color:#000000;"><span style="color:#000000;"><span style="color:#000000;"><span style="font-family:'新宋体';"><span style="color:#0000cc;">[</span>Tekkaman2440@SBC2440V4<span style="color:#0000cc;">]</span><span style="color:#0000cc;">#</span>ls <span style="color:#0000cc;">-</span>l <span style="color:#0000cc;">/</span>dev<span style="color:#0000cc;">/</span>sculld<span style="color:#0000cc;">*</span><br />ls<span style="color:#0000cc;">:</span> <span style="color:#0000cc;">/</span>dev<span style="color:#0000cc;">/</span>sculld<span style="color:#0000cc;">*</span><span style="color:#0000cc;">:</span> No such <span style="color:#ff0000;">file</span> <span style="color:#0000ff;">or</span> directory<br /></span><span style="font-family:'���宋体';"><span style="color:#ff0000;"><span style="color:#0000cc;">[</span>Tekkaman2440@SBC2440V4<span style="color:#0000cc;">]</span><span style="color:#0000cc;">#</span>cat <span style="color:#0000cc;">/</span>tmp<span style="color:#0000cc;">/</span>mdev<br />cat<span style="color:#0000cc;">:</span> can<span style="color:#ff00ff;">'t open '</span><span style="color:#0000cc;">/</span>tmp<span style="color:#0000cc;">/</span>mdev': No such file or directory</span><br />[Tekkaman2440@SBC2440V4]#insmod /lib/modules/lddbus.ko<br />Mount lddbus ok !<br />Bus device is ldd0 !<br />You can see me in sys/module/ , sys/devices/ , sys/class/ and sys/bus/ !<br />[Tekkaman2440@SBC2440V4]#insmod /lib/modules/sculld.ko<br />[Tekkaman2440@SBC2440V4]#ls -l /dev/sculld*<br /><span style="color:#ff0000;">crw-rw-rw-</span>    1 root     root     252,   0 Jan  1 00:00 /dev/sculld0<br /><span style="color:#ff0000;">crw-rw-rw-</span>    1 root     root     252,   1 Jan  1 00:00 /dev/sculld1<br /></span></span><span style="font-family:'新宋体';"><span style="color:#ff0000;">crw-rw----</span>    1 root     root     252,   2 Jan  1 00:00 /dev/sculld2<br /><span style="color:#ff0000;">crw-rw----</span>    1 root     root     252,   3 Jan  1 00:00 /dev/sculld3<br />[Tekkaman2440@SBC2440V4]#rmmod sculld<br />The LDD class<br /> ldd_classdev_release : sculld0 release!<br />The LDD class<br /> ldd_classdev_release : sculld1 release!<br />The LDD class<br /> ldd_classdev_release : sculld2 release!<br />The LDD class<br /> ldd_classdev_release : sculld3 release!<br />[Tekkaman2440@SBC2440V4]#ls -l /dev/sculld*<br />ls: /dev/sculld*: No such file or directory<br /></span></span><span style="color:#ff0000;font-family:'新宋体';">[Tekkaman2440@SBC2440V4]#cat /tmp/mdev<br />tekkaman</span></span>

相关推荐