1. Device Model
  2. kobject and kset
    1. struct kobject
      1. API
        1. kobject_create_and_add()
        2. kobject_init_and_add()
        3. kobject_put()
        4. kobject_get()
      2. Example
    2. struct kset
      1. API
        1. kset_create_and_add()
        2. kset_unregister()
      2. Example
  3. kref reference counter
    1. Common API Functions
      1. kref_init()
      2. kref_get()
      3. kref_put()
      4. refcount_set()
    2. How is Kobj released
      1. struct kobj_type
  4. Bus, Device, Driver, Class
    1. struct bus_type
    2. struct device
    3. struct device_driver
    4. struct class
  5. sysfs filesystem
    1. Source code analysis of file creation
      1. kobject_create_and_add()
      2. kobject_add()
      3. kobject_add_varg()
      4. kobject_add_internal()
      5. create_dir()
      6. sysfs_create_dir_ns()
    2. Analysis of sysfs directory hierarchy
      1. /sys/devices
      2. /sys/bus
      3. /sys/class
    3. Common sysfs APIs
      1. kobject_init_and_add()
      2. kobject_create_and_add()
      3. kobject_put()
      4. kobj_type
        1. struct kobj_type
        2. release()
        3. struct sysfs_ops
        4. default_attrs
      5. Attribute-related
        1. struct attribute
        2. struct kobj_attribute
        3. __ATTR macro
      6. sysfs_create_file()
      7. sysfs_remove_file()
      8. sysfs_create_group()
      9. sysfs_remove_group()
      10. struct attribute_group
    4. syfs create attribute example
      1. Usekobject_init_and_add()
      2. Usekobject_create_and_add()
      3. sysfs_create_group()Create multiple attribute files
  6. Register bus
    1. bus_register()
    2. bus_unregister()
    3. Example
    4. Create attributes under the bus directory
      1. bus_create_file()
      2. Example code
    5. Bus Registration Process Analysis
      1. bus_register()
      2. subsys_private()
      3. Linux subsystem
      4. Summary
    6. Analysis of platform bus registration process
      1. struct bus_type platform_bus_type
      2. platform_match()
  7. register a device under the bus
    1. Device Registration Process Analysis
      1. device_register()
      2. device_initialize()
      3. device_add()
      4. bus_add_device()
    2. Analysis of the platform bus device registration process
      1. platform_device_register()
      2. platform_device_add()
    3. Why register the device before registering the bus
  8. Registering a driver under the bus
    1. Driver Registration Process Analysis
      1. driver_register()
      2. bus_add_driver()
      3. Probe function execution flow analysis
        1. driver_attach()
        2. __driver_attach()
        3. driver_match_device()
        4. device_driver_attach()
        5. driver_probe_device()
        6. really_probe()
    2. Analysis of the order of loading drivers and devices
      1. device_add()
      2. bus_probe_device()
      3. device_initial_probe()
      4. __device_attach()
      5. device_bind_driver()
      6. driver_bound()
    3. Example Analysis of the Platform Bus Driver Registration Process
      1. platform_driver_register()
      2. __platform_driver_register()
      3. platform_drv_probe()
Cover image for Linux Device Model

Linux Device Model


Timeline

Timeline

2025-12-01

init

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.

Linux Driver Notes

Table of ContentsLinks
1. Linux Driver Framework
2. Linux Driver Loading Logic
3. Character Device Basics
4. Concurrency and Race Conditions
5. Advanced Character Device Progression
6. Interrupts
7. Platform Bus
8. Device Tree
9. Device Model
10. Hot Plug
11. pinctrl Subsystem
12. GPIO Subsystem
13. Input Subsystem
14. Single Bus
15. I2C
16. SPI
17. UART
18. PWM
19. RTC
20. Watchdog
21. CAN
22. Network Device
23. ADC
24. IIO
25. USB
26. LCD

Device Model

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 kernelkobjectis a structure that contains some attributes and methods describing the object. It provides a unified interface and mechanism for managing and operating kernel objects.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// include/linux/kobject.h
struct kobject {
const char *name;
struct list_head entry;
struct kobject *parent;
struct kset *kset;
struct kobj_type *ktype;
struct kernfs_node *sd; /* sysfs directory entry */
struct kref kref;
#ifdef CONFIG_DEBUG_KOBJECT_RELEASE
struct delayed_work release;
#endif
unsigned int state_initialized:1;
unsigned int state_in_sysfs:1;
unsigned int state_add_uevent_sent:1;
unsigned int state_remove_uevent_sent:1;
unsigned int uevent_suppress:1;
};
  • 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 krefUsed 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
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

Kobject tree relationship diagram
Kobject tree relationship diagram

API

kobject_create_and_add()

ItemDescription
Function Definitionstruct kobject *kobject_create_and_add(const char *name, struct kobject *parent);
Parameter namekobject name
Parameter parentparent directory
FunctionCreate + initialize + register kobject, will/syscreate a directory with this name under the directory
Return Valuekobject pointer

kobject_init_and_add()

ItemDescription
Function Definitionint kobject_init_and_add(struct kobject *kobj, const struct kobj_type *ktype, struct kobject *parent, const char *fmt, ...);
Parameter kobjkobject with allocated memory
Parameter ktypeType description structure
Parameter parentParent directory
Parameter fmtName format string
FunctionInitialize + add to sysfs
Typical scenarioCustom structure embedded in kobject
Return value0 success, others failure

kobject_put()

ItemDescription
Function definitionvoid kobject_put(struct kobject *kobj);
FunctionDecrease reference count
TriggerCount reaches 0 → release callback
Mustbe

kobject_get()

ItemDescription
Function definitionstruct kobject *kobject_get(struct kobject *kobj);
FunctionIncrease reference count
PurposePrevent premature release

Example

There are two ways to create a kobject

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kobject.h>
#include <linux/slab.h>

struct kobject *mykobject1;
struct kobject *mykobject2;
struct kobject *mykobject3;

struct kobj_type mytype;

static int __init kobject_test_init(void)
{
int ret = 0;

// Create kobject
// Method 1: kobject_create_and_add()
mykobject1 = kobject_create_and_add("mykobject01", NULL);
mykobject2 = kobject_create_and_add("mykobject02", mykobject1);

// Method 2: kzalloc() + kobject_init_and_add()
mykobject3 = kzalloc(sizeof(struct kobject), GFP_KERNEL);
ret = kobject_init_and_add(mykobject3, &mytype, NULL, "%s", "mykobject03");

return ret;
}
static void __exit kobject_test_exit(void)
{
kobject_put(mykobject3);
kobject_put(mykobject2);
kobject_put(mykobject1);
}

