Timeline
Timeline
2025-11-15
init
This article introduces the Platform Bus mechanism in the Linux kernel, explaining its role as a bridge between platform devices and platform drivers, mainly used to manage devices directly integrated on SoCs or motherboards. The article explains that the platform bus model separates device drivers from platform devices, improving the reusability and portability of driver code, and points out that devices and drivers are independent objects. At the same time, the article details the key fields of the platform_device structure (such as name, ID, device structure, resource count and pointer) and the meanings of parameters in struct resource such as start address, end address, name, and flags, and lists common resource types and attribute flags.
Linux Driver Notes
| Table of Contents | Links |
|---|---|
| 1. Linux Driver Framework | |
| 2. Linux Driver Loading Logic | |
| 3. Character Device Basics | |
| 4. Concurrency and Race Conditions | |
| 5. Advanced Character Device Topics | |
| 6. Interrupts | |
| 7. Platform Bus | |
| 8. Device Tree | |
| 9. Device Model | |
| 10. Hotplug | |
| 11. pinctrl Subsystem | |
| 12. GPIO subsystem | |
| 13. Input subsystem | |
| 14. 1-Wire | |
| 15. I2C | |
| 16. SPI | |
| 17. UART | |
| 18. PWM | |
| 19. RTC | |
| 20. Watchdog | |
| 21. CAN | |
| 22. Network devices | |
| 23. ADC | |
| 24. IIO | |
| 25. USB | |
| 26. LCD |
Platform Bus
Before Linux kernelin, Platform Bus is an abstraction layer used to manage and connect platform device and platform driver It acts as a bridge between platform devices and platform drivers, responsible for matching and binding them.
It is mainly used for those devices that are not discovered through standard buses (such as PCI, USB, I²C, SPI) but ratherdevices directly integrated on SoC (System on Chip) or motherboards。

