以leds-gpio.c为例,浅谈class、bus和platform子系统

本文通过分析Linux内核中的leds-gpio.c驱动程序,探讨class子系统如何为driver提供统一服务,class如何作为设备访问接口,以及bus和platform子系统在设备驱动中的作用。leds-gpio.c主要利用led驱动框架,通过class与platform交互,实现LED控制。文章介绍了led-class.c对外提供的函数,以及平台设备和驱动的匹配过程。
摘要由CSDN通过智能技术生成

一、前言

leds-gpio.c是内核源码drivers/leds/leds-gpio.c目录下的一个led驱动程序的例程(说明书位于Documentation/devicetree/bindings/leds/leds-gpio.txt),它使用了内核提供的led驱动框架(说明书位于Documentation/leds/leds-class.txt)。本文不具体分析led驱动框架,而是借由分析leds-gpio.ko模块的源文件leds-gpio.c来初步介绍一下class、bus和platform子系统,我觉得这比一上来就长篇大论系统地分析这三个子系统要好。这里不贴leds-gpio.c源文件的完整内容,建议大家直接去内核源码里找出这个源文件进行阅读,我这里使用BeagleBone AI这块板子为例进行介绍。本文会涉及到一些数据结构和函数的定义,但限于篇幅不会具体展开讲述其意义和用法,建议大家去内核里找到相关代码和文档进行阅读,我觉得这样比三言两语说一些似有若无、不痛不痒的内容要更为实在。本文引用的是4.14版本的内核源码。

二、初窥led驱动框架

所谓的led驱动框架主要包含两个文件led-class.c(实现了include/linux/leds.h)和led-core.c(实现了drivers/leds/leds.h头文件),从头文件的路径可以看出来led驱动框架对外提供的接口均在include/linux/leds.h头文件中声明,led-core.c中实现的函数由led-class.c等led驱动框架的内部源文件使用。leds-gpio.c主要使用的是include/linux/leds.h头文件中led-class.c实现的函数,而trigger相关的部分则没有涉及(一些用来触发led闪烁的模块,如MMC、定时器、CPU、GPIO输入、网络)。在开发板上执行如下命令可知led驱动框架由led-class.ko模块提供服务:

debian@beaglebone:~$ cat /lib/modules/4.14.108-ti-r143/modules.builtin | grep led
kernel/drivers/input/input-leds.ko
kernel/drivers/leds/led-class.ko
kernel/drivers/leds/leds-gpio.ko
kernel/drivers/leds/trigger/ledtrig-timer.ko
kernel/drivers/leds/trigger/ledtrig-oneshot.ko
kernel/drivers/leds/trigger/ledtrig-heartbeat.ko
kernel/drivers/leds/trigger/ledtrig-backlight.ko
kernel/drivers/leds/trigger/ledtrig-gpio.ko
kernel/drivers/leds/trigger/ledtrig-activity.ko
kernel/drivers/leds/trigger/ledtrig-default-on.ko
kernel/drivers/leds/trigger/ledtrig-netdev.ko

在led-class.c文件中搜索EXPORT_SYMBOL_GPL可知该文件对外提供了9个函数,其中与leds-gpio.c相关的只有如下两个函数:

/**
 * of_led_classdev_register - register a new object of led_classdev class.
 *
 * @parent: parent of LED device
 * @led_cdev: the led_classdev structure for this device.
 * @np: DT node describing this LED
 */
int of_led_classdev_register(struct device *parent, struct device_node *np,
			    struct led_classdev *led_cdev)
{
...
	led_cdev->dev = device_create_with_groups(leds_class, parent, 0,
				led_cdev, led_cdev->groups, "%s", name);
...
    led_init_core(led_cdev);
...
}
EXPORT_SYMBOL_GPL(of_led_classdev_register);

/**
 * devm_of_led_classdev_register - resource managed led_classdev_register()
 *
 * @parent: parent of LED device
 * @led_cdev: the led_classdev structure for this device.
 */