module_init(kobject_test_init);
module_exit(kobject_test_exit);

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 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.
*/
struct kset {
struct list_head list;
spinlock_t list_lock;
struct kobject kobj;
const struct kset_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_opsStructure 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
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.

API

kset_create_and_add()

ItemDescription
Function Definitionstruct kset *kset_create_and_add(const char *name, const struct kset_uevent_ops *uevent_ops, struct kobject *parent);
Header File#include <linux/kobject.h>
Parameter namekset name (directory name)
Parameter uevent_opsHotplug event callback (usually NULL)
Parameter parentParent kobject (NULL → /sys)
FunctionCreate and register a kset,/syscreate a directory with that name under
EffectCreate a directory in sysfs
Return valueSuccess: kset pointer; Failure: NULL

kset_unregister()

ItemDescription
Function definitionvoid kset_unregister(struct kset *k);
FunctionUnregister kset
EffectDelete sysfs directory
NoteAutomatically release reference

Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kobject.h>
#include <linux/slab.h>

// Define kobject pointer
struct kobject *mykobject01;
struct kobject *mykobject02;

// Define kset pointer
struct kset *mykset;

// Define kobj_type structure

struct kobj_type mytype;

static int __init kset_test_init(void)
{
int ret;

// Create and add mykset
mykset = kset_create_and_add("mykset", NULL, NULL);

// Create and add mykobject02
mykobject01 = kzalloc(sizeof(struct kobject), GFP_KERNEL);
mykobject01->kset = mykset;
ret = kobject_init_and_add(mykobject01, &mytype, NULL, "%s", "mykobject01");

// Create and add mykobject01
mykobject02 = kobject_create_and_add("mykobject02", mykobject01);

return 0;
}

static void __exit kset_test_exit(void)
{
// Release reference count of mykobject01
kobject_put(mykobject01);
// Release reference count of mykobject02
kobject_put(mykobject02);

kset_unregister(mykset);
}

module_init(kset_test_init);
module_exit(kset_test_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("This is a test sample for kset");

Test:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ 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

kref reference counter

1
2
3
4
5
6
7
8
9
10
11
12
13
// include/linux/kref.h
struct kref {
refcount_t refcount;
};
// include/linux/refcount.h
typedef struct refcount_struct {
atomic_t refs;
} refcount_t;

// include/linux/types.h
typedef struct {
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct device_node {
const char *name;
phandle phandle;
const char *full_name;
struct fwnode_handle fwnode;

struct property *properties;
struct property *deadprops; /* removed properties */
struct device_node *parent;
struct device_node *child;
struct device_node *sibling;
#if defined(CONFIG_OF_KOBJ)
struct kobject kobj;
#endif
unsigned long _flags;
void *data;
#if defined(CONFIG_SPARC)
unsigned int unique_id;
struct of_irq_controller *irq_trans;
#endif
};

Common API Functions

kref_init()

Function: Initialize astruct krefobject and set its reference count to1All reference-counted objects must call this function once before use.

Function Prototype

1
2
3
4
static inline void kref_init(struct kref *kref)
{
refcount_set(&kref->refcount, 1);
}

kref_get()

Function: Increment the reference count of kref by 1.

Function Prototype

1
2
3
4
static inline void kref_get(struct kref *kref)
{
refcount_inc(&kref->refcount);
}

Applicable Scenario: Whenever a structure is held or used by a new user, this function needs to be called to increase the reference count.

kref_put()

Function: Reference count**-1**, when the count reaches zero, callrelease()function to release resources (usually freeing memory).

Function prototype

1
2
3
4
5
6
7
8
static inline int kref_put(struct kref *kref, void (*release)(struct kref *kref))
{
if (refcount_dec_and_test(&kref->refcount)) {
release(kref);
return 1;
}
return 0;
}

Key points

  • 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
static inline void refcount_set(refcount_t *r, int n)
{
atomic_set(&r->refs, n);
}

How is Kobj released

There are two methods to create a kobject:

  1. 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.
  2. 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// lib/kobject.c
/**
* kobject_put() - Decrement refcount for object.
* @kobj: object.
*
* Decrement the refcount, and if 0, call kobject_cleanup().
*/
void kobject_put(struct kobject *kobj)
{
if (kobj) {
if (!kobj->state_initialized)
WARN(1, KERN_WARNING
"kobject: '%s' (%p): is not initialized, yet kobject_put() is being called.\n",
kobject_name(kobj), kobj);
kref_put(&kobj->kref, kobject_release);
}
}
EXPORT_SYMBOL(kobject_put);


static void kobject_release(struct kref *kref)
{
struct kobject *kobj = container_of(kref, struct kobject, kref);
#ifdef CONFIG_DEBUG_KOBJECT_RELEASE
unsigned long delay = HZ + HZ * (get_random_int() & 0x3);
pr_info("kobject: '%s' (%p): %s, parent %p (delayed %ld)\n",
kobject_name(kobj), kobj, __func__, kobj->parent, delay);
INIT_DELAYED_WORK(&kobj->release, kobject_delayed_cleanup);

schedule_delayed_work(&kobj->release, delay);
#else
kobject_cleanup(kobj);
#endif
}

It can be seen that when kref is 0, it will callkobject_release(), and this function will callkobject_cleanup()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// lib/kobject.c
/*
* kobject_cleanup - free kobject resources.
* @kobj: object to cleanup
*/
static void kobject_cleanup(struct kobject *kobj)
{
struct kobject *parent = kobj->parent;
struct kobj_type *t = get_ktype(kobj);
const char *name = kobj->name;

pr_debug("kobject: '%s' (%p): %s, parent %p\n",
kobject_name(kobj), kobj, __func__, kobj->parent);

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

if (t && t->release) {
pr_debug("kobject: '%s' (%p): calling ktype release\n",
kobject_name(kobj), kobj);
t->release(kobj);
}

/* 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.

struct kobj_type

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// include/linux/kobject.h
struct kobj_type {
void (*release)(struct kobject *kobj);
const struct sysfs_ops *sysfs_ops;
struct attribute **default_attrs; /* use default_groups instead */
const struct attribute_group **default_groups;
const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
const void *(*namespace)(struct kobject *kobj);
void (*get_ownership)(struct kobject *kobj, kuid_t *uid, kgid_t *gid);
};

// lib/kobject.c
struct kobject *kobject_create(void)
{
struct kobject *kobj;

kobj = kzalloc(sizeof(*kobj), GFP_KERNEL);
if (!kobj)
return NULL;

kobject_init(kobj, &dynamic_kobj_ktype);
return kobj;
}

// lib/kobject.c
void kobject_init(struct kobject *kobj, struct kobj_type *ktype)
{
...
kobject_init_internal(kobj);
kobj->ktype = ktype;
return;
...
}
EXPORT_SYMBOL(kobject_init);

Anddynamic_kobj_ktypeis defined as:

1
2
3
4
5
6
7
8
9
10
11
// lib/kobject.c
static void dynamic_kobj_release(struct kobject *kobj)
{
pr_debug("kobject: (%p): %s\n", kobj, __func__);
kfree(kobj);
}

static struct kobj_type dynamic_kobj_ktype = {
.release = dynamic_kobj_release,
.sysfs_ops = &kobj_sysfs_ops,
};

Bus, Device, Driver, Class

The device model includes the following four concepts:

  1. 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.
  2. DeviceA 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.
  3. DriverA 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.
  4. 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
Platform bus

struct bus_type

The bus_type structure is a data structure in the Linux kernel used to describe a bus.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
// include/linux/device/bus.h

/**
* struct bus_type - The bus type of the device
*
* @name: The name of the bus.
* @dev_name: Used for subsystems to enumerate devices like ("foo%u", dev->id).
* @dev_root: Default device to use as the parent.
* @bus_groups: Default attributes of the bus.
* @dev_groups: Default attributes of the devices on the bus.
* @drv_groups: Default attributes of the device drivers on the bus.
* @match: Called, perhaps multiple times, whenever a new device or driver
* is added for this bus. It should return a positive value if the
* given device can be handled by the given driver and zero
* otherwise. It may also return error code if determining that
* the driver supports the device is not possible. In case of
* -EPROBE_DEFER it will queue the device for deferred probing.
* @uevent: Called when a device is added, removed, or a few other things
* that generate uevents to add the environment variables.
* @probe: Called when a new device or driver add to this bus, and callback
* the specific driver's probe to initial the matched device.
* @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.
*/
struct bus_type {
const char *name; //Name of the bus type
const char *dev_name;//Bus device name
struct device *dev_root;//Root device of the bus device
const struct attribute_group **bus_groups;//Attribute group of the bus type
const struct attribute_group **dev_groups;//Attribute group of the device
const struct attribute_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

const struct dev_pm_ops *pm;// Device power management operations

const struct iommu_ops *iommu_ops;// Device IOMMU operations

struct subsys_private *p; // Subsystem private data
struct lock_class_key lock_key; // Lock class key for lock mechanism

bool need_parent_lock; // Whether parent lock is needed
};

struct device

The device structure is a data structure in the Linux kernel used to describe devices

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
// 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.
*/
struct device {

struct kobject kobj; //Corresponding kobj
struct device *parent; // Parent device of the device

struct device_private *p; // Private pointer

const char *init_name; /* initial name of the device */ //Device initialization name
const struct device_type *type;

struct bus_type *bus; /* type of bus device is on */ //Bus to which the device belongs
struct device_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
struct mutex lockdep_mutex;
#endif
struct mutex mutex; /* mutex to synchronize calls to
* its driver.
*/

struct dev_links_info links;
struct dev_pm_info power;
struct dev_pm_domain *pm_domain;

#ifdef CONFIG_ENERGY_MODEL
struct em_perf_domain *em_pd;
#endif

#ifdef CONFIG_GENERIC_MSI_IRQ_DOMAIN
struct irq_domain *msi_domain;
#endif
#ifdef CONFIG_PINCTRL
struct dev_pin_info *pins;
#endif
#ifdef CONFIG_GENERIC_MSI_IRQ
raw_spinlock_t msi_lock;
struct list_head msi_list;
#endif
#ifdef CONFIG_DMA_OPS
const struct dma_map_ops *dma_ops;
#endif
u64 *dma_mask; /* dma mask (if dma'able device) */
u64 coherent_dma_mask;/* Like dma_mask, but for
alloc_coherent mappings as
not all hardware supports
64 bit addresses for consistent
allocations such descriptors. */
u64 bus_dma_limit; /* upstream dma constraint */
const struct bus_dma_region *dma_range_map;

struct device_dma_parameters *dma_parms;

struct list_head dma_pools; /* dma pools (if dma'ble) */

#ifdef CONFIG_DMA_DECLARE_COHERENT
struct dma_coherent_mem *dma_mem; /* internal for coherent mem
override */
#endif
#ifdef CONFIG_DMA_CMA
struct cma *cma_area; /* contiguous memory area for dma
allocations */
#endif
/* arch specific additions */
struct dev_archdata archdata;

struct device_node *of_node; /* associated device tree node */
struct fwnode_handle *fwnode; /* firmware device node */

#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 */

spinlock_t devres_lock;
struct list_head devres_head;

struct class *class; //Class to which the device belongs
const struct attribute_group **groups; /* optional groups */ //Device attribute group

void (*release)(struct device *dev);
struct iommu_group *iommu_group;
struct dev_iommu *iommu;

enum device_removable removable;

bool offline_disabled:1;
bool offline:1;
bool of_node_reused:1;
bool state_synced:1;
#if defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_DEVICE) || \
defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU) || \
defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU_ALL)
bool dma_coherent:1;
#endif
#ifdef CONFIG_DMA_OPS_BYPASS
bool dma_ops_bypass : 1;
#endif
};

struct device_driver

struct device_driverIs a data structure in the Linux kernel that describes a device driver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
// 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.
*/
struct device_driver {
const char *name;// Device driver name
struct bus_type *bus; // Bus type to which the device driver belongs

struct module *owner; // Module that owns this driver
const char *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
enum probe_type probe_type; // Probe type, used to specify the probing method

const struct of_device_id *of_match_table; // Device match table
const struct acpi_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
const struct attribute_group **groups; // Driver attribute group
const struct attribute_group **dev_groups;

const struct dev_pm_ops *pm; // Power management operations
void (*coredump) (struct device *dev);//Device core dump function

struct driver_private *p; //Driver private data.
};

struct class

struct classIs a data structure in the Linux kernel that describes device classes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// 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.
*/
struct class {
const char *name; //Name of the device class
struct module *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
const struct attribute_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
const struct attribute_group **dev_groups;
// device kernel object
struct kobject *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

const struct kobj_ns_type_operations *ns_type;// namespace type operation
const void *(*namespace)(struct device *dev);// namespace function

void (*get_ownership)(struct device *dev, kuid_t *uid, kgid_t *gid);// function to acquire device ownership

const struct dev_pm_ops *pm;// power management operation

struct subsys_private *p;// subsystem private data
};

sysfs filesystem

sysfs filesystem is a type ofvirtual filesystemused 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
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)

