Linux设备驱动开发 - 平台设备和驱动Platform device和driver

By: fulinux

E-mail: fulinux@sina.com

Blog: https://blog.csdn.net/fulinus

喜欢的盆友欢迎点赞和订阅!

你的喜欢就是我写作的动力!

目录

本篇注重理论内容,参考如下文档:

bash 复制代码
linux-5.4/Documentation/driver-api/driver-model/platform.rst

Platform device和driver

相关的数据结构和函数在如下头文件中:

bash 复制代码
linux/platform_device.h

platform bus(平台总线)的驱动模型接口:platform_device和platform_driver。这是一个伪总线,与实际的像PCI和USB总线相对而存在的,主要是方便了编程,可以用最少的代码量来完成总线上的设备连接驱动等,适用于那些用于集成许多片上系统处理器上的外设。

platform device

platform device通常作为系统中的独立的实体出现。这包括传统的基于端口的设备和到外围总线的主机桥,以及集成到片上系统平台的大多数控制器。它们的共同点是直接从CPU总线寻址。很少情况下,一个平台设备会通过其他类型的总线连接,但是它的寄存器仍然可以直接寻址。

平台设备有一个名字,用于驱动绑定,以及一个资源列表,如地址和IRQs:

c 复制代码
struct platform_device {
      const char      *name;
      u32             id;
      struct device   dev;
      u32             num_resources;
      struct resource *resource;
};

Platform drivers

平台驱动程序遵循标准驱动程序模型约定,发现、枚举在驱动程序外部处理,驱动程序提供probe()和remove()等方法,使用标准约定支持电源管理和关机通知。参考如下platform_driver结构体:

c 复制代码
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 (*suspend_late)(struct platform_device *, pm_message_t state);
        int (*resume_early)(struct platform_device *);
        int (*resume)(struct platform_device *);
        struct device_driver driver;
  };

注意,probe()通常应该验证指定的设备硬件是否确实存在,有时平台setup code不能确定,probing过程可以使用设备资源,包括时钟和设备平台数据。

平台驱动以如下通用的方式注册自己:

c 复制代码
int platform_driver_register(struct platform_driver *drv);

或者,在已知设备不可热插拔的常见情况下,probe()函数可以位于init函数内部,在初始化阶段就执行probe(),以减少驱动程序的运行时内存占用:

c 复制代码
int platform_driver_probe(struct platform_driver *drv, int (*probe)(struct platform_device *))

内核模块可以由多个platform driver程序组成。platform core(platform 框架的核心)提供注册和注销一系列驱动程序的方法:

c 复制代码
int __platform_register_drivers(struct platform_driver * const *drivers,
                                      unsigned int count, struct module *owner);
void platform_unregister_drivers(struct platform_driver * const *drivers,
                                         unsigned int count);

如果其中一个驱动程序未能注册,则在此之前注册的所有驱动程序将按相反顺序注销。

tips: 有一个方便的宏将THIS_MODULE作为所有者参数传递:

c 复制代码
#define platform_register_drivers(drivers, count)

该宏就是将我们通常的struct module *owner设置为THIS_MODULE的字段个默认设置了。

设备枚举

通常,特定的平台的setup code将注册平台设备:

c 复制代码
int platform_device_register(struct platform_device *pdev);
int platform_add_devices(struct platform_device **pdevs, int ndev);

一般规则是只注册那些实际存在的设备,但在某些情况下可能会注册额外的设备。例如一个内核可能被配置为与一个外部网络适配器一起工作,这个适配器可能不会被填充到所有的板上,或者类似地与一个集成控制器一起工作,一些板子可能不会连接到任何外设。

在某些情况下,引导固件将导出描述在给定板上填充的设备的表。如果没有这些表,系统设置代码设置正确设备的唯一方法通常是为特定的目标板构建内核。这种特定于板的内核在嵌入式和定制系统开发中很常见。

在许多情况下,与平台设备相关联的内存和IRQ资源不足以让设备的驱动程序工作。设置代码通常会提供额外的消息,并使用struct device结构体中的platform_data字段提供附加信息。

嵌入式系统经常需要为platform device提供一个或多个时钟,这些时钟通常保持关闭状态,直到它们被使用(以节省电力)时才会运行。系统设置阶段还将这些时钟与设备关联起来,以便调用clk_get(&pdev->dev, clock_name)时根据需要返回它们。

传统的驱动程序-设备探测

有些驱动程序没有完全转换为驱动程序模型,因为它们扮演了一个非驱动程序的角色:驱动程序注册了它的平台设备,而不是把它留给系统基础设施。这样的驱动程序不能热插拔或冷插拔,因为这些机制要求设备创建在与驱动程序不同的系统组件中。

这样做的唯一"好的"理由是处理较旧的系统设计,这些设计像原来的IBM 电脑一样,在硬件配置方面依赖于容易出错的"探测硬件"模型。较新的系统已经在很大程度上放弃了这种模型,转而支持总线级的动态配置(PCI、USB)或引导固件(例如x86上的PNPACPI)提供的设备表。关于什么可能是什么地方,有太多相互冲突的选项,甚至操作系统的有根据的猜测也常常是错误的,足以制造麻烦。

