Linux设备驱动程序学习之设备模型二

Linux设备驱动程序学习笔记系列文章原作者是:Tekkaman Ninja,他博客地址
http://blog.chinaunix.net/u1/34474/showart_404278.html
在此向Tekkaman Ninja表示感谢,写出这么好的文章,使我少走了很多弯路。
通过一个设备在内核中生命周期的各个阶段,可以更好地理解Linux设备模型。我将通过分析lddbus和sculld的源码来了解Linux 设备模型中各环节的整合。《LDD3》中的(PCI总线)各环节的整合这部分内容作为参考资料,因为嵌入式Linux比较少用到PCI总线。 看这部分内容一定要先熟悉一下 lddbus 和 sculld 的源码。
 
一、 lddbus模块:添加总线、导出总线设备和设备驱动的注册函数。
lddbus子系统声明了一个bus_type结构,称为ldd_bus_type 。源码是在编译时初始化了这个结构体,源码:

/*
 * And the bus type.
 */

struct bus_type ldd_bus_type = {
    . name = "ldd" ,
    . match = ldd_match,
    .uevent  = ldd_uevent ,
} ;

在将lddbus子系统装载到内核和从内核卸载的源码如下:

static int __init ldd_bus_init( void )
{
    int ret;


    ret = bus_register( & ldd_bus_type) ; /*注册总线,在调用这个函数之后ldd_bus_type 结构体将向内核注册,在/sys/bus中出现ldd文件夹,其中包含两个目录:devices 和 drivers */
    if ( ret)
        return ret;
    if ( bus_create_file( & ldd_bus_type, & bus_attr_version) ) /*添加总线属性,将在/sys/bus/ldd目 录中出现version属性文件 */
        printk( KERN_NOTICE "Unable to create version attribute ! /n" ) ;
    ret = device_register( & ldd_bus) ;/*将总线作为设备注册。因为总线也可以是一个设备,比如在S3C2440中SPI 总线控制器相对于ARM920T核心来说,其实就是一个外设。调用此函数后,就会在/sys/devices中出现ldd0目录*/
    if ( ret)
        printk( KERN_NOTICE "Unable to register ldd0 ! /n" ) ;
    
    printk( KERN_NOTICE "Mount lddbus ok !/nBus device is ldd0 !/nYou can see me in sys/module/ , sys/devices/ and sys/bus/ ! /n" ) ;
    
    return ret;
}

static void ldd_bus_exit( void )
{
    device_unregister( & ldd_bus) ;
    bus_unregister( & ldd_bus_type) ;
}

module_init( ldd_bus_init) ;
module_exit( ldd_bus_exit) ;

   lddbus模块的主要部分就是这些,很简单。因为这只不过是一个虚拟的总线,没有实际的驱动。模块还导出了加载总线设备和总线驱动时需要用到的注册和注 销函数。对于实际的总线,应该还要导出总线的读写例程。

   将总线设备和驱动注册函数放在lddbus模块,并导出给其他的总线驱动程序使用,是因为注册总线设备和驱动需要总线结构体的信息,而且这些注册函数对于 所有总线设备和驱动都一样。只要这个总线驱动一加载,其他的总线驱动程序就可以通过调用这些函数注册总线设备和驱动,方便了总线设备驱动的作者,减少了代 码的冗余。

  这些注册函数内部调用driver_register、 device_register 和 driver_unregister、device_unregister 这些函数。


二、 sculld模块:在scull的基础上添加设备和驱动注册和注销函数。
   sculld模块基本和scull模块实现的功能一致,我参考《LDD3》提供的sculld,将以前实验过的功能较全的scull进行修改。主要 的修改如下(其他还有些小改动):
 

//*******在源码的声明阶段添加如下代码,以增加设备和驱动的结构体*****
struct sculld_dev * sculld_devices;  /* allocated in scull_init_module */


/* Device model stuff */
static struct ldd_driver sculld_driver = {
    . version = "$Revision: 1.21-tekkamanninja $" ,
    . module = THIS_MODULE,
    . driver = {
        . name = "sculld" ,
    } ,
} ;
//**************************************************************

//****** 增加设备注册函数和设备号属性********************************
static ssize_t sculld_show_dev( struct device * ddev, struct device_attribute * attr , char * buf)
{
    struct sculld_dev * dev = ddev- > driver_data;
    return print_dev_t( buf, dev- > cdev. dev) ;
}