kobject_create_and_add()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 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(const char *name, struct kobject *parent)
{
struct kobject *kobj;
int retval;

kobj = kobject_create();
if (!kobj)
return NULL;

retval = kobject_add(kobj, parent, "%s", name);
if (retval) {
pr_warn("%s: kobject_add error: %d\n", __func__, retval);
kobject_put(kobj);
kobj = NULL;
}
return kobj;
}
EXPORT_SYMBOL_GPL(kobject_create_and_add);

kobject_add()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/**
* 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.
*/
int kobject_add(struct kobject *kobj, struct kobject *parent,
const char *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);

return retval;
}
EXPORT_SYMBOL(kobject_add);

kobject_add_varg()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static __printf(3, 0) int kobject_add_varg(struct kobject *kobj,
struct kobject *parent,
const char *fmt, va_list vargs)
{
int retval;

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

kobject_add_internal()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
static int kobject_add_internal(struct kobject *kobj)
{
int error = 0;
struct kobject *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;
}

pr_debug("kobject: '%s' (%p): %s: parent: '%s', set: '%s'\n",
kobject_name(kobj), kobj, __func__,
parent ? kobject_name(parent) : "<NULL>",
kobj->kset ? kobject_name(&kobj->kset->kobj) : "<NULL>");

error = create_dir(kobj);
if (error) {
kobj_kset_leave(kobj);
kobject_put(parent);
kobj->parent = NULL;

/* 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;

return error;
}

create_dir()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
static int create_dir(struct kobject *kobj)
{
const struct kobj_type *ktype = get_ktype(kobj);
const struct kobj_ns_type_operations *ops;
int error;

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

return 0;
}

It can be seen thaterror = create_dir(kobj);a folder is created in the file system

sysfs_create_dir_ns()

sysfs_create_dir_nsThe function is as follows

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 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
*/
int sysfs_create_dir_ns(struct kobject *kobj, const void *ns)
{
struct kernfs_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;

if (!parent)
return -ENOENT;

kobject_get_ownership(kobj, &uid, &gid);

kn = kernfs_create_dir_ns(parent, kobject_name(kobj),
S_IRWXU | S_IRUGO | S_IXUGO, uid, gid,
kobj, ns);
if (IS_ERR(kn)) {
if (PTR_ERR(kn) == -EEXIST)
sysfs_warn_dup(parent, kobject_name(kobj));
return PTR_ERR(kn);
}

kobj->sd = kn;
return 0;
}

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// fs/sysfs/mount.c
static struct file_system_type sysfs_fs_type = {
.name = "sysfs",
.init_fs_context = sysfs_init_fs_context,
.kill_sb = sysfs_kill_sb,
.fs_flags = FS_USERNS_MOUNT,
};

int __init sysfs_init(void)
{
int err;

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

return 0;
}

Through the above analysis of the API functions, we can summarize the rules for creating directories, as follows:

  1. Without a parent directory and without a kset, the directory will be created under the root directory of sysfs (i.e.,/sys).
  2. Without a parent directory but with a kset, the directory will be created under the kset, and the kobj will be added tokset.list
  3. With a parent directory but without a kset, the directory will be created under the parent.
  4. 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/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
/sys/bus

For example, devices connected under the I2C bus are shown as follows:

/sys/bus/i2c
/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
/sys/class

The benefits of using class for categorization are as follows:

  1. 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.
  2. 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.
  3. 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.
  4. 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:

1
echo 1 > /sys/class/gpio/gpio157/value

If not using a class, use the following command:

1
echo 1 > /sys/devices/platform/fe770000.gpio/gpiochip4/gpio/gpio157/value

The sys directory structure is as follows
The sys directory structure is as follows

Common sysfs APIs

kobject_init_and_add()

ItemDescription
Function Definitionint kobject_init_and_add(struct kobject *kobj, const struct kobj_type *ktype, struct kobject *parent, const char *fmt, ...);
Header File#include <linux/kobject.h>
Parameter kobjAllocated Memory kobject
Parameter ktypekobject Type (Attributes + Callbacks)
Parameter parentParent Directory
Parameter fmtsysfs Directory Name
FunctionInitialize and Register kobject
EffectCreate Directory in sysfs
Return ValueSuccess: 0

kobject_create_and_add()

ItemDescription
Function Definitionstruct kobject *kobject_create_and_add(const char *name, struct kobject *parent);
FunctionAllocation + Initialization + Registration
Applicable ScenarioSimple kobject
Return Valuekobject pointer

kobject_put()

ItemDescription
Function Definitionvoid kobject_put(struct kobject *kobj);
FunctionDecrease Reference Count
triggercount becomes 0 → release
mustis

kobj_type

struct kobj_type

memberdescription
releaserelease function (required)
sysfs_opssysfs read/write callback
default_attrsdefault attribute array

release()

itemdescription
prototypevoid (*release)(struct kobject *kobj);
functionfree memory
mustMust implement

struct sysfs_ops

MemberDescription
showUnified read callback
storeUnified write callback

default_attrs

ItemDescription
Positionkobj_type
Typestruct attribute **
FunctionAuto-create attribute
RequirementNULL-terminated

Example:

1
2
3
4
5
struct attribute *attrs[] = {
&attr1.attr,
&attr2.attr,
NULL
};

struct attribute

MemberDescription
nameAttribute file name
modePermissions (0644 / 0664)

struct kobj_attribute

MemberDescription
attrstruct attribute
showRead function
storeWrite function

__ATTR macro

ItemDescription
Macro definition__ATTR(name, mode, show, store)
FunctionDefine attribute object
Generated typestruct kobj_attribute

sysfs_create_file()

ItemDescription
Function Definitionint sysfs_create_file(struct kobject *kobj, const struct attribute *attr);
FunctionManually Add Property File
Applicablekobject_create_and_add Scenario
Return Value0 Success

sysfs_remove_file()

ItemDescription
Function Definitionvoid sysfs_remove_file(struct kobject *kobj, const struct attribute *attr);
FunctionDelete Property File

sysfs_create_group()

ItemDescription
Function Definitionint sysfs_create_group(struct kobject *kobj, const struct attribute_group *grp);
FunctionCreate Attribute Group (Directory + Multiple Files)
Return Value0 Success

sysfs_remove_group()

ItemDescription
Function Definitionvoid sysfs_remove_group(struct kobject *kobj, const struct attribute_group *grp);
FunctionDelete Attribute Group
RequiredPaired with Create

struct attribute_group

MemberDescription
nameSubdirectory name (NULL → no directory created)
attrsAttribute array
is_visibleDynamic visibility control

syfs create attribute example

Usekobject_init_and_add()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kobject.h>
#include <linux/slab.h>

// Custom struct
struct mykobj {
struct kobject kobj; // Embed kobject into custom struct
int value1;
int value2;
};

static struct mykobj *mykobjp;

void mykobj_release(struct kobject *kobj)
{
struct mykobj *p = container_of(kobj, struct mykobj, kobj);
pr_info("mykobj (%p) free: %s\n", p, __func__);
kfree(p);
}

struct attribute myattr1 = {
.name = "myattr1",
.mode = 0666,
};
struct attribute myattr2 = {
.name = "myattr2",
.mode = 0666,
};

struct attribute *mykobj_default_attrs[] = {
&myattr1,
&myattr2,
NULL,
};

ssize_t myshow(struct kobject *kobj, struct attribute *attr, char *buf)
{
ssize_t count;
struct mykobj *mykobj = container_of(kobj, struct mykobj, kobj);
if (strcmp(attr->name, "myattr1") == 0) {
count = sprintf(buf, "%d\n", mykobj->value1);
} else if (strcmp(attr->name, "myattr2") == 0) {
count = sprintf(buf, "%d\n", mykobj->value2);
} else {
count = 0;
}

return count;
}
ssize_t mystore(struct kobject *kobj, struct attribute *attr, const char *buf, size_t size)
{
struct mykobj *mykobj = container_of(kobj, struct mykobj, kobj);
if (strcmp(attr->name, "myattr1") == 0) {
sscanf(buf, "%d\n", &mykobj->value1);
} else if (strcmp(attr->name, "myattr2") == 0) {
sscanf(buf, "%d\n", &mykobj->value2);
}
return size;
}

const struct sysfs_ops my_sysfs_ops = {
.show = myshow,
.store = mystore,
};

struct kobj_type mytype = {
.release = mykobj_release,
.default_attrs = mykobj_default_attrs,
.sysfs_ops = &my_sysfs_ops,
};

static int __init sysfs_attribute_test_init(void)
{
int ret;
mykobjp = kzalloc(sizeof(struct mykobj), GFP_KERNEL);
if (!mykobjp)
return -ENOMEM;
ret = kobject_init_and_add(&mykobjp->kobj, &mytype, NULL, "mykobject");

return 0;
}

static void __exit sysfs_attribute_test_exit(void)
{
kobject_put(&mykobjp->kobj);
}

module_init(sysfs_attribute_test_init);
module_exit(sysfs_attribute_test_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("even629<asqwgo@outlook.com>");
MODULE_DESCRIPTION("This is a sample for sysfs attribute");

Test:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

$ insmod sysfs_attribute_test.ko
[ 33.038129] sysfs_attribute_test: loading out-of-tree module taints kernel.
$ cd /sys
/sys $ ls
block class devices fs module power
bus dev firmware kernel mykobject
/sys $ cd mykobject/
/sys/mykobject $ ls
myattr1 myattr2
/sys/mykobject $ cat myattr1
0
/sys/mykobject $ echo 3 > myattr1
/sys/mykobject $ cat myattr1
3
/sys/mykobject $ echo 4 > myattr2
/sys/mykobject $ cat myattr2
4
/sys/mykobject $ lsmod
sysfs_attribute_test 16384 0 - Live 0xffffffc008b30000 (O)
/sys/mykobject $ cd ../
/sys $ rmmod sysfs_attribute_test.ko
[ 108.441057] mykobj ((____ptrval____)) free: mykobj_release
/sys $ cd
/sys $ ls
block class devices fs module
bus dev firmware kernel power

Can optimize attribute file read/write, each attribute corresponds to a read/write function, usingkobj_attributeEncapsulation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kobject.h>
#include <linux/slab.h>

// Custom struct
struct mykobj {
struct kobject kobj; // Embed kobject into custom struct
};

static struct mykobj *mykobjp;

void mykobj_release(struct kobject *kobj)
{
struct mykobj *p = container_of(kobj, struct mykobj, kobj);
pr_info("mykobj (%p) free: %s\n", p, __func__);
kfree(p);
}

// Define attribute objects myattr1 and myattr2
struct myattribute{
struct kobj_attribute kobj_attr;
int value;
};

// Custom show function for reading attribute values
ssize_t myattr1_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
ssize_t count;
struct myattribute *myattr = container_of(attr, struct myattribute, kobj_attr);
count = sprintf(buf, "%d\n", myattr->value);
return count;
}

// Custom store function for writing attribute values
ssize_t myattr1_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf,
size_t size)
{
struct myattribute *myattr = container_of(attr, struct myattribute, kobj_attr);
sscanf(buf, "%d\n", &myattr->value);
return size;
}

// Custom show function for reading attribute values
ssize_t myattr2_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
ssize_t count;
struct myattribute *myattr = container_of(attr, struct myattribute, kobj_attr);
count = sprintf(buf, "%d\n", myattr->value);
return count;
}