int devm_of_led_classdev_register(struct device *parent,
				  struct device_node *np,
				  struct led_classdev *led_cdev)
{
...
	rc = of_led_classdev_register(parent, np, led_cdev);
...
}
EXPORT_SYMBOL_GPL(devm_of_led_classdev_register);

leds-gpio.c文件只在create_gpio_led函数内调用了led驱动框架中led-class.c文件定义的devm_of_led_classdev_register函数。在开发板上执行如下命令可以看到在/sys/class/leds目录下的所有文件均为指向platform设备的链接文件:

debian@beaglebone:~$ ls -l /sys/class/leds/
total 0
lrwxrwxrwx 1 root root 0 Mar 22 20:37 beaglebone:green:usr0 -> ../../devices/platform/leds/leds/beaglebone:green:usr0
lrwxrwxrwx 1 root root 0 Mar 22 20:37 beaglebone:green:usr1 -> ../../devices/platform/leds/leds/beaglebone:green:usr1
lrwxrwxrwx 1 root root 0 Mar 22 20:37 beaglebone:green:usr2 -> ../../devices/platform/leds/leds/beaglebone:green:usr2
lrwxrwxrwx 1 root root 0 Mar 22 20:37 beaglebone:green:usr3 -> ../../devices/platform/leds/leds/beaglebone:green:usr3
lrwxrwxrwx 1 root root 0 Mar 22 20:37 beaglebone:green:usr4 -> ../../devices/platform/leds/leds/beaglebone:green:usr4
lrwxrwxrwx 1 root root 0 Mar 22 20:37 mmc0:: -> ../../devices/platform/44000000.ocp/4809c000.mmc/leds/mmc0::
lrwxrwxrwx 1 root root 0 Mar 22 20:37 mmc1:: -> ../../devices/platform/44000000.ocp/480b4000.mmc/leds/mmc1::
lrwxrwxrwx 1 root root 0 Mar 22 20:37 mmc2:: -> ../../devices/platform/44000000.ocp/480d1000.mmc/leds/mmc2::

三、class

个人认为class由两个作用,一个是向上为某一类driver提供统一的服务(驱动框架),另一个向下为访问某一类device提供统一的接口(class子目录下指向各个设备的链接、class提供的访问函数)。class的存在有利于driver的分层,因为最底层的driver总是去匹配device从而获取硬件信息的程序(比如根据设备树compatible属性的值来匹配),如果我们想把鼠标、键盘、触摸屏的驱动程序进一步地抽象成input子系统,那么这个input子系统就不是直接与硬件信息打交道的driver,此时input子系统就需要借助class来实现,而事实上Linux也是这么做的,大家可以在开发板上执行ls -l /sys/class/input/命令来查看这些指向不同device的链接文件。内核源码中class的说明书是Documentation/driver-model/class.txt。内核模块可以为自己注册一个类,以更方便用户或上层驱动管理某一类设备,比如led驱动框架提供的led-class.c就注册了一个名为leds的class,以方便其它上层的驱动程序使用(比如leds-gpio.c),/sys/class/leds目录下也有多个位于不同路径的device,它们有一个共同点,那就是都使用了led驱动框架(led-class.c)。class结构体的定义如下(include/linux/device.h):

/**
 * struct class - device classes
 * @name:	Name of the class.
 * @owner:	The module owner.
 * @class_groups: Default attributes of this class.
 * @dev_groups:	Default attributes of the devices that belong to the class.
 * @dev_kobj:	The kobject that represents this class and links it into the hierarchy.
 * @dev_uevent:	Called when a device is added, removed from this class, or a
 *		few other things that generate uevents to add the environment
 *		variables.
 * @devnode:	Callback to provide the devtmpfs.
 * @class_release: Called to release this class.
 * @dev_release: Called to release the device.
 * @suspend:	Used to put the device to sleep mode, usually to a low power
 *		state.
 * @resume:	Used to bring the device from the sleep mode.
 * @shutdown_pre: Called at shut-down time before driver shutdown.
 * @ns_type:	Callbacks so sysfs can detemine namespaces.
 * @namespace:	Namespace of the device belongs to this class.
 * @pm:		The default device power management operations of this class.
 * @p:		The private data of the driver core, no one other than the
 *		driver core can touch this.
 *
 * A class is a higher-level view of a device that abstracts out low-level
 * implementation details. Drivers may see a SCSI disk or an ATA disk, but,
 * at the class level, they are all simply disks. Classes allow user space
 * to work with devices based on what they do, rather than how they are
 * connected or how they work.
 */
