Linux平台设备驱动模型(platform)-以tq2440的按键为例

本文详细解析了Linux平台总线模型的工作原理,包括平台总线的创建过程、平台设备和平台驱动的注册与匹配机制。重点介绍了TQ2440开发板按键设备的具体实例。

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

弄懂平台设备驱动模型对字符设备驱动的理解是非常有帮助的。

在linux2.6设备模型中,关心总线,设备,驱动这三个实体,总线将设备和驱动绑定,在系统每注册一个设备的时候,会寻找与之匹配的驱动。相反,在系统每注册一个驱动的时候,寻找与之匹配的设备,匹配是由总线来完成的。
    一个现实的Linux 设备和驱动通常都需要挂接在一种总线上,对于本身依附于PCI、USB、I2C、SPI 等的设备而言,这自然不是问题,但是在嵌入式系统里面,SoC 系统中集成的独立的外设控制器、挂接在SoC 内存空间的外设等确不依附于此类总线。基于这一背景,Linux 发明了一种虚拟的总线,称为platform 总线
    SOC系统中集成的独立外设单元(I2C,LCD,SPI,RTC等)都被当作平台设备来处理,而它们本身是字符型设备。

一、首先看一下内核运行时是怎么创建平台总线的。

基本步骤如下所示:

在init/main.c中:start_kernel()-->rest_init()-->kernel_init()-->do_basic_setup()-->(dirvers/base/Init.c)driver_init()-->(dirvers/base/Platform.c)platform_bus_init();  

int __init platform_bus_init(void)
{
	int error;

	early_platform_cleanup();

	error = device_register(&platform_bus);
	if (error)
		return error;
	error =  bus_register(&platform_bus_type);
	if (error)
		device_unregister(&platform_bus);
	return error;
}
device_register(&platform_bus)中的platform_bus如下:
struct device platform_bus = {
       .init_name       = "platform",
};
    该函数把设备名为platform 的设备platform_bus注册到系统中,其他的platform的设备都会以它为parent。它在sysfs中目录下.即 /sys/devices/platform。

    接着bus_register(&platform_bus_type)注册了platform_bus_type总线,看一下改总线的定义:

struct bus_type platform_bus_type = {
	.name		= "platform",
	.dev_attrs	= platform_dev_attrs,
	.match		= platform_match,
	.uevent		= platform_uevent,
	.pm		= PLATFORM_PM_OPS_PTR,
};
    其中platform_match和platform_uevent函数。在分析设备驱动模型是已经知道总线类型match函数是在设备匹配驱动时调用,uevent函数在产生事件时调用。

    看一下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);
}
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;
}
    不难看出,如果pdrv的id_table数组中包含了pdev->name,或者drv->name和pdev->name名字相同,都会认为是匹配成功。id_table数组是为了应对那些对应设备和驱动的drv->name和pdev->name名字不同的情况。

这样就可以通过系统提供的函数,向平台总线上注册平台设备平台驱动了。

二、向平台总线上注册平台设备(platform_device)

先看一下用来描述平台设备的结构(include/linuxplatform_device.h)

struct platform_device {
	const char	* name;
	int		id;
	struct device	dev;
	u32		num_resources;
	struct resource	* resource;
	struct platform_device_id	*id_entry;
};

其中:const char *name    表示设备名,在进行设备匹配时进行匹配的依据。

          u32 num_resources    表示设备所有的各类资源数量。 

          struct resource *resource    表示所使用到的资源。

下面根据具体的TQ2440中按键的设备来看一下。(arch/arm/mach-s3c2440/mach-tq2440.c)

