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

GPIO子系统实现LED操作,支持sys文件系统开关LED exynos4412,我们的LED是基于 GPIO的,为此,内核有两个对应的驱动程序,分别时GPIO驱动和LED驱动,基于GPIO的LED的驱动调用了GPIO驱动导出的函数,这一节我们并不关心GPIO的驱动(后面会有详细说明)。关于LED驱动,内核文档Documentation/leds/leds-class.txt有简单的描述,它实现了一个leds类,通过sysfs的接口才对LED进行控制,所以它并没有使用字符设备驱动的框架。 阅读详情

一、前言

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);
...
power_supply 探究设备 关系 makefile: obj-$(CONFIG_MTK_CHARGER) += charger_class.o mtk_basic_charger.o mtk_charger.o mtk_charger_algorithm_class.o mtk_charger_intf.o adapter_class.o mtk_pd_adapter.o mtk_pd.o mtk_pd_hal.o mtk_pulse_charger.o obj-$(CONFIG_CHARGER_TYPE_MT6357)... 阅读详情

相关推荐

树莓派linux led字符设备驱动( linux自带)

  树莓派的Linux 内核已经集成了LED 灯驱动。 Linux 内核的 LED 灯驱动采用 platform 框架。编译树莓派linux内核时,会输入配置命令构建配置linux内核,如: cd linux KERNEL=kernel7l make bcm2711_defconfig   linux内核的makefile会从arch/arm/configs 目录中寻找默认配置文件: bcm2711_defconfig,配置完成后会生成.config 文件。打开.config 文件,有“CONFIG_L

123 1万+

linux驱动_sigmastar_SSD202D_leds-gpio

修改linux内核驱动实现gpio闪灯效果

gavinpeng的专栏 1971

Linux设备驱动模型核心机制解析:从kobject到platform驱动

操作系统管理种类繁多的硬件设备,依赖一套统一的设备驱动模型作为核心骨架。该模型通过kobject/kset对象体系、sysfs虚拟文件系统以及device/driver/bus三大支柱,实现了设备资源的统一注册、匹配与生命周期管理。理解这套机制,开发者不仅能掌握驱动与设备自动绑定的原理,还能在热插拔、电源管理等复杂场景下快速定位问题。平台总线(platform bus)与设备树(Device Tree)让SoC内部控制器等非枚举设备也能融入标准框架,是嵌入式Linux驱动开发的高频实践领域。无论是编写字符设

weixin_34306593的博客 208

linux/drivers/leds/leds-gpio.c学习

`gpio_led_suspend()` 和 `gpio_led_resume()`:这两个函数用于在系统挂起和恢复时处理 GPIO LED 的状态。其中最重要的结构体是 `struct gpio_led`,它包含了 GPIO LED 的名称、GPIO 引脚号、默认亮度等信息。1. 头文件引入:该文件引入了一些必要的头文件,包括 ``、``、`` 等。

__Benco的博客 1027

【4】gpio

导出GPIO: (linux内核自带)执行完以上命令后,如果该gpio接口存在且未被占用则会出现如下目录:/sys/class/gpio/gpio943# 设置方向:in:表示该gpio用于输入。(如该gpio连接一个按钮, in时get value合理 )out:表示该gpio用于输出。(如该gpio连接一个led灯, out才能set value, out能get value,但是哪边gpio强就是哪边的值,所以out时get value不合理)

持续更新ing... 921

ComfyUI中一个超好用的变量管理类节点-KJNodes

在AI绘图中,ComfyUI的热度开始慢慢起来一些,但是这个软件在使用的过程中,需要很多连线,仅仅一个最简单的动画生成,就需要连接这么多线条,而且有些线会飞的很远,看起来就很不美观,尤其是一些复杂工作流,连接到最后自己可能已经分不太清楚到底连接好了没有。今天在研究C站的大佬们的工作流的时候,发现一个有意思的工作流,看起来大概是这种感觉:干净整洁。他用到了KJNodes这么个节点来管理变量,git上星标也不算很多,所以我想很多朋友可能还不知道,所以这里简单介绍下用法。