struct class {
	const char		*name;
	struct module		*owner;

	const struct attribute_group	**class_groups;
	const struct attribute_group	**dev_groups;
	struct kobject			*dev_kobj;

	int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);
	char *(*devnode)(struct device *dev, umode_t *mode);

	void (*class_release)(struct class *class);
	void (*dev_release)(struct device *dev);

	int (*suspend)(struct device *dev, pm_message_t state);
	int (*resume)(struct device *dev);
	int (*shutdown_pre)(struct device *dev);

	const struct kobj_ns_type_operations *ns_type;
	const void *(*namespace)(struct device *dev);

	const struct dev_pm_ops *pm;

	struct subsys_private *p;
};

内核文档中给的解释我个人认为是十分抽象的,我觉得去搜索内核源码里哪些地方调用了class相关的函数(可以执行ls /sys/class命令看看哪些模块创建了class,再阅读相关模块的源码),然后再作出一个总结会比较实在。个人认为class并不像bus或者platform那样不可或缺,属于是一个锦上添花的存在,提高了系统的易用性。比如在led-class.c中就调用了如下与class有关的函数(定义于include/linux/device.h和drivers/base/class.c):

/* This is a #define to keep the compiler from merging different
 * instances of the __key variable */
#define class_create(owner, name)		\
({						\
	static struct lock_class_key __key;	\
	__class_create(owner, name, &__key);	\
})
/**
 * class_create - create a struct class structure
 * @owner: pointer to the module that is to "own" this struct class
 * @name: pointer to a string for the name of this class.
 * @key: the lock_class_key for this class; used by mutex lock debugging
 *
 * This is used to create a struct class pointer that can then be used
 * in calls to device_create().
 *
 * Returns &struct class pointer on success, or ERR_PTR() on error.
 *
 * Note, the pointer created here is to be destroyed when finished by
 * making a call to class_destroy().
 */
struct class *__class_create(struct module *owner, const char *name,
			     struct lock_class_key *key);

/**
 * class_destroy - destroys a struct class structure
 * @cls: pointer to the struct class that is to be destroyed
 *
 * Note, the pointer to be destroyed must have been created with a call
 * to class_create().
 */
void class_destroy(struct class *cls)
{
	if ((cls == NULL) || (IS_ERR(cls)))
		return;

	class_unregister(cls);
}

/**
 * class_find_device - device iterator for locating a particular device
 * @class: the class we're iterating
 * @start: Device to begin with
 * @data: data for the match function
 * @match: function to check device
 *
 * This is similar to the class_for_each_dev() function above, but it
 * returns a reference to a device that is 'found' for later use, as
 * determined by the @match callback.
 *
 * The callback should return 0 if the device doesn't match and non-zero
 * if it does.  If the callback returns non-zero, this function will
 * return to the caller and not iterate over any more devices.
 *
 * Note, you will need to drop the reference with put_device() after use.
 *
 * @match is allowed to do anything including calling back into class
 * code.  There's no locking restriction.
 */
struct device *class_find_device(struct class *class, struct device *start,
				 const void *data,
				 int (*match)(struct device *, const void *));

四、bus

上节中反复提到了device和driver两个词,其中device是硬件信息的集合,driver则是使用这些硬件信息的驱动程序。内核中bus这个概念其实不是硬件上说的总线,内核中bus这个概念其实指的是匹配device和driver的一系列规则以及实现匹配这个功能的一系列代码,我们定义的不同类型的bus,其实就代表着不同类型的匹配规则及其实现逻辑,可以执行ls /sys/bus命令查看系统中有哪些bus。有了bus之后就可以提高驱动程序的通用性,不必为了硬件上一些无关紧要的改动而修改驱动程序,因为驱动程序可以通过bus获取那些经常被改动而又都可以兼容的硬件信息。bus的内核说明书是Documentation/driver-model/bus.txt。bus定义于文件include/linux/device.h:

/**
 * struct bus_type - The bus type of the device
 *
 * @name:	The name of the bus.
 * @dev_name:	Used for subsystems to enumerate devices like ("foo%u", dev->id).
 * @dev_root:	Default device to use as the parent.
 * @bus_groups:	Default attributes of the bus.
 * @dev_groups:	Default attributes of the devices on the bus.
 * @drv_groups: Default attributes of the device drivers on the bus.
 * @match:	Called, perhaps multiple times, whenever a new device or driver
 *		is added for this bus. It should return a positive value if the
 *		given device can be handled by the given driver and zero
 *		otherwise. It may also return error code if determining that
 *		the driver supports the device is not possible. In case of
 *		-EPROBE_DEFER it will queue the device for deferred probing.
 * @uevent:	Called when a device is added, removed, or a few other things
 *		that generate uevents to add the environment variables.
 * @probe:	Called when a new device or driver add to this bus, and callback
 *		the specific driver's probe to initial the matched device.
 * @remove:	Called when a device removed from this bus.
 * @shutdown:	Called at shut-down time to quiesce the device.
 *
 * @online:	Called to put the device back online (after offlining it).
 * @offline:	Called to put the device offline for hot-removal. May fail.
 *
 * @suspend:	Called when a device on this bus wants to go to sleep mode.
 * @resume:	Called to bring a device on this bus out of sleep mode.
 * @num_vf:	Called to find out how many virtual functions a device on this
 *		bus supports.
 * @pm:		Power management operations of this bus, callback the specific
 *		device driver's pm-ops.
 * @iommu_ops:  IOMMU specific operations for this bus, used to attach IOMMU
 *              driver implementations to a bus and allow the driver to do
 *              bus-specific setup
 * @p:		The private data of the driver core, only the driver core can
 *		touch this.
 * @lock_key:	Lock class key for use by the lock validator
 *
 * A bus is a channel between the processor and one or more devices. For the
 * purposes of the device model, all devices are connected via a bus, even if
 * it is an internal, virtual, "platform" bus. Buses can plug into each other.
 * A USB controller is usually a PCI device, for example. The device model
 * represents the actual connections between buses and the devices they control.
 * A bus is represented by the bus_type structure. It contains the name, the
 * default attributes, the bus' methods, PM operations, and the driver core's
 * private data.
 */
struct bus_type {
	const char		*name;
	const char		*dev_name;
	struct device		*dev_root;
	const struct attribute_group **bus_groups;
	const struct attribute_group **dev_groups;
	const struct attribute_group **drv_groups;

	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 (*online)(struct device *dev);
	int (*offline)(struct device *dev);

	int (*suspend)(struct device *dev, pm_message_t state);
	int (*resume)(struct device *dev);

	int (*num_vf)(struct device *dev);

	const struct dev_pm_ops *pm;

	const struct iommu_ops *iommu_ops;

	struct subsys_private *p;
	struct lock_class_key lock_key;
};

可以看到bus_type内还有一个比较重要的数据结构subsys_private,它定义于drivers/base/base.h:

/**
 * struct subsys_private - structure to hold the private to the driver core portions of the bus_type/class structure.
 *
 * @subsys - the struct kset that defines this subsystem
 * @devices_kset - the subsystem's 'devices' directory
 * @interfaces - list of subsystem interfaces associated
 * @mutex - protect the devices, and interfaces lists.
 *
 * @drivers_kset - the list of drivers associated
 * @klist_devices - the klist to iterate over the @devices_kset
 * @klist_drivers - the klist to iterate over the @drivers_kset
 * @bus_notifier - the bus notifier list for anything that cares about things
 *                 on this bus.
 * @bus - pointer back to the struct bus_type that this structure is associated
 *        with.
 *
 * @glue_dirs - "glue" directory to put in-between the parent device to
 *              avoid namespace conflicts
 * @class - pointer back to the struct class that this structure is associated
 *          with.
 *
 * This structure is the one that is the actual kobject allowing struct
 * bus_type/class to be statically allocated safely.  Nothing outside of the
 * driver core should ever touch these fields.
 */