static DEVICE_ATTR( dev, S_IRUGO, sculld_show_dev, NULL ) ;

static void sculld_register_dev( struct sculld_dev * dev, int index)
{
    sprintf ( dev- > devname, "sculld%d" , index) ;
    dev- > ldev. name = dev- > devname;
    dev- > ldev. driver = & sculld_driver;
    dev- > ldev. dev. driver_data = dev;
    register_ldd_device( & dev- > ldev) ;
    if ( device_create_file( & dev- > ldev. dev, & dev_attr_dev) )
    printk( "Unable to create dev attribute ! /n" ) ;
}
//*****************************************************************

/*还要在模块的初始化函数和模块清除函数中添加设备和驱动的注册和注销函数 */
    sculld_register_dev( sculld_devices + i, i) ;
    register_ldd_driver( & sculld_driver) ;
    unregister_ldd_device( & sculld_devices[ i] . ldev) ;
    unregister_ldd_driver( & sculld_driver) ;

修改后好模块就可以实现向sysfs文件系统导出信息。


三、分析设备 和驱动注册和注销核心函数,了解一般 的注册、注销过程。

以下也参考了《LDD3》中的PCI驱动分析

(1)设备的注册

在驱动程序中对设备进行注册的核心函数是:

int device_register( struct device * dev)
{
    device_initialize( dev) ;
    return device_add( dev) ;
}

   在 device_register 函数中, 驱动核心初始化 device 结构体中的许多成员, 向 kobject 核心注册设备的 kobject ( 导致热插拔事件产生), 接着添加设备到其 parent 节点所拥有的设备链表中。此后所有的设备都可通过正确的顺序被访问, 并知道其位于设备层次中的哪一点。

    设备接着被添加到总线相关的设备链表(包含了所有向总线注册的设备)中。接着驱动核心遍历这个链表, 为每个驱动程序调用该总线的match函数。

   match函数 主要是将驱动核心传递给它的 struct device 和 struct device_driver转换为特定的设备、驱动结构体 ,检查设备的特定信息, 以确定驱动程序是否支持该设备:

若不支持, 函数返回 0 给驱动核心,这样驱动核心移向链表中的下一个驱动;

若支持, 函数返回 1 给驱动核心,使驱动核心设置struct device 中的 driver 指针指向这个驱动, 并调用在 struct device_driver 中指定的 probe 函数.

   probe 函数 (又一次) 将驱动核心传递给它的 struct device 和 struct device_driver转换为特定的设备、驱动结构体 ,并再次验证这个驱动是否支持这个设备, 递增设备的引用计数, 接着调用总线驱动的 probe 函数:

若总线 probe 函数认为它不能处理这个设备,则返回一个负的错误值给驱动核心,这样驱动核心移向链表中的下一个设备;

若这个 probe 函数能够处理这个设备, 则初始化这个设备, 并返回 0 给驱动核心。这会使驱动核心添加设备到与这个特定驱动所绑定的设备链表中, 并在 /sys/bus的总线目录中的 drivers 目录中创建一个到这个设备符号链接(指向/sys/devices中的设备),使用户准确知道哪个驱动被绑定到了哪个设备。

(2)设备的注销
   在驱动程序中对设备进行注销的核心函数是:

void device_unregister( struct device * dev)

在 device_unregister 函数中, 驱动核心将删除这个设备的驱动程序(如果有)指向这个设备的符号链接, 并从它的内部设备链表中删除该设备, 再以 device 结构中的 struct kobject 指针为参数,调用 kobject_del。kobject_del 函数引起用户空间的 hotplug 调用,表明 kobject 现在从系统中删除, 接着删除所有该 kobject 以前创建的、与之相关联的 sysfs 文件和目录。kobject_del 函数也去除设备自身的 kobject 引用。此后, 所有的和这个设备关联的 sysfs 入口被去除, 并且和这个设备关联的内存被释放。

(3)驱动程序的注册

  在驱动程序中对驱动程序进行注册的核心函数是:

int driver_register( struct device_driver * drv)

driver_register 函数初始化 struct device_driver 结构体(包括 一个设备链表及其增删对象函数 和 一个自旋锁), 然后调用 bus_add_driver 函数。

bus_add_driver进行如下操作:

(1)查找驱动关联的总线:若未找到, 立刻返回负的错误值;
(2)根据驱动的名字和关联的总线,创建驱动的 sysfs 目录;
(3) 获取总线的内部锁, 遍历所有的已经注册到总线的设备,为这些设备调用match函数, 若成功,进行剩下的绑定过程。(类似注册设备,不再赘述)

(4)驱动程序的注销

   删除驱动程序是一个简单的过程,在驱动程序中对驱动程序进行注销的核心函数是:

void driver_unregister( struct device_driver * drv)

deiver_unregister 函数通过清理在 sysfs 树中连接到这个驱动入口的 sysfs 属性,来完成一些基本的管理工作。然后遍历所有属于该驱动的设备,为其调用 release 函数(类似设备从系统中删除时调用 release 函数)。

 

在所有的设备与驱动程序脱离后,通常在驱动程序中会使用下面两个函数:

down(&drv->unload_sem);
up(&drv->unload_sem);

它们在函数返回给调用者之前完成。这样做是因为在安全返回前,代码需要等待所有的对这个驱动的引用计数为 0。
模块卸载时,通常都要调用 driver_unregister 函数作为退出的方法。 只要驱动程序被设备引用并且等待这个锁时,模块就需要保留在内存中。这使得内核知道何时可以安全从内存删除驱动。


四、ARM9 开发板实验

实验源 码: http://blogimg.chinaunix.net/blog/upfile2/080109150139.rar
 
实验过程:

[ Tekkaman2440@SBC2440V4] # insmod / lib/ modules/ lddbus. ko
Mount lddbus ok !
Bus device is ldd0 !
You can see me in sys/ module/ , sys/ devices/ and sys/ bus/ !
[ Tekkaman2440@SBC2440V4] # tree - AC / sys/ module/ lddbus/ / sys/ devices/ ldd0/ / sys/ bus/ ldd/
/ sys/ module/ lddbus/
├── holders
├── initstate
├── refcnt
└── sections
    ├── __ksymtab
    └── __ksymtab_strings
/ sys/ devices/ ldd0/
├── power
│ └── wakeup
└── uevent
/ sys/ bus/ ldd/
├── devices
├── drivers
├── drivers_autoprobe
├── drivers_probe
└── version

5 directories, 9 files
[ Tekkaman2440@SBC2440V4] # cat / sys/ bus/ ldd/ version
Revision: 1. 9- tekkamanninja
[ Tekkaman2440@SBC2440V4] # rmmod lddbus
The LDD bus device
 ldd_bus_release : lddbus
[ Tekkaman2440@SBC2440V4] # ls / sys/ module / sys/ devices / sys/ bus
/ sys/ bus:
i2c ide mmc platform serio spi usb

/ sys/ devices:
platform system

/ sys/ module:
8250 loop rd snd_seq usbnet
atkbd mac80211 redboot snd_seq_oss v4l1_compat
cdrom mousedev rfd_ftl snd_soc_core vt
dm9000 nfs rtc_ds1307 snd_timer yaffs
hid ohci_hcd s3c2410_wdt spidev zc0301
ide_cd printk snd sunrpc
keyboard psmouse snd_pcm tcp_cubic
lockd rcupdate snd_pcm_oss usbcore
[ Tekkaman2440@SBC2440V4] # insmod / lib/ modules/ sculld. ko
sculld: Unknown symbol register_ldd_device
sculld: Unknown symbol register_ldd_driver
sculld: Unknown symbol unregister_ldd_driver
sculld: Unknown symbol unregister_ldd_device
insmod: cannot insert '/lib/modules/sculld.ko' : Unknown symbol in module ( - 1) : No such file or directory
[ Tekkaman2440@SBC2440V4] # insmod / lib/ modules/ lddbus. ko
Mount lddbus ok !
Bus device is ldd0 !
You can see me in sys/ module/ , sys/ devices/ and sys/ bus/ !
[ Tekkaman2440@SBC2440V4] # insmod / lib/ modules/ sculld. ko
[ Tekkaman2440@SBC2440V4] # tree - AC / sys/ module/ lddbus/ / sys/ devices/ ldd0/ / sys/ bus/ ldd/ / sys/ module/ sculld/
/ sys/ module/ lddbus/
├── holders
│ └── sculld - > . . / . . / . . / module/ sculld
├── initstate
├── refcnt
└── sections
    ├── __ksymtab
    └── __ksymtab_strings