聚梦小课堂 7518

从leds-gpio.c看懂Linux设备模型:platform总线与class子系统实战

Linux设备驱动模型是内核开发的核心,它将硬件设备、驱动与总线解耦,通过匹配机制完成自动绑定。platform总线作为虚拟总线,承接SoC内部无物理总线的外设,而class子系统则按功能将设备分类暴露到sysfs,使用户空间可以直观操作。设备树负责描述硬件,内核将其转换为platform_device,再通过compatible属性与驱动匹配,触发probe回调。leds-gpio.c正是这一流程的微型样板,融合了GPIO、设备树、platform_driver和led_classdev,从设备树节点到/

weixin_29185493的博客 199

人工智能100问☞第5问:什么是AI三要素?

人工智能的核心三要素是​​数据、算法和算力​​,分别作为AI的“燃料”、“大脑”和“动力”,三者协同驱动智能系统的学习、推理与决策能力。

bjdx_001的博客 1991

从leds-gpio.c看懂Linux设备模型:总线、类与平台驱动的协作

Linux内核的设备模型是驱动开发的根基,总线(bus)、类(class)与平台驱动(platform_driver)的协作机制则是其中最关键脉络。无论是GPIO控制、LED点灯还是SoC外设驱动,都遵循“设备-总线-驱动”的匹配框架。文章以leds-gpio.c源码为例,从设备树解析、platform总线匹配、probe回调执行,到class接口在sysfs中的呈现,完整串联起驱动加载与用户空间交互的数据流。理解这套逻辑,能快速定位驱动未加载、probe未调用、brightness写入无效等典型问题。结合

weixin_42623010的博客 185

从专利库到Zemax:一个6mm定焦镜头从零到交付的完整设计流程(含CodeV转换技巧)

本文详细介绍了6mm定焦镜头从专利库检索到最终交付的全流程设计,重点解析了CodeV与Zemax工具链的无缝衔接技巧。通过实际案例展示规格定义、专利筛选、结构适配、非球面优化等关键步骤,帮助光学设计工程师提升设计效率和制造良率。特别包含CodeV转换Zemax的实用技巧,适用于机器视觉和安防镜头开发。

weixin_42561040的博客 160

Opencv全景图像处理实战:柱面与透视展开技术详解

本文详细解析了使用OpenCV实现全景图像处理的两种核心技术:柱面展开与透视展开。通过原理拆解与实战代码演示,指导开发者将360度环绕的鱼眼或球面图像转换为可用的平面视图,适用于街景展示、车载环视、室内测量等计算机视觉应用场景。

weixin_29081163的博客 582

理解Linux 自带的 LED 灯驱动

LInux内核已自带了LED灯驱动,LED灯驱动采用platform框架 ,只需在设备树文件添加相应的LED节点。修改完后为避免编译依赖一致性问题执行一下make olddefconfig,然后就可以编译内核了。LED灯驱动文件是/driver/leds/leds-gpio.c,Makefile文件中可见leds-gpio.c文件会编译进内核。所以编写设备树的led设备节点的compatible属性值应为“gpio-leds”。

lorgenlorgen的专栏 193

魔改xjar支持springboot3,

其实这里基本就没问题了,实际项目中只要把这个spring-core包从私仓中替换,项目加密也就没有太大的毛病,但是这样对于追求完美的我来说,方便性还差点。原因找到了,看了下springboot github的issue,官方确实变更了,原因是因为springboot2中的。xjar项目文档还是不错的,clone下来,跟着文档,很容易就测试并通过了我的测试项目,于是便推给其他同事使用了。方法,那么问题来了,为啥springboot2没问题,spring3读取出来的却又是加密的,这是为毛?

jioulongzi的专栏 1536

Linux Platform Bus原理与实战:bus/dev/drv三位一体框架

