Writing a Misc Character Device Driver in Linux Kernel 6.12+-Linux Device Driver Training

Writing a Misc Character Device Driver in Linux Kernel 6.12+
Free Linux Device Drivers Course — Step-by-Step Tutorial
🧩 Hands-On Code
🐧 Kernel 6.12+ Ready
🆓 100% Free

This lecture is the hands-on core of our free Linux device drivers course. Building on the Linux Device Model concepts from the previous lecture, we now write a complete, working misc character device driver — the simplest, most beginner-friendly entry point into real kernel driver development, and a staple topic in any serious free Linux kernel development course.

All code shown here is original and written for current kernels. We will build it, load it, talk to it from user space, and unload it cleanly — the full lifecycle every embedded Linux engineer needs to master.

Topics covered in this free embedded systems course lecture:
misc_register() struct miscdevice file_operations /dev nodes Character Drivers

What You Will Learn

  • What the misc kernel framework is and why it’s the easiest way to start writing drivers
  • How struct miscdevice and misc_register() work together
  • How to implement open, read, write, and release file operations
  • How the kernel auto-creates your /dev node through devtmpfs
  • How to build, load, test, and unload the driver on a real machine or VM
  • Common mistakes, security pitfalls, and best practices for misc drivers

Prerequisites

  • Completion of the previous lecture on the Linux Device Model
  • A Linux machine or VM with kernel headers installed for your running kernel
  • Basic familiarity with make, insmod, rmmod, and dmesg

What Is the Misc Framework and Why Use It First?

The kernel’s misc (miscellaneous) framework exists for drivers that don’t cleanly belong to any specialized subsystem, and — importantly for beginners — for drivers that don’t need to register with any physical bus at all. This makes it the lowest-friction way to get a character driver running end-to-end without worrying about device tree bindings, bus matching, or probe/remove callbacks.

A misc driver is a great fit whenever the “hardware” you’re driving is actually logical — for example, a debugging interface, a shared-memory region, or a simple communication channel between user space and a kernel subsystem.

struct miscdevice Explained

Every misc driver revolves around one core data structure that describes the device to the kernel:

Field Purpose
minor Minor number; use MISC_DYNAMIC_MINOR to let the kernel assign one automatically
name Becomes the device node name under /dev/
fops Pointer to your file_operations table
mode Default permission bits for the auto-created device node
Misc Driver Registration Flow
module_init()
→
misc_register()
→
devtmpfs creates /dev node
→
user space opens it
→
misc_deregister()

Step 1: Define the file_operations Table

The file_operations structure connects standard system calls made from user space (open(), read(), write(), close()) to functions inside your driver.

static struct file_operations ep_misc_fops = {
    .owner   = THIS_MODULE,
    .open    = ep_misc_open,
    .read    = ep_misc_read,
    .write   = ep_misc_write,
    .release = ep_misc_release,
};

Step 2: Describe and Register the Device

Next, describe the device using struct miscdevice, then hand it to misc_register() inside your module’s init function:

static struct miscdevice ep_miscdev = {
    .minor = MISC_DYNAMIC_MINOR,
    .name  = "ep_miscdrv",
    .mode  = 0666,
    .fops  = &ep_misc_fops,
};

static int __init ep_miscdrv_init(void)
{
    int ret;

    ret = misc_register(&ep_miscdev);
    if (ret) {
        pr_err("ep_miscdrv: registration failed (%d)\n", ret);
        return ret;
    }

    pr_info("ep_miscdrv: loaded, minor number assigned dynamically\n");
    return 0;
}
module_init(ep_miscdrv_init);

Because name is set to "ep_miscdrv", the kernel’s devtmpfs layer automatically creates /dev/ep_miscdrv as soon as registration succeeds — no manual mknod required.

Step 3: Implement open, read, write, and release

These callbacks define what happens when user space interacts with /dev/ep_miscdrv. Below is a minimal but complete in-memory buffer implementation:

#define EP_BUF_SIZE 128
static char ep_buffer[EP_BUF_SIZE];
static size_t ep_data_len;

static int ep_misc_open(struct inode *inode, struct file *filp)
{
    pr_info("ep_miscdrv: device opened\n");
    return 0;
}

static ssize_t ep_misc_read(struct file *filp, char __user *ubuf,
                             size_t count, loff_t *offp)
{
    if (*offp >= ep_data_len)
        return 0;

    if (count > ep_data_len - *offp)
        count = ep_data_len - *offp;

    if (copy_to_user(ubuf, ep_buffer + *offp, count))
        return -EFAULT;

    *offp += count;
    return count;
}

static ssize_t ep_misc_write(struct file *filp, const char __user *ubuf,
                              size_t count, loff_t *offp)
{
    if (count > EP_BUF_SIZE)
        count = EP_BUF_SIZE;

    if (copy_from_user(ep_buffer, ubuf, count))
        return -EFAULT;

    ep_data_len = count;
    return count;
}

