Miscellaneous Device Drivers
Miscellaneous device drivers provide a simplified way to register character devices in the Linux kernel. The process involves two main steps:
Step 1: Populate the miscdevice Structure
The miscdevice structure requires only three essential members:
static struct miscdevice misc_d =
{
.minor = MISC_DYNAMIC_MINOR,
.name = "my_device",
.fops = &my_fops
};
.minor: Set toMISC_DYNAMIC_MINORto let the kernel assign an available minor number automatically.name: The device name that will appear in/dev.fops: Pointer to the file operations structure
Step 2: Register and Unregister the Device
Use misc_register() to register the device and misc_deregister() to remove it:
int ret = misc_register(&misc_d);
if (ret < 0) {
/* handle error */
}
/* later, during cleanup */
misc_deregister(&misc_d);
After loading the compiled kernel module, the device node is automaticallly created under /dev.
Automatic Device Node Creation
For traditional character device drivers, device nodes are not automatical created. The kernel provides two key functions to handle this automatically:
class_create()- Creates a device class in sysfsdevice_create()- Creates a device node under/dev
Complete Implementation Example
temperature.c
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/interrupt.h>
#include <linux/irqreturn.h>
#include <mach/irqs.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/device.h>
#define MAJOR_NUM 247
#define MINOR_NUM 0
#define DEV_NAME "temp_sensor"
static wait_queue_head_t data_ready_q;
static int data_available = 0;
#define SENSOR_CTRL 0x58000000
#define SENSOR_DATA 0x5800000C
#define CLK_CTRL 0x4C00000C
static volatile unsigned long *sensor_ctrl;
static volatile unsigned long *sensor_data;
static volatile unsigned long *clk_ctrl;
static void sensor_init(void)
{
*sensor_ctrl = (1 << 14) | (49 << 6);
}
static void sensor_trigger(void)
{
*sensor_ctrl |= (1 << 0);
}
static unsigned short read_sensor_value(void)
{
unsigned short value = *sensor_data & 0x3ff;
return value;
}
static irqreturn_t sensor_isr(int irq, void *arg)
{
data_available = 1;
wake_up_interruptible(&data_ready_q);
printk("IRQ handled: %d\n", irq);
return IRQ_HANDLED;
}
static int open_dev(struct inode *inode, struct file *filp)
{
sensor_init();
printk("Device opened\n");
return 0;
}
static ssize_t read_dev(struct file *filp, char __user *buf,
size_t len, loff_t *offset)
{
unsigned short sample = 0;
sensor_trigger();
data_available = 0;
wait_event_interruptible(data_ready_q, data_available);
sample = read_sensor_value();
copy_to_user(buf, &sample, sizeof(sample));
printk("Data read completed\n");
return sizeof(sample);
}
static ssize_t write_dev(struct file *filp, const char __user *buf,
size_t len, loff_t *offset)
{
printk("Write operation\n");
return 0;
}
static int release_dev(struct inode *inode, struct file *filp)
{
printk("Device closed\n");
return 0;
}
static dev_t device_number;
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = open_dev,
.read = read_dev,
.write = write_dev,
.release = release_dev
};
static struct cdev char_dev;
static struct class *device_class;
struct device *kernel_device;
static int __init sensor_init(void)
{
int result = 0;
device_number = MKDEV(MAJOR_NUM, MINOR_NUM);
cdev_init(&char_dev, &fops);
result = cdev_add(&char_dev, device_number, 1);
if (result < 0)
goto fail_cdev_add;
result = register_chrdev_region(device_number, 1, DEV_NAME);
if (result < 0)
goto fail_chrdev_region;
device_class = class_create(THIS_MODULE, "temp_sensors");
if (IS_ERR(device_class)) {
result = PTR_ERR(device_class);
goto fail_class_create;
}
kernel_device = device_create(device_class, NULL,
device_number, NULL, DEV_NAME);
if (IS_ERR(kernel_device)) {
result = PTR_ERR(kernel_device);
goto fail_device_create;
}
result = request_irq(IRQ_ADC, sensor_isr,
IRQF_TRIGGER_FALLING | IRQF_DISABLED,
"sensor_irq", NULL);
if (result < 0)
goto fail_irq_request;
init_waitqueue_head(&data_ready_q);
sensor_ctrl = ioremap(SENSOR_CTRL, sizeof(sensor_ctrl));
sensor_data = ioremap(SENSOR_DATA, sizeof(sensor_data));
clk_ctrl = ioremap(CLK_CTRL, sizeof(clk_ctrl));
*clk_ctrl |= (1 << 15);
printk("Temperature sensor driver loaded\n");
return 0;
fail_irq_request:
printk("IRQ request failed\n");
disable_irq(IRQ_ADC);
device_destroy(device_class, device_number);
class_destroy(device_class);
free_irq(IRQ_ADC, NULL);
unregister_chrdev_region(device_number, 1);
cdev_del(&char_dev);
return result;
fail_device_create:
printk("Device creation failed\n");
class_destroy(device_class);
unregister_chrdev_region(device_number, 1);
cdev_del(&char_dev);
return result;
fail_class_create:
printk("Class creation failed\n");
unregister_chrdev_region(device_number, 1);
cdev_del(&char_dev);
return result;
fail_chrdev_region:
printk("chrdev region registration failed\n");
cdev_del(&char_dev);
return result;
fail_cdev_add:
printk("cdev add failed\n");
return result;
}
static void __exit sensor_exit(void)
{
iounmap(clk_ctrl);
iounmap(sensor_data);
iounmap(sensor_ctrl);
device_destroy(device_class, device_number);
class_destroy(device_class);
disable_irq(IRQ_ADC);
free_irq(IRQ_ADC, NULL);
unregister_chrdev_region(device_number, 1);
cdev_del(&char_dev);
printk("Driver unloaded\n");
}
module_init(sensor_init);
module_exit(sensor_exit);
MODULE_LICENSE("GPL");
temp_misc.c
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/interrupt.h>
#include <linux/irqreturn.h>
#include <mach/irqs.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/miscdevice.h>
#define DEV_NAME "temp_sensor"
static wait_queue_head_t wait_queue;
static int ready_flag = 0;
#define SENSOR_BASE 0x58000000
#define DATA_REG 0x5800000C
#define CLOCK_REG 0x4C00000C
static volatile unsigned long *control_reg;
static volatile unsigned long *data_reg;
static volatile unsigned long *clock_reg;
static void init_hardware(void)
{
*control_reg = (1 << 14) | (49 << 6);
}
static void start_conversion(void)
{
*control_reg |= (1 << 0);
}
static unsigned short get_raw_value(void)
{
unsigned short val = *data_reg & 0x3ff;
return val;
}
static irqreturn_t irq_handler(int irq, void *dev_id)
{
ready_flag = 1;
wake_up_interruptible(&wait_queue);
printk("Interrupt %d\n", irq);
return IRQ_HANDLED;
}
static int open_dev(struct inode *inode, struct file *filp)
{
init_hardware();
printk("Device opened\n");
return 0;
}
static ssize_t read_dev(struct file *filp, char __user *buffer,
size_t count, loff_t *pos)
{
unsigned short raw_data = 0;
start_conversion();
ready_flag = 0;
wait_event_interruptible(wait_queue, ready_flag);
raw_data = get_raw_value();
copy_to_user(buffer, &raw_data, sizeof(raw_data));
printk("Read completed\n");
return sizeof(raw_data);
}
static ssize_t write_dev(struct file *filp, const char __user *buffer,
size_t count, loff_t *pos)
{
printk("Write operation\n");
return 0;
}
static int close_dev(struct inode *inode, struct file *filp)
{
printk("Device released\n");
return 0;
}
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = open_dev,
.read = read_dev,
.write = write_dev,
.release = close_dev
};
static struct miscdevice misc_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEV_NAME,
.fops = &fops
};
static int __init sensor_init(void)
{
int result;
result = misc_register(&misc_dev);
if (result < 0) {
printk("Misc device registration failed\n");
misc_deregister(&misc_dev);
return result;
}
result = request_irq(IRQ_ADC, irq_handler,
IRQF_TRIGGER_FALLING | IRQF_DISABLED,
"sensor_irq", NULL);
if (result < 0) {
printk("IRQ allocation failed\n");
disable_irq(IRQ_ADC);
free_irq(IRQ_ADC, NULL);
misc_deregister(&misc_dev);
return result;
}
init_waitqueue_head(&wait_queue);
control_reg = ioremap(SENSOR_BASE, sizeof(control_reg));
data_reg = ioremap(DATA_REG, sizeof(data_reg));
clock_reg = ioremap(CLOCK_REG, sizeof(clock_reg));
*clock_reg |= (1 << 15);
printk("Clock register: %lx\n", *clock_reg);
printk("Driver initialization complete\n");
return 0;
}
static void __exit sensor_exit(void)
{
iounmap(clock_reg);
iounmap(data_reg);
iounmap(control_reg);
disable_irq(IRQ_ADC);
free_irq(IRQ_ADC, NULL);
misc_deregister(&misc_dev);
printk("Cleanup complete\n");
}
module_init(sensor_init);
module_exit(sensor_exit);
MODULE_LICENSE("GPL");
Key Differences
| Approach | Device Registration | Node Creation |
|---|---|---|
| Traditional | register_chrdev_region() + cdev_add() |
Manual via mknod or class_create() + device_create() |
| Miscellaneous | misc_register() |
Automatic when using miscdevice |
The miscellaneous device apporach significantly reduces code complexity by automatically handling minor number allocation and device node creation in sysfs, making it ideal for simple character devices that don't require a specific major/minor number combination.