Character device files: Creation & Operations

This fifth article, which is part of the series on Linux device drivers, is continuation of the various concepts of character drivers and their implementation, dealt with in the previous article.

<< Fourth Article

In our previous article, we noted that even with the registration for <major, minor> device range, the device files were not created under the /dev, rather Shweta had to create them by hand using mknod. However, on further study, Shweta figured out a way for the automatic creation of the device files using the udev daemon. She also learnt the second step for connecting the device file with the device driver – “Linking the device file operations to the device driver functions”. Here are her learnings.

Automatic creation of device files

Earlier in kernel 2.4, automatic creation of device files was done by the kernel itself, by calling the appropriate APIs of devfs. However, as kernel evolved, kernel developers realized that device files are more of a user space thing and hence as a policy only the users should deal with it, not the kernel. With this idea, now kernel only populates the appropriate device class & device info into the /sys window for the device under consideration. And then, the user space need to interpret it and take an appropriate action. In most Linux desktop systems, the udev daemon picks up that information and accordingly creates the device files.

udev can be further configured using its configuration files to tune the device file names, their permissions, their types, etc. So, as far as driver is concerned, the appropriate /sys entries need to be populated using the Linux device model APIs declared in <linux/device.h> and the rest would be handled by udev. Device class is created as follows:

struct class *cl = class_create(THIS_MODULE, "<device class name>");

and then the device info (<major, minor>) under this class is populated by:

device_create(cl, NULL, first, NULL, "<device name format>", …);

where first is the dev_t with the corresponding <major, minor>.

The corresponding complementary or the inverse calls, which should be called in chronologically reverse order, are as follows:

device_destroy(cl, first);
class_destroy(cl);

Refer to Figure 9, for the /sys entries created using “chardrv” as the <device class name> and “mynull” as the <device name format>. That also shows the device file, created by udev, based on the <major>:<minor> entry in the dev file.

Figure 9: Automatic device file creation

Figure 9: Automatic device file creation

In case of multiple minors, device_create() and device_destroy() APIs may be put in for-loop, and the <device name format> string could be useful. For example, the device_create() call in a for-loop indexed by ‘i‘ could be as follows:

device_create(cl, NULL, MKDEV(MAJOR(first), MINOR(first) + i), NULL, "mynull%d", i);

File operations

Whatever system calls or more commonly file operations we talk of over a regular file, are applicable to the device files as well. That’s what we say a file is a file, and in Linux almost everything is a file from user space perspective. The difference lies in the kernel space, where virtual file system (VFS) decodes the file type and transfers the file operations to the appropriate channel, like file system module in case of a regular file or directory, corresponding device driver in case of a device file. Our discussion of interest is the second case.

Now, for VFS to pass the device file operations onto the driver, it should have been told about that. And yes, that is what is called registering the file operations by the driver with the VFS. This involves two steps. (The parenthesised text below refers to the ‘null driver’ code following it.) First, is to fill in a file operations structure (struct file_operations pugs_fops) with the desired file operations (my_open, my_close, my_read, my_write, …) and to initialize the character device structure (struct cdev c_dev) with that, using cdev_init(). The second step is to hand this structure to the VFS using the call cdev_add(). Both cdev_init() and cdev_add() are declared in <linux/cdev.h>. Obviously, the actual file operations (my_open, my_close, my_read, my_write) also had to be coded by Shweta. So, to start with, Shweta kept them as simple as possible, so as to say, as easy as the “null driver”.

The null driver

Following these steps, Shweta put all the pieces together to attempt her first character device driver. Let’s see what was the outcome. Here’s the complete code:

#include <linux/module.h>
#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/cdev.h>

static dev_t first; // Global variable for the first device number
static struct cdev c_dev; // Global variable for the character device structure
static struct class *cl; // Global variable for the device class

static int my_open(struct inode *i, struct file *f)
{
	printk(KERN_INFO "Driver: open()\n");
	return 0;
}
static int my_close(struct inode *i, struct file *f)
{
	printk(KERN_INFO "Driver: close()\n");
	return 0;
}
static ssize_t my_read(struct file *f, char __user *buf, size_t len, loff_t *off)
{
	printk(KERN_INFO "Driver: read()\n");
	return 0;
}
static ssize_t my_write(struct file *f, const char __user *buf, size_t len,
	loff_t *off)
{
	printk(KERN_INFO "Driver: write()\n");
	return len;
}

