Introduction to the Platform Device Driver Model

In the Linux kernel, the <span>platform</span> device driver model is used to manage devices that are not directly mounted on physical buses (such as PCI or USB), for example, hardware modules (such as GPIO controllers, timers, etc.) within a System on Chip (SoC). This model associates devices (<span>platform_device</span>) and drivers (<span>platform_driver</span>) through a virtual <span>platform_bus_type</span> bus, providing a unified resource management and matching mechanism.

Basic Concepts of the Model

Platform Device: A special type of device that does not have a standard device node or device file, but is described through a device tree. Platform devices are typically provided by chip manufacturers and are fixedly integrated into the hardware platform, making them non-removable or non-addable.

Platform Driver: A device driver in the Linux kernel that supports platform devices. It registers the mapping relationship between platform devices and device drivers to implement functions such as device initialization, configuration, and management.

Platform Bus: A type of device bus in the Linux kernel used to manage and describe the relationship between platform devices and device drivers. The platform bus supports describing the relationship between hardware platforms and devices in the device tree and can automatically load and initialize device drivers based on the information in the device tree.

Basic Data Structures

struct platform_device

Describes information about the platform device, including device name, resources (such as memory, interrupts), and device private data.

struct platform_device {
    const char  *name; // Device name, used to identify the type of device
    int     id; // Unique identifier for the device, used to distinguish multiple devices of the same type
    bool    id_auto;
    struct device   dev; // Device structure, contains basic information and operation functions of the device
    u32     num_resources; // Size of the resource array, indicating how many types of resources the device has
    struct resource *resource; // Resource array of the device, contains information about memory, I/O ports, interrupts, etc.
    const struct platform_device_id *id_entry;
    char *driver_override; /* Driver name to force a match */
    /* MFD cell pointer */
    struct mfd_cell *mfd_cell;
    /* arch specific additions */
    struct pdev_archdata    archdata;
};

struct platform_driver

Describes the behavior of the platform driver, including driver name, matching rules, device probe and remove functions, etc.

struct platform_driver {
    int (*probe)(struct platform_device *);
    int (*remove)(struct platform_device *);
    void (*shutdown)(struct platform_device *);
    int (*suspend)(struct platform_device *, pm_message_t state);
    int (*resume)(struct platform_device *);
    struct device_driver driver;
    const struct platform_device_id *id_table;
    bool prevent_deferred_probe;
};
  • probe: Device initialization function, called when the device successfully matches the driver, used to initialize the device.
  • remove: Device removal function, called when the device is removed, used to release device resources.
  • shutdown: Device shutdown function, called during system shutdown, used to turn off the device.
  • suspend: Device suspend function, called when the system enters sleep mode, used to save device state.
  • resume: Device resume function, called when the system wakes up from sleep mode, used to restore device state.
  • driver: Device driver structure, contains basic information and operation functions of the driver.
  • id_table: Device ID table, used to match devices and drivers.
  • prevent_deferred_probe: Flag to prevent deferred probing.

struct resource

struct 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;      // Type and attributes of the resource
    struct resource *parent, *sibling, *child; // Pointers for maintaining the hierarchy of resources
};
  • start: Start address of the resource, such as the starting address of memory or the starting number of an I/O port.
  • end: End address of the resource, such as the ending address of memory or the ending number of an I/O port.
  • name: Name of the resource, used to identify the resource.
  • flags: Type and attributes of the resource, common values include:
  • <span>IORESOURCE_IO</span>: Indicates an I/O port resource.
  • <span>IORESOURCE_MEM</span>: Indicates a memory resource.
  • <span>IORESOURCE_IRQ</span>: Indicates an interrupt resource.
  • <span>IORESOURCE_DMA</span>: Indicates a DMA channel resource.
  • parent, sibling, child: Used to maintain the hierarchy of resources, typically used when the kernel manages resources.

Manually describing hardware resources:

static struct resource my_device_resource[] = {
    [0] = {
        .start = MY_DEVICE_MEM_START,
        .end = MY_DEVICE_MEM_END,
        .flags = IORESOURCE_MEM,
    },
    [1] = {
        .start = MY_DEVICE_IRQ_START,
        .end = MY_DEVICE_IRQ_END,
        .flags = IORESOURCE_IRQ,
    },
};

