您的位置:首页 > 运维架构 > Linux

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

2018-03-15 15:19 639 查看
弄懂平台设备驱动模型对字符设备驱动的理解是非常有帮助的。
在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 *platfor
d0d8
m_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();函数。
至此,平台驱动和平台设备都加载完成。由于安全牵扯的到输入子系统,在此就不在继续介绍。
注:本人也是菜鸟一枚,如有错误的地方请各位指正。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: