Friday, May 3, 2013

A Walk-through of A Character Device Driver

This article explains how to compile a sample Linux character device driver and how to read and write from the device.

A Sample Character Device Driver

The sample code is a copy of ldd3's example code sleepy.

/*
 * sleepy.c -- the writers awake the readers
 *
 * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet
 * Copyright (C) 2001 O'Reilly & Associates
 *
 * The source code in this file can be freely used, adapted,
 * and redistributed in source or binary form, so long as an
 * acknowledgment appears in derived source files.  The citation
 * should list that the code comes from the book "Linux Device
 * Drivers" by Alessandro Rubini and Jonathan Corbet, published
 * by O'Reilly & Associates.   No warranty is attached;
 * we cannot take responsibility for errors or fitness for use.
 *
 * $Id: sleepy.c,v 1.7 2004/09/26 07:02:43 gregkh Exp $
 */

#include <linux/module.h>
#include <linux/init.h>

#include <linux/sched.h>  /* current and everything */
#include <linux/kernel.h> /* printk() */
#include <linux/fs.h>     /* everything... */
#include <linux/types.h>  /* size_t */
#include <linux/wait.h>

MODULE_LICENSE("Dual BSD/GPL");

static int sleepy_major = 0;

static DECLARE_WAIT_QUEUE_HEAD(wq);
static int flag = 0;

ssize_t sleepy_read (struct file *filp, char __user *buf, size_t count, loff_t *pos)
{
 printk(KERN_DEBUG "process %i (%s) going to sleep\n",
   current->pid, current->comm);
 wait_event_interruptible(wq, flag != 0);
 flag = 0;
 printk(KERN_DEBUG "awoken %i (%s)\n", current->pid, current->comm);
 return 0; /* EOF */
}

ssize_t sleepy_write (struct file *filp, const char __user *buf, size_t count,
  loff_t *pos)
{
 printk(KERN_DEBUG "process %i (%s) awakening the readers...\n",
   current->pid, current->comm);
 flag = 1;
 wake_up_interruptible(&wq);
 return count; /* succeed, to avoid retrial */
}


struct file_operations sleepy_fops = {
 .owner = THIS_MODULE,
 .read =  sleepy_read,
 .write = sleepy_write,
};


int sleepy_init(void)
{
 int result;

 /*
  * Register your major, and accept a dynamic number
  */
 result = register_chrdev(sleepy_major, "sleepy", &sleepy_fops);
 if (result < 0)
  return result;
 if (sleepy_major == 0)
  sleepy_major = result; /* dynamic */
 return 0;
}

void sleepy_cleanup(void)
{
 unregister_chrdev(sleepy_major, "sleepy");
}

module_init(sleepy_init);
module_exit(sleepy_cleanup);

Makefile

obj-m = sleepy.o
KVERSION = $(shell uname -r)
PWD = $(shell pwd)
all:
	make -C /lib/modules/$(KVERSION)/build M=$(PWD) modules
clean:
	make -C /lib/modules/$(KVERSION)/build M=$(PWD) clean

Compile The Driver

The ultimate way is to compile it along with a built kernel tree. However, for the time being, installing Linux kernel headers would suffice. It should already be installed by default.

sudo apt-get install linux-headers-$(uname -r)

If installed, type

make

Load and Remove The Driver

You will see some new files are generated, including sleepy.ko, to load this module

sudo insmod sleepy.ko

To unload,

sudo rmmod sleepy

Read from and write to the device

The sleepy is created with a dynamic major, we need to create a device file for it. First get the major number,

$ cat /proc/devices | grep sleepy
250 sleepy

Then create a node under /dev, c is to create a character file, 0 is its minor number.

sudo mknod /dev/sleepy c 250 0

Now we can read and write the device using C library function read, write, or from shell

echo "sleep" > /dev/sleepy
cat /dev/sleepy

Though what sleepy does is that it makes the reader process fall asleep while the writer process wakes all readers up.

#include <fcntl.h>
#include <unistd.h>

int fd;
char buffer[32];

fd = open("/dev/sleepy", O_RDONLY);
read(fd,buffer,10);

No comments:

Post a Comment