struct subsys_private {
	struct kset subsys;
	struct kset *devices_kset;
	struct list_head interfaces;
	struct mutex mutex;

	struct kset *drivers_kset;
	struct klist klist_devices;
	struct klist klist_drivers;
	struct blocking_notifier_head bus_notifier;
	unsigned int drivers_autoprobe:1;
	struct bus_type *bus;

	struct kset glue_dirs;
	struct class *class;
};
#define to_subsys_private(obj) container_of(obj, struct subsys_private, subsys.kobj)

与bus相关的函数:

int __init buses_init(void)
{
	bus_kset = kset_create_and_add("bus", &bus_uevent_ops, NULL);
	if (!bus_kset)
		return -ENOMEM;

	system_kset = kset_create_and_add("system", NULL, &devices_kset->kobj);
	if (!system_kset)
		return -ENOMEM;

	return 0;
}	/* drivers/base/bus.c */

/**
 * driver_init - initialize driver model.
 *
 * Call the driver model init functions to initialize their
 * subsystems. Called early from init/main.c.
 */
void __init driver_init(void)
{
	/* These are the core pieces */
	devtmpfs_init();
	devices_init();
	buses_init();
	classes_init();
	firmware_init();
	hypervisor_init();

	/* These are also core pieces, but must come after the
	 * core core pieces.
	 */
	platform_bus_init();
	cpu_dev_init();
	memory_dev_init();
	container_dev_init();
	of_core_init();
}	/* drivers/base/init.c */

/**
 * bus_register - register a driver-core subsystem
 * @bus: bus to register
 *
 * Once we have that, we register the bus with the kobject
 * infrastructure, then register the children subsystems it has:
 * the devices and drivers that belong to the subsystem.
 */
int bus_register(struct bus_type *bus)
{
...
}	/* drivers/base/bus.c */
EXPORT_SYMBOL_GPL(bus_register);

/**
 * device_register - register a device with the system.
 * @dev: pointer to the device structure
 *
 * This happens in two clean steps - initialize the device
 * and add it to the system. The two steps can be called
 * separately, but this is the easiest and most common.
 * I.e. you should only call the two helpers separately if
 * have a clearly defined need to use and refcount the device
 * before it is added to the hierarchy.
 *
 * For more information, see the kerneldoc for device_initialize()
 * and device_add().
 *
 * NOTE: _Never_ directly free @dev after calling this function, even
 * if it returned an error! Always use put_device() to give up the
 * reference initialized in this function instead.
 */
int device_register(struct device *dev)
{
	device_initialize(dev);
	return device_add(dev);
}	/* drivers/base/core.c */
EXPORT_SYMBOL_GPL(device_register);

/**
 * driver_register - register driver with bus
 * @drv: driver to register
 *
 * We pass off most of the work to the bus_add_driver() call,
 * since most of the things we have to do deal with the bus
 * structures.
 */
int driver_register(struct device_driver *drv)
{
...
}	/* drivers/base/driver.c */
EXPORT_SYMBOL_GPL(driver_register);

当一个device与drivers匹配成功后,bus会先自动调用pinctrl_bind_pins函数将对应device的pinctrl设置为"default"状态,然后才自动调用drivers的probe函数:

/**
 * bus_probe_device - probe drivers for a new device
 * @dev: device to probe
 *
 * - Automatically probe for a driver if the bus allows it.
 */
void bus_probe_device(struct device *dev)
{
...
	if (bus->p->drivers_autoprobe)
		device_initial_probe(dev);
...
}	/* drivers/base/bus.c */

