MODULE_DEVICE_TABLE Platform Driver Matching- Free Linux Device Drivers Course

MODULE_DEVICE_TABLE Platform Driver Matching
Free Linux Device Drivers Course • Free Linux Kernel Development Course • Kernel 6.x
Chapter 5 • Part 6
Beginner Friendly
Kernel 6.x Ready

If you are following our free linux device drivers course, you already know that a platform driver can only handle a platform device if the kernel decides they match. In this lecture we go one level deeper and study the module_device_table linux kernel macro, the exact matching sequence the kernel runs on modern kernel 6.x systems, and how struct platform_device_id lets one driver serve many devices. We will also write an original, from-scratch driver so you can see id-table based matching working on your own machine, which is exactly what this free embedded linux course is designed to help you do.

module_device_table linux kernel
platform_device_id
free linux kernel development course
free embedded systems course
driver_override
platform_match

What You Will Learn

  • How every bus in the kernel — I2C, USB, and the pseudo platform bus — forms one connected device tree
  • What the MODULE_DEVICE_TABLE macro does and why it matters for module auto-loading
  • The exact four-step order the kernel uses inside platform_match() on kernel 6.x
  • How struct platform_device_id lets one driver support several device names
  • How to write and test your own id-table based platform driver
  • How driver_override lets you force a specific driver to bind to a device

Prerequisites

This lecture continues directly from our previous platform bus matching lesson. If you have not gone through it yet, please read it first: Platform Bus Matching in Linux Kernel — Previous Lecture. You should already be comfortable with struct platform_driver, probe()/remove(), and building out-of-tree kernel modules.

Quick Recap: Why Matching Exists

Every registered driver and every registered device sits on a bus. A bus is simply the referee that decides whether a driver and a device are allowed to talk to each other. This is not a platform-only idea — I2C, SPI, USB, PCI, and the pseudo platform bus all follow the same core pattern: devices register themselves, drivers register themselves, and the bus core calls a match function to connect the two sides.

How the Platform Bus Fits Into the Bigger Bus Tree

None of these buses float in isolation. USB buses can sit as children of PCI buses, MDIO buses usually sit as children of other devices, and the pseudo platform bus sits directly on top of the physical hardware bus. Each bus keeps its own private list of registered drivers, and every one of those lists ultimately answers to the same physical bus underneath. The diagram below rebuilds this relationship using plain HTML boxes instead of a picture.

Bus and Driver Tree (Kernel 6.x)

I2C Drivers

driver A, driver B, …

USB Drivers

driver A, driver B, …

Platform Drivers

gpio_keypad, led_gpio, spi_bus, uart_bus, …
Pseudo Platform Bus (matches drivers ↔ devices)
Physical Bus — SPI, I2C, PCI, SATA, MDIO, USB …

The key takeaway: platform drivers are the ones written for devices that do not sit on a real discoverable bus (no PCI IDs, no USB descriptors). The pseudo platform bus exists purely so these non-discoverable devices can still use the standard kernel driver model.

Understanding the MODULE_DEVICE_TABLE Macro

The core question every bus core has to answer is: “which driver, if any, supports this newly added device?” The answer lives in a per-driver ID table, and that table is exposed to the kernel build system through the MODULE_DEVICE_TABLE macro, defined in linux/module.h:

#define MODULE_DEVICE_TABLE(type, name)
Parameter Meaning
type Which bus the device sits on — platform, i2c, spi, usb, pci, of, acpi, and more, defined in include/linux/mod_devicetable.h
name Pointer to the driver’s xxx_device_id array used for matching, for example platform_device_id for platform devices, i2c_device_id for I2C devices, or of_device_id for device-tree matching

At compile time, MODULE_DEVICE_TABLE writes this table into the module’s .modinfo section. Tools like depmod and modprobe scan that section on every driver in the system to build a system-wide alias database. That database is what makes hot-plug auto-loading possible: when the kernel discovers a device it cannot yet handle, udev looks up the matching module in this database and loads it automatically, without you ever running insmod by hand.

Why driver.name Must Match the Module Name

For a driver built as a loadable module, driver.name should normally match the actual module name. If it does not, the module will not be auto-loaded on demand unless the driver also carries a MODULE_ALIAS() entry that adds the missing name. In practice this means: keep your platform_driver.driver.name and your .ko file name consistent, or explicitly alias them.

Inside platform_match() on Kernel 6.x

The actual comparison logic lives in drivers/base/platform.c. On a modern kernel it checks four possible matching mechanisms, in a strict order, and stops at the first one that succeeds:

Order Mechanism How it decides
1 driver_override If set from userspace, only that exact driver name is allowed to bind; every other mechanism is skipped
2 OF style Compares the device tree compatible string against of_match_table
3 ACPI style Compares ACPI IDs against acpi_match_table
4 ID table match String-compares the device name against every entry in id_table
5 (fallback) Name match If no id_table exists, directly compares pdev->name with drv->name

