How to Perform Character Device Registration for a Linux Character Device

Beaglebone black board






In this article, we show how to perform character device registration for a linux character device.

Previously, we showed how to how to assign a character device number to a linux character device driver.

Assigning a unique character device number is the first step in creating a character device driver in linux.

The next step is we have to register the character device with the VFS, which is the Virtual File System.

This is where we register all the character devices we want to register. This may be 1 character device or several.

The character device registration is important, because if the character device is not registered with the virtual file system (VFS), the user-level system calls will not be connected to the file operation methods of the driver. The device driver lives in the kernel space. The user-level system calls must be connected to these file operations in the device driver code in the kernel space, or else the user-level system calls will not be understood (since it lacks connection with the file operations in the device driver in the kernel space).

So in order to register a character device, we need to use 2 functions: cdev_init() and cdev_add().

cdev_init() is used to initialize a cdev structure.

The cdev_init() function takes in 2 input arguments and takes the following format.


void cdev_init(struct cdev *cdev, const struct file_operations *fops);


Below we see the cdev structure, as well as the cdev_init() and the cdev_add() functions.

Before we look at the cdev_init() function, we first should look at the cdev structure, since this is used as an input to the cdev_init() function.

This is shown below.


cdev functions for character device registration in linux


We can see that the cdev structure takes in 2 pointer variables, *owner and *ops.

*owner is a pointer to the module that owns this structure; it should usually be initialized to THIS_MODULE.

THIS_MODULE is a macro which resolves to a pointer to a struc module variable which corresponds to the current module. So it identifies the current module which owns this structure.


THIS_MODULE macro in export.h file in linux


Looking at this code, we can see that the THIS_MODULE macro is simply an address to __this__module and __this_module is a structure of a module (which represents the current module).

This is important to know when we initialize the *owner pointer variable.

*ops is a pointer to the file operation structure of the driver. The cdev_init() function will initialize this field.

*ops will represent the file operations for this particular character device, meaning which file operations it will use such as the open() function, close() function, write() function, read() function, etc.

These will all be defined later.

Let's take a look at the cdev_init() function, as it is defined completely in the char_dev.c file in the /linux/fs/ directory.


cdev_init() function in char_dev.c file in linux


You can see more closely that it takes in 2 parameters, the cdev structure and the file_operations structure.

Now let's take a close look at the other function we will be examining, the cdev_add() function.

This is shown below.


cdev_add() function in char_dev.c file in linux


The cdev_add() function takes in 3 parameters, the cdev structure, the dev_t variable, and the count (number of character devices you are adding).

Remember that the dev_t variable is a variable we created when creating the character device number. this variable holds the device number (major and minor number).

Before we write the code, let's look at an example of how this initialization works.

Let's say we are creating a character device for an eeprom device.

Let's say it's a single eeprom device that we are initializing, not multiple devices.

Our code would look that shown below.


Example:

//Format
//void cdev_init(struct cdev *cdev, const struct file_operations *fops);

struct file_operations eeprom_fops;

struct cdev eeprom_cdev;

cdev_init(&eeprom_cdev,&eeprom_fops);

//Initializes the owner of this character device to current module
pcd_cdev.owner= THIS_MODULE;

cdev_add(&eeprom_cdev,device_number,1);



Now, as an example, let's say we are initializing multiple eeprom devices, such as 4 eeprom devices for a particular system that we are building.

How does this change our code?

The only way our code changes is in the cdev_add() function, where you would specify 4 as the count instead of 1.

This would change our code to resemble that as seen below.


Example:

//Format
//void cdev_init(struct cdev *cdev, const struct file_operations *fops);

struct file_operations eeprom_fops;

struct cdev eeprom_cdev;
cdev_init(&eeprom_cdev,&eeprom_fops);
//Initializes the owner of this character device to current module
eeprom_cdev.owner= THIS_MODULE;

cdev_add(&eeprom_cdev,device_number,4);


If you are registering multiple separate character devices (that have different major numbers), then you would have to create separate cdev structures for each, along with each structure having its own cdev_init() function, owner declaration, and cdev_add() function.

So this gives you an idea how the cdev_init() function works for a singular character device or multiple character devices.

Let's now go back to our pseudo character device function and write this code into our main.c code.


This is a continuation of our previous code, where we generated a device number for our character device.

We create a cdev variable with the line, struct cdev pcd_cdev;

We create a file_operations variable with the line, struct file_operations pcd_fops;

We initialize the cdev structure with the line, cdev_init(&pcd_cdev,&pcd_fops);

We define the owner of the structure with the line, pcd_cdev.owner= THIS_MODULE;

We register the device with the VFS through the line, pcd_cdev.owner= THIS_MODULE;

The cdev_add() function is how we finally register the device with the Virtual File System (VFS).

And this is all we need to perfrom character device registration for a linux character device.

If you have multiple minor device numbers, then the add() function would have a number proportional to the number of minor devices you are registering.

If you are creating multiple separate character devices (with different major numbers), then you would need to have separate cdev structure, cdev_init() function, and cdev_add() functions for each separate character device, along with declaring the owner of each character device to THIS_MODULE

Then save this file with :wq

Back to the terminal, run the command, make host

You should see that this runs without any errors as that shown below.


Linux character device registration source code build




And this is how to perform character device registration for a linux character device.



Related Resources





HTML Comment Box is loading comments...