Linux系统的驱动框架主要就是三个主要部分组成,驱动、总线、设备。现在常见的嵌入式SOC已经不是单纯的CPU的概念了,它们都会在片上集成很多外设电路,这些外设都挂接在SOC内部的总线上,不同与IIC、SPI和USB等这一类实际存在外部PCB走线总线,他是系统内的总线实际是CPU的内部走线,所以Linux为了统一驱动模型在系统在启动引导时初始化了一条虚拟总线作为一个抽象的总线称之为platform总线,实现在drivers/base/platform.c中。今天就来学习这一类驱动的框架结构。

总线的具体实现

struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,(属性)
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};

结合前面的分析,Linux下的设备都应该(但不是必须)有所属的bus_type(dev.bus)这个bus_type就抽象了他们共通的一些“属性”和“方法”。platform设备他包含一个普通的device的基础上由增加了一些平台设备需要的数据如下

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;/*记录和驱动的匹配表id_table中匹配的哪一个表项指针*/ /* MFD cell pointer */
struct mfd_cell *mfd_cell; /* arch specific additions */
struct pdev_archdata archdata; 这个参数一般都指向这个结构体实体本身地址
};

值得一提的是其中ID参数如果是-1则包含的设备名就是 platform_device .name的值,如果为-2则会自动分配platform设备ID具体是通过platform.c中的一个函数实现具体参考源码,否则其他参数则就按"%s.%d", pdev->name, pdev->id 格式格式化platform设备名。一般注册平台设备需要初始化的内容主要有name、 resource,有时还需要指定内涵dev的platform_data,这一部分数据常常被驱动使用,这也是Linux 驱动和设备分离的一部分体现。

platform_device

注册添加

这里只是简单罗列函数调用过程,这一部分实际上是Device注册的过程的一个封装,具体内部操作可以参考Linux设备注册。这一部分如果前面device的注册理解的比较透彻这一部分就很好理解了。

platform_device_register
  1、device_initialize(&pdev->dev);
  2、arch_setup_pdev_archdata(空函数)
  3、platform_driver_add
    1、pdev->dev.parent = &platform_bus;指定父设备为platform设备总线
    2、pdev->dev.bus = &platform_bus_type;
    3、设定设备名称三种情况(-1 -2(申请ID) other)
4、设备资源管理
5、调用device_add(pdev->dev)

添加过程

int platform_device_add(struct platform_device *pdev)
{
int i, ret; if (!pdev)
return -EINVAL; if (!pdev->dev.parent)
pdev->dev.parent = &platform_bus; pdev->dev.bus = &platform_bus_type; switch (pdev->id) {
default:
dev_set_name(&pdev->dev, "%s.%d", pdev->name, pdev->id);
break;
case PLATFORM_DEVID_NONE:
dev_set_name(&pdev->dev, "%s", pdev->name);
break;
case PLATFORM_DEVID_AUTO:
/*
* Automatically allocated device ID. We mark it as such so
* that we remember it must be freed, and we append a suffix
* to avoid namespace collision with explicit IDs.
*/
ret = ida_simple_get(&platform_devid_ida, 0, 0, GFP_KERNEL);
if (ret < 0)
goto err_out;
pdev->id = ret;
pdev->id_auto = true;
dev_set_name(&pdev->dev, "%s.%d.auto", pdev->name, pdev->id);
break;
} for (i = 0; i < pdev->num_resources; i++) {
struct resource *p, *r = &pdev->resource[i]; if (r->name == NULL)
r->name = dev_name(&pdev->dev); p = r->parent;
if (!p) {
if (resource_type(r) == IORESOURCE_MEM)
p = &iomem_resource;
else if (resource_type(r) == IORESOURCE_IO)
p = &ioport_resource;
} if (p && insert_resource(p, r)) {
dev_err(&pdev->dev, "failed to claim resource %d\n", i);
ret = -EBUSY;
goto failed;
}
} pr_debug("Registering platform device '%s'. Parent at %s\n",
dev_name(&pdev->dev), dev_name(pdev->dev.parent)); ret = device_add(&pdev->dev);
if (ret == 0)
return ret; failed:
if (pdev->id_auto) {
ida_simple_remove(&platform_devid_ida, pdev->id);
pdev->id = PLATFORM_DEVID_AUTO;
} while (--i >= 0) {
struct resource *r = &pdev->resource[i];
if (r->parent)
release_resource(r);
} err_out:
return ret;
}