// Custom store function for writing attribute values
ssize_t myattr2_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf,
size_t size)
{
struct myattribute *myattr = container_of(attr, struct myattribute, kobj_attr);
sscanf(buf, "%d\n", &myattr->value);
return size;
}


static struct myattribute myattr1 = {
.kobj_attr = __ATTR(myattr1, 0664, myattr1_show, myattr1_store),
.value = 0,
};

static struct myattribute myattr2 = {
.kobj_attr = __ATTR(myattr2, 0664, myattr2_show, myattr2_store),
.value = 0,
};


struct attribute *mykobj_default_attrs[] = {
&myattr1.kobj_attr.attr,
&myattr2.kobj_attr.attr,
NULL,
};

ssize_t myshow(struct kobject *kobj, struct attribute *attr, char *buf)
{
struct kobj_attribute *kobj_attr = container_of(attr, struct kobj_attribute, attr);
return kobj_attr->show(kobj, kobj_attr, buf);
}
ssize_t mystore(struct kobject *kobj, struct attribute *attr, const char *buf, size_t size)
{
struct kobj_attribute *kobj_attr = container_of(attr, struct kobj_attribute, attr);
return kobj_attr->store(kobj, kobj_attr, buf, size);
}

const struct sysfs_ops my_sysfs_ops = {
.show = myshow,
.store = mystore,
};

