This article introduces the concept, advantages, and underlying data structures of the Linux device model. Addressing the limitations of character devices in complex functions such as power management and hot-plugging, the Linux kernel introduced a device model inspired by object-oriented thinking. By providing generic APIs, it achieves code reuse, dynamic resource management, hot-plug support, and simplified driver development. It also elaborates on the mechanisms and APIs of kobject and kset, the fundamental objects used to represent kernel entities and build the sysfs hierarchy.
Character device drivers are typically suitable for relatively simple devices. For more complex functions, such aspower managementandhot-plug event management, using the character device framework may not be flexible or efficient enough.
To handle more complex devices and functions, the Linux kernel provides the device model. The device model allows developers to describe hardware devices and their relationships in a higher-level manner, and provides a set of generic APIs and mechanisms for device registration, hot-plug event management, power management, and more.
By using the device model, driver developers can delegate more low-level functions to the kernel for handling, without having to re-implement these basic functionalities. This makes driver development more advanced and modular, reducing repetitive work and the likelihood of errors.
For some common hardware devices, such as USB, I2C, and platform devices, the kernel already provides corresponding device models and related drivers. Developers can write drivers based on these models to more quickly implement the functionality of specific devices, and can leverage the kernel’s power management and hot-plug event management features.。
Advantages of Using the Device Model
The device model plays an important role in kernel drivers, providing a unified way to describe hardware devices and their relationships. The following are several important aspects of the device model in kernel drivers.
Code Reuse:
The device model allows multiple devices to reuse the same driver. By defining different device nodes in the device tree or on the bus, these devices can be initialized and managed using the same driver. This reduces code redundancy and improves driver reusability and maintainability.
Dynamic Allocation and Release of Resources
The device model provides a mechanism todynamically allocate and release resources required by devices, such as memory and interrupts. Drivers can use these mechanisms to manage the resources required by devices, ensuring correct resource allocation and release during device initialization and shutdown.
Simplifying Driver Writing
The device model provides a set of generic APIs and mechanisms, making driver writing more simplified and modular. Developers can use these APIs to register devices, handle device events, perform read and write operations on devices, and more, without having to re-implement these common functionalities.
Hot-Plug Mechanism
The device model supports the hot-plug mechanism, which can dynamically add or remove devices at runtime. When a device is inserted or removed, the kernel generates corresponding hotplug events, and drivers can listen to these events to perform corresponding operations, such as device initialization or release.
Object-oriented thinking in drivers
The design of the device model draws on the ideas of object-oriented programming (OOP).Each device is regarded as an object with its own attributes and methods, and can be inherited and extended through the mechanisms of the device model. This design makes driver writing more modular and extensible, better able to handle different types of devices and functional requirements.
kobject and kset
kobjectandksetare basic concepts in the Linux kernel for managing kernel objects.
struct kobject
kobject(kernel object) is a generic object model abstracted from the kernel,used to represent various entities in the kernel。kobjectis a structure that contains some attributes and methods describing the object. It provides a unified interface and mechanism for managing and operating kernel objects.
const char *name: Represents the name of the kobject, usually used to create a corresponding directory under the/sysdirectory
struct list_head entry: Used to link the kobject into the parent kobject’s child object list to establish a hierarchical relationship.
struct kobject *parent: Points to the parent kobject, indicating the hierarchical relationship of the kobject.
struct kset *kset: Points to the kset containing this kobject, used for further organizing and managing kobjects.
struct kobj_type *ktype: Points to the kobj_type structure that defines the kobject type,describing the attributes and operations of the kobject.。
struct kernfs_node *sd: Points to the correspondingkernfs_nodein the sysfs directory, used to access and manipulate sysfs directory entries.
struct kref kref:Used for reference counting of the kobject, ensuring proper resource release when no longer in use.。
unsigned intfield: Represents some status flags and configuration options, such as whether it is initialized, whether it is in sysfs, whether add/remove uevent events have been sent, etc.
Each kobject corresponds to a directory under the system/sys/directory
sys directory
Because a kobject represents a directory under the system/sysdirectory, and directories have multiple levels, the tree-like relationship of corresponding kobjects is shown in the following figure
MODULE_LICENSE("GPL"); MODULE_AUTHOR("even629<asqwgo@outlook.com>"); MODULE_DESCRIPTION("This is a test sample for kboject");
Test:
1 2 3 4 5 6 7
$ insmod kobject_test.ko [ 12.622003] kobject_test: loading out-of-tree module taints kernel. $ ls /sys/ block class devices fs module mykobject03 bus dev firmware kernel mykobject01 power $ ls /sys/mykobject01 mykobject02
struct kset
**kset(kernel object set) is a container used to organize and manage a group of relatedkobject**container.
ksetiskobjectan extension of , which provides a hierarchical organizational structure that can group a set of relatedkobjecttogether.ksetIn the kernel, it is represented by thestruct ksetstructure
// include/linux/kobject.h /** * struct kset - a set of kobjects of a specific type, belonging to a specific subsystem. * * A kset defines a group of kobjects. They can be individually * different "types" but overall these kobjects all want to be grouped * together and operated on in the same manner. ksets are used to * define the attribute callbacks and other common events that happen to * a kobject. * * @list: the list of all kobjects for this kset * @list_lock: a lock for iterating over the kobjects * @kobj: the embedded kobject for this kset (recursion, isn't it fun...) * @uevent_ops: the set of uevent operations for this kset. These are * called whenever a kobject has something happen to it so that the kset * can add new environment variables, or filter out the uevents if so * desired. */ structkset { structlist_headlist; spinlock_t list_lock; structkobjectkobj; conststructkset_uevent_ops *uevent_ops; } __randomize_layout;
struct list_head list: All kobjects of this kset are connected through akobject.entrylinked list.
spinlock_t list_lock: Used to protect iterative access to the kobject linked list, ensuring thread safety.
struct kobject kobj: Askobject representation of kset, used to create a corresponding directory under the/sysdirectory and associate it with the kset.
const struct kset_uevent_ops *uevent_ops:Structure pointing to the uevent operations of the kset, used to handle uevent events related to the kset(hotplug related).
The relationship between kobject and kset is as follows:
Diagram of the relationship between kobject and kset
kset is an extension of kobject
kset can be seen as a special form of kobject, which extends kobject and provides some additional functionality.A kset can contain multiple kobjects, forming a hierarchical organizational structure。
A kobject belongs to a kset
Each kobject belongs to a kset. Thestruct kset *ksetfield in the kobject structure points to the kset it belongs to. This association indicates the collection or organization to which the kobject belongs.
Summary: The relationship between kset and kobject is:A kset can contain multiple kobjects, while a kobject can only belong to one kset.。
The kset provides collection management and operation interfaces for kobjects, used to organize and manage kobjects with similar characteristics or relationships. This relationship allows the kernel to manage and operate different types of kernel objects in a unified manner.
$ insmod kset_test.ko [ 375.127833] kset_test: loading out-of-tree module taints kernel. $ cd sys /sys $ ls block class devices fs module power bus dev firmware kernel mykset /sys $ cd mykset/ /sys/mykset $ ls mykobject01 /sys/mykset $ cd mykobject01/ /sys/mykset/mykobject01 $ ls mykobject02 /sys/mykset/mykobject01 $ cd mykobject02/ /sys/mykset/mykobject01/mykobject02 $ ls /sys/mykset/mykobject01/mykobject02 $ cd / $ rmmod kset_test.ko $ lsmod $ cd sys /sys $ ls block class devices fs module bus dev firmware kernel power
// include/linux/types.h typedefstruct { int counter; } atomic_t;
When using a reference counter, it is common toembed the kref structure into other structures, for examplestruct kobject, to achieve reference count management.
To implement reference counting functionality,struct kobjectit usually contains an embeddedstruct krefobject. This allows managing the reference count ofstruct krefthrough operations onstruct kobjectto manage the reference count, and release related resources when the reference count drops to 0. For examplestruct device_node:
Reference count becomes0→ Call callbackrelease(kref)to perform actual destruction.
Return 1 indicates the reference count becomes 0 and destruction is performed.
Return 0 indicates the reference count > 0, the object is still valid.
refcount_set()
Function: Set the underlying atomic reference count value.kref_init()internally calls this function.
Function prototype
1 2 3 4
staticinlinevoidrefcount_set(refcount_t *r, int n) { atomic_set(&r->refs, n); }
How is Kobj released
There are two methods to create a kobject:
Usingkobject_create_and_add()function to create a kobject:kobject_create_and_add()The function first callskobject_create()function, which useskzalloc()to allocate memory space for the kobject. In thekobject_create()function, it callskobject_init()function to initialize the allocated memory and specify the default ktype. Next,kobject_create_and_add()the function callskobject_add()function to add the kobject to the system, making it visible.kobject_add()The function internally callskobject_add_internal()function, which is responsible for adding the kobject to the parent object’s child list and creating the corresponding sysfs filesystem entries.
Usingkobject_init_and_add()function to create a kobject:kobject_init_and_add()The function needs to manually allocate memory and, throughkobject_init()the function initializes the allocated memory. At this point, you need to implement the ktype structure yourself. After initialization, callkobject_add()the function to add the kobject to the system.
Regardless of the method, ultimately usekobject_put()to release the kobject
if (t && !t->release) pr_debug("kobject: '%s' (%p): does not have a release() function, it is broken and must be fixed. See Documentation/core-api/kobject.rst.\n", kobject_name(kobj), kobj);
/* remove from sysfs if the caller did not do it */ if (kobj->state_in_sysfs) { pr_debug("kobject: '%s' (%p): auto cleanup kobject_del\n", kobject_name(kobj), kobj); __kobject_del(kobj); } else { /* avoid dropping the parent reference unnecessarily */ parent = NULL; }
/* free name if we allocated it */ if (name) { pr_debug("kobject: '%s': free name\n", name); kfree_const(name); }
kobject_put(parent); }
Inside the function, a pointer tostruct kobj_typethe structure t is defined to obtain the type information of kobj
If t exists butt->releaseis NULL, it means the type of kobj does not define a release function, and a debug message will be printed to indicate this situation.
Then, check the state variable of kobjstate_in_sysfs. If true, it means the caller has not removed kobj from sysfs, and it will automatically callkobject_del()the function to remove it from sysfs.
Next, check again if t exists and checkt->releaseexists. If it does, it indicates that the type of kobj defines a release function, which will be called to clean up resources.
kobject_cleanup()The implementation of the function shows that,the release function ultimately called is defined in the kobj_type structure.。 This explains why when using thekobject_init_and_add()function,struct kobj_typethe structure cannot be empty.
The device model includes the following four concepts:
Bus: The bus is a fundamental component in the device model,a communication channel for connecting and transmitting data.。**The bus can be a physical bus (such as PCI, USB) or a virtual bus (such as a virtual device bus).**The bus provides the basic mechanism for communication and data transmission between devices.
Device:A device refers to a hardware component in a computer system, such as a network card, monitor, or keyboard. Each device has a unique identifier used for identification and management within the system. The device model describes the attributes and characteristics of a device through a device descriptor.
Driver:A driver is a software component in the device model used to control and manage the operation of a device. Each device requires a corresponding driver to interact and communicate with the operating system. The driver is responsible for sending commands to the device, receiving device events, performing device configuration, and other operations.
Class: A class is alogical organizational unitin the device model, used toclassify and manage devices with similar functions and characteristics. A class defines a collection of devices that share the same attributes and behaviors. Through device classes, devices can be grouped, identified, and accessed.
In the Linux device model, a fictitious bus named “platform” is used to connect some device controllers that are directly attached to the CPU. These device controllers typically do not conform to common bus standards, such as PCI bus and USB bus, so Linux uses the platform bus to manage these devices.
1. Platform The bus allows device controllers to communicate and interact with device drivers
2. Platform The device controller is defined in the device tree.,And it is matched with the corresponding device driver through the device tree.
In the device model, the Platform bus provides a unified interface and mechanism to register and manage these device controllers. Device drivers can bind and communicate with the corresponding device controllers by registering with the Platform bus. Device drivers can access the device controller’s registers, configure the device, handle interrupts, and perform other operations, as shown in the following diagram:
Platform bus
struct bus_type
The bus_type structure is a data structure in the Linux kernel used to describe a bus.
/** * 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. * @sync_state: Called to sync device state to software state after all the * state tracking consumers linked to this device (present at * the time of late_initcall) have successfully bound to a * driver. If the device has no consumers, this function will * be called at late_initcall_sync level. If the device has * consumers that are never bound to a driver, this function * will never get called until they do. * @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. * @dma_configure: Called to setup DMA configuration on a device on * this bus. * @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 * @need_parent_lock: When probing or removing a device on this bus, the * device core should lock the device's parent. * * 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. */ structbus_type { constchar *name; //Name of the bus type constchar *dev_name;//Bus device name structdevice *dev_root;//Root device of the bus device conststructattribute_group **bus_groups;//Attribute group of the bus type conststructattribute_group **dev_groups;//Attribute group of the device conststructattribute_group **drv_groups;//Attribute group of the driver
int (*match)(struct device *dev, struct device_driver *drv);//Match function between device and driver int (*uevent)(struct device *dev, struct kobj_uevent_env *env);//Event handler function of the device int (*probe)(struct device *dev);//Probe function of the device void (*sync_state)(struct device *dev);//Device state synchronization function int (*remove)(struct device *dev);//Device removal function void (*shutdown)(struct device *dev);
int (*online)(struct device *dev);//Device online function int (*offline)(struct device *dev);//Device offline function
int (*suspend)(struct device *dev, pm_message_t state);//Device suspend function int (*resume)(struct device *dev);//Device resume function
int (*num_vf)(struct device *dev);//Device virtual function count function
int (*dma_configure)(struct device *dev);//Device DMA configuration function
conststructdev_pm_ops *pm;// Device power management operations
// include/linux/device.h /** * struct device - The basic device structure * @parent: The device's "parent" device, the device to which it is attached. * In most cases, a parent device is some sort of bus or host * controller. If parent is NULL, the device, is a top-level device, * which is not usually what you want. * @p: Holds the private data of the driver core portions of the device. * See the comment of the struct device_private for detail. * @kobj: A top-level, abstract class from which other classes are derived. * @init_name: Initial name of the device. * @type: The type of device. * This identifies the device type and carries type-specific * information. * @mutex: Mutex to synchronize calls to its driver. * @lockdep_mutex: An optional debug lock that a subsystem can use as a * peer lock to gain localized lockdep coverage of the device_lock. * @bus: Type of bus device is on. * @driver: Which driver has allocated this * @platform_data: Platform data specific to the device. * Example: For devices on custom boards, as typical of embedded * and SOC based hardware, Linux often uses platform_data to point * to board-specific structures describing devices and how they * are wired. That can include what ports are available, chip * variants, which GPIO pins act in what additional roles, and so * on. This shrinks the "Board Support Packages" (BSPs) and * minimizes board-specific #ifdefs in drivers. * @driver_data: Private pointer for driver specific info. * @links: Links to suppliers and consumers of this device. * @power: For device power management. * See Documentation/driver-api/pm/devices.rst for details. * @pm_domain: Provide callbacks that are executed during system suspend, * hibernation, system resume and during runtime PM transitions * along with subsystem-level and driver-level callbacks. * @em_pd: device's energy model performance domain * @pins: For device pin management. * See Documentation/driver-api/pinctl.rst for details. * @msi_list: Hosts MSI descriptors * @msi_domain: The generic MSI domain this device is using. * @numa_node: NUMA node this device is close to. * @dma_ops: DMA mapping operations for this device. * @dma_mask: Dma mask (if dma'ble device). * @coherent_dma_mask: Like dma_mask, but for alloc_coherent mapping as not all * hardware supports 64-bit addresses for consistent allocations * such descriptors. * @bus_dma_limit: Limit of an upstream bridge or bus which imposes a smaller * DMA limit than the device itself supports. * @dma_range_map: map for DMA memory ranges relative to that of RAM * @dma_parms: A low level driver may set these to teach IOMMU code about * segment limitations. * @dma_pools: Dma pools (if dma'ble device). * @dma_mem: Internal for coherent mem override. * @cma_area: Contiguous memory area for dma allocations * @archdata: For arch-specific additions. * @of_node: Associated device tree node. * @fwnode: Associated device node supplied by platform firmware. * @devt: For creating the sysfs "dev". * @id: device instance * @devres_lock: Spinlock to protect the resource of the device. * @devres_head: The resources list of the device. * @knode_class: The node used to add the device to the class list. * @class: The class of the device. * @groups: Optional attribute groups. * @release: Callback to free the device after all references have * gone away. This should be set by the allocator of the * device (i.e. the bus driver that discovered the device). * @iommu_group: IOMMU group the device belongs to. * @iommu: Per device generic IOMMU runtime data * @removable: Whether the device can be removed from the system. This * should be set by the subsystem / bus driver that discovered * the device. * * @offline_disabled: If set, the device is permanently online. * @offline: Set after successful invocation of bus type's .offline(). * @of_node_reused: Set if the device-tree node is shared with an ancestor * device. * @state_synced: The hardware state of this device has been synced to match * the software state of this device by calling the driver/bus * sync_state() callback. * @dma_coherent: this particular device is dma coherent, even if the * architecture supports non-coherent devices. * @dma_ops_bypass: If set to %true then the dma_ops are bypassed for the * streaming DMA operations (->map_* / ->unmap_* / ->sync_*), * and optionall (if the coherent mask is large enough) also * for dma allocations. This flag is managed by the dma ops * instance from ->dma_supported. * * At the lowest level, every device in a Linux system is represented by an * instance of struct device. The device structure contains the information * that the device model core needs to model the system. Most subsystems, * however, track additional information about the devices they host. As a * result, it is rare for devices to be represented by bare device structures; * instead, that structure, like kobject structures, is usually embedded within * a higher-level representation of the device. */ structdevice {
structkobjectkobj;//Corresponding kobj structdevice *parent;// Parent device of the device
structdevice_private *p;// Private pointer
constchar *init_name; /* initial name of the device *///Device initialization name conststructdevice_type *type;
structbus_type *bus;/* type of bus device is on *///Bus to which the device belongs structdevice_driver *driver;/* which driver has allocated this device */ void *platform_data; /* Platform specific data, device core doesn't touch it */ void *driver_data; /* Driver data, set and get with dev_set_drvdata/dev_get_drvdata */ #ifdef CONFIG_PROVE_LOCKING structmutexlockdep_mutex; #endif structmutexmutex;/* mutex to synchronize calls to * its driver. */
#ifdef CONFIG_NUMA int numa_node; /* NUMA node this device is close to */ #endif dev_t devt; /* dev_t, creates the sysfs "dev" */ u32 id; /* device instance */
// include/linux/device/driver.h /** * struct device_driver - The basic device driver structure * @name: Name of the device driver. * @bus: The bus which the device of this driver belongs to. * @owner: The module owner. * @mod_name: Used for built-in modules. * @suppress_bind_attrs: Disables bind/unbind via sysfs. * @probe_type: Type of the probe (synchronous or asynchronous) to use. * @of_match_table: The open firmware table. * @acpi_match_table: The ACPI match table. * @probe: Called to query the existence of a specific device, * whether this driver can work with it, and bind the driver * to a specific device. * @sync_state: Called to sync device state to software state after all the * state tracking consumers linked to this device (present at * the time of late_initcall) have successfully bound to a * driver. If the device has no consumers, this function will * be called at late_initcall_sync level. If the device has * consumers that are never bound to a driver, this function * will never get called until they do. * @remove: Called when the device is removed from the system to * unbind a device from this driver. * @shutdown: Called at shut-down time to quiesce the device. * @suspend: Called to put the device to sleep mode. Usually to a * low power state. * @resume: Called to bring a device from sleep mode. * @groups: Default attributes that get created by the driver core * automatically. * @dev_groups: Additional attributes attached to device instance once the * it is bound to the driver. * @pm: Power management operations of the device which matched * this driver. * @coredump: Called when sysfs entry is written to. The device driver * is expected to call the dev_coredump API resulting in a * uevent. * @p: Driver core's private data, no one other than the driver * core can touch this. * * The device driver-model tracks all of the drivers known to the system. * The main reason for this tracking is to enable the driver core to match * up drivers with new devices. Once drivers are known objects within the * system, however, a number of other things become possible. Device drivers * can export information and configuration variables that are independent * of any specific device. */ structdevice_driver { constchar *name;// Device driver name structbus_type *bus;// Bus type to which the device driver belongs
structmodule *owner;// Module that owns this driver constchar *mod_name; /* used for built-in modules */// Name used for built-in modules
bool suppress_bind_attrs; /* disables bind/unbind via sysfs *///Disable bind/unbind attributes via sysfs enumprobe_typeprobe_type;// Probe type, used to specify the probing method
conststructof_device_id *of_match_table;// Device match table conststructacpi_device_id *acpi_match_table;// ACPI device match table // Note: device and device_driver must be attached to the same bus in order to trigger probe int (*probe) (struct device *dev);// Device probe function, used to initialize and configure the device
void (*sync_state)(struct device *dev); // Device state synchronization function
int (*remove) (struct device *dev); // Device removal function
void (*shutdown) (struct device *dev); // Device shutdown function
int (*suspend) (struct device *dev, pm_message_t state); // Device suspend function
int (*resume) (struct device *dev); // Device resume function conststructattribute_group **groups;// Driver attribute group conststructattribute_group **dev_groups;
conststructdev_pm_ops *pm;// Power management operations void (*coredump) (struct device *dev);//Device core dump function
structdriver_private *p;//Driver private data. };
struct class
struct classIs a data structure in the Linux kernel that describes device classes
// include/linux/device/class.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. * @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. * @get_ownership: Allows class to specify uid/gid of the sysfs directories * for the devices belonging to the class. Usually tied to * device's namespace. * @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. */ structclass { constchar *name; //Name of the device class structmodule *owner;//Module that owns this class // Class attribute group, used to describe attributes of the device class; when the class is registered with the kernel, corresponding attribute files are automatically created under /sys/class/xxx_class conststructattribute_group **class_groups; // Device attribute group, used to describe attributes of the device; when the class is registered with the kernel, corresponding attribute files are automatically created in the device directory under that class conststructattribute_group **dev_groups; // device kernel object structkobject *dev_kobj;
int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);// device event handler char *(*devnode)(struct device *dev, umode_t *mode);// function to create device node
void (*class_release)(struct class *class);// class resource release function void (*dev_release)(struct device *dev);// device resource release function
int (*shutdown_pre)(struct device *dev);// device shutdown callback function
conststructkobj_ns_type_operations *ns_type;// namespace type operation constvoid *(*namespace)(struct device *dev);// namespace function
void (*get_ownership)(struct device *dev, kuid_t *uid, kgid_t *gid);// function to acquire device ownership
conststructdev_pm_ops *pm;// power management operation
structsubsys_private *p;// subsystem private data };
sysfs filesystem
sysfs filesystem is a type ofvirtual filesystem,used to provide information about devices, drivers, and other kernel objects in the kernel to user spaceIt organizes data in a hierarchical manner and represents this data as files and directories, allowing user space to access and manipulate the attributes of kernel objects through the file system interface.
sysfs provides a unified interface for browsing and managing devices, buses, drivers, and other kernel objects within the kernel. It is mounted under the/sysdirectory, and users can view and modify/systhe files and directories under the directory to obtain and configure information about kernel objects.
We can consider buses, devices, and drivers as derived classes of kobject. Because they are all entities in the device model, they achieve integration with the device model by inheriting or extending kobject.
sysfs virtual file system
kobject is the cornerstone of the device model. By creating corresponding directory structures and attribute files, it provides a unified interface and framework for managing and operating various entities in the device model.
Source code analysis of file creation
The following explains step by step from the code level why when using thekobject_create_and_add()function to create a kobject, if the parent node is NULL, it will be created under the system root directory/sysCreate under
The step-by-step path tracing is as follows: kobject_create_and_add └──kobject_add └──kobject_add_varg └──kobject_add_internal └──create_dir └──sysfs_create_dir_ns(fs/sysfs/dir.c)
// lib/kobject.c /** * kobject_create_and_add() - Create a struct kobject dynamically and * register it with sysfs. * @name: the name for the kobject * @parent: the parent kobject of this kobject, if any. * * This function creates a kobject structure dynamically and registers it * with sysfs. When you are finished with this structure, call * kobject_put() and the structure will be dynamically freed when * it is no longer being used. * * If the kobject was not able to be created, NULL will be returned. */ struct kobject *kobject_create_and_add(constchar *name, struct kobject *parent) { structkobject *kobj; int retval;
/** * kobject_add() - The main kobject add function. * @kobj: the kobject to add * @parent: pointer to the parent of the kobject. * @fmt: format to name the kobject with. * * The kobject name is set and added to the kobject hierarchy in this * function. * * If @parent is set, then the parent of the @kobj will be set to it. * If @parent is NULL, then the parent of the @kobj will be set to the * kobject associated with the kset assigned to this kobject. If no kset * is assigned to the kobject, then the kobject will be located in the * root of the sysfs tree. * * Note, no "add" uevent will be created with this call, the caller should set * up all of the necessary sysfs files for the object and then call * kobject_uevent() with the UEVENT_ADD parameter to ensure that * userspace is properly notified of this kobject's creation. * * Return: If this function returns an error, kobject_put() must be * called to properly clean up the memory associated with the * object. Under no instance should the kobject that is passed * to this function be directly freed with a call to kfree(), * that can leak memory. * * If this function returns success, kobject_put() must also be called * in order to properly clean up the memory associated with the object. * * In short, once this function is called, kobject_put() MUST be called * when the use of the object is finished in order to properly free * everything. */ intkobject_add(struct kobject *kobj, struct kobject *parent, constchar *fmt, ...) { va_list args; int retval;
if (!kobj) return -EINVAL;
if (!kobj->state_initialized) { pr_err("kobject '%s' (%p): tried to add an uninitialized object, something is seriously wrong.\n", kobject_name(kobj), kobj); dump_stack(); return -EINVAL; } va_start(args, fmt); retval = kobject_add_varg(kobj, parent, fmt, args); va_end(args);
retval = kobject_set_name_vargs(kobj, fmt, vargs); if (retval) { pr_err("kobject: can not set name properly!\n"); return retval; } kobj->parent = parent; return kobject_add_internal(kobj); }
staticintkobject_add_internal(struct kobject *kobj) { int error = 0; structkobject *parent;
if (!kobj) return -ENOENT;
if (!kobj->name || !kobj->name[0]) { WARN(1, "kobject: (%p): attempted to be registered with empty name!\n", kobj); return -EINVAL; }
parent = kobject_get(kobj->parent);
/* join kset if set, use it as parent if we do not already have one */ if (kobj->kset) { if (!parent) parent = kobject_get(&kobj->kset->kobj); kobj_kset_join(kobj); kobj->parent = parent; }
/* be noisy on error issues */ if (error == -EEXIST) pr_err("%s failed for %s with -EEXIST, don't try to register things with the same name in the same directory.\n", __func__, kobject_name(kobj)); else pr_err("%s failed for %s (error: %d parent: %s)\n", __func__, kobject_name(kobj), error, parent ? kobject_name(parent) : "'none'"); } else kobj->state_in_sysfs = 1;
error = sysfs_create_dir_ns(kobj, kobject_namespace(kobj)); if (error) return error;
error = populate_dir(kobj); if (error) { sysfs_remove_dir(kobj); return error; }
if (ktype) { error = sysfs_create_groups(kobj, ktype->default_groups); if (error) { sysfs_remove_dir(kobj); return error; } }
/* * @kobj->sd may be deleted by an ancestor going away. Hold an * extra reference so that it stays until @kobj is gone. */ sysfs_get(kobj->sd);
/* * If @kobj has ns_ops, its children need to be filtered based on * their namespace tags. Enable namespace support on @kobj->sd. */ ops = kobj_child_ns_ops(kobj); if (ops) { BUG_ON(ops->type <= KOBJ_NS_TYPE_NONE); BUG_ON(ops->type >= KOBJ_NS_TYPES); BUG_ON(!kobj_ns_type_registered(ops->type));
sysfs_enable_ns(kobj->sd); }
return0; }
It can be seen thaterror = create_dir(kobj);a folder is created in the file system
// fs/sysfs/dir.c /** * sysfs_create_dir_ns - create a directory for an object with a namespace tag * @kobj: object we're creating directory for * @ns: the namespace tag to use */ intsysfs_create_dir_ns(struct kobject *kobj, constvoid *ns) { structkernfs_node *parent, *kn; kuid_t uid; kgid_t gid;
if (WARN_ON(!kobj)) return -EINVAL;
if (kobj->parent) parent = kobj->parent->sd; else parent = sysfs_root_kn;
In the above function, when there is no parent node, the parent node is assigned assysfs_root_kn, that is,/systhe node of the root directory. If there isparent, then its parent node iskobj->parent->sd, and then callkernfs_create_dir_nsto create the directory.
Andsysfs_root_knis created during the initialization of the sysfs file system, that is, in thesysfs_initfunction:
sysfs_root = kernfs_create_root(NULL, KERNFS_ROOT_EXTRA_OPEN_PERM_CHECK, NULL); if (IS_ERR(sysfs_root)) return PTR_ERR(sysfs_root);
sysfs_root_kn = sysfs_root->kn;
err = register_filesystem(&sysfs_fs_type); if (err) { kernfs_destroy_root(sysfs_root); return err; }
return0; }
Through the above analysis of the API functions, we can summarize the rules for creating directories, as follows:
Without a parent directory and without a kset, the directory will be created under the root directory of sysfs (i.e.,/sys).
Without a parent directory but with a kset, the directory will be created under the kset, and the kobj will be added tokset.list。
With a parent directory but without a kset, the directory will be created under the parent.
If there is a parent directory and a kset, a directory will be created under the parent, and the kobj will be added.kset.list。
Analysis of sysfs directory hierarchy
The folders related to the device model are bus, class, and devices. The full paths are as follows:
/sys/bus
/sys/class
/sys/devices
Explanation:
/sys/devices
This directory contains subdirectories for all devices in the system.Each device subdirectory represents a specific device, reflecting device relationships and topology through its path hierarchy and symbolic links. Each device subdirectory contains the device’s attributes, status, and other related information.
/sys/devices
/sys/bus
This directory contains subdirectories for bus types. Each subdirectory represents a specific type of bus, such as PCI, USB, etc. Each bus subdirectory contains information about devices and drivers related to that bus.
/sys/bus
For example, devices connected under the I2C bus are shown as follows:
/sys/bus/i2c
/sys/class
This directory contains subdirectories for device classes. Each subdirectory represents a device class, such as disk, network interface, etc. Each device class subdirectory contains information about devices belonging to that class, as shown in the figure below.
/sys/class
The benefits of using class for categorization are as follows:
Logical organization: By categorizing devices by class, a logical organizational structure can be established in the device model. This way,related types of devices can be placed under the same class directory, making the device organizational structure clearer and more manageable.
Unified interfaces and attributes: Eachdevice category directory can define a set of unified interfaces and attributes, used to describe and configure the common characteristics and behaviors of all devices under that category. In this way, for devices of the same category, the same methods and attributes can be used to operate and configure them, simplifying the writing and maintenance of device drivers.
Simplify device discovery and management: By classifying devices, a simplified device discovery and management mechanism can be provided. Users and applications can search for and identify specific types of devices in the category directory without traversing the entire device model. This makes device discovery and access more efficient and convenient.
Scalability and portability: Usingclassfor classification can provide a mechanism for scalability and portability. When introducing new device types, they can be classified into existing categories without modifying existing device management and drivers. This scalability and portability makes the system more flexible and makes it easier for developers and device vendors to integrate new devices.
For example, if an application now needs to set GPIO, if using a class, the following command can be used directly:
The attribute group will be a subdirectory under the kobject directory (if the group name exists) or attributes at the same level (when the group name is NULL)
2.grp
Points to astruct attribute_group, used to describe the group name and list of attribute files.
structbus_typemybus = { .name = "mybus", // Bus name .match = mybus_match, // Callback function for matching device and driver .probe = mybus_probe, // Callback function for device probing }; EXPORT_SYMBOL_GPL(mybus); // Export bus symbols
ssize_tmybus_show(struct bus_type *bus, char *buf) { // Display the bus value in sysfs returnsprintf(buf, "%s\n", "mybus_show"); };
structbus_attributemybus_attr = { .attr = { .name = "value", // Attribute name .mode = 0664, // Attribute access permissions }, .show = mybus_show, // Attribute show callback function };
// Module initialization function staticintbus_init(void) { int ret; ret = bus_register(&mybus); // Register bus ret = bus_create_file(&mybus, &mybus_attr); // Create attribute file in sysfs
return0; }
// Module exit function staticvoidbus_exit(void) { bus_remove_file(&mybus, &mybus_attr); // Remove attribute file from sysfs bus_unregister(&mybus); // Unregister bus }
module_init(bus_init); // Specify module initialization function module_exit(bus_exit); // Specify module exit function
$ insmod create_attr_under_my_own_bus.ko [ 23.827916] create_attr_under_my_own_bus: loading out-of-tree module taints kernel. $ cd /sys/bus/mybus/ /sys/bus/mybus $ ls devices drivers_autoprobe uevent drivers drivers_probe value /sys/bus/mybus $ cat value mybus_show /sys/bus/mybus $ cd / $ rmmod create_attr_under_my_own_bus.ko $ ls /sys/bus/mybus ls: /sys/bus/mybus: No such file or directory
constchar *init_name; /* initial name of the device */ conststructdevice_type *type;
structbus_type *bus;/* type of bus device is on */ structdevice_driver *driver;/* which driver has allocated this device */ void *platform_data; /* Platform specific data, device core doesn't touch it */ void *driver_data; /* Driver data, set and get with dev_set_drvdata/dev_get_drvdata */ #ifdef CONFIG_PROVE_LOCKING structmutexlockdep_mutex; #endif structmutexmutex;/* mutex to synchronize calls to * its driver. */
#ifdef CONFIG_NUMA int numa_node; /* NUMA node this device is close to */ #endif dev_t devt; /* dev_t, creates the sysfs "dev" */ u32 id; /* device instance */
/** * 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. */ intbus_register(struct bus_type *bus) { int retval; structsubsys_private *priv; structlock_class_key *key = &bus->lock_key; // allocate and initialize a subsys_private structure to store subsystem-related information priv = kzalloc(sizeof(struct subsys_private), GFP_KERNEL); if (!priv) return -ENOMEM;
priv->bus = bus; bus->p = priv; // initialize a blocking notification linked list bus_notifier BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier); // set the subsystem name retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name); if (retval) goto out; // set the subsystem's kset and ktype priv->subsys.kobj.kset = bus_kset; priv->subsys.kobj.ktype = &bus_ktype; priv->drivers_autoprobe = 1; // register the subsystem's kset retval = kset_register(&priv->subsys); if (retval) goto out; // create an attribute file on the bus retval = bus_create_file(bus, &bus_attr_uevent); if (retval) goto bus_uevent_fail; // create and add the kset for the "devices" subdirectory priv->devices_kset = kset_create_and_add("devices", NULL, &priv->subsys.kobj); if (!priv->devices_kset) { retval = -ENOMEM; goto bus_devices_fail; } // Create and add the kset for the "drivers" subdirectory priv->drivers_kset = kset_create_and_add("drivers", NULL, &priv->subsys.kobj); if (!priv->drivers_kset) { retval = -ENOMEM; goto bus_drivers_fail; } // Initialize the interface linked list, mutex lock, and the klist for devices/drivers INIT_LIST_HEAD(&priv->interfaces); __mutex_init(&priv->mutex, "subsys mutex", key); klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put); klist_init(&priv->klist_drivers, NULL, NULL); // Add the driver probe file retval = add_probe_files(bus); if (retval) goto bus_probe_files_fail; // Add the attribute group of the bus retval = bus_add_groups(bus, bus->bus_groups); if (retval) goto bus_groups_fail;
This line of code initializes the kernel linked list namedpriv->klist_devices.klist_devices_getandklist_devices_putare two callback functions used to perform corresponding operations when adding or removing elements from the linked list.
Typically, these callback functionsare used to perform additional operations when each element in the linked list is referenced or released. For example, when a device is added to the linked list,klist_devices_getthe function may increase the device’s reference count; when the device is removed from the linked list,klist_devices_putthe function may decrease the device’s reference count.
klist_init(&priv->klist_drivers, NULL, NULL);
This line of code initializes the kernel linked list namedpriv->klist_driverskernel linked list, but unlike the first initialization, no callback function is provided here. Therefore, this linked list does not perform additional operations when adding or removing elements. In this case, the linked list is mainly used to store driver objects without requiring additional processing logic.
// 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. */ structsubsys_private { structksetsubsys; structkset *devices_kset; structlist_headinterfaces; structmutexmutex;
struct subsys_privateis a structure used to store private information of the driver core subsystem (bus). Each subsystem can have private data, which is stored in thestruct subsys_privatestructure.
Linux subsystem
In Linux,a subsystem is a mechanism for abstracting the implementation of a specific function into an independent entity. It provides a convenient way to organize related code and data structures together to achieve specific functions. A subsystem can be regarded as a functional module that encapsulates related functions and operations, allowing users and applications to interact with it through a unified interface.
In Linux, there are many common subsystems, each responsible for implementing specific functions. The following are some examples of common subsystems.
Virtual File System (VFS) subsystem: The VFS subsystem provides a unified access interface to different file systems, allowing applications to transparently access various file systems (such as ext4, NTFS, FAT, etc.) without needing to care about the specific implementation of the underlying file system.
Device driver subsystem: The device driver subsystem manages and controls drivers for hardware devices. It provides an interface for interacting with hardware devices, allowing applications to communicate with and control devices through drivers.
Network subsystem: The network subsystem is responsible for managing and controlling network-related functions. It includes the network protocol stack, socket interface, network device drivers, etc., used to implement network communication and network protocol processing.
Memory Management Subsystem: The memory management subsystem is responsible for managing the system’s physical memory and virtual memory. It includes functions such as memory allocation, page replacement, and memory mapping, used to efficiently allocate and manage the system’s memory resources.
Process Management Subsystem: The process management subsystem is responsible for managing and controlling processes in the system. It includes functions such as process creation, scheduling, and termination, as well as inter-process communication mechanisms like signals, pipes, and shared memory.
Power Management Subsystem: The power management subsystem is responsible for managing and controlling the system’s power management functions. It can be used to control power on/off, switch power modes, and implement energy-saving features.
File System Subsystem: The file system subsystem is responsible for managing and controlling operations such as file system creation, formatting, mounting, and data access. It supports various file system types, such as ext4, FAT, and NTFS.
Graphics Subsystem: The graphics subsystem is responsible for managing and controlling graphics display functions, including display drivers, window management, and graphics rendering. It provides support for a graphical interface, allowing users to interact with the computer through graphics.
Summary
kobjectandksetare the basic framework of the device model, and they canbe embedded into other structures to provide the functionality of the device model. A kobject represents an object in the device model, while a kset is a collection of related kobjects.
attribute filesplay an important role in the device model, theyare used for data exchange between kernel space and user space. Attribute files can be represented as files in user space through the sysfs virtual file system, and users can read or write these files to interact with the device model. Attribute files allow users to access device status, configuration, and control information, thereby enabling management and configuration of the device model.
The sysfs virtual file system plays a key role in the device model, it can display the organizational hierarchy of the device model. Through sysfs, objects, attributes, and relationships in the device model can be represented in user space as directories and files. This organizational form allows users to browse and manage the device model in a hierarchical manner, making it convenient to obtain device information, configuration, and status. sysfs provides a unified interface that enables users to interact with the device model through file system operations, offering visualization and operability of the device model.
Analysis of platform bus registration process
During kernel initialization, theplatform_bus_init()function is called to initialize the platform bus, and the call flow is as follows:
structbus_typeplatform_bus_type = { // Specify the name of the platform bus type as "platform" .name = "platform", // Specify the pointer to the device group, used to define device attribute groups related to the platform bus .dev_groups = platform_dev_groups, // Specify the pointer to the match function, used to determine whether a device is compatible with the platform bus .match = platform_match, // Specify the pointer to the event handler function, used to handle events related to the platform bus .uevent = platform_uevent, // Specify the pointer to the DMA configuration function, used to configure DMA on the platform bus .dma_configure = platform_dma_configure, // Specify the pointer to the power management-related operation function, used to manage device power on the platform bus .pm = &platform_dev_pm_ops, }; EXPORT_SYMBOL_GPL(platform_bus_type);
platform_match()
platform_matchis a function used to determine whether a device and a driver match. It accepts two parameters:
/* 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)) return1;
/* Then try ACPI style match */ if (acpi_driver_match_device(dev, drv)) return1;
/* 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); }
First, convert dev and drv respectively tostruct platform_deviceandstruct platform_drivertype pointers for subsequent use.
Checkpdev->driver_overrideWhether it is set. If set, it means that as long as it matches the specified driver name, the device and driver are considered matched. The function comparespdev->driver_overrideanddrv->namestrings for equality, and if equal, returns a match (non-zero).
Ifpdev->driver_overrideis not set, first attempt OF-style matching (Open Firmware). Call theof_driver_match_device(dev, drv)function, which checks whether the device matches the driver. If the match succeeds, it returns a match (non-zero).
If OF-style matching fails, next attempt ACPI-style matching (Advanced Configuration and Power Interface). Call theacpi_driver_match_device(dev, drv)function, which checks whether the device matches the driver. If the match succeeds, it returns a match (non-zero).
If ACPI-style matching also fails, finally attempt matching based on the driver’s ID table. Check whether pdrv->id_table exists. If it does, call theplatform_match_id(pdrv->id_table, pdev)function to check whether the device matches any entry in the ID table. If the match succeeds, it returns a match (non-zero).
If all the above matching attempts fail, finally usedriver nameanddevice namefor comparison. Comparepdev->nameanddrv->namewhether the strings are equal; if equal, return a match (non-zero).
From the above analysis, we understand why when matching priorities on the platform bus, the matching priority:of_match_table > id_table > name。
can useMODULE_DEVICE_TABLE(type, name);export the device ID tableplatform_driver.driver.of_match_tableto the module’s ELF section, allowing userspace (udev/modprobe) to automatically load the driver. For example:
MODULE_LICENSE("GPL"); MODULE_AUTHOR("even629<asqwgo@outlook.com>"); MODULE_DESCRIPTION("This is a test sample for my own bus");
becausemybusis also a module driver, so setKBUILD_EXTRA_SYMBOLSpoint to the compiledmybusgenerated by the moduleModule.symvers, otherwise an error will be reported:ERROR: modpost: "mybus" undefined!, for exampleKBUILD_EXTRA_SYMBOLS := $(PWD)/../55_create_attr_under_my_own_bus/Module.symvers
// drivers/base/core.c voiddevice_initialize(struct device *dev) { // The code sets the kobj.kset member of the device object to devices_kset, indicating that the kset to which the device object belongs is devices_kset, meaning the device object belongs to the devices subsystem dev->kobj.kset = devices_kset; // Call kobject_init function initializes the kobj member of the device object, using device_ktype as the ktype. Through this function call, the kobject of the device object is correctly initialized and set. kobject_init(&dev->kobj, &device_ktype); // Use INIT_LIST_HEAD macro to initialize the dma of the device object_pools、msi_list、consumers、suppliers、needs_suppliers and defer_hook and other list heads to ensure they are empty lists INIT_LIST_HEAD(&dev->dma_pools); // Call the mutex_init function to initialize the mutex lock of the device object for mutual exclusion operations on the device. mutex_init(&dev->mutex); #ifdef CONFIG_PROVE_LOCKING mutex_init(&dev->lockdep_mutex); #endif // Through lockdep_set_novalidate_class function sets the validation class of dev->mutex to invalid to avoid deadlock analyzer validation of this mutex lockdep_set_novalidate_class(&dev->mutex); spin_lock_init(&dev->devres_lock); INIT_LIST_HEAD(&dev->devres_head); // Initialize power management related information of the device object device_pm_init(dev); // Indicates no device node is specified set_dev_node(dev, -1); #ifdef CONFIG_GENERIC_MSI_IRQ raw_spin_lock_init(&dev->msi_lock); INIT_LIST_HEAD(&dev->msi_list); #endif INIT_LIST_HEAD(&dev->links.consumers); INIT_LIST_HEAD(&dev->links.suppliers); INIT_LIST_HEAD(&dev->links.needs_suppliers); INIT_LIST_HEAD(&dev->links.defer_hook); // The code sets the status member of the device object to DL_DEV_NO_DRIVER, indicating the device currently has no driver dev->links.status = DL_DEV_NO_DRIVER; } EXPORT_SYMBOL_GPL(device_initialize);
// drivers/base/core.c intdevice_add(struct device *dev) { structdevice *parent; structkobject *kobj; structclass_interface *class_intf; int error = -EINVAL; structkobject *glue_dir =NULL; // Get a reference to the device dev = get_device(dev); if (!dev) goto done;
if (!dev->p) { // If the device's private data is not initialized, initialize it error = device_private_init(dev); if (error) goto done; }
/* * for statically allocated devices, which should all be converted * some day, we need to initialize the name. We prevent reading back * the name, and force the use of dev_name() */ if (dev->init_name) { dev_set_name(dev, "%s", dev->init_name);// Initialize the device name dev->init_name = NULL; }
/* subsystems can specify simple device enumeration */ if (!dev_name(dev) && dev->bus && dev->bus->dev_name) dev_set_name(dev, "%s%u", dev->bus->dev_name, dev->id);// If the device name is empty and the bus name of the device is not empty, set the device name
if (!dev_name(dev)) { error = -EINVAL; goto name_error; }
parent = get_device(dev->parent);// Get a reference to the parent device kobj = get_device_parent(dev, parent);// Get the parent kobject of the device if (IS_ERR(kobj)) { error = PTR_ERR(kobj); goto parent_error; } if (kobj) dev->kobj.parent = kobj;
/* use parent numa_node */ if (parent && (dev_to_node(dev) == NUMA_NO_NODE)) set_dev_node(dev, dev_to_node(parent));// Use the NUMA node of the parent device
/* first, register with generic layer. */ /* we require the name to be set before, and pass NULL */ // First, register the device with the generic layer // Need to set the device name before this and set parent to NULL error = kobject_add(&dev->kobj, dev->kobj.parent, NULL); if (error) { glue_dir = get_glue_dir(dev); goto Error; }
/* notify platform of device entry */ // Notify the addition of a platform device error = device_platform_notify(dev, KOBJ_ADD); if (error) goto platform_error; // Create the uevent attribute file for the device error = device_create_file(dev, &dev_attr_uevent); if (error) goto attrError; // Add symbolic links for the device class error = device_add_class_symlinks(dev); if (error) goto SymlinkError; // Add device attributes error = device_add_attrs(dev); if (error) goto AttrsError; // Add the device to the bus error = bus_add_device(dev); if (error) goto BusError; // Add the device to the device power management directory error = dpm_sysfs_add(dev); if (error) goto DPMError; // Add the device to power management device_pm_add(dev);
// If the device's devt has a major device number if (MAJOR(dev->devt)) { // Create the dev attribute file for the device error = device_create_file(dev, &dev_attr_dev); if (error) goto DevAttrError; // Create the sys device node for the device error = device_create_sys_dev_entry(dev); if (error) goto SysEntryError; // Create the device node on devtmpfs devtmpfs_create_node(dev); }
/* Notify clients of device addition. This call must come * after dpm_sysfs_add() and before kobject_uevent(). */ if (dev->bus)// Notify the event chain of device addition blocking_notifier_call_chain(&dev->bus->p->bus_notifier, BUS_NOTIFY_ADD_DEVICE, dev);
kobject_uevent(&dev->kobj, KOBJ_ADD);
/* * Check if any of the other devices (consumers) have been waiting for * this device (supplier) to be added so that they can create a device * link to it. * * This needs to happen after device_pm_add() because device_link_add() * requires the supplier be registered before it's called. * * But this also needs to happen before bus_probe_device() to make sure * waiting consumers can link to it before the driver is bound to the * device and the driver sync_state callback is called for this device. */ // Check if other devices (consumers) have been waiting for the addition of this device (supplier) so that device links can be created. if (dev->fwnode && !dev->fwnode->dev) { dev->fwnode->dev = dev; fw_devlink_link_device(dev); } // Probe the device on the bus bus_probe_device(dev); if (parent)// If a parent device exists, add the current device to the parent device's child device list klist_add_tail(&dev->p->knode_parent, &parent->p->klist_children); // If the device has a class if (dev->class) { mutex_lock(&dev->class->p->mutex); /* tie the class to the device */ klist_add_tail(&dev->p->knode_class, &dev->class->p->klist_devices);// Add the device to the category's device list
/* notify any interfaces that the device is here */ list_for_each_entry(class_intf, &dev->class->p->interfaces, node) if (class_intf->add_dev) class_intf->add_dev(dev, class_intf);// Notify any interface that the device has been added mutex_unlock(&dev->class->p->mutex); } done: put_device(dev);// Release the device reference return error; SysEntryError: if (MAJOR(dev->devt))// If a major device number exists, remove the device's dev attribute file device_remove_file(dev, &dev_attr_dev); DevAttrError: device_pm_remove(dev);// Remove the device's power management dpm_sysfs_remove(dev);// Remove the device from the device power management directory DPMError: bus_remove_device(dev);// Remove the device from the bus BusError:// Remove the device's attributes device_remove_attrs(dev); AttrsError:// Remove the device class symbolic link device_remove_class_symlinks(dev); SymlinkError:// Remove the device's uevent attribute file device_remove_file(dev, &dev_attr_uevent); attrError: device_platform_notify(dev, KOBJ_REMOVE); platform_error: kobject_uevent(&dev->kobj, KOBJ_REMOVE);// Send the KOBJ_REMOVE event for the device's kobject glue_dir = get_glue_dir(dev);// Get the device's glue directory kobject_del(&dev->kobj);// Delete the device's kobject Error: cleanup_glue_dir(dev, glue_dir);// Clean up the device's glue directory parent_error: put_device(parent);// Release the parent device reference name_error: kfree(dev->p);// Release the device's private data dev->p = NULL; goto done; } EXPORT_SYMBOL_GPL(device_add);
The above code usesbus_add_devicefunction to add the device to the bus
// drivers/base/bus.c /** * bus_add_device - add device to bus * @dev: device being added * * - Add device's bus attributes. * - Create links to device's bus. * - Add the device to its bus's list of devices. */ intbus_add_device(struct device *dev) { structbus_type *bus = bus_get(dev->bus);// Get the pointer to the bus type (bus_type) to which the device belongs int error = 0;
if (bus) {// If the bus type pointer is successfully obtained pr_debug("bus: '%s': add device %s\n", bus->name, dev_name(dev)); error = device_add_groups(dev, bus->dev_groups);// Add the device to the device groups (dev_groups) of the bus type if (error) goto out_put; error = sysfs_create_link(&bus->p->devices_kset->kobj, &dev->kobj, dev_name(dev));// Create a symbolic link for the device under the kernel object (kobj) of the bus type's device set (kset) if (error) goto out_groups; error = sysfs_create_link(&dev->kobj, &dev->bus->p->subsys.kobj, "subsystem");// Create a symbolic link pointing to the bus type's subsystem under the device's kernel object (kobj) if (error) goto out_subsys; klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);// Add the device's node to the device list of the bus type } return0;
sysfs_create_link(&bus->p->devices_kset->kobj, &dev->kobj, dev_name(dev)) Under the kernel object (kobj) of the bus type’s device set (devices_kset) create a symbolic link for the device. This symbolic link links the device’s sysfs directory to the bus type’s device set directory.
sysfs_create_link(&dev->kobj, &dev->bus->p->subsys.kobj, "subsystem") Create a symbolic link pointing to the bus type’s subsystem under the device’s kernel object (kobj). This symbolic link links the device’s sysfs directory to the bus type’s subsystem directory.
Analysis of the platform bus device registration process
In the platform device driver, we useplatform_device_registerto register the device
/** * platform_device_add - add a platform device to device hierarchy * @pdev: platform device we're adding * * This is part 2 of platform_device_register(), though may be called * separately _iff_ pdev was allocated by platform_device_alloc(). */ intplatform_device_add(struct platform_device *pdev) { u32 i; int ret;
if (!pdev)// Check if the input platform device pointer is null return -EINVAL;
if (!pdev->dev.parent)// If the parent device of the platform device is null, set the parent device to platform_bus pdev->dev.parent = &platform_bus;
pdev->dev.bus = &platform_bus_type;// Set the bus of the platform device to platform_bus_type
switch (pdev->id) {// Handle differently based on the id of the platform device default: dev_set_name(&pdev->dev, "%s.%d", pdev->name, pdev->id);// Set the device name based on the device name and id break; case PLATFORM_DEVID_NONE: // If the id is PLATFORM_DEVID_NONE, then only use the device name as the device name dev_set_name(&pdev->dev, "%s", pdev->name); break; case PLATFORM_DEVID_AUTO: /* * Automatically allocated device ID. We mark it as such so * that we remember it must be freed, and we append a suffix * to avoid namespace collision with explicit IDs. */ /* * Automatically allocated device ID。Mark it as automatically allocated,So that we remember it needs to be freed, * And to avoid conflicts with explicitly ID named namespace conflicts,We append a suffix。 */ ret = ida_alloc(&platform_devid_ida, GFP_KERNEL); if (ret < 0) goto err_out; pdev->id = ret; pdev->id_auto = true; dev_set_name(&pdev->dev, "%s.%d.auto", pdev->name, pdev->id); break; } // Traverse the resource list of the platform device and process each resource for (i = 0; i < pdev->num_resources; i++) { structresource *p, *r = &pdev->resource[i]; // If the resource name is null, set the resource name to the device name if (r->name == NULL) r->name = dev_name(&pdev->dev);
p = r->parent; if (!p) { // If the resource does not specify a parent resource, set the default parent resource based on the resource type. if (resource_type(r) == IORESOURCE_MEM) p = &iomem_resource; elseif (resource_type(r) == IORESOURCE_IO) p = &ioport_resource; }
if (p) { // If the parent resource exists and inserting the resource into the parent resource fails, return an error. ret = insert_resource(p, r); if (ret) { dev_err(&pdev->dev, "failed to claim resource %d: %pR\n", i, r); goto failed; } } }
pr_debug("Registering platform device '%s'. Parent at %s\n", dev_name(&pdev->dev), dev_name(pdev->dev.parent)); // Add the device to the device hierarchy and register the device. ret = device_add(&pdev->dev); if (ret == 0) return ret;
failed: if (pdev->id_auto) {// If the device ID is automatically assigned, the assigned ID needs to be removed. ida_free(&platform_devid_ida, pdev->id); pdev->id = PLATFORM_DEVID_AUTO; }
while (i--) {// In case of failure, release the inserted resources. structresource *r = &pdev->resource[i]; if (r->parent) release_resource(r); }
Handle differently based on the platform device ID:
By default, set the device name based on the device name and ID.
If the ID isPLATFORM_DEVID_NONE, then only use the device name as the device name.
If the ID isPLATFORM_DEVID_AUTO, then automatically assign the device ID. Use theida_simple_getfunction to obtain an available ID and mark the device ID as automatically assigned. The device name will have a suffix appended to avoid namespace conflicts with explicit IDs.
Set the device name. The name has three formats, as shown in the figure below.
Device Name
Device Name
Why register the device before registering the bus
Before registering a platform device, the functiondevice_register()is called to register a deviceplatform_bus。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
//drivers/base/platform.c int __init platform_bus_init(void) { int error;
early_platform_cleanup();
error = device_register(&platform_bus); if (error) { put_device(&platform_bus); return error; } error = bus_register(&platform_bus_type); if (error) device_unregister(&platform_bus); of_platform_register_reconfig_notifier(); return error; }
It can be seen that the functiondevice_register(&platform_bus)is called first, then bus_register(&platform_bus_type)
First, calldevice_registerfunction to registerplatform_busthis device, which will create a directory under the/sys/devicesdirectory/sys/devices/platform, and this created /sys/devices/platformdirectory is the parent directory of all platform devices. That is, allplatform_devicedevices will create subdirectories under/sys/devices/platform, as shown in the following figure:
$ insmod create_attr_under_my_own_bus.ko [ 13.087171] create_attr_under_my_own_bus: loading out-of-tree module taints kernel. $ insmod register_device_under_my_own_bus.ko $ insmod register_driver_under_my_own_bus.ko [ 35.109914] Driver 'mydevice' needs updating - please use bus_type methods [ 35.110974] mydriver_probe $ ls /sys/bus/mybus/ devices drivers_autoprobe uevent drivers drivers_probe value $ ls /sys/bus/mybus/drivers mydevice $ ls /sys/bus/mybus/devices/ mydevice
Printed when loading driver in the above testDriver 'mydevice' needs updating - please use bus_type methods, this is because in mybusprobefunction calleddrv->probe(dev), this is because indriver_register()function contains:
In factbus_type.probewas historically used for certain special buses (such as early versions of the platform bus), but modern kernels have regarded it as a legacy interface. Such asdrivers/base/platform. cis defined as follows:
And indrivers/base/dd.cin thereally_probe()function, it will calldrv->probefunction:
1 2 3 4 5 6 7 8 9
if (dev->bus->probe) { ret = dev->bus->probe(dev); if (ret) goto probe_failed; } elseif (drv->probe) { ret = drv->probe(dev); if (ret) goto probe_failed; }
// drivers/base/driver.c /** * 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. */ intdriver_register(struct device_driver *drv) { int ret; structdevice_driver *other;
// Check if the bus has been initialized if (!drv->bus->p) { pr_err("Driver '%s' was unable to register with bus_type '%s' because the bus was not initialized.\n", drv->name, drv->bus->name); return -EINVAL; } // Check if the driver's methods need updating if ((drv->bus->probe && drv->probe) || (drv->bus->remove && drv->remove) || (drv->bus->shutdown && drv->shutdown)) pr_warn("Driver '%s' needs updating - please use " "bus_type methods\n", drv->name); // Check if the driver has already been registered other = driver_find(drv->name, drv->bus); if (other) { pr_err("Error: Driver '%s' is already registered, " "aborting...\n", drv->name); return -EBUSY; }
ret = bus_add_driver(drv);// Add the driver to the bus if (ret) return ret; ret = driver_add_groups(drv, drv->groups);// Add group attributes of the driver if (ret) { bus_remove_driver(drv);// Remove the added driver return ret; } kobject_uevent(&drv->p->kobj, KOBJ_ADD);// Send a kernel object event to notify that the driver was added successfully
return ret; } EXPORT_SYMBOL_GPL(driver_register);
driver_registerThe function is used to register a device driver and add it to the bus. The following is an explanation of the function’s functionality:
Lines 16 to 20: Check if the bus has been initialized. First, throughdrv->busaccess the bus information in the device driver structure. If the p member of the bus is NULL, it indicates the bus is not initialized. If the bus is not initialized, print an error message and return-EINVALerror code indicating an invalid parameter.
Lines 22 to 26: Check if the driver’s methods need updating. By checking thebus->probeanddrv->probe、bus->removeanddrv->remove、bus->shutdownanddrv->shutdownmembers to determine if they exist simultaneously. If there is a combination of methods that need updating, it indicates the driver needs an update. In this case, print a warning message and recommend usingbus_typethe defined methods for updating.
Lines 28 to 33: Check if the driver has already been registered. Call thedriver_findfunction to find if a driver with the same name has already been registered. If a driver with the same name is found, it indicates the driver has already been registered. In this case, print an error message and return-EBUSYThe error code indicates that the device is busy.
Line 35: Add the driver to the bus: callbus_add_driverfunction to add the driver to the bus. If the addition fails, the corresponding error code is returned.
Line 38: Add the driver’s group attributes: calldriver_add_groupsfunction to add the driver’s group attributes to the driver. If the addition fails, then callbus_remove_driverfunction to remove the added driver and return the corresponding error code.
Line 43: Send a kernel object event: callkobject_ueventfunction to send an event to the driver’s kernel object, notifying the driver that it has been successfully added to the system.
In summary,driver_registerthe function’s purpose is to register the device driver and add it to the bus, while performing various checks and error handling operations.
bus_add_driver()
In the above code, callbus_add_driverfunction to add the driver to the bus. Let’s analyze in detailbus_add_driverfunction
// drivers/base/bus.c /** * bus_add_driver - Add a driver to the bus. * @drv: driver. */ intbus_add_driver(struct device_driver *drv) { structbus_type *bus; structdriver_private *priv; int error = 0; // Get the bus object bus = bus_get(drv->bus); if (!bus) return -EINVAL;
pr_debug("bus: '%s': add driver %s\n", bus->name, drv->name); // Allocate and initialize driver private data priv = kzalloc(sizeof(*priv), GFP_KERNEL); if (!priv) { error = -ENOMEM; goto out_put_bus; } klist_init(&priv->klist_devices, NULL, NULL); priv->driver = drv; drv->p = priv; priv->kobj.kset = bus->p->drivers_kset; error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL, "%s", drv->name);// Initialize and add the driver's kernel object if (error) goto out_unregister; // Add the driver to the bus's driver list klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers); if (drv->bus->p->drivers_autoprobe) {// If the bus has auto-probing enabled, attempt to auto-probe devices error = driver_attach(drv); if (error) goto out_del_list; } module_add_driver(drv->owner, drv);// Add the driver to the module // Create the driver's uevent attribute file error = driver_create_file(drv, &driver_attr_uevent); if (error) { printk(KERN_ERR "%s: uevent attr (%s) failed\n", __func__, drv->name); } error = driver_add_groups(drv, bus->drv_groups);// Add the driver's group attributes if (error) { /* How the hell do we get out of this pickle? Give up */ printk(KERN_ERR "%s: driver_create_groups(%s) failed\n", __func__, drv->name); }
if (!drv->suppress_bind_attrs) {// If the driver does not prohibit the bind attribute file, add the bind attribute file error = add_bind_files(drv); if (error) { /* Ditto */ printk(KERN_ERR "%s: add_bind_files(%s) failed\n", __func__, drv->name); } }
return0;
out_del_list: klist_del(&priv->knode_bus); out_unregister: kobject_put(&priv->kobj); /* drv->p is freed in driver_release() */ drv->p = NULL; out_put_bus: bus_put(bus); return error; }
bus_add_driverThis function is used to add a device driver to the bus. The following is a detailed explanation of its functionality:
Line 13 gets the bus object: viadrv->busaccess the bus information in the device driver structure. By calling thebus_getfunction to get the bus object. If the bus object does not exist, return-EINVALerror code indicating an invalid parameter.
Lines 20 to 24 allocate and initialize driver private data: call thekzallocfunction to allocate memory for the driver’s private data structure priv, and useGFP_KERNELflag for memory allocation. If memory allocation fails, it returns the -ENOMEM error code indicating insufficient memory.
Lines 25 to 27 use theklist_initfunction to initialize the device list in the priv structure. Set the driver pointer in the priv structure and assign it to the current driver. Setdrv->pto point to the priv structure for subsequent release operations.
Lines 29 to 32 initialize and add the kernel object of the driver: set thepriv->kobj.ksetmember to the bus object’sdrivers_kset. Call thekobject_init_and_addfunction to initialize and add the kernel object of the driver. If initialization or addition fails, jump toout_unregisterfor error handling.
Line 35 adds the driver to the bus’s driver list: use theklist_add_tailfunction to add the driver’s node to the bus’s driver list
Probe function execution flow analysis
In the abovebus_add_driverfunction, if the bus enables automatic probing (drivers_autoprobeflag), then calldriver_attachThe function attempts to automatically probe the device. If automatic probing fails, it jumps to out_unregister for error handling.
Variabledrivers_autoprobecan also be controlled in user space via the attribute filedrivers_autoprobe, once again demonstrating the role of the attribute file.
drivers_autoprobe
The entire process is shown in the following diagram:
Device and driver matching process
driver_attach()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
//drivers/base/dd.c /** * driver_attach - try to bind driver to devices. * @drv: driver. * * Walk the list of devices that the bus has on it and try to * match the driver with each one. If driver_probe_device() * returns 0 and the @dev->driver is set, we've found a * compatible pair. */ intdriver_attach(struct device_driver *drv) { //bus_for_The each_dev() function primarily provides a way to traverse the list of device objects on a specified bus, // and a shortcut for performing specific operations on each device object, which can be used in scenarios where a driver needs to manage and operate a large number of device instances. return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach); } EXPORT_SYMBOL_GPL(driver_attach);
staticint __driver_attach(struct device *dev, void *data) { structdevice_driver *drv = data;// The passed data parameter data serves as the device driver object. bool async = false; int ret;
/* * Lock device and try to bind to it. We drop the error * here and always return 0, because we need to keep trying * to bind to devices and some drivers will return an error * simply if it didn't support the device. * * driver_probe_device() will spit a warning if there * is an error. */
ret = driver_match_device(drv, dev);// Attempt to bind the driver to the device. if (ret == 0) { /* no match */ return0; } elseif (ret == -EPROBE_DEFER) { dev_dbg(dev, "Device match requests probe deferral\n"); driver_deferred_probe_add(dev);// Request to defer probing the device. /* * Driver could not match with device, but may match with * another device on the bus. */ return0; } elseif (ret < 0) {// The bus cannot match the device, returning an error code. dev_dbg(dev, "Bus failed to match device: %d\n", ret); /* * Driver could not match with device, but may match with * another device on the bus. */ return0; } /* ret > 0 means positive match */
if (driver_allows_async_probing(drv)) { /* * Instead of probing the device synchronously we will * probe it asynchronously to allow for more parallelism. * * We only take the device lock here in order to guarantee * that the dev->driver and async_driver fields are protected */ dev_dbg(dev, "probing driver %s asynchronously\n", drv->name); device_lock(dev);// Lock the device to protect the dev->driver and async_driver fields. if (!dev->driver) { get_device(dev); dev->p->async_driver = drv;// Set the device's asynchronous driver. async = true; } device_unlock(dev); if (async) async_schedule_dev(__driver_attach_async_helper, dev);// Additional handler function for asynchronous scheduling driver return0; }
device_driver_attach(drv, dev);// Synchronously probe device and bind driver
// drivers/base/dd.c /** * device_driver_attach - attach a specific driver to a specific device * @drv: Driver to attach * @dev: Device to attach it to * * Manually attach driver to a device. Will acquire both @dev lock and * @dev->parent lock if needed. */ intdevice_driver_attach(struct device_driver *drv, struct device *dev) { int ret = 0;
__device_driver_lock(dev, dev->parent);
/* * If device has been removed or someone has already successfully * bound a driver before us just skip the driver probe call. */ if (!dev->p->dead && !dev->driver) ret = driver_probe_device(drv, dev);
/** * driver_probe_device - attempt to bind device & driver together * @drv: driver to bind a device to * @dev: device to try to bind to the driver * * This function returns -ENODEV if the device is not registered, * 1 if the device is bound successfully and 0 otherwise. * * This function must be called with @dev lock held. When called for a * USB interface, @dev->parent lock must be held as well. * * If the device has a parent, runtime-resume the parent before driver probing. */ intdriver_probe_device(struct device_driver *drv, struct device *dev) { int ret = 0;
if (!device_is_registered(dev))// Check if device is registered; if not, return error code -ENODEV return -ENODEV; // Print debug info indicating device matches driver pr_debug("bus: '%s': %s: matched device %s with driver %s\n", drv->bus->name, __func__, dev_name(dev), drv->name); // Get runtime reference count of device supplier pm_runtime_get_suppliers(dev); if (dev->parent)// If device has a parent, get synchronous runtime reference count of parent pm_runtime_get_sync(dev->parent);
pm_runtime_barrier(dev);// Wait for device runtime state to stabilize if (initcall_debug)// Select and call the real probe function based on initialization debug flags ret = really_probe_debug(dev, drv); else ret = really_probe(dev, drv); pm_request_idle(dev);// Request device to enter idle state (power-saving mode)
if (dev->parent)// If device has a parent, release runtime reference count of parent pm_runtime_put(dev->parent);
// drivers/base/dd.c staticintreally_probe(struct device *dev, struct device_driver *drv) { int ret = -EPROBE_DEFER;// Initialize return value as deferred probe int local_trigger_count = atomic_read(&deferred_trigger_count);// Get current deferred probe count bool test_remove = IS_ENABLED(CONFIG_DEBUG_TEST_DRIVER_REMOVE) && !drv->suppress_bind_attrs;
if (defer_all_probes) { /* * Value of defer_all_probes can be set only by * device_block_probing() which, in turn, will call * wait_for_device_probe() right after that to avoid any races. */ dev_dbg(dev, "Driver %s force probe deferral\n", drv->name); driver_deferred_probe_add(dev); return ret; }
ret = device_links_check_suppliers(dev);// Check the device's supplier link if (ret == -EPROBE_DEFER) driver_deferred_probe_add_trigger(dev, local_trigger_count);// Add the device to the deferred probe trigger list if (ret) return ret;
atomic_inc(&probe_count);// Increment probe count pr_debug("bus: '%s': %s: probing driver %s with device %s\n", drv->bus->name, __func__, drv->name, dev_name(dev)); if (!list_empty(&dev->devres_head)) { dev_crit(dev, "Resources present before probing\n"); ret = -EBUSY; goto done; }
re_probe: dev->driver = drv;
/* If using pinctrl, bind pins now before probing */ ret = pinctrl_bind_pins(dev);/* If pinctrl is used, bind pins */ if (ret) goto pinctrl_bind_failed;
if (dev->bus->dma_configure) {// Configure DMA ret = dev->bus->dma_configure(dev); if (ret) goto probe_failed; }
ret = driver_sysfs_add(dev);// Add driver's sysfs if (ret) { pr_err("%s: driver_sysfs_add(%s) failed\n", __func__, dev_name(dev)); goto probe_failed; }
if (dev->pm_domain && dev->pm_domain->activate) {// If the device has a power management domain and an activation function exists, activate the power management domain ret = dev->pm_domain->activate(dev); if (ret) goto probe_failed; }
if (dev->bus->probe) {// If the bus has a probe function, call the bus's probe function ret = dev->bus->probe(dev); if (ret) goto probe_failed; } elseif (drv->probe) {// Otherwise, call the driver's probe function ret = drv->probe(dev); if (ret) goto probe_failed; }
ret = device_add_groups(dev, drv->dev_groups); if (ret) { dev_err(dev, "device_add_groups() failed\n"); goto dev_groups_failed; }
if (dev_has_sync_state(dev)) { ret = device_create_file(dev, &dev_attr_state_synced); if (ret) { dev_err(dev, "state_synced sysfs add failed\n"); goto dev_sysfs_state_synced_failed; } }
if (test_remove) {// If driver removal test is enabled test_remove = false;
if (dev->bus->remove)// If the bus has a remove function, call the bus's remove function dev->bus->remove(dev); elseif (drv->remove)// Otherwise, call the driver's remove function drv->remove(dev);
devres_release_all(dev);// Release the device's resources arch_teardown_dma_ops(dev);// Remove driver's sysfs kfree(dev->dma_range_map); dev->dma_range_map = NULL; driver_sysfs_remove(dev); dev->driver = NULL; dev_set_drvdata(dev, NULL); if (dev->pm_domain && dev->pm_domain->dismiss)// If the device has a power management domain and a detach function exists, detach the power management domain dev->pm_domain->dismiss(dev); pm_runtime_reinit(dev);// Reinitialize power management runtime
if (dev->pm_domain && dev->pm_domain->sync)// If the device has a power management domain and a sync function exists, synchronize the power management domain dev->pm_domain->sync(dev);
dev_sysfs_state_synced_failed: device_remove_groups(dev, drv->dev_groups); dev_groups_failed: if (dev->bus->remove) dev->bus->remove(dev); elseif (drv->remove) drv->remove(dev); probe_failed: if (dev->bus) blocking_notifier_call_chain(&dev->bus->p->bus_notifier, BUS_NOTIFY_DRIVER_NOT_BOUND, dev); pinctrl_bind_failed: device_links_no_driver(dev);// Unbind the device from the driver devres_release_all(dev);// Release device resources arch_teardown_dma_ops(dev);// Cancel DMA configuration kfree(dev->dma_range_map); dev->dma_range_map = NULL; driver_sysfs_remove(dev);// Remove driver sysfs dev->driver = NULL; dev_set_drvdata(dev, NULL); if (dev->pm_domain && dev->pm_domain->dismiss)// If the device has a power management domain and a detach function exists, detach the power management domain dev->pm_domain->dismiss(dev); pm_runtime_reinit(dev);// Reinitialize power management runtime dev_pm_set_driver_flags(dev, 0);// Set device driver flags to 0
switch (ret) { case -EPROBE_DEFER:/* Driver requests deferred probe */ /* Driver requested deferred probing */ dev_dbg(dev, "Driver %s requests probe deferral\n", drv->name); driver_deferred_probe_add_trigger(dev, local_trigger_count);// Add device to deferred probe trigger list break; case -ENODEV: case -ENXIO: pr_debug("%s: probe of %s rejects match %d\n", drv->name, dev_name(dev), ret); break; default:/* Driver matched but probe failed */ /* driver matched but the probe failed */ pr_warn("%s: probe of %s failed with error %d\n", drv->name, dev_name(dev), ret); } /* * Ignore errors returned by ->probe so that the next driver can try * its luck. */ ret = 0; done: atomic_dec(&probe_count);// Reduce probe count wake_up_all(&probe_waitqueue);// Wake up processes waiting for probe return ret; }
Analysis of the order of loading drivers and devices
Based on the previous analysis, whether we loaddevice.koordriver.ko, both drivers and devices can match successfully. So we can infer that whether it is a device driver or a driver driver, there will be a matching operation.
device_add()
In thedrivers/base/core.cfile, thedevice_addfunction calls thebus_probe_devicefunction
// to probe devices on the bus bus_probe_device(dev); if (parent)// If a parent device exists, add the current device to the parent device's child device list klist_add_tail(&dev->p->knode_parent, &parent->p->klist_children); // If the device has a class if (dev->class) { mutex_lock(&dev->class->p->mutex); ...
} EXPORT_SYMBOL_GPL(device_add);
bus_probe_device()
bus_probe_deviceThe most important part of the function is thedevice_initial_probefunction
// drivers/base/bus.c /** * bus_probe_device - probe drivers for a new device * @dev: device to probe * * - Automatically probe for a driver if the bus allows it. */ voidbus_probe_device(struct device *dev) { structbus_type *bus = dev->bus; structsubsys_interface *sif;
if (!bus) return;
if (bus->p->drivers_autoprobe) device_initial_probe(dev);
mutex_lock(&bus->p->mutex); list_for_each_entry(sif, &bus->p->interfaces, node) if (sif->add_dev) sif->add_dev(dev, sif); mutex_unlock(&bus->p->mutex); }
// drivers/base/dd.c staticint __device_attach(struct device *dev, bool allow_async) { int ret = 0; bool async = false;
device_lock(dev); if (dev->p->dead) { goto out_unlock; } elseif (dev->driver) { if (device_is_bound(dev)) {// returns 1 if the device is already bound to a driver ret = 1; goto out_unlock; } ret = device_bind_driver(dev);// attempts to bind the device to a driver if (ret == 0) ret = 1; else { dev->driver = NULL;// if binding fails, sets the device's driver pointer to NULL ret = 0; } } else { struct device_attach_data data = {// if the device has no driver, it needs to iterate over drivers on the bus for matching .dev = dev, .check_async = allow_async, .want_async = false, };
if (dev->parent)// if the device has a parent device, call pm_runtime_get_sync() increments the reference count of the parent device pm_runtime_get_sync(dev->parent); // iterate over drivers on the bus, call__device_attach_driver() for matching ret = bus_for_each_drv(dev->bus, NULL, &data, __device_attach_driver); if (!ret && allow_async && data.have_async) { /* * If we could not find appropriate driver * synchronously and we are allowed to do * async probes and there are drivers that * want to probe asynchronously, we'll * try them. */ /* * if a suitable driver cannot be found synchronously,and asynchronous probing is allowed and a driver requests asynchronous probing, * then attempt asynchronous probing。 */ dev_dbg(dev, "scheduling asynchronous probe\n"); get_device(dev);// increment the device's reference count to ensure the device is not freed during asynchronous probing async = true; } else { pm_request_idle(dev);// if asynchronous probing fails or no driver requests asynchronous probing, call pm_request_idle() enters idle state }
if (dev->parent)// if the device has a parent device, call pm_runtime_put() decrements the parent device's reference count pm_runtime_put(dev->parent); } out_unlock: device_unlock(dev); if (async) async_schedule_dev(__device_attach_async_helper, dev);// schedule an asynchronous task__device_attach_async_helper() performs asynchronous probing return ret; }
device_bind_driver()
used in the above functionsdevice_bind_driverbind device driver
// drivers/base/dd.c /** * device_bind_driver - bind a driver to one device. * @dev: device. * * Allow manual attachment of a driver to a device. * Caller must have already set @dev->driver. * * Note that this does not modify the bus reference count. * Please verify that is accounted for before calling this. * (It is ok to call with no other effort from a driver's probe() method.) * * This function must be called with the device lock held. */ intdevice_bind_driver(struct device *dev) { int ret;
ret = driver_sysfs_add(dev); if (!ret) driver_bound(dev); elseif (dev->bus) blocking_notifier_call_chain(&dev->bus->p->bus_notifier, BUS_NOTIFY_DRIVER_NOT_BOUND, dev); return ret; } EXPORT_SYMBOL_GPL(device_bind_driver);
staticvoiddriver_bound(struct device *dev) { if (device_is_bound(dev)) {// if the device is already bound to a driver, output a warning and return pr_warn("%s: device %s already bound\n", __func__, kobject_name(&dev->kobj)); return; }
pr_debug("driver: '%s': %s: bound to device '%s'\n", dev->driver->name, __func__, dev_name(dev)); // add the device to the driver's device list klist_add_tail(&dev->p->knode_driver, &dev->driver->p->klist_devices); device_links_driver_bound(dev);// update the device's driver link status
device_pm_check_callbacks(dev);// check the device's power management callback function
/* * Make sure the device is no longer in one of the deferred lists and * kick off retrying all pending devices */ /* * ensure the device is no longer in the deferred probing list,and initiate retry for all pending devices */ driver_deferred_probe_del(dev); driver_deferred_probe_trigger(); // if the device has a bus, call the bus notification chain to notify if (dev->bus) blocking_notifier_call_chain(&dev->bus->p->bus_notifier, BUS_NOTIFY_BOUND_DRIVER, dev); // send kernel object event notification kobject_uevent(&dev->kobj, KOBJ_BIND); }
The purpose of the above code is to bind the driver and the device. First, by callingdevice_is_bound(dev)to check whether the device has already been bound to a driver.
If the device is already bound to a driver, a warning message is output and the function returns.
If the device is not bound to a driver, binding information is output, including the driver’s name, function name, and device name. Next, by callingklist_add_tail()the device is added to the driver’s device list. In this way,the driver can access all bound devices by traversing this list.。
Then, callingdevice_links_driver_bound()updates the device’s driver link status. This function ensures that the link relationship between the device and the driver is correct.
Example Analysis of the Platform Bus Driver Registration Process
staticintplatform_drv_probe(struct device *_dev) { // Convert the device pointer passed to the driver into a platform_driver structure pointer structplatform_driver *drv = to_platform_driver(_dev->driver); // Convert the device pointer passed to the driver into a platform_device structure pointer structplatform_device *dev = to_platform_device(_dev); int ret; // Set the default clock properties of the device node ret = of_clk_set_defaults(_dev->of_node, false); if (ret < 0) return ret; // Attach the device to the power domain ret = dev_pm_domain_attach(_dev, true); if (ret) goto out; // Call the driver's probe function if (drv->probe) { ret = drv->probe(dev); if (ret) dev_pm_domain_detach(_dev, true); }
out: if (drv->prevent_deferred_probe && ret == -EPROBE_DEFER) {// Handle probe delays and error conditions dev_warn(_dev, "probe deferral not supported\n"); ret = -ENXIO; }
return ret; }
The main logic of this function is as follows:
First, the device pointer passed to the driver_devis converted toplatform_driverstructure pointer drv, the device pointer passed to the driver_devis converted toplatform_devicestructure pointer dev.
Use theof_clk_set_defaults()function to set the default clock properties of the device node. This function configures the device’s clock based on the attribute information of the device node.
Calldev_pm_domain_attach()to attach the device to the power domain. This function associates the device with the corresponding power domain based on the device’s power management requirements.
If the driver’sprobefunction exists, call it to perform the device probe operation.drv->probe(dev)indicates calling the driver’sprobefunction and passingplatform_devicestruct pointerstruct devas a parameter. If probing fails, it will calldev_pm_domain_detach()detach the device’s power domain.
Handle probe deferral and error conditions. If the driver sets theprevent_deferred_probeflag, and the return value is-EPROBE_DEFER, it indicates that probing is deferred. In this case, if the driver sets theprevent_deferred_probeflag, and the return value is-EPROBE_DEFER,it indicates that probing is deferred. In this case, the code prints a warning message “probe deferral not supported” and sets the return value to-ENXIO, indicating that the device does not exist.
Overall, the function performs the probe operation of the platform driver, calls the driver’sprobefunction on the device, and handles probe deferral and error conditions