platform虚拟平台总线驱动分析总结

本文详细解析了Linux平台设备与驱动模型的核心结构和工作原理,包括platform_device和platform_driver的重要成员,资源管理,注册与卸载流程,以及设备与驱动在总线上的匹配机制。同时介绍了私有数据的设置方法,以及如何使用id_table实现一个驱动支持多个设备。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、platform的两大重要结构体:

struct platform_device {
	const char	* name;
	int		id;
	struct device	dev;
	u32		num_resources;
	struct resource	* resource;
 
	struct platform_device_id	*id_entry;
 
	/* arch specific additions */
	struct pdev_archdata	archdata;
};	

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;
	struct platform_device_id *id_table;
};

    二、struct resource结构体是物理资源信息存储的结构体:

struct resource {
	resource_size_t start;
	resource_size_t end;
	const char *name;
	unsigned long flags;
	struct resource *parent, *sibling, *child;
};

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

平台设备device和平台驱动driver在总线上发生匹配是在设备或者驱动注册时,总线上已经有对应的匹配函数。

三、设备注册相关的函数(主要使用到):

extern int platform_device_register(struct platform_device *);
extern void platform_device_unregister(struct platform_device *);
/* include/linux/platform_device.h */
//Platform设备的注册/注销接口,和底层的device_register等接口类似。arch_setup_pdev_archdata,设置platform_device变量中的archdata指针。
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 *);

//通过这些接口,可以获取platform_device变量中的resource信息,以及直接获取IRQ的number等等。
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);

//其它形式的设备注册。调用者只需要提供一些必要的信息,如name、ID、resource等,Platform模块就会自动分配一个struct platform_device变量,填充内容后,注册到内核中。
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)

//以name和id为参数,动态分配一个struct platform_device变量。
extern struct platform_device *platform_device_alloc(const char *name, int id);
//向platform device中增加资源描述。
extern int platform_device_add_resources(struct platform_device *pdev,
                                         const struct resource *res,
                                         unsigned int num);
//向platform device中添加自定义的数据(保存在pdev->dev.platform_data指针中)。
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 driver的注册、注销接口。

extern int platform_driver_register(struct platform_driver *);
extern void platform_driver_unregister(struct platform_driver *);

//probe相关操作
//platform_driver_probe,主动执行probe动作。(就是说在驱动注册时,就有设备匹配到,忽略了匹配环节,失去了动态的特点,不适合用于支持热拔插的设备)
/* non-hotpluggable platform devices may use this so that probe() and
 * its support may live in __init sections, conserving runtime memory.
 */
extern int platform_driver_probe(struct platform_driver *driver,
		int (*probe)(struct platform_device *));
#define platform_get_drvdata(_dev)	dev_get_drvdata(&(_dev)->dev)
//platform_get_drvdata,设置或者获取driver保存在device变量中的私有数据。
#define platform_set_drvdata(_dev,data)	dev_set_drvdata(&(_dev)->dev, (data));

五、总线的内容:

struct bus_type platform_bus_type = {
	.name		= "platform",
	.dev_attrs	= platform_dev_attrs,
	.match		= platform_match,
	.uevent		= platform_uevent,
	.pm		= &platform_dev_pm_ops,
};

六、设备与驱动在总线上匹配的过程

——>设备和驱动注册函数调用总线的platform_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);
 
	/* match against the id table first */
	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里面调用有两个匹配方式:

1、根据驱动的id_table(struct platform_device_id *id_table;)里面的名字与设备名字匹配

static struct platform_device_id s3c24xx_driver_ids[] = {
	{
		.name		= "s3c2410-i2c",
		.driver_data	= TYPE_S3C2410,
	}, {
		.name		= "s3c2440-i2c",
		.driver_data	= TYPE_S3C2440,
	}, { },
};
struct platform_device s3c_device_i2c0 = {
	.name		  = "s3c2410-i2c",
#ifdef CONFIG_S3C_DEV_I2C1
	.id		  = 0,
#else
	.id		  = -1,
#endif
	.num_resources	  = ARRAY_SIZE(s3c_i2c_resource),
	.resource	  = s3c_i2c_resource,
};
static const struct platform_device_id *platform_match_id(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;
}

由匹配函数可以看出,设备里面的struct platform_device_id    *id_entry;实在匹配成功后,把驱动里面的(pdev->id_entry = id;)id传递给设备,platform_match_id传入的id是驱动的。

2、如果没有id_table,就直接拿设备的名字与驱动的名字进行比较return (strcmp(pdev->name, drv->name) == 0);

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);
 
	/* match against the id table first */
	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);
}

id_table的使用可以让一个驱动对应多个设备,在platform_match函数中用id_table进行匹配就会遍历id_table中所有的内容,因为id_table中name保存的是与设备名字匹配的信息,id_table可以是数值指针,指向一组匹配信息,只要设备的名字在id_table里面,就会匹配成功。一个驱动对应多个设备属于一对多关系。

在上面id_table情况下,匹配的过程是在总线设备链表上拿出一个设备调用platform_match对id_table遍历,不匹配则调用下一个设备,重复对id_table的遍历。

七、对于同种类型的设备,我们可以使用相同的名字+标号表示,这时候就用到了pletform_device 中的 id 。

 if (pdev->id != -1)      /* 如果不是-1 对name编号 */  
        dev_set_name(&pdev->dev, "%s.%d", pdev->name,  pdev->id);  
    else                             /* -1时直接是名字 */
        dev_set_name(&pdev->dev, pdev->name); 