void device_initial_probe(struct device *dev)
{
	__device_attach(dev, true);
}	/* drivers/base/dd.c */

static int __device_attach(struct device *dev, bool allow_async)
{
...
		ret = bus_for_each_drv(dev->bus, NULL, &data,
					__device_attach_driver);
...
}	/* drivers/base/dd.c */

static int __device_attach_driver(struct device_driver *drv, void *_data)
{
...
	return driver_probe_device(drv, dev);
}	/* drivers/base/dd.c */

int driver_probe_device(struct device_driver *drv, struct device *dev)
{
...
	ret = really_probe(dev, drv);
...
}	/* drivers/base/dd.c */

static int really_probe(struct device *dev, struct device_driver *drv)
{
...
	/* If using pinctrl, bind pins now before probing */
	ret = pinctrl_bind_pins(dev);
...
	if (dev->bus->probe) {
		ret = dev->bus->probe(dev);
		if (ret)
			goto probe_failed;
	} else if (drv->probe) {
		ret = drv->probe(dev);
		if (ret)
			goto probe_failed;
	}
...
}	/* drivers/base/dd.c */

/**
 * pinctrl_bind_pins() - called by the device core before probe
 * @dev: the device that is just about to probe
 */
int pinctrl_bind_pins(struct device *dev)
{
...
	dev->pins->default_state = pinctrl_lookup_state(dev->pins->p,
					PINCTRL_STATE_DEFAULT);
...
}	/* drivers/base/pinctrl.c */

五、platform

platform虚拟总线继承自bus(也可以说是platform类型的bus),进一步实现了其功能,可以执行ls /sys/bus/platform命令查看platform虚拟总线下挂载了哪些设备和驱动,与bus相关的三个数据结构分别是bus_type、device、device_driver,而platform中与之对应的三个数据结构是platform_bus_type、platform_device、platform_driver。在platform_match函数中platform会优先根据设备树来匹配driver和device。platform相关的结构体和函数定义在include/linux/platform_device.h文件和drivers/base/platform.c文件:

struct bus_type platform_bus_type = {
	.name		= "platform",
	.dev_groups	= platform_dev_groups,
	.match		= platform_match,
	.uevent		= platform_uevent,
	.pm		= &platform_dev_pm_ops,
};
EXPORT_SYMBOL_GPL(platform_bus_type);

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);
	of_platform_register_reconfig_notifier();
	return error;
}

/**
 * platform_device_register - add a platform-level device
 * @pdev: platform device we're adding
 */
int platform_device_register(struct platform_device *pdev)
{
	device_initialize(&pdev->dev);
	arch_setup_pdev_archdata(pdev);
	return platform_device_add(pdev);
}
EXPORT_SYMBOL_GPL(platform_device_register);

/*
 * use a macro to avoid include chaining to get THIS_MODULE
 */
#define platform_driver_register(drv) \
	__platform_driver_register(drv, THIS_MODULE)
/**
 * __platform_driver_register - register a driver for platform-level devices
 * @drv: platform driver structure
 * @owner: owning module/driver
 */
int __platform_driver_register(struct platform_driver *drv,
				struct module *owner)
{
	drv->driver.owner = owner;
	drv->driver.bus = &platform_bus_type;
	drv->driver.probe = platform_drv_probe;
	drv->driver.remove = platform_drv_remove;
	drv->driver.shutdown = platform_drv_shutdown;

	return driver_register(&drv->driver);
}
EXPORT_SYMBOL_GPL(__platform_driver_register);

