I2C Driver Remove and Registration- Free Linux Device Drivers Course

PREV_LEC | NEXT_LEC

I2C Driver Remove and Registration

Free Linux Kernel Development Course — I2C Client Drivers, Part 2

Kernel 6.x Ready
Beginner Friendly
Hands-On Driver Code

free embedded systems course
free linux device drivers course
free linux kernel development course
i2c driver registration linux kernel
free linux development course

This lecture is part of EmbeddedPathashala’s free linux device drivers course, and it picks up exactly where our first I2C lecture left off. Once an I2C client driver’s probe() function has run successfully, two more questions come up naturally: how does the driver clean up when the device goes away, and how does the driver actually get registered with the I2C core in the first place? This lecture answers both, using an i2c driver registration linux kernel approach that is fully updated for kernel 6.x. If you’re searching for a free linux kernel development course that explains this without skipping the “why”, you’re in the right place.

What You Will Learn

  • How the remove() callback works in a modern I2C client driver, and why its function signature changed
  • How to release private data and undo whatever probe() set up
  • How module_i2c_driver() saves you from writing boilerplate init/exit code
  • How struct i2c_device_id and MODULE_DEVICE_TABLE() let the kernel know which devices your driver supports
  • How to build, load, and unload a complete, original I2C client driver on a real kernel 6.x system

Prerequisites

Before this lecture, you should be comfortable with the material in our previous I2C lecture: the two-layer I2C driver architecture (bus controller driver vs client driver), the single-argument probe(struct i2c_client *client) signature used since kernel 6.3, and i2c_set_clientdata() / i2c_get_clientdata(). You should also have a working cross-compilation setup or a Raspberry Pi / BeagleBone-style board with I2C enabled, since we will build and load a real kernel module.

Why a remove() Function Matters

Every resource a driver allocates in probe() — memory, GPIO lines, character device nodes, workqueues, sysfs entries — has to be released somewhere, or the kernel accumulates leaks every time the device is unbound or the module is removed. That “somewhere” is the remove() callback of your struct i2c_driver. Think of probe() and remove() as a matched pair: whatever you set up on the way in, you tear down on the way out, usually in the reverse order.

Probe / Remove Lifecycle
Device detected on I2C bus probe() runs
allocate memory, set clientdata, register resources
Driver unloaded / device unbound remove() runs
free memory, release resources, undo probe()

The Modern remove() Signature: int vs void

If you look at older training material or older mainline drivers, you will see the remove callback declared to return an int:

static int foo_remove(struct i2c_client *client);

This is now outdated. Kernel maintainers noticed that the return value of an I2C remove function was, in practice, almost always ignored by the I2C core — at most it triggered a warning message. Returning a non-zero value never actually stopped the device from being removed, so drivers were effectively lying about being able to “fail” a teardown. To close that gap, the I2C core moved to a void-returning remove callback, merged in kernel 6.1. On kernel 6.1 and later, the correct signature is:

static void foo_remove(struct i2c_client *client);

The parameter is unchanged — you still receive the same struct i2c_client * pointer that was passed to probe(), and you still use it to fetch your private data.

Kernel Range remove() Signature Notes
Before 6.1 int foo_remove(struct i2c_client *) Return value ignored by the core in practice
6.1 and later void foo_remove(struct i2c_client *) Current, correct signature for all new drivers

Retrieving Private Data in remove()

Just like in probe(), the first thing most remove() functions do is pull back the private driver structure that was attached with i2c_set_clientdata(). Here is a small, original example built for this course — a driver that manages one GPIO line exposed as an I2C-attached expander pin:

struct ep_gpio_priv {
    struct i2c_client *client;
    struct gpio_chip chip;
};

static void ep_gpio_remove(struct i2c_client *client)
{
    struct ep_gpio_priv *priv = i2c_get_clientdata(client);

    gpiochip_remove(&priv->chip);
}