static struct platform_device my_platform_device = {
    .name = "my_device",
    .id = -1,
    .resource = my_device_resource,
    .num_resources = ARRAY_SIZE(my_device_resource),
};

Describing hardware resources using device tree:

After Linux 3.x, developers no longer fill in platform_device and register devices using the above coding method, but prefer to expand platform_device based on the contents of the device tree. Device nodes are defined in the device tree source files (<span>.dts</span> or <span>.dtsi</span>) to describe hardware resources.

/ {
    compatible = "my-custom-board";
    #address-cells = &lt;1&gt;;
    #size-cells = &lt;1&gt;;

    my_device@10000000 {            // Device node name (customizable)
        compatible = "vendor,my-device";  // Key identifier for matching the driver
        reg = &lt;0x10000000 0x1000&gt;;       // Memory resource: start address 0x10000000, length 0x1000
        interrupts = &lt;5 0x1&gt;;            // Interrupt number 5, trigger method (e.g., 0x1 indicates rising edge trigger)
        status = "okay";                 // Enable device
    };
};
  • compatible: Key identifier for driver matching, must be consistent with the <span>of_match_table</span> in the driver.
  • reg: Describes memory resources, formatted as <span><start_address length></span>.
  • interrupts: Interrupt number and trigger method (specific format depends on the interrupt controller).
  • status: Set to <span>"okay"</span> to enable the device, <span>"disabled"</span> to disable it.
// Define device tree matching table
static const struct of_device_id mydrv_of_match[] = {
    { .compatible = "vendor,my-device" },  // Must match the compatible in the device tree
    {},
};
MODULE_DEVICE_TABLE(of, mydrv_of_match);   // Export matching table for kernel recognition

// Define Platform driver
static struct platform_driver mydrv_driver = {
    .probe    = mydrv_probe,
    .remove   = mydrv_remove,
    .driver   = {
        .name           = "my_device",     // Name (optional, device tree match takes precedence)
        .of_match_table = mydrv_of_match,  // Bind device tree matching table
        .owner          = THIS_MODULE,
    },
};

In the Platform driver, the <span>of_match_table</span> is used to associate device tree nodes.

// Define device tree matching table
static const struct of_device_id mydrv_of_match[] = {
    { .compatible = "vendor,my-device" },  // Must match the compatible in the device tree
    {},
};
MODULE_DEVICE_TABLE(of, mydrv_of_match);   // Export matching table for kernel recognition

// Define Platform driver
static struct platform_driver mydrv_driver = {
    .probe    = mydrv_probe,
    .remove   = mydrv_remove,
    .driver   = {
        .name           = "my_device",     // Name (optional, device tree match takes precedence)
        .of_match_table = mydrv_of_match,  // Bind device tree matching table
        .owner          = THIS_MODULE,
    },
};

Matching Process of Platform Devices and Drivers

static int platform_match(struct device *dev, struct device_driver *drv)
{
    struct platform_device *pdev = to_platform_device(dev);
    struct platform_driver *pdrv = to_platform_driver(drv);

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

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

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

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

    /* fall-back to driver name match */
    return (strcmp(pdev-&gt;name, drv-&gt;name) == 0);
}

There are four possible ways to match <span>platform_device</span> and <span>platform_driver</span>: first, based on device tree style matching; second, based on ACPI style matching; third, matching the ID table (i.e., whether the name of the <span>platform_device</span> appears in the <span>platform_driver</span>‘s ID table); and fourth, matching the name of the <span>platform_device</span> with the driver’s name.

Advantages of Introducing the Platform Device Driver Model

  • Allows devices to be attached to a bus, conforming to the device model of the Linux kernel after 2.6. This results in the possibility of associated sysfs nodes and device power management.

  • Isolates BSP and drivers. The BSP defines platform devices and the resources used by devices, as well as specific configuration information for devices, while in the driver, resources and data can be obtained through a generic API, achieving separation of board-specific code and driver code, resulting in better scalability and cross-platform compatibility for drivers.

  • Allows a single driver to support multiple device instances.

Leave a Comment