Linux Device Driver Tutorial Part 6 – Cdev structure and File Operations

This article is a continuation of the  Series on Linux Device Driver, and carries on the discussion on character drivers and their implementation.This is the Part 6 of Linux device driver tutorial. In this tutorial we will discuss Cdev structure and File Operations of Character drivers.

Based on our previous tutorial, we know about major, minor number and device file. So as i said in previous tutorial, we need to open, read, write and close the device file. So let’s start…

Cdev structure and File Operations of Character drivers

If we want to open, read, write and close we need to register some structures to driver.

cdev structure

In linux kernel struct inode structure is used to represent files. Therefore, it is different from the file structure that represents an open file descriptor. There can be numerous file structures representing multiple open descriptors on a single file, but they all point to a single inode structure.

The inode structure contains a great deal of information about the file. As a general rule, cdev structure is useful for writing driver code:

 struct cdev is one of the elements of the inode structure. As you probably may know already, an inode structure is used by the kernel internally to represent files. The struct cdev is the kernel’s internal structure that represents char devices.  This field contains a pointer to that structure when the inode refers to a char device file.

This is cdev structure. Here we need to fill two fields,

  1. file_operation (This we will see after this cdev structure)
  2. owner (This should be THIS_MODULE)

There are two ways of allocating and initializing one of these structures.

  1. Runtime Allocation
  2. Own allocation

If you wish to obtain a standalone cdev structure at runtime, you may do so with code such as:

struct cdev *my_cdev = cdev_alloc( );
my_cdev->ops = &my_fops;

Or else you can embed the cdev structure within a device-specific structure of your own by using below function.

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

Once the cdev structure is set up with file_operations and owner, the final step is to tell the kernel about it with a call to:

int cdev_add(struct cdev *dev, dev_t num, unsigned int count);

Here,

dev is the cdev structure,

num is the first device number to which this device responds, and

count is the number of device numbers that should be associated with the device. Often count is one, but there are situations where it makes sense to have more than one device number correspond to a specific device.

If this function returns negative error code, your device has not been added to the system. So check the return value of this function.

After a call to cdev_add(), your device is immediately alive. All functions you defined (through the file_operations structure) can be called.

To remove a char device from the system, call:

void cdev_del(struct cdev *dev);

Clearly, you should not access the cdev structure after passing it to cdev_del.

File_Operations

The file_operations structure is how a char driver sets up this connection. The structure, defined in <linux/fs.h>, is a collection of function pointers. Each open file  is associated with its own set of functions . The operations are mostly in charge of implementing the system calls and are therefore, named openread, and so on.

file_operations structure or a pointer to one is called fops. Each field in the structure must point to the function in the driver that implements a specific operation, or be left NULL for unsupported operations. The whole structure is mentioned below snippet.

This file_operations structure contains many fields. But we will concentrate on very basic functions. Below we will see some fields explanation.

struct module *owner:

The first file_operations field is not an operation at all; it is a pointer to the module that “owns” the structure. This field is used to prevent the module from being unloaded while its operations are in use. Almost all the time, it is simply initialized to THIS_MODULE, a macro defined in <linux/module.h>.

ssize_t (*read) (struct file *, char _ _user *, size_t, loff_t *);

Used to retrieve data from the device. A null pointer in this position causes the read system call to fail with -EINVAL (“Invalid argument”). A non negative return value represents the number of bytes successfully read (the return value is a “signed size” type, usually the native integer type for the target platform).

ssize_t (*write) (struct file *, const char _ _user *, size_t, loff_t *);

Sends data to the device. If NULL-EINVAL is returned to the program calling the write system call. The return value, if non negative, represents the number of bytes successfully written.

int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);

The ioctl system call offers a way to issue device-specific commands (such as formatting a track of a floppy disk, which is neither reading nor writing). Additionally, a few ioctl commands are recognized by the kernel without referring to the fops table. If the device doesn’t provide an ioctl method, the system call returns an error for any request that isn’t predefined (-ENOTTY, “No such ioctl for device”).

int (*open) (struct inode *, struct file *);

Though this is always the first operation performed on the device file, the driver is not required to declare a corresponding method. If this entry is NULL, opening the device always succeeds, but your driver isn’t notified.

int (*release) (struct inode *, struct file *);

This operation is invoked when the file structure is being released. Like openrelease can be NULL.

Example

If you want to understand the complete flow just have a look at our dummy driver.

Dummy Driver

Here i have added dummy driver snippet. In this driver code, we can do all open, read, write, close operations. Just go through the code.

  1. Build the driver by using Makefile (sudo make)
  2. Load the driver using sudo insmod
  3. Do echo 1 > /dev/etx_device

Echo will open the driver and write 1 into the driver and finally close the driver. So if i do echo to our driver, it should call the open, write and release functions. Just check.

[email protected]:/home/driver/driver#  echo 1 > /dev/etx_device 

4. Now Check using dmesg

[email protected]:/home/driver/driver$ dmesg
[19721.611967] Major = 246 Minor = 0
[19721.618716] Device Driver Insert...Done!!!
[19763.176347] Driver Open Function Called...!!!
[19763.176363] Driver Write Function Called...!!!
[19763.176369] Driver Release Function Called...!!!

5. Do cat > /dev/etx_device

Cat command will open the driver, read the driver and close the driver. So if i do cat to our driver, it should call the open, read and release functions. Just check.

[email protected]:/home/driver/driver#  cat /dev/etx_device 

6. Now Check using dmesg

[email protected]:/home/driver/driver$ dmesg
[19763.176347] Driver Open Function Called...!!!
[19763.176363] Driver Read Function Called...!!!
[19763.176369] Driver Release Function Called...!!!

7. Unload the driver using sudo rmmod

Instead of doing echo and cat command in terminal you can also use open(), read(), write(), close() system calls from user space application.

I hope you understood this tutorial. This is just dummy driver tutorial. In our next tutorial we will see the some real time applications using file operations device driver. 🙂

%d bloggers like this: