%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%									%
%	Copyright (C) 1992, 1993 Michael K. Johnson,			%
%	johnsonm@sunsite.unc.edu					%
%									%
%	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, {\bf [I suppose there
should be a discussion of interrupts before this\dots]} then you
really need to be working in kernel space, and need to write a real
device driver, as there is no way at this time to deliver interrupts
to user processes.

If your driver must be accessible to multiple processes, and/or manage
contention for a resource, then you also need to write a real device
driver.

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

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>}.  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 stands.  If
you have a better example, I'd be glad to see it.}