struct kobj_type mytype = {
.release = mykobj_release,
.default_attrs = mykobj_default_attrs,
.sysfs_ops = &my_sysfs_ops,
};

static int __init sysfs_attribute_test_init(void)
{
int ret;
mykobjp = kzalloc(sizeof(struct mykobj), GFP_KERNEL);
if (!mykobjp)
return -ENOMEM;
ret = kobject_init_and_add(&mykobjp->kobj, &mytype, NULL, "mykobject");

return 0;
}

static void __exit sysfs_attribute_test_exit(void)
{
kobject_put(&mykobjp->kobj);
}

module_init(sysfs_attribute_test_init);
module_exit(sysfs_attribute_test_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("even629<asqwgo@outlook.com>");
MODULE_DESCRIPTION("This is a sample for sysfs attribute");

Test:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ insmod sysfs_attribute_improved_test.ko
[ 115.098529] sysfs_attribute_improved_test: loading out-of-tree module taints kernel.
$ cd /sys/mykobject/
/sys/mykobject $ ls
myattr1 myattr2
/sys/mykobject $ cat myattr1
0
/sys/mykobject $ echo 1 > myattr1
/sys/mykobject $ cat myattr1
1
/sys/mykobject $ cd /sys
/sys $ rmmod sysfs_attribute_improved_test.ko
[ 149.750265] mykobj ((____ptrval____)) free: mykobj_release
/sys $ ls

block class devices fs module
bus dev firmware kernel power

Usekobject_create_and_add()

Usekobject_create_and_add(), need to callsysfs_create_fileAdd attribute file

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kobject.h>
#include <linux/slab.h>

static int value1;
static int value2;

// Custom show function for reading attribute values
ssize_t myattr1_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
return sprintf(buf, "%d\n", value1);
}

// Custom store function for writing attribute values
ssize_t myattr1_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf,
size_t size)
{
sscanf(buf, "%d\n", &value1);
return size;
}

// Custom show function for reading attribute values
ssize_t myattr2_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
return sprintf(buf, "%d\n", value2);
}

// Custom store function for writing attribute values
ssize_t myattr2_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf,
size_t size)
{
sscanf(buf, "%d\n", &value2);
return size;
}

static struct kobj_attribute kobj_attr1 = __ATTR(myattr1, 0664, myattr1_show, myattr1_store);
static struct kobj_attribute kobj_attr2 = __ATTR(myattr2, 0664, myattr2_show, myattr2_store);

static struct kobject *mykobj;

static int __init sysfs_attribute_test_init(void)
{
int ret;

mykobj = kobject_create_and_add("mykobject", NULL);
ret = sysfs_create_file(mykobj, &kobj_attr1.attr);
ret = sysfs_create_file(mykobj, &kobj_attr2.attr);

return 0;
}

static void __exit sysfs_attribute_test_exit(void)
{
kobject_put(mykobj);
}

module_init(sysfs_attribute_test_init);
module_exit(sysfs_attribute_test_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("even629<asqwgo@outlook.com>");
MODULE_DESCRIPTION("This is a sample for sysfs attribute");

Test:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ insmod sysfs_attribute_another_way.ko
[ 14.955347] sysfs_attribute_another_way: loading out-of-tree module taints kernel.
$ cd /sys/mykobject/
/sys/mykobject $ ls
myattr1 myattr2
/sys/mykobject $ cat myattr1
0
/sys/mykobject $ echo 122 > myattr1
/sys/mykobject $ cat myattr1
122
/sys/mykobject $ cd /sys
/sys $ rmmod sysfs_attribute_another_way.ko
/sys $ ls
block class devices fs module
bus dev firmware kernel power

sysfs_create_group()Create multiple attribute files

sysfs_create_group()Used to create an attribute group (directory + multiple attribute files) for a kobject in sysfs.

Function prototype

1
int sysfs_create_group(struct kobject *kobj, const struct attribute_group *grp);

Parameter Description

1.kobj

  • Points to astruct kobject
  • 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.

struct attribute_group structure

1
2
3
4
5
6
7
struct attribute_group {
const char *name;
const struct attribute **attrs;
mode_t (*is_visible)(struct kobject *kobj,
struct attribute *attr,
int index);
};

Field Description:

MemberDescription
nameGroup name. If not NULL, a directory will be created under sysfs.
attrsAttribute array, terminated by NULL; each entry is astruct attribute *
is_visible(Optional) Determines attribute visibility based on index; if not visible, the attribute file will not be created.

Example

  • Define Attribute File
1
2
3
4
5
static struct kobj_attribute attr1 =
__ATTR(attr1, 0644, attr1_show, attr1_store);

static struct kobj_attribute attr2 =
__ATTR(attr2, 0644, attr2_show, attr2_store);
  • Create Attribute Array (terminated by NULL)
1
2
3
4
5
struct attribute *attrs[] = {
&attr1.attr,
&attr2.attr,
NULL,
};
  • Define attribute group
1
2
3
4
const struct attribute_group attr_group = {
.name = "my_group",
.attrs = attrs,
};
  • Register attribute group
1
sysfs_create_group(kobj, &attr_group);
  • Delete attribute group (must be used in pairs)

Must be deleted when module exits or device is removed:

1
sysfs_remove_group(kobj, &attr_group);

Driver:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kobject.h>
#include <linux/slab.h>

static int value1;
static int value2;

// Custom show function for reading attribute values
ssize_t myattr1_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
return sprintf(buf, "%d\n", value1);
}

// Custom store function for writing attribute values
ssize_t myattr1_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf,
size_t size)
{
sscanf(buf, "%d\n", &value1);
return size;
}

// Custom show function for reading attribute values
ssize_t myattr2_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
return sprintf(buf, "%d\n", value2);
}

// Custom store function for writing attribute values
ssize_t myattr2_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf,
size_t size)
{
sscanf(buf, "%d\n", &value2);
return size;
}

static struct kobj_attribute kobj_attr1 = __ATTR(myattr1, 0664, myattr1_show, myattr1_store);
static struct kobj_attribute kobj_attr2 = __ATTR(myattr2, 0664, myattr2_show, myattr2_store);

struct attribute *attr_array[] = {
&kobj_attr1.attr,
&kobj_attr2.attr,
NULL,
};

const struct attribute_group attr_grp = {
.name = "mygroup",
.attrs = attr_array,
};

static struct kobject *mykobj;

