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

posted @ 2019-11-09 23:13  Acuity  阅读(425)  评论(0编辑  收藏  举报