一、前言
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);
...