OSC:Device Drivers
From SOFTICE
|
Device Drivers
- Introduce Kernel APIs:
- Introduce Kernel Data Structures:
- Big picture:
- Implement a char device driver not relying on specific hardware
- Revisit previous problems (e.g. ProcFS lab) and solve them using the /dev/ filesystem and device driver techniques
Synopsis
[Briefing] Character device drivers
Chapter 3 of [ldd3] is documenting a char driver named scull which we are going to use as basis for this laboratory. Your first task is to read this chapter on which we'll base our "Briefing" and "Solved" sections. Once you are done, answer the following sections to assess your understanding of your readings.
- Major and minor device numbers
- What does the major device number traditionally specify? What does the minor device number specify?
- Which datatype holds a combination of a major and minor device number? Which macro combines a major and a minor number into a variable of this datatype?
- How many major and minor device numbers could kernels prior to the 2.6 series address? How many can the 2.6 series (as of 2.6.10) address?
- Filesystem-related data structures
-
struct file_operations- What does a variable of type
struct file_operationsrepresent? - What does a NULL pointer in a field mean?
- What does a variable of type
-
struct file- What does a variable of type
struct filerepresent? - What is the relationship between
struct fileandstruct file_operations?
- What does a variable of type
-
struct inode- What does a variable of type
struct inoderepresent? - What is the relationship between
struct inodeandstruct file?
- What does a variable of type
-
- Char device registration
- Which data structure represents a character device within the kernel?
- If you have this data structure allocated, how do you initialize it?
- When is the device considered 'active' and ready to be used?
- File operations: open, release, read, and write
- When are the functions associated with each of these operations called?
- If there is no function defined for each of these operations, what happens when that operation is performed?
[Solved] RAM buffer device example
/* * RAM Buffer Device * * Written by Benjamin Geiger, SOFTICE Project * Copyright (c) 2006 SOFTICE Project * * Portions copyright (c) 2001 Alessandro Rubini and Jonoathan Corbet * Portions copyright (c) 2001 O'Reilly & Associates * * Portions of this code has been adapted from the book, "Linux Device * Drivers", 3rd edition, by Alessandro Rubi and Jonathan Corbet, published * by O'Reilly & Associates. */ /* Include-file section taken directly from scull source */ #include <linux/config.h> #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/init.h> #include <linux/kernel.h> /* printk() */ #include <linux/slab.h> /* kmalloc() */ #include <linux/fs.h> /* everything... */ #include <linux/errno.h> /* error codes */ #include <linux/types.h> /* size_t */ #include <linux/fcntl.h> /* O_ACCMODE */ #include <linux/seq_file.h> #include <linux/cdev.h> #include <asm/system.h> /* cli(), *_flags */ #include <asm/uaccess.h> /* copy_*_user */
We need two parameters: the size of each buffer and the number of buffers to create.
/************************************************************************ * Parameters ***********************************************************************/ /* size: the number of bytes in each buffer */ static int size = 1024; module_param(size, int, 0); /* numbufs: the number of buffers */ static int numbufs = 1; module_param(numbufs, int, 0);
Each device is associated with a major number and a minor number. The major number is assigned by the kernel development team, though there is a specified range (240 to 254) for temporary use. Our major number is 243.
The minor number, in this case, is something of a misnomer; as the code is actually written, the given minor number is actually the minor number of the first buffer device; subsequent devices have following minor numbers.
/***********************************************************************
* The major device number was chosen randomly (to wit: "1d15 + 239")
* from the "local/experimental" range.
***********************************************************************/
int device_major = 243; /* Major device number - must be between
240 and 254, to avoid conflicts */
int device_minor = 0; /* Minor device number */
MODULE_AUTHOR("Benjamin Geiger, SOFTICE Project");
MODULE_LICENSE("GPL");
The bufdev_t structure holds all the data needed to represent a single buffer.
/***********************************************************************
* Structure representing a single memory buffer device.
***********************************************************************/
typedef struct bufdev_t {
struct semaphore mutex; /* Mutual exclusion semaphore */
char *buffer; /* Data storage */
int buflen; /* The amount of data stored */
int size; /* The maximum size of the buffer */
struct cdev cdev;
} bufdev_t;
/* A list of all memory buffer devices. */
static bufdev_t *buffers = NULL;
When we open a memory buffer device for writing, we empty the device (by analogy with opening a file for writing, which truncates it). We don't implement the 'append' mode.
/**********************************************************************
* bufdev_open: Open a memory buffer device.
*
* Called automatically when the device file is opened for reading.
**********************************************************************/
int
bufdev_open (struct inode *inode, struct file *filp)
{
/* Get the device info struct from the inode. */
bufdev_t *bufdev = container_of(inode->i_cdev, bufdev_t, cdev);
filp->private_data = bufdev; /* so we don't have to keep looking it up */
/* If we're opening it for writing, truncate it to 0 bytes. */
if ( (filp->f_flags & O_ACCMODE) == O_WRONLY ) {
if (down_interruptible(&bufdev->mutex)) {
return -ERESTARTSYS;
}
bufdev->buflen = 0;
up(&bufdev->mutex);
}
/* We can't fail, so return success. */
return 0;
}
We don't need to do anything when a buffer device is released, so our release function does nothing.
/**********************************************************************
* bufdev_release: Close a memory buffer device.
*
* Called automatically when the last reference to the file is released.
**********************************************************************/
int
bufdev_release (struct inode *inode, struct file *filp)
{
/* There's nothing to do. */
return 0;
}
The read function is called whenever the user attempts to retrieve data from the buffer.
/**********************************************************************
* bufdev_read: Retrieve data from the memory buffer.
*
* Called automatically by the read() system call.
*
* Returns the number of bytes read, or -errno on error.
**********************************************************************/
ssize_t
bufdev_read (struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
We store the address of the bufdev_t structure in the private_data member of the filp structure. This saves us the hassle of locating it every time.
struct bufdev_t *bufdev = filp->private_data;
down_interruptible returns nonzero if it returns due to a signal rather than by acquiring the lock. If the process is interrupted, instead of manually handling the signal and attempting again to acquire the lock, we can simply return -ERESTARTSYS, which acts as a message to the VFS layer to either call this function again or inform the user that it was interrupted.
/* If we get interrupted before we get the lock, tell the VFS
to restart the call (or report it to the user). */
if (down_interruptible(&bufdev->mutex)) {
return -ERESTARTSYS;
}
Make sure the user won't be given more data than we have available.
if (*f_pos > bufdev->buflen) {
/* If we're looking past the end, return EOF. */
up(&bufdev->mutex);
return 0;
} else if (*f_pos + count > bufdev->buflen) {
/* If we're asking for more than is available, return
only the part that is available. */
count = bufdev->buflen - *f_pos;
}
We are provided with a pointer to userspace memory, so we must use the copy_to_user function to put information there.
/* Perform the copy into the provided buffer. */
if (copy_to_user(buf, bufdev->buffer + *f_pos, count)) {
/* If it failed, scream and die. */
printk( KERN_WARNING "bufdev: Copy failed!\n");
up(&bufdev->mutex);
return -EFAULT;
}
Let the caller know where the 'current position' is (to be used as the offset for the next read), and return control to the caller.
/* Advance the current position. */ *f_pos += count; /* Release the lock and return. */ up(&bufdev->mutex); return count; }
The write function is called whenever the user attempts to put data into the device.
/**********************************************************************
* bufdev_write: Write data to a memory buffer.
*
* Called automatically by the write() system call.
*
* Returns the number of bytes written, or -errno on error.
**********************************************************************/
ssize_t
bufdev_write (struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
struct bufdev_t *bufdev = filp->private_data;
/* If we get interrupted before we get the lock, tell the VFS
to restart the call (or report it to the user). */
if (down_interruptible(&bufdev->mutex)) {
return -ERESTARTSYS;
}
/* If they ask to write more than is available, write only
what can fit into the buffer. */
if (count > bufdev->size - bufdev->buflen) {
count = bufdev->size - bufdev->buflen;
}
/* Copy data from the user to the provided buffer. */
if (copy_from_user(bufdev->buffer + *f_pos, buf, count)) {
/* If it fails, scream and die. */
printk( KERN_WARNING "bufdev: Copy failed!\n");
up(&bufdev->mutex);
return -EFAULT;
}
/* Advance the current position. */
*f_pos += count;
/* Update the current size of the buffer. */
if (bufdev->buflen < *f_pos) {
bufdev->buflen = *f_pos;
}
/* Release the lock and return. */
up(&bufdev->mutex);
return count;
}
/* File operation lookup table. */
struct file_operations bufdev_fops = {
.owner = THIS_MODULE,
.read = bufdev_read,
.write = bufdev_write,
.open = bufdev_open,
.release = bufdev_release,
};
As in previous modules, the init function is called when the module is loaded. It sets up necessary data structures and bindings.
/**********************************************************************
* init_module: Load the module.
**********************************************************************/
int
init_module (void)
{
dev_t devid = 0;
int result;
int i;
int err;
/* Register the device file. */
devid = MKDEV(device_major, device_minor);
result = register_chrdev_region(devid, numbufs, "bufdev");
if (result < 0) {
printk(KERN_WARNING "bufdev: couldn't acquire major %d\n", device_major);
return result;
}
/* Allocate the buffer structures. */
buffers = kmalloc(numbufs * sizeof(bufdev_t), GFP_KERNEL);
if (buffers == NULL) {
cleanup_module();
return -ENOMEM;
}
memset(buffers, 0, numbufs * sizeof(bufdev_t));
/* Initialize the buffer structures. */
for (i = 0; i < numbufs; i++) {
init_MUTEX(&buffers[i].mutex);
cdev_init(&buffers[i].cdev, &bufdev_fops);
buffers[i].cdev.owner = THIS_MODULE;
buffers[i].cdev.ops = &bufdev_fops;
err = cdev_add(&buffers[i].cdev, MKDEV(device_major, device_minor + i), 1);
if (err) {
printk(KERN_WARNING "bufdev: Error %d adding bufdev%d", err, i);
}
buffers[i].buffer = kmalloc(size, GFP_KERNEL);
if (buffers[i].buffer == NULL) {
cleanup_module();
return -ENOMEM;
}
buffers[i].size = size;
}
return 0; /* Success. */
}
The cleanup function removes the module from the kernel. It is also called in the case of a loading failure, so it must be capable of handling a situation where not everything has been loaded and allocated.
/**********************************************************************
* cleanup_module: Unload the module.
**********************************************************************/
void
cleanup_module(void)
{
int i;
/* Deallocate the buffers if they exist. */
if (buffers) {
for (i = 0; i < numbufs; i++) {
if (buffers[i].buffer) {
kfree(buffers[i].buffer);
buffers[i].buffer = NULL;
}
cdev_del(&buffers[i].cdev);
}
kfree(buffers);
buffers = NULL;
}
/* Unregister the device files. */
unregister_chrdev_region(MKDEV(device_major, device_minor), numbufs);
}
[Exercises]
Exercise 1: Readers/Writers
Change the bufdev device to use a read/write semaphore to control access to the memory buffer. As in project 2 in the Synchronization lab, introduce delays in the read and write functions so that blocking will be evident when it happens.
As usual, you will validate your LKM code with userland programs. You can use the same tools you used in the Synchronization lab.
[Projects]
Project 1: Producer / Consumer
Create a device that maintains a circular buffer of characters, similar to a Unix named pipe. Use the three-semaphore producer/consumer mechanism to control access to the device.
The read and write functions should read or write a single byte at a time. (This is inefficient, but for the sake of education it's tolerable.)
You will test your LKM with two user-land programs; a producer and consumer accessing the data.
References
[LDD3] Linux Device Drivers 3E Safary Reference
- Corbet, Rubini, Kroah-Hartman, 3rd Edition, 2005/02
- http://www.oreilly.com/catalog/linuxdrive3/
- Chapter(s): 3 (char drivers), 6 (advanced char driver operations)
[LKMPG] Linux Kernel Module Programmer's Guide
- Salzman, Burian, Pomerantz, 2005/05/26 version 2.6.1
- http://www.tldp.org/LDP/lkmpg/2.6/lkmpg.pdf
- Chapter(s): #04 Character Device Files
References (Textbooks)
[NUTT] Operating Systems, 3/e
- Gary Nutt, Addison Wesley, ISBN 0-210-77344-9
- http://www.cs.colorado.edu/~nutt/osamp.html
- Chapter(s): #5 (Device Management)
[STALL] Operating Systems, Internals and Design Principles
- William Stallings, Prentice Hall, ISBN 0-13-1479-54-7
- http://williamstallings.com/
- Chapter(s): #5 (Concurrency)
[SILB] Operating System Concepts with Java
- http://os-book.com/
- Abraham Silberschatz, Peter Baer Galvin, Greg Gagne
- Wiley, ISBN: 978-0-471-76907-1
- Chapter(s): #6 (Processes Synchronization)
[DEIT] Operating Systems
- Deitel, ISBN: 0131828274
- Chapter(s): #5 (Asynchronous Concurrent Execution)

