Linux平台总线设备驱动模型

【版权申明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权) 

Linux平台总线设备驱动模型

1. 传统Linux驱动模型 与 平台总线设备驱动模型

   传统的Linux驱动模型将对于设备的一切操作都放在了一个文件当中, 这个文件既包含了硬件相关的设备信息(例如引脚信息), 也包含了对设备的操作函数, 这中模型直观, 容易. 但是站在软件设计的角度来看, 确实一种非常糟糕的方式, 它有一个很致命的缺点,就是设备信息和驱动代码冗余在一起,一旦硬件信息发生改变(例如硬件更改引脚, 或者想把代码复用到另一个平台中),就必须要修改驱动源码,非常的麻烦,为了解决这种驱动代码和设备信息耦合的问题,Linux推出了platform bus(平台总线).

   platform bus(平台总线), 即使用虚拟总线将设备信息和驱动程序进行分离,将设备信息进行更好的整理。平台总线会维护两条链表,分别管理设备和驱动,当一个设备被注册到总线上时,总线会根据其名字搜索对应的驱动,如果找到就将设备信息导入驱动程序并执行驱动;当一个驱动被注册到平台总线的时候,总线也会搜索设备。总之,平台总线负责将设备信息和驱动代码进行匹配,这样就可以做到驱动和设备信息的分离。
Linux平台总线设备驱动模型

2. 平台总线设备驱动模型 - 设备端

  上面提到了平台总线会维护两条链表,分别管理设备和驱动. 他们相互独立, 分别管理着设备信息和驱动代码, 我们统一将他们交由Linux平台总线进行管理, 当
他们两个同时存在时, 这个设备将会被驱动起来(怎么知道他们同时存在? 平台总线已经实现了这个功能, 我们只需要使用平台总线提供的机制/接口, 如它所以期待的方式写出符合它要求的代码即可!)

2.1 设备端 - 相关的数据结构
struct platform_device {
	const char	*name;	// 设备的名字,驱动方法和设备信息匹配的桥梁
	int		id;			// 表示这个platform_device对象表征了几个设备,当多个设备有共用资源时,里面填充相应的设备数量,如果只是一个,填-1
	bool		id_auto;
	struct device	dev;// 父类对象,我们通常关心里面的platform_data和release,前者是用来存储私有设备信息的,后者是供当这个设备的最后引用被删除时被内核回调,注意和rmmod没关系。
	u32		num_resources;	// 资源的数量,即resource数组中元素的个数,我们用ARRAY_SIZE()宏来确定数组的大小
	struct resource	*resource;	// 资源指针,如果是多个资源就是struct 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;
};
2.1 设备端 - 设备的注册和注销
// 注册:把指定设备添加到内核中平台总线的设备列表,等待匹配,匹配成功则回调驱动中probe;
int platform_device_register(struct platform_device *);

// 注销:把指定设备从设备列表中删除,如果驱动已匹配则回调驱动方法和设备信息中的release;
void platform_device_unregister(struct platform_device *);

// 在动态编译的情况下,我们往往在模块初始化函数中注册一个驱动方法对象,而在模块卸载函数中注销一个驱动方法对象,所以我们可以使用内核中如下的宏来提高代码复用
module_platform_driver(driver_name);

3. 平台总线设备驱动模型 - 驱动端

3.1 驱动端 - 相关的数据结构
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 dev_pm_ops *pm;

	struct driver_private *p;
};


struct platform_driver {
	int (*probe)(struct platform_device *);	// 探测函数,如果驱动匹配到了目标设备,总线会自动回调probe函数
	int (*remove)(struct platform_device *); // 释放函数,如果匹配到的设备从总线移除了,总线会自动回调remove函数
	void (*shutdown)(struct platform_device *);
	int (*suspend)(struct platform_device *, pm_message_t state);
	int (*resume)(struct platform_device *);
	struct device_driver driver;	// platform_driver的父类
	const struct platform_device_id *id_table;
	bool prevent_deferred_probe;
};
3.2 驱动端 - 驱动的注册和注销
// 注册
int platform_driver_register(struct platform_driver *drv);

// 注销
int platform_driver_unregister(struct platform_driver *drv);

4. 平台总线设备驱动模型 - 设备与驱动的匹配

   设备信息有三种表达方式,而一个驱动是可以匹配多个设备的,平台总线中的驱动要具有三种匹配信息的能力,基于这种需求,platform_driver中使用不同的成员来进行相应的匹配。
4.1 struct of_device_id *of_match_table

对于使用设备树编码的设备信息,我们使用其父类device_driver中的of_match_table来匹配

struct platform_driver
	struct device_driver driver;	// linux/platform_device.h
		const struct of_device_id	*of_match_table;	// linux/device.h

/*
 * Struct used for matching a device
 * linux/mod_devicetable.h
 */
struct of_device_id {
	char	name[32];			// 设备名
	char	type[32];			// 设备类型
	char	compatible[128];	// 与设备树compatible属性值匹配的字符串
	const void *data;			// data驱动私有数据
};

对于一个驱动匹配多个设备的情况,我们使用struct of_device_id table[]来表示。

4.2 struct platform_device_id *id_table
struct platform_driver
	const struct platform_device_id *id_table;	// linux/platform_device.h

struct platform_device_id {
	char name[PLATFORM_NAME_SIZE];	// 设备名
	kernel_ulong_t driver_data;		// 驱动数据
};

static struct platform_device_id table[] = {
    {"device0"},
    {"device1"},
    {},		// 内核判断数组已经结束的标志, 不能省略.
};
4.3 const char *name
struct platform_driver
	struct device_driver driver;	// linux/platform_device.h
		const char		*name;	// linux/device.h
4.4 platform_driver匹配的优先级
/**
 * drivers\base\platform.c
 * 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);

	/* 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);
}

可见, of_match_table > id_table > name.

5. probe() 与 remove()

probe即探测函数,如果驱动匹配到了目标设备(或者设备匹配到了目标驱动),总线会自动回调probe函数, 并把匹配到的设备信息封装成一个platform_device对象传入probe函数,我们可以很容易的得到关于这个设备的所有信息,但是更好的方法就是直接使用内核API中相关的函数.

probe() 函数中的主要工作:

  1. 申请资源
  2. 初始化
  3. 提供接口(cdev/sysfs/proc, 或者说是向内核注册字符设备驱动程序)

显然,remove主要完成与probe相反的操作,这两个接口都是我们必须实现的。

5.1 probe获取设备资源的方式
/**
 * drivers\base\platform.c
 * platform_get_resource - get a resource for a device
 * @dev: platform device, 平台总线设备
 * @type: resource type, 资源类型,include/linux/ioport.h中有定义
 * @num: resource index, 资源索引,即第几个此类型的资源,从0开始
 */
struct resource *platform_get_resource(struct platform_device *dev,
				       unsigned int type, unsigned int num)
{
	int i;

	for (i = 0; i < dev->num_resources; i++) {
		struct resource *r = &dev->resource[i];

		if (type == resource_type(r) && num-- == 0)
			return r;
	}
	return NULL;
}

注意,通过内核API(eg,上下这两个API)获取的resource如果是中断,那么只能是软中断号,而不是芯片手册/C语言设备信息/设备树设备信息中的硬中断号,但是此时获取的resource的flag是可以正确的反映该中断的触发方式的,只需要flag & IRQF_TRIGGER_MASK即可获取该中断的触发方式。

/**
 * platform_get_irq - get an IRQ for a device	// 获取一个设备的中断号
 * @dev: platform device	// 平台总线设备
 * @num: IRQ number index	// 中断号索引,即想要获取的第几个中断号,从0开始
 */
int platform_get_irq(struct platform_device *dev, unsigned int num)
{
#ifdef CONFIG_SPARC
	/* sparc does not have irqs represented as IORESOURCE_IRQ resources */
	if (!dev || num >= dev->archdata.num_irqs)
		return -ENXIO;
	return dev->archdata.irqs[num];
#else
	struct resource *r;
	if (IS_ENABLED(CONFIG_OF_IRQ) && dev->dev.of_node) {
		int ret;

		ret = of_irq_get(dev->dev.of_node, num);
		if (ret > 0 || ret == -EPROBE_DEFER)
			return ret;
	}

	r = platform_get_resource(dev, IORESOURCE_IRQ, num);
	/*
	 * The resources may pass trigger flags to the irqs that need
	 * to be set up. It so happens that the trigger flags for
	 * IORESOURCE_BITS correspond 1-to-1 to the IRQF_TRIGGER*
	 * settings.
	 */
	if (r && r->flags & IORESOURCE_BITS) {
		struct irq_data *irqd;

		irqd = irq_get_irq_data(r->start);
		if (!irqd)
			return -ENXIO;
		irqd_set_trigger_type(irqd, r->flags & IORESOURCE_BITS);
	}

	return r ? r->start : -ENXIO;
#endif
}
static inline void *dev_get_platdata(const struct device *dev)
{
	return dev->platform_data;
}

6. 一个实例

