innercoder.com

A blog for Linux programming enthusiasts

Linux Kernel Modules: Character Devices

| Comments

This post will be part of a series where device drivers will be presented and quickly analized for people that need a quick reference of how to make device drivers on an updated kernel. The driver presented here was tested on a 3.2 and 3.16 Linux Kernel. I will make a driver with more features and test it on a newer kernel next time.

Note: some space formatting is lost when processed by octopress so for a more cleanly looking code, I recommend github.

The complete code is in its github repository.

The purpose of this particular char driver is to display “Hello World” a certain amount of time when pass a parameter, if not passed, it will still do it for a default amount. Other features it has are, dynamic major number allocation and dynamic node creation. Once the device is initialized, it allocates a buffer to store and read a string from userspace.

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
/*
 *       Filename:  chdx.c
 *    Description:  A character driver that displays "Hello World!".
 *         Author:  Jaime Arrocha 
 */

#include <linux/module.h>      /* for modules          */
#include <linux/init.h>            /* module_init, module_exit */
#include <linux/moduleparam.h>     /* module parameters        */
#include <linux/fs.h>          /* file operations      */
#include <linux/slab.h>            /* kernel memory allocation */
#include <linux/cdev.h>            /* device registration      */
#include <linux/device.h>      /* udev class creation      */
#include <asm/uaccess.h>       /* copy_to/from_user        */
#define MOD_NAME "chdx"

/*
 * global variables
 */
char *kbuf;              /* kernel buffer for module    */
static dev_t first;          /* device registration numbers */
static struct device *chdx_dev;
static struct cdev *chdx_cdev;      /* char device structure   */
static struct class *chdx_cl;       /* device class            */
int qty = 0;             /* paramenter variable     */
static unsigned int count = 1;
static size_t kbuf_size = 1024;

/*
 * function prototypes 
 */
static int chdx_open(struct inode *, struct file *);
static int chdx_close(struct inode *, struct file *);
static ssize_t chdx_read(struct file *, char __user *, size_t, loff_t *);
static ssize_t chdx_write(struct file *, const char __user *, size_t, loff_t *);

/* 
 * module paramenter prototype
 */
module_param(qty, int, S_IRUGO|S_IWUSR);

/*
 * file operations structure initialization
 */
struct file_operations chdx_fops = {
  .owner = THIS_MODULE,
  .open = chdx_open,
  .release = chdx_close,
  .read = chdx_read,
  .write = chdx_write,
};

Headers, global varaibles, and prototypes. Several comments are inplace to explain the different sections.

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
static int __init chdx_init(void)
{
  int ret_val = -1;
  int x;

  /* dynamic allocation of major number */
  if((ret_val = alloc_chrdev_region(&first, 0, count, MOD_NAME)) < 0) {
      dev_err(chdx_dev,"Registration error");
      return ret_val;
  }

  /* device registration */
  if(!(chdx_cdev = cdev_alloc())) {
      dev_err(chdx_dev, "cdev_alloc() failed");
      goto unreg1;
  }

  cdev_init(chdx_cdev, &chdx_fops);

  /* dynamic allocation of kernel buffer */   
  if(!(kbuf = kmalloc(kbuf_size, GFP_KERNEL)))
      goto unreg2;

  if((cdev_add(chdx_cdev, first, count)) < 0) {
      dev_err(chdx_dev, "cdev_add() failed");
      goto unreg3;
  }
  
      
  if(!(chdx_cl = class_create(THIS_MODULE, MOD_NAME)))
      goto unreg3;
  if((chdx_dev = device_create(chdx_cl, NULL, first, NULL, "%s",
                   MOD_NAME)) == NULL)
      goto unreg4;

  /* module registration test */  
  if (qty == 0)
      qty = 10;  
  for(x = 0; x < qty; x++)
      dev_info(chdx_dev, "Hello World!");
  dev_info(chdx_dev, "Major: %d, Minor: %d", MAJOR(first), MINOR(first));

  return 0;
  
  unreg4: device_destroy(chdx_cl, first);
  unreg3: kfree(kbuf);
  unreg2: cdev_del(chdx_cdev);
  unreg1: unregister_chrdev_region(first, count);
  return ret_val;
}

