Introduction
The part 2 of embedded Linux drivers series explained how arguments can be implemented in a module and how they are applied in user command lines when loading a module to the kernel.
This post will continue a step farther to introduce communication between user application and kernel module focusing on the Major and Minor numbers.
Communication between user space and kernel module
Communication between a user space application and kernel module is a crucial mechanism for loadable kernel modules. It is implemented in few steps in both the user app and the module itself.
Consider the diagram below showing a communication flow from user Application to the kernel module. The application uses a system call to interact with the device file that recognizes the concerned module by its Major and Minor numbers. So the three main steps are:
- System call
System calls are to be implemented in both the user application and the kernel module. Many functionalities can be used for System calls such as IOCTL, SysFS, ProcFS, UDP Sockets… (System calls are subjects of future posts) - Device File (AKA Device Node)
Hardware devices are handled from kernel space thanks to their device files that are accessed from user space for read and write instructions. The kernel stores in its space data written to device files from the user space. The kernel space identifies the corresponding file to a device by its Major and Minor numbers that are provided when creating the device file. (Device file is subject of future posts) - Major and Minor numbers
So the flow of the implementation starts from implementing Major and Minor numbers in the kernel module. They identify a character or block device to the kernel as a pair of number <Major> and <Minor>.

Major and Minor numbers
Major number
The Major number identifies a device to the kernel and there could be several devices of the same type sharing the same Major number. E.g. tty0, tty1, tty2 can have the same Major number but different Minor numbers.
Minor numbers
The minor identifies then a specific device and it’s unique to a device and it distinguishes devices having same Major number. Major and Minor numbers can be listed from /dev using:
$ ls -l /dev
The screenshot in figure2 shows a few tty devices that have “4” as Major number and “0”, “1”, “10” respectively for tty0, tty1, tty10.

Specifying Major and Minor numbers
To specify a Major number to a device, the user must ensure the wanted number is free and not already associated to another device. Depending on the use-cases if the user knows a free number or not, there are two ways to allocate a Major number:
- Static allocation
Used when the users knows a free specific Major number. - Dynamic allocation
Used if the user wants to leave the choice of the number to the kernel.
Static allocation
The static allocation can be implemented using the function:
int register_chrdev_region ( dev_t from,
unsigned count,
const char * name);
- from
Specifies the first number in the desired range of device numbers. It includes both the Major and the Minor.
dev_t is a u32 type (32 bits) . - count
Defines the number of consecutive device numbers required. - name
Defines the device driver name. Its the name that will be shown /proc/devices.
This function returns zero on success and a negative error code on failure.
The function MKDEV(ma,mi) is used to create Major and Minor numbers. It is defined in linux/kdev_t.h and it reserves 12 bits for Major and 20 for Minor of the dev_t variable.
The numbers can then be extracted using MAJOR(dev_t dev) and MINOR(dev_t dev).
Code below shows an example of MKDEV() usage:
dev_t dev = MKDEV(100, 0);
register_chrdev_region(dev, 1, "AJ_Device");
Static allocation complete driver example
/*******************************************************************************
* \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>
#include <linux/fs.h>
/* Associating Major and Minor number to a device*/
dev_t dev = MKDEV(200, 0);
const char * dev_name = "AJ_Dev";
/*
** Module Init function
*/
static int AJ_driver_init(void)
{
/* Creating a device with the reserved Major and Minor numbers */
if ( (register_chrdev_region(dev, 1, dev_name)) < 0 )
{
printk(KERN_INFO "Cannot allocate major number for device %s \n", dev_name);
return -1;
}
else
{
printk(KERN_INFO "The device %s is created with Major = %d Minor = %d \n",dev_name, MAJOR(dev), MINOR(dev));
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:1.0");

Dynamic allocation
The dynamic allocation can be implemented using the function:
int alloc_chrdev_region ( dev_t * dev,
unsigned baseminor,
unsigned count,
const char * name);
- dev
Is the allocation output parameter for the first assigned number. It stores the first major number selected by the kernel and the minor number. - baseminor
First of the requested range of minor numbers. - count
The number of minor numbers required. - name
The name of the associated device or driver.
Unregistering Major and Minor numbers
To unregister Major and Minor numbers of a device no matter the method used static or dynamic allocation, the function below can be used:
void unregister_chrdev_region(dev_t from, unsigned count);
- from
The first number in the range to unregister. - count
The number of device numberss to unregister.
Dynamic allocation complete driver example
/*******************************************************************************
* \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>
#include <linux/fs.h>
/* Associating Major and Minor number to a device*/
dev_t dev = 0;
const char * dev_name = "AJ_Dev";
/*
** Module Init function
*/
static int AJ_driver_init(void)
{
/* Creating a device with the reserved Major and Minor numbers */
if ( ( alloc_chrdev_region(&dev, 0, 1, dev_name)) < 0 )
{
printk(KERN_INFO "Cannot allocate major number for device %s \n", dev_name);
return -1;
}
else
{
printk(KERN_INFO "The device %s is created with Major = %d Minor = %d \n",dev_name, MAJOR(dev), MINOR(dev));
printk(KERN_INFO "The AJ-driver is inserted successfully...\n");
return 0;
}
}
/*
** Module Exit function
*/
static void AJ_driver_exit(void)
{
unregister_chrdev_region(dev, 1);
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:1.1");

Leave a Reply