介绍

在linux kernel中通过module_platform_driver来实现模块化平台驱动。大量的设备驱动程序都基于该种方式来实现,使用频次非常的高,在linux kernel 5.4.124的代码中搜索module_platform_driver共有2356次引用。

这个宏的使用方式大相径庭,有一套成熟的代码书写方式,将驱动程序入口符号作为宏的参数,基本格式如下:

历史

它的定义在include/linux/platform_device.h中,从文件的名字来看可知它存在的意义是基于platform_device的。platform_device.h这个文件在2005年的linux-2.6.15就存在了。

platform_device.h在创建初期并没有现在这么多丰富的功能,通过platform_xxx_register来注册驱动和设备,并没有提供module_platform_driver这个辅助宏。

/** platform_device.h - generic, centralized driver model** Copyright (c) 2001-2003 Patrick Mochel <mochel@osdl.org>** This file is released under the GPLv2** See Documentation/driver-model/ for more information.*/#ifndef _PLATFORM_DEVICE_H_
#define _PLATFORM_DEVICE_H_#include <linux/device.h>struct platform_device {const char * name;u32  id;struct device dev;u32  num_resources;struct resource * resource;
};#define to_platform_device(x) container_of((x), struct platform_device, dev)extern int platform_device_register(struct platform_device *);
extern void platform_device_unregister(struct platform_device *);extern struct bus_type platform_bus_type;
extern struct device platform_bus;extern struct resource *platform_get_resource(struct platform_device *, unsigned int, unsigned int);
extern int platform_get_irq(struct platform_device *, unsigned int);
extern struct resource *platform_get_resource_byname(struct platform_device *, unsigned int, char *);
extern int platform_get_irq_byname(struct platform_device *, char *);
extern int platform_add_devices(struct platform_device **, int);extern struct platform_device *platform_device_register_simple(char *, unsigned int, struct resource *, unsigned int);extern struct platform_device *platform_device_alloc(const char *name, unsigned int id);
extern int platform_device_add_resources(struct platform_device *pdev, struct resource *res, unsigned int num);
extern int platform_device_add_data(struct platform_device *pdev, void *data, size_t size);
extern int platform_device_add(struct platform_device *pdev);
extern void platform_device_put(struct platform_device *pdev);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;
};extern int platform_driver_register(struct platform_driver *);
extern void platform_driver_unregister(struct platform_driver *);#define platform_get_drvdata(_dev) dev_get_drvdata(&(_dev)->dev)
#define platform_set_drvdata(_dev,data) dev_set_drvdata(&(_dev)->dev, (data))#endif /* _PLATFORM_DEVICE_H_ */

platform_xxx_register这类宏在linux kernel 5.4.124中也在使用。

不能通过引用计数少或者版本迭代的原因来评价这两类宏谁好谁坏,各自有各自的应用场景。当使用platform_xxx_register时,基本格式也是比较固定的,例如:

static int __init ehci_platform_init(void)
{if (usb_disabled())return -ENODEV;ehci_init_driver(&ehci_platform_hc_driver, &platform_overrides);return platform_driver_register(&ehci_mv_driver);
}
module_init(ehci_platform_init);static void __exit ehci_platform_cleanup(void)
{platform_driver_unregister(&ehci_mv_driver);
}
module_exit(ehci_platform_cleanup);MODULE_DESCRIPTION("Marvell EHCI driver");
MODULE_AUTHOR("Chao Xie <chao.xie@marvell.com>");
MODULE_AUTHOR("Neil Zhang <zhangwm@marvell.com>");
MODULE_ALIAS("mv-ehci");
MODULE_LICENSE("GPL");
MODULE_DEVICE_TABLE(of, ehci_mv_dt_ids);

从2012年linux kernel 3.xx开始增加了module_platform_driver这个宏,一直延续至今。从module_platform_driver的定义处可以发现,它是platform_driver_register的一个封装应用。