处理过程是给设备指定父设备即依托的总系即platform_bus,指定bus这一步很关键涉及到后面的驱动匹配(因为设备添加过程会拿这个设备所属的总线总线上由注册的驱动list),然后就是根据ID的不同值以不同的策略初始化设备name字段。然后就是资源的保存添加,其中最关键的就是device_add的操作过程这一部分参考我的Linux设备章节,就可以知道设备添加的细节。

device卸载过程

这一部分内容也是上面的操作的一个逆向操作,其实核心的内容还是设备删除的操作同样可以参考上面给出的联接查看设备的注销过程,就能明白platform框架只是在原有的驱动和设备驱动模型上的更高一层的封装。所以这里还是简单的罗列一下调用过程。

platform_device_unregister
  1、platform_device_del
    1、释放platform_device id
    2、device_del(pdev->dev)
  2、platform_device_put
  3、put_device

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

从结构体可以看出平台设备驱动提供了一些操作接口和一个platform_device_id 类型的兼容性匹配表(后面分析)。其次是结构体中的操作接口函数其在内部的device_driver结构体内也是有一套类似的操作接口;其他的电源管理现在已经很少用平台设备驱动中的接口了而转而使用内涵的device_driver驱动中的dev_pm_ops结构体中的接口来实现。

driver注册添加

__platform_driver_register(drv, THIS_MODULE)
  1、drv->driver.bus = platform_bus_type;
  2、如果platform驱动中的xx_probe或xx_remove等为空则指定drv->driver.同名接口指针为platform驱动默认行为(仅支持acpi方式匹配)。
  3、driver_register

__platform_driver_register

int __platform_driver_register(struct platform_driver *drv,
struct module *owner)
{
drv->driver.owner = owner;
drv->driver.bus = &platform_bus_type;
if (drv->probe)
drv->driver.probe = platform_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实际上也是对通用驱动的注册流程的一个高层级的封装,具体的驱动注册过程还是需要参考前面的驱动注册过程

driver注册移除

移除过程同样很简单就是设备驱动的删除操作同上参考设备驱动的注销过程,这里也是仅仅简单的罗列API的调用层级和过程。

platform_device_unregister
  1、platform_driver_unregister
    1、driver_unregister

platform驱动和设备的匹配

无论上面的注册设备还是注册驱动,最后都是要调用总线类型的mach函数进行驱动和设备的匹配,这也是platform 驱动框架中比较重要核心的部分所以这里从源码分析一下。

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; /* 采用ACPI的方式匹配驱动和设备 */
if (acpi_driver_match_device(dev, drv))
return 1; /* 通过驱动和设备的mach表来匹配驱动和设备 */
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL; /* 最后就是按驱动和设备名称是否相同来判断当前驱动是否支持这个设备 */
return (strcmp(pdev->name, drv->name) == 0);
}

从这个函数我们可以知道platform的driver和device的匹配就是通过以上四种规则来进行匹配的,前两种方式暂时不深究学到再来看;除此之外这个函数还告诉我们一个内核机制
如果驱动指定了mach_id_table则驱动将放弃名称相同匹配机制这一点需要重点记住。具体这个mach函数是在何时调用的参考Linux device的分析。其中兼容ID的匹配表格式是

struct platform_device_id {
char name[PLATFORM_NAME_SIZE];
kernel_ulong_t driver_data;
};

具体的匹配规则也很简单就是使用ID表內的名称来和设备名比较具体看代码,比较简单,需要注意的是这里还将匹配的id 表的句柄保存在platform device的id_entry项上,id_table里常常带一个long型的driver_data数据保存驱动数据。

static const struct platform_device_id *platform_match_id(
const struct platform_device_id *id,
struct platform_device *pdev)
{
while (id->name[0]) {
if (strcmp(pdev->name, id->name) == 0) {
pdev->id_entry = id;
return id;
}
id++;
}
return NULL;
}

具体实例分析