/ sys/ devices/ ldd0/
├── power
│ └── wakeup
├── sculld0
│ ├── bus - > . . / . . / . . / bus/ ldd
│ ├── dev
│ ├── driver - > . . / . . / . . / bus/ ldd/ drivers/ sculld
│ ├── power
│ │ └── wakeup
│ ├── subsystem - > . . / . . / . . / bus/ ldd
│ └── uevent
├── sculld1
│ ├── bus - > . . / . . / . . / bus/ ldd
│ ├── dev
│ ├── driver - > . . / . . / . . / bus/ ldd/ drivers/ sculld
│ ├── power
│ │ └── wakeup
│ ├── subsystem - > . . / . . / . . / bus/ ldd
│ └── uevent
├── sculld2
│ ├── bus - > . . / . . / . . / bus/ ldd
│ ├── dev
│ ├── driver - > . . / . . / . . / bus/ ldd/ drivers/ sculld
│ ├── power
│ │ └── wakeup
│ ├── subsystem - > . . / . . / . . / bus/ ldd
│ └── uevent
├── sculld3
│ ├── bus - > . . / . . / . . / bus/ ldd
│ ├── dev
│ ├── driver - > . . / . . / . . / bus/ ldd/ drivers/ sculld
│ ├── power
│ │ └── wakeup
│ ├── subsystem - > . . / . . / . . / bus/ ldd
│ └── uevent
└── uevent
/ sys/ bus/ ldd/
├── devices
│ ├── sculld0 - > . . / . . / . . / devices/ ldd0/ sculld0
│ ├── sculld1 - > . . / . . / . . / devices/ ldd0/ sculld1
│ ├── sculld2 - > . . / . . / . . / devices/ ldd0/ sculld2
│ └── sculld3 - > . . / . . / . . / devices/ ldd0/ sculld3
├── drivers
│ └── sculld
│ ├── bind
│ ├── sculld0 - > . . / . . / . . / . . / devices/ ldd0/ sculld0
│ ├── sculld1 - > . . / . . / . . / . . / devices/ ldd0/ sculld1
│ ├── sculld2 - > . . / . . / . . / . . / devices/ ldd0/ sculld2
│ ├── sculld3 - > . . / . . / . . / . . / devices/ ldd0/ sculld3
│ ├── unbind
│ └── version
├── drivers_autoprobe
├── drivers_probe
└── version
/ sys/ module/ sculld/
├── holders
├── initstate
├── parameters
│ ├── scull_major
│ ├── scull_minor
│ ├── scull_nr_devs
│ ├── scull_qset
│ └── scull_quantum
├── refcnt
└── sections
    ├── __ex_table
    └── __param

38 directories, 33 files
[ Tekkaman2440@SBC2440V4] # cat / sys/ bus/ ldd/ version / sys/ devices/ ldd0/ sculld* / dev / sys/ bus/ ldd/ drivers/ sculld/ version
Revision: 1. 9- tekkamanninja
252: 0
252: 1
252: 2
252: 3
$Revision: 1. 21- tekkamanninja $
[ Tekkaman2440@SBC2440V4] # rmmod sculld
[ Tekkaman2440@SBC2440V4] # tree - ACd / sys/ module/ lddbus/ / sys/ devices/ ldd0/ / sys/ bus/ ldd/ / sys/ module/
/ sys/ module/ lddbus/
├── holders
└── sections
/ sys/ devices/ ldd0/
└── power
/ sys/ bus/ ldd/
├── devices
└── drivers
/ sys/ module/
├── 8250
│ └── parameters
├── atkbd
│ └── drivers
│ └── serio: atkbd - > . . / . . / . . / bus/ serio/ drivers/ atkbd
├── cdrom
├── dm9000
│ └── parameters
├── hid
│ └── parameters
├── ide_cd
│ └── parameters
├── keyboard
│ └── parameters
├── lddbus
│ ├── holders
│ └── sections
├── lockd
│ └── parameters
├── loop
├── mac80211
│ └── parameters
├── mousedev
│ └── parameters
├── nfs
│ └── parameters
├── ohci_hcd
├── printk
│ └── parameters
├── psmouse
│ ├── drivers
│ │ └── serio: psmouse - > . . / . . / . . / bus/ serio/ drivers/ psmouse
│ └── parameters
├── rcupdate
├── rd
├── redboot
├── rfd_ftl
├── rtc_ds1307
├── s3c2410_wdt
├── snd
│ └── parameters
├── snd_pcm
│ └── parameters
├── snd_pcm_oss
│ └── parameters
├── snd_seq
│ └── parameters
├── snd_seq_oss
│ └── parameters
├── snd_soc_core
├── snd_timer
│ └── parameters
├── spidev
│ └── parameters
├── sunrpc
│ └── parameters
├── tcp_cubic
│ └── parameters
├── usbcore
│ ├── drivers
│ │ ├── usb: hub - > . . / . . / . . / bus/ usb/ drivers/ hub
│ │ └── usb: usbfs - > . . / . . / . . / bus/ usb/ drivers/ usbfs
│ └── parameters
├── usbnet
├── v4l1_compat
│ └── parameters
├── vt
│ └── parameters
├── yaffs
│ └── parameters
└── zc0301
    ├── drivers
    │ └── usb: zc0301 - > . . / . . / . . / bus/ usb/ drivers/ zc0301
    └── parameters