The init function. Each section is commented for its purpose done. Goto statements are used for easy error exits. It makes the code more readable in these cases. Extra attention is needed to the order of initialization of the different registration hearders. We should only call cdev_add() only when everything is in place in regards to the character driver major and minor numbers and its file operation structure. After that, we call class_create() and device_create(), to have udev() perform mknod() for us.

1
2
3
4
5
6
7
8
9
static void __exit chdx_exit(void)
{
  device_destroy(chdx_cl, first);
  class_destroy(chdx_cl);
  kfree(kbuf);
  cdev_del(chdx_cdev);
  unregister_chrdev_region(first, count);
  dev_info(chdx_dev, "chdx unloaded");
}

The exit function. Takes care of everything once the driver is unloaded. Without this function, the driver will not unload. The order on which functions are called is very important.

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
static ssize_t chdx_read(struct file *fl, char __user *ubuf, size_t ubuf_len,
                         loff_t *off)
{
        int rbytes, maxbytes, bytes_to_rd;

        maxbytes = kbuf_size - *off;
        bytes_to_rd = maxbytes > ubuf_len? ubuf_len : maxbytes;
        if (bytes_to_rd == 0)
                dev_info(chdx_dev,"End of buffer");
        rbytes = bytes_to_rd - copy_to_user(ubuf, kbuf + *off, bytes_to_rd);
        *off += rbytes;
        dev_info(chdx_dev, "%d bytes read\n", rbytes);

        return rbytes;
}


static ssize_t chdx_write(struct file *fl, const char __user *ubuf,
                          size_t ubuf_len, loff_t *off)
{
        int wbytes, maxbytes, bytes_to_wr;

        maxbytes = kbuf_size - *off;
        bytes_to_wr = maxbytes > ubuf_len ? ubuf_len : maxbytes;
        if (bytes_to_wr == 0)
                dev_info(chdx_dev, "Reached end of the device on a write");
        wbytes = bytes_to_wr - copy_from_user(kbuf + *off, ubuf, bytes_to_wr);
        *off += wbytes;
        dev_info(chdx_dev, "%d bytes written\n", wbytes);

        return wbytes;
}

The read and write function, they makes sure that userspace does not read or write beyond the allocated space in kernel. Special functions are used to handle pointers received from userspace. These pointers need to be check to be safe to use by the kernel. If not, undefined behavior with memory references will likely happen.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[jaime@LinuxServer kmod]$ make
make -C /lib/modules/3.2.0-4-686-pae/build  M=/home/jaime/kmod modules;
make[1]: Entering directory `/usr/src/linux-headers-3.2.0-4-686-pae'
  CC [M]  /home/jaime/kmod/chdx.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /home/jaime/kmod/chdx.mod.o
  LD [M]  /home/jaime/kmod/chdx.ko
make[1]: Leaving directory `/usr/src/linux-headers-3.2.0-4-686-pae'
[jaime@LinuxServer kmod]$ sudo insmod chdx.ko qty=5
[sudo] password for jaime:
[jaime@LinuxServer kmod]$ dmesg | tail
[  580.006310] chdx chdx: Hello World!
[  580.006312] chdx chdx: Hello World!
[  580.006313] chdx chdx: Hello World!
[  580.006314] chdx chdx: Hello World!
[  580.006314] chdx chdx: Hello World!
[  580.006315] chdx chdx: Major: 251, Minor: 0
[jaime@LinuxServer kmod]$ sudo chmod 666 /dev/chdx
[jaime@LinuxServer kmod]$ ls -l /dev/chdx
crw-rw-rw- 1 root root 251, 0 Jul  3 13:07 /dev/chdx
[jaime@LinuxServer kmod]$ echo "Linux Kernel" > /dev/chdx
[jaime@LinuxServer kmod]$ cat /dev/chdx
Linux Kernel

A short demonstration of the driver.

More to come soon…

Comments