订阅
纠错
加入自媒体

Linux设备驱动统一模型解析

2021-04-13 09:48
一口Linux
关注

2. 设备树解析流程

 2.1.内核启动并获取设备树

在uboot引导内核的时候,会将设备树在物理内存中的物理起始内存地址传递给Linux内核,然后Linux内核在unflattern_device_tree中解析设备镜像,并利用扫描到的信息创建由device node构成的链表,全局变量of_allnodes指向链表的根节点,设备树的每一个节点都由一个struct device_node与之对应。unflatten_device_tree的意思是解开设备树,在这个函数里调用了__unflatten_device_tree这一函数:


* __unflatten_device_tree - create tree of device_nodes from flat blob

* unflattens a device-tree, creating the
* tree of struct device_node. It also fills the "name" and "type"
* pointers of the nodes so the normal device-tree walking functions
* can be used.
* @blob: The blob to expand
* @mynodes: The device_node tree created by the call
* @dt_alloc: An allocator that provides a virtual address to memory
* for the resulting tree

static void __unflatten_device_tree(struct boot_param_header *blob,
       struct device_node **mynodes,
       void * (*dt_alloc)(u64 size, u64 align))

所以,现在为止,我们得到了一个名为of_allnodes的struct *device_node,它指向了设备树展开后的device_node树,后续的操作都是基于device_node树。

2.2.创建platform_device

内核从启动到创建设备的过程大致如下:在do_initcalls中会传递level给do_initcall_level来调用不同层次的初始化函数,level的对应关系见linux-3.10/include/linux/init.h 第196行。在这个初始化过程中,会调用一个customize_machine的函数。

2.3.Platform driver注册流程

此节分析Platform driver的注册流程,以memctrl驱动的注册为例分析。关于系统调用驱动初始化函数的流程分析,参考自动初始化机制章节。本章节分析从设备驱动文件的xxx_init函数开始分析。

2.3.1. struct platform_driver

platform_driver是在device_driver之上的一层封装,其结构如下:

struct platform_driver {
int (*probe)(struct platform_device *);   探测函数
int (*remove)(struct platform_device *);  驱动卸载时执行
void (*shutdown)(struct platform_device *);  关机时执行函数
int (*suspend)(struct platform_device *, pm_message_t state);  挂起函数
int (*resume)(struct platform_device *);     恢复函数
struct device_driver driver;           管理的driver对象
const struct platform_device_id *id_table;   匹配时使用
};

2.3.2. struct device_driver

struct device_driver是系统提供的基本驱动结构:

struct device_driver {
const char   *name;  驱动名称
struct bus_type   *bus; 所属总线
struct module   *owner; 模块拥有者
const char   *mod_name; 内建的模块使用
bool suppress_bind_attrs;  是否绑定到sysfs
const struct of_device_id  *of_match_table; 设备树匹配表
const struct acpi_device_id  *acpi_match_table; ACPI匹配表
int (*probe) (struct device *dev);  探测设备
int (*remove) (struct device *dev); 与设备脱离时调用
void (*shutdown) (struct device *dev); 在关机时关闭设备
int (*suspend) (struct device *dev, pm_message_t state); 使设备进入睡眠模式调用
int (*resume) (struct device *dev);  唤醒设备时调用
const struct attribute_group **groups; 自动创建的默认属性组
const struct dev_pm_ops *pm;  设备的功耗管理
struct driver_private *p; 驱动的私有数据
};

2.3.3. platform_driver_register

Platform_driver的注册接口是platform_driver_register,其定义如下:

int platform_driver_register(struct platform_driver *drv)

drv->driver.bus = &platform_bus_type;  设置总线类型
if (drv->probe)    确认定义了probe函数    
 drv->driver.probe = platform_drv_probe;  里面实际调用的是drv的probe函数
if (drv->remove)
 drv->driver.remove = platform_drv_remove;
if (drv->shutdown)
 drv->driver.shutdown = platform_drv_shutdown;
return driver_register(&drv->driver);

