Device Drivers, Part 5: Character Device Files — Creation & Operations

Device Drivers, Part 5: Character Device Files — Creation & Operations

Device drivers lessons time

This article is a continuation of the  series on Linux device drivers, and carries on the discussion on character drivers and their implementation.

In my previous article, I had mentioned that even with the registration for the <major, minor>device range, the device files were not created under /dev — instead, Shweta had to create them manually, using mknod. However, on further study, Shweta figured out a way to automatically create the device files, using the udev daemon. She also learnt the second step to connect the device file with the device driver — linking the device file operations to the device driver functions. Here is what she learnt.

Automatic creation of device files

Earlier, in kernel 2.4, the automatic creation of device files was done by the kernel itself, by calling the appropriate APIs of devfs. However, as the kernel evolved, kernel developers realised that device files were more related to user-space and hence, as a policy, that is where they ought to be dealt with, not at the kernel. Based on this idea, the kernel now only populates the appropriate device class and device information into the /sys window, for the device under consideration. User-space then needs to interpret it and take 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 via its configuration files to tune the device file names, their permissions, their types, etc. So, as far as the driver is concerned, the appropriate /sys entries need to be populated using the Linux device model APIs declared in <linux/device.h>. The rest should be handled by udev. The device class is created as follows:

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

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

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

Here, the first is 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 1 for the /sys entries created using chardrv as the <device class name> andmynull as the <device name format>. That also shows the device file, created by udev, based on the <major>:<minor> entry in the dev file.

Automatic device file creation

Figure 1: Automatic device file creation

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

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

File operations

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

Now, for VFS to pass the device file operations onto the driver, it should have been informed about it. And yes, that is what is called registering the file operations by the driver with the VFS. This involves two steps. (The parenthesised code refers to the “null driver” code below.)

First, let’s fill in a file operations structure (struct file_operations pugs_fops) with the desired file operations (my_openmy_closemy_readmy_write, …) and initialise the character device structure (struct cdev c_dev) with that, using cdev_init().

Then, hand this structure to the VFS using the call cdev_add(). Both cdev_init() andcdev_add() are declared in <linux/cdev.h>. Obviously, the actual file operations (my_open,my_closemy_readmy_write) also had to be coded.

So, to start with, let’s keep them as simple as possible — let’s say, as easy as the “null driver”.

The null driver

Following these steps, Shweta put the pieces together, attempting her first character device driver. Let’s see what the outcome was. Here’s the complete code — ofcd.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#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 */
{
   printk(KERN_INFO "Namaskar: ofcd registered" );
   if (alloc_chrdev_region(&first, 0, 1, "Shweta" ) < 0)
   {
     return -1;
   }
     if ((cl = class_create(THIS_MODULE, "chardrv" )) == NULL)
   {
     unregister_chrdev_region(first, 1);
     return -1;
   }
     if (device_create(cl, NULL, first, NULL, "mynull" ) == NULL)
   {
     class_destroy(cl);
     unregister_chrdev_region(first, 1);
     return -1;
   }
     cdev_init(&c_dev, &pugs_fops);
     if (cdev_add(&c_dev, first, 1) == -1)
   {
     device_destroy(cl, first);
     class_destroy(cl);
     unregister_chrdev_region(first, 1);
     return -1;
   }
   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_at_sarika-pugs_dot_com>" );
MODULE_DESCRIPTION( "Our First Character Driver" );

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

  1. Build the driver (.ko file) by running make.
  2. Load the driver using insmod.
  3. List the loaded modules using lsmod.
  4. List the major number allocated, using cat /proc/devices.
  5. “null driver”-specific experiments (refer to Figure 2 for details).
  6. Unload the driver using rmmod.
'null driver' experiments

Figure 2: 'null driver' experiments

Summing up

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

However, one thing began to bother Shweta. She had got her own calls (my_openmy_close,my_readmy_write) in her driver, but wondered why they worked so unusually, unlike any regular file system calls. What was unusual? Whatever was written, she got nothing when reading — unusual, at least from the regular file operations’ perspective. How would she crack this problem? Watch out for the next article.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值