Linux内核 platform总线驱动

platform总线驱动

在linux2.6以后的设备驱动模型中,需要关心总线、设备和驱动三个实体,总线将设备和驱动绑定。在系统中每注册一个设备时,总线会寻找与之匹配的驱动;相反的,在系统每注册一个驱动的时候,总线会寻找与之匹配的设备,而匹配绑定工作由总线完成。

platform总线是linux系统中的一种虚拟总线,相应的设备称为platform_device对应的驱动称为platfor_driver

platform_device

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#include <linux/platform_device.h>
struct platform_device {
	const char	* name;
	int		id;
	struct device	dev;  // 在linux设备驱动模型文章中有所介绍,是linux中所有设备对象的抽象
	u32		num_resources; // 设备对应的资源数量
	struct resource	* resource; // 设备资源数组

	const struct platform_device_id	*id_entry; //用于和platform_driver匹配

	/* MFD cell pointer */
	struct mfd_cell *mfd_cell;

	/* arch specific additions */
	struct pdev_archdata	archdata;
};

int platform_device_register(struct platform_device *); //此函数调用platform_device_add(pdev)完成向platform总线注册设备
void platform_device_unregister(struct platform_device *); //此函数调用platform_device_del(pdev)完成从platform总线中删除设备
int platform_add_devices(struct platform_device **, int); //循环调用platform_device_register函数,批量注册platform_device


//函数定义在 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_initialize(&pdev->dev);
	return platform_device_add(pdev);
}
EXPORT_SYMBOL_GPL(platform_device_register);


/**
 * platform_device_unregister - unregister a platform-level device
 * @pdev: platform device we're unregistering
 *
 * Unregistration is done in 2 steps. First we release all resources
 * and remove it from the subsystem, then we drop reference count by
 * calling platform_device_put().
 */
void platform_device_unregister(struct platform_device *pdev)
{
	platform_device_del(pdev);
	platform_device_put(pdev);
}
EXPORT_SYMBOL_GPL(platform_device_unregister);

/**
 * platform_add_devices - add a numbers of platform devices
 * @devs: array of platform devices to add
 * @num: number of platform devices in array
 */
int platform_add_devices(struct platform_device **devs, int num)
{
	int i, ret = 0;

	for (i = 0; i < num; i++) {
		ret = platform_device_register(devs[i]);
		if (ret) {
			while (--i >= 0)
				platform_device_unregister(devs[i]);
			break;
		}
	}

	return ret;
}
EXPORT_SYMBOL_GPL(platform_add_devices);

在板文件中,platform_device通常被组织成一个数组,通过在板级初始化时调用platform_add_devices()函数统一注册,例如mach-smdkv210.csmdkv210_machine_init()函数调用platform_add_devices(smdkv210_devices, ARRAY_SIZE(smdkv210_devices));就是采用这种统一注册多个设备的方法。

platform_driver

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#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; // 在linux设备驱动模型文章中有所介绍,是linux中所有驱动对象的抽象
	const struct platform_device_id *id_table;
};

// 操作方法如下
int platform_driver_register(struct platform_driver *drv); //初始化platform_driver结构,并向系统中注册struct driver
void platform_driver_unregister(struct platform_driver *drv); //从系统中删除struct driver

// drivers/base/platform.c中定义
/*
 * platform_driver_register - register a driver for platform-level devices
 * @drv: platform driver structure
 */
int platform_driver_register(struct platform_driver *drv)
{
	drv->driver.bus = &platform_bus_type;
	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);
}
/**
 * platform_driver_unregister - unregister a driver for platform-level devices
 * @drv: platform driver structure
 */
void platform_driver_unregister(struct platform_driver *drv)
{
	driver_unregister(&drv->driver);
}

platform的bus_type定义

系统为platform总线定义了一个bus_type的实例,在drivers/base/platform.c中描述。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// drivers/base/platform.c
struct device platform_bus = {
	.init_name	= "platform",
};

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


int __init platform_bus_init(void)
{
	int error;

	early_platform_cleanup();

	error = device_register(&platform_bus);
    	/*
    		注册了一个struct device对象作为,其他platform_device的parent
    		比如在platform_device_add(pdev)函数中
    		pdev->dev.parent = &platform_bus
    	*/
	if (error)
		return error;
	error =  bus_register(&platform_bus_type); 
    	/*
    		注册总线
    		在platform_device_add(pdev)函数中pdev->dev.bus = &platform_bus_type
    	*/
	if (error)
		device_unregister(&platform_bus);
	return error;
}

其中match()方法完成platform_deviceplatform_driver之间是如何进行匹配的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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 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);
}

//其中
#define to_platform_driver(drv)	(container_of((drv), struct platform_driver, driver))
#define to_platform_device(x) container_of((x), struct platform_device, dev)

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

struct platform_device_id {
	char name[PLATFORM_NAME_SIZE];
	kernel_ulong_t driver_data
			__attribute__((aligned(sizeof(kernel_ulong_t))));
};

下边是一个platform_deviceplatform_driver匹配的实例。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// arch/arm/mach-smdkv210.c
static struct resource s3c_i2c_resource[] = {
	[0] = {
		.start = S3C_PA_IIC,
		.end   = S3C_PA_IIC + SZ_4K - 1,
		.flags = IORESOURCE_MEM,
	},
	[1] = {
		.start = IRQ_IIC,
		.end   = IRQ_IIC,
		.flags = IORESOURCE_IRQ,
	},
};

struct platform_device s3c_device_i2c0 = {
	.name		  = "s3c2410-i2c",
	.id		  = 0,
	.num_resources	  = ARRAY_SIZE(s3c_i2c_resource),
	.resource	  = s3c_i2c_resource,
};

// drivers/i2c/busses/i2c-s3c2410.c中关于platform_driver的定义

/* device driver for platform bus bits */
static struct platform_device_id s3c24xx_driver_ids[] = {
	{
		.name		= "s3c2410-i2c",
		.driver_data	= TYPE_S3C2410,
	}, {
		.name		= "s3c2440-i2c",
		.driver_data	= TYPE_S3C2440,
	}, { }, // platform_device_id是一个数组,最后以空元素结尾
};

MODULE_DEVICE_TABLE(platform, s3c24xx_driver_ids);
static struct platform_driver s3c24xx_i2c_driver = {
	.probe		= s3c24xx_i2c_probe,
	.remove		= s3c24xx_i2c_remove,
	.id_table	= s3c24xx_driver_ids, // 用于和platform_device中的name字段匹配
	.driver		= {
		.owner	= THIS_MODULE,
		.name	= "s3c-i2c",
		.pm	= S3C24XX_DEV_PM_OPS,
	},
};

关于资源

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <linux/ioport.h>
struct resource {
	resource_size_t start;
	resource_size_t end;
	const char *name;
	unsigned long flags; // IORESOURCE_IO  IORESOURCE_MEM IORESOURCE_IRQ等 在include /linux/ioport.h中有描述
	struct resource *parent, *sibling, *child;
};

#include <linux/platform_device.h>
/**
 * platform_get_resource - get a resource for a device
 * @dev: platform device
 * @type: resource type
 * @num: resource index
 */
struct resource *platform_get_resource(struct platform_device *, unsigned int, unsigned int);

/**
 * platform_get_irq - get an IRQ for a device
 * @dev: platform device
 * @num: IRQ number index
 */
int platform_get_irq(struct platform_device *, unsigned int);

定义资源以及获取资源的实例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// arch/arm/mach-at91/board-sam9261ek.c
static struct resource dm9000_resource[] = {
	[0] = {
		.start	= AT91_CHIPSELECT_2,
		.end	= AT91_CHIPSELECT_2 + 3,
		.flags	= IORESOURCE_MEM
	},
	[1] = {
		.start	= AT91_CHIPSELECT_2 + 0x44,
		.end	= AT91_CHIPSELECT_2 + 0xFF,
		.flags	= IORESOURCE_MEM
	},
	[2] = {
		.start	= AT91_PIN_PC11,
		.end	= AT91_PIN_PC11,
		.flags	= IORESOURCE_IRQ
			| IORESOURCE_IRQ_LOWEDGE | IORESOURCE_IRQ_HIGHEDGE,
	}
};

// 获取资源实例
res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
port->membase = ioremap(res_mem, ALTERA_JTAGUART_SIZE);

res_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
request_irq(res_irq, ....)    

platform_match()什么时候被谁调用?

参考文献:platform_driver_register()–如何match之后调用probe

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
platform_driver_register
    |
    driver_register
    	|
    	bus_add_driver
    		|
    		driver_attach(drv)
    			int driver_attach(struct device_driver *drv)
                {
                    return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
                }
				|
                __driver_attach
                    |
                    driver_match_device(drv, dev)
                    static inline int driver_match_device(struct device_driver *drv,struct device *dev)
                    {
                        return drv->bus->match ? drv->bus->match(dev, drv) : 1;
                    }
                    	|
                   	 	driver_probe_device
                    		|
                    		really_probe
                    			|
                    			if (dev->bus->probe) {
                               		ret = dev->bus->probe(dev);
                            	} else if (drv->probe) {
                                	ret = drv->probe(dev);
                            	}

总结:

  • bus_for_each_dev遍历总线drv->bus上的每个设备,并对每一个设备执行__driver_attach()函数;
  • __driver_attach()函数又调用driver_match_device()函数匹配pdevpdrv, 而driver_match_device()函数会调用总线的match函数,对于platform总线来说match函数即platform_match();
  • 匹配成功后首先看总线有没有probe函数,若有则调用,否则调用驱动自己的probe函数, 而总线没有probe函数