Every one of these mechanisms ultimately boils down to a string comparison somewhere. The helper that performs the id-table walk is small and worth reading once:

static const struct platform_device_id *platform_match_id(
    const struct platform_device_id *id,
    struct platform_device *pdev)
{
    while (id->name[0]) {
        if (strcmp(pdev->name, id->name) == 0) {
            pdev->id_entry = id;
            return id;
        }
        id++;
    }
    return NULL;
}

Notice the loop walks the array until it hits an empty { } terminator entry, which is why every platform_device_id array you write must end with a blank element.

struct platform_device_id Explained

This is the structure your driver fills in to describe every device name it can support:

struct platform_device_id {
    char name[PLATFORM_NAME_SIZE];
    kernel_ulong_t driver_data;
};

name is the exact string the kernel compares against the platform device’s registered name. driver_data is a free-form value you control — a very common pattern is to use it to tag which hardware variant matched, so one probe() function can behave slightly differently depending on which entry matched.

Hands-On Example: Id-Table Based Matching

Let’s write two small, original modules: one that registers two platform devices with different names, and one driver that binds to both of them using a single id_table.

Step 1: The Device Registration Module

// ep_idmatch_board.c
#include <linux/module.h>
#include <linux/platform_device.h>

static struct platform_device *ep_temp_dev;
static struct platform_device *ep_humid_dev;

static int __init ep_idmatch_board_init(void)
{
    ep_temp_dev = platform_device_register_simple("ep-tempsensor", -1, NULL, 0);
    if (IS_ERR(ep_temp_dev))
        return PTR_ERR(ep_temp_dev);

    ep_humid_dev = platform_device_register_simple("ep-humiditysensor", -1, NULL, 0);
    if (IS_ERR(ep_humid_dev)) {
        platform_device_unregister(ep_temp_dev);
        return PTR_ERR(ep_humid_dev);
    }

    pr_info("ep_idmatch_board: registered ep-tempsensor and ep-humiditysensor\n");
    return 0;
}

static void __exit ep_idmatch_board_exit(void)
{
    platform_device_unregister(ep_humid_dev);
    platform_device_unregister(ep_temp_dev);
    pr_info("ep_idmatch_board: devices removed\n");
}

