以下内容源于朱有鹏嵌入式课程的学习与整理,如有侵权请告知删除。
一、平台总线的简介
1、平台总线的简介
(1)平台总线属于总线中的一种,相对于usb、pci、i2c等物理总线来说,平台总线是虚拟的、抽象出来的。
(2)CPU与外部通信有两种方式,即地址总线式连接(比如SoC内部集成的各种内部外设与CPU的连接)与专用接口式连接(比如nand和CPU连接)。平台总线对应着地址总线式连接设备。
2、平台总线的意义
平台总线的设计目的,是为了管理上的方便和统一。
二、平台总线的两员大将
平台总线的工作体系都定义在内核源代码drivers/base/platform.c中。
其主要涉及两个结构体:struct platform_device和 struct platform_driver。
1、struct platform_device 结构体
struct platform_device {const char * name; // 平台总线下设备的名字int id;struct device dev; // 所有设备通用的属性部分u32 num_resources;// 设备使用到的resource(IO或者中断号等)的个数struct resource* resource;// 设备使用到的资源数组的首地址// 设备ID表,很多个类似的同系列的产品,可以用同一个驱动const struct platform_device_id* id_entry;/* arch specific additions */struct pdev_archdata archdata;// 自留地,用来提供扩展性的,表示设备的一些属性 };
2、struct platform_driver 结构体
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;// 所有设备共用的一些属性const struct platform_device_id *id_table;// 设备ID表,表示支持哪些设备 };
3、两个接口函数
(1)platform_device_register():在系统启动时用来注册设备。
(2)platform_driver_register():用来注册驱动。
三、平台总线的工作流程
1、工作流程简介
(1)第一步:系统启动时在bus系统中注册platform,使得在/sys/bus/目录有platform目录。
(2)第二步:内核移植的人负责提供platform_device,即提供板文件。
比如板文件x210_kernel\arch\arm\mach-s5pv210\mach-x210.c文件中部分内容如下:
#if defined(CONFIG_BACKLIGHT_PWM) static struct platform_pwm_backlight_data smdk_backlight_data = {.pwm_id = 0,.max_brightness = 255,.dft_brightness = 255,.pwm_period_ns = 78770*4, };static struct platform_device smdk_backlight_device = { //描述设备信息.name = "pwm-backlight",.id = -1,.dev = {.parent = &s3c_device_timer[0].dev,.platform_data = &smdk_backlight_data,}, };static void __init smdk_backlight_register(void) {int ret; //省略部分代码ret = platform_device_register(&smdk_backlight_device);//进行设备注册if (ret)printk(KERN_ERR "smdk: failed to register backlight device: %d\n", ret); } #endif
(3)第三步:写驱动的人负责提供platform_driver,主要是填充结构体并register。
比如x210_kernel\drivers\video\backlight\pwm_bl.c文件部分代码如下:
static struct platform_driver pwm_backlight_driver = {.driver = {.name = "pwm-backlight",.owner = THIS_MODULE,},.probe = pwm_backlight_probe,.remove = pwm_backlight_remove,.suspend = pwm_backlight_suspend,.resume = pwm_backlight_resume, };static int __init pwm_backlight_init(void) {return platform_driver_register(&pwm_backlight_driver); } module_init(pwm_backlight_init);
(4)第四步:平台总线的match函数通过driver和device两者的name发现它们匹配,于是调用driver的probe函数来完成驱动的初始化和安装,然后设备就工作起来了。这个是自动的,有别于之前的手动安装。
2、工作流程细述
第一步:平台总线的注册
(1)平台总线注册函数: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; }
由上可知,该函数调用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_dev_pm_ops, };
它是struct bus_type结构体类型的实例化,struct bus_type结构体内容如下。
struct bus_type {const char *name;struct bus_attribute *bus_attrs;struct device_attribute *dev_attrs;struct driver_attribute *drv_attrs;int (*match)(struct device *dev, struct device_driver *drv);int (*uevent)(struct device *dev, struct kobj_uevent_env *env);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 dev_pm_ops *pm;struct bus_type_private *p; };
(2)match函数
每种总线(包括平台总线、usb总线、i2c总线等)都会携带一个match函数,用来对总线下的device和driver进行匹配。理论上每种总线的匹配算法是不同的,但实际上都是看名字的。
由平台总线“platform_bus_type”的定义可知,平台总线的匹配方法是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(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; }
由此可知:如果驱动有id_table,则说明该驱动可能支持多个设备;这时候要去对比id_table中所有的name,只要找到一个与设备名字相同的就匹配上了不再找了,如果找完id_table都还没找到就说明没有匹配上。如果没有id_table或者没有匹配上,那就直接对比device和driver的name,如果还没匹配上那就匹配失败。
第二步:平台设备的注册
(1)平台设备的注册过程
内容总结
对于linux2.6 arm平台而言,对platform_device的定义通常在bsp的板文件中实现。板文件将众多设备归纳为一个数组,通过platform_add_devices()函数逐个注册。
过程分析
如何寻找在板文件定义的设备信息呢?可以通过搜索名字。以leds-s3c24xx.c这个驱动文件为例进行说明,由驱动名字可知它对应的设备名字叫做“s3c24xx_led”。
static struct platform_driver s3c24xx_led_driver = {.probe = s3c24xx_led_probe,.remove = s3c24xx_led_remove,.driver = {.name = "s3c24xx_led",//由此可知该驱动对应的设备名字应该也叫s3c24xx_led.owner = THIS_MODULE,}, };
于是在SI工具里搜索这个内容,得知其出现在x210_kernel\arch\arm\mach-s3c2440\mach-mini2440.c文件中,内容如下:
static struct platform_device mini2440_led1 = {.name = "s3c24xx_led",.id = 1,.dev = {.platform_data = &mini2440_led1_pdata,}, };static struct platform_device mini2440_led2 = {.name = "s3c24xx_led",.id = 2,.dev = {.platform_data = &mini2440_led2_pdata,}, };static struct platform_device mini2440_led3 = {.name = "s3c24xx_led",.id = 3,.dev = {.platform_data = &mini2440_led3_pdata,}, }; ……
由上可知其定义了mini2440_led1等设备信息。搜索关键词mini2440_led1,得知其出现在mach-mini2440.c文件中,其内容如下:
static struct platform_device *mini2440_devices[] __initdata = {&s3c_device_ohci,&s3c_device_wdt,&s3c_device_i2c0,&s3c_device_rtc,&s3c_device_usbgadget,&mini2440_device_eth,&mini2440_led1,//这里&mini2440_led2,&mini2440_led3,&mini2440_led4,&mini2440_button_device,&s3c_device_nand,&s3c_device_sdi,&s3c_device_iis,&mini2440_audio, };
由上可知,mach-mini2440.c文件将众多设备都归纳为一个指针数组mini2440_devices,通过在SI中搜索关键词mini2440_devices,得知其出现在mach-mini2440.c文件中,其内容如下:
static void __init mini2440_init(void) {struct mini2440_features_t features = { 0 };int i;//省略部分代码//出现在这里platform_add_devices(mini2440_devices, ARRAY_SIZE(mini2440_devices));if (features.count) /* the optional features */platform_add_devices(features.optional, features.count);}
platform_add_devices()函数内容如下,也就是循环利用platform_device_register函数,对每个平台设备进行注册。从代码可知,每个平台设备的注册都不能出错。
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; }
(2)平台设备的platform_data如何使用
platform_data,其实就是设备注册时要提供的与设备有关的一些数据(比如设备对应的gpio、使用到的中断号、设备名称等等),一般定义在板级文件里。比如平台设备mini2440_led1,其硬件信息里有platform_data。
//以下三个结构体的位置并非如下所示,只是为了一目了然而写在同一代码段里。 static struct platform_device mini2440_led1 = {.name = "s3c24xx_led",.id = 1,.dev = {.platform_data = &mini2440_led1_pdata,//关注这个}, };static struct s3c24xx_led_platdata mini2440_led1_pdata = {.name = "led1",.gpio = S3C2410_GPB(5),.flags = S3C24XX_LEDF_ACTLOW | S3C24XX_LEDF_TRISTATE,.def_trigger = "heartbeat", };struct s3c24xx_led_platdata {unsigned int gpio;unsigned int flags;char *name;char *def_trigger; };
在设备和驱动匹配之后,这些数据会由设备方转给驱动方(驱动的probe函数的第一句代码)。驱动拿到这些数据后,通过这些数据得知设备的具体信息,进而操作设备。如此一来,驱动源码中不携带数据,只负责算法(对硬件的操作方法)。现代驱动设计理念就是算法和数据分离,这样最大程度保持驱动的独立性和适应性。比如leds-s3c24xx.c作为驱动,不会携带设备信息,设备信息写在板文件mach-mini2440.c中。
static int s3c24xx_led_probe(struct platform_device *dev) { //在设备和驱动匹配之后,这些数据会由设备方转给驱动方(probe的第一句代码)struct s3c24xx_led_platdata *pdata = dev->dev.platform_data;struct s3c24xx_gpio_led *led;int ret;//省略部分代码//驱动拿到这些数据后,通过这些数据得知设备的具体信息,进而操作设备。if (pdata->flags & S3C24XX_LEDF_TRISTATE) {s3c2410_gpio_setpin(pdata->gpio, 0);s3c2410_gpio_cfgpin(pdata->gpio, S3C2410_GPIO_INPUT);} else {s3c2410_gpio_pullup(pdata->gpio, 0);s3c2410_gpio_setpin(pdata->gpio, 0);s3c2410_gpio_cfgpin(pdata->gpio, S3C2410_GPIO_OUTPUT);}/* register our new led device *///这里的注册设备,与平台设备的注册,有矛盾吗?ret = led_classdev_register(&dev->dev, &led->cdev);if (ret < 0) {dev_err(&dev->dev, "led_classdev_register failed\n");kfree(led);return ret;}return 0; }
s3c24xx_led_probe()函数中有设备的注册,板级文件间接调用platform_device_register()函数完成设备的注册,它们有什么区别吗?会不会冲突?
第三步:平台驱动的注册
(1)平台驱动的注册过程
以leds-s3c24xx.c这个驱动文件为例进行说明,平台驱动的注册如下:
static struct platform_driver s3c24xx_led_driver = {.probe = s3c24xx_led_probe,.remove = s3c24xx_led_remove,.driver = {.name = "s3c24xx_led",//驱动名字,和设备名字应该一致.owner = THIS_MODULE,}, };static int __init s3c24xx_led_init(void) {return platform_driver_register(&s3c24xx_led_driver); }static void __exit s3c24xx_led_exit(void) {platform_driver_unregister(&s3c24xx_led_driver); }module_init(s3c24xx_led_init); module_exit(s3c24xx_led_exit);
(2)probe函数的功能和意义
static int s3c24xx_led_probe(struct platform_device *dev) {struct s3c24xx_led_platdata *pdata = dev->dev.platform_data;struct s3c24xx_gpio_led *led;int ret;led = kzalloc(sizeof(struct s3c24xx_gpio_led), GFP_KERNEL);if (led == NULL) {dev_err(&dev->dev, "No memory for device\n");return -ENOMEM;}platform_set_drvdata(dev, led);led->cdev.brightness_set = s3c24xx_led_set;led->cdev.default_trigger = pdata->def_trigger;led->cdev.name = pdata->name;led->cdev.flags |= LED_CORE_SUSPENDRESUME;led->pdata = pdata;/* no point in having a pull-up if we are always driving */if (pdata->flags & S3C24XX_LEDF_TRISTATE) {s3c2410_gpio_setpin(pdata->gpio, 0);s3c2410_gpio_cfgpin(pdata->gpio, S3C2410_GPIO_INPUT);} else {s3c2410_gpio_pullup(pdata->gpio, 0);s3c2410_gpio_setpin(pdata->gpio, 0);s3c2410_gpio_cfgpin(pdata->gpio, S3C2410_GPIO_OUTPUT);}/* register our new led device */ret = led_classdev_register(&dev->dev, &led->cdev);if (ret < 0) {dev_err(&dev->dev, "led_classdev_register failed\n");kfree(led);return ret;}return 0; }