6.1 设备端
#include <linux/init.h>
#include <linux/module.h>

#include <linux/platform_device.h>
#include <asm/io.h>


static struct resource led_resource[] = {
    [0] = {
        .start = 0x120D6100,
        .end   = 0x120D6100 + 1 - 1,
        .flags = IORESOURCE_MEM,
    }
};

static void led_release(struct device * dev)
{
}

static struct platform_device led_dev = {
    .name         = "platform_led",
    .id       = -1,
    .num_resources    = ARRAY_SIZE(led_resource),
    .resource     = led_resource,
    .dev = { 
    	.release = led_release, 
	},
};



static int __init led_dev_init(void)
{
	platform_device_register(&led_dev);
	return 0;
}

static void __exit led_dev_exit(void)
{
	platform_device_unregister(&led_dev);
}

module_init(led_dev_init);
module_exit(led_dev_exit);

MODULE_AUTHOR("yangbkGit");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("platform device model.");
MODULE_ALIAS("model");
6.1 驱动端
#include <linux/init.h>
#include <linux/module.h>

#include <linux/platform_device.h>
#include <asm/io.h>
#include <asm/uaccess.h>

#include <linux/cdev.h>
#include <linux/device.h>

static dev_t dev_num = 0;
static struct cdev *cdevice = NULL;

static struct class *sys_class = NULL;
static struct device *class_device = NULL;

static volatile unsigned int *gpio_data;

int led_open(struct inode *pinode, struct file *pfile)
{
	printk("[%s]\n", __func__);
	return 0;
}

ssize_t led_write(struct file *pfile, const char __user *userbuf, size_t size, loff_t *loff)
{
	int ret = -1;
	int val = -1;
	printk("[%s]\n", __func__);

	//long copy_from_user(void *to, const void __user * from, unsigned long n)
	ret = copy_from_user(&val, userbuf, size);

	if(ret == 0 && val == 1){
		*gpio_data |= (1<<6);
	} else{
		*gpio_data &= ~(1<<6);
	}

	return size;
}

ssize_t led_read(struct file *pfile, char __user *userbuf, size_t size, loff_t *loff)
{
	printk("[%s]\n", __func__);
	// long copy_to_user(void __user *to, const void *from, unsigned long n)

	return 0;
}

static struct file_operations led_fops = {
	.owner = THIS_MODULE,
	.open  = led_open,
	.read  = led_read, 
	.write = led_write
};

int led_probe(struct platform_device *pdev)
{
	struct resource *res = NULL;

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	gpio_data = (int *)ioremap(res->start, res->end - res->start + 1);


	printk("led_probe, found led\n");
	cdevice = cdev_alloc();
	cdev_init(cdevice, &led_fops);

	alloc_chrdev_region(&dev_num, 0, 1, "yangbkLed");
	cdev_add(cdevice, dev_num, 1);

	sys_class = class_create(THIS_MODULE, "yangbkClass");
	class_device = device_create(sys_class, NULL, dev_num, NULL, "yangbkDevice");
	
	return 0;
}

int led_remove(struct platform_device *pdev)
{
	printk("led_remove, remove led\n");

	device_destroy(sys_class, dev_num);
	class_destroy(sys_class);

	unregister_chrdev_region(dev_num, 1);
	
	cdev_del(cdevice);

	iounmap(gpio_data);

	return 0;
}

struct platform_driver led_drv = {
	.probe		= led_probe,
	.remove		= led_remove,
	.driver		= {
		.name	= "platform_led",
	}
};


static int __init led_drv_init(void)
{
	platform_driver_register(&led_drv);
	return 0;
}

static void __exit led_drv_exit(void)
{
	platform_driver_unregister(&led_drv);
}

module_init(led_drv_init);
module_exit(led_drv_exit);

MODULE_AUTHOR("yangbkGit");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("platform driver model.");
MODULE_ALIAS("model");
6.3 应用端
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>


int main(int argc, char **argv)
{
	char ch;
	int fd = -1;

	fd = open("/dev/yangbkDevice", O_WRONLY);

	printf("press any keys(not 1/0/enter) to exit this sample.\n");
	while(1){
		ch = (char)getchar();
		
		if(ch == '\n')
			continue;
		if(ch == '1'){
			int data = 1;
			write(fd, &data, sizeof(data));
		} else if(ch == '0'){
			int data = 0;
			write(fd, &data, sizeof(data));
		} else{
			break;
		}
	}
	close(fd);
	
	return 0;
}
发布了68 篇原创文章 · 获赞 22 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/MACMACip/article/details/104840319