首页 > 代码库 > Linux内核驱动注册方式泛谈

Linux内核驱动注册方式泛谈

 Linux驱动注册有多种方式,通常是以内核提供的表征数据结构封装后按照内核子系统提供的接口函数进行注册,还有一些是比较复杂的以链表方式进行维护。以下对几种驱动注册方式进行介绍:

一、子系统有专门的驱动注册函数:

例如RTC子系统,提供rtc_device_register注册接口函数。

例如:

rtc_device_register(client->name,&client->dev, &rx8025_rtc_ops, THIS_MODULE);

static struct rtc_class_ops rx8025_rtc_ops= {

         .read_time= rx8025_get_time,

         .set_time= rx8025_set_time,

         .read_alarm= rx8025_read_alarm,

         .set_alarm= rx8025_set_alarm,

         .alarm_irq_enable= rx8025_alarm_irq_enable,

};

主要实现rtc_class_ops。使用RTC接口rtc_device_register,注册到RTC子系统中,受RTC子系统控制,包括设备创建,PROC和SYS文件系统属性创建。可以使用RTC子系统接口文件interface.c中的函数。

另外网口类型驱动也是有专门的接口:

register_netdev(ndev);

注册网口后受内核管理。

 

二、没有专门的驱动注册函数

这类驱动注册比较直接,按照通用的字符驱动函数进行。例如看门狗类驱动。采用misc类型驱动进行注册。

例如:

misc_register(&rc32434_wdt_miscdev);

static const struct file_operationsrc32434_wdt_fops = {

         .owner               = THIS_MODULE,

         .llseek                = no_llseek,

         .write                 = rc32434_wdt_write,

         .unlocked_ioctl         = rc32434_wdt_ioctl,

         .open                 = rc32434_wdt_open,

         .release   = rc32434_wdt_release,

};

实现看门狗自有的接口。

 

三、复杂驱动

   该类驱动在内核配置中有专门的选项,一旦选中该选项,在内核启动后会执行相关系统模块初始化,维护一个链表,而具体驱动是往该链表中添加成员,系统模块会检测新成员加入并完成加入到子系统处理工作。

例如PCI:

PCI子系统在注册时启动BIOS:

subsys_initcall(pcibios_init);

其中subsys_initcall在内核初始化时进行调用。

关于subsys_initcall定义:

#define pure_initcall(fn)             __define_initcall("0",fn,0)

#define core_initcall(fn)             __define_initcall("1",fn,1)

#define core_initcall_sync(fn)        __define_initcall("1s",fn,1s)

#define postcore_initcall(fn)         __define_initcall("2",fn,2)

#definepostcore_initcall_sync(fn)    __define_initcall("2s",fn,2s)

#define arch_initcall(fn)             __define_initcall("3",fn,3)

#define arch_initcall_sync(fn)        __define_initcall("3s",fn,3s)

#define subsys_initcall(fn)           __define_initcall("4",fn,4)

#define subsys_initcall_sync(fn)      __define_initcall("4s",fn,4s)

#define fs_initcall(fn)               __define_initcall("5",fn,5)

#define fs_initcall_sync(fn)          __define_initcall("5s",fn,5s)

#define rootfs_initcall(fn)    __define_initcall("rootfs",fn,rootfs)

#define device_initcall(fn)           __define_initcall("6",fn,6)

#define device_initcall_sync(fn)      __define_initcall("6s",fn,6s)

#define late_initcall(fn)             __define_initcall("7",fn,7)

#define late_initcall_sync(fn)        __define_initcall("7s",fn,7s)

在链接脚本vmlinux.lds中有这么一段:


各段在内核文件中的位置:


当内核在配置是选中PCI总线子系统时,内核启动后即会执行subsys_initcall(pcibios_init);

该模块维护一个全局变量hose_list。并以链表管理的方式进行管理,一旦该链表有新成员添加即会使用PCI子系统对其进行处理,包括扫描,探测等。

   该方式主要工作是要往hose_list链表中添加成员然后即可使用PCI子系统。以Powerpc为例,PCI流程示意图如下所示:


CPU在内核启动后初始化ARCH重要模块,其中添加一个PCI桥,桥对Powerpc 的PCI控制器资源解析并分配一个controller控制器,然后添加到hose_list链表中。完成注册工作。

而pci_32.c文件中是对BOIS的管理,其中对hose_list链表的管理,管理添加进行的成员。发现有成员注册进来即会使用PCI子系统进行相关处理。

类似的RapdiIO驱动:

RapidIO系统也是采用类似PCI的方式,在内核配置时选中RapidIO后系统运行即会启动RapidIO子系统。

在rio.c中:

int __devinit rio_init_mports(void)

{

         struct rio_mport *port;

         list_for_each_entry(port,&rio_mports, node) {

                   if(port->host_deviceid >= 0)

                            rio_enum_mport(port);

                   else

                            rio_disc_mport(port);

         }

         rio_init();

         return 0;

}

device_initcall_sync(rio_init_mports);

其中device_initcall_sync就是上面讲到的初始化段。

#define device_initcall_sync(fn)      __define_initcall("6s",fn,6s)

执行这个初始化启动RapidIo子系统的条件是内核中有相关配置。

再以Powerpc为例,以下是添加到RapidIO子系统的过程:


其中,rio.c中维护rio-mports链表,检测是否有新成员注册进来,一旦有新成员注册进来即添加到rapidIO子系统中进行处理。

  而fsl_rio.c文件中有subsys_initcall初始化,完成RapdidIo驱动注册,这部分的工作也是内核配置之后即可完成。而注册完之后按照Powerpc上的资源进行解析并添加mport到rio-mports链表中。完成注册。

 RapidIo的注册过程与PCI的注册过程属于同一种类型的。都是内核在配置时选中相关支持启动子系统,子系统启动一个检测链表的工作。而具体的驱动需要往该链表中添加成员。