Notice there is no return 0; at the end, and no return statement at all if there’s nothing to hand back — that’s expected for a void function. If your driver allocated the private structure with devm_kzalloc() in probe(), you don’t even need to free it manually in remove(); the device-managed allocator does that automatically once remove() returns. You only need to manually undo things that devm_ doesn’t track, such as unregistering a gpiochip, stopping a workqueue, or deleting a character device.

Driver Initialization and Registration

Once probe() and remove() exist, the driver still needs to tell the kernel it exists. In older code you’ll see this done manually with module_init() and module_exit() macros wrapping small init/exit functions that call i2c_add_driver() and i2c_del_driver(). For the overwhelming majority of I2C drivers, that boilerplate is unnecessary — there is nothing custom to do at load or unload time beyond registering the driver structure. The kernel provides a single helper macro for exactly this case:

module_i2c_driver(ep_gpio_driver);

This one line expands to the equivalent init and exit functions and wires them into module_init()/module_exit() for you. It only works when your driver has no special setup or teardown logic beyond the I2C registration itself — which is the common case for the vast majority of client drivers.

Driver and Device Provisioning: struct i2c_device_id

Registering the driver only tells the kernel that a driver named “ep_gpio” exists. It does not tell the kernel which devices that driver is willing to manage. That’s the job of the device ID table, built from struct i2c_device_id:

static const struct i2c_device_id ep_gpio_idtable[] = {
    { "ep,gpio-a" },
    { "ep,gpio-b" },
    { }
};
MODULE_DEVICE_TABLE(i2c, ep_gpio_idtable);

Each entry pairs a name string with an optional driver_data value you can use to distinguish device variants inside probe(). The table must always end with an empty { } sentinel entry so the kernel knows where the list stops. The MODULE_DEVICE_TABLE(i2c, ...) macro is what makes this table visible to user-space tools like modprobe, enabling automatic module loading when a matching device is discovered — without it, your module would build fine but never auto-load.

Assembling the Complete i2c_driver Structure

Everything comes together inside one struct i2c_driver. This is the structure the kernel actually uses to match devices to your driver and call your callbacks:

static struct i2c_driver ep_gpio_driver = {
    .driver = {
        .name = "ep_gpio",
    },
    .id_table = ep_gpio_idtable,
    .probe    = ep_gpio_probe,
    .remove   = ep_gpio_remove,
};

module_i2c_driver(ep_gpio_driver);

Reading this top to bottom: .driver.name is a human-readable driver name used in sysfs and logs, .id_table points at the device list you just built, and .probe/.remove point at your lifecycle functions. This is a complete, working registration — nothing else is required for a basic I2C client driver.

Full Original Example: ep_gpio_demo

Below is the complete driver, combining everything from this lecture with the single-argument probe() pattern from our previous lecture. This is original example code written for this course, not copied from any book.

#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/gpio/driver.h>

struct ep_gpio_priv {
    struct i2c_client *client;
    struct gpio_chip chip;
};

static int ep_gpio_probe(struct i2c_client *client)
{
    struct ep_gpio_priv *priv;

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

    priv->client = client;
    i2c_set_clientdata(client, priv);

    dev_info(&client->dev, "ep_gpio_demo probed at address 0x%02x\n",
             client->addr);

    return 0;
}

static void ep_gpio_remove(struct i2c_client *client)
{
    struct ep_gpio_priv *priv = i2c_get_clientdata(client);

    dev_info(&client->dev, "ep_gpio_demo removed\n");
}

static const struct i2c_device_id ep_gpio_idtable[] = {
    { "ep,gpio-demo" },
    { }
};
MODULE_DEVICE_TABLE(i2c, ep_gpio_idtable);

static struct i2c_driver ep_gpio_driver = {
    .driver = {
        .name = "ep_gpio_demo",
    },
    .id_table = ep_gpio_idtable,
    .probe    = ep_gpio_probe,
    .remove   = ep_gpio_remove,
};

