Linux Platform Driver Data Matching- Free Linux Device Drivers Course

 

← Previous Lecture  |  Next Lecture

Linux Platform Driver Data Matching

Part of the Free Linux Kernel Development Course — Platform Device Drivers Chapter, Lecture 7

Kernel Target: 6.x
Level: Intermediate
Read Time: 14 min

In this free Linux device drivers course lecture, we look at how a single platform driver can serve several hardware variants using the driver_data field inside platform_device_id. If you have been following this free Linux kernel development course, you already know how the kernel matches a driver to a device. Here we go one level deeper: how the driver tells devices apart once a match has already happened, and what the kernel does when no id_table is present at all.

What You Will Learn

  • What the driver_data field in platform_device_id actually does
  • Two ways to use driver_data: as a raw pointer versus as an index
  • Why the index pattern is the safer, more modern choice for kernel 6.x drivers
  • How to build one driver that supports multiple hardware variants
  • How platform bus name-matching works as the final fallback
  • Common mistakes, best practices, and troubleshooting tips

Prerequisites

This lecture builds directly on the previous parts of this free embedded Linux course. Please review these first if you are new here:

You should be comfortable building and loading a kernel module, and you should already understand what an id_table is and why MODULE_DEVICE_TABLE() matters.

Quick Recap: Why driver_data Exists

A platform driver often has to support more than one physical device that behaves almost the same way, but not exactly. Think of a family of sensor chips: same register layout, same driver logic, but different measurement ranges or optional features per chip. Instead of writing a separate driver for every variant, the kernel lets you attach a small piece of per-entry data directly to each row of your id_table, through the driver_data field.

How driver_data Flows From id_table To probe()

1. id_table entry { .name = “ep-tempsensor-pro”, .driver_data = EP_TEMP_PRO }
2. Bus match platform_match() compares device name to table name
3. probe() runs pdev->id_entry->driver_data now holds EP_TEMP_PRO
4. Driver uses it Index into a capability array to configure this instance

Two Styles of driver_data

Pointer-Style driver_data (Legacy Pattern)

The oldest approach casts driver_data directly into a pointer address, then casts it back inside probe(). It works, but it has real downsides on a modern kernel: the pointer has to stay valid for the lifetime of the module, it is easy to accidentally point at stack or freed memory, and static analysis tools generally dislike arbitrary pointer-to-integer-to-pointer casts.

/* Legacy-style: driver_data cast as a pointer address (avoid for new code) */
static const struct ep_chip_info ep_pro_info = { .max_range_mdeg = 150000, .has_alarm = true };

static const struct platform_device_id ep_tempsensor_ids[] = {
    { "ep-tempsensor-pro", (kernel_ulong_t)&ep_pro_info },
    { }
};

Index-Style driver_data (Recommended for Kernel 6.x)

The safer pattern treats driver_data as a plain integer index into a fixed array that already exists in your driver’s .data or .rodata section. There is no runtime pointer arithmetic, no casting risk, and the compiler can catch type mistakes at build time. This is the pattern used throughout the rest of this lecture.

Hands-On: A Multi-Variant Platform Driver (ep_variant_demo)

We will build an original driver called ep_variant_demo that supports three fictional temperature sensor variants sharing one driver: a basic model, a pro model, and an industrial model. Each variant has a different maximum measurable range and a different alarm capability. The driver exposes the active variant’s capabilities through a read-only sysfs attribute.

Step 1: Define the Variant Enum and Capability Table

enum ep_temp_variant {
    EP_TEMP_BASIC,
    EP_TEMP_PRO,
    EP_TEMP_INDUSTRIAL,
    EP_TEMP_VARIANT_MAX
};

struct ep_chip_info {
    const char *label;
    int max_range_mdeg;   /* max measurable range, milli-degrees C */
    bool has_alarm;
};

static const struct ep_chip_info ep_chip_table[EP_TEMP_VARIANT_MAX] = {
    [EP_TEMP_BASIC]      = { "basic",      85000,  false },
    [EP_TEMP_PRO]        = { "pro",        150000, true  },
    [EP_TEMP_INDUSTRIAL] = { "industrial", 200000, true  },
};

Step 2: Build the id_table Using the Enum as driver_data