static struct file_operations pugs_fops =
{
	.owner = THIS_MODULE,
	.open = my_open,
	.release = my_close,
	.read = my_read,
	.write = my_write
};

static int __init ofcd_init(void) /* Constructor */
{
	int ret;
	struct device *dev_ret;

	printk(KERN_INFO "Namaskar: ofcd registered");
	if ((ret = alloc_chrdev_region(&first, 0, 1, "Shweta")) < 0)
	{
		return ret;
	}
	if (IS_ERR(cl = class_create(THIS_MODULE, "chardrv")))
	{
		unregister_chrdev_region(first, 1);
		return PTR_ERR(cl);
	}
	if (IS_ERR(dev_ret = device_create(cl, NULL, first, NULL, "mynull")))
	{
		class_destroy(cl);
		unregister_chrdev_region(first, 1);
		return PTR_ERR(dev_ret);
	}

	cdev_init(&c_dev, &pugs_fops);
	if ((ret = cdev_add(&c_dev, first, 1)) < 0)
	{
		device_destroy(cl, first);
		class_destroy(cl);
		unregister_chrdev_region(first, 1);
		return ret;
	}
	return 0;
}

static void __exit ofcd_exit(void) /* Destructor */
{
	cdev_del(&c_dev);
	device_destroy(cl, first);
	class_destroy(cl);
	unregister_chrdev_region(first, 1);
	printk(KERN_INFO "Alvida: ofcd unregistered");
}

