Basics of Device Driver Development in C Language

Basics of Device Driver Development in C Language

A device driver is a bridge between the operating system and hardware, responsible for managing and controlling hardware devices. This article will introduce how to develop simple device drivers using the C language, suitable for beginners.

1. What is a Device Driver?

A device driver is a special type of software that allows the operating system to communicate with hardware components. Each hardware device has its corresponding driver so that the operating system can recognize and control that hardware.

1.1 Classification of Drivers

  • Character Devices: Data is transferred in units of characters, such as keyboards and serial ports.
  • Block Devices: Data is transferred in units of blocks, such as hard drives and USB storage.
  • Network Devices: Used for network communication, such as network cards.

2. Preparing the Development Environment

Before you start writing code, ensure that your development environment is set up. Here are some basic requirements:

  • Linux operating system (e.g., Ubuntu)
  • GCC compiler
  • Kernel source code (can be installed via package management tools)

3. Creating a Simple Character Device Driver

Next, we will create a simple character device driver that can read and write data.

3.1 File Structure

First, we need to create a new folder to store our code. For example, you can name it <span>my_char_device</span>, and then create a file named <span>my_char_device.c</span> within it.

3.2 Writing the Code

Below is an example code for <span>my_char_device.c</span>:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#define DEVICE_NAME "mychar"
#define BUFFER_SIZE 256
static char message[BUFFER_SIZE];
static short size_of_message;
static int major_number;

static int device_open(struct inode *inode, struct file *file) {
    return 0;
}

static int device_release(struct inode *inode, struct file *file) {
    return 0;
}

static ssize_t device_read(struct file *file, char __user *buffer, size_t length, loff_t *offset) {
    if (*offset >= size_of_message) {
        return 0; // EOF
    }
    if (length > size_of_message - *offset) {
        length = size_of_message - *offset; // Adjust length
    }
    if (copy_to_user(buffer, message + *offset, length)) {
        return -EFAULT; // Error in copying data to user space
    }
    *offset += length;
    return length; // Return number of bytes read
}

static ssize_t device_write(struct file *file, const char __user *buffer, size_t length, loff_t *offset) {
    if (length > BUFFER_SIZE - 1) {
        length = BUFFER_SIZE - 1; // Prevent buffer overflow
    }
    if (copy_from_user(message, buffer, length)) {
        return -EFAULT; // Error in copying data from user space
    }
    message[length] = '\0'; // Null terminate the string
    size_of_message = strlen(message);
    return length; // Return number of bytes written
}

struct file_operations fops = {
   .open = device_open,
   .release = device_release,
   .read = device_read,
   .write = device_write,
};

static int __init mychar_init(void) {
   major_number = register_chrdev(0, DEVICE_NAME, &fops);
   if (major_number < 0) {
      printk(KERN_ALERT "Failed to register a major number\n");
      return major_number;
   }
   printk(KERN_INFO "MyCharDevice registered with major number %d\n", major_number);
   return 0;
}

static void __exit mychar_exit(void) {
   unregister_chrdev(major_number, DEVICE_NAME);
   printk(KERN_INFO "MyCharDevice unregistered\n");
}

module_init(mychar_init);
module_exit(mychar_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple character driver");

3.3 Code Analysis

Including Header Files

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/uaccess.h>

These header files provide essential functionalities required for kernel modules, including module registration, file operations, and data interaction between user space and kernel space.

Defining Constants and Variables

#define DEVICE_NAME "mychar"
#define BUFFER_SIZE 256
static char message[BUFFER_SIZE];

Here we define our character device name and buffer size, and declare an array to store messages.

Implementing File Operation Functions

We implemented four basic functions:

  • <span>device_open</span>: Called when opening.
  • <span>device_release</span>: Called when closing.
  • <span>device_read</span>: Reads data from kernel to user space.
  • <span>device_write</span>: Writes data from user space to kernel.

Module Initialization and Exit Functions

module_init(mychar_init);
module_exit(mychar_exit);

These two macros specify the initialization function to be executed when the module is loaded and the cleanup function to be executed when it is unloaded.

4. Compiling and Loading the Module

Navigate to the directory containing the source code in the terminal, then run the following command to compile the module:

make -C /lib/modules/$(uname -r)/build M=$(pwd) modules

Next, use the following command to load the module:

sudo insmod my_char_device.ko

Check if it loaded successfully:

dmesg | tail

5. Testing Our Character Device

You can access this newly created character device via <span>/dev/mychar</span>. In the terminal, you can use the following commands to test the read and write functionality:

Writing Data:

echo "Hello World" > /dev/mychar

Reading Data:

cat /dev/mychar

You should see the output “Hello World”.

Conclusion

This article introduced how to write a simple Linux character device driver using the C language, including basic concepts, environment preparation, and example code. I hope it helps you understand driver development in Linux.

Leave a Comment