static struct gpio_keys_button gpio_buttons[] = {
	{//RIGHT 1
		.gpio		= S3C2410_GPF0,
		.code		= KEY_RIGHT,//#define KEY_UP  103
		.desc		= "KEY_RIGHT",
		.active_low	= 1,
		.wakeup		= 0,
	},
	{//UP 2
		.gpio		= S3C2410_GPF1,
		.code		= KEY_UP,//#define KEY_DOWN	108
		.desc		= "KEY_UP",
		.active_low	= 1,
		.wakeup		= 0,
	},
	{//LEFT 3
		.gpio		= S3C2410_GPF2,
		.code		= KEY_LEFT,//#define KEY_LEFT 105
		.desc		= "KEY_LEFT",
		.active_low	= 1,
		.wakeup		= 0,
	},
	{//DOWN 4
		.gpio		= S3C2410_GPF4,
		.code		= KEY_DOWN,//#define KEY_RIGHT	106
		.desc		= "KEY_DOWN",
		.active_low	= 1,
		.wakeup		= 0,
	},
};
static struct gpio_keys_platform_data gpio_button_data = {
	.buttons	= gpio_buttons,
	.nbuttons	= ARRAY_SIZE(gpio_buttons),
};

struct platform_device s3c_device_gpio_button = {
	.name		= "tq2440-keys",
	.id		= -1,
	.num_resources	= 0,
	.dev		= {
		.platform_data	= &gpio_button_data,
	}
};

平台设备的注册和注销是采用下面两个函数进行的。

platform_device_register();    

platform_device_unregister();

看下面代码:(arch/arm/mach-s3c2440/mach-tq2440.c)

static void __init tq2440_machine_init(void)
{
	s3c24xx_fb_set_platdata(&tq2440_fb_info);
	s3c_i2c0_set_platdata(NULL);

	platform_add_devices(tq2440_devices, ARRAY_SIZE(tq2440_devices));
	EmbedSky_machine_init();
	s3c2410_gpio_setpin(S3C2410_GPG12, 0);
	s3c2410_gpio_cfgpin(S3C2410_GPG12, S3C2410_GPIO_OUTPUT);
	s3c24xx_udc_set_platdata(&EmbedSky_udc_cfg);
}

MACHINE_START(S3C2440, "TQ2440")
	.phys_io		= S3C2410_PA_UART,
	.io_pg_offst	= (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
	.boot_params	= S3C2410_SDRAM_PA + 0x100,

	.init_irq		= s3c24xx_init_irq,
	.map_io			= tq2440_map_io,
	.init_machine	= tq2440_machine_init,
	.timer			= &s3c24xx_timer,
MACHINE_END
1. 其中platform_add_devices()函数原型在(dirvers/base/Platform.c)文件中定义如下:
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;
}
可以看出最终还是调用了platform_device_register()函数,在其中又调用了device_initialize()platform_device_add()函数,最终将平台设备添加到平台总线,在此就不在深入分析。

2. platform_device_register()函数又在下面的宏MACHINE_START中被使用。看一下宏的原型。

struct machine_desc {
	/*
	 * Note! The first four elements are used
	 * by assembler code in head.S, head-common.S
	 */
	unsigned int		nr;		/* architecture number	*/
	unsigned int		phys_io;	/* start of physical io	*/
	unsigned int		io_pg_offst;	/* byte offset for io 
						 * page tabe entry	*/

	const char		*name;		/* architecture name	*/
	unsigned long		boot_params;	/* tagged list		*/

	unsigned int		video_start;	/* start of video RAM	*/
	unsigned int		video_end;	/* end of video RAM	*/

	unsigned int		reserve_lp0 :1;	/* never has lp0	*/
	unsigned int		reserve_lp1 :1;	/* never has lp1	*/
	unsigned int		reserve_lp2 :1;	/* never has lp2	*/
	unsigned int		soft_reboot :1;	/* soft reboot		*/
	void			(*fixup)(struct machine_desc *,
					 struct tag *, char **,
					 struct meminfo *);
	void			(*map_io)(void);/* IO mapping function	*/
	void			(*init_irq)(void);
	struct sys_timer	*timer;		/* system tick timer	*/
	void			(*init_machine)(void);
};

/*
 * Set of macros to define architecture features.  This is built into
 * a table by the linker.
 */
