Files
oldlinux-files/docs/drivers.txt
2024-02-19 00:23:35 -05:00

948 lines
33 KiB
Plaintext

************************************************************
* *
* 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.