Linux 内核:设备驱动模型(5)平台设备驱动

背景

我们已经大概熟悉了Linux Device Driver Model;知道了流程大概是怎么样的,为了加深对LDDM框架的理解,我们继续来看platform device driver(平台设备驱动)吧。

系列:Linux 内核:设备驱动模型 学习总结

参考:

介绍

Linux系统的驱动框架主要就是三个主要部分组成,驱动、总线、设备。

随着电子行业的发展,控制器越来越强大,SOC(片上系统)出现了:在片内的CPU外围集成很多外设电路,这些外设都挂接在SOC内部的总线上。

不同于IIC、SPI和USB等这一类实际存在外部PCB走线总线,片内外设从Chip之外是看不到的。

为了统一驱动架构抽象,所以Linux从2.6版本开始引入了platform bus这个虚拟的总线模型。

组成

platform平台设备驱动是基于设备总线驱动模型的,机制本身并不复杂,由两部分组成: platform_device platfrom_driver

  • platform_device:基于device的封装
  • platform_device_driver:基于 device_driver 的封装

整体的架构是这样子的。

前面已经分析过设备总线驱动模型,关于device 与 device_driver 的注册过程以及它们在sysfs文件系统中的层次关系就不在分析,本文重点分析platform平台设备驱动与设备总线驱动模型相比较新增添的那些东西。

platform设备

platform设备在device的基础上,增加了一些平台设备需要的数据。

platform_device原型

// include/linux/platform_device.h
struct platform_device {
/* 分配id的方式,决定了name的值 */
const char *name;
int id;
bool id_auto; //真正的设备,通过 container_of ,就能找到整个platform_device ,访问其它成员,如后面要提到的 resource
struct device dev;
/*
num_resources、resource,该设备的资源描述,由struct resource(include/linux/ioport.h)结构抽象。
在Linux中, 系统资源包括I/O、Memory、Register、IRQ、DMA、Bus等多种类型。这些资源大多具有独占性,不允许多个设备同时使用,因此Linux内核提供了一些API,用于分配、管理这些资源。
当某个设备需要使用某些资源时,只需利用struct resource组织这些资源(如名称、类型、起始、结束地址等),并保存在该设备的resource指针中即可。然后在设备probe时,设备需求会调用资源管理接口,分配、使用这些资源。而内核的资源管理逻辑,可以判断这些资源是否已被使用、是否可被使用等等。 */
u32 num_resources;
struct resource *resource; /*记录和驱动的匹配表id_table中匹配的哪一个表项指针*/
const struct platform_device_id *id_entry; /* MFD cell pointer */
struct mfd_cell *mfd_cell; /* arch specific additions */
// 这个参数一般都指向这个结构体实体本身地址
struct pdev_archdata archdata;
};

一般注册平台设备需要初始化的内容主要有name、 resource;

有时还需要指定dev.platform_data,这一部分数据常常被驱动使用,这也是Linux 驱动和设备分离的一部分体现。

// include/linux/device.h
/**
* struct device - The basic device structure
...
* @platform_data: Platform data specific to the device.
* Example: For devices on custom boards, as typical of embedded
* and SOC based hardware, Linux often uses platform_data to point
* to board-specific structures describing devices and how they
* are wired. That can include what ports are available, chip
* variants, which GPIO pins act in what additional roles, and so
* on. This shrinks the "Board Support Packages" (BSPs) and
* minimizes board-specific #ifdefs in drivers.
...
*/
struct device {
// ... void *platform_data; /* Platform specific data, device
core doesn't touch it */
// ...
};

注册添加device

这里只是简单罗列函数调用过程,这一部分实际上是Device注册的过程的一个封装,具体内部操作可以参考Linux设备注册。

流程:

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

platform_device_register

// drivers/base/platform.c
/**
* platform_device_register - add a platform-level device
* @pdev: platform device we're adding
*/
int platform_device_register(struct platform_device *pdev)
{
// device 初始化(之前说过了,略)
device_initialize(&pdev->dev);
// 空函数
arch_setup_pdev_archdata(pdev);
return platform_device_add(pdev);
}
EXPORT_SYMBOL_GPL(platform_device_register); void __weak arch_setup_pdev_archdata(struct platform_device *pdev)
{
}

platform_device_add

