Introduction
This blog series will discuss topics related to embedded Linux device drivers development. It will explain theoretical concepts and walks through practical examples using hardware devices.
This post will introduce some necessary basic definitions to know in device drivers field and developing a basic driver example for a better view.
Linux kernel modules
To start off with, a kernel module is a software that can be loaded and unloaded during kernel runtime. Those types of modules are known as Loadable Kernel Modules (LKM).
The kernel is loaded by the bootloader at system power-up whilst the kernel modules are loaded after the kernel is started. The fact that a module is loadable, gives the system a safe flexibility in kernel customization without the need to implement the code directly in the kernel and recompiling it.
The difference between kernel modules and user applications is that kernel modules run in the kernel space, whereas user applications run in the user space. This post discusses more details about kernel space and user space.
The most common cases where LKMs are used are:
- Device drivers
- System calls drivers
- FileSystem drivers
Linux Device Drivers
A device driver is a module that allows the kernel to interact with the hardware of a specific device.
System calls drivers
System calls are used to allow user space to communicate with the kernel. Some of them may be used to send instructions to the hardware such as a power-off and Linux power management. Most of those drivers are integrated in the kernel and are not loadable but there is always possibility to customize a System call driver to be loadable.
FileSystem drivers
The FileSystem refers to the way of storing files and directories on a storage device. There are many filesystems that are used such as NTFS, FAT32…
A FileSystem driver is used to read a storage device content for a specific FileSystem type. For example NTFS driver allows the system to view the content of an NTFS disk drive.
Linux Device Driver types
Devices in Linux are accessed by users as files and there are three types of devices that are supported in Linux kernel:
- Character devices
Character devices are read and written character by character.
e.g. keyboard, mouse, serial ports… - Block devices
For block devices, data length is large and is structured in blocks.
E.g. hard drives, ramdisks… - Network devices
Network devices transmit and receive data in the form of packets, they are used for communication protocols
E.g. Ethernet card
Programming a kernel module
A kernel module program is characterized by having two interesting functions, the “Init function” and “Exit function” that are run respectively when the module is loaded or unloaded.
Init function
The init function is executed once the module is loaded to the kernel using the commands “insmod” or “modprobe” and registered by using module_init() macro. Below an init function example:
static int __init AJ_driver_init(void)
{
return 0;
}
module_init(AJ_driver_init);
Exit function
The Exit function is called when the module is removed using the command “rmmod” and registered by using module_exit() macro. Below an exit function example:
static void __exit AJ_driver_exit(void)
{
}
module_exit(AJ_driver_exit);
Printk() for kernel logs
When dealing with the kernel it’s necessary to track kernel actions that are related to the module management using kernel logs. For that, printk() function provides several kernel log levels to handle modules information. Linux kernel logs can be displayed using “dmesg” command. Below a list of available kernel log levels:
- KERN_INFO
- KERN_ERR
- KERN_WARNING
- KERN_DEBUG
- KERN_EMERG
- KERN_ALERT
- KERN_CRIT
- KERN_NOTICE
- KERN_CONT
KERN_CONT is used to continue printing in the same line as previous print.
Here is an example of using kernel info log level in printk:
printk(KERN_INFO "Welcome to AJ-ESE \n");
There are alternative functions for printk() in newer Linux kernels that are:
- pr_info()
- pr_err()
- pr_warn()
- pr_debug()
- pr_emerg()
- pr_alert()
- pr_crit()
- pr_notice()
- pr_cont()
Simple Linux kernel module example
.C file
/*******************************************************************************
* \file driver-example.c
*
* \details Simple driver example
*
* \author AJ Embedded Systems Consultant https://aj-ese.com
*
* *******************************************************************************/
#include<linux/kernel.h>
#include<linux/init.h>
#include<linux/module.h>
/*
** Module Init function
*/
static int AJ_driver_init(void)
{
printk(KERN_INFO "Welcome to AJ-ESE \n");
printk(KERN_INFO "The AJ-driver is inserted successfully...\n");
return 0;
}
/*
** Module Exit function
*/
static void AJ_driver_exit(void)
{
printk(KERN_INFO "The AJ-driver is removed successfully...\n");
}
module_init(AJ_driver_init);
module_exit(AJ_driver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("AJ Embedded Systems Consultant https://aj-ese.com");
MODULE_DESCRIPTION("Simple driver example");
MODULE_VERSION("1:0.0");
Makefile
obj-m += driver-example.o
KDIR = /lib/modules/$(shell uname -r)/build
all:
make -C $(KDIR) M=$(shell pwd) modules
clean:
make -C $(KDIR) M=$(shell pwd) clean
- make -C $(KDIR) M=$(shell pwd) modules
-C for the kernel source directory.
M=$() for the module files location.
modules for generating output files in the default directory (current directory). - make -C $(KDIR) M=$(shell pwd) clean
clean for removing all generated files in the module directory only.
Kernel module compilation
Before compiling the module make sure kernel headers are installed, if not it can be done using command line below:
$ sudo apt-get install linux-headers-$(uname -r)
The compilation can be run from the module directory by running make command:

Inserting and removing kernel module
After compilation is completed some output files will be generated in the module directory. The one used for inserting the module in the kernel is the .ko. and it can be loaded using the command “insmod” or “modprobe”
The kernel module can be removed using the command “rmmod”.

Leave a Reply