Linux设备驱动模型3——平台总线的工作原理

以下内容源于朱有鹏嵌入式课程的学习与整理,如有侵权请告知删除。

一、平台总线的简介

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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天糊土

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值