Device drivers are the critical interface between hardware and the operating system. They translate generic OS requests into hardware-specific operations, enabling applications to interact with devices without needing to understand their low-level details. This comprehensive guide covers everything from basic driver concepts to advanced techniques for writing Linux device drivers in C.
Understanding Device Drivers
A device driver is a software component that manages a specific hardware device. It provides a uniform interface to the kernel while handling device-specific operations.
┌─────────────────────────────────────────────────────────────┐ │ User Space Applications │ ├─────────────────────────────────────────────────────────────┤ │ System Call Interface │ ├─────────────────────────────────────────────────────────────┤ │ Virtual File System (VFS) │ ├─────────────────────────────────────────────────────────────┤ │ Device Driver Framework │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │ │ │ Character │ │ Block │ │ Network │ │ │ │ Drivers │ │ Drivers │ │ Drivers │ │ │ └─────────────┘ └─────────────┘ └─────────────────────┘ │ ├─────────────────────────────────────────────────────────────┤ │ Hardware Layer │ └─────────────────────────────────────────────────────────────┘
Types of Device Drivers
| Type | Description | Examples |
|---|---|---|
| Character | Stream of bytes, no buffering | Serial ports, keyboards |
| Block | Fixed-size blocks, buffered | Hard drives, SSDs |
| Network | Network interfaces | Ethernet, Wi-Fi |
| Misc | Simple character devices | LEDs, GPIOs |
Driver Development Environment Setup
# Install kernel headers and build tools sudo apt-get install linux-headers-$(uname -r) sudo apt-get install build-essential # Verify kernel version uname -r # Set up kernel build directory cd /lib/modules/$(uname -r)/build
Basic Driver Structure
// simplest_driver.c - Minimal character driver
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple character driver");
MODULE_VERSION("1.0");
// Driver information
static int major_number;
static struct class *driver_class;
static struct device *driver_device;
// Device data
static char device_buffer[256];
static int device_open_count = 0;
// Called when device is opened
static int driver_open(struct inode *inode, struct file *file) {
device_open_count++;
pr_info("Driver: Device opened %d times\n", device_open_count);
return 0;
}
// Called when device is closed
static int driver_release(struct inode *inode, struct file *file) {
device_open_count--;
pr_info("Driver: Device closed\n");
return 0;
}
// Called when reading from device
static ssize_t driver_read(struct file *file, char __user *user_buffer,
size_t len, loff_t *offset) {
size_t bytes_to_read;
pr_info("Driver: Read request for %zu bytes\n", len);
// Check if we've reached the end
if (*offset >= sizeof(device_buffer)) {
return 0;
}
// Calculate bytes to read
bytes_to_read = min(len, sizeof(device_buffer) - *offset);
// Copy data from kernel space to user space
if (copy_to_user(user_buffer, device_buffer + *offset, bytes_to_read)) {
return -EFAULT;
}
// Update offset
*offset += bytes_to_read;
return bytes_to_read;
}
// Called when writing to device
static ssize_t driver_write(struct file *file, const char __user *user_buffer,
size_t len, loff_t *offset) {
size_t bytes_to_write;
pr_info("Driver: Write request for %zu bytes\n", len);
// Check if we've reached the end
if (*offset >= sizeof(device_buffer)) {
return -ENOSPC;
}
// Calculate bytes to write
bytes_to_write = min(len, sizeof(device_buffer) - *offset);
// Copy data from user space to kernel space
if (copy_from_user(device_buffer + *offset, user_buffer, bytes_to_write)) {
return -EFAULT;
}
// Update offset
*offset += bytes_to_write;
return bytes_to_write;
}
// File operations structure
static struct file_operations driver_fops = {
.owner = THIS_MODULE,
.open = driver_open,
.release = driver_release,
.read = driver_read,
.write = driver_write,
};
// Module initialization
static int __init driver_init(void) {
pr_info("Driver: Initializing\n");
// Allocate major number dynamically
major_number = register_chrdev(0, "simpledriver", &driver_fops);
if (major_number < 0) {
pr_err("Driver: Failed to register device\n");
return major_number;
}
// Create device class
driver_class = class_create(THIS_MODULE, "simpledriver_class");
if (IS_ERR(driver_class)) {
unregister_chrdev(major_number, "simpledriver");
pr_err("Driver: Failed to create class\n");
return PTR_ERR(driver_class);
}
// Create device
driver_device = device_create(driver_class, NULL,
MKDEV(major_number, 0),
NULL, "simpledriver");
if (IS_ERR(driver_device)) {
class_destroy(driver_class);
unregister_chrdev(major_number, "simpledriver");
pr_err("Driver: Failed to create device\n");
return PTR_ERR(driver_device);
}
// Initialize device buffer
memset(device_buffer, 0, sizeof(device_buffer));
snprintf(device_buffer, sizeof(device_buffer), "Hello from driver!\n");
pr_info("Driver: Initialized successfully (major=%d)\n", major_number);
return 0;
}
// Module cleanup
static void __exit driver_exit(void) {
pr_info("Driver: Exiting\n");
device_destroy(driver_class, MKDEV(major_number, 0));
class_destroy(driver_class);
unregister_chrdev(major_number, "simpledriver");
pr_info("Driver: Cleaned up\n");
}
module_init(driver_init);
module_exit(driver_exit);
Makefile for Kernel Module
# Makefile obj-m += simpledriver.o KDIR := /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) all: $(MAKE) -C $(KDIR) M=$(PWD) modules clean: $(MAKE) -C $(KDIR) M=$(PWD) clean install: sudo insmod simpledriver.ko remove: sudo rmmod simpledriver info: modinfo simpledriver.ko test: @echo "Testing driver..." sudo dmesg | tail -20 .PHONY: all clean install remove info test
Build and test:
# Build the driver
make
# Install the driver
sudo insmod simpledriver.ko
# Check if loaded
lsmod | grep simpledriver
# Check kernel messages
dmesg | tail
# Create device node (if not created automatically)
sudo mknod /dev/simpledriver c $(cat /proc/devices | grep simpledriver | awk '{print $1}') 0
# Test with cat and echo
echo "Hello Driver" > /dev/simpledriver
cat /dev/simpledriver
# Remove driver
sudo rmmod simpledriver
Advanced Character Driver
// advanced_char_driver.c - With ioctl and mmap support
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/mm.h>
#include <linux/ioctl.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Advanced character driver");
#define DEVICE_NAME "advdriver"
#define BUFFER_SIZE 4096
// IOCTL commands
#define IOCTL_MAGIC 'a'
#define IOCTL_GET_SIZE _IOR(IOCTL_MAGIC, 1, int)
#define IOCTL_SET_SIZE _IOW(IOCTL_MAGIC, 2, int)
#define IOCTL_CLEAR _IO(IOCTL_MAGIC, 3)
#define IOCTL_GET_FLAGS _IOR(IOCTL_MAGIC, 4, int)
#define IOCTL_SET_FLAGS _IOW(IOCTL_MAGIC, 5, int)
// Flags
#define FLAG_READ_ONLY 0x01
#define FLAG_WRITE_ONLY 0x02
#define FLAG_APPEND 0x04
// Device structure
struct adv_device {
struct cdev cdev;
char *buffer;
size_t buffer_size;
size_t data_size;
int flags;
struct mutex lock;
wait_queue_head_t read_queue;
wait_queue_head_t write_queue;
};
static struct adv_device *device;
static dev_t dev_num;
static struct class *device_class;
// Called when device is opened
static int adv_open(struct inode *inode, struct file *file) {
struct adv_device *dev = container_of(inode->i_cdev, struct adv_device, cdev);
file->private_data = dev;
pr_info("Advanced driver: Device opened\n");
return 0;
}
// Called when device is closed
static int adv_release(struct inode *inode, struct file *file) {
pr_info("Advanced driver: Device closed\n");
return 0;
}
// Read from device
static ssize_t adv_read(struct file *file, char __user *user_buffer,
size_t len, loff_t *offset) {
struct adv_device *dev = file->private_data;
size_t bytes_to_read;
ssize_t ret;
mutex_lock(&dev->lock);
// Wait for data if none available (non-blocking check)
if (dev->data_size == 0) {
if (file->f_flags & O_NONBLOCK) {
ret = -EAGAIN;
goto out_unlock;
}
mutex_unlock(&dev->lock);
if (wait_event_interruptible(dev->read_queue, dev->data_size > 0)) {
return -ERESTARTSYS;
}
mutex_lock(&dev->lock);
}
// Calculate bytes to read
if (*offset >= dev->data_size) {
ret = 0;
goto out_unlock;
}
bytes_to_read = min(len, dev->data_size - *offset);
// Copy to user space
if (copy_to_user(user_buffer, dev->buffer + *offset, bytes_to_read)) {
ret = -EFAULT;
goto out_unlock;
}
*offset += bytes_to_read;
ret = bytes_to_read;
// Wake up writers if space available
if (dev->data_size < dev->buffer_size) {
wake_up_interruptible(&dev->write_queue);
}
out_unlock:
mutex_unlock(&dev->lock);
return ret;
}
// Write to device
static ssize_t adv_write(struct file *file, const char __user *user_buffer,
size_t len, loff_t *offset) {
struct adv_device *dev = file->private_data;
size_t bytes_to_write;
size_t write_pos;
ssize_t ret;
// Check if device is read-only
if (dev->flags & FLAG_READ_ONLY) {
return -EPERM;
}
mutex_lock(&dev->lock);
// Determine write position
if (dev->flags & FLAG_APPEND) {
write_pos = dev->data_size;
} else {
write_pos = *offset;
}
// Check if we need to expand buffer
if (write_pos + len > dev->buffer_size) {
char *new_buffer;
size_t new_size = max(dev->buffer_size * 2, write_pos + len);
new_buffer = kmalloc(new_size, GFP_KERNEL);
if (!new_buffer) {
ret = -ENOMEM;
goto out_unlock;
}
memcpy(new_buffer, dev->buffer, dev->buffer_size);
kfree(dev->buffer);
dev->buffer = new_buffer;
dev->buffer_size = new_size;
}
// Calculate bytes to write
bytes_to_write = min(len, dev->buffer_size - write_pos);
// Copy from user space
if (copy_from_user(dev->buffer + write_pos, user_buffer, bytes_to_write)) {
ret = -EFAULT;
goto out_unlock;
}
// Update data size if writing beyond current end
if (write_pos + bytes_to_write > dev->data_size) {
dev->data_size = write_pos + bytes_to_write;
}
// Update offset
if (!(dev->flags & FLAG_APPEND)) {
*offset = write_pos + bytes_to_write;
}
ret = bytes_to_write;
// Wake up readers
wake_up_interruptible(&dev->read_queue);
out_unlock:
mutex_unlock(&dev->lock);
return ret;
}
// mmap support
static int adv_mmap(struct file *file, struct vm_area_struct *vma) {
struct adv_device *dev = file->private_data;
unsigned long size = vma->vm_end - vma->vm_start;
int ret;
if (size > dev->buffer_size) {
return -EINVAL;
}
// Set permissions
vma->vm_flags |= VM_DONTCOPY;
// Map kernel buffer to user space
ret = remap_pfn_range(vma, vma->vm_start,
virt_to_phys(dev->buffer) >> PAGE_SHIFT,
size, vma->vm_page_prot);
if (ret) {
pr_err("Advanced driver: mmap failed\n");
return ret;
}
pr_info("Advanced driver: mmap successful\n");
return 0;
}
// IOCTL handling
static long adv_ioctl(struct file *file, unsigned int cmd, unsigned long arg) {
struct adv_device *dev = file->private_data;
int ret = 0;
int value;
// Verify command
if (_IOC_TYPE(cmd) != IOCTL_MAGIC) {
return -ENOTTY;
}
switch (cmd) {
case IOCTL_GET_SIZE:
ret = put_user(dev->data_size, (int __user *)arg);
break;
case IOCTL_SET_SIZE:
ret = get_user(value, (int __user *)arg);
if (ret) break;
mutex_lock(&dev->lock);
if (value > dev->buffer_size) {
ret = -ENOSPC;
} else {
dev->data_size = value;
}
mutex_unlock(&dev->lock);
break;
case IOCTL_CLEAR:
mutex_lock(&dev->lock);
memset(dev->buffer, 0, dev->buffer_size);
dev->data_size = 0;
mutex_unlock(&dev->lock);
break;
case IOCTL_GET_FLAGS:
ret = put_user(dev->flags, (int __user *)arg);
break;
case IOCTL_SET_FLAGS:
ret = get_user(dev->flags, (int __user *)arg);
break;
default:
return -ENOTTY;
}
return ret;
}
// File operations structure
static struct file_operations adv_fops = {
.owner = THIS_MODULE,
.open = adv_open,
.release = adv_release,
.read = adv_read,
.write = adv_write,
.unlocked_ioctl = adv_ioctl,
.mmap = adv_mmap,
};
// Module initialization
static int __init adv_init(void) {
int ret;
pr_info("Advanced driver: Initializing\n");
// Allocate device structure
device = kmalloc(sizeof(struct adv_device), GFP_KERNEL);
if (!device) {
pr_err("Advanced driver: Failed to allocate device structure\n");
return -ENOMEM;
}
// Allocate device buffer
device->buffer = kmalloc(BUFFER_SIZE, GFP_KERNEL);
if (!device->buffer) {
kfree(device);
pr_err("Advanced driver: Failed to allocate buffer\n");
return -ENOMEM;
}
// Initialize device
device->buffer_size = BUFFER_SIZE;
device->data_size = 0;
device->flags = 0;
mutex_init(&device->lock);
init_waitqueue_head(&device->read_queue);
init_waitqueue_head(&device->write_queue);
// Allocate device numbers
ret = alloc_chrdev_region(&dev_num, 0, 1, DEVICE_NAME);
if (ret < 0) {
pr_err("Advanced driver: Failed to allocate device numbers\n");
goto err_free_buffer;
}
// Initialize cdev
cdev_init(&device->cdev, &adv_fops);
device->cdev.owner = THIS_MODULE;
// Add cdev to kernel
ret = cdev_add(&device->cdev, dev_num, 1);
if (ret < 0) {
pr_err("Advanced driver: Failed to add cdev\n");
goto err_unregister_region;
}
// Create device class
device_class = class_create(THIS_MODULE, "advdriver_class");
if (IS_ERR(device_class)) {
pr_err("Advanced driver: Failed to create class\n");
ret = PTR_ERR(device_class);
goto err_cdev_del;
}
// Create device
device_create(device_class, NULL, dev_num, NULL, DEVICE_NAME);
pr_info("Advanced driver: Initialized (major=%d, minor=%d)\n",
MAJOR(dev_num), MINOR(dev_num));
return 0;
err_cdev_del:
cdev_del(&device->cdev);
err_unregister_region:
unregister_chrdev_region(dev_num, 1);
err_free_buffer:
kfree(device->buffer);
kfree(device);
return ret;
}
// Module cleanup
static void __exit adv_exit(void) {
pr_info("Advanced driver: Exiting\n");
device_destroy(device_class, dev_num);
class_destroy(device_class);
cdev_del(&device->cdev);
unregister_chrdev_region(dev_num, 1);
kfree(device->buffer);
kfree(device);
pr_info("Advanced driver: Cleaned up\n");
}
module_init(adv_init);
module_exit(adv_exit);
User-Space Test Program
// test_adv_driver.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#define IOCTL_MAGIC 'a'
#define IOCTL_GET_SIZE _IOR(IOCTL_MAGIC, 1, int)
#define IOCTL_SET_SIZE _IOW(IOCTL_MAGIC, 2, int)
#define IOCTL_CLEAR _IO(IOCTL_MAGIC, 3)
#define IOCTL_GET_FLAGS _IOR(IOCTL_MAGIC, 4, int)
#define IOCTL_SET_FLAGS _IOW(IOCTL_MAGIC, 5, int)
int main() {
int fd;
char buffer[256];
int size, flags;
// Open device
fd = open("/dev/advdriver", O_RDWR);
if (fd < 0) {
perror("Failed to open device");
return 1;
}
printf("Device opened successfully\n");
// Test write
strcpy(buffer, "Hello from user space!");
write(fd, buffer, strlen(buffer));
printf("Wrote: %s\n", buffer);
// Test read
memset(buffer, 0, sizeof(buffer));
lseek(fd, 0, SEEK_SET);
read(fd, buffer, sizeof(buffer));
printf("Read: %s\n", buffer);
// Test append mode
flags = 0x04; // FLAG_APPEND
ioctl(fd, IOCTL_SET_FLAGS, &flags);
write(fd, "Appended text", 13);
lseek(fd, 0, SEEK_SET);
read(fd, buffer, sizeof(buffer));
printf("After append: %s\n", buffer);
// Test IOCTL
ioctl(fd, IOCTL_GET_SIZE, &size);
printf("Data size: %d bytes\n", size);
// Test clear
ioctl(fd, IOCTL_CLEAR, 0);
ioctl(fd, IOCTL_GET_SIZE, &size);
printf("After clear, size: %d bytes\n", size);
close(fd);
return 0;
}
Platform Device Driver
// platform_driver.c - GPIO LED driver for embedded systems
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("GPIO LED Platform Driver");
struct led_data {
int gpio;
int active_low;
struct device *dev;
};
static int led_probe(struct platform_device *pdev) {
struct device *dev = &pdev->dev;
struct led_data *led;
struct device_node *np = dev->of_node;
int ret;
led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
if (!led) {
return -ENOMEM;
}
led->dev = dev;
// Get GPIO from device tree
led->gpio = of_get_gpio(np, 0);
if (!gpio_is_valid(led->gpio)) {
dev_err(dev, "Invalid GPIO\n");
return -EINVAL;
}
// Get active-low property
led->active_low = of_property_read_bool(np, "active-low");
// Request GPIO
ret = devm_gpio_request(dev, led->gpio, "led_gpio");
if (ret) {
dev_err(dev, "Failed to request GPIO\n");
return ret;
}
// Set GPIO direction
ret = gpio_direction_output(led->gpio, led->active_low ? 1 : 0);
if (ret) {
dev_err(dev, "Failed to set GPIO direction\n");
return ret;
}
platform_set_drvdata(pdev, led);
dev_info(dev, "LED driver probed (GPIO=%d, active_low=%d)\n",
led->gpio, led->active_low);
return 0;
}
static int led_remove(struct platform_device *pdev) {
struct led_data *led = platform_get_drvdata(pdev);
gpio_set_value(led->gpio, led->active_low ? 1 : 0);
dev_info(&pdev->dev, "LED driver removed\n");
return 0;
}
static const struct of_device_id led_of_match[] = {
{ .compatible = "example,led-gpio" },
{ }
};
MODULE_DEVICE_TABLE(of, led_of_match);
static struct platform_driver led_driver = {
.driver = {
.name = "led_driver",
.of_match_table = led_of_match,
},
.probe = led_probe,
.remove = led_remove,
};
module_platform_driver(led_driver);
Device Tree Binding
// led_device.dts - Device tree entry
/dts-v1/;
/ {
model = "My Embedded Board";
compatible = "example,board";
leds {
compatible = "gpio-leds";
led0 {
label = "status-led";
gpios = <&gpio1 18 GPIO_ACTIVE_HIGH>;
default-state = "off";
linux,default-trigger = "heartbeat";
};
};
myled {
compatible = "example,led-gpio";
gpios = <&gpio1 19 GPIO_ACTIVE_LOW>;
active-low;
};
};
Interrupt Handling
// interrupt_driver.c - Driver with interrupt support
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#define DEVICE_NAME "intr_driver"
static int irq_number;
static int irq_counter = 0;
static wait_queue_head_t wq;
static int event_occurred = 0;
// Interrupt handler
static irqreturn_t irq_handler(int irq, void *dev_id) {
irq_counter++;
event_occurred = 1;
// Wake up any waiting processes
wake_up_interruptible(&wq);
pr_info("Interrupt occurred! Count: %d\n", irq_counter);
return IRQ_HANDLED;
}
static int intr_open(struct inode *inode, struct file *file) {
pr_info("Interrupt driver: Opened\n");
return 0;
}
static int intr_release(struct inode *inode, struct file *file) {
pr_info("Interrupt driver: Closed\n");
return 0;
}
static ssize_t intr_read(struct file *file, char __user *user_buffer,
size_t len, loff_t *offset) {
char buffer[32];
size_t bytes;
// Wait for interrupt (non-blocking check)
if (file->f_flags & O_NONBLOCK) {
if (!event_occurred) {
return -EAGAIN;
}
} else {
wait_event_interruptible(wq, event_occurred);
if (signal_pending(current)) {
return -ERESTARTSYS;
}
}
// Clear event flag
event_occurred = 0;
// Format interrupt count
bytes = snprintf(buffer, sizeof(buffer), "%d\n", irq_counter);
// Copy to user space
if (copy_to_user(user_buffer, buffer, bytes)) {
return -EFAULT;
}
return bytes;
}
static struct file_operations intr_fops = {
.owner = THIS_MODULE,
.open = intr_open,
.release = intr_release,
.read = intr_read,
};
static int __init intr_init(void) {
int ret;
dev_t dev_num;
pr_info("Interrupt driver: Initializing\n");
// Initialize wait queue
init_waitqueue_head(&wq);
// Allocate device number
ret = alloc_chrdev_region(&dev_num, 0, 1, DEVICE_NAME);
if (ret) {
pr_err("Failed to allocate device number\n");
return ret;
}
// Register interrupt (example: GPIO interrupt)
irq_number = gpio_to_irq(18); // GPIO 18
if (irq_number < 0) {
pr_err("Failed to get IRQ number\n");
unregister_chrdev_region(dev_num, 1);
return irq_number;
}
ret = request_irq(irq_number, irq_handler,
IRQF_TRIGGER_RISING, "intr_driver", NULL);
if (ret) {
pr_err("Failed to register IRQ handler\n");
unregister_chrdev_region(dev_num, 1);
return ret;
}
pr_info("Interrupt driver: Initialized (IRQ=%d)\n", irq_number);
return 0;
}
static void __exit intr_exit(void) {
pr_info("Interrupt driver: Exiting\n");
free_irq(irq_number, NULL);
unregister_chrdev_region(dev_num, 1);
}
module_init(intr_init);
module_exit(intr_exit);
DMA Support
// dma_driver.c - Driver with DMA support
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/dma-mapping.h>
#include <linux/slab.h>
#define DMA_BUFFER_SIZE (1024 * 1024) // 1 MB
struct dma_device {
struct device *dev;
void *cpu_addr;
dma_addr_t dma_addr;
size_t size;
};
static struct dma_device *dma_dev;
static int __init dma_init(void) {
dma_dev = kmalloc(sizeof(*dma_dev), GFP_KERNEL);
if (!dma_dev) {
return -ENOMEM;
}
dma_dev->dev = NULL; // Set to actual device
dma_dev->size = DMA_BUFFER_SIZE;
// Allocate DMA buffer
dma_dev->cpu_addr = dma_alloc_coherent(dma_dev->dev, dma_dev->size,
&dma_dev->dma_addr, GFP_KERNEL);
if (!dma_dev->cpu_addr) {
pr_err("Failed to allocate DMA buffer\n");
kfree(dma_dev);
return -ENOMEM;
}
// Initialize buffer
memset(dma_dev->cpu_addr, 0, dma_dev->size);
pr_info("DMA driver: Allocated buffer at virt=%p, phys=%pad\n",
dma_dev->cpu_addr, &dma_dev->dma_addr);
// Setup DMA transfer (simplified)
// dma_async_memcpy_buf_to_buf(...);
return 0;
}
static void __exit dma_exit(void) {
if (dma_dev) {
dma_free_coherent(dma_dev->dev, dma_dev->size,
dma_dev->cpu_addr, dma_dev->dma_addr);
kfree(dma_dev);
pr_info("DMA driver: Cleaned up\n");
}
}
module_init(dma_init);
module_exit(dma_exit);
Debugging Techniques
// debug_helper.h
#ifndef DEBUG_HELPER_H
#define DEBUG_HELPER_H
// Debug levels
#define DEBUG_INFO BIT(0)
#define DEBUG_WARN BIT(1)
#define DEBUG_ERROR BIT(2)
#define DEBUG_IO BIT(3)
#define DEBUG_DMA BIT(4)
static int debug_mask = DEBUG_INFO | DEBUG_ERROR;
#define dbg_print(level, fmt, ...) \
do { \
if (debug_mask & level) { \
pr_info("[" #level "] " fmt, ##__VA_ARGS__); \
} \
} while(0)
// Procfs debug interface
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
static int debug_proc_show(struct seq_file *m, void *v) {
seq_printf(m, "Debug mask: 0x%x\n", debug_mask);
seq_printf(m, "Device status: ...\n");
return 0;
}
static ssize_t debug_proc_write(struct file *file, const char __user *buffer,
size_t count, loff_t *pos) {
char buf[32];
int mask;
if (count > sizeof(buf) - 1) {
return -EINVAL;
}
if (copy_from_user(buf, buffer, count)) {
return -EFAULT;
}
buf[count] = '\0';
if (kstrtoint(buf, 0, &mask) == 0) {
debug_mask = mask;
pr_info("Debug mask set to 0x%x\n", debug_mask);
}
return count;
}
static int debug_proc_open(struct inode *inode, struct file *file) {
return single_open(file, debug_proc_show, NULL);
}
static const struct file_operations debug_proc_fops = {
.owner = THIS_MODULE,
.open = debug_proc_open,
.read = seq_read,
.write = debug_proc_write,
.llseek = seq_lseek,
.release = single_release,
};
static void create_debug_interface(void) {
proc_create("driver_debug", 0644, NULL, &debug_proc_fops);
}
static void remove_debug_interface(void) {
remove_proc_entry("driver_debug", NULL);
}
#endif
Best Practices
- Error Handling: Always check return values and clean up on failure
- Memory Management: Use kernel allocators (kmalloc, kzalloc) appropriately
- Synchronization: Use mutexes, spinlocks, and atomic operations correctly
- Interrupt Context: Be aware of what can be called in interrupt handlers
- User Space Copy: Always use copy_to_user/copy_from_user
- Device Tree: Use device tree for hardware description
- Power Management: Implement suspend/resume callbacks
- Testing: Test on multiple kernel versions and hardware
- Documentation: Document device tree bindings and driver interfaces
- License Compliance: Ensure proper license headers
Building and Testing
# Build module make # Install module sudo insmod simpledriver.ko # Check module lsmod | grep simpledriver cat /proc/devices | grep simpledriver # Test with user program gcc -o test test_adv_driver.c ./test # Check kernel log dmesg | tail -50 # Remove module sudo rmmod simpledriver
Conclusion
Writing device drivers in C requires deep understanding of kernel internals, hardware interfaces, and system programming concepts. This guide covered:
- Basic character driver structure
- Advanced features (ioctl, mmap)
- Platform drivers and device tree
- Interrupt handling
- DMA support
- Debugging techniques
Driver development is a complex but rewarding field that bridges hardware and software. With practice and attention to kernel conventions, you can create reliable, efficient drivers that power everything from embedded systems to high-performance computing platforms.
Building Blocks of C: A Complete Guide to Functions
Explains how functions work in C programming, including function declaration, definition, parameters, return values, and how functions help organize reusable code.
https://macronepal.com/bash/building-blocks-of-c-a-complete-guide-to-functions/
The Heart of Text Processing: A Complete Guide to Strings in C
Explains how strings are used in C, covering character arrays, string handling functions, and common techniques for text processing tasks.
https://macronepal.com/bash/the-heart-of-text-processing-a-complete-guide-to-strings-in-c-2/
The Cornerstone of Data Organization: A Complete Guide to Arrays in C
Describes how arrays store multiple values in C, including indexing, initialization, and using arrays to manage structured data efficiently.
https://macronepal.com/bash/the-cornerstone-of-data-organization-a-complete-guide-to-arrays-in-c/
Guaranteed Execution: A Complete Guide to the Do-While Loop in C
Explains the do-while loop structure in C, highlighting how it ensures code runs at least once before checking the loop condition.
https://macronepal.com/bash/guaranteed-execution-a-complete-guide-to-the-do-while-loop-in-c/
Mastering Iteration: A Complete Guide to the For Loop in C
Explains how the for loop works in C, including initialization, condition checking, and increment steps for repeated execution of code blocks.
https://macronepal.com/bash/mastering-iteration-a-complete-guide-to-the-for-loop-in-c/
Mastering Iteration: A Complete Guide to While Loops in C
Explains the while loop structure in C, focusing on condition-based repetition and proper loop control techniques.
https://macronepal.com/bash/mastering-iteration-a-complete-guide-to-while-loops-in-c/
Beyond If-Else: A Complete Guide to Switch Case in C
Explains how switch-case statements work in C programming, enabling efficient handling of multiple conditional branches.
https://macronepal.com/bash/beyond-if-else-a-complete-guide-to-switch-case-in-c/
Mastering the Fundamentals: A Complete Guide to Arithmetic Operations in C
Explains how arithmetic operators such as addition, subtraction, multiplication, and division work in C, along with operator precedence and usage examples.
https://macronepal.com/bash/mastering-the-fundamentals-a-complete-guide-to-arithmetic-operations-in-c/
Foundation of C Programming: A Complete Guide to Basic Input Output
Explains how input and output functions like printf and scanf work in C, forming the foundation for interacting with users and displaying program results.
https://macronepal.com/bash/foundation-of-c-programming-a-complete-guide-to-basic-input-output/