platform_driver_register接口是为注册总线驱动做一些准备工作,定义了总线类型,设置了driver的部分接口,最后driver_register会向总线注册驱动

2.3.4. driver_registerint driver_register(struct device_driver *drv)

int ret;
struct device_driver *other;
BUG_ON(!drv->bus->p);
if ((drv->bus->probe && drv->probe) ||
    (drv->bus->remove && drv->remove) ||
    (drv->bus->shutdown && drv->shutdown))
 printk(KERN_WARNING "Driver '%s' needs updating - please use "
  "bus_type methods", drv->name);
other = driver_find(drv->name, drv->bus); 检查驱动是否已经注册
if (other) {
 printk(KERN_ERR "Error: Driver '%s' is already registered, "
  "aborting...", drv->name);
 return -EBUSY;

ret = bus_add_driver(drv);   driver_register的主要工作放在了这里
if (ret)
 return ret;
ret = driver_add_groups(drv, drv->groups); 主要是在sysfs添加驱动属性
if (ret) {
 bus_remove_driver(drv);
 return ret;

kobject_uevent(&drv->p->kobj, KOBJ_ADD);   涉及到uevent,暂时不分析
return ret;

2.3.5. bus_add_driver

由以上分析可知,驱动的注册,重点在bus_add_driver()函数,它会向总线添加驱动:

Drivers/base/bus.c
int bus_add_driver(struct device_driver *drv)

struct bus_type *bus;
struct driver_private *priv;  包含与驱动相关的kobject和klist结构
int error = 0;
bus = bus_get(drv->bus);  获取设备所属的总线类型
if (!bus)
 return -EINVAL;
pr_debug("bus: '%s': add driver %s", bus->name, drv->name);
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
if (!priv) {
 error = -ENOMEM;
 goto out_put_bus;

klist_init(&priv->klist_devices, NULL, NULL);
priv->driver = drv;
drv->p = priv;
priv->kobj.kset = bus->p->drivers_kset;
error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,
        "%s", drv->name);
if (error)
 goto out_unregister;
klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);
if (drv->bus->p->drivers_autoprobe) { 如果设置了自动探测
 error = driver_attach(drv);
 if (error)
  goto out_unregister;

module_add_driver(drv->owner, drv);
error = driver_create_file(drv, &driver_attr_uevent);
if (error) {
 printk(KERN_ERR "%s: uevent attr (%s) failed",
  __func__, drv->name);

error = driver_add_attrs(bus, drv);
if (error) {
  How the hell do we get out of this pickle? Give up
 printk(KERN_ERR "%s: driver_add_attrs(%s) failed",
  __func__, drv->name);

if (!drv->suppress_bind_attrs) {
 error = add_bind_files(drv);
 if (error) {
   Ditto
  printk(KERN_ERR "%s: add_bind_files(%s) failed",
   __func__, drv->name);
 }

return 0;
out_unregister:
kobject_put(&priv->kobj);
kfree(drv->p);
drv->p = NULL;
out_put_bus:
bus_put(bus);
return error;

2.3.6. driver_attach

driver_attach会尝试绑定设备和驱动。编译总线上的所有设备,然驱动挨个尝试匹配,如果driver_probe_device()返回0且@dev->driver被设置,就代表找到了一对兼容的设备驱动。

int driver_attach(struct device_driver *drv)

return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);

EXPORT_SYMBOL_GPL(driver_attach);
2.3.7. __driver_attach

对于每一个总线的设备,driver_attach都会调用__driver_attach来尝试与驱动匹配。

static int __driver_attach(struct device *dev, void *data)

struct device_driver *drv = data;

 * Lock device and try to bind to it. We drop the error
 * here and always return 0, because we need to keep trying
 * to bind to devices and some drivers will return an error
 * simply if it didn't support the device.
 *
 * driver_probe_device() will spit a warning if there
 * is an error.
 
if (!driver_match_device(drv, dev))  匹配设备和驱动,这里调用的是platform_match
 return 0;
if (dev->parent)  Needed for USB
 device_lock(dev->parent);
device_lock(dev);  设置互斥锁,防止其他进程访问设备资源
if (!dev->driver)  
如果设备没有驱动,则为设备探测驱动,这个函数与注册设备调用的是同一个函数
 driver_probe_device(drv, dev);  
device_unlock(dev);
if (dev->parent)
 device_unlock(dev->parent);
return 0;

driver_probe_device里调用really_probe函数,并在really_probe中调用驱动文件中的probe函数,对于memctrl驱动而言,就是xxxx_memctrl_probe函数。至此,platfprm driver就注册好了。

2.4.Platform Bus的匹配原则

由以上的代码分析得知,注册platform device时,会调用__device_attach -> driver_match_device,注册platform driver时,会调用__driver_attach -> driver_match_device,也就是说设备和驱动都会调用到这个函数:

static inline int driver_match_device(struct device_driver *drv,
         struct device *dev)

return drv->bus->match ? drv->bus->match(dev, drv) : 1;

drv->bus->match,这是驱动绑定的总线提供的匹配函数,这里注册的是platform总线设备,而platform总线的定义参考3.2.6 platform_bus_type。Platform对应的match函数为:platform_match:

static int platform_match(struct device *dev, struct device_driver *drv)

struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);
 Attempt an OF style match first
if (of_driver_match_device(dev, drv))
 return 1;
 Then try ACPI style match
if (acpi_driver_match_device(dev, drv))
 return 1;
 Then try to match against the id table
if (pdrv->id_table)
 return platform_match_id(pdrv->id_table, pdev) != NULL;
 fall-back to driver name match
return (strcmp(pdev->name, drv->name) == 0);

2.4.1. of_driver_match_device

根据驱动的of_match_table判断是否有驱动与之匹配。对memctrl驱动而言,其of_match_table如下:

static struct of_device_id xxxx_memctrl_of_match[] = {
{ .compatible = "xxxx,memctrl", },
  {},
};

of_driver_match_device的执行流程如下:

所以重点应该在__of_match_node函数:

2.4.1.1. __of_match_nodestatic const struct of_device_id *__of_match_node(const struct of_device_id *matches, const struct device_node *node)

if (!matches)
 return NULL;
while (matches->name[0] || matches->type[0] || matches->compatible[0]) {
 int match = 1;
 if (matches->name[0])   查找名字
      match &= node->name && !strcmp(matches->name, node->name);
 if (matches->type[0])   查找类型
      match &= node->type && !strcmp(matches->type, node->type);
 if (matches->compatible[0])  查找属性,检测节点的compatible是否与驱动的一致
      match &= __of_device_is_compatible(node, matches->compatible);
 if (match)
      return matches;
     matches++;

return NULL;

3. 使用设备资源  

4. 自动初始化机制 

4.1.编译到内核

4.1.1. module_init宏展开

Linux中每一个模块都有一个module_init函数,并且有且只有一个,其定义如下:


* module_init() - driver initialization entry point
* @x: function to be run at kernel boot time or module insertion

* module_init() will either be called during do_initcalls() (if
* builtin) or at module insertion time (if a module).  There can only
* be one per module.

#define module_init(x) __initcall(x);
__initcall(x)定义如下:
#define __initcall(fn) device_initcall(fn)

device_initcall(fn)定义如下:

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

__define_initcall的定义如下:

initcalls are now grouped by functionality into separate
* subsections. Ordering inside the subsections is determined
* by link order.
* For backwards compatibility, initcall() puts the call in
* the device init subsection.

* The `id' arg to __define_initcall() is needed so that multiple initcalls
* can point at the same handler without causing duplicate-symbol build errors.


#define __define_initcall(fn, id)
static initcall_t __initcall_##fn##id __used
__attribute__((__section__(".initcall" #id ".init"))) = fn

Initcalls现在按照功能分组到单独的子部分。子部分内部的顺序由链接顺序决定。为了向后兼容,initcall()将调用放到device init小节中。需要定义initcall()的’id’参数,以便多个initcall可以指向同一个处理程序,而不会导致重复符号构建错误。若不理解上述代码的用法,可以参考__attribute__的section用法和C语言宏定义中#和##的用法。所以将__define_initcall展开将会是下面的内容:

假设__define_initcall(led_init, 6)
Static initcall_t __initcall_led_init6 __used
__attribute__((__section__(".initcall6.init"))) = led_init

即是定义了一个类型为initcall_t的函数指针变量__initcall_led_init6,并赋值为led_init,该变量在链接时会链接到section(.initcall6.init)。

4.1.2. 链接脚本

在linux3.10/arch/arm/kernel/vmlinux.lds.S中:

......
SECTIONS   line 54

......
.init.data : {  line 202
#ifndef CONFIG_XIP_KERNEL
 INIT_DATA
#endif
 INIT_SETUP(16)
 INIT_CALLS
 CON_INITCALL
 SECURITY_INITCALL
 INIT_RAM_FS

......

在linux3.10/include/asm-generic/vmlinux.lds.h中:

#define VMLINUX_SYMBOL(x) __VMLINUX_SYMBOL(x)
#define __VMLINUX_SYMBOL(x) x
......  line 664
#define INIT_CALLS_LEVEL(level)      
 VMLINUX_SYMBOL(__initcall##level##_start) = .;  
 *(.initcall##level##.init)    
 *(.initcall##level##s.init)    
#define INIT_CALLS      
 VMLINUX_SYMBOL(__initcall_start) = .;  
 *(.initcallearly.init)    
 INIT_CALLS_LEVEL(0)    
 INIT_CALLS_LEVEL(1)    
 INIT_CALLS_LEVEL(2)    
 INIT_CALLS_LEVEL(3)    
 INIT_CALLS_LEVEL(4)    
 INIT_CALLS_LEVEL(5)    
 INIT_CALLS_LEVEL(rootfs)    
 INIT_CALLS_LEVEL(6)    
 INIT_CALLS_LEVEL(7)    
 VMLINUX_SYMBOL(__initcall_end) = .;
......

所以 INIT_CALLS_LEVEL(6)会展开为:

__initcall6_start = .;  *(.initcall6.init)   *(.initcall6s.init)

所以__initcall_led_init6会链接到

section(.initcall6.init)
4.1.3. 初始化

内核启动流程为:

do_initcall_level的主要内容如下:

linux3.10/init/main.c line 744
static void __init do_initcall_level(int level)

.....
for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
         do_one_initcall(*fn);

由代码可知,内核会依次调用level段存储的初始化函数。比如对于模块来说level等于6。

4.2.动态加载的模块(.ko)4.2.1. Module_init展开

如果设置为编译成动态加载的模块(.ko),module_init的展开形式与编译到内核不一样。

Each module must use one module_init().
#define module_init(initfn)    
static inline initcall_t __inittest(void)     检查定义的函数是否符合initcall_t类型
{ return initfn; }    
int init_module(void) __attribute__((alias(#initfn)));

alias属性是GCC的特有属性,将定义init_module为函数initfn的别名,所以module_init(initfn)的作用就是定义一个变量名 init_module,其地址和initfn是一样的。

4.2.2. *mod.c文件

编译成module的模块都会自动产生一个*.mod.c的文件,例如:

struct module __this_module
__attribute__((section(".gnu.linkonce.this_module"))) = {
.name = KBUILD_MODNAME,
.init = init_module,
#ifdef CONFIG_MODULE_UNLOAD
.exit = cleanup_module,
#endif
.arch = MODULE_ARCH_INIT,
};

即定义了一个类型为module的全局变量__this_module,其成员.init就是上文由module_init定义的init_module变量。并且__this_module会被链接到section(".gnu.linkonce.this_module")。

4.2.3. 动态加载

insmod是busybox提供的用户层命令:路径busybox/modutils/ insmod.c

insmod_main
bb_init_module
init_module

路径busybox/modutils/modutils.c:

#define init_module(mod, len, opts) .
syscall(__NR_init_module, mod, len, opts)该系统调用对应内核层的sys_init_module函数

路径:kernel/module.c

SYSCALL_DEFINE3(init_module,…)
//加载模块的ko文件,并解释各个section,重定位
mod = load_module(umod, len, uargs);
//查找section(".gnu.linkonce.this_module")
modindex = find_sec(hdr, sechdrs, secstrings,".gnu.linkonce.this_module");
//找到Hello_module.mod.c定义的module数据结构
mod = (void *)sechdrs[modindex].sh_addr;
if (mod->init != NULL)
ret = do_one_initcall(mod->init); //调用initfn.
4.3.__attribute__的section用法

__define_initcall使用了gcc的 __attribute__众多属性中的section子项,其使用方式为:

__attribute__((__section__("section_name")))

其作用是将作用的函数或数据放入指定的名为”section_name”的段。

4.4. C语言宏定义中#和##的用法4.4.1. 一般用法

我们使用#把宏参数变为一个字符串。

#define PRINT(FORMAT,VALUE)
printf("The value of"#VALUE"is " FORMAT"",VALUE)

调用:printf("%d",x+3);     -->     打印:The value of x+3 is 20

这是因为”The value of”#VALUE”is ” FORMAT””实际上是包含了”The value of “,#VALUE,”is “,FORMAT,”” 五部分字符串,其中VALUE和FORMAT被宏参数的实际值替换了。

用##把两个宏参数贴合在一起

#define ADD_TO_SUM(sum_number,val) sum##sum_bumber+=(val)

调用:ADD_TO_SUM(2,100);     -->     打印:sum2+=(100)

需要注意的是凡宏定义里有用'#'或'##'的地方宏参数是不会再展开。

4.4.2. '#'和'##'的一些应用特例合并匿名变量名#define  ___ANONYMOUS1(type, var, line)  type  var##line
#define  __ANONYMOUS0(type, line)  ___ANONYMOUS1(type, _anonymous, line)
#define  ANONYMOUS(type)  __ANONYMOUS0(type, __LINE__)

例:ANONYMOUS(static int);  即 static int _anonymous70;  70表示该行行号;第一层:ANONYMOUS(static int); -->  __ANONYMOUS0(static int, LINE);第二层:                           -->  ___ANONYMOUS1(static int, _anonymous, 70);第三层:                           -->  static int  _anonymous70;即每次只能解开当前层的宏,所以__LINE__在第二层才能被解开;

填充结构#define  FILL(a)   {a, #a}
enum IDD{OPEN, CLOSE};
typedef struct MSG{
 IDD id;
 const char  msg;
}MSG;
MSG _msg[] = {FILL(OPEN), FILL(CLOSE)};

相当于:

MSG _msg[] = {{OPEN, OPEN},
             {CLOSE, CLOSE}};
记录文件名#define  _GET_FILE_NAME(f)   #f
#define  GET_FILE_NAME(f)    _GET_FILE_NAME(f)
static char  FILE_NAME[] = GET_FILE_NAME(__FILE__);
得到一个数值类型所对应的字符串缓冲大小#define  _TYPE_BUF_SIZE(type)  sizeof #type
#define  TYPE_BUF_SIZE(type)   _TYPE_BUF_SIZE(type)
char  buf[TYPE_BUF_SIZE(INT_MAX)];
    --  char  buf[_TYPE_BUF_SIZE(0x7fffffff)];
    --  char  buf[sizeof 0x7fffffff];

这里相当于:

char  buf[11];
- END -


<上一页  1  2  3  
声明: 本文由入驻维科号的作者撰写,观点仅代表作者本人,不代表OFweek立场。如有侵权或其他问题,请联系举报。

发表评论

0条评论,0人参与

请输入评论内容...

请输入评论/评论长度6~500个字

您提交的评论过于频繁,请输入验证码继续

暂无评论

暂无评论

人工智能 猎头职位 更多
扫码关注公众号
OFweek人工智能网
获取更多精彩内容
文章纠错
x
*文字标题:
*纠错内容:
联系邮箱:
*验 证 码:

粤公网安备 44030502002758号