/**
* platform_device_add - add a platform device to device hierarchy
* @pdev: platform device we're adding
*
* This is part 2 of platform_device_register(), though may be called
* separately _iff_ pdev was allocated by platform_device_alloc().
*/
int platform_device_add(struct platform_device *pdev)
{
int i, ret; // ... // 指定父设备 即 依托的总线 为 platform_bus
if (!pdev->dev.parent)
pdev->dev.parent = &platform_bus; // 指定bus 涉及到后面的驱动匹配
// (因为设备添加过程会拿这个设备所属的总线总线上由注册的驱动list)
pdev->dev.bus = &platform_bus_type; // 根据ID的不同值以不同的策略初始化设备name字段
switch (pdev->id) {
default: // 默认
dev_set_name(&pdev->dev, "%s.%d", pdev->name, pdev->id);
break;
case PLATFORM_DEVID_NONE: // platform_device.name 的 值
dev_set_name(&pdev->dev, "%s", pdev->name);
break;
case PLATFORM_DEVID_AUTO: // 自动分配platform设备ID
/*
* 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); 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)); // 最关键的就是device_add,把设备添加到总线上。
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];
unsigned long type = resource_type(r); if (type == IORESOURCE_MEM || type == IORESOURCE_IO)
release_resource(r);
} err_out:
return ret;
}
EXPORT_SYMBOL_GPL(platform_device_add);

device卸载过程

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

platform_device_unregister
platform_device_del
释放platform_device id
device_del(pdev->dev)
platform_device_put
put_device

platform驱动

platform_driver原型

同理样platform_driver 也是一个包含了device_driver 的结构。

//include/linux/platform_device.h
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)
drv->driver.bus = platform_bus_type;
设置probe、remove、shutdown。
driver_register
/**
* __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;
// 指定为 platform_bus_type,也是为了到时候的probe
drv->driver.bus = &platform_bus_type;
/*
如果platform驱动中的xx_probe或xx_remove等为空则指定drv->driver.
同名接口指针为platform驱动默认行为(仅支持acpi方式匹配)
*/
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);
}
EXPORT_SYMBOL_GPL(__platform_driver_register); // include/linux/platform_device.h
/*
* use a macro to avoid include chaining to get THIS_MODULE
*/
#define platform_driver_register(drv) \
__platform_driver_register(drv, THIS_MODULE)

通过上面的代码我们很清楚的看到platform_driver实际上也是对通用驱动的注册流程的一个高层级的封装,具体的驱动注册过程参考之前的文章就可以了

driver注册移除

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

platform_device_unregister
platform_driver_unregister
driver_unregister

platform总线

刚刚可能有人会好奇,为什么没有platform_bus,实际上platform_bus_type是一个bus_type的实例;而不是基于bus_type的封装。

因为在一般情况下,内核中已经初始化并挂载了一条platform总线在sysfs文件系统中。

platform_bus_type原型

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

结合前面的分析,Linux下的设备都应该(但不是必须)有所属的bus_type(dev.bus)这个bus_type就抽象了他们共通的一些“属性”和“方法”。

platform驱动和设备的匹配

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

再看driver_match_device

还记得吗?无论是driver_register还是device_register,都会调用到driver_match_device。例如driver_register的调用过程:

driver_register(drv) [core.c]
bus_add_driver(drv) [bus.c]
if (drv->bus->p->drivers_autoprobe)
driver_attach(dev)[dd.c]
bus_for_each_dev(dev->bus, NULL, drv,__driver_attach)
__driver_attach(dev, drv) [dd.c]
driver_match_device(drv, dev) [base.h]
// 执行 bus->match
drv-bus->match ? drv->bus->match(dev, drv) : 1
if false, return;
driver_probe_device(drv, dev) [dd.c]
really_probe(dev, drv) [dd.c]
dev-driver = drv;
if (dev-bus->probe)
dev->bus->probe(dev);
else if (drv->probe)
drv-aprobe(dev);
probe_failed:
dev->-driver = NULL;

结合刚刚介绍的platform_bus_type,我们知道,待会就会调用platform_match进行设备与驱动的匹配。

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

platform_match

/**
* platform_match - bind platform device to platform driver.
* @dev: device.
* @drv: driver.
*
* Platform device IDs are assumed to be encoded like this:
* "<name><instance>", where <name> is a short description of the type of
* device, like "pci" or "floppy", and <instance> is the enumerated
* instance of the device, like '0' or '42'. Driver IDs are simply
* "<name>". So, extract the <name> from the platform_device structure,
* and compare it against the name of the driver. Return whether they match
* or not.
*/
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 */
/* 采用ACPI的方式匹配驱动和设备 略*/
if (acpi_driver_match_device(dev, drv))
return 1; /* Then try to match against the id table */
/* 通过驱动和设备的match表来匹配驱动和设备 */
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的driver和device的匹配就是通过四种规则来进行匹配的,按优先级划分:

  • 通过设备树匹配
  • 通过ACPI匹配
  • 通过match表匹配
  • 通过两者的名称来匹配