下面通过自己实现一个platform device 来匹配内核的一个三星的led驱动,内核代码是3-16-57版本驱动在drivers\leds\leds-s3c24xx.c。

static struct platform_driver s3c24xx_led_driver = {
.probe = s3c24xx_led_probe,
.remove = s3c24xx_led_remove,
.driver = {
.name = "s3c24xx_led",
.owner = THIS_MODULE,
},
};

主要分析其s3c24xx_led_probe函数的执行过程就能明白对应的设备应该如何添加。通过驱动的声明我得出结论,这个驱动除了设备树和ACPI的方式匹配设备外就只能通过名称来匹配设备了,所以先定义设备如下然后慢慢填充。

static struct platform_device tiny210_device_led []= {
.name = "s3c24xx_led",
.id = 0,
};

然后在看s3c24xx_led_probe函数都是怎样处理的

static int s3c24xx_led_probe(struct platform_device *dev)
{
struct s3c24xx_led_platdata *pdata = dev_get_platdata(&dev->dev);
/* 首先获取 platform_data 这个我还没定义所以后面需要定义 */
struct s3c24xx_gpio_led *led;
int ret;
/* 申请驱动私有数据结构体 */
led = devm_kzalloc(&dev->dev, sizeof(struct s3c24xx_gpio_led),
GFP_KERNEL);
if (!led)
return -ENOMEM;
/* 将私有数据结构体绑定到device的driver_data成员上方便使用 */
platform_set_drvdata(dev, led);
/* 这里涉及LED class 子系统的内容 可以暂时当作黑盒 */
led->cdev.brightness_set = s3c24xx_led_set;
led->cdev.default_trigger = pdata->def_trigger;
led->cdev.name = pdata->name;
led->cdev.flags |= LED_CORE_SUSPENDRESUME;
/* 绑定platform_data 到私有数据结构 */
led->pdata = pdata; ret = devm_gpio_request(&dev->dev, pdata->gpio, "S3C24XX_LED");
if (ret < 0)
return ret; /* no point in having a pull-up if we are always driving */
/* GPIO 子系统内容 配置对应的GPIO */
s3c_gpio_setpull(pdata->gpio, S3C_GPIO_PULL_NONE);
/* 如果设备定义时指定了这个标志则会执行下面的设置将GPIO配置为输入方向 */
if (pdata->flags & S3C24XX_LEDF_TRISTATE)
/* GPIO 子系统内容 配置对应的GPIO方向为输入 一般底层由芯片厂商实现 */
gpio_direction_input(pdata->gpio);
else
/* 第二个参数是保证LED在默认状态下是不点亮的 */
gpio_direction_output(pdata->gpio,
pdata->flags & S3C24XX_LEDF_ACTLOW ? 1 : 0); /* register our new led device */
/* 这里涉及LED class 子系统的内容 可以暂时当作黑盒 */
ret = led_classdev_register(&dev->dev, &led->cdev);
if (ret < 0)
dev_err(&dev->dev, "led_classdev_register failed\n"); return ret;
}

到此LED驱动的匹配操作就完了,除了中间涉及Linux 的led class 和 gpio 子系统的内容外还是很简单的所以接下来完善我的LED设备
增加驱动所需的数据

struct s3c24xx_led_platdata led_data
{
.gpio = S5PV210_GPJ2(0),(gpio 子系统)
.flags = S3C24XX_LEDF_ACTLOW,(驱动的私有标志,指明LED开启时的IO电平)
.name = "led",
.def_trigger = "",
};
static struct platform_device tiny210_device_led []= {
.name = "s3c24xx_led",
.id = 0,
.dev ={
.platform_data = &led_data,
},
};

flags 的内容是后来补上的他的意思就是led在低电平时点亮,不要这个标志LED默认状态是开启的这和具体的硬件有关。

最后将设备以模块的形式加入。
然后在/sys/class/leds/ 下将看到一个led0文件他是一个符合链接指向/devices/platform/latform/s3c24xx_led.0/leds/led0
进入 会看到
brightness max_brightness subsystem uevent
device power trigger
这些就是ledclass的内容的,通过向brightness写数据就可以控制LED的开启和关闭了。也可以直接使用脚本