module_i2c_driver(ep_gpio_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("EmbeddedPathashala");
MODULE_DESCRIPTION("Original i2c registration demo driver");

Build, Load, and Test

Assuming you have a kernel build tree set up (see our earlier lectures if not), build and test the module like this:

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

Expected output (assuming a device tree node with compatible = "ep,gpio-demo" is present, or you register a test device manually):

[  102.341122] ep_gpio_demo 1-0050: ep_gpio_demo probed at address 0x50

To unload and confirm the remove() path runs:

sudo rmmod ep_gpio_demo
dmesg | tail -n 2
[  118.220043] ep_gpio_demo 1-0050: ep_gpio_demo removed

If you don’t have a physical I2C device with this compatible string, you can still confirm registration succeeded by checking:

cat /sys/bus/i2c/drivers/ep_gpio_demo/module/*

Common Mistakes and Troubleshooting

Mistake Symptom Fix
Declaring remove() as returning int on kernel 6.1+ Compiler warning: incompatible pointer type for .remove Change signature to void, drop return 0;
Forgetting MODULE_DEVICE_TABLE Module loads fine with insmod, but never auto-loads via modprobe/udev Add MODULE_DEVICE_TABLE(i2c, ...) right after the id table
Missing sentinel entry in id table Kernel oops or garbage matches at driver load Always end the array with an empty { } entry
Manually freeing devm_-allocated memory in remove() Double-free warning or crash on unload Let devm_ handle it; only free non-devm resources manually

Best Practices

  • Always mirror probe() and remove() — for every resource acquired, plan its release.
  • Prefer devm_ managed allocations wherever the kernel provides them; it shrinks your remove() function and removes a whole class of leak bugs.
  • Use module_i2c_driver() unless you genuinely need custom module init/exit behavior.
  • Keep your id_table names consistent with the compatible strings used in your device tree nodes.

Summary and Key Takeaways

  • The I2C remove() callback is void-returning starting from kernel 6.1 — no more return 0;.
  • remove() should undo exactly what probe() set up, in reverse order where it matters.
  • module_i2c_driver() eliminates manual module_init()/module_exit() boilerplate for standard drivers.
  • struct i2c_device_id plus MODULE_DEVICE_TABLE() is what allows the kernel to match and auto-load your driver for specific devices.

Conclusion

With probe(), remove(), and registration in place, you now have every piece needed for a complete, loadable I2C client driver on a modern kernel. This is the same registration pattern used by real production drivers in the mainline kernel tree, just simplified for learning. In the next lecture in this free linux device drivers course, we move from driver lifecycle to actual bus communication — sending and receiving bytes over I2C using i2c_master_send(), i2c_master_recv(), and i2c_transfer().

Frequently Asked Questions

Why did the I2C remove() function change from int to void?

Because the returned error code was effectively ignored by the I2C core — removal always proceeded regardless of the value. Making it void prevents driver authors from mistakenly assuming they can block device removal by returning an error.

Do I need module_init() and module_exit() for a basic I2C driver?

No. For drivers with no special load/unload behavior beyond I2C registration, module_i2c_driver() generates the equivalent init/exit functions automatically.

What happens if I forget the sentinel entry in i2c_device_id?

The kernel has no way to know where your ID array ends, which can lead to reading past the array during matching — always terminate it with an empty {} entry.

Is MODULE_DEVICE_TABLE mandatory?

It’s not mandatory to compile, but without it your module’s supported device IDs are invisible to modprobe/udev, so automatic module loading on device detection won’t work.

Can devm_kzalloc’d memory be freed manually in remove()?

You normally shouldn’t — the device-managed core frees it automatically after remove() returns. Manually freeing it can cause a double-free.

Where is this covered in the kernel documentation?

See Documentation/i2c/writing-clients.rst in the mainline kernel source tree for the canonical, up-to-date reference.

Continue Your Free Linux Kernel Development Course

Next: sending and receiving real data over the I2C bus.

PREV_LEC | NEXT_LEC

Leave a Reply

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