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.
What You Will Learn
- What the misc kernel framework is and why it’s the easiest way to start writing drivers
- How
struct miscdeviceandmisc_register()work together - How to implement
open,read,write, andreleasefile operations - How the kernel auto-creates your
/devnode 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, anddmesg
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 |
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_MINORunless 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 yourdmesglogs 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 miscdeviceplusmisc_register()is all you need to register itfile_operationsconnects user-space syscalls to your driver code- devtmpfs auto-creates your
/devnode, nomknodneeded - Always pair
misc_register()withmisc_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.
Next up: adding IOCTL support and proper concurrency handling to your driver.
Next Lecture » Back to Course Index
2 Comments