Driver Shape
The example is a platform driver named drhyme-sensor. It binds through
the Linux driver model, creates a per-device miscdevice, exposes a
character-device node such as /dev/drhyme_sensor0, and implements a
small ioctl ABI. It is deliberately more realistic than a pure module_init
toy while still being compact enough to read in one sitting.
In real hardware, the platform device usually comes from Device Tree, ACPI, or board
setup code. For lab testing, this module has create_demo_device=1 by
default, so the module registers a synthetic platform_device and then
binds its own platform_driver.
Example Files
The split is intentional: the driver includes a small UAPI header shared with the userspace test program. That mirrors the way real drivers keep ioctl command numbers and userspace-visible structs stable and explicit.
Lifecycle
The important call chain in the demo path is:
insmod/modprobe drhyme_sensor
-> kernel loads drhyme_sensor.ko
-> module_init(drhyme_init)
-> platform_driver_register(&drhyme_driver)
-> platform_device_register_simple("drhyme-sensor", ...)
-> platform bus matches device name/id table
-> drhyme_probe(pdev)
-> misc_register(&sensor->miscdev)
-> userspace sees /dev/drhyme_sensor0
The teardown path reverses ownership:
rmmod/modprobe -r drhyme_sensor
-> module_exit(drhyme_exit)
-> platform_device_unregister(demo_pdev)
-> drhyme_remove(pdev)
-> misc_deregister(&sensor->miscdev)
-> platform_driver_unregister(&drhyme_driver)
In a real Device Tree boot, the device would often exist before the module is loaded.
Then platform_driver_register is enough to cause the platform bus to walk
unbound devices and call probe for a match.
Probe
probe is the point where the abstract driver meets one concrete device.
The callback should allocate per-device state, map resources, request IRQs, initialize
locks and runtime state, read firmware properties, register userspace interfaces, and
attach state with platform_set_drvdata.
static int drhyme_probe(struct platform_device *pdev)
{
struct drhyme_sensor *sensor;
u32 default_config = 0;
int ret;
sensor = devm_kzalloc(&pdev->dev, sizeof(*sensor), GFP_KERNEL);
if (!sensor)
return -ENOMEM;
sensor->dev = &pdev->dev;
mutex_init(&sensor->lock);
device_property_read_u32(&pdev->dev,
"digital-rhyme,default-config",
&default_config);
sensor->miscdev.minor = MISC_DYNAMIC_MINOR;
sensor->miscdev.name = sensor->misc_name;
sensor->miscdev.fops = &drhyme_fops;
sensor->miscdev.parent = &pdev->dev;
ret = misc_register(&sensor->miscdev);
if (ret)
return dev_err_probe(&pdev->dev, ret,
"failed to register misc device\n");
platform_set_drvdata(pdev, sensor);
return 0;
}
The example uses devm_kzalloc, so the memory lifetime is tied to the
device. It still explicitly deregisters the misc device in remove because
that registration creates a userspace-visible object that must disappear before the
driver state goes away.
Ioctls
Ioctls are an ABI, not just an internal command dispatch table. Once shipped, command numbers and struct layouts become compatibility promises. This example uses the standard ioctl macros:
| Command | Direction | Purpose |
|---|---|---|
DRHYME_IOCTL_RESET | _IO | No pointer argument; reset driver state. |
DRHYME_IOCTL_GET_STATUS | _IOR | Copy struct drhyme_sensor_status to userspace. |
DRHYME_IOCTL_SET_CONFIG | _IOW | Copy struct drhyme_sensor_config from userspace. |
static long drhyme_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
if (_IOC_TYPE(cmd) != DRHYME_IOCTL_MAGIC)
return -ENOTTY;
switch (cmd) {
case DRHYME_IOCTL_GET_STATUS:
memset(&status, 0, sizeof(status));
/* fill status under lock */
if (copy_to_user((void __user *)arg, &status, sizeof(status)))
return -EFAULT;
return 0;
default:
return -ENOTTY;
}
}
- Return
-ENOTTYfor unknown commands. - Use fixed-width UAPI types such as
__u32and__u64. - Initialize structs before copying them to userspace to avoid leaking padding.
- Use
copy_from_userandcopy_to_user; never trust userspace pointers. - For 32-bit userspace on a 64-bit kernel, add
.compat_ioctlwhere pointer layout permits it.
modprobe And The Load Mechanism
modprobe is a userspace loader from kmod. It does not directly call
your driver's probe. It resolves module names and aliases, loads
dependencies, asks the kernel to load the .ko, and then the kernel runs
the module's init function.
Manual load by module name:
sudo modprobe drhyme_sensor
Automatic load by modalias is the more interesting path:
kernel discovers device
-> emits modalias through sysfs/uevent
-> udev asks kmod/modprobe to satisfy that alias
-> depmod-generated modules.alias maps alias to drhyme_sensor.ko
-> module is loaded
-> module_init registers platform_driver
-> bus matching calls drhyme_probe
The example provides both MODULE_DEVICE_TABLE(of, ...) and
MODULE_DEVICE_TABLE(platform, ...). Those macros place alias metadata in
the module. After installation, depmod reads that metadata and updates
files such as modules.alias.
sudo make modules_install
sudo depmod -a
modinfo drhyme_sensor
modprobe drhyme_sensor
If a matching Device Tree node exists, such as
compatible = "digital-rhyme,drhyme-sensor", the kernel's device model can
cause the module to be requested by alias. If you load by hand with insmod,
dependency and alias handling are bypassed.
Build System
External modules should build through kbuild. The kernel build system injects the correct include paths, compiler flags, generated headers, symbol versioning, and module post-processing.
ifneq ($(KERNELRELEASE),)
obj-m += drhyme_sensor.o
else
KDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all: modules user
modules:
$(MAKE) -C $(KDIR) M=$(PWD) modules
user: drhyme_ioctl_test
$(CC) -Wall -Wextra -O2 -o drhyme_ioctl_test drhyme_ioctl_test.c
endif
The wrapper Makefile is evaluated twice: first by normal make, then by
kbuild with KERNELRELEASE set. The obj-m line is what kbuild
consumes to produce drhyme_sensor.ko.
Build And Run
sudo apt install build-essential linux-headers-$(uname -r)
cd examples/serious_platform_driver
make
sudo insmod drhyme_sensor.ko
dmesg | tail
cat /dev/drhyme_sensor0
./drhyme_ioctl_test /dev/drhyme_sensor0
sudo rmmod drhyme_sensor
make clean
To test the installed/modprobe path:
sudo make modules_install
sudo depmod -a
sudo modprobe drhyme_sensor
sudo modprobe -r drhyme_sensor
create_demo_device=0 and let firmware/device enumeration create the
platform device that triggers matching and probe.
Useful Kernel Driver Links
- Building External Modules: official kbuild guide for out-of-tree module builds.
- ioctl based interfaces: official guidance on ioctl command numbers, returns, compat, and ABI pitfalls.
- Platform Devices and Drivers: platform bus, platform devices, platform drivers, and binding behavior.
- Driver Model: the larger driver-core documentation area.
- Submitting patches: kernel patch submission expectations if this grows beyond a local module.
- Linux kernel coding style: naming, formatting, and idioms expected in kernel code.
- Kernel development tools: debugging, testing, sanitizers, and analysis tools.
Source notes: the kbuild page explains why external modules should use kbuild and how
M=$PWD works; the ioctl page documents the modern ioctl macros and return
conventions; the platform-driver page describes probe/remove
and automatic driver/device binding.