By using the platform bus model, device drivers and platform devices are separated. In this way, we only need to write one generic driver code and then configure it for different platform devices, which greatly reduces the workload of repeatedly writing code and improves the reusability of driver code.
When we need to port the driver to a different platform, we only need to adapt the hardware-related parts, and the rest can remain unchanged.
Under the platform bus,devices (platform_device) and drivers (platform_driver) are two independent objects。
- Devices come from DT/board-level files/platform code
- Drivers come from module loading/compiled into the kernel
- Driver unload does not mean the device disappears
Registering platform devices
struct platform_device
123456789101112131415161718192021222324 | // include/linux/platform_device.hstruct platform_device { const char *name;// The name of the device, used to uniquely identify the device int id;// The ID of the device, which can be used to distinguish different instances of the same device bool id_auto;// Indicates whether the device ID is automatically generated struct device dev;// Represents the struct device structure corresponding to the platform device, used for basic device management and operations u64 platform_dma_mask; struct device_dma_parameters dma_parms; u32 num_resources;// Number of device resources struct resource *resource;// Pointer to device resources const struct platform_device_id *id_entry;// Pointer to the device ID table entry, used to match devices and drivers /* * Driver name to force a match. Do not set directly, because core * frees it. Use driver_set_override() to set or clear it. */ const char *driver_override;// Driver name that forces the device to match the specified driver /* MFD cell pointer */ struct mfd_cell *mfd_cell;// Pointer to the multi-function device (MFD) cell, used for describing multi-function devices /* arch specific additions */ struct pdev_archdata archdata;// Used to store architecture-specific device data}; |
const char *name: The name of the device, used to uniquely identify the device. A unique name must be provided so that the kernel can correctly identify and manage the device.int id: The device ID, which can be used to distinguish different instances of the same type of device. This parameter is optional. If there is no need to use the ID for distinction, it can be set to -1.struct device dev: Represents the struct device corresponding to the platform device, used for basic management and operation of the device. A valid struct device object must be provided for this parameter,The release method of this structure must be implemented, otherwise a compilation error will be reported.。u32 num_resources: The number of device resources. If the device has resources (such as memory regions, interrupts, etc.), the number of resources needs to be provided.struct resource *resource: Pointer to device resources. If the device has resources, a pointer to the resource array needs to be provided. This structure will be explained in detail in the next section.
struct resource
12345678910 | //include/linux/ioport.hstruct resource { resource_size_t start;/* Start address of the resource */ resource_size_t end;/* End address of the resource */ const char *name;/* Name of the resource */ unsigned long flags;/* Flags of the resource */ unsigned long desc;/* Description information of the resource */ struct resource *parent, *sibling, *child;/* Pointer to child resources */}; |
Among them, the most important are the first four parameters. The specific introduction of each parameter is as follows:
resource_size_t start: The start address of the resource. It represents the starting position of the resource or the address of the starting register.resource_size_t end: The end address of the resource. It represents the ending position of the resource or the address of the ending register.const char *name: The name of the resource. It is a string used to identify and describe the resource.unsigned long flags: The flags of the resource. It contains specific flags used to indicate the attributes or characteristics of the resource. For example, flags can be used to indicate the availability, shareability, cache attributes, etc. of the resource. The specific values and meanings of the flags parameter can be defined and interpreted according to the needs of the system and driver, but in general, it is used to represent the attributes, characteristics, or configuration options of the resource. Below are some common flags and their possible meanings.
| Category | Flags | Description |
|---|---|---|
| Resource typeRelated flags | IORESOURCE_IO | Resource is an I/O port resource |
IORESOURCE_MEM | Resource is a memory resource | |
IORESOURCE_REG | Resource is a register offset | |
IORESOURCE_IRQ | Resource is an interrupt resource | |
IORESOURCE_DMA | Resource is a DMA (Direct Memory Access) resource | |
IORESOURCE_BUS | Bus | |
| Resource attributes and characteristicsRelated flags | IORESOURCE_PREFETCH | Resource is a side-effect-free prefetchable resource |
IORESOURCE_READONLY | Resource is read-only | |
IORESOURCE_CACHEABLE | Resource supports caching | |
IORESOURCE_RANGELENGTH | Resource range length | |
IORESOURCE_SHADOWABLE | Resource can be replaced by a shadow resource | |
IORESOURCE_SIZEALIGN | Resource size field alignment | |
IORESOURCE_STARTALIGN | Start address field alignment | |
IORESOURCE_MEM_64 | Resource is a 64-bit memory resource | |
IORESOURCE_WINDOW | Resource is forwarded by a bridge | |
IORESOURCE_MUXED | Resource is reused by software | |
IORESOURCE_SYSRAM | Resource is system RAM (modifier) | |
| Status and controlRelated flags | IORESOURCE_EXCLUSIVE | User space cannot map this resource |
IORESOURCE_DISABLED | Resource is currently disabled | |
IORESOURCE_UNSET | Address has not been assigned to the resource | |
IORESOURCE_AUTO | The address is automatically assigned by the system. | |
IORESOURCE_BUSY | Driver marks resource as busy |
embedded instruct platform_deviceinstruct resourceCan pass throughplatform_get_resource()The function performs the search. The following areplatform_get_resourcePrototype of:
1 | struct resource *platform_get_resource(struct platform_device *pdev, unsigned int type, unsigned int num); |
- The first parameter is an instance of the platform device itself.
- The second parameter describes what kind of resource is needed. For memory, it should be
IORESOURCE_MEM。 - The num parameter is an index indicating which resource type is needed. Zero means the first, and so on.
If it is an interrupt resource, you must use:
123 | int platform_get_irq(struct platform_device *dev, unsigned int num);int platform_get_irq_optional(struct platform_device *dev, unsigned int num) |
platform_device_register()
| Item | Description |
|---|---|
| Function definition | int platform_device_register(struct platform_device *pdev); |
| Header file | #include <linux/platform_device.h> |
| parameter pdev | point toplatform_devicePointer to a structure, describing the platform device to be registered, including device name, resources, device ID, and other information. |
| Function | Register the platform device with the kernel, enabling it to participate in device resource allocation and driver matching. |
| Return value | Success: returns 0; Failure: return a negative error code |
1234567891011121314151617181920212223 | // include/linux/platform_device.hextern int platform_device_register(struct platform_device *);extern void platform_device_unregister(struct platform_device *);// drivers/base/platform.c/** * 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); |
device_initialize(&pdev->dev)forpdev->devPerform initialization.pdev->devYesstruct platform_deviceA member in a structure, it represents the one corresponding to the platform device.struct deviceStruct. By callingdevice_initializeFunction, yespdev->devPerform some basic initialization work, such as setting the device’s reference count, device type, etc.setup_pdev_dma_masksSet the architecture-related data of pdev based on the architecture data of the platform device. The specific implementation of this function may be architecture-specific, and it is mainly used to perform specific settings for platform devices under different architectures.platform_device_addFunction that adds the platform device pdev to the kernel.platform_device_addThe function completes the platform device addition operation, including adding the device to the device hierarchy, adding device resources, etc. It returns an int type result indicating the result of the device addition.
platform_device_registerThe main purpose of the function is toplatform_deviceThe platform device described by the structure is registered into the kernel, including operations such as device initialization, adding to the platform bus and device hierarchy, and adding device resources.
Through this function, after the platform device is registered, it can participate in the device’s resource allocation and driver matching process. The return value of the function can be used to determine whether the device registration is successful.
platform_device_unregister()
| Item | Description |
|---|---|
| Function definition | void platform_device_unregister(struct platform_device *pdev); |
| Header file | #include <linux/platform_device.h> |
| parameter pdev | Pointing to the platform device to be unregisteredplatform_devicestructure pointer |
| Function | Unregister a registered platform device, remove the device from the kernel, and perform resource cleanup. |
| Return value | No return value |
12345678910111213141516 | // drivers/base/platform.c/** * platform_device_unregister - unregister a platform-level device * @pdev: platform device we're unregistering * * Unregistration is done in 2 steps. First we release all resources * and remove it from the subsystem, then we drop reference count by * calling platform_device_put(). */void platform_device_unregister(struct platform_device *pdev){ platform_device_del(pdev); platform_device_put(pdev);}EXPORT_SYMBOL_GPL(platform_device_unregister); |
platform_device_delFunction used to remove the device from the platform bus’s device list. It removes the device from the device hierarchy, stops the device’s resource allocation and driver matching.platform_device_putFunction used to decrement the device’s reference count. This function checks the device’s reference count; if the reference count drops to zero, it releases the device structure and related resources. By decrementing the reference count, it ensures that the device can be freed when no longer in use.
platform_device_unregisterThe function is used to unregister a registered platform device and remove the device from the kernel. It first callsplatform_device_delThe function removes the device from the device hierarchy, then callsplatform_device_putThe function decrements the device’s reference count, ensuring that the device can be freed when no longer in use.
example
In lower kernel versions platform_device the release callback function must be implemented, otherwise compilation may fail.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859 | static struct resource my_resources[] = { { .start = MEM_START_ADDR, .end = MEM_END_ADDR, .name = "test_resource1", .flags = IORESOURCE_MEM, // Marked as memory resource }, { .start = IRQ_NUMBER, .end = IRQ_NUMBER, .flags = IORESOURCE_IRQ, // Marked as interrupt resource } };void my_dev_release(struct device *dev) // Callback function for pdev->dev resource release{ pr_info("my_dev_release is called\n");}static struct platform_device my_platform_device = { .name = "my_platform_device", .id = -1, // Device ID .num_resources = ARRAY_SIZE(my_resources), .resource = my_resources, .dev.release = my_dev_release,};static int __init platform_device_test_init(void){ int ret; ret = platform_device_register(&my_platform_device); if (ret < 0){ pr_err("platform_device_register fail\n"); return ret; } pr_info("platform_device register success\n"); return 0;}static void __exit platform_device_test_exit(void){ platform_device_unregister(&my_platform_device);}module_init(platform_device_test_init);module_exit(platform_device_test_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("even629<asqwgo@outlook.com>");MODULE_DESCRIPTION("This is a test sample for platform_device"); |
After loading, in/sys/bus/platform/devicesthe directory, you can see the … we createdmy_platform_devicedevice
1234567891011121314 | ~ # insmod platform_device_test.ko[ 10.099551] platform_device_test: loading out-of-tree module taints kernel.[ 10.115921] platform_device register success~ # ls /sys/devices/platform/Fixed MDIO bus.0/ fixedregulator_5v0/ scb/arm-pmu/ kgdboc/ sd_io_1v8_reg/cam1_regulator/ leds/ sd_vcc_reg/cam_dummy_reg/ my_platform_device/ soc/cpufreq-dt/ phy/ timer/emmc2bus/ power/ ueventfixedregulator_3v3/ reg-dummy/ v3dbus/~ # ls /sys/devices/platform/my_platform_device/driver_override power ueventmodalias subsystem |
Register platform driver
struct platform_driver
1234567891011 | // include/linux/platform_device.hstruct platform_driver { int (*probe)(struct platform_device *); /* Platform device probe function pointer */ int (*remove)(struct platform_device *); /* Platform device remove function pointer */ void (*shutdown)(struct platform_device *);/* Platform device shutdown function pointer */ int (*suspend)(struct platform_device *, pm_message_t state);/* Platform device suspend function pointer */ int (*resume)(struct platform_device *);/* Platform device resume function pointer */ struct device_driver driver;/* Generic data of the device driver */ const struct platform_device_id *id_table;/* Association table between platform devices and drivers */ bool prevent_deferred_probe;/* Whether to prevent deferred probing */}; |
probe: The probe function pointer of the platform device. When the system detects that a platform device matches the driver, this function will be called to initialize and configure the device.remove: The removal function pointer of the platform device. When the platform device is removed from the system, this function will be called to perform cleanup and release resources.shutdown: The shutdown function pointer of the platform device. When the system shuts down, this function will be called to perform shutdown operations related to the platform device.suspend: The suspend function pointer of the platform device. When the system enters the suspend state, this function will be called to perform suspend operations related to the platform device.resume: The resume function pointer of the platform device. When the system resumes from the suspended state, this function will be called to perform resume operations related to the platform device.driver: Contains general data related to device drivers, which isstruct device_driverAn instance of the type. This includes information such as the driver name, bus type, module owner, attribute group array pointer, etc.id_table: points tostruct platform_device_idPointer to an array of structures, used to match the association between platform devices and drivers. Only when the match succeeds can the probe function be entered, but the priority is lower thanplatform_driver.driver.of_match_tableprevent_deferred_probe: A boolean value used to determine whether to block delayed probing. If set to true, delayed probing will be disabled.
Note: The probe function is not a replacement for the init function. The probe function is called whenever a given device matches a driver, while the init function runs only once when the module is loaded.
Can be used
MODULE_DEVICE_TABLE(type, name);Device ID Tableplatform_driver.driver.of_match_tableExport to the module’s ELF section so that userspace (udev/modprobe) can automatically load the driver.
struct device_driver
struct device_driverYes Device Model layer the abstraction, which describes The identity and behavior of the driver itselfFor example:
- Driver name, bus, and module information
- Device Matching Table (Device Tree, ACPI)
- Driver operation callback:
probe/remove/suspend/resume - sysfs attributes and power management
Core idea:It focuses on the matching and lifecycle management between drivers and devices.。
1234567891011121314151617181920212223242526272829 | // include/linux/device/driver.hstruct device_driver { const char *name;// The name of the driver, used to match devices. struct bus_type *bus;// The bus type to which the driver belongs, such as PCI, USB, I2C, etc. struct module *owner;// Points to the module (struct module) that the driver belongs to, used for module reference counting. const char *mod_name; /* used for built-in modules */ //Module name; if it is a built-in driver rather than a module, this name is used. bool suppress_bind_attrs; /* disables bind/unbind via sysfs */ //If true, manual binding/unbinding of devices via sysfs is disabled. enum probe_type probe_type;// Driver probe type, determining how the driver is automatically bound to devices (e.g., normal probe or deferred probe). const struct of_device_id *of_match_table;// Used for Device Tree matching. const struct acpi_device_id *acpi_match_table;// Used for ACPI device matching. int (*probe) (struct device *dev);// Core function, called when the driver matches a device, initializes the device. void (*sync_state)(struct device *dev);// Synchronize device state, mainly for internal kernel use. int (*remove) (struct device *dev);// Called when the device is removed or the driver is unloaded. void (*shutdown) (struct device *dev);// Called when the system shuts down. int (*suspend) (struct device *dev, pm_message_t state);// For power management, suspend. int (*resume) (struct device *dev);// For power management, resume. // Used to expose sysfs attributes of drivers and devices, which can be viewed and manipulated via /sys/bus/... or /sys/class/... const struct attribute_group **groups; const struct attribute_group**dev_groups; const struct dev_pm_ops *pm;// Pointer to the power management operations structure, containing callbacks such as suspend/resume, runtime PM, etc. void (*coredump) (struct device *dev);// When a device encounters a serious error, the driver's coredump callback can be triggered for debugging. struct driver_private *p;// For internal kernel use, stores driver-related private data, such as the list of bound devices.}; |
platform_driver_register()
| Item | Description |
|---|---|
| Function definition | int platform_driver_register(struct platform_driver *driver); |
| Header file | #include <linux/platform_device.h> |
| The driver parameter | point toplatform_driverA pointer to the structure, describing the platform driver to be registered, including attributes and callback functions. |
| Function | Registers the platform driver with the kernel, enabling the kernel to match it with specific platform devices and invoke the corresponding callback functions. |
| Return value | Success: returns 0; Failure: return a negative error code |
1234 | extern int __platform_driver_register(struct platform_driver *, struct module *); |
This macro is used to simplify the registration process of platform drivers. It associates the actual registration function__platform_driver_registerwith the current module (driver). The macro parameter drv is a pointer tostruct platform_drivera pointer to the structure, describing the attributes and callback functions of the platform driver to be registered.THIS_MODULEis a macro used to obtain the pointer to the current module.
123456789101112131415161718 | // 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;// Sets the ownership of the platform driver to the current module. drv->driver.bus = &platform_bus_type;// Set the bus type of the platform driver to the platform bus drv->driver.probe = platform_drv_probe;// Set the probe function of the platform driver drv->driver.remove = platform_drv_remove;// Set the remove function of the platform driver drv->driver.shutdown = platform_drv_shutdown;// Set the shutdown function of the platform driver return driver_register(&drv->driver);// Register the platform driver with the kernel}EXPORT_SYMBOL_GPL(__platform_driver_register); |
Through these operations,__platform_driver_registerThe function associates the platform driver with the kernel and ensures that the kernel can correctly identify and call the various callback functions of the driver to achieve interaction and management with platform devices. The return value of the function indicates the execution status of the registration process, so that error handling can be performed when needed.
platform_driver_register(): Registers the driver and places it into the driver list maintained by the kernel, so that whenever a new match is found, its probe() function can be called on demand.platform_driver_probe(): After calling this function, the kernel immediately runs the matching loop to check whether there is a matching platform device name. If it matches, it calls the driver’sprobe(), which means the device exists; otherwise, the driver will be ignored. This method prevents deferred probing because it does not register the driver on the system. Here, the probe function is placed in the__initsection, which is released when the kernel startup completes, thereby preventing deferred probing and reducing the driver’s memory footprint. If you are 100% sure that the device exists in the system, use this method
platform_driver_unregister()
| Item | Description |
|---|---|
| Function definition | void platform_driver_unregister(struct platform_driver *driver); |
| Header file | #include <linux/platform_device.h> |
| The driver parameter | point toplatform_drivera pointer to a structure that describes the platform driver to be unregistered. |
| Function | Unregister the platform driver from the kernel so that it no longer participates in driver matching. |
| Return value | No return value |
12345678910111213141516171819202122232425262728293031323334 | // include/linux/platform_device.hextern void platform_driver_unregister(struct platform_driver *);// drivers/base/platform.c/** * platform_driver_unregister - unregister a driver for platform-level devices * @drv: platform driver structure */void platform_driver_unregister(struct platform_driver *drv){ driver_unregister(&drv->driver);}EXPORT_SYMBOL_GPL(platform_driver_unregister);// drivers/base/driver.c/** * driver_unregister - remove driver from system. * @drv: driver. * * Again, we pass off most of the work to the bus-level call. */void driver_unregister(struct device_driver *drv){ // Check whether the passed device driver pointer and the p member are valid. if (!drv || !drv->p) { WARN(1, "Unexpected driver unregister!\n"); return; } driver_remove_groups(drv, drv->groups);// Remove the attribute groups associated with the device driver. bus_remove_driver(drv);// Remove the device driver from the bus.}EXPORT_SYMBOL_GPL(driver_unregister); |
bus_remove_driverFunction used to remove a device driver from the bus. This function performs the following operations:- Remove the specified device driver from the bus driver list.
- Call the remove callback function associated with the device driver (if defined).
- Release the resources and memory occupied by the device driver.
- Finally destroy the data structure of the device driver.
by callingdriver_unregisterFunction that can correctly unregister the device driver and perform necessary cleanup during the unregistration process. This avoids resource leaks and other problems. After calling this function, you should avoid continuing to use the unregistered device driver pointer, because the driver no longer exists in the kernel.
platform_get_resource()
Obtain the resource information of the platform device in the driver, and perform subsequent operations and configuration based on this information.
| Item | Description |
|---|---|
| Function definition | struct resource *platform_get_resource(struct platform_device *pdev, unsigned int type, unsigned int num); |
| Header file | #include <linux/platform_device.h> |
| parameter pdev | Pointer to the platform device from which to obtain the resource (platform_device) structure pointer |
| Parameter type | Resource type, such as: IORESOURCE_MEM: memory resourceIORESOURCE_IO: I/O resourceIORESOURCE_IRQ: interrupt resource |
| Parameter num | Resource index number, used to select the same type of thenumth resource |
| Function | Get resource information of the specified type and index from the platform device’s resource array. |
| Return value | Success: return pointerstruct resourcepointer;Failure or resource does not exist: return NULL |
helper_macro
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849 | /* module_platform_driver() - Helper macro for drivers that don't do * anything special in module init/exit. This eliminates a lot of * boilerplate. Each module may only use this macro once, and * calling it replaces module_init() and module_exit() *//* builtin_platform_driver() - Helper macro for builtin drivers that * don't do anything special in driver init. This eliminates some * boilerplate. Each driver may only use this macro once, and * calling it replaces device_initcall(). Note this is meant to be * a parallel of module_platform_driver() above, but w/o _exit stuff. *//* module_platform_driver_probe() - Helper macro for drivers that don't do * anything special in module init/exit. This eliminates a lot of * boilerplate. Each module may only use this macro once, and * calling it replaces module_init() and module_exit() *//* builtin_platform_driver_probe() - Helper macro for drivers that don't do * anything special in device init. This eliminates some boilerplate. Each * driver may only use this macro once, and using it replaces device_initcall. * This is meant to be a parallel of module_platform_driver_probe above, but * without the __exit parts. */ |
module_platform_driver(driver)
📌 Purpose
used forLoadable kernel module(.ko), and the driver structure has been fully defined.probe,.removeand other members.
🔧 Expansion effect
1234567891011 | static int __init driver_init(void){ return platform_driver_register(&driver);}module_init(driver_init);static void __exit driver_exit(void){ platform_driver_unregister(&driver);}module_exit(driver_exit); |
✅ Prerequisites
driveris a completestruct platform_drivervariable;- has been implemented
.probe,.removeand other callback functions.
123456 | static struct platform_driver my_driver = { .probe = my_probe, .remove = my_remove, .driver = { .name = "my-dev", ... },};module_platform_driver(my_driver); |
builtin_platform_driver(driver)
📌 Purpose
used forCompiled into the kernel (not a module) of the driver, and the driver structure has been fully defined。
🔧 Expansion effect
12345 | static int __init driver_init(void){ return platform_driver_register(&driver);}device_initcall(driver_init); // called at kernel startup |
❗ No
exitpart (because built-in drivers are usually not unloaded).
✅ Applicable scenarios
- Driver statically linked to the kernel (
CONFIG_MY_DRIVER=y); - No need to support runtime unloading.
module_platform_driver_probe(driver, probe_fn)
📌 Purpose
used forloadable module, but you only want to provide a.probefunction, and don’t want to manually define the fullplatform_driverstructure (especially when you don’t need.removeand other callbacks).
🔧 Expansion effect
1234567891011 | static int __init driver_init(void){ return platform_driver_probe(&driver, probe_fn);}module_init(driver_init);static void __exit driver_exit(void){ platform_driver_unregister(&driver);}module_exit(driver_exit); |
⚠️ Key points
platform_driver_probe()is aLightweight registration method:- It only supports
.probe, does not support.remove、.shutdownetc.; - drivercannot be unloaded and reloaded(because internally it sets
driver->prevent_deferred_probe = true); - Applicable tosimple, one-time probingof devices (such as some SoC built-in controllers).
- It only supports
📝 Example
1234567891011 | static int my_probe(struct platform_device *pdev){ // Initialize device return 0;}static struct platform_driver my_driver = { .driver = { .name = "my-simple-dev", },};module_platform_driver_probe(my_driver, my_probe); |
💡 At this point
my_driverin , there is no need to write.probe = my_probe, the macro will automatically associate.
builtin_platform_driver_probe(driver, probe_fn)
📌 Purpose
module_platform_driver_probeofBuilt-in version(compiled into the kernel, cannot be unloaded).
🔧 Expansion effect
12345 | static int __init driver_init(void){ return platform_driver_probe(&driver, probe_fn);}device_initcall(driver_init); |
Similarly,no exit path, suitable for simple built-in drivers.
example
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374 | static int platform_driver_test_probe(struct platform_device *pdev){ struct resource *res_mem, *res_irq; // Method 1: Direct access if (pdev->num_resources >= 2) { struct resource *res_mem = &pdev->resource[0]; struct resource *res_irq = &pdev->resource[1]; pr_info("using pdev->resource[i] to get resource\n"); pr_info("Memory Resource: start=0x%llx, end=0x%llx\n", res_mem->start, res_mem->end); pr_info("IRQ Resource: number=%lld\n", res_irq->start); } // Method 2: Using platform_get_resource() res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (res_mem == NULL) { dev_err(&pdev->dev, "Fail to get MEMORY resource\n"); return -ENODEV; } res_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); if (res_irq == NULL) { dev_err(&pdev->dev, "Fail to get IRQ resource\n"); return -ENODEV; } pr_info("using platform_get_resource() to get resource\n"); pr_info("Memory Resource: start=0x%llx, end=0x%llx\n", res_mem->start, res_mem->end); pr_info("IRQ Resource: number=%lld\n", res_irq->start); return 0;}static int platform_driver_test_remove(struct platform_device *pdev){ pr_info("platform_driver_test_remove is called\n"); return 0;}static struct platform_driver my_platform_driver={ .driver = { .name = "my_platform_device", .owner = THIS_MODULE, }, .probe = platform_driver_test_probe, .remove = platform_driver_test_remove,};static int __init platform_driver_test_init(void){ int ret; ret = platform_driver_register(&my_platform_driver); if (ret < 0) { pr_err("platform_driver_register failed\n"); return ret; } pr_info("platform_driver_register success\n"); return 0;}static void __exit platform_driver_test_exit(void){ platform_driver_unregister(&my_platform_driver);}module_init(platform_driver_test_init);module_exit(platform_driver_test_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("even629<asqwgo@outlook.com>");MODULE_DESCRIPTION("This is a test sample for platform_driver"); |
Test:
1234567891011 | ~ # insmod platform_device_test.ko[ 13.668535] platform_device_test: loading out-of-tree module taints kernel.[ 13.679351] platform_device register success~ # insmod platform_driver_test.ko[ 20.597568] using pdev->resource[i] to get resource[ 20.597797] Memory Resource: start=0xfdd60000, end=0xfdd60004[ 20.597884] IRQ Resource: number=101[ 20.597958] using platform_get_resource() to get resource[ 20.598036] Memory Resource: start=0xfdd60000, end=0xfdd60004[ 20.598309] IRQ Resource: number=101[ 20.599166] platform_driver_register success |
LED light platform bus example
platform_device
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677 | /* Multiplexing register *//* Data register and direction register */// Direction register// Data register// External input register, read-onlystruct resource my_resources[] = { { .start = GPIO0B_IOMUX, .end = GPIO0B_IOMUX + REG_SIZE - 1, .name = "GPIO0B_IOMUX", .flags = IORESOURCE_MEM, }, { .start = GPIO0_SWPORT_DDR_L, .end = GPIO0_SWPORT_DDR_L + REG_SIZE - 1, .name = "GPIO0_SWPORT_DDR_L", .flags = IORESOURCE_MEM, }, { .start = GPIO0_SWPORT_DR_L, .end = GPIO0_SWPORT_DR_L + REG_SIZE - 1, .name = "GPIO0_SWPORT_DR_L", .flags = IORESOURCE_MEM, }, { .start = GPIO0_EXT_PORT, .end = GPIO0_EXT_PORT + REG_SIZE - 1, .name = "GPIO0_EXT_PORT", .flags = IORESOURCE_MEM, } };static struct platform_device my_platform_device={ .name = "light_up_led", .num_resources = ARRAY_SIZE(my_resources), .resource = my_resources,};static int __init platform_device_led_init(void){ int ret; ret = platform_device_register(&my_platform_device); if(ret < 0){ pr_info("platform_device_register fail\n"); return ret; } return 0;}static void __exit platform_device_led_exit(void){ platform_device_unregister(&my_platform_device);}module_init(platform_device_led_init);module_exit(platform_device_led_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("even629<asqwgo@outlook.com>");MODULE_DESCRIPTION("This is light_up_led example using platform"); |
platform_driver
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281 | struct led_drv_data { dev_t dev_num; struct cdev cdev; struct class *class; struct device *dev; void __iomem *gpio0b_iomux; void __iomem *gpio0_swport_ddr_l; void __iomem *gpio0_swport_dr_l; void __iomem *gpio0_ext_port;};int led_open(struct inode *inode, struct file *file){ struct cdev *led_cdev = inode->i_cdev; file->private_data = container_of(led_cdev, struct led_drv_data, cdev); pr_info("led_open is called\n"); return 0;}ssize_t led_read(struct file *file, char __user *buf, size_t size, loff_t *offset){ struct led_drv_data *drv_data = file->private_data; u32 val; if (size != sizeof(u32)) { pr_info("read need 4 bytes\n"); return -ENOMEM; } val = readl(drv_data->gpio0_ext_port); val &= 1 << 15; // gpio0_b7 val = val >> 15; if (copy_to_user(buf, &val, sizeof(u32)) != 0) { return -EFAULT; } pr_info("led_read is called\n"); return sizeof(u32);}ssize_t led_write(struct file *file, const char __user *buf, size_t size, loff_t *offset){ struct led_drv_data *drv_data = file->private_data; u32 val; if (size != sizeof(u32)) { pr_err("write need 4 bytes\n"); return -ENOMEM; } if (copy_from_user(&val, buf, sizeof(u32)) != 0) { return -EFAULT; } if (val > 0) { // Configure as GPIO output val = readl(drv_data->gpio0_swport_ddr_l); val |= 0x80008000; writel(val, drv_data->gpio0_swport_ddr_l); // Open val = readl(drv_data->gpio0_swport_dr_l); val |= 0x80008000; writel(val, drv_data->gpio0_swport_dr_l); } else if (val == 0) { // Configure as GPIO output val = readl(drv_data->gpio0_swport_ddr_l); val |= 0x80008000; writel(val, drv_data->gpio0_swport_ddr_l); // Shutdown val = readl(drv_data->gpio0_swport_dr_l); val |= 0x80000000; val &= 0xffff7fff; writel(val, drv_data->gpio0_swport_dr_l); } pr_info("led_write is called\n"); return sizeof(u32);}long led_unlocked_ioctl(struct file *file, unsigned int op, unsigned long arg){ struct led_drv_data *drv_data = file->private_data; int val; switch (op) { case LED_OPEN: // Configure as GPIO output val = readl(drv_data->gpio0_swport_ddr_l); val |= 0x80008000; writel(val, drv_data->gpio0_swport_ddr_l); // Open val = readl(drv_data->gpio0_swport_dr_l); val |= 0x80008000; writel(val, drv_data->gpio0_swport_dr_l); break; case LED_CLOSE: // Configure as GPIO output val = readl(drv_data->gpio0_swport_ddr_l); val |= 0x80008000; writel(val, drv_data->gpio0_swport_ddr_l); // Shutdown val = readl(drv_data->gpio0_swport_dr_l); val |= 0x80000000; val &= 0xffff7fff; writel(val, drv_data->gpio0_swport_dr_l); break; case LED_STATUS: val = readl(drv_data->gpio0_ext_port); val &= 1 << 15; // gpio0_b7 val = val >> 15; if (copy_to_user((void __user *)arg, &val, sizeof(val))) return -EFAULT; break; default: return -EFAULT; } pr_info("led_ioctl is called\n"); return 0;}int led_release(struct inode *inode, struct file *file){ pr_info("led_release is called\n"); return 0;}struct file_operations fops = { .owner = THIS_MODULE, .open = led_open, .read = led_read, .write = led_write, .unlocked_ioctl = led_unlocked_ioctl, .release = led_release,};static int my_platform_driver_probe(struct platform_device *pdev){ int ret; u32 val; struct led_drv_data *led_drv_dat; struct resource *res_iomux, *res_ddr_l, *res_dr_l, *res_ext_port; led_drv_dat = devm_kzalloc(&pdev->dev, sizeof(struct led_drv_data), GFP_KERNEL); if (led_drv_dat == NULL) { ret = -ENOMEM; goto devm_kzalloc_fail; } ret = alloc_chrdev_region(&led_drv_dat->dev_num, 0, 1, "lightup_led_chrdev_region"); if (ret < 0) { ret = -ENOMEM; goto alloc_chrdev_region_fail; } platform_set_drvdata(pdev, led_drv_dat); cdev_init(&led_drv_dat->cdev, &fops); led_drv_dat->cdev.owner = THIS_MODULE; ret = cdev_add(&led_drv_dat->cdev, led_drv_dat->dev_num, 1); if (ret < 0) goto cdev_add_fail; led_drv_dat->class = class_create(THIS_MODULE, "test_led"); if (IS_ERR(led_drv_dat->class)) { ret = PTR_ERR(led_drv_dat->class); goto class_create_fail; } led_drv_dat->dev = device_create(led_drv_dat->class, NULL, led_drv_dat->dev_num, NULL, "led0"); if (IS_ERR(led_drv_dat->dev)) { ret = PTR_ERR(led_drv_dat->dev); goto device_create_fail; } res_iomux = platform_get_resource(pdev, IORESOURCE_MEM, 0); res_ddr_l = platform_get_resource(pdev, IORESOURCE_MEM, 1); res_dr_l = platform_get_resource(pdev, IORESOURCE_MEM, 2); res_ext_port = platform_get_resource(pdev, IORESOURCE_MEM, 3); if (!res_iomux || !res_ddr_l || !res_dr_l || !res_ext_port) { ret = -ENODEV; goto address_fail; } led_drv_dat->gpio0b_iomux = devm_ioremap(&pdev->dev, res_iomux->start, resource_size(res_iomux)); led_drv_dat->gpio0_swport_ddr_l = devm_ioremap(&pdev->dev, res_ddr_l->start, resource_size(res_ddr_l)); led_drv_dat->gpio0_swport_dr_l = devm_ioremap(&pdev->dev, res_dr_l->start, resource_size(res_dr_l)); led_drv_dat->gpio0_ext_port = devm_ioremap(&pdev->dev, res_ext_port->start, resource_size(res_ext_port)); if (!led_drv_dat->gpio0b_iomux || !led_drv_dat->gpio0_swport_ddr_l || !led_drv_dat->gpio0_swport_dr_l || !led_drv_dat->gpio0_ext_port) { ret = -ENOMEM; goto address_fail; } // led_drv_dat->gpio0b_iomux = // devm_ioremap_resource(&pdev->dev, platform_get_resource(pdev, IORESOURCE_MEM, 0)); // if (IS_ERR(led_drv_dat->gpio0b_iomux)) { // ret = PTR_ERR(led_drv_dat->gpio0b_iomux); // goto ioremap_fail; // } // led_drv_dat->gpio0_swport_ddr_l = // devm_ioremap_resource(&pdev->dev, platform_get_resource(pdev, IORESOURCE_MEM, 1)); // if (IS_ERR(led_drv_dat->gpio0_swport_ddr_l)) { // ret = PTR_ERR(led_drv_dat->gpio0_swport_ddr_l); // goto ioremap_fail; // } // led_drv_dat->gpio0_swport_dr_l = // devm_ioremap_resource(&pdev->dev, platform_get_resource(pdev, IORESOURCE_MEM, 2)); // if (IS_ERR(led_drv_dat->gpio0_swport_dr_l)) { // ret = PTR_ERR(led_drv_dat->gpio0_swport_dr_l); // goto ioremap_fail; // } // led_drv_dat->gpio0_ext_port = // devm_ioremap_resource(&pdev->dev, platform_get_resource(pdev, IORESOURCE_MEM, 3)); // if (!led_drv_dat->gpio0_ext_port) { // ret = -ENODEV; // goto ioremap_fail; // } // Set pin multiplexing to GPIO val = readl(led_drv_dat->gpio0b_iomux); val |= 0x70000000; // write access val &= 0xFFFF8FFF; // gpio0_b7 writel(val, led_drv_dat->gpio0b_iomux); pr_info("gpio0b_7 is set as GPIO\n"); return 0;address_fail: device_destroy(led_drv_dat->class, led_drv_dat->dev_num);device_create_fail: class_destroy(led_drv_dat->class);class_create_fail: cdev_del(&led_drv_dat->cdev);cdev_add_fail: unregister_chrdev_region(led_drv_dat->dev_num, 1);alloc_chrdev_region_fail:devm_kzalloc_fail: return ret;}static int my_platform_driver_remove(struct platform_device *pdev){ struct led_drv_data *led_drv_dat = platform_get_drvdata(pdev); device_destroy(led_drv_dat->class, led_drv_dat->dev_num); class_destroy(led_drv_dat->class); cdev_del(&led_drv_dat->cdev); unregister_chrdev_region(led_drv_dat->dev_num, 1); return 0;}static struct platform_driver my_platform_driver = { .driver ={ .name = "light_up_led", .owner = THIS_MODULE, }, .probe = my_platform_driver_probe, .remove = my_platform_driver_remove, };module_platform_driver(my_platform_driver);MODULE_LICENSE("GPL");MODULE_AUTHOR("even629<asqwgo@outlook.com>");MODULE_DESCRIPTION("This is light up led example for platform bus"); |
A platform_driver may be used by multiple platform_device. If written in the module_exit function, when the device is hot-unplugged (or removed via sysfs), resources will not be released. Therefore, resource cleanup logic should be placed in the remove function.

