Cover image for Linux Platform Bus

Linux Platform Bus

Words 6.2k
Views
Visitors

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

Relationship between device/driver/platform bus
Relationship between device/driver/platform bus

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.
CategoryFlagsDescription
Resource typeRelated flagsIORESOURCE_IOResource is an I/O port resource
IORESOURCE_MEMResource is a memory resource
IORESOURCE_REGResource is a register offset
IORESOURCE_IRQResource is an interrupt resource
IORESOURCE_DMAResource is a DMA (Direct Memory Access) resource
IORESOURCE_BUSBus
Resource attributes and characteristicsRelated flagsIORESOURCE_PREFETCHResource is a side-effect-free prefetchable resource
IORESOURCE_READONLYResource is read-only
IORESOURCE_CACHEABLEResource supports caching
IORESOURCE_RANGELENGTHResource range length
IORESOURCE_SHADOWABLEResource can be replaced by a shadow resource
IORESOURCE_SIZEALIGNResource size field alignment
IORESOURCE_STARTALIGNStart address field alignment
IORESOURCE_MEM_64Resource is a 64-bit memory resource
IORESOURCE_WINDOWResource is forwarded by a bridge
IORESOURCE_MUXEDResource is reused by software
IORESOURCE_SYSRAMResource is system RAM (modifier)
Status and controlRelated flagsIORESOURCE_EXCLUSIVEUser space cannot map this resource
IORESOURCE_DISABLEDResource is currently disabled
IORESOURCE_UNSETAddress has not been assigned to the resource
IORESOURCE_AUTOThe address is automatically assigned by the system.
IORESOURCE_BUSYDriver 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 beIORESOURCE_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()

ItemDescription
Function definitionint platform_device_register(struct platform_device *pdev);
Header file#include <linux/platform_device.h>
parameter pdevpoint toplatform_devicePointer to a structure, describing the platform device to be registered, including device name, resources, device ID, and other information.
FunctionRegister the platform device with the kernel, enabling it to participate in device resource allocation and driver matching.
Return valueSuccess: returns 0;
Failure: return a negative error code
1234567891011121314151617181920212223
// include/linux/platform_device.h#define platform_get_device_id(pdev)	((pdev)->id_entry)#define dev_is_platform(dev) ((dev)->bus == &platform_bus_type)#define to_platform_device(x) container_of((x), struct platform_device, dev)extern 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()

ItemDescription
Function definitionvoid platform_device_unregister(struct platform_device *pdev);
Header file#include <linux/platform_device.h>
parameter pdevPointing to the platform device to be unregisteredplatform_devicestructure pointer
FunctionUnregister a registered platform device, remove the device from the kernel, and perform resource cleanup.
Return valueNo 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
#include <linux/init.h>#include <linux/module.h>#include <linux/platform_device.h>#include <linux/ioport.h> // struct resource#define MEM_START_ADDR 0xFDD60000#define MEM_END_ADDR 0xFDD60004#define IRQ_NUMBER 101static 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_table

  • prevent_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 usedMODULE_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()

ItemDescription
Function definitionint platform_driver_register(struct platform_driver *driver);
Header file#include <linux/platform_device.h>
The driver parameterpoint toplatform_driverA pointer to the structure, describing the platform driver to be registered, including attributes and callback functions.
FunctionRegisters the platform driver with the kernel, enabling the kernel to match it with specific platform devices and invoke the corresponding callback functions.
Return valueSuccess: returns 0;
Failure: return a negative error code
1234
#define platform_driver_register(drv) \	__platform_driver_register(drv, THIS_MODULE)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()

ItemDescription
Function definitionvoid platform_driver_unregister(struct platform_driver *driver);
Header file#include <linux/platform_device.h>
The driver parameterpoint toplatform_drivera pointer to a structure that describes the platform driver to be unregistered.
FunctionUnregister the platform driver from the kernel so that it no longer participates in driver matching.
Return valueNo 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.

ItemDescription
Function definitionstruct resource *platform_get_resource(struct platform_device *pdev, unsigned int type, unsigned int num);
Header file#include <linux/platform_device.h>
parameter pdevPointer to the platform device from which to obtain the resource (platform_device) structure pointer
Parameter typeResource type, such as:
IORESOURCE_MEM: memory resource
IORESOURCE_IO: I/O resource
IORESOURCE_IRQ: interrupt resource
Parameter numResource index number, used to select the same type of thenumth resource
FunctionGet resource information of the specified type and index from the platform device’s resource array.
Return valueSuccess: return pointerstruct resourcepointer;
Failure or resource does not exist: returnNULL

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() */#define module_platform_driver(__platform_driver) \	module_driver(__platform_driver, platform_driver_register, \			platform_driver_unregister)/* 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. */#define builtin_platform_driver(__platform_driver) \	builtin_driver(__platform_driver, platform_driver_register)/* 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() */#define module_platform_driver_probe(__platform_driver, __platform_probe) \static int __init __platform_driver##_init(void) \{ \	return platform_driver_probe(&(__platform_driver), \				     __platform_probe);    \} \module_init(__platform_driver##_init); \static void __exit __platform_driver##_exit(void) \{ \	platform_driver_unregister(&(__platform_driver)); \} \module_exit(__platform_driver##_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. */#define builtin_platform_driver_probe(__platform_driver, __platform_probe) \static int __init __platform_driver##_init(void) \{ \	return platform_driver_probe(&(__platform_driver), \				     __platform_probe);    \} \device_initcall(__platform_driver##_init); \

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

Noexitpart (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 setsdriver->prevent_deferred_probe = true);
    • Applicable tosimple, one-time probingof devices (such as some SoC built-in controllers).

📝 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 pointmy_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
#include <linux/init.h>#include <linux/module.h>#include <linux/platform_device.h>#include <linux/ioport.h>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
#include <linux/init.h>#include <linux/module.h>#include <linux/platform_device.h>#include <linux/ioport.h>/* Multiplexing register */#define PMU_GRF_BASE 0xFDC20000#define GPIO0B_IOMUX_OFFSET 0xC#define GPIO0B_IOMUX (PMU_GRF_BASE + GPIO0B_IOMUX_OFFSET)/* Data register and direction register */#define GPIO0_BASE 0xFDD60000#define GPIO_SWPORT_DDR_L_OFFSET 0x8#define GPIO_SWPORT_DR_L_OFFSET 0x0#define GPIO_EXT_PORT_OFFSET 0x70// Direction register#define GPIO0_SWPORT_DDR_L (GPIO0_BASE + GPIO_SWPORT_DDR_L_OFFSET)// Data register#define GPIO0_SWPORT_DR_L (GPIO0_BASE + GPIO_SWPORT_DR_L_OFFSET)// External input register, read-only#define GPIO0_EXT_PORT (GPIO0_BASE + GPIO_EXT_PORT_OFFSET)#define REG_SIZE 4struct 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
#include <linux/init.h>#include <linux/module.h>#include <linux/platform_device.h>#include <linux/ioport.h>#include <linux/cdev.h>#include <linux/fs.h>#include <linux/slab.h>#include <linux/uaccess.h>#include <linux/io.h>#include <linux/ioctl.h>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);}#define LED_OPEN _IO('A', 0)#define LED_CLOSE _IO('A', 1)#define LED_STATUS _IOR('A', 2, 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.

Loading comments…