static const struct platform_device_id ep_tempsensor_ids[] = {
    { "ep-tempsensor-basic",      EP_TEMP_BASIC      },
    { "ep-tempsensor-pro",        EP_TEMP_PRO        },
    { "ep-tempsensor-industrial", EP_TEMP_INDUSTRIAL },
    { }
};
MODULE_DEVICE_TABLE(platform, ep_tempsensor_ids);

Step 3: Private Data and the probe() Function

struct ep_temp_dev {
    const struct ep_chip_info *info;
};

static ssize_t capability_show(struct device *dev,
                                struct device_attribute *attr, char *buf)
{
    struct ep_temp_dev *tdev = dev_get_drvdata(dev);

    return sysfs_emit(buf, "variant=%s max_range_mdeg=%d has_alarm=%d\n",
                       tdev->info->label, tdev->info->max_range_mdeg,
                       tdev->info->has_alarm);
}
static DEVICE_ATTR_RO(capability);

static int ep_variant_probe(struct platform_device *pdev)
{
    const struct platform_device_id *id = platform_get_device_id(pdev);
    struct ep_temp_dev *tdev;

    if (!id || id->driver_data >= EP_TEMP_VARIANT_MAX)
        return -EINVAL;

    tdev = devm_kzalloc(&pdev->dev, sizeof(*tdev), GFP_KERNEL);
    if (!tdev)
        return -ENOMEM;

    tdev->info = &ep_chip_table[id->driver_data];
    platform_set_drvdata(pdev, tdev);

    dev_info(&pdev->dev, "ep_variant_demo: probed %s variant\n", tdev->info->label);

    return device_create_file(&pdev->dev, &dev_attr_capability);
}

static void ep_variant_remove(struct platform_device *pdev)
{
    device_remove_file(&pdev->dev, &dev_attr_capability);
}