echo 0 > brightness led灯就亮了
echo 1 >brightness led灯就灭了。

综上Linux下的platform总线出现的意义就是统一linux下的驱动模型,即设备、驱动、总线其中总线负责设备和驱动的匹配。一般Linux下的复杂驱动都是基于platform总线的
通过驱动的probe函数进行调用其他子系统的接口实习更加复杂的驱动模型。而platform_driver和platform_device都是在Linux device和device_driver之上封装的所以需要在明白
Linux device和device_driver 的相关机制之上来理解就更加容易了。

附设备添加源码,不过源码后来我又添加了三个LED。

#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/fb.h>
#include <linux/gpio.h>
#include <linux/delay.h> #include <asm/mach/arch.h>
#include <asm/mach/map.h>
#include <asm/setup.h>
#include <asm/mach-types.h> #include <mach/map.h>
#include <plat/gpio-cfg.h>
#include <plat/devs.h>
#include <plat/cpu.h>
#include <linux/platform_data/leds-s3c24xx.h> static struct s3c24xx_led_platdata led_data[] = {
[0]={
.gpio = S5PV210_GPJ2(0),
.flags = S3C24XX_LEDF_ACTLOW,
.name = "led0",
.def_trigger = "",
},
[1]={
.gpio = S5PV210_GPJ2(1),
.name = "led1",
.flags = S3C24XX_LEDF_ACTLOW,
.def_trigger = "",
},
[2]={
.gpio = S5PV210_GPJ2(2),
.name = "led2",
.flags = S3C24XX_LEDF_ACTLOW,
.def_trigger = "",
},
[3]={
.gpio = S5PV210_GPJ2(3),
.name = "led3",
.flags = S3C24XX_LEDF_ACTLOW,
.def_trigger = "",
}, }; static struct platform_device tiny210_device_led []= {
[0]={
.name = "s3c24xx_led",
.id = 0,
.dev ={
.platform_data = &led_data[0],
.devt = MAJOR(22),
},
},
[1]={
.name = "s3c24xx_led",
.id = 1,
.dev ={
.platform_data = &led_data[1],
.devt = MAJOR(22),
},
},
[2]={
.name = "s3c24xx_led",
.id = 2,
.dev ={
.platform_data = &led_data[2],
.devt = MAJOR(22),
},
},
[3]={
.name = "s3c24xx_led",
.id = 3,
.dev ={
.platform_data = &led_data[3],
.devt = MAJOR(22),
},
} }; static int __init platform_led_init(void)
{
int i; for(i=0;i<ARRAY_SIZE(tiny210_device_led);i++){
if(platform_device_register(&tiny210_device_led[i])<0)
{
printk(KERN_ERR "tiny210_device_led %d Fail\n",i);
return -1;
}
}
printk(KERN_INFO "tiny210_device_led Succse\n");
return 0; } static void __exit platform_led_exit(void)
{
int i;
for(i=0;i<ARRAY_SIZE(tiny210_device_led);i++){
platform_device_unregister(&tiny210_device_led[i]);
}
} module_init(platform_led_init);
module_exit(platform_led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("smile@shanghai");

platform-leds

Linux 驱动框架---platform驱动框架的更多相关文章

  1. Linux驱动:LCD驱动框架分析

    一直想花时间来整理一下Linux内核LCD驱动,却一直都忙着做其他事情去了,这些天特意抽出时间来整理之前落下的笔记,故事就这样开始了.LCD驱动也是字符设备驱动的一种,框架上相对于字符设备驱动稍微复杂 ...

  2. (转)S5pv210 HDMI 接口在 Linux 3.0.8 驱动框架解析 (By liukun321 咕唧咕唧)

    作者:liukun321 咕唧咕唧 日期:2014.1.18 转载请标明作者.出处:http://blog.csdn.net/liukun321/article/details/18452663 本文 ...

  3. S5pv210 HDMI 接口在 Linux 3.0.8 驱动框架解析

    作者:liukun321 咕唧咕唧 日期:2014.1.18 转载请标明作者.出处:http://blog.csdn.net/liukun321/article/details/18452663 本文 ...

  4. linux设备驱动程序--串行通信驱动框架分析

    linux 串行通信接口驱动框架 在学习linux内核驱动时,不论是看linux相关的书籍,又或者是直接看linux的源码,总是能在linux中看到各种各样的框架,linux内核极其庞杂,linux各 ...

  5. 【Linux开发】V4L2驱动框架分析学习

    Author:CJOK Contact:cjok.liao#gmail.com SinaWeibo:@廖野cjok 1.概述 Video4Linux2是Linux内核中关于视频设备的内核驱动框架,为上 ...

  6. Linux 驱动框架---i2c驱动框架

    i2c驱动在Linux通过一个周的学习后发现i2c总线的驱动框架还是和Linux整体的驱动框架是相同的,思想并不特殊比较复杂的内容如i2c核心的内容都是内核驱动框架实现完成的,今天我们暂时只分析驱动开 ...

  7. Linux 驱动框架---input子系统框架

    前面从具体(Linux 驱动框架---input子系统)的工作过程学习了Linux的input子系统相关的架构知识,但是前面的学习比较实际缺少总结,所以今天就来总结一下输入子系统的架构分层,站到远处来 ...

  8. 【Linux高级驱动】input子系统框架【转】

    转自:http://www.cnblogs.com/lcw/p/3802617.html [1.input子系统框架(drivers\input)] 如何得出某个驱动所遵循的框架?    1) 通过网 ...

  9. Linux 驱动框架---net驱动框架

    这一篇主要是学习网络设备驱动框架性的东西具体的实例分析可以参考Linux 驱动框架---dm9000分析 .Linux 对于网络设备的驱动的定义分了四层分别是网络接口层对上是IP,ARP等网络协议,因 ...

随机推荐

  1. windows_myql 安装与卸载详细讲解,

    windows_myql 安装 注意: 安装前把 所有杀毒软件,安全卫士等关闭. 打开下载的mysql安装文件双击解压缩,运行"mysql-5.5.40-win64.msi". 注 ...

  2. vue3.0改变概况

    一.slot API在render实现原理上的变化 二.全局API使用规范变化 三.Teleport添加 四.composition API变化 五.v-model变化

  3. 转 8 jmeter之集合点

    8 jmeter之集合点   集合点:集合点用以同步虚拟用户,以便恰好在同一时刻执行任务.在测试计划中,可能会要求系统能够承受1000 人同时提交数据,在LoadRunner 中可以通过在提交数据操作 ...

  4. 为什么MySQL索引使用B+树

    为什么MySQL索引使用B+树 聚簇索引与非聚簇索引 不同的存储引擎,数据文件和索引文件位置是不同的,但是都是在磁盘上而不是内存上,根据索引文件.数据文件是否放在一起而有了分类: 聚簇索引:数据文件和 ...

  5. Failed to start ssh.service: Unit not found.

    Failed to start ssh.service: Unit not found. 报错内容: [Centos7@localhost ~]$ service ssh start Redirect ...

  6. Flutter--Flutter开发环境搭建

    一.前言 Flutter 是 Google推出并开源的移动应用开发框架,主打跨平台.高保真.高性能.开发者可以通过 Dart语言开发 App,一套代码同时运行在 iOS 和 Android平台. Fl ...

  7. 在nodejs中创建cluster

    目录 简介 cluster集群 cluster详解 cluster中的event cluster中的方法 cluster中的属性 cluster中的worker 总结 在nodejs中创建cluste ...

  8. Spring Cloud,Docker书籍资源、优秀博文等记录

    Spring Cloud,Docker书籍资源.优秀博文等记录 Spring Cloud,Docker书籍资源.优秀博文等记录 一.书籍 二.博文地址 三.思维导图Or图片 3.1一张图总结 Dock ...

  9. Linux lsblk和df命令区别

    lsblk 查看的是block device,也就是逻辑磁盘大小. df查看的是file system, 也就是文件系统层的磁盘大小

  10. 2019牛客暑期多校训练营(第六场)C Palindrome Mouse (回文树+DFS)

    题目传送门 题意 给一个字符串s,然后将s中所有本质不同回文子串放到一个集合S里面,问S中的两个元素\(a,b\)满足\(a\)是\(b\)的子串的个数. 分析 首先要会回文树(回文自动机,一种有限状 ...