Go back

Mastering Linux Kernel Driver Development

Table of contents

  1. INTRODUCTION
  2. CHARACTER DEVICE DRIVER BASICS
    • What Are Character Device Drivers
    • The Role of Char Drivers
    • Major and Minor Device Numbers
    • File Operations
  3. EXAMPLE OF A CHARACTER DEVICE DRIVER
    • Set Up Your Development Environment
    • Header Files and Macros
    • Global Variables
    • Function Prototypes
    • File Operations Structure
    • Open Function
    • Release Function
    • Read Function
    • Write Function
    • Initialization Function
    • Exit Function
    • Initialization and Exit Macros
    • Linux Character Device Driver – Code Implementation.
    • Makefile for Building the Linux Kernel Module.
  4. BUILDING AND INSERTING A LINUX CHARACTER DEVICE DRIVER
  5. TESTING THE USER-SPACE APPLICATION FOR THE CHARACTER DRIVER
  6. CONCLUSION
  7. REFERENCES

1.    INTRODUCTION

How to build Linux Character Device Drivers

This document serves as a guide for developers seeking to write Linux Device Drivers, which are crucial software components designed for specific hardware. 

Kernel drivers are essential software components that enable communication between the operating system and hardware devices. They facilitate communication and control of hardware devices, allowing the operating system to manage and utilize various hardware components effectively.

There are several types of kernel drivers, each with a distinct purpose and functionality. The different types of kernel drivers are:

  • Block Device Drivers: Responsible for managing block-level storage devices such as hard drives, SSDs, and USB storage,
  • Character Device Drivers: Responsible for managing character-oriented devices, such as serial ports, keyboards, and mice,
  • USB Device Drivers: Crucial for supporting a wide range of peripheral devices, including keyboards, mice, printers, and storage devices, connected via USB ports,
  • Filesystem Drivers: Manage various filesystems (e.g., FAT, NTFS, ext4), enabling the operating system to interact with storage devices in a file-centric manner,
  • Graphics Device Drivers : Essential for rendering graphics on computer screens
  • and Audio Device Drivers:  Enable the playback and recording of audio on a computer system.

This document focuses on character device drivers, providing insights into their development.

The Character device drivers are a fundamental component of modern operating systems, facilitating the interaction between software and character-oriented hardware devices. These drivers play a vital role in managing devices such as serial ports, keyboards, mice, and other peripherals that transmit data on a character-by-character basis. Unlike block devices, which handle data in fixed-size blocks, character devices process information in its raw, individual form.

This intimate interaction with hardware makes character device drivers an essential bridge between user-space applications and the underlying kernel, allowing for real-time and event-driven communication with a diverse range of input and output devices. In this article, we explore the complexities of character device drivers, their essential role in kernel development, and the principles governing their design and implementation.

2.     CHARACTER DEVICE DRIVER BASICS

This section discusses the fundamental concepts and essential components that form the backbone of character device drivers, providing a solid foundation for understanding their inner workings and development.

2.1    What Are Character Device Drivers?

Character Devices: A Byte-Level Interface

Character devices are hardware peripherals or components that can be accessed as a continuous stream of bytes. Unlike block devices, which handle data in fixed-sized blocks or sectors, character devices process data one byte at a time. This byte-level interface makes them suitable for devices that generate or consume data sequentially, like serial ports or input devices.

2.2    The Role of Char Drivers

Character device drivers are responsible for bridging the gap between user-space applications and character devices, acting as intermediaries to ensure that data flows smoothly between the users’ requests and the devices’ capabilities. These drivers implement essential system calls such as open, close, read, and write to provide a standardized way for applications to interact with character devices.

2.3    Major and Minor Device Numbers

Each character device is identified by a combination of major and minor device numbers. The major number identifies the driver associated with the device, while the minor number distinguishes individual instances or units of the same driver. These numbers are essential for the kernel to route requests to the correct driver and device.

Example:

Major and Minor Device numbers Linux

One Major number 5 for every ttySX device and different (1-3) Minor numbers.

2.4    File Operations

File operations in a character device driver are essential functions that define how the driver interacts with user-space applications when reading from or writing to the device. These operations allow the driver to manage data transfers, handle device-specific logic, and provide a standardized interface for user programs. The below are the common file operations In a character driver.

1. Open(‘open’):

  • The open function is called when a user-space program opens the character device file (e.g: /dev/mydevice).
  • It can perform tasks like initializing the device, checking permissions, and allocating resources.
  • The open function returns a file descriptor to the calling process, which is used for subsequent read and write operations.

2. Close(‘release’ or ‘close’):

  • The close (or release) function is called when a user-space program closes the device file.
  • It is responsible for cleaning up any resources allocated during the open operation and ensuring the device is properly closed.

3. Read (‘read’):

  • The read function handles requests to read data from the device into a user-space buffer.
  • It checks for available data, copies data from the device’s internal buffer to the user’s buffer, and updates the file position.
  • The number of bytes read is returned to the user-space program.