static int __init sysfs_attribute_test_init(void)
{
int ret;
mykobj = kobject_create_and_add("mykobject", NULL);
ret = sysfs_create_group(mykobj, &attr_grp);

return ret;
}

static void __exit sysfs_attribute_test_exit(void)
{
sysfs_remove_group(mykobj, &attr_grp);
kobject_put(mykobj);
}

module_init(sysfs_attribute_test_init);
module_exit(sysfs_attribute_test_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("even629<asqwgo@outlook.com>");
MODULE_DESCRIPTION("This is a sample for sysfs attribute");

Test:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ insmod sysfs_attr_group_test.ko
[ 13.889636] sysfs_attr_group_test: loading out-of-tree module taints kernel.
$ cd /sys/
/sys $ ls
block class devices fs module power
bus dev firmware kernel mykobject
/sys $ cd mykobject/
/sys/mykobject $ ls
mygroup
/sys/mykobject $ cd mygroup/
/sys/mykobject/mygroup $ ls
myattr1 myattr2
/sys/mykobject/mygroup $ cat myattr2
0
/sys/mykobject/mygroup $ echo 2 > myattr2
/sys/mykobject/mygroup $ cat myattr2
2
/sys/mykobject/mygroup $ cd /sys
/sys $ rmmod sysfs_attr_group_test.ko
/sys $ ls
block class devices fs module
bus dev firmware kernel power

More references:

In sysfs, high-version kernels recommend using sys_emit for printing, low-version kernels use scnprintf

Register bus

bus_register()

ProjectDescription
Function Definitionint bus_register(struct bus_type *bus);
Header File#include <linux/device/bus.h>or#include <linux/device.h>
Parameter buspointer tostruct bus_typepointer, representing the custom bus to be registered
FunctionRegisters a custom bus with the Linux kernel, enabling the kernel to recognize the bus and provide a device-driver matching mechanism
Return ValueSuccess: returns 0;
Failure: returns a negative error code (e.g.,-EINVAL,-ENOMEMetc.)

bus_unregister()

ItemDescription
Function Definitionvoid bus_unregister(struct bus_type *bus);
Header file#include <linux/device/bus.h>or#include <linux/device.h>
Parameter buspointer tostruct bus_typepointer to the custom bus to be unregistered
FunctionUnregister a registered custom bus, release related resources, and make it no longer visible in the kernel
Return valueNo return value

Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>

int mybus_match(struct device *dev, struct device_driver *drv)
{
return (strcmp(dev_name(dev), drv->name) == 0);
}

int mybus_probe(struct device *dev)
{
struct device_driver *drv = dev->driver;
if (drv->probe)
drv->probe(dev);

return 0;
}

struct bus_type mybus_type = {
.name = "mybus",
.match = mybus_match,
.probe = mybus_probe,
};

static int __init my_own_bus_test_init(void)
{
int ret;
ret = bus_register(&mybus_type);
return ret;
}

static void __exit my_own_bus_test_exit(void)
{
bus_unregister(&mybus_type);
}

module_init(my_own_bus_test_init);
module_exit(my_own_bus_test_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("even629<asqwgo@outlook.com>");
MODULE_DESCRIPTION("This is a test sample for bus");

Test:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ insmod bus_register_test.ko
[ 19.436050] bus_register_test: loading out-of-tree module taints kernel.
$ ls /sys/bus/
amba gpio mmc_rpmb serial
clockevents hid mybus soc
clocksource i2c nvmem spi
container iscsi_flashnode pci usb
cpu mdio_bus platform workqueue
event_source mipi-dsi scsi
genpd mmc sdio
$ ls /sys/bus/mybus
devices drivers_autoprobe uevent
drivers drivers_probe
$ rmmod bus_register_test.ko
$ ls /sys/bus/mybus
ls: /sys/bus/mybus: No such file or directory

Create attributes under the bus directory

bus_create_file()

ItemDescription
Function definitionint bus_create_file(struct bus_type *bus, struct kobject *kobj, const struct attribute *attr);
Header file#include <linux/device/bus.h>or#include <linux/device.h>
Parameter buspointer tostruct bus_typepointer, indicating on which bus to create the attribute file
Parameter kobjpointer tostruct kobjectpointer, indicating under which directory to create the attribute file (usually the kobject of the bus)
Parameter attrpointer tostruct attributepointer, indicating the sysfs attribute file to create (including name, permissions, etc.)
FunctionCreate an attribute file under the sysfs directory corresponding to the bus (e.g.,/sys/bus/<busname>/value
Return valueSuccess: returns 0;
Failure: negative error code

Attribute structure (struct bus_attribute) example description table

FieldDescription
attr.nameAttribute file name
attr.modeFile permissions, e.g.0664
showsysfs read callback (for cat reading)
storesysfs write callback (for echo writing)

Example usage:

1
2
3
4
5
6
7
8
9
struct bus_attribute mybus_attr = {
.attr = {
.name = "value",
.mode = 0664,
},
.show = mybus_show,
};

ret = bus_create_file(&mybus, &mydevice.kobj, &mybus_attr.attr);

Example code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/configfs.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
#include <linux/device.h>
#include <linux/sysfs.h>

int mybus_match(struct device *dev, struct device_driver *drv)
{
// Check whether device name and driver name match
return (strcmp(dev_name(dev), drv->name) == 0);
};

int mybus_probe(struct device *dev)
{
struct device_driver *drv = dev->driver;
if (drv->probe)
drv->probe(dev);
return 0;
};

struct bus_type mybus = {
.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_t mybus_show(struct bus_type *bus, char *buf)
{
// Display the bus value in sysfs
return sprintf(buf, "%s\n", "mybus_show");
};

struct bus_attribute mybus_attr = {
.attr = {
.name = "value", // Attribute name
.mode = 0664, // Attribute access permissions
},
.show = mybus_show, // Attribute show callback function
};

// Module initialization function
static int bus_init(void)
{
int ret;
ret = bus_register(&mybus); // Register bus
ret = bus_create_file(&mybus, &mybus_attr); // Create attribute file in sysfs

return 0;
}

// Module exit function
static void bus_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

MODULE_LICENSE("GPL"); // Module license
MODULE_AUTHOR("topeet"); // Module author

Test:

1
2
3
4
5
6
7
8
9
10
11
12
$ 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

Bus Registration Process Analysis

bus_register()

struct bus_typeStructure:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// include/linux/device/bus.h

struct bus_type {
const char *name;
const char *dev_name;
struct device *dev_root;// device structure, dev_root
const struct attribute_group **bus_groups;
const struct attribute_group **dev_groups;
const struct attribute_group **drv_groups;

int (*match)(struct device *dev, struct device_driver *drv);
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev);
void (*sync_state)(struct device *dev);
int (*remove)(struct device *dev);
void (*shutdown)(struct device *dev);

int (*online)(struct device *dev);
int (*offline)(struct device *dev);

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

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

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

const struct dev_pm_ops *pm;

const struct iommu_ops *iommu_ops;

struct subsys_private *p;
struct lock_class_key lock_key;

bool need_parent_lock;
};

It can be seen thatstruct bus_typethe structure containsstruct devicestructure, while the device structure contains kobject

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
// include/linux/device.h
struct device {
struct kobject kobj;
struct device *parent;

struct device_private *p;

const char *init_name; /* initial name of the device */
const struct device_type *type;

struct bus_type *bus; /* type of bus device is on */
struct device_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
struct mutex lockdep_mutex;
#endif
struct mutex mutex; /* mutex to synchronize calls to
* its driver.
*/

struct dev_links_info links;
struct dev_pm_info power;
struct dev_pm_domain *pm_domain;

#ifdef CONFIG_ENERGY_MODEL
struct em_perf_domain *em_pd;
#endif

#ifdef CONFIG_GENERIC_MSI_IRQ_DOMAIN
struct irq_domain *msi_domain;
#endif
#ifdef CONFIG_PINCTRL
struct dev_pin_info *pins;
#endif
#ifdef CONFIG_GENERIC_MSI_IRQ
raw_spinlock_t msi_lock;
struct list_head msi_list;
#endif
#ifdef CONFIG_DMA_OPS
const struct dma_map_ops *dma_ops;
#endif
u64 *dma_mask; /* dma mask (if dma'able device) */
u64 coherent_dma_mask;/* Like dma_mask, but for
alloc_coherent mappings as
not all hardware supports
64 bit addresses for consistent
allocations such descriptors. */
u64 bus_dma_limit; /* upstream dma constraint */
const struct bus_dma_region *dma_range_map;

struct device_dma_parameters *dma_parms;

struct list_head dma_pools; /* dma pools (if dma'ble) */

#ifdef CONFIG_DMA_DECLARE_COHERENT
struct dma_coherent_mem *dma_mem; /* internal for coherent mem
override */
#endif
#ifdef CONFIG_DMA_CMA
struct cma *cma_area; /* contiguous memory area for dma
allocations */
#endif
/* arch specific additions */
struct dev_archdata archdata;

struct device_node *of_node; /* associated device tree node */
struct fwnode_handle *fwnode; /* firmware device node */

#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 */

spinlock_t devres_lock;
struct list_head devres_head;

struct class *class;
const struct attribute_group **groups; /* optional groups */

void (*release)(struct device *dev);
struct iommu_group *iommu_group;
struct dev_iommu *iommu;

enum device_removable removable;

bool offline_disabled:1;
bool offline:1;
bool of_node_reused:1;
bool state_synced:1;
#if defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_DEVICE) || \
defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU) || \
defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU_ALL)
bool dma_coherent:1;
#endif
#ifdef CONFIG_DMA_OPS_BYPASS
bool dma_ops_bypass : 1;
#endif
};

andbus_register()in

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
/**
* bus_register - register a driver-core subsystem
* @bus: bus to register
*
* Once we have that, we register the bus with the kobject
* infrastructure, then register the children subsystems it has:
* the devices and drivers that belong to the subsystem.
*/
int bus_register(struct bus_type *bus)
{
int retval;
struct subsys_private *priv;
struct lock_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;

pr_debug("bus: '%s': registered\n", bus->name);
return 0;

bus_groups_fail:
remove_probe_files(bus);
bus_probe_files_fail:
kset_unregister(bus->p->drivers_kset);
bus_drivers_fail:
kset_unregister(bus->p->devices_kset);
bus_devices_fail:
bus_remove_file(bus, &bus_attr_uevent);
bus_uevent_fail:
kset_unregister(&bus->p->subsys);
/* Above kset_unregister() will kfree @bus->p */
bus->p = NULL;
out:
kfree(bus->p);
bus->p = NULL;
return retval;
}
EXPORT_SYMBOL_GPL(bus_register);

  • klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);

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.

