Error Handling Patterns

Proper error handling is critical in kernel code. A bug in a driver can crash the entire system.

Return Value Conventions

Function Return Values

Kernel functions return errors as negative errno values:

#include <linux/errno.h>

int my_function(void)
{
        if (bad_input)
                return -EINVAL;   /* Invalid argument */

        if (no_memory)
                return -ENOMEM;   /* Out of memory */

        if (device_busy)
                return -EBUSY;    /* Device busy */

        return 0;  /* Success */
}

Common Error Codes

Code Meaning Common Use
-EINVAL Invalid argument Bad parameter values
-ENOMEM Out of memory Allocation failed
-EBUSY Device busy Resource in use
-ENODEV No such device Device not found
-ENOENT No such entry File/entry not found
-EIO I/O error Hardware error
-EFAULT Bad address Invalid user pointer
-EACCES Permission denied Access not allowed
-EAGAIN Try again Temporary condition
-EPROBE_DEFER Probe deferred Resource not ready yet
-ETIMEDOUT Timeout Operation timed out

Checking Return Values

int ret;

ret = some_function();
if (ret) {
        pr_err("some_function failed: %d\n", ret);
        return ret;
}

/* Or for clearer negative-only errors */
if (ret < 0) {
        pr_err("failed with error %d\n", ret);
        return ret;
}

Error Pointers (ERR_PTR)

Encode error codes in pointer values:

#include <linux/err.h>

/* Return error as pointer */
void *allocate_thing(void)
{
        void *ptr;

        ptr = kmalloc(size, GFP_KERNEL);
        if (!ptr)
                return ERR_PTR(-ENOMEM);

        return ptr;
}

/* Check for error pointer */
void *ptr = allocate_thing();
if (IS_ERR(ptr)) {
        int err = PTR_ERR(ptr);
        pr_err("allocation failed: %d\n", err);
        return err;
}

/* Safe to use ptr now */

ERR_PTR Functions

/* Create error pointer */
ERR_PTR(-ENOMEM)

/* Check if pointer is error */
IS_ERR(ptr)          /* true if error */
IS_ERR_OR_NULL(ptr)  /* true if error or NULL */

/* Extract error code */
PTR_ERR(ptr)         /* Returns negative errno */

/* Return error or valid pointer */
ERR_CAST(ptr)        /* Cast to another pointer type */

Common Pattern

struct resource *get_resource(void)
{
        struct resource *res;

        res = find_resource();
        if (!res)
                return ERR_PTR(-ENOENT);

        if (resource_busy(res))
                return ERR_PTR(-EBUSY);

        return res;
}

/* Caller */
struct resource *res = get_resource();
if (IS_ERR(res))
        return PTR_ERR(res);

The Goto Cleanup Pattern

Standard pattern for handling multiple allocations/registrations:

Basic Pattern

static int __init my_init(void)
{
        int ret;

        /* Step 1 */
        ret = alloc_resource_a();
        if (ret)
                return ret;

        /* Step 2 */
        ret = alloc_resource_b();
        if (ret)
                goto err_b;

        /* Step 3 */
        ret = register_device();
        if (ret)
                goto err_register;

        return 0;

err_register:
        free_resource_b();
err_b:
        free_resource_a();
        return ret;
}

static void __exit my_exit(void)
{
        /* Cleanup in reverse order */
        unregister_device();
        free_resource_b();
        free_resource_a();
}

Real-World Example

static int my_probe(struct platform_device *pdev)
{
        struct my_device *dev;
        int ret;

        /* Allocate device structure */
        dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
        if (!dev)
                return -ENOMEM;

        /* Get resources */
        dev->base = devm_platform_ioremap_resource(pdev, 0);
        if (IS_ERR(dev->base))
                return PTR_ERR(dev->base);

        dev->irq = platform_get_irq(pdev, 0);
        if (dev->irq < 0)
                return dev->irq;

        /* Request IRQ (not devm, needs manual cleanup) */
        ret = request_irq(dev->irq, my_irq_handler, 0, "my_device", dev);
        if (ret)
                return ret;

        /* Register with subsystem */
        ret = register_my_device(dev);
        if (ret)
                goto err_irq;

        platform_set_drvdata(pdev, dev);
        return 0;

err_irq:
        free_irq(dev->irq, dev);
        return ret;
}