4. Write (‘write’):

  • The write function processes requests to write data from a user-space buffer to the device.
  • It checks for available space in the device’s buffer, copies data from the user’s buffer, and updates the file position.
  • The number of bytes written is returned to the user-space program.

5. Ioctl (‘ioctl’):

  • ‘ioctl’ is a system call in a character driver that enables device-specific control and configuration operations.
  • It allows user-space programs to interact with and configure the device using custom command codes and associated data.

The file operation functions are part of a file operations structure (i.e. struct file_operations or fops). When a user program interacts with the character device file, the kernel routes the requests to the appropriate file operation functions defined in this structure.

3.    EXAMPLE OF A CHARACTER DEVICE DRIVER

This is a simple example of a character device driver that open, read, write, and close a file, along with the steps to create and test it.

3.1. Set Up Your Development Environment

Make sure you have a Linux development environment with kernel headers installed. You’ll also need root privileges to load kernel modules.

3.2. Header Files and Macros

This section includes the necessary header files for kernel development and defines macros, such as mem_size representing the size of the device’s memory.

Header files and Macros Linux

 3.3. Global Variables

Global Variables Linux
  • dev_t: Represents the device numbers (both major and minor).
  • struct class *dev_class: Represents the device class for udev.
  • struct cdev chr_cdev: Represents the character device structure.
  • uint8_t *kernel_buffer: Represents the kernel memory buffer.

3.4 Function Prototypes

This section declares the prototypes of the functions used in the driver.

Function prototypes Linux

3.5 File Operations Structure:

file operations structure Linux

Initializes the file operations structure with the functions that will be called when the user interacts with the device file.

3.6 Open Function

Open function Linux

This function allocates kernel memory for the device buffer during the device file open operation.

3.7 Release Function

Release function Linux

This function frees the allocated kernel memory during the device file close operation.

3.8 Read Function

Read function Linux

This function copies data from the kernel space to the user space during the read operation.

3.9 Write Function

Write function Linux

This function copies data from the user space to the kernel space during the write operation.

3.10 Initialization Function

This function initializes and registers the character device driver, including major number allocation, cdev initialization, class creation, and device creation.

Initialization function Linux

3.11 Exit Function

Exit Function

This function cleans up and unregisters the character device driver during module removal.

3.12 Initialization and Exit Macros

Initialization and Exit Macros

This specifies the initialization and exit functions when the module is loaded and unloaded, respectively.

3.13 Linux Character Device Driver – Code Implementation

Now, please find below the consolidated Linux character device driver code, incorporating all the sections we discussed earlier, into one complete code.

Linux Character Device Driver – Code Implementation
Linux Character Device Driver – Code Implementation
Linux Character Device Driver – Code Implementation

3.11 Makefile for Building the Linux Kernel Module

Makefile for Building the Linux Kernel Module

     The Makefile is to compile and build the kernel module.

4.    BUILDING AND INSERTING A LINUX CHARACTER DEVICE DRIVER

Step 1: In the terminal go to the directory where your character driver  and Makefile are located and run  the ‘make‘ command to build the driver and generate the .ko file.

Step 2: Use ‘insmod‘ to insert the compiled kernel module into the kernel.

Step 3: Verify module Insertion use the below commands

 3.a dmesg | tail : To display the last few lines of the kernel message buffer.

3.b cat /proc/devices: is used in Linux to display a list of character and block devices registered in the kernel. This command shows the major and minor numbers of each device, along with the device type.

5.    TESTING THE USER-SPACE APPLICATION FOR THE CHARACTER DRIVER

This section focuses on testing procedures for the user-space application associated with the character driver ‘chr_Dev.’ The example code provided below illustrates how to test the read and write operations, ensuring the interaction between user programs and the ‘chr_Dev’ character driver. Through these tests, we aim to validate the reliability and effectiveness of the driver in handling data exchanges between the user space and the Linux kernel.

 5.a User Space Application

5. b Output

Create an executable for the user space application using a suitable compiler (e.g: gcc) and then run the resulting file.

The below screenshot displays log files retrieved using the dmesg command, offering insights into kernel-related events and system messages for the chr-dev character driver

6.    CONCLUSION

The exploration of the chr-dev character driver and its integration with a user-space application highlights the data exchange between the user and kernel spaces. The driver, identified by Major Number 239, demonstrates its versatility in handling read and write operations. Through testing we have confirmed the reliability and robustness of the driver, making it a valuable tool for developers.

7.    REFERENCES FOR THIS GUIDE “How to build Linux Character Device Drivers”

1. [Jonathan Corbet and Kroah-Hartman, 2005] Jonathan Corbet, A. R. and Kroah-Hartman, G. (2005). Linux Device Drivers, Third Edition . O’Reilly Media

2. Character device drivers, accessed 18 September 2023.