static int ep_misc_release(struct inode *inode, struct file *filp)
{
    pr_info("ep_miscdrv: device closed\n");
    return 0;
}

Note: Always move data across the user/kernel boundary using copy_to_user() and copy_from_user(). Never dereference a user-space pointer directly — it can fault, or worse, be attacker-controlled.

Step 4: Clean Up with misc_deregister()

static void __exit ep_miscdrv_exit(void)
{
    misc_deregister(&ep_miscdev);
    pr_info("ep_miscdrv: unloaded\n");
}
module_exit(ep_miscdrv_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("EmbeddedPathashala");
MODULE_DESCRIPTION("Simple misc character device driver example");

Every resource claimed in init must be released in exit. For this simple driver, that means a single misc_deregister() call — but in more complex drivers this is where you would free memory, release IRQs, and tear down workqueues.

Building and Testing the Driver

Build using a minimal out-of-tree Kbuild Makefile:

obj-m += ep_miscdrv.o

all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

Then load, test, and unload it:

$ make
$ sudo insmod ep_miscdrv.ko
$ dmesg | tail
$ ls -l /dev/ep_miscdrv
$ echo "hello kernel" > /dev/ep_miscdrv
$ cat /dev/ep_miscdrv
$ sudo rmmod ep_miscdrv
$ dmesg | tail

How the /dev Node Gets Created Automatically

You may have noticed we never called mknod. When misc_register() succeeds, the kernel also creates the corresponding entry under /sys/class/misc/. The devtmpfs filesystem, mounted early in boot, watches for these device registrations and automatically creates the matching /dev node with the permissions you specified in mode. This is why modern drivers rarely need udev rules just to get a basic node working.

Real-World Use Cases for Misc Drivers

  • Debug/diagnostic interfaces exposed by vendor BSPs
  • Simple IPC channels between a kernel module and a user-space daemon
  • Watchdog-style control interfaces
  • Prototyping new hardware interfaces before writing a full bus-based driver

Common Mistakes and Troubleshooting

Mistake Fix
Dereferencing user pointers directly Always use copy_to_user() / copy_from_user()
Forgetting bounds checks on count Clamp count to your buffer size before copying
No matching misc_deregister() on exit Leaves a dangling /dev node and can crash later loads

Best Practices

  • Use MISC_DYNAMIC_MINOR unless you have a specific reason to hardcode a minor number
  • Keep your buffer sizes bounded and validated on every read/write
  • Use pr_fmt() so your dmesg logs are easy to trace back to this driver
  • Guard concurrent access to shared buffers with a mutex once more than one process may open the device

Performance and Security Considerations

Performance: For small, infrequent transfers like this example, a simple static buffer is fine. For higher-throughput use cases, consider read_iter/write_iter with scatter-gather buffers instead of single fixed arrays.

Security: A misc device with mode = 0666 is world-readable and world-writable. In production, restrict permissions with udev rules or set a tighter mode, and always validate the length and contents of anything copied in from user space.

Summary / Key Takeaways

  • The misc framework is the fastest path to a working character driver
  • struct miscdevice plus misc_register() is all you need to register it
  • file_operations connects user-space syscalls to your driver code
  • devtmpfs auto-creates your /dev node, no mknod needed
  • Always pair misc_register() with misc_deregister() on unload

Conclusion

You’ve now written, built, loaded, and tested a complete misc character device driver from scratch. This pattern — register a miscdevice, implement file_operations, clean up on exit — is the foundation you’ll reuse for far more advanced drivers later in this free Linux device drivers course, including ones that add IOCTL support, poll/select, and proper concurrency handling.

Frequently Asked Questions

Q1. What is a misc character device driver?
A simple Linux driver registered through the kernel’s miscellaneous framework, ideal for logical devices that don’t need bus registration.

Q2. Do I need to manually create the /dev node?
No. devtmpfs automatically creates it once misc_register() succeeds.

Q3. What does MISC_DYNAMIC_MINOR do?
It tells the kernel to automatically assign a free, unused minor number instead of hardcoding one.

Q4. Why use copy_to_user() instead of a direct pointer assignment?
User-space pointers can’t be trusted or dereferenced directly from kernel context; these helper functions safely and correctly cross that boundary.

Q5. Can a misc driver handle ioctl calls?
Yes, by adding an .unlocked_ioctl entry to the file_operations table, covered in a later lecture.

Q6. Is the misc framework suitable for production drivers?
Yes, many production debug and utility drivers use it; just add proper locking and permission handling.

Q7. What happens if I forget misc_deregister()?
The device node and kernel structures remain registered, which can cause errors or crashes on the next module load.

Continue This Free Linux Device Drivers Course

Next up: adding IOCTL support and proper concurrency handling to your driver.

Next Lecture » Back to Course Index

2 Comments

Leave a Reply

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