%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%									%
%	Copyright (C) 1992--1995 Michael K. Johnson,			%
%	johnsonm@nigel.vnet.net						%
%									%
%	This file is freely copyable, but you must preserve this	%
%	copyright notice on all copies, it must only be distributed	%
%	as part of the Linux Kernel Hackers' Guide, and its use is	%
%	is subject to the conditions expressed in the copyright for	%
%	the whole guide, in the file prelim/copyright.tex		%
%									%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%


%\section{How to {\em not} write a device driver}
\section{User-space device drivers}

It is not always necessary to write a device driver for a device,
especially in applications where no two applications will compete for
the device.  The most useful example of this is a memory-mapped
device, but you can also do this with devices in I/O space (devices
accessed with {\tt inb()} and {\tt outb()}, etc.).  If your process is
running as superuser (root), you can use the {\tt mmap()} call to map
some of your process memory to actual memory locations, by {\tt
mmap()}'ing a section of /dev/mem.  When you have done this mapping,
it is pretty easy to write and read from real memory addresses just as
you would read and write any variables.

If your driver needs to respond to interrupts, then you
really need to be working in kernel space, and need to write a real
device driver, as there is no good way at this time to deliver interrupts
to user processes.  Although the DOSEMU project has created something
called the SIG (Silly Interrupt Generator) which allows interrupts
to be posted to user processes (I believe through the use of signals),
the SIG is not particularly fast, and should be thought of as a last
resort for things like DOSEMU.

An interrupt (for those who don't know) is an asyncronous notification
posted by the hardware to alert the device driver of some condition.
You have likely dealt with `IRQ's when setting up your hardware; an
IRQ is an ``Interrupt ReQuest line,'' which is triggered when the
device wants to talk to the driver.  This may be because it has data
to give to the drive, or because it is now ready to receive data, or
because of some other ``exceptional condition'' that the driver needs
to know about.  It is similar to user-level processes receiving a {\bf
signal,} so similar that the same {\tt sigaction} structure is used in
the kernel to deal with interrupts as is used in user-level programs
to deal with signals.  Where the user-level has its signals delivered
to it by the kernel, the kernel has interrupt delivered to it by
hardware.

If your driver must be accessible to multiple processes at once,
and/or manage contention for a resource, then you also need to write a
real device driver at the kernel level, and a user-space device driver
will not be sufficient or even possible.

\subsection{Example: {\tt vgalib}}

A good example of a user-space driver is the {\tt vgalib} library.
The standard {\tt read()} and {\tt write()} calls are really
inadequate for writing a really fast graphics driver, and so
instead there is a library which acts conceptually like a device
driver, but runs in user space.  Any processes which use it {\bf must}
run setuid root, because it uses the {\tt ioperm()} system call.  It is
possible for a process that is not setuid root to write to /dev/mem if
you have a group {\tt mem} or {\tt kmem} which is allowed write
permission to /dev/mem and the process is properly setgid, but only a
process running as root can execute the {\tt ioperm()} call.

There are several I/O ports associated with VGA graphics.  {\tt
vgalib} creates symbolic names for this with {\tt \#define}
statements, and then issues the {\tt ioperm()} call like this to make
it possible for the process to read and write directly from and to
those ports:
\begin{screen}\begin{verbatim}
    if (ioperm(CRT_IC, 1, 1)) {
        printf("VGAlib: can't get I/O permissions \n");
        exit (-1);
    }
    ioperm(CRT_IM,  1, 1);
    ioperm(ATT_IW, 1, 1);
    \end{verbatim}[\dots]
\end{screen}
It only needs to do error checking once, because the only reason for
the {\tt ioperm()} call to fail is that it is not being called by the
superuser, and this status is not going to change.

\blackdiamond After making this call, the process is allowed to use
{\tt inb} and {\tt outb} machine instructions, but only on the
specified ports.  These instructions can be accessed without writing
directly in assembly by including {\tt <linux/asm>}, but will only
work if you compile {\bf with optimization on,} by giving the
{\tt -O{\em{}?}} to gcc.  Read {\tt <linux/asm>} for details.

After arranging for port I/O, {\tt vgalib} arranges for writing
directly to kernel memory with the following code:
\begin{screen}\begin{verbatim}
    /* open /dev/mem */
    if ((mem_fd = open("/dev/mem", O_RDWR) ) < 0) {
        printf("VGAlib: can't open /dev/mem \n");
        exit (-1);
    }

    /* mmap graphics memory */
    if ((graph_mem = malloc(GRAPH_SIZE + (PAGE_SIZE-1))) == NULL) {
        printf("VGAlib: allocation error \n");
        exit (-1);
    }
    if ((unsigned long)graph_mem % PAGE_SIZE)
        graph_mem += PAGE_SIZE - ((unsigned long)graph_mem % PAGE_SIZE);
    graph_mem = (unsigned char *)mmap(
        (caddr_t)graph_mem, 
        GRAPH_SIZE,
        PROT_READ|PROT_WRITE,
        MAP_SHARED|MAP_FIXED,
        mem_fd, 
        GRAPH_BASE
    );
    if ((long)graph_mem < 0) {
        printf("VGAlib: mmap error \n");
        exit (-1);
    }
\end{verbatim}\end{screen}
It first opens /dev/mem, then allocates memory enough so that the
mapping can be done on a page (4 KB) boundary, and then attempts the
map.  {\tt GRAPH\_SIZE} is the size of VGA memory, and {\tt
GRAPH\_BASE} is the first address of VGA memory in /dev/mem.  Then by
writing to the address that is returned by {\tt mmap()}, the process
is actually writing to screen memory.

\subsection{Example: mouse conversion}

If you want a driver that acts a bit more like a kernel-level driver,
but does not live in kernel space, you can also make a fifo, or named
pipe.  This usually lives in the /dev/ directory (although it doesn't
need to) and acts substantially like a device once set up.  However,
fifo's are one-directional only~--- they have one reader and one
writer.

For instance, it used to be that if you had a PS/2-style mouse, and
wanted to run XFree86, you had to create a fifo called /dev/mouse,
and run a program called mconv which read PS/2 mouse ``droppings''
from /dev/psaux, and wrote the equivalent microsoft-style
``droppings'' to /dev/mouse.  Then XFree86 would read the ``droppings'' from
/dev/mouse, and it would be as if there were a microsoft mouse connected to
/dev/mouse.\footnote{Even though XFree86 is now able to read PS/2
style ``droppings'', the concepts in this example still stand.  If
you have a better example, I'd be glad to see it.}