subsys_private()

A new structure subsys_private appears:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// drivers/base/base.h
/**
* struct subsys_private - structure to hold the private to the driver core portions of the bus_type/class structure.
*
* @subsys - the struct kset that defines this subsystem
* @devices_kset - the subsystem's 'devices' directory
* @interfaces - list of subsystem interfaces associated
* @mutex - protect the devices, and interfaces lists.
*
* @drivers_kset - the list of drivers associated
* @klist_devices - the klist to iterate over the @devices_kset
* @klist_drivers - the klist to iterate over the @drivers_kset
* @bus_notifier - the bus notifier list for anything that cares about things
* on this bus.
* @bus - pointer back to the struct bus_type that this structure is associated
* with.
*
* @glue_dirs - "glue" directory to put in-between the parent device to
* avoid namespace conflicts
* @class - pointer back to the struct class that this structure is associated
* with.
*
* This structure is the one that is the actual kobject allowing struct
* bus_type/class to be statically allocated safely. Nothing outside of the
* driver core should ever touch these fields.
*/
struct subsys_private {
struct kset subsys;
struct kset *devices_kset;
struct list_head interfaces;
struct mutex mutex;

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

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

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

  1. 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.
  2. 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.
  3. 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:

1
2
3
4
kernel_init_freeable()
->do_basic_setup()
->driver_init()
->platform_bus_init()

platform_bus_initThe function is as follows

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//drivers/base/platform.c
int __init platform_bus_init(void)
{
int error;

early_platform_cleanup();// Clean up platform bus related resources in advance

error = device_register(&platform_bus);// Register platform bus device
if (error) {
put_device(&platform_bus); // Registration failed, release platform bus device
return error;
}
error = bus_register(&platform_bus_type);// Register platform bus type
if (error)
device_unregister(&platform_bus);// Registration failed, unregister platform bus device
of_platform_register_reconfig_notifier();// Notifier for platform reconfiguration registration
return error;
}

struct bus_type platform_bus_type

platform_bus_typeDefined as follows

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// drivers/base/platform.c
static const struct dev_pm_ops platform_dev_pm_ops = {
.runtime_suspend = pm_generic_runtime_suspend,
.runtime_resume = pm_generic_runtime_resume,
USE_PLATFORM_PM_SLEEP_OPS
};

struct bus_type platform_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:

  • dev represents the device object pointer

  • drv represents the driver object pointer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);

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

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

/* Then try ACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1;

/* Then try to match against the id table */
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;

/* fall-back to driver name match */
return (strcmp(pdev->name, drv->name) == 0);
}
  1. First, convert dev and drv respectively tostruct platform_deviceandstruct platform_drivertype pointers for subsequent use.
  2. 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).
  3. 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).
  4. 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).
  5. 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).
  6. 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:

1
2
3
4
5
MODULE_DEVICE_TABLE(of, my_of_match);
MODULE_DEVICE_TABLE(i2c, my_i2c_id);
MODULE_DEVICE_TABLE(spi, my_spi_id);
MODULE_DEVICE_TABLE(usb, my_usb_id);
MODULE_DEVICE_TABLE(pci, my_pci_id);

register a device under the bus

first add under the custom bus module

1
EXPORT_SYMBOL_GPL(mybus);// export bus symbols

add device:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>

extern struct bus_type mybus;

void mydev_release(struct device *dev)
{
pr_info("%s\n", __func__);
}

struct device mydevice = {
.init_name = "mydevice",
.bus = &mybus,
.release = mydev_release,
.devt = ((255 << 20) | 0),
};

static int __init device_test_init(void)
{
int ret;
ret = device_register(&mydevice);
return ret;
}

static void __exit device_test_exit(void)
{
device_unregister(&mydevice);
}

module_init(device_test_init);
module_exit(device_test_exit);

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

Test:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
$ insmod create_attr_under_my_own_bus.ko
[ 21.174906] create_attr_under_my_own_bus: loading out-of-tree module taints kernel.
$ insmod register_device_under_my_own_bus.ko
$ ls /sys/bus
amba gpio mmc_rpmb serial
clockevents hid mybus soc
clocksource i2c nvmem spi
container iscsi_flashnode pci usb
cpu mdio_bus platform workqueue
event_source mipi-dsi scsi
genpd mmc sdio
$ ls /sys/bus/mybus
devices drivers_autoprobe uevent
drivers drivers_probe value
$ ls /sys/bus/mybus/devices/
mydevice
$ ls /sys/bus/mybus/devices/mydevice/
dev power subsystem uevent
$ lsmod
register_device_under_my_own_bus 16384 0 - Live 0xffffffc008b35000 (O)
create_attr_under_my_own_bus 16384 1 register_device_under_my_own_bus, Live 0xffffffc008b30000 (O)
$ rmmod register_device_under_my_own_bus.ko
[ 127.412626] mydev_release
$ lsmod
create_attr_under_my_own_bus 16384 0 - Live 0xffffffc008b30000 (O)
$ ls /sys/bus/mybus/devices/
$ ls /sys/bus/mybus/
devices drivers_autoprobe uevent
drivers drivers_probe value
$ rmmod create_attr_under_my_own_bus.ko
$ ls /sys/bus/mybus/
ls: /sys/bus/mybus/: No such file or directory