总线(Bus)是操作系统设备管理的核心抽象,本质是一套软硬协同的通信契约与资源调度机制,而非物理连线。其核心原理在于通过标准化的匹配、资源仲裁和电源状态机,解决裸机开发中常见的资源冲突、驱动绑定混乱与热插拔失控问题。Platform Bus作为Linux中最广泛使用的内存映射型总线模型,以struct bus_type、struct device和struct device_driver构成稳定三角,支撑即插即用、可追溯、可复用的驱动工程实践。它广泛应用于嵌入式系统、工业网关、SoC外设管理等场景,是理解L

weixin_34355559的博客 381

GDB交叉编译与问题解决:从零到精通的深度指南

完成后,GDB会安装到/usr/local/gdb-aarch64/bin,可以用aarch64-linux-gnu-gdb --version检查。:得有支持目标架构的交叉编译工具链,比如gcc-arm-none-eabi(用于ARM裸机)或aarch64-linux-gnu-gcc(用于ARM Linux)。:在配置工具链时,运行aarch64-linux-gnu-gcc --version检查版本,确保工具链没问题。这种场景下用GDB调试,就像在荒野求生,但别慌,GDB依然是你的好伙伴。

大模型大数据攻城狮的专栏 1万+

HAL STM32+EC11编码器实现增减调节及单击、双击、长按功能

HAL STM32+EC11编码器实现增减调节及单击、双击、长按功能

perseverance51博客 4231

Linux驱动入门:GPIO LED点灯与platform驱动框架实战

嵌入式Linux系统中,设备驱动是连接硬件与应用程序的桥梁。现代Linux驱动采用设备、驱动、总线分离的模型,设备树负责描述硬件资源,platform驱动通过compatible属性完成设备匹配,字符设备则通过file_operations向用户态提供统一的操作接口。GPIO子系统提供了标准化的API,让开发者无需直接操作物理寄存器,通过逻辑电平即可控制引脚。GPIO LED点灯作为最经典的入门案例,虽逻辑简单,却能完整串联设备树解析、platform总线匹配、字符设备注册、应用层交互等一整条驱动开发链路,

weixin_34199335的博客 300

GD32F303标准库开发:Keil5新建GD32标准库工程

GD32F303标准库开发:Keil5新建GD32标准库工程

MQ0522的博客 8898

Linux GPIO子系统深度解析:从设备树到驱动开发与libgpiod实践