#define module_platform_driver(__platform_driver) \module_driver(__platform_driver, platform_driver_register, \platform_driver_unregister)

存在的意义和原理

正如前面介绍的module_init这个宏,在使用它的时候要定义两个函数以及生命两个宏。而使用了module_platform_driver这个宏之后,只需要一行代码就可以实现这些功能。将module_platform_driver这个宏展开之后,就是module_init这一部分代码内容。

#define module_platform_driver(__platform_driver) \module_driver(__platform_driver, platform_driver_register, \platform_driver_unregister)#define module_driver(__driver, __register, __unregister, ...) \
static int __init __driver##_init(void) \
{ \return __register(&(__driver) , ##__VA_ARGS__); \
} \
module_init(__driver##_init); \
static void __exit __driver##_exit(void) \
{ \__unregister(&(__driver) , ##__VA_ARGS__); \
} \
module_exit(__driver##_exit);

驱动的注册与卸载方法采用了platform.c中提供的通用API。

/*** __platform_driver_register - register a driver for platform-level devices* @drv: platform driver structure* @owner: owning module/driver*/
int __platform_driver_register(struct platform_driver *drv,struct module *owner)
{drv->driver.owner = owner;drv->driver.bus = &platform_bus_type;drv->driver.probe = platform_drv_probe;drv->driver.remove = platform_drv_remove;drv->driver.shutdown = platform_drv_shutdown;return driver_register(&drv->driver);
}
EXPORT_SYMBOL_GPL(__platform_driver_register);

module_init这个宏在include/linux/module.h中定义,在kernel初始化过程中调用do_initcall()或插入驱动ko文件时得到执行。每个驱动模块仅需实现一个module_init与module_exit即可。驱动代码在使用module_platform_driver注册驱动时,经过编译后的文件内容如下:

module_init宏最终是调用了__initcall(x),定义了程序链接时的初始化等级为1。

#define module_init(x) __initcall(x);
#define __initcall(fn) __define_initcall("1", fn)

关于initcall:

#ifdef CONFIG_HAVE_ARCH_PREL32_RELOCATIONS
#define ___define_initcall(fn, id, __sec)   \__ADDRESSABLE(fn)     \asm(".section \"" #__sec ".init\", \"a\" \n" \"__initcall_" #fn #id ":   \n" \".long " #fn " - .   \n" \".previous     \n");
#else
#define ___define_initcall(fn, id, __sec) \static initcall_t __initcall_##fn##id __used \__attribute__((__section__(#__sec ".init"))) = fn;
#endif

而通过module_init定义的驱动API编译后的符号表示都增加了initcall的前缀

最后,透过一张图看清module_platform_driver声明的驱动调用流程:

END


推荐阅读:

专辑|Linux文章汇总

专辑|程序人生

专辑|C语言

我的知识小密圈

关注公众号,后台回复「1024」获取学习资料网盘链接。

欢迎点赞,关注,转发,在看,您的每一次鼓励,我都将铭记于心~

嵌入式Linux

微信扫描二维码,关注我的公众号

Linux kernel 中模块化的平台驱动代码介绍相关推荐

  1. arm linux kernel 从入口到start_kernel 的代码分析

    Linux系统启动过程分析(主要是加载内核前的动作) 经过对Linux系统有了一定了解和熟悉后,想对其更深层次的东西做进一步探究.这当中就包括系统的启动流程.文件系统的组成结构.基于动态库和静态库的程 ...

  2. linux kernel中的栈的介绍

    目录 1.linux kernel中的中断irq的栈stack (1).arm32体系的irq的栈 (2).arm64体系的irq的栈 2.linux kernel中的栈stack (1).概念介绍: ...

  3. linux kernel中的进程栈

    1.linux中的user mode的进程栈 在thread_info.h中,设置进程栈的大小为16k #define THREAD_SIZE 16384 #define THREAD_START_S ...

  4. Linux kernel中常见的宏整理

    0x00 宏的基本知识 // object-like #define 宏名 替换列表 换行符 //function-like #define 宏名 ([标识符列表]) 替换列表 换行符 替换列表和标识 ...

  5. 内存访问顺序 - part2: 屏障及Linux kernel中屏障的使用

    文章目录 屏障是什么 Linux Kernel 中的屏障 Linux 屏障 API 一般的屏障 强制性屏障 SMP 条件屏障 隐式屏障 其他屏障 屏障的开销 未来的文章 本文翻译自 Memory ac ...

  6. i.MX 6ULL 驱动开发 二十九:向 Linux 内核中添加自己编写驱动

    一.概述 Linux 内核编译流程如下: 1.配置 Linux 内核. 2.编译 Linux 内核. 说明:进入 Linux 内核源码,使用 make help 参看相关配置. 二.make menu ...

  7. Linux Kernel中AEP的现状和发展

    阿里 石洋内核月谈Yesterday AEP简介 AEP是Intel推出的一种新型的非易失Optane Memory设备,又被称作Apache Pass,所以一般习惯称作AEP.在这之前也有类似的设备 ...

  8. [armv8-arch64]linux kernel 5.9的异常量表介绍(irq,fiq,sync,svc)

    在entry.S中,定义了异常向量表,从代码中我们可以知道以下信息: 该表的基地址在vectors处(在开机的时候,会将其写入到vbar_el1中) 这个表以".align 11" ...

  9. linux kernel中local_irq_disable()、local_irq_enable()代码解读

    在armv8-arch64架构下,控制cpu是否响应IRQ,FIQ,SERROR,DEBUG中断,是由PSTATUS(daif寄存器)控制的. 在armv8-arch32或armv7架构下,控制cpu ...

最新文章

  1. 用了3年CAT,这次我想选择SkyWalking,老板反手就是一个赞!
  2. 跟郎朗媳妇有得一拼的AI,只看弹琴动作,完美复现原声 | CVPR 2020
  3. 那些年我们一起玩DIY总结出的经验——网络篇
  4. pyspark 连接mysql
  5. css实现一级下拉菜单
  6. 应用squid全面加速web(全)
  7. ups容量计算和配置方法_山埔UPS电源后备时间计算方法
  8. android开发实现选择列表,Android使用RecyclerView实现列表数据选择操作
  9. postgresql安装hypopg
  10. 组件化开发Android应用及SDK
  11. 计算机驱动程序的安装过程,电脑常用的驱动程序的安装与管理
  12. HTML简单的网页代码编写
  13. bootstrap FileInput多文件上传插件使用详解(包括Java代码)
  14. 【Frobenius norm(弗罗贝尼乌斯-范数)(F-范数)】
  15. java 中vo、po、dto、bo、pojo、entity、mode如何区分
  16. ubuntu系统切换高性能模式
  17. 【算法与数据结构】——乘法逆元
  18. MP4文件中h264的 SPS、PPS获取
  19. 偏序集的Dilworth定理学习 (转载)
  20. 粒子群算法实战分享-附原版动画PPT(技术分享也可以文艺范?)

热门文章

  1. [js高手之路] 跟GhostWu一起封装一个字符串工具库-扩展字符串位置方法(4)
  2. 高通被欧盟指控垄断 或将面临高达25亿美元罚款
  3. C Primer Plus 第7章 C控制语句:分支和跳转 7.4 一个统计字数的程序
  4. 山寨“饿了么”应用中添加菜品数量按钮效果
  5. 服务器最小化安装后的优化脚本
  6. Server 2008 R2 AD RMS完整部署:AD部署篇
  7. 深度学习之 soft-NMS
  8. 脚本启动显示查询频繁被服务器防御_面对CC攻击,该如何进行防御
  9. Emmet的html语法
  10. np.random.seed(0)作用