前两种方式暂时不深究学到再来看,通过名字匹配也比较简单。我们看看通过match表匹配是如何匹配的。

通过match表匹配

其中兼容ID的匹配表格式是:

// include/linux/mod_devicetable.h
#define PLATFORM_NAME_SIZE 20
struct platform_device_id {
char name[PLATFORM_NAME_SIZE];
kernel_ulong_t driver_data;
};

具体的匹配规则也很简单,就是使用ID表內的名称来和设备名比较。

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;
}

需要注意的是,这里还将匹配的id 表的句柄保存在platform device的id_entry项上,id_table里常常带一个long型的driver_data数据保存驱动数据。

显然,platform_match_id 的作用就是遍历整个 Id_table 数组,寻找是否有与 platform_device->name 同名的,如果有,则返回这个 Platform_device_id ,使用Id_table 打破了原本设备总线驱动模型,一个 device 只能用与一个 device_driver 配对的局限性。

现在一个platform_device_driver 可以与多个platform_device配对。

具体实例分析

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

static const struct platform_device_id s3c24xx_led_id[] = {
{ "s3c24xx_led", 0 },
{},
}; static struct platform_driver s3c24xx_led_driver = {
.probe = s3c24xx_led_probe,
.remove = s3c24xx_led_remove,
.driver = {
.name = "s3c24xx_led",
.owner = THIS_MODULE,
},
.id_table = s3c24xx_led_id,
};

主要分析其s3c24xx_led_probe函数的执行过程就能明白对应的设备应该如何添加。

通过驱动的声明我得出结论,这个驱动除了设备树和ACPI的方式匹配设备外,使用到了platform_device_id来匹配设备,所以先定义设备如下然后慢慢填充。

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");

例子2:

设备

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/sched.h>
#include <linux/irq.h>
#include <asm/uaccess.h>
#include <linux/input.h>
#include <linux/platform_device.h>
// 设备资源
static struct resource led_resource[] = { //jz2440的参数,驱动未测试
[0] = {
.start = 0x56000010,
.end = 0x56000010 + 8 - 1,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = 5,
.end = 5,
.flags = IORESOURCE_IRQ,
},
}; static void led_release(struct device *dev){ } // 创建一个设备
static struct platform_device led_dev = {
.name = "myled", //设备名字 与 驱动相匹配
.id = -1,
.num_resources = ARRAY_SIZE(led_resource),
.resource = led_resource, .dev = {
.release = led_release,
//.devt = MKDEV(252, 1),
},
}; static int led_dev_init(void){ //向bus注册led_dev match drv链表进行配对
platform_device_register(&led_dev);
return 0;
} static void led_dev_exit(void){
platform_device_unregister(&led_dev);
} module_init(led_dev_init);
module_exit(led_dev_exit);
MODULE_LICENSE("GPL");

驱动

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/sched.h>
#include <linux/irq.h>
#include <asm/uaccess.h> #include <linux/platform_device.h>
#include <linux/io.h> static int major; static struct class *cls;
static struct device *dev; static volatile unsigned long *gpio_con;
static volatile unsigned long *gpio_dat;
static int pin; static int led_open(struct inode *inode, struct file *file){ *gpio_con &= ~(0x03 << (pin*2));
*gpio_con |= (0x01 << (pin*2));
return 0;
} static ssize_t led_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos){ int val;
copy_from_user(&val, buf, count); if(val == 1){ *gpio_dat &= ~(1<<pin);
}else{ *gpio_dat &= (1<<pin);
} return 0;
} static struct file_operations led_fops = { .owner = THIS_MODULE,
.open = led_open,
.write = led_write,
}; static int led_probe(struct platform_device *pdev){ struct resource *res;
// 最后一个参数 0 表示第1个该类型的资源
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
gpio_con = ioremap(res->start, res->end - res->start + 1);
gpio_dat = gpio_con + 1; res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
pin = res->start; printk("led_probe, found led\n"); // 注册设备驱动 创建设备节点
major = register_chrdev(0, "myled", &led_fops);
// 创建类
cls = class_create(THIS_MODULE, "myled");
// 创建设备节点
dev = device_create(cls, NULL, MKDEV(major, 0), NULL, "led"); return 0;
} static int led_remove(struct platform_device *pdev){ printk("led_remove, remove led\n");
// 删除设备节点
device_unregister(dev);
// 销毁类
class_destroy(cls);
// 取消注册设备驱动
unregister_chrdev(major, "myled");
// 取消内存映射
iounmap(gpio_con); return 0;
} struct platform_driver led_drv = { .probe = led_probe, //匹配到dev之后调用probe
.remove = led_remove,
.driver = {
.name = "myled",
},
}; static int led_drv_init(void){ platform_driver_register(&led_drv);
return 0;
} static void led_drv_exit(void){ platform_driver_unregister(&led_drv);
} module_init(led_drv_init);
module_exit(led_drv_exit);
MODULE_LICENSE("GPL");

