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. Mybatis Plus 3.4版本之后分页插件的变化

    一.MybatisPlusInterceptor 从Mybatis Plus 3.4.0版本开始,不再使用旧版本的PaginationInterceptor ,而是使用MybatisPlusInter ...

  2. EasyExcel基本使用

    EasyExcel基本使用 一.应用场景 1.数据导入:减轻录入工作量 2.数据导出:统计信息归档 3.数据传输:异构系统之间数据传输 二.简介 Java领域解析.生成Excel比较有名的框架有Apa ...

  3. 令牌桶、漏斗、冷启动限流在sentinel的应用

    分布式系统为了保证系统稳定性,在服务治理的限流中会根据不同场景进行限流操作,常见的限流算法有: 令牌桶:可容忍一定突发流量的速率的限流,令牌桶算法的原理是系统以恒定的速率产生令牌,然后把令牌放到令牌桶 ...

  4. LOJ10144宠物收养所

    HNOI 2004 最近,阿 Q 开了一间宠物收养所.收养所提供两种服务:收养被主人遗弃的宠物和让新的主人领养这些宠物. 每个领养者都希望领养到自己满意的宠物,阿 Q 根据领养者的要求通过他自己发明的 ...

  5. Vue技术点整理-Vuex

    什么是Vuex? 1,Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式.它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化 2,每一个 Vuex ...

  6. Language Guide (proto3) | proto3 语言指南(五)使用其他消息类型

    Using Other Message Types - 使用其他消息类型 可以将其他消息类型用作字段类型.例如,假设您希望在每个SearchResponse消息中包含Result消息--为此,您可以在 ...

  7. 深度学习论文翻译解析(十八):MobileNetV2: Inverted Residuals and Linear Bottlenecks

    论文标题:MobileNetV2: Inverted Residuals and Linear Bottlenecks 论文作者:Mark Sandler Andrew Howard Menglong ...

  8. Git轻松入门1:本地仓库篇

    什么是Git 版本控制系统 首先我们要明白,什么是版本控制系统(version control systems)? 版本控制系统,顾名思义,就是能控制文件处于哪个版本的系统. e.g. 你在博客园里编 ...

  9. (三)集成 JavaMail ,实现异步发送邮件

    集成 JavaMail ,实现异步发送邮件 1.JavaMail的核心API 1.1 API功能图解 1.2 API说明 1.2.1 Message 类: 1.2.2 Transport 类 1.2. ...

  10. stop脚本

    PID=$(ps -ef | grep eladmin-system-2.0.jar | grep -v grep | awk '{ print $2 }')if [ -z "$PID&qu ...