这种类型的驱动程序是不鼓励的。如果你正在更新这样的驱动程序,请尝试移动设备枚举到一个更合适的位置,在驱动程序之外。这通常是清理,因为这样的驱动程序往往已经有"正常"模式,比如使用由PNP或平台设备设置创建的设备节点。

尽管如此,还是有一些api支持这种遗留驱动程序。避免使用这些调用,除非有hotplug缺陷的驱动:

c 复制代码
struct platform_device *platform_device_alloc(const char *name, int id);

可以使用platform_device_alloc()来动态分配设备,然后使用resources和platform_device_register()来初始化设备。更好的解决方案通常是:

c 复制代码
struct platform_device *platform_device_register_simple(
                const char *name, int id,
                struct resource *res, unsigned int nres);

可以使用platform_device_register_simple() 作为一步调用来分配和注册设备。

设备命名和驱动程序绑定

platform_device.dev.bus_id是设备的规范名称。它由两个组件组成:

  • platform_device.name - 也用于驱动匹配;
  • platform_device.id - 设备实例号,否则"-1"表示只有一个;

它们是连接的,因此name/id "serial" /0表示bus_id "serial" /0,"serial/3"表示bus_id "serial.3"; 两者都将使用名为"serial"的platform_driver。而" my_rtc" /-1将是bus_id "my_rtc "(没有实例id),并使用名为" my_rtc "的平台驱动程序。

驱动绑定由驱动核心自动执行,在找到设备和驱动之间的匹配后调用Driver probe()。如果probe()成功,驱动程序和设备将像往常一样绑定。有三种不同的方法可以找到这样的匹配:

  • 每当一个设备被注册时,总线的驱动程序就会被检查是否匹配,platform_device应该在系统启动时尽早注册。
  • 当使用platform_driver_register()注册一个驱动程序时,将检查该总线上所有未绑定的设备是否匹配。驱动程序通常在启动时注册,或者通过模块加载。
  • 使用platform_driver_probe()注册驱动程序的工作方式与使用platform_driver_register()的工作方式相同,只是当另一个设备注册时,该驱动程序不会被探测。(这没问题,因为这个接口只适用于非热插拔设备)

早期的平台设备和驱动程序

早期的平台接口在系统引导期间向platform driver程序提供platform_data,代码构建在early_param()命令行解析之上,可以在很早就执行。

示例:"earlyprintk"类早期串行控制台打印信息分为6个步骤

体系结构代码使用early_platform_add_devices()函数注册平台设备数据。在早期串行控制台的情况下,这应该是串行端口的硬件配置。此时注册的设备稍后将与早期的平台驱动程序匹配。

解析内核命令行

体系结构代码调用parse_early_param()来解析内核命令行。这将执行所有匹配的early_param()回调函数。用户指定的早期平台设备将在此时注册。对于早期的串行控制台情况,用户可以在内核命令行中将端口指定为"earlyprintk=serial"。其中" earlyprintk "是类字符串," serial"是平台驱动程序的名称,0是平台设备id。如果id是-1,那么点和id可以省略。

安装属于某一类的早期平台驱动程序

架构代码可以使用early_platform_driver_register_all()函数强制注册属于某个类的所有早期平台驱动程序。用户在步骤2中指定的设备优先于这些设备。串口驱动程序示例省略了这一步,因为早期的串口驱动程序代码应该被禁用,除非用户在内核命令行上指定了端口。

早期平台驱动程序注册

使用early_platform_init()的编译的平台驱动程序会在步骤2或3中自动注册。串口驱动程序示例应该使用

c 复制代码
early_platform_init("earlyprintk", &platform_driver)。

架构代码调用early_platform_driver_probe()将与某个类相关联的注册的早期平台设备与注册的早期平台驱动相匹配。匹配的设备将被探测()。这个步骤可以在早期引导期间的任何时候执行。越快可能越好,适合串口的情况。

相关推荐
qichengzong_right4 分钟前
CNCF云原生生态版图-分类指南(一)- 观测和分析
linux·云原生
linux修理工18 分钟前
centos 7 升级内核到4.19
linux·运维·centos
jekc8681 小时前
Ubuntu安装grafana
linux·ubuntu·grafana
sz66cm1 小时前
Linux基础 -- epoll监听Netlink并实现
linux
枇杷鹭1 小时前
Linux shell 使用 trap 命令优雅处理程序中断: shell 中的回调、锁与事务、以及 debug 调试
linux·运维·服务器
星光璀璨山河无恙1 小时前
【Linux】系统信息和状态命令
大数据·linux
羑悻的小杀马特2 小时前
什么是缓冲区???
linux·缓冲区
不会吉他的肌肉男不是好的挨踢男2 小时前
记录解决 linux部署完web服务却只能127.0.0.1访问但不能公网ip访问
linux·前端·tcp/ip
嵌入式小能手2 小时前
shell编程入门之提取字符并设置rtc时间
linux·单片机·嵌入式硬件·arm
安年CJ2 小时前
头歌 计算机操作系统 Linux之线程同步二
java·linux·运维·服务器·开发语言·数据库·c++