附录:API接口

Platform Device提供的API

/* include/linux/platform_device.h */
extern int platform_device_register(struct platform_device *);
extern void platform_device_unregister(struct platform_device *); extern void arch_setup_pdev_archdata(struct platform_device *);
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, const char *);
extern int platform_get_irq_byname(struct platform_device *, const char *);
extern int platform_add_devices(struct platform_device **, int); extern struct platform_device *platform_device_register_full(const struct platform_device_info *pdevinfo); static inline struct platform_device *platform_device_register_resndata(
struct device *parent, const char *name, int id,
const struct resource *res, unsigned int num,
const void *data, size_t size) static inline struct platform_device *platform_device_register_simple(
const char *name, int id,
const struct resource *res, unsigned int num) static inline struct platform_device *platform_device_register_data(
struct device *parent, const char *name, int id,
const void *data, size_t size) extern struct platform_device *platform_device_alloc(const char *name, int id);
extern int platform_device_add_resources(struct platform_device *pdev,
const struct resource *res,
unsigned int num);
extern int platform_device_add_data(struct platform_device *pdev,
const void *data, size_t size);
extern int platform_device_add(struct platform_device *pdev);
extern void platform_device_del(struct platform_device *pdev);
extern void platform_device_put(struct platform_device *pdev); platform_device_register、platform_device_unregister,Platform设备的注册/注销接口,和底层的device_register等接口类似。
arch_setup_pdev_archdata,设置platform_device变量中的archdata指针。
platform_get_resource、platform_get_irq、platform_get_resource_byname、platform_get_irq_byname,通过这些接口,可以获取platform_device变量中的resource信息,以及直接获取IRQ的number等等。
platform_device_register_full、platform_device_register_resndata、platform_device_register_simple、platform_device_register_data,其它形式的设备注册。调用者只需要提供一些必要的信息,如name、ID、resource等,Platform模块就会自动分配一个struct platform_device变量,填充内容后,注册到内核中。
platform_device_alloc,以name和id为参数,动态分配一个struct platform_device变量。
platform_device_add_resources,向platform device中增加资源描述。
platform_device_add_data,向platform device中添加自定义的数据(保存在pdev->dev.platform_data指针中)。
platform_device_add、platform_device_del、platform_device_put,其它操作接口。

Platform Driver提供的API

platform_driver_registe、platform_driver_unregister,platform driver的注册、注销接口。
platform_driver_probe,主动执行probe动作。
platform_set_drvdata、platform_get_drvdata,设置或者获取driver保存在device变量中的私有数据。

Linux 内核:设备驱动模型(5)平台设备驱动的更多相关文章

  1. 【Linux高级驱动】linux设备驱动模型之平台设备驱动机制

    [1:引言: linux字符设备驱动的基本编程流程] 1.实现模块加载函数  a.申请主设备号    register_chrdev(major,name,file_operations);  b.创 ...

  2. Linux中总线设备驱动模型及平台设备驱动实例

    本文将简要地介绍Linux总线设备驱动模型及其实现方式,并不会过多地涉及其在内核中的具体实现,最后,本文将会以平台总线为例介绍设备和驱动程序的实现过程. 目录: 一.总线设备驱动模型总体介绍及其实现方 ...

  3. 驱动开发学习笔记. 0.05 linux 2.6 platform device register 平台设备注册 2/2 共2篇

    驱动开发读书笔记. 0.05 linux 2.6 platform device register 平台设备注册 2/2 共2篇 下面这段摘自 linux源码里面的文档 : 内核版本2.6.22Doc ...

  4. 驱动开发学习笔记. 0.04 linux 2.6 platform device register 平台设备注册 1/2 共2篇

    驱动开发读书笔记. 0.04  linux 2.6 platform device register 平台设备注册  1/2 共2篇下面这段摘自 linux源码里面的文档 : Documentatio ...

  5. Linux内核分析(五)----字符设备驱动实现

    原文:Linux内核分析(五)----字符设备驱动实现 Linux内核分析(五) 昨天我们对linux内核的子系统进行简单的认识,今天我们正式进入驱动的开发,我们今后的学习为了避免大家没有硬件的缺陷, ...

  6. Linux内核分析(六)----字符设备控制方法实现|揭秘系统调用本质

    原文:Linux内核分析(六)----字符设备控制方法实现|揭秘系统调用本质 Linux内核分析(六) 昨天我们对字符设备进行了初步的了解,并且实现了简单的字符设备驱动,今天我们继续对字符设备的某些方 ...

  7. Linux驱动之平台设备驱动模型简析(驱动分离分层概念的建立)

    Linux设备模型的目的:为内核建立一个统一的设备模型,从而有一个对系统结构的一般性抽象描述.换句话说,Linux设备模型提取了设备操作的共同属性,进行抽象,并将这部分共同的属性在内核中实现,而为需要 ...

  8. Linux Platform驱动模型(一) _设备信息

    我在Linux字符设备驱动框架一文中简单介绍了Linux字符设备编程模型,在那个模型中,只要应用程序open()了相应的设备文件,就可以使用ioctl通过驱动程序来控制我们的硬件,这种模型直观,但是从 ...

  9. 【总结】设备树对platform平台设备驱动带来的变化(史上最强分析)【转】

    本文转载自:http://blog.csdn.net/fengyuwuzu0519/article/details/74375086 版权声明:本文为博主原创文章,转载请注明http://blog.c ...

  10. 深入理解Linux内核-I/O体系结构和设备驱动程序

    系统总线:1.链接CPU.RAM.I/O设备之间的数据流动.例如:PCI.ISA.EISA.MCA.SCSI.USB2.任何I\O设备有且仅能链接一条总线. I\O端口:1.每个连接到I\O总线上的设 ...

随机推荐

  1. docker镜像仓库搭建-Harbor

    一.Harbor简介 Harbor是一个用于存储和分发Docker镜像的企业级Registry服务器. 作为一个企业级私有 Registry 服务器,Harbor 提供了更好的性能和安全.提升用户使用 ...

  2. 第8讲 browse命令的使用技巧

    第8讲 browse命令的使用技巧 1.浏览所有parts,使用技巧 选中工程文件*.dsn/Edit/Browse/Parts.列出工程中用到的所有元件,方便在画完原理图后,查看哪些元件没有编号或数 ...

  3. Dijkstra迪杰斯特拉求最短路和最短路的条数和各个点权值的最大值

    作为一个城市的紧急救援队队长,你会得到一张你所在国家的特殊地图. 该地图显示了由一些道路连接的几个分散的城市. 地图上标出了每个城市的救援队伍数量以及任意两个城市之间每条道路的长度. 当其他城市接到紧 ...

  4. 【HarmonyOS】安装DevEco Studio后检查环境出现ohpm not set up

    按照官网的操作方式,下载完ohpm总是检测不到 打开cmd,发现我之前因为其他项目改过编码 输入 regedit 打开注册表编辑页 按路径[计算机\HKEY_LOCAL_MACHINE\SOFTWAR ...

  5. gorm 如何对字段进行comment注释?

    type User struct { Model Name string `gorm:"type:char(30);comment:'姓名'"` }

  6. SpringBoot-mybatis-plus 分页

    前言: 想必数据分页对于每一个程序员并不陌生,针对分页查询功能代码实现上:肯定是代码简洁明了且能达到分页的效果会更好! 现在我将基于SpringBoot - mybatisPlus分页查询的方法总结如 ...

  7. C 语言编程 — 高级数据类型 — 结构体与位域

    目录 文章目录 目录 前文列表 结构体 定义结构体 初始化结构体变量 访问结构体成员 将结构体作为实参传入函数 指向结构体变量的指针 位域 定义位域 使用位域结构体的成员 前文列表 <程序编译流 ...

  8. 网络性能评估工具Iperf详解

    一.网络性能评估工具Iperf 网络性能评估主要是监测网络带宽的使用率,将网络带宽利用最大化是保证网络性能的基础,但是由于网络设计不合理.网络存在安全漏洞等原因,都会导致网络带宽利用率不高.要找到网络 ...

  9. k8s&dapr开发部署实验(1)服务调用

    前置条件 安装docker与dapr: 手把手教你学Dapr - 3. 使用Dapr运行第一个.Net程序 安装k8s dapr 自托管模式运行 新建一个webapi无权限项目 launchSetti ...

  10. golang kmp算法实现

    // 不多逼逼直接上代码.原理的话可以参考下面的链接.讲的非常清晰package main import "fmt" func genNext(s string) []int { ...