Linux 字符驱动之platform框架
1. platform总线驱动框架
1.1 为什么用platform总线
Linux设备驱动框架模式是“驱动总线+设备驱动”,因此,一个设备驱动通常是挂接对应的设备总线上,如常用的总线USB、I2C、SPI、UART。对于嵌入式系统,存在很多外设并不存在实际的“总线”,只是依附在MPU内存空间中,如LED、按键、ADC等。当然,这一类驱动也可以不遵循“总线+驱动”的模式,弊端是不便于系统管理和增加代码繁琐。
因此,Linux内核从2.6版本开始,提供一种虚拟总线,即“platform”总线,使得不存在实际总线的外设驱动遵循标准驱动框架,可以使用标准接口,便于内核管理,同时外设驱动程序也提供标准的访问接口,提高驱动和资源的独立性,降低驱动耦合度,使得驱动具备较高的可移植性和安全性。platform总线抽象出总线、设备、驱动即paltform_bus、platform_device和platform_driver,paltform_bus Linux内核已经提供,而驱动工程师要做的工作是实现platform_device和platform_driver,大多数情况下下我们写驱动其实就是适配platform总线驱动。
1.2 platform总线优点
【1】platform 机制将设备本身的资源注册进内核,由内核统一管理,在驱动程序中用使用这些资源时,通过 platform device 提供的标准接口进行申请并使用;
【2】实现设备与驱动的分离,通过platform总线,设备与驱动是分开注册的,通过platform总线的probe来随时检测与设备匹配的驱动,可以实现设备“热插拔”功能;
【3】一个驱动可以供同类的多个设备使用。
1.3 platform匹配驱动方式
Linux内核抽象出“struct bus_type”结构体表示platfrom总线,“struct bus_type”位于“include/linux/device.h”中。
struct bus_type {
const char *name;
const char *dev_name;
struct device *dev_root;
const struct attribute_group **bus_groups;
const struct attribute_group **dev_groups;
const struct attribute_group **drv_groups;
int (*match)(struct device *dev, struct device_driver *drv);
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev);
int (*remove)(struct device *dev);
void (*shutdown)(struct device *dev);
int (*online)(struct device *dev);
int (*offline)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
int (*num_vf)(struct device *dev);
int (*dma_configure)(struct device *dev);
const struct dev_pm_ops *pm;
const struct iommu_ops *iommu_ops;
struct subsys_private *p;
struct lock_class_key lock_key;
bool need_parent_lock;
};
其中关键函数是“match”,match中文意思有“匹配”的含义,“match”形参分别是“struct device”(设备)和“struct driver”(驱动),platform总线利用match函数来匹配已经注册的设备驱动程序。因此,platform总线必须实现“match”函数实例。Linux系统已经实现了platform总线,源码位于“driver/base/platform.c”中。
/* Linux platform总线驱动 */
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.dma_configure = platform_dma_configure,
.pm = &platform_dev_pm_ops,
};
/* 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);
/* When driver_override is set, only bind to the matching driver */
if (pdev->driver_override)
return !strcmp(pdev->driver_override, drv->name);
/* 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_match”函数源码可知道,设备和驱动匹配有4中方式。
- 设备树方式,也是目前Linux主流方式,推荐使用该方式。
函数调用过程:of_driver_match_devicet()->of_match_device()->of_match_node()函数,of_match_node()把struct device_driver->of_match_table和struct device->device_node->of_node进行匹配,比较两者的name、type、compatible字段,compatible是字符串,从设备树文件(dts)获取。贴上以上源码。
static inline int of_driver_match_device(struct device *dev,
const struct device_driver *drv)
{
return of_match_device(drv->of_match_table, dev) != NULL;
}
const struct of_device_id *of_match_device(const struct of_device_id *matches,
const struct device *dev)
{
if ((!matches) || (!dev->of_node))
return NULL;
return of_match_node(matches, dev->of_node);
}
const struct of_device_id *of_match_node(const struct of_device_id *matches,
const struct device_node *node)
{
if (!matches)
return NULL;
while (matches->name[0] || matches->type[0] || matches->compatible[0]) {
int match = 1;
if (matches->name[0])
match &= node->name
&& !strcmp(matches->name, node->name);
if (matches->type[0])
match &= node->type
&& !strcmp(matches->type, node->type);
if (matches->compatible[0])
match &= of_device_is_compatible(node,
matches->compatible);
if (match)
return matches;
matches++;
}
return NULL;
}
- ACPI方式,这个方式对于嵌入式中比较少,ACPI是Intel(i386,x86_64,IA64)平台的标准固件规范,绝大部分OS需要从BIOS得到的信息都可以从ACPI得到,并且现在的趋势是未来的任何新的特性相关的信息都只能从ACPI得到。
- 设备注册表“id_table”方式,通过“platform_devie_id”源码可知,是比较“driver->id_table->name”和“device->name”进行匹配。
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;
}
- 设备“name”字段,比较“driver->name”和“driver->name”是否相同。该方式简单易懂,使用比较多。但是该方式只是简单比较“name”字段,一个驱动只能支持一个设备,而通过方式【3】可以实现一个驱动支持多个设备。
2. platform驱动实现
platform驱动的实现,主要包括两部分,platform device(设备)和platform driver(驱动)。platform device与platform虚拟总线相关,实现虚拟总线相关应用接口,将设备挂在platform总线上;platform driver则是具体驱动,与板级相关的设备操作。应用程序调用platform框架,最终会调用platform driver回调函数实例。实现platform框架下驱动程序总体流程为:定义platform deive->注册platform device->定义platform driver->注册platform driver。
2.1 platform device实现
第一步,device抽象设备示例化
platform device实现,主要是实现Linux抽象出来的“struct platform_device”结构体,“struct platform_device”于“include/linux/platform_device.h”中。
struct platform_device {
const char *name;
int id;
bool id_auto;
struct device dev;
u64 dma_mask;
u32 num_resources;
struct resource *resource;
const struct platform_device_id *id_entry;
char *driver_override; /* Driver name to force a match */
/* MFD cell pointer */
struct mfd_cell *mfd_cell;
/* arch specific additions */
struct pdev_archdata archdata;
};
- name,设备名称,必须与“platform driver”的name字段名称一致。
- id,用于同一设备类型不同设备;id编号从0开始,id为-1时表示由系统分配值。
- dev,实际设备。
- num_resources,资源数目。
- resource,资源,如硬件寄存器、DMA、中断号、内存等。
- id_entry,设备和驱动匹配字段,匹配方式【3】。
- archdata,保留给特殊驱动用。
“struct resource”是我们需要重点关注的,它是用来描述硬件资源相关信息,“struct resource”抽象结构体位于“include/linux/ioport.h”中。[Linux内核2.6以后版本支持设备树描述板级硬件信息,更加方便和清晰]
struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
struct resource *parent, *sibling, *child;
};
start & end,资源起始和结束信息,如内存地址、寄存器地址、中断地址等。
name,资源名称。
flags,资源具体类型,“ioport.h”已有相关详细资源类型的定义宏。
基于之前文章的“dev_mem”字符驱动,实现个dev_mem platform device,由于并未使用实际硬件资源,所以资源信息为空。
struct platform_device memory_dev =
{
.name = "dev_mem",
.id = -1,
};
第二步,device注册
通过“platform_device_register”函数将设备信息注册到Linux内核中。
int platform_device_register(struct platform_device *pdev)
static int __init memory_dev_init(void)
{
return platform_device_register(&memory_dev);
}
第三步,device卸载
通过“platform_device_unregister”函数将设备信息从Linux内核中注销。
void platform_device_unregister(struct platform_device *pdev)
static void __exit memory_dev_exit(void)
{
platform_device_unregister(&memory_dev);
}
2.2 platform driver实现
platform driver实现,主要是实现Linux抽象出来的“struct platform_driver”结构体,“struct platform_device”于“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;
};
probe,探测函数,platform总线适配到对应驱动时,调用该函数初始化驱动。
remove,驱动释放函数,驱动从platform总线移除,调用该函数释放驱动资源。
driver,实际驱动,Linux基础驱动框架,platform driver “继承”这个基本驱动框架,与ACIP匹配和设备树匹配方式相关。
id_table,驱动匹配表,匹配方式【3】使用。
“struct device_driver”为Linux抽象出来的基础驱动结构体,该结构体包含了Linux驱动的基本信息,所有驱动都遵循这个接口,“struct device_driver”位于“include/linux/device.h”。
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 */
enum probe_type probe_type;
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 attribute_group **dev_groups;
const struct dev_pm_ops *pm;
void (*coredump) (struct device *dev);
struct driver_private *p;
};
“of_match_table”为采用设备树匹配驱动的匹配表,“acpi_match_table”为采用ACIP匹配驱动的匹配表。主要关注设备树的方式,因为这个目前主要的使用方式。设备树匹配表为“struct of_device_id”类型的数组,匹配结构体中的name、type、compatiable,其中“compatible”字段用来和设备树文件(dts)描述的属性进行比较,“struct of_device_id”位于“linux/include/mod_devicetable.h”。
struct of_device_id {
char name[32];
char type[32];
char compatible[128];
const void *data;
};
【drv_mem platform driver 实现过程】
platform 设备驱动并非是全新的驱动框架,而是在原有的Linux基础驱动框架“继承”过来,进行分层和封装。因此,驱动(driver)实现,基本的文件描述符操作接口与原字符驱动、块驱动、网络驱动等的是不变的。
static const struct file_operations memory_fops =
{
.owner = THIS_MODULE,
.open = memory_drv_open,
.read = memory_drv_read,
.write = memory_drv_write,
.release = memory_drv_close,
.ioctl = memory_drv_ioctl,
.llseek = memory_drv_llseek,
};
在此基础上增加对于platform设备的接口。
第一步,探测函数
&esmp;探测函数(probe)主要实现工:
- 驱动资源申请,包括驱动设备号、内存、IO资源、。
- 注册驱动和对应的子系统对于上层暴露的接口。
- 创建设备文件。
- 特殊情况下将“open”函数初始化的任务放在该函数执行。
- 最后是注册irq。
static int memory_drv_probe(void)
{
int ret = -1;
dev_t devno = 0;
ret = alloc_chrdev_region(&devno, 0, 1, "dev_mem");
if (ret)
{
printk("alloc dev-no failed.\n");
return ret;
}
pmemory_dev = kmalloc(sizeof(struct memory_device), GFP_KERNEL);
if (NULL == pmemory_dev)
{
ret = -ENOMEM;
printk("kmalloc request memory falied.\n");
return ret;
}
memset(pmemory_dev, 0, sizeof(struct memory_device));
pmemory_dev->devno = devno;
cdev_init(&pmemory_dev->dev, &memory_fops);
pmemory_dev->dev.owner = THIS_MODULE;
pmemory_dev->dev.ops = &memory_fops;
ret = cdev_add(&pmemory_dev->dev, pmemory_dev->devno, 1);
if (ret)
{
unregister_chrdev_region(pmemory_dev->devno, 1);
kfree(pmemory_dev);
return ret;
}
pmemory_dev->devclass = class_create(THIS_MODULE, "mem_class");
if (IS_ERR(pmemory_dev->devclass))
{
printk("class_create failed.\n");
cdev_del(&pmemory_dev->dev);
ret = -EIO;
return ret;
}
device_create(pmemory_dev->devclass, NULL, pmemory_dev->devno, NULL, "dev_mem");
return 0;
}
第二步,移除函数
顾名思义,该函数的作用是在驱动卸载时,释放“probe”申请的内存、设备号释放,然后注销驱动。
static int memory_drv_remove(void)
{
device_destroy(pmemory_dev->devclass, pmemory_dev->devno);
class_destroy(pmemory_dev->devclass);
cdev_del(&pmemory_dev->dev);
unregister_chrdev_region(pmemory_dev->devno, 1);
kfree(pmemory_dev->mem_buf);
pmemory_dev->devclass = NULL;
pmemory_dev->mem_buf = NULL;
pmemory_dev->mem_size = 0;
kfree(pmemory_dev);
pmemory_dev = NULL;
}
第三步, driver 注册
通过“platform_device_register”函数注册驱动。
static int __init memory_drv_init(void)
{
return platform_driver_register(&memory_drv);
}
第四步,driver 卸载
通过“platform_device_unregister”函数注册驱动。
static void __exit memory_drv_exit(void)
{
platform_driver_unregister(&memory_drv);
}![在这里插入图片描述](https://img-blog.csdnimg.cn/20191114001600604.png)
3. 源码
3.1 测试
在Ubuntu下编译dev_mem.c和drv_mem.c,分别生成dev_mem.ko和drv_mem.ko,分别手动insmod到内核中。然后执行app测试文件。
3.2 源码
【1】https://github.com/Prry/linux-drivers/tree/master/devmem_platform
4. 参考
【1】https://blog.csdn.net/fengyuwuzu0519/article/details/74375086