首页 > 技术文章 > platform驱动架构初探

pusidun 2018-05-18 16:44 原文

platform总线是Linux2.6引入的虚拟总线,这类总线没有对应的硬件结构。与之相反,USB总线和PCI总线在内核中是有对应的bus(USB-bus和PCI-bus)的。为了统一管理CPU这些既不属于USB又不属于PCI总线的外设资源,采用了platform虚拟总线。和字符设备不同,在platform架构中,整个驱动分为了device和driver两部分,提高了系统的可移植性。
在学习platform架构时,我们可以借助一点面向对象的思想,注意关注一些重要的结构体,将属性和行为分开学习,再联系起来。
本文所有代码基于linux3.9.5

platform总线驱动架构概览

可以分为如下三层:

  1. 设备struct platform_device : 资源分配
  2. 驱动struct platform_driver :初始化
  3. 总线struct platform_bus :device和driver的匹配,管理

linux内核启动流程和platform总线的注册

kernel在进入C语言阶段,会进入start_kernel函数(init/main.c),进行一些内存管理,调度。该函数的最后会执行rest_init();
下面是rest_init(init/main.c)源码

static noinline void __init_refok rest_init(void)
{
	int pid;

	rcu_scheduler_starting();
	/*
	 * We need to spawn init first so that it obtains pid 1, however
	 * the init task will end up wanting to create kthreads, which, if
	 * we schedule it before we create kthreadd, will OOPS.
	 */
	kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
	numa_default_policy();
	pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
	rcu_read_lock();
	kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
	rcu_read_unlock();
	complete(&kthreadd_done);

	/*
	 * The boot idle thread must execute schedule()
	 * at least once to get things moving:
	 */
	init_idle_bootup_task(current);
	schedule_preempt_disabled();
	/* Call into cpu_idle with preempt disabled */
	cpu_idle();
}

我们可以看到,该函数做了三件事:

  • 首先创建了一个线程执行kernel_init函数,该函数读取根文件系统下的init程序。这个操作完成了从内核态到用户态的转变。init进程作为所以用户态进程的父进程,将永远存在,PID是1
  • kthreadd是一个守护进程,PID是2
  • idle是空闲进程,cpu空闲时启动

我们进入kernel_init函数,在进入其中的kernel_init_freeable函数,继续进入do_basic_setup函数,这里我们就可以看到对驱动的初始化函数driver_init();
driver_init函数中,倒数第三个执行的函数platform_bus_init就是我们想找的platform总线的注册函数,位于drivers/base/platform.c

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

除了最后的platform_bus_init位于drivers/base,其余函数都位于init/main.c中。下面我们将注意力从内核启动转移到platform设备上。

platform架构总线

platform_bus是一种设备

struct device platform_bus = {
	.init_name	= "platform",
};

从上面的结构体我们可以看到,platform_bus是一个名字为“platform”的device。device结构体是内核中设备的基本结构体。其他是设备,例如USB,都和device有关。这些设备的结构体或包含device成员,或实现device的部分成员。C中没有面向对象的继承特性,所以通过这种方式,我们变相的实现了“继承”

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,  //电源管理
};

我们关注下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);

	/* Attempt an OF style match first */
	if (of_driver_match_device(dev, drv))
		return 1;

	/* Then try ACPI style match */
	if (acpi_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);
}

我们可以看到,这个函数的作用是将platform的device和driver名字相比较,相同则返回True表示匹配。

总线注册

上文linux启动过程中已经分析,不赘述

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

platform设备

platform_device结构体

这里我们要关注下platform_device结构体,位于include/linux/platform_device.h

struct platform_device {
	const char	* name;
	int		id;
	bool		id_auto;
	struct device	dev;
	u32		num_resources;
	struct resource	* resource;

	const struct platform_device_id	*id_entry;

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

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

platform_device 封装了device。

  • name:设备的名称
  • dev:真正有用的设备,通过contain_of,能找到整个platform_device
  • num_resources, resource: 系统使用的资源。Linux系统资源包括IO,寄存器,DMA,Bus,Memory等。

设备的注册和卸载

int platform_device_register(struct platform_device *pdev)
{
	device_initialize(&pdev->dev);
	arch_setup_pdev_archdata(pdev);
	return platform_device_add(pdev);
}

void platform_device_unregister(struct platform_device *pdev)
{
	platform_device_del(pdev);
	platform_device_put(pdev);
}

platform driver

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

由device_driver结构体封装而来

struct device_driver {
	const char		*name;
	struct bus_type		*bus;

	struct module		*owner;
	const char		*mod_name;	/* used for built-in modules */

	bool suppress_bind_attrs;	/* disables bind/unbind via sysfs */

	const struct of_device_id	*of_match_table;
	const struct acpi_device_id	*acpi_match_table;

	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 attribute_group **groups;

	const struct dev_pm_ops *pm;

	struct driver_private *p;
};
  • probe:将driver绑定到device上调用该函数
  • remove:系统卸载设备的时候,将driver和device解绑
  • shutdown:关机时使设备静默
  • suspend:使设备进入睡眠模式
  • resume: 唤醒设备

推荐阅读