platform总线是Linux2.6引入的虚拟总线,这类总线没有对应的硬件结构。与之相反,USB总线和PCI总线在内核中是有对应的bus(USB-bus和PCI-bus)的。为了统一管理CPU这些既不属于USB又不属于PCI总线的外设资源,采用了platform虚拟总线。和字符设备不同,在platform架构中,整个驱动分为了device和driver两部分,提高了系统的可移植性。

在学习platform架构时,我们可以借助一点面向对象的思想,注意关注一些重要的结构体,将属性和行为分开学习,再联系起来。

本文所有代码基于linux3.9.5

platform总线驱动架构概览

可以分为如下三层:

  1. 设备struct platform_device : 资源分配
  2. 驱动struct platform_driver :初始化
  3. 总线struct platform_bus :device和driver的匹配,管理

linux内核启动流程和platform总线的注册

kernel在进入C语言阶段,会进入start_kernel函数(init/main.c),进行一些内存管理,调度。该函数的最后会执行rest_init();

下面是rest_init(init/main.c)源码

static noinline void __init_refok rest_init(void)
{
int pid; rcu_scheduler_starting();
/*
* We need to spawn init first so that it obtains pid 1, however
* the init task will end up wanting to create kthreads, which, if
* we schedule it before we create kthreadd, will OOPS.
*/
kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
numa_default_policy();
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
rcu_read_lock();
kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
rcu_read_unlock();
complete(&kthreadd_done); /*
* The boot idle thread must execute schedule()
* at least once to get things moving:
*/
init_idle_bootup_task(current);
schedule_preempt_disabled();
/* Call into cpu_idle with preempt disabled */
cpu_idle();
}

我们可以看到,该函数做了三件事:

  • 首先创建了一个线程执行kernel_init函数,该函数读取根文件系统下的init程序。这个操作完成了从内核态到用户态的转变。init进程作为所以用户态进程的父进程,将永远存在,PID是1
  • kthreadd是一个守护进程,PID是2
  • idle是空闲进程,cpu空闲时启动

我们进入kernel_init函数,在进入其中的kernel_init_freeable函数,继续进入do_basic_setup函数,这里我们就可以看到对驱动的初始化函数driver_init();

driver_init函数中,倒数第三个执行的函数platform_bus_init就是我们想找的platform总线的注册函数,位于drivers/base/platform.c

int __init platform_bus_init(void)
{
int error; early_platform_cleanup(); error = device_register(&platform_bus);
if (error)
return error;
error = bus_register(&platform_bus_type);
if (error)
device_unregister(&platform_bus);
return error;
}

除了最后的platform_bus_init位于drivers/base,其余函数都位于init/main.c中。下面我们将注意力从内核启动转移到platform设备上。

platform架构总线

platform_bus是一种设备

struct device platform_bus = {
.init_name = "platform",
};

从上面的结构体我们可以看到,platform_bus是一个名字为“platform”的device。device结构体是内核中设备的基本结构体。其他是设备,例如USB,都和device有关。这些设备的结构体或包含device成员,或实现device的部分成员。C中没有面向对象的继承特性,所以通过这种方式,我们变相的实现了“继承”

platform_bus_type实现总线的管理

struct bus_type platform_bus_type = {
.name = "platform",
.dev_attrs = platform_dev_attrs,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops, //电源管理
};

我们关注下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);
}

我们可以看到,这个函数的作用是将platform的device和driver名字相比较,相同则返回True表示匹配。

总线注册

上文linux启动过程中已经分析,不赘述

int __init platform_bus_init(void)
{
int error; early_platform_cleanup(); error = device_register(&platform_bus);
if (error)
return error;
error = bus_register(&platform_bus_type);
if (error)
device_unregister(&platform_bus);
return error;
}

platform设备

platform_device结构体

这里我们要关注下platform_device结构体,位于include/linux/platform_device.h

struct platform_device {
const char * name;
int id;
bool id_auto;
struct device dev;
u32 num_resources;
struct resource * resource; const struct platform_device_id *id_entry; /* MFD cell pointer */
struct mfd_cell *mfd_cell; /* arch specific additions */
struct pdev_archdata archdata;
};

platform_device 封装了device。

  • name:设备的名称
  • dev:真正有用的设备,通过contain_of,能找到整个platform_device
  • num_resources, resource: 系统使用的资源。Linux系统资源包括IO,寄存器,DMA,Bus,Memory等。

设备的注册和卸载

int platform_device_register(struct platform_device *pdev)
{
device_initialize(&pdev->dev);
arch_setup_pdev_archdata(pdev);
return platform_device_add(pdev);
} void platform_device_unregister(struct platform_device *pdev)
{
platform_device_del(pdev);
platform_device_put(pdev);
}

platform driver

platform_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;
const struct platform_device_id *id_table;
};

由device_driver结构体封装而来

struct device_driver {
const char *name;
struct bus_type *bus; struct module *owner;
const char *mod_name; /* used for built-in modules */ bool suppress_bind_attrs; /* disables bind/unbind via sysfs */ const struct of_device_id *of_match_table;
const struct acpi_device_id *acpi_match_table; 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;
};
  • probe:将driver绑定到device上调用该函数
  • remove:系统卸载设备的时候,将driver和device解绑
  • shutdown:关机时使设备静默
  • suspend:使设备进入睡眠模式
  • resume: 唤醒设备