Device Registration Process Analysis

device_register()

1
2
3
4
5
6
7
// drivers/base/core.c
int device_register(struct device *dev)
{
device_initialize(dev);
return device_add(dev);
}
EXPORT_SYMBOL_GPL(device_register);

device_initialize()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// drivers/base/core.c
void device_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);

device_add()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
// drivers/base/core.c
int device_add(struct device *dev)
{
struct device *parent;
struct kobject *kobj;
struct class_interface *class_intf;
int error = -EINVAL;
struct kobject *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;
}

pr_debug("device: '%s': %s\n", dev_name(dev), __func__);

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

bus_add_device()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 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.
*/
int bus_add_device(struct device *dev)
{
struct bus_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
}
return 0;

out_subsys:
sysfs_remove_link(&bus->p->devices_kset->kobj, dev_name(dev));
out_groups:
device_remove_groups(dev, bus->dev_groups);
out_put:
bus_put(dev->bus);
return error;
}
  • 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_register()

1
2
3
4
5
6
7
8
9
10
11
12
13
// drivers/base/platform_device_register
/**
* platform_device_register - add a platform-level device
* @pdev: platform device we're adding
*/
int platform_device_register(struct platform_device *pdev)
{
device_initialize(&pdev->dev);
setup_pdev_dma_masks(pdev);
return platform_device_add(pdev);
}
EXPORT_SYMBOL_GPL(platform_device_register);

platform_device_add()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
/**
* 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().
*/
int platform_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++) {
struct resource *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;
else if (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.
struct resource *r = &pdev->resource[i];
if (r->parent)
release_resource(r);
}

err_out:
return ret;
}
EXPORT_SYMBOL_GPL(platform_device_add);

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

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:

/sys/devices/platform
/sys/devices/platform

Registering a driver under the bus

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/configfs.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
#include <linux/device.h>
#include <linux/sysfs.h>

extern struct bus_type mybus;

int mydriver_remove(struct device *dev){
printk("This is mydriver_remove\n");
return 0;
};

int mydriver_probe(struct device *dev){
printk("This is mydriver_probe\n");
return 0;
};


struct device_driver mydriver = {
.name = "mydevice",
.bus = &mybus,
.probe = mydriver_probe,
.remove = mydriver_remove,

};

// Module initialization function
static int mydriver_init(void)
{
int ret;
ret = driver_register(&mydriver);

return ret;
}

// Module exit function
static void mydriver_exit(void)
{
driver_unregister(&mydriver);
}

module_init(mydriver_init); // Specify module initialization function
module_exit(mydriver_exit); // Specify module exit function

MODULE_LICENSE("GPL"); // Module license
MODULE_AUTHOR("topeet"); // Module author

Test:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ 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:

1
2
3
4
5
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);

That is, ifstruct bus_typea probe function is defined instruct device_driverthis function is also defined and will

Comment outmybusthe probe function and test again:

1
2
3
4
5
~ # insmod create_attr_under_my_own_bus.ko
[ 28.767545] 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
[ 39.782320] mydriver_probe

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:

1
2
3
4
5
6
7
8
9
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.dma_configure = platform_dma_configure,
.pm = &platform_dev_pm_ops,
};
EXPORT_SYMBOL_GPL(platform_bus_type);

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;
} else if (drv->probe) {
ret = drv->probe(dev);
if (ret)
goto probe_failed;
}

Driver Registration Process Analysis

driver_register()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// 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.
*/
int driver_register(struct device_driver *drv)
{
int ret;
struct device_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->probebus->removeanddrv->removebus->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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
// drivers/base/bus.c
/**
* bus_add_driver - Add a driver to the bus.
* @drv: driver.
*/
int bus_add_driver(struct device_driver *drv)
{
struct bus_type *bus;
struct driver_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);
}
}

return 0;

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
drivers_autoprobe

The entire process is shown in the following diagram:

Device and driver matching process
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.
*/
int driver_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);

__driver_attach()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
static int __driver_attach(struct device *dev, void *data)
{
struct device_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 */
return 0;
} else if (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.
*/
return 0;
} else if (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.
*/
return 0;
} /* 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
return 0;
}

device_driver_attach(drv, dev);// Synchronously probe device and bind driver

return 0;
}

driver_match_device()

1
2
3
4
5
6
// drivers/base/base.h
static inline int driver_match_device(struct device_driver *drv,
struct device *dev)
{
return drv->bus->match ? drv->bus->match(dev, drv) : 1;
}

If the device and driver match, execution continuesdevice_driver_attachfunction

device_driver_attach()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 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.
*/
int device_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);

__device_driver_unlock(dev, dev->parent);

return ret;
}

driver_probe_device()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/**
* 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.
*/
int driver_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);

pm_runtime_put_suppliers(dev);// Release runtime reference count of device supplier
return ret;
}

really_probe()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
// drivers/base/dd.c
static int really_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;
} else if (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;

device_remove_file(dev, &dev_attr_state_synced);
device_remove_groups(dev, drv->dev_groups);

if (dev->bus->remove)// If the bus has a remove function, call the bus's remove function
dev->bus->remove(dev);
else if (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

goto re_probe;// Re-probe
}

pinctrl_init_done(dev);// Complete pinctrl initialization

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

driver_bound(dev);// Driver binding successful
ret = 1;
pr_debug("bus: '%s': %s: bound device %s to driver %s\n",
drv->bus->name, __func__, dev_name(dev), drv->name);
goto done;

dev_sysfs_state_synced_failed:
device_remove_groups(dev, drv->dev_groups);
dev_groups_failed:
if (dev->bus->remove)
dev->bus->remove(dev);
else if (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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// drivers/base/core.c
int device_add(struct device *dev)
{
...

// 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 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.
*/
void bus_probe_device(struct device *dev)
{
struct bus_type *bus = dev->bus;
struct subsys_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);
}

device_initial_probe()

device_initial_probecall__device_attach

1
2
3
4
5
6
// drivers/base/dd.c
void device_initial_probe(struct device *dev)
{
__device_attach(dev, true);
}

__device_attach()

__device_attachas follows

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// drivers/base/dd.c
static int __device_attach(struct device *dev, bool allow_async)
{
int ret = 0;
bool async = false;

device_lock(dev);
if (dev->p->dead) {
goto out_unlock;
} else if (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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 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.
*/
int device_bind_driver(struct device *dev)
{
int ret;

ret = driver_sysfs_add(dev);
if (!ret)
driver_bound(dev);
else if (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);

driver_bound()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
static void driver_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

platform_driver_register()

1
2
#define platform_driver_register(drv) \
__platform_driver_register(drv, THIS_MODULE)

__platform_driver_register()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

// drivers/base/platform.c
/**
* __platform_driver_register - register a driver for platform-level devices
* @drv: platform driver structure
* @owner: owning module/driver
*/
int __platform_driver_register(struct platform_driver *drv,
struct module *owner)
{
drv->driver.owner = owner;
drv->driver.bus = &platform_bus_type;
drv->driver.probe = platform_drv_probe;
drv->driver.remove = platform_drv_remove;
drv->driver.shutdown = platform_drv_shutdown;

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

driver_registerThe function has been analyzed before, so next we will focus on how the probe function of the platform bus is executed:

platform_drv_probe()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
static int platform_drv_probe(struct device *_dev)
{
// Convert the device pointer passed to the driver into a platform_driver structure pointer
struct platform_driver *drv = to_platform_driver(_dev->driver);
// Convert the device pointer passed to the driver into a platform_device structure pointer
struct platform_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:

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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