static struct platform_driver ep_variant_driver = {
    .probe = ep_variant_probe,
    .remove = ep_variant_remove,
    .id_table = ep_tempsensor_ids,
    .driver = {
        .name = "ep_variant_demo",
    },
};
module_platform_driver(ep_variant_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("EmbeddedPathashala");
MODULE_DESCRIPTION("Original multi-variant platform driver example, driver_data index pattern");

Notice that remove() returns void, matching the modern platform driver signature used since kernel 6.11. If you are following along on an older kernel, change it back to int and return 0.

Step 4: Register a Device Instance and Test It

/* In a small test module, or via platform_device_register_simple() */
struct platform_device *pdev =
    platform_device_register_simple("ep-tempsensor-pro", -1, NULL, 0);
$ sudo insmod ep_variant_demo.ko
$ sudo insmod ep_variant_test.ko
$ dmesg | tail -n 2
[  512.114201] ep_variant_demo: probed pro variant
$ cat /sys/devices/platform/ep-tempsensor-pro.0/capability
variant=pro max_range_mdeg=150000 has_alarm=1

Try changing the registered name to ep-tempsensor-industrial and reloading; the sysfs output should switch to the industrial capability line without touching a single line of driver logic. That is the entire point of the index pattern: one driver, several personalities, zero duplicated code.

Name-Based Matching: The Final Fallback

Not every platform driver ships an id_table at all. Many modern drivers only fill in .driver.name and rely on platform_match() falling back to a plain string comparison between the driver’s name and the device’s name. This still works today, but on a kernel 6.x system it is genuinely the last resort: the match function checks device tree compatible strings and ACPI identifiers first, then the id_table, and only then the bare name string.

Match Method Checked When Typical Use Today
Device Tree compatible First, if CONFIG_OF is enabled and a DT node is bound Almost all SoC platform drivers
ACPI ID Second, if CONFIG_ACPI is enabled and an ACPI companion exists x86 laptop and server platform drivers
platform_device_id table Third, when the driver declares an id_table Drivers supporting multiple chip variants, like ep_variant_demo
Name string fallback Last, direct strcmp between device and driver name Legacy board-file drivers, simple single-device drivers

In practice you will rarely need to lean on name matching by itself for new code, but you will see it constantly while reading existing kernel drivers, so it is worth recognizing on sight: if a platform_driver struct only has .driver.name filled in and no id_table, it is depending entirely on this fallback.

Common Mistakes and Troubleshooting

  • Forgetting the bounds check on driver_data. If you index an array with id->driver_data without checking it against your enum’s max value, a mismatched or corrupted id_table entry can read out of bounds. Always validate first, as in ep_variant_probe() above.
  • Mixing pointer-style and index-style in the same table. Pick one convention per driver. Mixing the two makes the probe function fragile and hard to review.
  • Missing MODULE_DEVICE_TABLE(). Without it, module auto-loading based on the id_table will not work, even though manual probing still does.
  • Assuming name matching still fires when an id_table exists. Once your driver declares an id_table, the kernel prefers table-based matching for entries present in that table; do not expect the bare name fallback to catch typos in your table.

Best Practices

  • Prefer the index pattern for driver_data on any new kernel 6.x driver.
  • Keep your capability table static const so it lives in read-only memory.
  • Always range-check driver_data before using it as an array index.
  • Use devm_kzalloc() for per-device private data so cleanup is automatic on probe failure.

Performance Considerations

Both driver_data styles are effectively free at runtime: matching happens once at probe time, not on every I/O operation. The index pattern has a very slight edge because a plain array lookup is more cache-friendly than dereferencing a scattered pointer, but this only matters if you are probing thousands of devices at boot.

Security Considerations

Because driver_data ultimately comes from data compiled into your own module, it is not attacker-controlled in the way userspace input is. The real risk is a self-inflicted out-of-bounds read if you skip the bounds check shown above, which is why that single if statement in ep_variant_probe() is not optional in production code.

Chapter Recap: Platform Device Drivers

This closes out the Platform Device Drivers chapter of this free Linux kernel development course. Across this chapter you learned:

Full Chapter Path

Platform bus concept & probe/remove
Platform data via dev.platform_data
Bus matching order
MODULE_DEVICE_TABLE & id_table
driver_data indexing (this lecture)
Name matching fallback (this lecture)

What’s Next

The platform bus mechanism you just mastered is still widely used, but on modern embedded Linux, most device discovery happens through the device tree instead of hand-written board files. The next chapter in this free embedded Linux course moves on to device tree fundamentals: compatible strings, of_match_table, and reading properties from a .dts file into your driver.

Continue This Free Linux Device Drivers Course

Start the next chapter on Device Tree fundamentals and keep building real kernel 6.x driver skills.

Start Device Tree Chapter
Back to Course Index

Key Takeaways

  • driver_data in platform_device_id carries per-entry information from your id_table into probe().
  • Index-style driver_data is the modern, safer choice over raw pointer casting.
  • One driver can serve multiple hardware variants through a shared capability table.
  • Name-based matching is still the final fallback in platform_match(), behind DT, ACPI, and id_table matching.

Conclusion

The driver_data field looks like a small detail, but it is what lets a single, well-tested platform driver scale across an entire product family instead of forking into near-duplicate drivers. Combined with the matching order you now understand end to end, from device tree down to plain name comparison, you have the full picture of how the Linux platform bus decides which driver owns which device. That completes the Platform Device Drivers chapter of this free Linux kernel development course.

Frequently Asked Questions

What is driver_data used for in platform_device_id?

It carries a small piece of per-entry data, usually an index or flags value, from a specific id_table row into the probe() function so one driver can distinguish between hardware variants.

Should I cast driver_data as a pointer or use it as an index?

For new kernel 6.x drivers, prefer the index pattern. It avoids pointer casting risks and lets the compiler catch type errors at build time.

Does every platform driver need an id_table?

No. Many simple drivers only set driver.name and rely on the name-matching fallback inside platform_match(), especially older or single-device drivers.

What is the platform bus matching priority order in kernel 6.x?

Device tree compatible strings are checked first, then ACPI identifiers, then the platform_device_id table, and finally a plain name string comparison.

Why does driver_data need a bounds check?

Because it is used as an array index in the recommended pattern, an unchecked value could read outside your capability array if an id_table entry is ever mismatched or corrupted.

Is name-based platform matching still relevant on modern kernels?

Yes, it still exists as the final fallback, but new drivers targeting device tree or ACPI platforms rarely depend on it directly.

What changed in the platform_driver remove() function on recent kernels?

Since kernel 6.11, remove() returns void instead of int, reflecting that the kernel could not act on a nonzero return value anyway.

Is this free Linux kernel development course suitable for beginners?

Yes, each lecture builds on the previous one, starting from character device driver basics through platform drivers and now into device data matching, with original hands-on code examples throughout.

 

← Previous Lecture |  Next Lecture

 

Leave a Reply

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