/**
 * platform_match - bind platform device to platform driver.
 * @dev: device.
 * @drv: driver.
 *
 * Platform device IDs are assumed to be encoded like this:
 * "<name><instance>", where <name> is a short description of the type of
 * device, like "pci" or "floppy", and <instance> is the enumerated
 * instance of the device, like '0' or '42'.  Driver IDs are simply
 * "<name>".  So, extract the <name> from the platform_device structure,
 * and compare it against the name of the driver. Return whether they match
 * or not.
 */
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);

	/* When driver_override is set, only bind to the matching driver */
	if (pdev->driver_override)
		return !strcmp(pdev->driver_override, drv->name);

	/* 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虚拟总线比bus多实现了两个功能,一个是在内核启动阶段把设备树中具有compatible属性的节点自动展开成了platform设备,一个是根据设备树来匹配driver和device。在此之前内核先调用了unflatten_device_tree函数将设备树文件转化成了节点类型为device_node的树形内核数据结构:

void __init setup_arch(char **cmdline_p)
{
...
	unflatten_device_tree();
...
}	/* arch/arm/kernel/setup.c */

/**
 * unflatten_device_tree - create tree of device_nodes from flat blob
 *
 * unflattens the device-tree passed by the firmware, creating the
 * tree of struct device_node. It also fills the "name" and "type"
 * pointers of the nodes so the normal device-tree walking functions
 * can be used.
 */
void __init unflatten_device_tree(void)
{
	__unflatten_device_tree(initial_boot_params, NULL, &of_root,
				early_init_dt_alloc_memory_arch, false);

	/* Get pointer to "/chosen" and "/aliases" nodes for use everywhere */
	of_alias_scan(early_init_dt_alloc_memory_arch);

	unittest_unflatten_overlay_base();
}	/* drivers/of/fdt.c */

然后内核会调用of_platform_populate函数把设备树中具有compatible属性的节点自动展开成了platform设备(内核先调用setup_machine_fdt函数根据设备树根节点的compatible属性的值找到内核中对应的machine_desc结构体,rest_init函数会调用带__init标记的customize_machine函数,在customize_machine函数内执行machine_desc结构体中的init_machine函数指针,在BeagleBone AI使用的am5729芯片上这个函数指针指向omap_generic_init函数,然后在omap_generic_init函数内调用的of_platform_populate函数):

/* init/main.c */
asmlinkage __visible void __init start_kernel(void)
{
...
    setup_arch(&command_line);
...
	/* Do the rest non-__init'ed, we're now alive */
	rest_init();
}

/* arch/arm/kernel/setup.c */
void __init setup_arch(char **cmdline_p)
{
...
	mdesc = setup_machine_fdt(__atags_pointer);
...
	machine_desc = mdesc;
...
	unflatten_device_tree();
...
}

/* arch/arm/kernel/setup.c */
static int __init customize_machine(void)
{
	/*
	 * customizes platform devices, or adds new ones
	 * On DT based machines, we fall back to populating the
	 * machine from the device tree, if no callback is provided,
	 * otherwise we would always need an init_machine callback.
	 */
	if (machine_desc->init_machine)
		machine_desc->init_machine();

	return 0;
}
arch_initcall(customize_machine);

/* arch/arm/mach-omap2/board-generic.c */
#ifdef CONFIG_SOC_DRA7XX
static const char *const dra74x_boards_compat[] __initconst = {
	"ti,dra762",
	"ti,am5728",
	"ti,am5726",
	"ti,dra742",
	"ti,dra7",
	NULL,
};
DT_MACHINE_START(DRA74X_DT, "Generic DRA74X (Flattened Device Tree)")
#if defined(CONFIG_ZONE_DMA) && defined(CONFIG_ARM_LPAE)
	.dma_zone_size	= SZ_2G,
#endif
	.reserve	= omap_reserve,
	.smp		= smp_ops(omap4_smp_ops),
	.map_io		= dra7xx_map_io,
	.init_early	= dra7xx_init_early,
	.init_late	= dra7xx_init_late,
	.init_irq	= omap_gic_of_init,
	.init_machine	= omap_generic_init,
	.init_time	= omap5_realtime_timer_init,
	.dt_compat	= dra74x_boards_compat,
	.restart	= omap44xx_restart,
MACHINE_END
...
#endif

/* arch/arm/mach-omap2/board-generic.c */
static void __init __maybe_unused omap_generic_init(void)
{
	pdata_quirks_init(omap_dt_match_table);

	omapdss_init_of();
	omap_soc_device_init();
}

