16 内核中注册设备

Posted xuan01

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了16 内核中注册设备相关的知识,希望对你有一定的参考价值。

cosmos为简化驱动的设计,把程序和内核链接到一起,省略了加载驱动的过程,cosmos自动加载驱动,在驱动中注册相应的设备;

流程:内核初始化驱动,内核扫描驱动表,加载一个驱动,创建driver_t 结构,调用驱动入口函数,驱动开始运行,创建device_t 结构,向内核注册设备,查看是否还有驱动,初始化完成;

驱动程序表:

drventyexit类型 ,就是函数指针数组,存放驱动入口函数;init_krl 函数调用init_krldriver 函数;

init_krldriver 函数:遍历驱动程序表的每个驱动程序入口,并作为参数传给krlrun_driverentry 函数;

运行驱动程序:

  调用驱动程序入口函数:

krlrun_driverentry 函数:先调用new_driver_dsc函数,建立一个driver_t 结构;用驱动描述符指针 drvp 指向;然后调用drventry 函数,运行程序入口函数;最后调用krldriver_add_system 函数,把驱动程序加入系统;

  驱动入口函数流程:

首先要建立建立一个设备描述符,接着把驱动程序的功能函数设置到 driver_t 结构中的 drv_dipfun 数组中,并将设备挂载到驱动上,然后要向内核注册设备,最后驱动程序初始化自己的物理设备,安装中断回调函数

  设备挂载到驱动:

krldev_add_driver 函数,遍历这个驱动上所有设备;比较设备id 有相同的则返回错误,将设备挂载到驱动上;

  向内核注册设备:

krlnew_device 函数:遍历设备类型表上的所有设备,检查有无设备id冲突,没有的话就加入全局设备链表中;再加入对应设备类型的链表中;最终通过设备id找到对应设备;

  安装中断回调函数:

该函数是驱动程序提供,内核提供接口安装该函数;

krlnew_devhandle 安装接口函数:调用内核层中断框架接口 krladd_irqhandle 函数;

krladd_irqhandle 函数:创建一个intserdsc_t结构,保存设备和驱动程序提供的回调函数,中断处理框架和设备驱动相联系;

中断处理框架找到对应的intserdsc_t结构,又能从 该结构中得到回调函数和设备描述符,继而调用回调函数,执行中断;

  驱动加入内核:

krldriver_add_system 挂载函数:将driver_t 结构挂载到全局驱动程序链表上,并增加驱动程序计数变量;

 

新旧内核的device设备注册对比

转自:http://blog.chinaunix.net/uid-7332782-id-3268801.html

 

1. Version2.6内核启动过程

    start_kernel( )  //板子上电启动后进入start_kernel( ),相当于程序的main入口

        -->setup_arch(&command_line)  //command_line由内核传入

            -->mdesc = setup_machine(machine_arch_type);

                -->list = lookup_machine_type(nr); //汇编实现查找机器码所定义的平台,找到后返回mdesc结构               

            -->init_machine = mdesc->init_machine;  //struct machine_desc *mdesc;machine_desc结构很重要,

        -->rest_init()

            -->kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND); //定义进程kernel_init,pid=1,在kthreadd进程创建好后调度运行          

                -->kernel_init()

                    -->do_basic_setup()

                        -->driver_init()

                            -->devices_init()

                            -->buses_init()

                            -->classes_init()

                            -->platform_bus_init()

                        -->do_initcalls()  //此函数很重要,执行了initcall表中所有的函数,包含了init_machine(saar_init())函数                       

                            -->saar_init()

                    -->init_post()    //调度用户空间程序,比如bash,在用户空间死循环执行程序                   

            -->pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);  //定义进程kthreadd

2. Version3.x内核启动过程和2.6版本在init_machine加载设备资源的差异对比

  • 在2.6内核中, 在setup_arch()中,举个例子imx5,

