Linux Kernel & Device Driver Programming

Ch 3 - Char Drivers

 

scull pseudo-device

See scull module code on line.


Major and Minor Device Numbers


Some examples of the "ls" output for special inodes.

crw-------   1 root root     4,   2 Mar  9 14:13 tty2
crw-------   1 root root     4,   6 Mar  9 14:13 tty6
crw-------   1 root root    10,  10 Feb 23  2004 adbmouse
crw-r--r--   1 root root    10, 175 Feb 23  2004 agpgart
crw-------   1 root root    10, 134 Feb 23  2004 apm_bios
crw-------   1 root root    14,   4 Feb 23  2004 audio
crw-------   1 root root    14,  20 Feb 23  2004 audio1
crw-------   1 root root    14,   7 Feb 23  2004 audioctl
brw-rw----   1 root floppy   2,   0 Feb 23  2004 fd0
brw-rw----   1 root floppy   2,   1 Feb 23  2004 fd1
brw-rw----   1 root disk     3,   0 Feb 23  2004 hda
brw-rw----   1 root disk     3,   1 Feb 23  2004 hda1
brw-rw----   1 root disk     3,  10 Feb 23  2004 hda10

The "c" indicates a char device and the "b" indicates a block device.


Internal Representation of Device Numbers

See example of usage in scull_init_module.


Allocation of Device Numbers

int register_chrdev_region(dev_t first, unsigned int count, char *name);
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);
void unregister_chrdev_region(dev_t first, unsigned int count);

The above create /proc/devices/ entries, but do not create the device nodes in the filesystem. That must be done separately, and is not the responsibility of the driver.

Example content of /proc/devices:

Character devices:
  1 mem
  4 /dev/vc/0
  4 tty
  4 ttyS
  5 /dev/tty
  5 /dev/console
  5 /dev/ptmx
  7 vcs
 10 misc
 13 input
 29 fb
 36 netlink
128 ptm
136 pts
180 usb

Block devices:
  1 ramdisk
  3 ide0
  7 loop
  9 md
 22 ide1
253 device-mapper
254 mdp

After module initialization, dynamically assigned major device number can be determined from this pseudo-file, and used to create device nodes in the filesystem.

file_operations Structure

struct file_operations {
	struct module * owner;
	loff_t (*lseek) (struct file *, loff_t, int);
        ...
	ssize_t (*read) (struct file *, char *, size_t, loff_t *);
        ...
	ssize_t (*write) (struct file *, const char *, size_t, loff_t *);
        ...
}

Some of the "Method" Fields:


Initializing a file_operations Structure

struct file_operations scull_fops = {
    .owner =    THIS_MODULE,
    .llseek =   scull_llseek,
    .read =     scull_read,
    .write =    scull_write,
    .ioctl =    scull_ioctl,
    .open =     scull_open,
    .release =  scull_release,
};

See the above example of use in context in the scull module.

We need one of the above for each minor type of device.

The above code uses a gcc-specific langauge extension to initialize a few fields of a structure, and get default values (zero) for the rest, without knowing the positions of the named fields or what other fields may be present. This allows code to be robust against reordering and addition of fields irrelevant to the current driver.


file Structure

struct file {
	struct list_head	f_list;
	struct dentry		*f_dentry;
	struct vfsmount         *f_vfsmnt;
	struct file_operations	*f_op;
	atomic_t		f_count;
	unsigned int 		f_flags;
	mode_t			f_mode;
	int			f_error;
	loff_t			f_pos;
	struct fown_struct	f_owner;
	unsigned int		f_uid, f_gid;
	struct file_ra_state	f_ra;
        size_t                  f_maxcount;
	unsigned long		f_version;
        void                    *f_security;
	/* needed for tty driver, and maybe others */
	void			*private_data;
        ...
        struct address_space    *f_mapping;
}

Some Important Fields


inode Structure

inode {
...
       dev_t                   i_rdev;
...
       struct cdev             *i_cdev;
};

Char Device Registration

See code of scull_init_module for how driver registration and initialization of the scull_dev and cdev structures is done. Steps include:


Note that the older form of registration, using register_chrdev is still used by many drivers, but is now deprecated.

The call to kobj_map in cdev_add apparently is responsible for registering the new driver binding with the implementation of the sysfs virtual filesystem. This is undone later by cdev_dell which calls kobj_unmap.


Unregistration

See code of scull_cleanup_module for how driver unregistration and other finalization is done.


Open Method

See code of scull_open for example.


container_of Macro

Observe use of container_of macro, defined in kernel.h, to obtain a pointer to the struct scull_dev that contains the struct cdev referred to by inode->i_cdev.

Also observe use of kernel semaphore operations down_interruptible and up to protect a critical section.


Release Method

See code of scull_release for a trivial example. Nothing needs to be done, because there is no hardware to shut down. More interesting examples will come up later.


Note there are two different kinds of "last close" here:

  1. of an open file descriptor (Linux file object)
  2. of a device

The kernel does not call the close method of the file object for every application-level close() call. It only calls for the "last close", i.e., when the reference count of the file object (f_count) goes to zero. That is the job of the kernel.

The job of the driver is to do the analogous thing for its own per-driver data structures, using device driver usage count.


Memory Usage

struct scull_qset {
   void **data;              /* pointer to array of pointers to quanta */
   struct scull_qset *next;  /* pointer to next quantum set */
};
struct scull_dev {
   struct scull_qset *data;  /* pointer to first quantum set */
   int quantum;              /* the current quantum size */
   int qset;                 /* the current array size */
   unsigned long size;
   unsigned int access_key;  /* used by sculluid and scullpriv */
   struct semaphore sem;     /* mutual exclusion semaphore     */
   struct cdev cdev;         /* char device structure */
} scull_dev;

This is not necessarily a data structure that anyone should emulate. The main point of this chapter is how to implement read and write. This structure is just an artifact of the example, not a feature of the Linux kernel. As the book says, a more efficient design would allocate memory in whole pages. It would also avoid the internal fragmentation problem this one has.


Diagram of scull device storage


Sample Code: Freeing Quantum Buffers

/*
 * Empty out the scull device; must be called with the device
 * semaphore held.
 */
int scull_trim(struct scull_dev *dev)
{
	struct scull_qset *next, *dptr;
	int qset = dev->qset;   /* "dev" is not-null */
	int i;

	for (dptr = dev->data; dptr; dptr = next) { /* all the list items */
		if (dptr->data) {
			for (i = 0; i < qset; i++)
				kfree(dptr->data[i]);
			kfree(dptr->data);
			dptr->data = NULL;
		}
		next = dptr->next;
		kfree(dptr);
	}
	dev->size = 0;
	dev->quantum = scull_quantum;
	dev->qset = scull_qset;
	dev->data = NULL;
	return 0;
}

Race Condition Protection


You should be already familiar with the danger of race conditions, and the use of locks to prevent them. This example illustrates a form of lock that works between processes, which may block while they are executing in the driver. You will later see other forms of lock that are used with interrupt handlers and other forms of code that are not allowed to block.


Semaphore Initialization in scull

        /* Initialize each device. */
	for (i = 0; i < scull_nr_devs; i++) {
		scull_devices[i].quantum = scull_quantum;
		scull_devices[i].qset = scull_qset;
		init_MUTEX(&scull_devices[i].sem);
		scull_setup_cdev(&scull_devices[i], i);
	}

See the example above in context.


Example of a Critical Section

		if (down_interruptible(&dev->sem))
			return -ERESTARTSYS;
		scull_trim(dev); /* ignore errors */
		up(&dev->sem);

See the example above in context.


Semaphore Implementation

Semaphores are implemented by a set of macros defined in asm-i386/semaphore.h

The macros use wait queues, which we will see in more detail in Chapter 5.


When to Use Semaphore vs. Spinlock?

Semaphores locking operations may block.

It is the calling process that is blocked until the lock is released.

Spinlock locking operations may spin (loop).

It is the calling processor that spins until the lock is released.

Never call the "down" operation unless it is OK for the current thread of control to block. Especially:


Learn to treat critical sections systematically, always following the same general usage patterns. That way, you will be less likely to make errors. Imitation/duplication of coding idioms is OK.

Always remember to release locks before exiting (whether via a return, break, or goto) a critical section.

Always look out for potential deadlocks. If you are thinking about holding two locks at once, make sure they are always locked in a well-known order, or else find a way to get by using only one lock at a a time.


Cross-Address Space Copying

The following examples of usage are from scull_read and scull_write:

if (copy_to_user(buf, dptr->data[s_pos]+q_pos, count)) {
  ret = -EFAULT; goto out;
}
*f_pos += count;
...
if (copy_from_user(dptr->data[s_pos]+q_pos, buf, count)) {
  ret = -EFAULT; goto out;
}

read Method

scull_read (struct file *filp, char __user *buf, size_t count, loff_t *ppos);

Write Method

See example scull_write.
© 2003, 2005 T. P. Baker. ($Id: ch3.html,v 1.1 2010/06/07 14:29:15 baker Exp baker $)