platform驱动架构初探的更多相关文章

  1. 云原生时代, Kubernetes 多集群架构初探

    为什么我们需要多集群? 近年来,多集群架构已经成为“老生常谈”.我们喜欢高可用,喜欢异地多可用区,而多集群架构天生就具备了这样的能力.另一方面我们也希望通过多集群混合云来降低成本,利用到不同集群各自的 ...

  2. C#进阶系列——DDD领域驱动设计初探(一):聚合

    前言:又有差不多半个月没写点什么了,感觉这样很对不起自己似的.今天看到一篇博文里面写道:越是忙人越有时间写博客.呵呵,似乎有点道理,博主为了证明自己也是忙人,这不就来学习下DDD这么一个听上去高大上的 ...

  3. C#进阶系列——DDD领域驱动设计初探(二):仓储Repository(上)

    前言:上篇介绍了DDD设计Demo里面的聚合划分以及实体和聚合根的设计,这章继续来说说DDD里面最具争议的话题之一的仓储Repository,为什么Repository会有这么大的争议,博主认为主要原 ...

  4. C#进阶系列——DDD领域驱动设计初探(三):仓储Repository(下)

    前言:上篇介绍了下仓储的代码架构示例以及简单分析了仓储了使用优势.本章还是继续来完善下仓储的设计.上章说了,仓储的最主要作用的分离领域层和具体的技术架构,使得领域层更加专注领域逻辑.那么涉及到具体的实 ...

  5. C#进阶系列——DDD领域驱动设计初探(四):WCF搭建

    前言:前面三篇分享了下DDD里面的两个主要特性:聚合和仓储.领域层的搭建基本完成,当然还涉及到领域事件和领域服务的部分,后面再项目搭建的过程中慢慢引入,博主的思路是先将整个架构走通,然后一步一步来添加 ...

  6. C#进阶系列——DDD领域驱动设计初探(六):领域服务

    前言:之前一直在搭建项目架构的代码,有点偏离我们的主题(DDD)了,这篇我们继续来聊聊DDD里面另一个比较重要的知识点:领域服务.关于领域服务的使用,书中也介绍得比较晦涩,在此就根据博主自己的理解谈谈 ...

  7. C#进阶系列——DDD领域驱动设计初探(七):Web层的搭建

    前言:好久没更新博客了,每天被该死的业务缠身,今天正好一个模块完成了,继续来完善我们的代码.之前的六篇完成了领域层.应用层.以及基础结构层的部分代码,这篇打算搭建下UI层的代码. DDD领域驱动设计初 ...

  8. DDD领域驱动设计初探

    DDD领域驱动设计初探1 前言:又有差不多半个月没写点什么了,感觉这样很对不起自己似的.今天看到一篇博文里面写道:越是忙人越有时间写博客.呵呵,似乎有点道理,博主为了证明自己也是忙人,这不就来学习下D ...

  9. Linux Platform驱动模型(二) _驱动方法

    在Linux设备树语法详解和Linux Platform驱动模型(一) _设备信息中我们讨论了设备信息的写法,本文主要讨论平台总线中另外一部分-驱动方法,将试图回答下面几个问题: 如何填充platfo ...

随机推荐

  1. UIAutomator2的API文档(三)

    1.UI对象识别器Selector 用法d(text='Clock', className='android.widget.TextView') 支持以下参数,详细信息可参考UiSelector Ja ...

  2. java方式实现堆排序

    一.堆排序和堆相关概念描述 堆排序是指利用堆这种数据结构所设计的一种排序算法.堆是一个近似完全二叉树的结构,并同时满足堆的性质:即子结点的值总是小于(或者大于)它的父节点,若子结点的值总是小于它的父节 ...

  3. 前端 vue-cli+Webpack 项目开发环境配置、创建一个vue-demo

    一.软件及命令: (1)下载node.js 最新的LTS 版本,下载 msi格式的(直接点击安装即可). (2)命令1:npm install cnpm -g 命令2:cnpm install web ...

  4. Rocket - devices - CLINT

    https://mp.weixin.qq.com/s/4LfZZDKCTQhiKIUjvbDKEg 简单介绍CLINT的实现. 1. 概述 CLINT即是Core Local Interrupter的 ...

  5. 【Mybatis】mybatis3入门

    mybatis简介 MyBatis 是一款优秀的持久层框架,它支持定制化 SQL.存储过程以及高级映射.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可 ...

  6. 面试题: SpringBoot 的自启动原理

    个人博客网:https://wushaopei.github.io/    (你想要这里多有) 引言 不论在工作中,亦或是求职面试,Spring Boot 已经成为我们必知必会的技能项.除了比较老旧的 ...

  7. 核心记账业务可用jdk7的PriorityBlockingQueue优先阻塞队列结合乐观锁实现

    -- 1.优先级阻塞队列 当前核心记账业务是悲观锁实现,但考虑到高并发和死锁的问题,可以用PriorityBlockingQueue优先阻塞队列结合乐观锁实现,对于并发时出现锁无法update时可以重 ...

  8. Linux内核进程调度overview(1)

    一.概述 决定何时.如何选择一个新进程运行的这组规则叫做:调度策略(scheduling policy). Linux的调度是基于分时技术(time sharing):多个进程以“时间多路复用”方式运 ...

  9. Java实现 LeetCode 794 有效的井字游戏 (暴力分析)

    794. 有效的井字游戏 用字符串数组作为井字游戏的游戏板 board.当且仅当在井字游戏过程中,玩家有可能将字符放置成游戏板所显示的状态时,才返回 true. 该游戏板是一个 3 x 3 数组,由字 ...

  10. Java实现 蓝桥杯 算法训练 最大最小公倍数

    算法训练 最大最小公倍数 时间限制:1.0s 内存限制:256.0MB 问题描述 已知一个正整数N,问从1~N中任选出三个数,他们的最小公倍数最大可以为多少. 输入格式 输入一个正整数N. 输出格式 ...