pletform_device 中的id如果不是-1,就会对设备的名字进行修改,在后面加入id的标识,否则直接设置设备的名字。

pletform_device 中的 id 对设备的名字不影响,加入id是为了区分设备在sys文件下的文件名,其实这些设备的名字是一样的。

八、私有数据的设置:

platform_dirver驱动中涉及数据跨函数访问的操作:有时候在probe函数中的某些数据需要在remove中调用,

或者当extern void platform_driver_unregister(struct platform_driver *);函数要卸载多个驱动时,如果相关的数据结构使用全局变量的形式访问,导致卸载的结果是只卸载了最后一个注册的驱动。

extern void platform_driver_unregister(struct platform_driver *); 函数卸载的过程是将当前驱动在所有联系的设备中卸载,也就是要对整个设备链表进行遍历,判断哪个设备的驱动是这个驱动,将对应驱动的设备与驱动分离,最后调用驱动的remove函数,设备作为参数传递进入remove函数,在这里完成申请内存的释放和字符设备的删除操作。

但是,完成申请内存的释放和字符设备的删除操作需要对应的内存地址和设备结构体,如果是全局变量,只会是最后一个有效。

不过,我们可以看到总线上的设备是独立的,我们挂载设备的时候就已经完成了每个设备的挂载,虽然名字一样,但是他们还是独立的一个设备,id区分了它们。而且remove函数的参数就只是设备指针,在设备的结构体内有struct device结构体,这个结构体可以包含一个私有的数据结构driver_data,用于传递给其它需要的函数。将一个需要在其它地方使用的数据赋给这个driver_data,在需要的地方取出来,这两个操作的相关函数是:(变量不能是全局的,局部变量才可以,原因是全局变量在程序的整个生命周期,而且地址在这个周期期间唯一,而如果使用局部变量,在函数中使用kmalloc分配内存空间,就可以获得不同内存保存的数据。局部变量要是指针类型的,不能是变量,因为局部变量在函数结束后就会释放,用于其它作用,而分配的空间就不会如此,所以程序结束后,分配的内存空间就必须用kfree函数来释放,避免内存泄露 )

驱动中常用到platform_set_drvdata 和 platform_get_drvdata这两个函数,用于保存局部变量:
include/linux/platform_device.h中:
static inline void *platform_get_drvdata(const struct platform_device *pdev)
{
        return dev_get_drvdata(&pdev->dev);
}
static inline void platform_set_drvdata(struct platform_device *pdev, void *data)
{
        dev_set_drvdata(&pdev->dev, data);
}

九、卸载过程:

/**
 * platform_driver_unregister
 * @drv: platform driver structure
 */
void platform_driver_unregister(struct platform_driver *drv)
{
	driver_unregister(&drv->driver);
}
EXPORT_SYMBOL_GPL(platform_driver_unregister);

    |
    |
    |
   \ /
    |
/**
 * driver_unregister - remove driver from system.
 * @drv: driver.
 *
 * Again, we pass off most of the work to the bus-level call.
 */
void driver_unregister(struct device_driver *drv)
{
	if (!drv || !drv->p) {
		WARN(1, "Unexpected driver unregister!\n");
		return;
	}
	driver_remove_groups(drv, drv->groups);
	bus_remove_driver(drv);
}
EXPORT_SYMBOL_GPL(driver_unregister);

    |
    |
    |
   \ /
    |
/**
 * bus_remove_driver - delete driver from bus's knowledge.
 * @drv: driver.
 *
 * Detach the driver from the devices it controls, and remove
 * it from its bus's list of drivers. Finally, we drop the reference
 * to the bus we took in bus_add_driver().
 */
void bus_remove_driver(struct device_driver *drv)
{
	if (!drv->bus)
		return;

	remove_bind_files(drv);
	driver_remove_attrs(drv->bus, drv);
	driver_remove_file(drv, &driver_attr_uevent);
	klist_remove(&drv->p->knode_bus);
	pr_debug("bus: '%s': remove driver %s\n", drv->bus->name, drv->name);
	driver_detach(drv);
	module_remove_driver(drv);
	kobject_put(&drv->p->kobj);
	bus_put(drv->bus);
}
    |
    |
    |
   \ /
    |
/**
 * driver_detach - detach driver from all devices it controls.
 * @drv: driver.
 */
void driver_detach(struct device_driver *drv)
{
	struct device_private *dev_prv;
	struct device *dev;


//在for无限循环里面查找driver与device


	for (;;) {
		spin_lock(&drv->p->klist_devices.k_lock);
		if (list_empty(&drv->p->klist_devices.k_list)) {
			spin_unlock(&drv->p->klist_devices.k_lock);
			break;
		}
//在设备的链表中找出一个包含设备的结构体图
		dev_prv = list_entry(drv->p->klist_devices.k_list.prev,
				     struct device_private,
				     knode_driver.n_node);
//提取出一个设备
		dev = dev_prv->device;
		get_device(dev);
		spin_unlock(&drv->p->klist_devices.k_lock);

		if (dev->parent)	/* Needed for USB */
			down(&dev->parent->sem);
		down(&dev->sem);
//判断设备里面的驱动是否是这个驱动,是的话就执行__device_release_driver(dev);,实现分离。
//为什么设备会有驱动的结构体,这是因为在注册的时候,设备和驱动匹配的化,就会把驱动赋给
//设备结构体里面的对应驱动结构体的指针
		if (dev->driver == drv)
			__device_release_driver(dev);
		up(&dev->sem);
		if (dev->parent)
			up(&dev->parent->sem);
		put_device(dev);
	}
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值