module_init(ep_idmatch_board_init);
module_exit(ep_idmatch_board_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("EmbeddedPathashala");
MODULE_DESCRIPTION("Registers two demo platform devices for id_table matching");

Step 2: The Driver With an Id Table

// ep_idmatch_demo.c
#include <linux/module.h>
#include <linux/platform_device.h>

#define EP_SENSOR_TEMP   1
#define EP_SENSOR_HUMID  2

static const struct platform_device_id ep_idmatch_ids[] = {
    { "ep-tempsensor",     EP_SENSOR_TEMP  },
    { "ep-humiditysensor", EP_SENSOR_HUMID },
    { }
};
MODULE_DEVICE_TABLE(platform, ep_idmatch_ids);

static int ep_idmatch_probe(struct platform_device *pdev)
{
    const struct platform_device_id *id = platform_get_device_id(pdev);

    dev_info(&pdev->dev, "probed device: %s (driver_data=%lu)\n",
             id->name, id->driver_data);

    if (id->driver_data == EP_SENSOR_TEMP)
        dev_info(&pdev->dev, "initializing temperature sensor logic\n");
    else if (id->driver_data == EP_SENSOR_HUMID)
        dev_info(&pdev->dev, "initializing humidity sensor logic\n");

    return 0;
}

static void ep_idmatch_remove(struct platform_device *pdev)
{
    dev_info(&pdev->dev, "ep_idmatch_demo removed\n");
}

static struct platform_driver ep_idmatch_driver = {
    .driver = {
        .name = "ep_idmatch_demo",
    },
    .id_table = ep_idmatch_ids,
    .probe    = ep_idmatch_probe,
    .remove   = ep_idmatch_remove,
};
module_platform_driver(ep_idmatch_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("EmbeddedPathashala");
MODULE_DESCRIPTION("Demonstrates platform_device_id based matching on kernel 6.x");

Note the remove() function above returns void, matching the modern kernel 6.11+ prototype, and we read the matched entry back with platform_get_device_id() instead of comparing strings ourselves.

Step 3: Build and Load

make -C /lib/modules/$(uname -r)/build M=$PWD modules
sudo insmod ep_idmatch_board.ko
sudo insmod ep_idmatch_demo.ko
dmesg | tail -n 6

Expected output:

ep_idmatch_board: registered ep-tempsensor and ep-humiditysensor
ep_idmatch_demo: probed device: ep-tempsensor (driver_data=1)
ep_idmatch_demo: initializing temperature sensor logic
ep_idmatch_demo: probed device: ep-humiditysensor (driver_data=2)
ep_idmatch_demo: initializing humidity sensor logic

One driver, one id_table, two devices matched automatically — exactly what platform_match() and platform_match_id() are doing behind the scenes.

Forcing a Match With driver_override

Sometimes you want to override the normal matching order entirely, for testing or for unusual hardware setups. Every platform device exposes a driver_override sysfs attribute for exactly this purpose:

echo ep_idmatch_demo | sudo tee /sys/bus/platform/devices/ep-tempsensor/driver_override
echo ep-tempsensor | sudo tee /sys/bus/platform/drivers_probe

Once driver_override is set, the kernel skips OF, ACPI, and id_table matching completely and binds only to the named driver, as we saw in step 1 of the platform_match() table above.

Common Mistakes and Troubleshooting

Mistake Fix
Forgetting the terminating { } entry in platform_device_id[] platform_match_id() reads past the array end, causing a match failure or a crash
Device registered with a name that does not exactly match any id_table entry Check for trailing spaces, case mismatch, or hyphen/underscore differences
Missing MODULE_DEVICE_TABLE() call Matching still works manually, but hot-plug auto-loading via modprobe will not
Old remove() returning int on kernel 6.11+ Switch to the void-returning prototype used in the example above

Best Practices

  • Always terminate platform_device_id[] and other id-table arrays with an empty entry
  • Prefer device tree (of_match_table) matching for new, discoverable hardware designs
  • Reserve id_table matching for non-discoverable, legacy, or test/demo devices like the one above
  • Use driver_data to branch cleanly instead of re-comparing name strings inside probe()
  • Always call MODULE_DEVICE_TABLE() on any id table you define, even during development

Performance and Security Considerations

Matching happens only at device registration or module load time, so its runtime cost is negligible for normal system operation. From a security angle, be careful with driver_override: because it bypasses the normal match hierarchy, a compromised or careless userspace write to this sysfs file could bind an unintended driver to a device, so access to it should be restricted the same way you would restrict any privileged sysfs write.

Summary / Key Takeaways

  • All buses, including the pseudo platform bus, register drivers and devices into a common kernel bus tree
  • MODULE_DEVICE_TABLE(type, name) exposes a driver’s id table for module auto-loading and matching
  • platform_match() checks, in order: driver_override, OF, ACPI, id_table, then name fallback
  • struct platform_device_id lets one driver support multiple device names via one id_table
  • driver_override can force a specific driver to bind, skipping the normal matching order

Conclusion

Understanding MODULE_DEVICE_TABLE and the internals of platform_match() takes the mystery out of how the Linux kernel decides which driver owns which device. With the id-table example you built in this lecture, you now have a working, testable pattern you can reuse for your own non-discoverable hardware. This lecture is part of our ongoing free linux kernel development course and free embedded systems course — keep following along as we move deeper into platform driver internals.

Frequently Asked Questions

What does MODULE_DEVICE_TABLE actually do in the Linux kernel?

It exposes a driver’s device id table into the module’s metadata so tools like depmod and modprobe can build an alias database used for automatic module loading when matching hardware is detected.

What is the difference between id_table matching and name matching?

id_table matching lets one driver support several device names through an array, while name matching is a simple fallback that only works when the driver has no id_table at all and the device name equals the driver name exactly.

In what order does platform_match() check for a match on kernel 6.x?

It checks driver_override first, then OF style device tree matching, then ACPI matching, then id_table matching, and finally falls back to a direct name string comparison.

Why is my platform module not loading automatically on boot?

Most often the driver is missing a MODULE_DEVICE_TABLE() call, or driver.name does not match the actual module name and no MODULE_ALIAS() was added.

What is driver_data used for inside struct platform_device_id?

It is a free-form value you define yourself, commonly used to tag which specific device variant matched so probe() can branch its behaviour without re-comparing strings.

Is id_table matching still relevant when device tree is available?

Yes, for non-discoverable or legacy hardware, and for quick demo/test drivers like the one built in this lecture, but for new discoverable hardware designs device tree (OF) matching is preferred.

What happens if I forget the terminating empty entry in platform_device_id[]?

The kernel’s matching loop keeps reading past the end of your array looking for a zero-length name, which can cause incorrect matches or a crash, so always terminate the array with { }.

Can I force a specific driver to bind to a device?

Yes, by writing the driver name to that device’s driver_override sysfs file, which skips OF, ACPI, and id_table matching and binds only to the driver you named.

Continue the Free Linux Device Drivers Course

New original lectures on platform drivers, device tree, and kernel synchronization every week.

 

Leave a Reply

Your email address will not be published. Required fields are marked *