-->init_machine=mdesc->init_machine;  //init/main.c->start_kernel()->setup_arch()

  -->.init_machine=mxc_board_init;    //arch/arm/mach-mx5/mx50_arm2.c->MACHINE_START() ,宏即时初始化machine_desc结构体        

    -->mxc_register_device(&mxc_dma_device);  //arch/arm/mach-mx5/mx50_arm2.c->mxc_board_init()

    -->mxc_register_device(&mxc_wdt_device);  //mxc_wdt_device这些设备资源也都申明在mach-mx50下面

    -->mxc_register_device(&mxci2c_devices[0]);

    -->..........

当然在setup_arch()中对init_machine进行初始化,这个时候并没有调用init_machine函数,而是将init_machine属性定义为arch_initcall,然后在do_initcalls()中进行遍历调用,具体见上述的启动过程。

由上述可以看出,在系统启动时,device设备就已经register到总线上了,而3.x以后已经不在mach-**中申明设备资源了,那启动流程如何呢,见下一节。

  •  在3.x内核中,在setup_arch()是这么处理的, 首先解析dtb

-->setup_arch()  //init/main.c->start_kernel()->setup_arch()

  -->mdesc=setup_machine_fdt();

  -->unflatten_device_tree()

     -->__unflatten_device_tree()

        -->unflatten_dt_node()  //到此基本完成dts中node到链表的操作

节点中的设备注册(这儿也是在init_machine中完成,但是还没找到依据,并不像2.6中,init_machine有arch_initcall属性--解决:其实和2.6一样,在./arch/arm/kernel/setup.c中也是对init_machine添加arch_initcall属性。)

-->DT_MACHINE_START  //machine_desc结构体赋值

  -->.init_machine=imx6sx_init_machine

     -->of_platform_populate();   //imx6sx_init_machine()调用

        -->of_platform_bus_create();   //由for_each_child_of_node()调用,遍历device tree中每个节点

           -->of_platform_device_create_pdata()

              -->of_device_alloc()    //为每个device申请空间

              -->platform_device_put()

           -->of_platform_bus_create()

到此完成设备的注册。

在3.x的setup.c中关于init_machine的调用是这么定义的

 1 static int __init customize_machine(void)
 2 {
 3     /*
 4      * customizes platform devices, or adds new ones
 5      * On DT based machines, we fall back to populating the
 6      * machine from the device tree, if no callback is provided,
 7      * otherwise we would always need an init_machine callback.
 8      */
 9     if (machine_desc->init_machine)
10         machine_desc->init_machine();
11 #ifdef CONFIG_OF
12     else
13         of_platform_populate(NULL, of_default_bus_match_table,
14                     NULL, NULL);
15 #endif
16     return 0;
17 }

 

关于init_machine到底会不会被执行,在Documentation/Devicetree/usage-model.txt中有这么一段话

The most interesting hook in the DT context is .init_machine() which
is primarily responsible for populating the Linux device model with
data about the platform.  Historically this has been implemented on
embedded platforms by defining a set of static clock structures,
platform_devices, and other data in the board support .c file, and
registering it en-masse in .init_machine().  When DT is used, then
instead of hard coding static devices for each platform, the list of
devices can be obtained by parsing the DT, and allocating device
structures dynamically.

The simplest case is when .init_machine() is only responsible for
registering a block of platform_devices.  A platform_device is a concept
used by Linux for memory or I/O mapped devices which cannot be detected
by hardware, and for composite or virtual devices (more on those
later).  While there is no platform device terminology for the DT,
platform devices roughly correspond to device nodes at the root of the
tree and children of simple memory mapped bus nodes.

 

以上是关于16 内核中注册设备的主要内容,如果未能解决你的问题,请参考以下文章

RK3399驱动开发 | 16 -PCA9557 GPIO 扩展芯片的使用(linux5.4.32内核)

RK3399驱动开发 | 16 -PCA9557 GPIO 扩展芯片的使用(linux5.4.32内核)

内核LED驱动框架讲解以及led设备注册示例代码

内核启动早期的打印

新旧内核的device设备注册对比

Linux设备驱动-内核如何管理设备号