Introduction
The previous post (Part 3) explained the Major and Minor numbers and their role in Linux kernel.
To remind, in order to communicate with the hardware from a user application, the application uses a system call to interact with the device file that recognizes the concerned module by its Major and Minor numbers, the module then controls the hardware.
Continuing the same series of kernel device drivers development, this article caries on the communication between the hardware and user space to describe the device files and how they are managed.
Device files
Hardware devices are handled from kernel space thanks to their device files that are accessed from user space for read and write instructions.
Device files are classic files that can be accessed from user space for read and write instructions allowing kernel device handling for sending/receiving data from hardware. The data written to device files from the user space is stored in kernel space.
Device files of a system are available in /dev and they can be listed using:
$ ls -l /dev

Notice that the first letter in permissions is ‘c’ for char devices, it can also be ‘b’ for block devices. The numbers “0”, “1”, “10” are respectively for tty0, tty1, tty10 that have “4” as Major number.
Device files can be created manually from command line or automatically in module code.
Creating device files manually
To create a device file the easiest way is by using mknod command described below:
mknod -m <permissions> <file_name> <device_type> <Major> <Minor>
- -m <permission>
It sets permission bits to the file. If it’s not set default permission will be applied. - file_name
Specifies the path and file name. - device_type
Specifies the device type; ‘c’ for character device or ‘b’ for block device. - Major Minor
For Major and Minor numbers
Below a command example for creating AJ_Dev as block device file with read/write permissions and (245,0) for (Major,Minor):
$ sudo mknod -m 666 /dev/AJ_Dev b 245 0

Creating device files automatically
Creating device files can be implemented in the module code in two steps:
- Creating a device driver Class
- Creating the device using the class previously created
Creating a device driver Class
The device driver class can be created using the function class_create() described below and will be available in /sys/class:
struct class * class_create(struct module *owner,
const char *name);
- owner
Pointer to the module for which this class is created. - name
Pointer to the string containing the class name.
The return value can be checked with IS_ERR() and the class can be destroyed using the function:
void class_destroy (struct class * cls);
Creating a device
The function below creates a device in SysFS which is a virtual filesystem mounted in /sys and consists of files containing information about devices:
struct device *device_create(const struct class *class,
struct device *parent,
dev_t devt, void *drvdata,
const char *fmt,
...)
- class
Refers to the class created previously. - parent
Pointer to the parent struct device of this device, if any. - devt
The device to be added. - drvdata
The data to be added to the device for callbacks. - fmt
Pointer to the string containing the device name. - …
For variable parameters.
The device file will then be created in /dev showing the Major and Minor numbers if they’re different to 0,0.
The created device in SysFS will be a child of the parent device if specified. Or it can be itself a parent for further sysfs files thanks to the common type (struct device) between the device and the parent.
Similarly to the class, the return value can be check with IS_ERR() and the device can be destroyed using the function:
void device_destroy (struct class * class, dev_t devt);
Module example
Below a code example implementing automatic device file creation:
/*******************************************************************************
* \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>
#include <linux/kdev_t.h>
#include <linux/err.h>
#include <linux/device.h>
/* Associating Major and Minor number to a device*/
dev_t dev = 0;
const char * device_name = "AJ_Dev";
static struct class *device_class;
/*
** 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, device_name)) < 0 )
{
printk(KERN_INFO "Cannot allocate major number for device %s \n", device_name);
return -1;
}
/*Creating device class*/
device_class = class_create(THIS_MODULE,"AJ_class");
if(IS_ERR(device_class))
{
pr_err("Cannot create the struct class for device\n");
goto destroy_class;
}
/*Creating device*/
if(IS_ERR(device_create(device_class,NULL,dev,NULL,"AJ_device")))
{
pr_err("Cannot create the Device\n");
goto destroy_device;
}
printk(KERN_INFO "The device %s is created with Major = %d Minor = %d \n",device_name, MAJOR(dev), MINOR(dev));
printk(KERN_INFO "The AJ-driver is inserted successfully...\n");
return 0;
destroy_class:
class_destroy(device_class);
destroy_device:
device_destroy(device_class, dev);
return -1;
}
/*
** Module Exit function
*/
static void AJ_driver_exit(void)
{
device_destroy(device_class,dev);
class_destroy(device_class);
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.2");

Leave a Reply