GPIO(通用输入输出)是嵌入式系统与物理世界交互的基础接口,其核心原理是通过软件控制硬件引脚的电平状态,实现数字信号的输入与输出。在Linux系统中,GPIO子系统作为内核框架,将不同芯片厂商的硬件操作抽象为统一API,为驱动和应用开发者提供了标准化的访问方式,其技术价值在于实现了硬件操作的抽象化、安全性与可移植性。该技术广泛应用于LED控制、按键检测、传感器读取等嵌入式场景。本文聚焦于Linux环境下GPIO的现代操作方法,深入探讨了设备树(Device Tree)中的资源定义、GPIO描述符(gpio

weixin_33908217的博客 291

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

Linux设备驱动模型:从kobject到platform驱动的内核级解析

Linux设备驱动模型是内核实现硬件抽象与资源管理的核心机制,其本质是基于kobject的面向对象式架构,通过bus-device-driver-class四层继承关系组织设备生命周期。该模型支撑devicetree动态匹配、sysfs实时映射、uevent事件分发及引用计数(kref)内存安全等关键能力。它不仅是字符设备、platform驱动开发的基础,更是嵌入式BSP适配、国产SoC(如RK3566、H616)外设移植、ACPI/DTS双轨兼容的前提。理解其原理,才能真正调试probe失败、sysfs缺

weixin_33906657的博客 413

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

Linux设备驱动模型核心原理:kobject、bus、device与driver协同机制

Linux设备驱动模型是内核实现硬件抽象与驱动管理的底层框架,其本质是一套基于对象生命周期、引用计数和分层匹配的元数据管理体系。核心依托kobject作为统一身份与资源管理单元,通过kset组织同类对象,由ktype定义行为契约,并经sysfs实时映射至用户空间。该模型支撑bus(如platform_bus)完成device与driver的自动匹配、绑定与解绑,实现热插拔、uevent通知及跨总线class统一接口。理解其原理,是调试ch340串口驱动加载失败、stlink识别异常、probe无响应等高频问

weixin_34021089的博客 1504

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

Linux内核总线机制:从platform bus到自定义总线实战

在Linux驱动开发中,总线(Bus)并非物理线路,而是设备与驱动之间动态匹配的软件契约机制。其核心原理是通过设备树compatible属性与驱动of_match_table进行声明式匹配,由bus_type的match回调触发probe初始化流程,实现硬件资源抽象与驱动复用。该机制显著提升代码可移植性,支撑多平台适配与模块化开发。典型应用场景包括嵌入式SoC外设管理、FPGA IP核集成及专用协处理器驱动架构设计。本文深入解析platform bus运行范式,并手把手演示如何安全创建自定义总线,覆盖mat

weixin_34242509的博客 595

50 使用linux内核源码里的led驱动<LED Support for GPIO connected LEDs>

这个设备驱动适用于,连接到一个IO口的led灯. 需要在linux内核配置里选上相关的配置。在内核源码目录下: make menuconfig ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- Device Drivers ---> [*] LED Support ---> <*> LED Class

jklinux的博客 3254

Linux设备树与platform_driver驱动开发实战:从硬件解耦到可移植设计

在嵌入式Linux开发中,硬件与软件的解耦是提升驱动可移植性和维护性的核心挑战。传统驱动开发常将GPIO编号、中断号等硬件信息硬编码在代码中,导致每更换硬件平台就需修改驱动,效率低下且容易出错。设备树(Device Tree)作为一种硬件描述机制,通过将硬件配置信息从内核代码中剥离,以结构化数据形式在启动时传递给内核,实现了硬件描述的标准化和动态化。结合platform_driver框架,驱动可通过compatible属性与设备树节点自动匹配,并在probe函数中动态解析GPIO等资源,从而构建出与具体硬件

weixin_30642869的博客 490

深入理解Linux设备树与驱动匹配机制:从语法到platform总线实战

嵌入式Linux开发中,硬件资源描述与驱动匹配是核心问题。设备树(Device Tree)作为描述硬件资源的标准化机制,将硬件信息与内核代码解耦,驱动通过compatible属性与设备节点匹配,实现自动probe。GPIO、中断、时钟等资源均可通过设备树节点描述,结合platform总线驱动模型,开发者无需修改内核即可适配新硬件。本文从设备树语法、资源描述、驱动解析入手,讲解compatible匹配、GPIO控制、中断申请等实战细节,并分享常见问题排查技巧,帮助读者理解设备树与驱动的协作关系,快速上手嵌入式

weixin_33895657的博客 384

Linux内核设备驱动模型核心原理与实战解析

设备驱动模型是Linux内核资源组织与生命周期管理的元规则,其本质是通过device、driver、bus三类结构体构建的动态匹配状态机。它基于kobject和sysfs实现硬件抽象与用户空间交互,依托match/bind/probe机制完成声明式绑定,并深度整合电源管理(PM)、热插拔(hotplug)和class分类等子系统。该模型决定了驱动加载顺序、probe触发条件、uevent事件生成及/sys接口暴露逻辑,是理解platform驱动、设备树匹配、DMA映射失败等典型问题的底层基础。掌握其注册链路

ciya3282的博客 355
上一篇: Linux开发小贴士
下一篇: 以leds-gpio.c为例,浅谈device-tree、pinctrl和GPIO子系统
Charming Sun
博客等级 码龄11年 212粉丝 36原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值