/* arch/arm/mach-omap2/pdata-quirks.c */
void __init pdata_quirks_init(const struct of_device_id *omap_dt_match_table)
{
...
	of_platform_populate(NULL, omap_dt_match_table,
			     omap_auxdata_lookup, NULL);
...
}

/**
 * of_platform_populate() - Populate platform_devices from device tree data
 * @root: parent of the first level to probe or NULL for the root of the tree
 * @matches: match table, NULL to use the default
 * @lookup: auxdata table for matching id and platform_data with device nodes
 * @parent: parent to hook devices from, NULL for toplevel
 *
 * Similar to of_platform_bus_probe(), this function walks the device tree
 * and creates devices from nodes.  It differs in that it follows the modern
 * convention of requiring all device nodes to have a 'compatible' property,
 * and it is suitable for creating devices which are children of the root
 * node (of_platform_bus_probe will only create children of the root which
 * are selected by the @matches argument).
 *
 * New board support should be using this function instead of
 * of_platform_bus_probe().
 *
 * Returns 0 on success, < 0 on failure.
 */
int of_platform_populate(struct device_node *root,
			const struct of_device_id *matches,
			const struct of_dev_auxdata *lookup,
			struct device *parent)
{
	struct device_node *child;
	int rc = 0;

	root = root ? of_node_get(root) : of_find_node_by_path("/");
	if (!root)
		return -EINVAL;

	pr_debug("%s()\n", __func__);
	pr_debug(" starting at: %pOF\n", root);

	for_each_child_of_node(root, child) {
		rc = of_platform_bus_create(child, matches, lookup, parent, true);
		if (rc) {
			of_node_put(child);
			break;
		}
	}
	of_node_set_flag(root, OF_POPULATED_BUS);

	of_node_put(root);
	return rc;
}	/* drivers/of/platform.c */
EXPORT_SYMBOL_GPL(of_platform_populate);

of_platform_populate函数内会进一步调用of_platform_bus_create函数来创建platform_device,在创建platform_device之前会检查设备树节点是否具有compatible属性,如果没有compatible属性,那么就不创建这个设备树节点的platform_device:

/**
 * of_platform_bus_create() - Create a device for a node and its children.
 * @bus: device node of the bus to instantiate
 * @matches: match table for bus nodes
 * @lookup: auxdata table for matching id and platform_data with device nodes
 * @parent: parent for new device, or NULL for top level.
 * @strict: require compatible property
 *
 * Creates a platform_device for the provided device_node, and optionally
 * recursively create devices for all the child nodes.
 */
static int of_platform_bus_create(struct device_node *bus,
				  const struct of_device_id *matches,
				  const struct of_dev_auxdata *lookup,
				  struct device *parent, bool strict)
{
...
	/* Make sure it has a compatible property */
	if (strict && (!of_get_property(bus, "compatible", NULL))) {
		pr_debug("%s() - skipping %pOF, no compatible prop\n",
			 __func__, bus);
		return 0;
	}
...
}	/* drivers/of/platform.c */

leds-gpio.c中有如下与platform相关的部分:

...
static const struct of_device_id of_gpio_leds_match[] = {
	{ .compatible = "gpio-leds", },
	{},
};

MODULE_DEVICE_TABLE(of, of_gpio_leds_match);

static int gpio_led_probe(struct platform_device *pdev)
{
	struct gpio_led_platform_data *pdata = dev_get_platdata(&pdev->dev);
...
	platform_set_drvdata(pdev, priv);

	return 0;
}

static void gpio_led_shutdown(struct platform_device *pdev)
{
	struct gpio_leds_priv *priv = platform_get_drvdata(pdev);
...
}

static struct platform_driver gpio_led_driver = {
	.probe		= gpio_led_probe,
	.shutdown	= gpio_led_shutdown,
	.driver		= {
		.name	= "leds-gpio",
		.of_match_table = of_gpio_leds_match,
	},
};

module_platform_driver(gpio_led_driver);
...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值