#define MACHINE_START(_type,_name)			\
static const struct machine_desc __mach_desc_##_type	\
 __used							\
 __attribute__((__section__(".arch.info.init"))) = {	\
	.nr		= MACH_TYPE_##_type,		\
	.name		= _name,

#define MACHINE_END				\
};

    就是定义了一个struct machine_desc类型结构体变量,这个结构体还定义了其他一些成员,着重看一下.init_machine这个成员,它是一个函数指针,值为tq2440_machine_init();

其实这个宏的调用顺序是:

start_kernel()-->(arch/arm/kernel/Setup.c)setup_arch()-->init_machine = mdesc->init_machine。

至此在内核启动后,平台设备被加载到平台总线上。

三、向平台总线上注册平台驱动(platform_driver)

同样先看一下用来描述平台驱动的结构(include/linuxplatform_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 (*suspend_late)(struct platform_device *, pm_message_t state);
	int (*resume_early)(struct platform_device *);
	int (*resume)(struct platform_device *);
	struct device_driver driver;
	struct platform_device_id *id_table;
};

系统上电后,加载内核驱动模块时会调用下面函数(tq2440_keys_init())(drivers/input/keyboard/tq2440_buttons.c):

static struct platform_driver tq2440_keys_device_driver = {
	.probe		= tq2440_keys_probe,
	.remove		= __devexit_p(tq2440_keys_remove),
	.driver		= {
		.name	= "tq2440-keys",
		.owner	= THIS_MODULE,
#ifdef CONFIG_PM
		.pm	= &tq2440_keys_pm_ops,
#endif
	}
};

static int __init tq2440_keys_init(void)
{
	return platform_driver_register(&tq2440_keys_device_driver);
}

注册和注销平台驱动是使用下面两个函数进行:

platform_driver_register();

platform_driver_unregister();

看一下tq2440_keys_device_driver这个结构体,指定了probe函数,以及驱动的名字"tq2440-keys"。当此驱动的名字与平台总线上的设备匹配成功后,会调用probe指向的函数tq2440_keys_probe()

下面具体分析一下:进入platform_driver_register()函数dirvers/base/Platform.c看一下:

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;
	if (drv->suspend)
		drv->driver.suspend = platform_drv_suspend;
	if (drv->resume)
		drv->driver.resume = platform_drv_resume;
	return driver_register(&drv->driver);
}

1. 首先指明了,平台驱动的驱动所属的平台总线的类型。platform_bus_type(在注册平台总线时定义)

2. 初始化一下平台驱动的一些函数指针。

3. 最后调用driver_register()函数进行驱动注册。继续向下看driver_register()函数。

int driver_register(struct device_driver *drv)
{
	int ret;
	struct device_driver *other;

	BUG_ON(!drv->bus->p);

	if ((drv->bus->probe && drv->probe) ||
	    (drv->bus->remove && drv->remove) ||
	    (drv->bus->shutdown && drv->shutdown))
		printk(KERN_WARNING "Driver '%s' needs updating - please use "
			"bus_type methods\n", drv->name);

	other = driver_find(drv->name, drv->bus);
	if (other) {
		put_driver(other);
		printk(KERN_ERR "Error: Driver '%s' is already registered, "
			"aborting...\n", drv->name);
		return -EEXIST;
	}

	ret = bus_add_driver(drv);
	if (ret)
		return ret;
	ret = driver_add_groups(drv, drv->groups);
	if (ret)
		bus_remove_driver(drv);
	return ret;
}

1. 首先调用了driver_find(drv->name, drv->bus);向平台总线上查找是否存在我这个名字的驱动,存在就不在注册。

2. 如果不存在就调用bus_add_driver();添加这个驱动到总线。具体怎么添加在此不再详述,添加成功后,会调用平台驱动结构中的probe指向的函数tq2440_keys_probe();函数。

至此,平台驱动和平台设备都加载完成。由于安全牵扯的到输入子系统,在此就不在继续介绍。

注:本人也是菜鸟一枚,如有错误的地方请各位指正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值