static int my_remove(struct platform_device *pdev)
{
        struct my_device *dev = platform_get_drvdata(pdev);

        unregister_my_device(dev);
        free_irq(dev->irq, dev);
        /* devm resources freed automatically */

        return 0;
}

Managed Resources (devm_*)

The devm_* functions automatically free resources when the device is removed:

/* Automatically freed on device removal */
ptr = devm_kzalloc(dev, size, GFP_KERNEL);
base = devm_ioremap_resource(dev, res);
ret = devm_request_irq(dev, irq, handler, flags, name, data);
clk = devm_clk_get(dev, "my_clock");

Benefits

  • No need for explicit cleanup code
  • Can’t forget to free resources
  • Cleaner probe/remove functions

Example: Before and After devm

/* Without devm - manual cleanup */
static int probe(struct device *dev)
{
        void *buf = kmalloc(1024, GFP_KERNEL);
        if (!buf)
                return -ENOMEM;

        void __iomem *base = ioremap(addr, size);
        if (!base) {
                kfree(buf);
                return -ENOMEM;
        }

        int ret = request_irq(irq, handler, 0, "name", dev);
        if (ret) {
                iounmap(base);
                kfree(buf);
                return ret;
        }

        return 0;
}

static void remove(struct device *dev)
{
        free_irq(irq, dev);
        iounmap(base);
        kfree(buf);
}

/* With devm - automatic cleanup */
static int probe(struct device *dev)
{
        void *buf = devm_kmalloc(dev, 1024, GFP_KERNEL);
        if (!buf)
                return -ENOMEM;

        void __iomem *base = devm_ioremap(dev, addr, size);
        if (!base)
                return -ENOMEM;

        int ret = devm_request_irq(dev, irq, handler, 0, "name", dev);
        if (ret)
                return ret;

        return 0;
}

static void remove(struct device *dev)
{
        /* Nothing to do! devm handles it */
}

Logging Errors

Use appropriate log levels:

/* For errors */
pr_err("Failed to allocate memory\n");
dev_err(dev, "Hardware error: %d\n", status);

/* For warnings */
pr_warn("Unexpected condition\n");
dev_warn(dev, "Timeout, retrying\n");

/* Include error code in message */
dev_err(dev, "probe failed: %d\n", ret);

Device-Aware Logging

Prefer dev_* over pr_* in drivers:

/* Includes device name in output */
dev_info(dev, "Device initialized\n");
/* [    1.234567] my_device 0000:00:01.0: Device initialized */

dev_err(dev, "I/O error on read\n");
/* [    1.234567] my_device 0000:00:01.0: I/O error on read */

Assertions and BUG()

For conditions that should never happen:

/* Print warning but continue */
WARN_ON(condition);
WARN_ON_ONCE(condition);  /* Only warn once */

/* Warning with message */
WARN(condition, "message: %d\n", value);

/* Crash the kernel (use sparingly!) */
BUG_ON(condition);
BUG();

BUG() kills the system. Only use for unrecoverable corruption. Prefer WARN_ON() and return an error.

Summary

  • Return negative errno values for errors
  • Use ERR_PTR/IS_ERR/PTR_ERR for pointer-returning functions
  • Use goto cleanup pattern for multiple resources
  • Use devm_* functions for automatic cleanup
  • Log errors with appropriate levels
  • Include error codes in log messages

Next

Learn about common kernel API macros and helper functions.


Back to top

Linux Driver Development Guide is a community resource for learning kernel driver development. Not affiliated with the Linux Foundation. Content provided for educational purposes.

This site uses Just the Docs, a documentation theme for Jekyll.