module_init(ofcd_init);
module_exit(ofcd_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Anil Kumar Pugalia <email@sarika-pugs.com>");
MODULE_DESCRIPTION("Our First Character Driver");

Then, Shweta repeated the usual build with new test steps as follows:

  • Build the driver (.ko file) by running make.
  • Load the driver using insmod.
  • List the loaded modules using lsmod.
  • List the major number allocated using cat /proc/devices.
  • “null driver” specific experiments (Refer to Figure 10 for details).
  • Unload the driver using rmmod.
Figure 10: “null driver” experiments

Figure 10: “null driver” experiments

Summing up

Shweta was surely happy as all on her own she got a character driver written, which works same as the driver for the standard device file /dev/null. To understand what it means, check for yourself the <major, minor> tuple for /dev/null, and similarly also try out the echo and cat commands with it.

But one thing started bothering Shweta. She had got her own calls (my_open, my_close, my_read, my_write) in her driver, but how are they working so unusually unlike any regular file system calls. What’s so unusual? Whatever I write, I get nothing when read – isn’t that unusual, at least from regular file operations’ perspective. Any guesses on how would she crack this nut? Watch out for the next article.

Sixth Article >>

Notes:

  1. For using a fixed major number, you may use register_chrdev_region() instead of alloc_chrdev_region().
  2. Use kernel version >= 2.6.3x for the class_create() and the device_create() APIs to compile properly work as explained. As, before that version they have been rapidly evolving and changing.
  3. Kernel APIs (like class_create(), device_create()) which returns pointers, should be checked using IS_ERR macro instead of comparing with NULL, as NULL is zero (i.e. success and not an error). These APIs return negative pointers on error – error code from which could be extracted using PTR_ERR. See the usage in the above example.

Other References:

  1. Working of udev daemon
Anil Kumar Pugalia Anil Kumar Pugalia (115 Posts)

The author is a hobbyist in open source hardware and software, with a passion for mathematics, and philosopher in thoughts. A gold medallist from the Indian Institute of Science, Linux, mathematics and knowledge sharing are few of his passions. He experiments with Linux and embedded systems to share his learnings through his weekend workshops. Learn more about him and his experiments at https://sysplay.in.


   Send article as PDF   

29 thoughts on “Character device files: Creation & Operations

  1. Pingback: Linux Character Drivers | Playing with Systems

  2. Ghaith B.

    Thank you very much Mr.Anil Kumar! I followed through this great tutorial and now have a very clear understanding of character device drivers. Thanks again!

    Reply
  3. Pingback: Decoding the character device file operations | Playing with Systems

  4. Santosh Yadav

    Sir, my query seems to be quite silly but i am very confused with this point.

    i have read some articles by still have doubt
    as per my understanding in the above code of (my_open.my_read,my_write,my_close) we have not done any initialization for the function parameters like *inode, *buf ,*off ,*f etc
    i wanted to know weather the parameter initialization is done by VFS or our device file
    as no where we have passed any parameters for these file operation.

    Thanks – SANTOSH

    Reply
  5. Pingback: Zenz Answers

  6. Gopal

    hai ,i have doubt that why u did ” class_create(), device_create() ” in driver code…

    this code is driver code r device code(what is the need of device_create api & why we r opening that /dev/mychar in application)

    can u explain about it clearly..

    Reply
    1. Anil Kumar PugaliaAnil Kumar Pugalia Post author

      With the APIs mentioned by you, we are just creating entries in the /sys kernel window corresponding to the drivers acquired major-minor range. The information from the entries would then be picked up by the udev daemon in the user space, for it to create the corresponding device file(s) namely the /dev/mynull in the article above, so that the application can talk to the driver through this. For more clarity, read its previous set of articles as well, by clicking on “Previous” at the beginning of this article.

      Reply
  7. Aarti Kashyap

    driver: module verification failed: signature and/or required key missing – tainting kernel

    I’m getting this message in the log when I enter dmesg.How do I resolve it?

    Reply
    1. Anil Kumar PugaliaAnil Kumar Pugalia Post author

      Which distro are you using? Possibly, the kernel configuration used to build your driver is set for module signature verification (CONFIG_MODULE_SIG) and is not set to automatically sign all modules (CONFIG_MODULE_SIG_ALL)

      Reply
  8. Sandip Dey

    Hi Anil

    Thank you very much for putting all those profound technical details in such a lucid way in sysplay.in, on linux device driver.

    Regards
    Sandip

    Reply
  9. P JAGADISHWAR

    facing problem in atomic creation
    unable to populate things in /sys/class
    how to debug???
    used 3 API –> class_create(THIS_MODULE,”char_dev”)
    –> device_create(cl,NULL,first,NULL,”mynull”)

    Reply
  10. Narreddy Siva Prakash Reddy

    Hello Sir,
    I am using NXP’s MPC8323E-RDB development board and using LTIB for Linux kernel and rootfs building. I added external Hardware having Exar’s 4-channel UART. I accessed UART 4 channels successfully with small application by mapping their address. Now I am trying to write driver for my uart device for data transactions. Even though I am using device_create() and class_create() as per this blog, I am not getting /dev/. I need to do the steps mentioned in previous blog like “mknod /dev/ c major minor”. Why it is behaving like this.?

    I am using 2.6.20-rt8 kernel.

    Please, reply as soon as possible.

    Thanks and Regards,
    Siva Prakash Reddy Narreddy

    Reply
      1. Narreddy Siva Prakash Reddy

        Sorry, I didn’t look at reply. No, Udev package is not selected while kernel building. Now, I selected udev package in kernel configuration and built it. It is failed because of lack of supportive packages like “Hotplug”. let me check by installing supportive packages too.

        Reply
  11. Igal

    Hello, Sir,

    I have implemented a driver per you great tutorial, but although the /sys/class.. is populated. The device does not appear under the /dev. I have created the device with device_create_with_groups.

    What do you think might be the problem?

    Reply
  12. Perumalla Deepak

    Hello Sir,

    Great article on device files creation and operations!

    I have a query regarding the device_create() function. To my understanding, device_create() creates a device entry with specified device name under /sys/class/ directory and triggers a uevent due to which Udev creates a device node under /dev directory.
    But what if we intend to create a device node only when a specified hardware is plugged-in ?
    Just a thought – What if we restrict device_create() only to device entry creation under /sys but not a device node creation and edit the udev rules such that whenever the corresponding hardware is plugged-in – device node is created with same name as entry under /sys directory ? Is this possible ? If yes, how can we achieve it?

    Thank you.

    Regards,
    Deepak

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *