add directory docs
This commit is contained in:
947
docs/drivers.txt
Normal file
947
docs/drivers.txt
Normal file
@@ -0,0 +1,947 @@
|
||||
************************************************************
|
||||
* *
|
||||
* Guide To Linux Driver Writing -- Character Devices *
|
||||
* *
|
||||
* or, *
|
||||
* *
|
||||
* The Wacky World of Driver Development (I) *
|
||||
* *
|
||||
* Last Revision: Apr 11, 1993 *
|
||||
* *
|
||||
************************************************************
|
||||
|
||||
This document (C) 1993 Robert Baruch. This document may be freely
|
||||
copied as long as the entire title, copyright, this notice, and all of
|
||||
the introduction are included along with it. Suggestions, criticisms,
|
||||
and comments to baruch@nynexst.com. This document, nor the work
|
||||
performed by Robert Baruch using Linux, nor the results of said work
|
||||
are connected in any way to any of the Nynex companies. Information
|
||||
may settle during transportation. This product should not be used
|
||||
in conjunction with a dietary regime except under supervision by your
|
||||
doctor.
|
||||
|
||||
Right, now that that's over with, let's get into the fun stuff!
|
||||
|
||||
========================
|
||||
Introduction
|
||||
========================
|
||||
|
||||
There is a companion guide to this Guide, the Linux Character Device
|
||||
Tutorial. This tutorial contains working examples of driver code. It
|
||||
introduces the reader gently into each aspect of character device driver
|
||||
writing through experiments which are carried out by the programmer.
|
||||
|
||||
This Guide should serve as a reference to both beginning and advanced
|
||||
driver writers.
|
||||
|
||||
-=-=-=-=-=-=-
|
||||
|
||||
Some words of thanks:
|
||||
|
||||
Many thanks to:
|
||||
|
||||
Donald J. Becker (becker@metropolis.super.org)
|
||||
Don Holzworth (donh@gcx1.ssd.csd.harris.com)
|
||||
Michael Johnson (johnsonm@stolaf.edu)
|
||||
Karl Heinz Kremer (khk@raster.kodak.com)
|
||||
All the driver writers!
|
||||
|
||||
...and of course, Linus "Linux" Torvalds and all the guys who helped
|
||||
develop Linux into a BLOODY KICKIN' O/S!
|
||||
|
||||
-=-=-=-=-=-=-
|
||||
|
||||
...and now a word of warning:
|
||||
|
||||
Messing about with drivers is messing with the kernel. Drivers are run
|
||||
at the kernel level, and as such are not subject to scheduling. Further,
|
||||
drivers have access to various kernel structures. Before you actually
|
||||
write a driver, be *damned* sure of what you are doing, lest you end
|
||||
up having to re-format your harddrive and re-install Linux!
|
||||
|
||||
The information in this Guide is as up-to-date as I could make it. It also
|
||||
has no stamp of approval whatsoever by any of the designers of the kernel.
|
||||
I am not responsible for damage caused to anything as a result of using this
|
||||
Guide.
|
||||
|
||||
========================
|
||||
End of Introduction
|
||||
========================
|
||||
|
||||
Kernal-callable functions:
|
||||
--------------------------
|
||||
|
||||
Note: There is no close for a character device. There is only release.
|
||||
See the file data structure below to find out how to determine the number
|
||||
of processes which have the device open.
|
||||
|
||||
-=-=-=-=-=-=-=-
|
||||
|
||||
init : Initializes the driver on bootup.
|
||||
|
||||
unsigned long driver_init(unsigned long kmem_start, unsigned long kmem_end)
|
||||
|
||||
Arguments: kmem_start -- the start of kernel memory
|
||||
kmem_end -- the end of kernel memory
|
||||
|
||||
Returns: The new start of kernel memory. This will be different from the
|
||||
kmem_start argument if you want to allocate memory for the driver.
|
||||
|
||||
The arguments you use depends on what you want to do. Remember that since
|
||||
you are going to add your init function to kernel/chr_dev/mem.c, you can
|
||||
make your call anything you like, but you have access to the kernel memory
|
||||
start and end.
|
||||
|
||||
Generally, the init function initializes the driver and hardware, and
|
||||
displays some message telling of the availability of the driver and
|
||||
hardware. In addition, the register_chrdev function is usually called here.
|
||||
|
||||
|
||||
**************
|
||||
open : Open a device
|
||||
|
||||
static int driver_open(struct inode * inode, struct file * file)
|
||||
|
||||
Arguments: inode -- pointer to the inode structure for this device
|
||||
file -- pointer to the file structure for this device
|
||||
|
||||
Returns: 0 on success,
|
||||
-errno on error.
|
||||
|
||||
This function is called whenever a process performs open (or fopen) on
|
||||
the device special file. If there is no open function for the driver,
|
||||
nothing spectacular happens. As long as the /dev file exists, the
|
||||
open will succeed.
|
||||
|
||||
|
||||
**************
|
||||
read : Read from a device
|
||||
|
||||
static int driver_read(struct inode * inode, struct file * file,
|
||||
char * buffer, int count)
|
||||
|
||||
Arguments: inode -- pointer to the inode structure for this device
|
||||
file -- pointer to the file structure for this device
|
||||
buffer -- pointer to the buffer in user space to read into
|
||||
count -- the number of bytes to read
|
||||
|
||||
Returns: -errno on error
|
||||
>=0 : the number of bytes actually read
|
||||
|
||||
If there is no read function for the driver, read calls will return EINVAL.
|
||||
|
||||
|
||||
**************
|
||||
write : Write to a device
|
||||
|
||||
static int driver_write(struct inode * inode, struct file * file,
|
||||
char * buffer, int count)
|
||||
|
||||
Arguments: inode -- pointer to the inode structure for this device
|
||||
file -- pointer to the file structure for this device
|
||||
buffer -- pointer to the buffer in user space to write from
|
||||
count -- the number of bytes to write
|
||||
|
||||
Returns: -errno on error
|
||||
>=0 : the number of bytes actually written
|
||||
|
||||
If there is no write function for the driver, write calls will return
|
||||
EINVAL.
|
||||
|
||||
|
||||
**************
|
||||
lseek : Change the position offset of the device
|
||||
|
||||
static int driver_lseek(struct inode * inode, struct file * file,
|
||||
off_t offset, int origin)
|
||||
|
||||
Arguments: inode -- pointer to the inode structure for this device
|
||||
file -- pointer to the file structure for this device
|
||||
offset -- offset from origin to move to (bytes)
|
||||
origin -- origin to move from :
|
||||
0 = from origin 0 (beginning)
|
||||
1 = from current position
|
||||
2 = from end
|
||||
|
||||
Returns: -errno on error
|
||||
>=0 : the position after the move
|
||||
|
||||
See Also: Data Structure 'file'
|
||||
|
||||
If there is no lseek function for the driver, the kernel will take the default
|
||||
seek action, which is to alter the file->f_pos element. For origins of 2,
|
||||
the default action results in -EINVAL if file->f_inode is NULL, or it
|
||||
sets file->f_pos to file->f_inode->i_size + offset otherwise.
|
||||
|
||||
|
||||
**************
|
||||
ioctl : Various device-dependent services
|
||||
|
||||
static int driver_ioctl(struct inode *inode, struct file *file,
|
||||
unsigned int cmd, unsigned long arg)
|
||||
|
||||
Arguments: inode -- pointer to the inode structure for this device
|
||||
file -- pointer to the file structure for this device
|
||||
cmd -- the user-defined command to perform
|
||||
arg -- the user-defined argument. You may use this
|
||||
as a pointer to user space, since sizeof(long)==
|
||||
sizeof(void *).
|
||||
|
||||
Returns: -errno on error
|
||||
>=0 : whatever you like! (user-defined)
|
||||
|
||||
For cmd, FIOCLEX, FIONCLEX, FIONBIO, and FIOASYNC are already defined.
|
||||
See the file linux/fs/ioctl.c, sys_ioctl to find out what they do.
|
||||
If there is no ioctl call for the driver, and the ioctl command performed
|
||||
is not one of the four types listed here, ioctl will return -EINVAL.
|
||||
|
||||
|
||||
**************
|
||||
select : Performs the select call on the device:
|
||||
|
||||
static int driver_select(struct inode *inode, struct file *file,
|
||||
int sel_type, select_table * wait)
|
||||
|
||||
Arguments: inode -- pointer to the inode structure for this device
|
||||
file -- pointer to the file structure for this device
|
||||
sel_type -- the select type to perform :
|
||||
SEL_IN (read)
|
||||
SEL_OUT (write)
|
||||
SEL_EX (exception)
|
||||
wait -- see the section "Some Notes" for select.
|
||||
|
||||
Returns: 0 if the device is not ready to perform the sel_type operation
|
||||
!=0 if it is.
|
||||
|
||||
See the "Some Notes" section 'way below on information on how to use
|
||||
the select call in drivers. If there is no select call for the driver,
|
||||
select will act as if the driver is ready for the operation.
|
||||
|
||||
|
||||
**************
|
||||
release : Release device (no process holds it open)
|
||||
|
||||
static void driver_release(struct inode * inode, struct file * file)
|
||||
|
||||
Arguments: inode -- pointer to the inode structure for this device
|
||||
file -- pointer to the file structure for this device
|
||||
|
||||
The release call is activated only when the process closes the device as
|
||||
many times as it has opened it. That is, if the process has opened the
|
||||
device five times, then only when close is called for the fifth time
|
||||
will release be called (that is, provided there were no more calls to open!).
|
||||
If there is no release call for the driver, nothing spectacular happens.
|
||||
|
||||
|
||||
**************
|
||||
readdir : Get the next directory entry
|
||||
|
||||
static int driver_readdir(struct inode *inode, struct file *file,
|
||||
struct dirent *dirent, int count)
|
||||
|
||||
Arguments: inode -- pointer to the inode structure for this device
|
||||
file -- pointer to the file structure for this device
|
||||
dirent -- pointer to a dirent ("directory entry") structure
|
||||
count -- number of entries to read (currently always 1)
|
||||
|
||||
Returns: 0 on success
|
||||
-errno on failure.
|
||||
|
||||
If there is no readdir function for the driver, readdir will return
|
||||
-ENOTDIR. This is really for file systems, but you can probably use
|
||||
it for whatever you like in a non-fs device, as long as you return
|
||||
a dirent structure.
|
||||
|
||||
See Also: dirent (data structure)
|
||||
|
||||
|
||||
**************
|
||||
mmap : Forget this. According to the source (src/linux/mm/mmap.c),
|
||||
for character devices only /dev/[k]mem may be mapped.
|
||||
Besides, I'm not too clear on what it will do.
|
||||
|
||||
|
||||
----------------------------------------------------------------------
|
||||
|
||||
Data structures:
|
||||
----------------
|
||||
|
||||
dirent : Information about files in a directory.
|
||||
|
||||
#include <linux/dirent.h>
|
||||
|
||||
struct dirent {
|
||||
long d_ino; /* Inode of file */
|
||||
off_t d_off;
|
||||
unsigned short d_reclen;
|
||||
char d_name[NAME_MAX+1]; /* Name of file */
|
||||
};
|
||||
|
||||
|
||||
**************
|
||||
file : Information about open files
|
||||
|
||||
According to the Hacker's Guide to Linux, this structure is mainly used
|
||||
for writing filesystems, not drivers. However, there is no reason it
|
||||
cannot be used by drivers.
|
||||
|
||||
#include <linux/fs.h>
|
||||
|
||||
struct file {
|
||||
mode_t f_mode;
|
||||
dev_t f_rdev; /* needed for /dev/tty */
|
||||
off_t f_pos; /* Curr. posn in file */
|
||||
unsigned short f_flags; /* The flags arg passed to open */
|
||||
unsigned short f_count; /* Number of opens on this file */
|
||||
unsigned short f_reada;
|
||||
struct inode * f_inode; /* pointer to the inode struct */
|
||||
struct file_operations * f_op; /* pointer to the fops struct */
|
||||
};
|
||||
|
||||
|
||||
**************
|
||||
file_operations : Tells the kernel which function to call for
|
||||
which kernel function.
|
||||
|
||||
#include <linux/fs.h>
|
||||
|
||||
struct file_operations {
|
||||
int (*lseek) (struct inode *, struct file *, off_t, int);
|
||||
int (*read) (struct inode *, struct file *, char *, int);
|
||||
int (*write) (struct inode *, struct file *, char *, int);
|
||||
int (*readdir) (struct inode *, struct file *, struct dirent *, int);
|
||||
int (*select) (struct inode *, struct file *, int, select_table *);
|
||||
int (*ioctl) (struct inode *, struct file *, unsigned int,
|
||||
unsigned int);
|
||||
int (*mmap) (void);
|
||||
int (*open) (struct inode *, struct file *);
|
||||
void (*release) (struct inode *, struct file *);
|
||||
int (*fsync) (struct inode *, struct file *);
|
||||
};
|
||||
|
||||
|
||||
**************
|
||||
inode : Information about the /dev/xxx file (or inode)
|
||||
|
||||
#include <linux/fs.h>
|
||||
|
||||
struct inode {
|
||||
dev_t i_dev;
|
||||
unsigned long i_ino; /* Inode number */
|
||||
umode_t i_mode; /* Mode of the file */
|
||||
nlink_t i_nlink;
|
||||
uid_t i_uid;
|
||||
gid_t i_gid;
|
||||
dev_t i_rdev; /* Device major and minor numbers */
|
||||
off_t i_size;
|
||||
time_t i_atime;
|
||||
time_t i_mtime;
|
||||
time_t i_ctime;
|
||||
unsigned long i_blksize;
|
||||
unsigned long i_blocks;
|
||||
struct inode_operations * i_op;
|
||||
struct super_block * i_sb;
|
||||
struct wait_queue * i_wait;
|
||||
struct file_lock * i_flock;
|
||||
struct vm_area_struct * i_mmap;
|
||||
struct inode * i_next, * i_prev;
|
||||
struct inode * i_hash_next, * i_hash_prev;
|
||||
struct inode * i_bound_to, * i_bound_by;
|
||||
unsigned short i_count;
|
||||
unsigned short i_flags; /* Mount flags (see fs.h) */
|
||||
unsigned char i_lock;
|
||||
unsigned char i_dirt;
|
||||
unsigned char i_pipe;
|
||||
unsigned char i_mount;
|
||||
unsigned char i_seek;
|
||||
unsigned char i_update;
|
||||
union {
|
||||
struct pipe_inode_info pipe_i;
|
||||
struct minix_inode_info minix_i;
|
||||
struct ext_inode_info ext_i;
|
||||
struct msdos_inode_info msdos_i;
|
||||
struct iso_inode_info isofs_i;
|
||||
struct nfs_inode_info nfs_i;
|
||||
} u;
|
||||
};
|
||||
|
||||
See Also: Driver Calls: MAJOR, MINOR, IS_RDONLY, IS_NOSUID, IS_NODEV,
|
||||
IS_NOEXEC, IS_SYNC
|
||||
|
||||
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
||||
|
||||
Driver calls:
|
||||
|
||||
|
||||
**************
|
||||
add_timer : Cause a function to be executed when a given amount of time
|
||||
has passed.
|
||||
|
||||
#include <linux/sched.h>
|
||||
|
||||
void add_timer(long jiffies, void (*fn)(void))
|
||||
|
||||
Arguments: jiffies -- The number of jiffies to time out after.
|
||||
fn -- The function in kernel space to run after timeout.
|
||||
|
||||
Note! This is NOT process-specific! If you are looking for a way
|
||||
to have a process go to sleep and timeout, look for ?
|
||||
Excessive use of this function will cause the kernel to panic if there are
|
||||
too many timeouts active at once.
|
||||
|
||||
|
||||
**************
|
||||
cli : Macro, Prevent interrupts from occuring
|
||||
|
||||
#include <asm/system.h>
|
||||
|
||||
#define cli() __asm__ __volatile__ ("cli"::)
|
||||
|
||||
See Also: sti
|
||||
|
||||
|
||||
**************
|
||||
free_irq : Free a registered interrupt
|
||||
|
||||
#include <linux/sched.h>
|
||||
|
||||
void free_irq(unsigned int irq)
|
||||
|
||||
Arguments: irq -- the interrupt level to free up
|
||||
|
||||
See Also: request_irq
|
||||
|
||||
|
||||
**************
|
||||
get_fs_byte, get_fs_word, get_fs_long : Get data from user space
|
||||
|
||||
Purpose: Allows a driver to access data in user space (which is in
|
||||
a different segment than the kernel!)
|
||||
|
||||
#include <asm/segment.h>
|
||||
|
||||
inline unsigned char get_fs_byte(const char * addr)
|
||||
inline unsigned short get_fs_word(const unsigned short *addr)
|
||||
inline unsigned long get_fs_long(const unsigned long *addr)
|
||||
|
||||
Arguments: addr -- the address in user space to get data from
|
||||
|
||||
Returns: the value in user space.
|
||||
|
||||
See Also: memcpy_fromfs, memcpy_tofs, put_fs_byte, put_fs_word, put_fs_long
|
||||
|
||||
|
||||
**************
|
||||
inb, inb_p : Inputs a byte from a port
|
||||
|
||||
#include <asm/io.h>
|
||||
|
||||
inline unsigned int inb(unsigned short port)
|
||||
inline unsigned int inb_p(unsigned short port)
|
||||
|
||||
Arguments: port -- the port to input a byte from
|
||||
|
||||
Returns: Byte received in the low byte. High byte unused.
|
||||
|
||||
See Also: outb, outb_p
|
||||
|
||||
|
||||
**************
|
||||
IS_RDONLY, IS_NOSUID, IS_NODEV, IS_NOEXEC, IS_SYNC: Macros, check the status
|
||||
of the device on the filesystem
|
||||
|
||||
#include <linux/fs.h>
|
||||
|
||||
#define IS_RDONLY(inode) (((inode)->i_sb) && ((inode)->i_sb->s_flags &
|
||||
MS_RDONLY))
|
||||
#define IS_NOSUID(inode) ((inode)->i_flags & MS_NOSUID)
|
||||
#define IS_NODEV(inode) ((inode)->i_flags & MS_NODEV)
|
||||
#define IS_NOEXEC(inode) ((inode)->i_flags & MS_NOEXEC)
|
||||
#define IS_SYNC(inode) ((inode)->i_flags & MS_SYNC)
|
||||
|
||||
|
||||
**************
|
||||
kfree, kfree_s : Free memory which has been kmalloced.
|
||||
|
||||
#include <linux/kernel.h>
|
||||
|
||||
#define kfree(x) kfree_s((x), 0)
|
||||
void kfree_s(void * obj, int size)
|
||||
|
||||
Arguments : obj -- pointer to kernel memory you want to free
|
||||
size -- size of block you want to free (0 if you don't know
|
||||
or are lazy -- slows things down)
|
||||
|
||||
|
||||
**************
|
||||
kmalloc : Allocate memory in kernel space
|
||||
|
||||
#include <linux/kernel.h>
|
||||
|
||||
void * kmalloc(unsigned int len, int priority)
|
||||
|
||||
Arguments: len -- the length of the memory to allocate. Must not be bigger
|
||||
than 4096.
|
||||
priority -- GFP_KERNEL or GFP_ATOMIC. GFP_ATOMIC causes kmalloc
|
||||
to return NULL if the memory could not be found
|
||||
immediately. GFP_KERNEL is the usual priority.
|
||||
|
||||
Returns: NULL on failure, a pointer to kernel space on success.
|
||||
|
||||
|
||||
**************
|
||||
memcpy_fromfs, memcpy_tofs : Copies memory from user(fromfs)/kernel(tofs)
|
||||
space to kernel/user space
|
||||
|
||||
#include <asm/segment.h>
|
||||
|
||||
inline void memcpy_fromfs(void * to, const void * from, unsigned long n)
|
||||
inline void memcpy_tofs(void * to, const void * from, unsigned long n)
|
||||
|
||||
Arguments: to -- Address to copy data to
|
||||
from -- Address to copy data from
|
||||
n -- number of bytes to copy
|
||||
|
||||
See Also: get_fs_byte, get_fs_word, get_fs_long,
|
||||
put_fs_byte, put_fs_word, put_fs_long
|
||||
|
||||
Warning! Get the order of arguments right!
|
||||
|
||||
|
||||
**************
|
||||
MAJOR, MINOR : Macros, get major/minor device number from inode i_dev entry.
|
||||
|
||||
#include <linux/fs.h>
|
||||
|
||||
#define MAJOR(a) (((unsigned)(a))>>8)
|
||||
#define MINOR(a) ((a)&0xff)
|
||||
|
||||
|
||||
**************
|
||||
outb, outb_p : Outputs a byte to a port
|
||||
|
||||
#include <asm/io.h>
|
||||
|
||||
inline void outb(char value, unsigned short port)
|
||||
inline void outb_p(char value, unsigned short port)
|
||||
|
||||
Arguments: value -- the byte to write out
|
||||
port -- the port to write it out on
|
||||
|
||||
See Also: inb, inb_p
|
||||
|
||||
|
||||
**************
|
||||
printk : Kernel printf
|
||||
|
||||
#include <linux/kernel.h>
|
||||
|
||||
int printk(const char *fmt, ...)
|
||||
|
||||
Arguments: fmt -- printf-style format
|
||||
... -- var-arguments, printf-style
|
||||
|
||||
Returns: Number of characters printed.
|
||||
|
||||
|
||||
**************
|
||||
put_fs_byte, put_fs_word, put_fs_long : Put data into user space
|
||||
|
||||
Purpose: Allows a driver to put a byte, word, or long into user space,
|
||||
which is at a different segment than the kernel.
|
||||
|
||||
#include <asm/segment.h>
|
||||
|
||||
inline void put_fs_byte(char val,char *addr)
|
||||
inline void put_fs_word(short val,short * addr)
|
||||
inline void put_fs_long(unsigned long val,unsigned long * addr)
|
||||
|
||||
Arguments: addr -- the address in user space to get data from
|
||||
|
||||
Returns: the value in user space.
|
||||
|
||||
See Also: memcpy_fromfs, memcpy_tofs, get_fs_byte, get_fs_word, get_fs_long
|
||||
|
||||
Warning! Get the order of arguments right!
|
||||
|
||||
|
||||
**************
|
||||
register_chrdev : Register a character device with the kernel
|
||||
|
||||
#include <linux/fs.h>
|
||||
#include <linux/errno.h>
|
||||
|
||||
int register_chrdev(unsigned int major, const char *name,
|
||||
struct file_operations *fops)
|
||||
|
||||
Arguments: major -- the major device number to register as
|
||||
name -- the name of the device (currently unused)
|
||||
fops -- a file_operations structure for the device.
|
||||
|
||||
Returns: -EINVAL if major is >= MAX_CHRDEV (defined in fs.h as 32)
|
||||
-EBUSY if major device has already been allocated
|
||||
0 on success.
|
||||
|
||||
|
||||
**************
|
||||
request_irq : Request to perform a function on an interrupt
|
||||
|
||||
#include <linux/sched.h>
|
||||
#include <linux/errno.h>
|
||||
|
||||
int request_irq(unsigned int irq, void (*handler)(int))
|
||||
|
||||
Arguments: irq -- the interrupt to request.
|
||||
handler -- the function to handle the interrupt. The interrupt
|
||||
handler should be of the form void handler(int).
|
||||
Unless you really know what you are doing, don't
|
||||
use the int argument.
|
||||
|
||||
Returns: -EINVAL if irq>15 or handler==NULL
|
||||
-EBUSY if irq is already allocated.
|
||||
0 on success.
|
||||
|
||||
See Also: free_irq
|
||||
|
||||
|
||||
**************
|
||||
select_wait : Add a process to the select-wait queue
|
||||
|
||||
#include <linux/sched.h>
|
||||
|
||||
inline void select_wait(struct wait_queue ** wait_address, select_table * p)
|
||||
|
||||
Arguments: wait_address -- Address of a wait_queue pointer
|
||||
p -- Address of a select_table
|
||||
|
||||
Devices which use select should define a struct wait_queue pointer and
|
||||
initialize it to NULL. select_wait adds the current process to a circular
|
||||
list of waits. The pointer to the circular list is wait_address. If
|
||||
p is NULL, select_wait does nothing, otherwise the current process is
|
||||
put to sleep.
|
||||
|
||||
See Also: sleep_on, interruptible_sleep_on, wake_up, wake_up_interruptible
|
||||
|
||||
|
||||
**************
|
||||
sleep_on, interruptible_sleep_on : Put the current process to sleep.
|
||||
|
||||
#include <linux/sched.h>
|
||||
|
||||
void sleep_on(struct wait_queue ** p)
|
||||
void interruptible_sleep_on(struct wait_queue ** p)
|
||||
|
||||
Arguments: q -- Pointer to the driver's wait_queue (see select_wait)
|
||||
|
||||
sleep_on puts the current process to sleep in an uninterruptible state.
|
||||
That is, signals will not wake the process up. The only thing which
|
||||
will wake a process up in this state is a hardware interrupt (which
|
||||
would call the interrupt handler of the driver) -- and even then the
|
||||
interrupt routine needs to call wake_up to put the process in a running
|
||||
state.
|
||||
|
||||
interruptible_sleep_on puts the current process to sleep in an interruptible
|
||||
state, which means that not only will hardware interrupts get through, but
|
||||
also signals and process timeouts ("alarms") will cause the process to
|
||||
wake up (and execute interrupt or signal handlers). A call to
|
||||
wake_up_interruptible is necessary to wake up the process and allow it
|
||||
to continue running where it left off.
|
||||
|
||||
See Also: select_wait, wake_up, wake_up_interruptible
|
||||
|
||||
|
||||
**************
|
||||
sti : Macro, Allow interrupts to occur
|
||||
|
||||
#include <asm/system.h>
|
||||
|
||||
#define sti() __asm__ __volatile__ ("sti"::)
|
||||
|
||||
See Also: cli
|
||||
|
||||
|
||||
**************
|
||||
sys_getegid, sys_getgid, sys_getpid, sys_getppid, sys_getuid, sys_geteuid :
|
||||
Funky functions which get various information about the current process,
|
||||
|
||||
#include <linux/sys.h>
|
||||
|
||||
int sys_getegid(void)
|
||||
int sys_getgid(void)
|
||||
int sys_getpid(void)
|
||||
int sys_getppid(void)
|
||||
int sys_getuid(void)
|
||||
int sys_geteuid(void)
|
||||
|
||||
sys_getegid gets the effective gid of the process.
|
||||
sys_getgid gets the group ID of the process.
|
||||
sys_getpid gets the process ID of the process.
|
||||
sys_getppid gets the process ID of the process' parent.
|
||||
sys_geteuid gets the effective uid of the process.
|
||||
sys_getuid gets the user ID of the process.
|
||||
|
||||
|
||||
**************
|
||||
wake_up, wake_up_interruptible : Wake up _all_ processes waiting on
|
||||
the wait queue.
|
||||
|
||||
#include <linux/sched.h>
|
||||
|
||||
void wake_up(struct wait_queue **q)
|
||||
void wake_up_interruptible(struct wait_queue **q)
|
||||
|
||||
Arguments: q -- Pointer to the driver's wait_queue (see select_wait)
|
||||
|
||||
See Also: select_wait, sleep_on, interruptible_sleep_on
|
||||
|
||||
|
||||
--------------------------------------------------------
|
||||
|
||||
==========
|
||||
Some notes
|
||||
==========
|
||||
|
||||
Interrupts, Drivers, and You!
|
||||
-----------------------------
|
||||
|
||||
First, a brief exposition on the Meaning of Interrupts. There are three
|
||||
ways by which a program running in the CPU may be interrupted. The first is
|
||||
the external interrupt. This is caused by an external device (that is,
|
||||
external to the CPU) signalling for attention. These are referred to as
|
||||
"interrupt requests" or "IRQs".
|
||||
|
||||
The second method is the exception, which is caused by something internal to
|
||||
the CPU, usually in response to a condition generated by execution of an
|
||||
instruction.
|
||||
|
||||
The third method is the software interrupt, which is a deliberately executed
|
||||
interrupt -- the INT instruction in assembly. System calls are implemented
|
||||
using software interrupts; when a system call is desired, Linux places the
|
||||
system call number in EAX, and performs an INT 0x80 instruction.
|
||||
|
||||
Since drivers usually deal with hardware devices, it is logical that driver
|
||||
interrupts should refer to external interrupts. There are 16 available IRQs
|
||||
-- IRQ0 through IRQ15. The following table lists the official uses of the
|
||||
various IRQs:
|
||||
|
||||
IRQ0 -- timer 0
|
||||
IRQ1 -- keyboard
|
||||
IRQ2 -- AT slave 8259 ("cascade")
|
||||
IRQ3 -- COM2
|
||||
IRQ4 -- COM1
|
||||
IRQ5 -- LPT2
|
||||
IRQ6 -- floppy
|
||||
IRQ7 -- LPT1
|
||||
IRQ8-12 ??????
|
||||
IRQ13 -- coprocessor error
|
||||
IRQ14,15 ??????
|
||||
|
||||
Writing drivers which can be interrupted requires care. Be aware that
|
||||
every line you write can be interrupted, and thus cause variable
|
||||
changes to occur. If you really want to protect critical sections from
|
||||
being interrupted, use the cli() and sti() driver calls.
|
||||
|
||||
Suppose you wanted to test some kind of funky condition, where success of
|
||||
the condition leads to going to sleep, and being woken up by an interrupt.
|
||||
Consider this code:
|
||||
|
||||
void driver_interrupt(int unused)
|
||||
{
|
||||
if (!driver_stuff.int_flag) return; /* Spurious interrupts
|
||||
are not unheard of */
|
||||
driver_stuff.int_flag=0;
|
||||
weird_wacky(); /* Do some weird and wacky stuff
|
||||
here to handle the interrupt */
|
||||
disable_ints(); /* Disable the device from issuing interrupts */
|
||||
wake_up(&driver_stuff.wait_queue); /* Sets process to TASK_RUNNING */
|
||||
}
|
||||
|
||||
if (conditions_are_ripe())
|
||||
{
|
||||
driver_stuff.int_flag = 1;
|
||||
enable_ints(); /* Enable device to interrupt us */
|
||||
sleep_on(&driver_stuff.wait_queue); /* Sets process to TASK_UNINTERRUPTIBLE */
|
||||
}
|
||||
|
||||
Assume we just leave the conditions_are_ripe code, determining that the
|
||||
conditions are ripe! We have just enabled the device to interrupt the
|
||||
machine. So we are now about to enter the sleep_on code, and
|
||||
what should happen but the pesky device issues an interrupt. Ka-chunk! and
|
||||
we enter the driver_interrupt routine, which does some weird and wacky
|
||||
stuff to handle the interrupt, and then we disable the device's interrupts.
|
||||
Ka-ching! we enter the wake_up function which sets the process up to run again.
|
||||
Boink! we exit the interrupt handler and commence where we left off
|
||||
(just about to enter the sleep_on code). Vooosh! we're now sleeping the
|
||||
process, awaiting an interrupt which will never occur, since the interrupt
|
||||
handler disabled the device from interrupts! What to do?
|
||||
|
||||
Use cli() and sti() to protect the critical sections of code:
|
||||
|
||||
cli();
|
||||
if (conditions_are_ripe())
|
||||
{
|
||||
driver_stuff.int_flag = 1;
|
||||
enable_ints(); /* Enable device to interrupt us */
|
||||
sleep_on(&driver_stuff.wait_queue); /* Sets process to TASK_UNINTERRUPTIBLE */
|
||||
}
|
||||
else sti();
|
||||
|
||||
First we clear interrupts. This is not the same as disabling device
|
||||
interrupts! This actually prevents a hardware interrupt from causing the
|
||||
CPU to execute interrupt code. In effect, the interrupt is deferred.
|
||||
|
||||
Now we can do our check and perform sleep_on, secure in the knowledge that
|
||||
the interrupt handler cannot be called. The sleep_on (and interruptible_
|
||||
sleep_on) call has a sti() in it in the right place, so you don't have to
|
||||
worry about calling sti() before sleep_on, and running into a race condition
|
||||
again.
|
||||
|
||||
Of course, with any interruptible device driver, you must be careful never
|
||||
to spend too much time in the interrupt routine if you are expecting more
|
||||
than one interrupt, because you may miss your second interrupt.
|
||||
|
||||
-=-=-=-=-=-=-
|
||||
|
||||
Drivers and signals:
|
||||
--------------------
|
||||
|
||||
When a process is sleeping in an interruptible state, any signal can wake it
|
||||
up. This is the sequence of events which occurs when a sleeping process
|
||||
receives a signal:
|
||||
|
||||
(1) Set current->signal.
|
||||
(2) Set the process to a runnable state.
|
||||
(3) Execute the rest of the driver call.
|
||||
(4) Run the signal handler.
|
||||
(5) If the driver call in step 3 returned -ERESTARTNOHAND or -ERESTARTNOINTR,
|
||||
then return from the driver call with EINTR. If the driver call in step
|
||||
3 returned -ERESTARTSYS, then restart the driver call. Otherwise, just
|
||||
return with whatever was returned from the driver call.
|
||||
|
||||
In the driver, you can tell if a sleep has been interrupted by a signal
|
||||
with the following code:
|
||||
|
||||
if (current->signal & ~current->blocked)
|
||||
{
|
||||
/* Do things based on sleep interrupted by signal */
|
||||
}
|
||||
|
||||
-=-=-=-=-=-=-
|
||||
|
||||
Drivers and timeouts:
|
||||
---------------------
|
||||
|
||||
Suppose you wanted to sleep on an interrupt, but also time out after
|
||||
a period of time. You could always use the add_timer, but that's
|
||||
frowned upon because there are only a limited number of timers
|
||||
available -- currently there are 64.
|
||||
|
||||
The usual solution is to manually alter the current process's timeout:
|
||||
|
||||
current->timeout = jiffies + X;
|
||||
interruptible_sleep_on(&driver_stuff.wait_queue);
|
||||
|
||||
(Interruptible sleep_on must be used here to allow a timeout to interrupt
|
||||
the sleep). This will cause the scheduler to set the task running again when
|
||||
X jiffies has gone by. Even if the timeout goes off and the process is
|
||||
allowed to continue running, it is probably a good idea to call
|
||||
wake_up_interruptible in case the process needs to be rescheduled.
|
||||
|
||||
To find out if it was a timeout which caused the process to wake up,
|
||||
check current->timeout. If it is 0, a timeout occurred. Otherwise it
|
||||
should remain what you set it at. If a timeout did not occur, and something
|
||||
else woke the process up, you should set current->timeout to 0 to prevent
|
||||
the timeout from continuing.
|
||||
|
||||
The disadvantage of this method is that the process can only have one
|
||||
timeout at a time. Over *all* drivers.
|
||||
|
||||
|
||||
-=-=-=-=-=-=-
|
||||
|
||||
The driver_select call:
|
||||
-----------------------
|
||||
|
||||
When a process issues a select call, it is checking to see if the given
|
||||
devices are ready to perform the given operations. For example, suppose
|
||||
you want a driver to have a command written to it, and to disallow further
|
||||
commands until the current command is complete. Well, in the write call
|
||||
you would block commands if there is already a command operating (for example,
|
||||
waiting for a board to do something). But that would require the process to
|
||||
write over and over again until it succeeds. That just burns cycles.
|
||||
|
||||
The select call allows a process to determine the availability of read and
|
||||
write. In the above example, one merely has to select for write on that
|
||||
device's file descriptor (as returned by open), and the process would be
|
||||
put to sleep until the device is ready to be written to.
|
||||
|
||||
The kernel will call the driver's driver_select call when the process issues a
|
||||
select call. The arguments to the driver_select call are detailed above.
|
||||
If the wait argument is non-NULL, and there is no error condition caused
|
||||
by the select, driver_select should put the process to sleep, and arrange
|
||||
to be woken up when the device becomes ready (usually through an interrupt).
|
||||
|
||||
If, however, the wait argument is NULL, then the driver should quickly
|
||||
see if the device is ready, and return even if it is not. The select_wait
|
||||
function does this already for you (see further).
|
||||
|
||||
Putting the process to sleep does not require calling a sleep_on function.
|
||||
It is the select_wait function which is called, with the p argument being
|
||||
equal to the wait argument passed to driver_select.
|
||||
|
||||
select_wait is pretty much equivalent to interruptible_sleep_on in that it
|
||||
adds the current process to the wait queue and sleeps the process in
|
||||
an interruptible state. The internals of the differences between
|
||||
select_wait and interruptible_sleep_on are relatively irrelevant here.
|
||||
Suffice it to say that to wake the process up from the select, one needs
|
||||
to perform the wake_up_interruptible call. When that happens, the
|
||||
process is free to run.
|
||||
|
||||
However, in the case of interruptible_sleep_on, the process will continue
|
||||
running after the call to interruptible_sleep_on. In the case of select_wait,
|
||||
the process does not. driver_select is called as a "side effect" of the
|
||||
select call, and so completes even when it calls select_wait. It is
|
||||
not select_wait which sleeps the process, but the select call. Nevertheless,
|
||||
it is required to call select_wait to add the process to the wait-queue,
|
||||
since select will not do that.
|
||||
|
||||
All one needs to remember for driver_select is:
|
||||
|
||||
(1) Call select_wait if the device is not ready, and return 0.
|
||||
(2) Return 1 if the device is ready.
|
||||
|
||||
|
||||
Calling select with a timeout is really no different to the driver than
|
||||
calling it without select. But there is one crucial difference. Remember
|
||||
timing out on interrupts above? Well, interrupt timeouts and select timeouts
|
||||
cannot co-exist. They both use current->timeout to wake the process up
|
||||
after a period of time. Remember that!
|
||||
|
||||
|
||||
-=-=-=-=-=-=-
|
||||
|
||||
Installation notes:
|
||||
-------------------
|
||||
|
||||
Before you sit down and write your first driver, first make sure you
|
||||
understand how to recompile the kernel. Then go ahead and recompile it!
|
||||
Recompilation of the kernel is described in the FAQ. If you can't
|
||||
recompile the kernel, you can't install your driver into the kernel.
|
||||
[Although I hear tell of a package on sunsite which can load and unload
|
||||
drivers while the kernel is running. Until I test out this package,
|
||||
I won't include instructions for it here.]
|
||||
|
||||
For character devices, you need to go into the mem.c file in the
|
||||
(source)/linux/kernel/chr_dev directory, to the chr_dev_init function,
|
||||
and add your init function to it. Recompile the kernel, and away you go!
|
||||
|
||||
(BTW, would you manually have to do a mknod to make the /dev/xxx entry
|
||||
for your driver? Can you do it in the init function?)
|
||||
|
||||
In general, one installs a device special file in /dev manually, by using
|
||||
mknod:
|
||||
|
||||
mknod /dev/xxx c major minor
|
||||
|
||||
If you registered your character driver as major device X, then all accesses
|
||||
to /dev/xxx where major==X will call your driver functions.
|
||||
Reference in New Issue
Block a user