79 directories
[ Tekkaman2440@SBC2440V4] # insmod / lib/ modules/ sculld. ko
[ Tekkaman2440@SBC2440V4] # rmmod lddbus
rmmod: lddbus: Resource temporarily unavailable
[ Tekkaman2440@SBC2440V4] # rmmod sculld
[ Tekkaman2440@SBC2440V4] # rmmod lddbus
The LDD bus device
 ldd_bus_release : lddbus
[ Tekkaman2440@SBC2440V4] #


 
在系统 中添加“tree”命令
实验中用到了Linux的常用命令“ tree ”,若使用busybox可能没有这个命令。如出现这种情况,可以下载此命 令的源码,交叉编译一下,再放到根文件系统中的/bin目录中就好。在 CalmArrow 的博客中可以下载: http://blog.chinaunix.net/u/21948/showart_297101.html
(因为源码的下载地址 ftp://mama.indstate.edu/linux/tree/ 我 一直进不去, 在这里 谢谢CalmArrow ) 

热插拔
有 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的资料网上十分丰富,我就不在这废话了,给出以下链接有兴趣的自己研究:
《 UDEV Primer 》(英文),地址: http://webpages.charter.net/decibelshelp/LinuxHelp_UDEVPrimer.html
 
《udev规则编写》(luofuchong翻译),地址: http://www.cnitblog.com/luofuchong/archive/2007/12/18/37831.html
 
《什么是udev》地址: http://blog.csdn.net/steganography/archive/2006/04/10/657620.aspx
 
《udev-FAQ 中文翻译》地址: http://gnawux.bokee.com/3225765.html
 
《udev轻松上路》地址: http://www.blog.edu.cn/user1/3313/archives/2007/1635169.shtml
 
《Udev (简体中文)》地址: http://wiki.archlinux.org/index.php/Udev_(%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87 )
 
Udev官方主页: http://www.kernel.org/pub/linux/utils/kernel/hotplug/udev.html
下载地址: http://www.kernel.org/pub/linux/utils/kernel/hotplug/
 
在《LFS》中也有介绍udev的使用,很值得参考!下载地址: http://lfs.osuosl.org/lfs/downloads/stable/
 
 
mdev: 一个简化版的udev,是busybox所带的程序,十分适合嵌入式系统。
 

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

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

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


mdev
 
在一开始建立根文件系统时,我根据 WeiBing 的博客上《UDEV on embeded Linux-2.6.19.2》(地址: http://weibing.blogbus.com/logs/4485453.html ) 这篇文章的提示,开始使用mdev,但是当时只是启动时mdev -s 一下,并没有深究。现在在学习了Linux设备模型之后,对于Linux中/dev目录的动态管理有了更深的认识,并认真的看了一下busybox中的 mdev.txt文档并翻译了一下,做成了PDF(下载地址: http://blogimg.chinaunix.net/blog/upfile2/080111091002.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行:

if ( ! strcmp ( action, "remove" ) )
       make_device( temp, 1) ;

else if ( ! strcmp ( action, "add" ) ) {
         if (env_path[2]=='l') make_device(temp,0);  //tekkamanninja
            if ( ENABLE_FEATURE_MDEV_LOAD_FIRMWARE)
                load_firmware( getenv ( "FIRMWARE" ) , temp) ;
        }

也就是在增加设备节点文件之前检查 /sys/目录下的 路径是 否为/cl ass和/bl ock(通过检查路径字符串的第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文件中 添 加 了如下指令:

# 挂载/ sys 为sysfs文件系统
    echo "----------mount /sys as sysfs"
    / bin/ mount - t tmpfs mdev / dev
    / bin/ mount - t sysfs sysfs / sys
    echo "----------Starting mdev......"
    / bin/ echo / sbin / mdev > / proc/ sys/ kernel/ hotplug
    mdev - s

注意:是 / bin/ echo / sbin / mdev > / proc/ sys/ kernel/ hotplug,并非 / bin/ echo / bin / mdev > / proc/ sys/ kernel/ hotplug。busybox的文档有错!!

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

    sculld[ 0- 1] 0: 0 666 * echo tekkaman > / tmp/ mdev

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

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

firmware

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

内核固件接口


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

# include < linux/ firmware. h>
int request_firmware( const struct firmware * * fw,

                     const char * name, /* name 为固件文件名*/

                     struct device * device) ;
/*要求用户空间定位并提供一个固件映象给内核;若成功加载, 返回值是 0(否则返回错误码)*/

/*因为 request_firmware 需要用户空间的操作, 所以返回前将保持休眠。若驱动必须使用固件而不能进入休眠时,可使用以下异步函数:*/
int request_firmware_nowait(
    struct module * module, /* = THIS_MODULE*/
    int uevent,
    const char * name,
    struct device * device,
    void * context, /*不由固件子系统使用的私有数据指针*/
    void ( * cont) ( const struct firmware * fw, void * context) ) ;
/*如果一切正常,request_firmware_nowait 开始固件加载过程并返回 0. 过了一段时间后(默认10秒),将用加载的结果(若加载失败, fw 为 NULL)作为参数调用 cont。*/

/* fw 参数指向以下结构体:*/
struct firmware {
    size_t size;
    u8 * data;
} ;
/*那个结构包含实际的固件, 它现在可被下载到设备中.但是请注意:在发送它到硬件之前,必须检查这个文件以确保它是正确的固件映象(设备固件常常包含标识字符串、 校验和等等)*/

/*当固件已经发送到设备后,应当释放 firmware 结构体, 使用:*/
void release_firmware( struct firmware * fw) ;

注意:要使用firmware,必须 要在配置内核时选上:

   Device Drivers  --->   

          Generic Driver Options  --->     

              <*> Userspace firmware loading support

否则会出现: Unknown symbol release_firmware 和: Unknown symbol request_firmware 的错误。
              

固件接口工作 原理

固件子系统使用 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 开发板实验
 
实验源码: http://blogimg.chinaunix.net/blog/upfile2/080114113255.gz
 
实验现 象:

[ Tekkaman2440@SBC2440V4] # ls - l / dev/ sculld*
ls: / dev/ sculld* : No such file or directory
[ Tekkaman2440@SBC2440V4] # cat / tmp/ mdev
cat: can't open ' / tmp/ mdev': No such file or directory

[Tekkaman2440@SBC2440V4]#insmod /lib/modules/lddbus.ko
Mount lddbus ok !
Bus device is ldd0 !
You can see me in sys/module/ , sys/devices/ , sys/class/ and sys/bus/ !
[Tekkaman2440@SBC2440V4]#insmod /lib/modules/sculld.ko
[Tekkaman2440@SBC2440V4]#ls -l /dev/sculld*
crw-rw-rw-     1 root     root     252,   0 Jan  1 00:00 /dev/sculld0
crw-rw-rw-     1 root     root     252,   1 Jan  1 00:00 /dev/sculld1
crw-rw----     1 root     root     252,   2 Jan  1 00:00 /dev/sculld2
crw-rw----     1 root     root     252,   3 Jan  1 00:00 /dev/sculld3
[Tekkaman2440@SBC2440V4]#rmmod sculld
The LDD class
 ldd_classdev_release : sculld0 release!
The LDD class
 ldd_classdev_release : sculld1 release!
The LDD class
 ldd_classdev_release : sculld2 release!
The LDD class
 ldd_classdev_release : sculld3 release!
[Tekkaman2440@SBC2440V4]#ls -l /dev/sculld*
ls: /dev/sculld*: No such file or directory
[Tekkaman2440@SBC2440V4]#cat /tmp/mdev
tekkaman

 

到此 Linux设备模型 的学习暂时告一段落。

你可能感兴趣的:(基于类linux的开发,嵌入式系统的研究与开发)