Miscellaneous Device Drivers and Automatic Device Node Creation

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 to MISC_DYNAMIC_MINOR to 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:

  1. class_create() - Creates a device class in sysfs
  2. device_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.

Tags: Linux kernel device driver misc device Character Device device node

Posted on Mon, 12 Oct 2026 16:43:21 +0000 by niclarke