420 lines
12 KiB
XML
420 lines
12 KiB
XML
<?xml version="1.0" encoding="iso-8859-1"?>
|
||
<!--
|
||
The FreeBSD Documentation Project
|
||
|
||
$FreeBSD$
|
||
-->
|
||
|
||
<chapter xmlns="http://docbook.org/ns/docbook"
|
||
xmlns:xlink="http://www.w3.org/1999/xlink" version="5.0"
|
||
xml:id="driverbasics">
|
||
|
||
<info>
|
||
<title>Writing FreeBSD Device Drivers</title>
|
||
|
||
<authorgroup>
|
||
<author>
|
||
<personname>
|
||
<firstname>Murray</firstname>
|
||
<surname>Stokely</surname>
|
||
</personname>
|
||
|
||
<contrib>Written by </contrib>
|
||
</author>
|
||
</authorgroup>
|
||
|
||
<authorgroup>
|
||
<author>
|
||
<personname>
|
||
<firstname>Jörg</firstname>
|
||
<surname>Wunsch</surname>
|
||
</personname>
|
||
|
||
<contrib>Based on intro(4) manual page by </contrib>
|
||
</author>
|
||
</authorgroup>
|
||
</info>
|
||
|
||
<sect1 xml:id="driverbasics-intro">
|
||
<title>Introduction</title>
|
||
|
||
<indexterm><primary>device driver</primary></indexterm>
|
||
<indexterm><primary>pseudo-device</primary></indexterm>
|
||
|
||
<para>This chapter provides a brief introduction to writing device
|
||
drivers for FreeBSD. A device in this context is a term used
|
||
mostly for hardware-related stuff that belongs to the system,
|
||
like disks, printers, or a graphics display with its keyboard.
|
||
A device driver is the software component of the operating
|
||
system that controls a specific device. There are also
|
||
so-called pseudo-devices where a device driver emulates the
|
||
behavior of a device in software without any particular
|
||
underlying hardware. Device drivers can be compiled into the
|
||
system statically or loaded on demand through the dynamic kernel
|
||
linker facility `kld'.</para>
|
||
|
||
<indexterm><primary>device nodes</primary></indexterm>
|
||
|
||
<para>Most devices in a &unix;-like operating system are accessed
|
||
through device-nodes, sometimes also called special files.
|
||
These files are usually located under the directory
|
||
<filename>/dev</filename> in the filesystem hierarchy.</para>
|
||
|
||
<para>Device drivers can roughly be broken down into two
|
||
categories; character and network device drivers.</para>
|
||
|
||
</sect1>
|
||
|
||
<sect1 xml:id="driverbasics-kld">
|
||
<title>Dynamic Kernel Linker Facility - KLD</title>
|
||
|
||
<indexterm>
|
||
<primary>kernel linking</primary>
|
||
<secondary>dynamic</secondary>
|
||
</indexterm>
|
||
<indexterm>
|
||
<primary>kernel loadable modules (KLD)</primary>
|
||
</indexterm>
|
||
|
||
<para>The kld interface allows system administrators to
|
||
dynamically add and remove functionality from a running system.
|
||
This allows device driver writers to load their new changes into
|
||
a running kernel without constantly rebooting to test
|
||
changes.</para>
|
||
|
||
<indexterm>
|
||
<primary>kernel modules</primary>
|
||
<secondary>loading</secondary>
|
||
</indexterm>
|
||
<indexterm>
|
||
<primary>kernel modules</primary>
|
||
<secondary>unloading</secondary>
|
||
</indexterm>
|
||
<indexterm>
|
||
<primary>kernel modules</primary>
|
||
<secondary>listing</secondary>
|
||
</indexterm>
|
||
|
||
<para>The kld interface is used through:</para>
|
||
|
||
<itemizedlist>
|
||
<listitem>
|
||
<simpara><command>kldload</command> - loads a new kernel
|
||
module</simpara></listitem>
|
||
<listitem>
|
||
<simpara><command>kldunload</command> - unloads a kernel
|
||
module</simpara></listitem>
|
||
<listitem>
|
||
<simpara><command>kldstat</command> - lists loaded
|
||
modules</simpara></listitem>
|
||
</itemizedlist>
|
||
|
||
<para>Skeleton Layout of a kernel module</para>
|
||
|
||
<programlisting>/*
|
||
* KLD Skeleton
|
||
* Inspired by Andrew Reiter's Daemonnews article
|
||
*/
|
||
|
||
#include <sys/types.h>
|
||
#include <sys/module.h>
|
||
#include <sys/systm.h> /* uprintf */
|
||
#include <sys/errno.h>
|
||
#include <sys/param.h> /* defines used in kernel.h */
|
||
#include <sys/kernel.h> /* types used in module initialization */
|
||
|
||
/*
|
||
* Load handler that deals with the loading and unloading of a KLD.
|
||
*/
|
||
|
||
static int
|
||
skel_loader(struct module *m, int what, void *arg)
|
||
{
|
||
int err = 0;
|
||
|
||
switch (what) {
|
||
case MOD_LOAD: /* kldload */
|
||
uprintf("Skeleton KLD loaded.\n");
|
||
break;
|
||
case MOD_UNLOAD:
|
||
uprintf("Skeleton KLD unloaded.\n");
|
||
break;
|
||
default:
|
||
err = EOPNOTSUPP;
|
||
break;
|
||
}
|
||
return(err);
|
||
}
|
||
|
||
/* Declare this module to the rest of the kernel */
|
||
|
||
static moduledata_t skel_mod = {
|
||
"skel",
|
||
skel_loader,
|
||
NULL
|
||
};
|
||
|
||
DECLARE_MODULE(skeleton, skel_mod, SI_SUB_KLD, SI_ORDER_ANY);</programlisting>
|
||
|
||
|
||
<sect2>
|
||
<title>Makefile</title>
|
||
|
||
<para>&os; provides a system makefile to simplify compiling a
|
||
kernel module.</para>
|
||
|
||
<programlisting>SRCS=skeleton.c
|
||
KMOD=skeleton
|
||
|
||
.include <bsd.kmod.mk></programlisting>
|
||
|
||
<para>Running <command>make</command> with this makefile
|
||
will create a file <filename>skeleton.ko</filename> that can
|
||
be loaded into the kernel by typing:</para>
|
||
|
||
<screen>&prompt.root; <userinput>kldload -v ./skeleton.ko</userinput></screen>
|
||
</sect2>
|
||
</sect1>
|
||
|
||
<sect1 xml:id="driverbasics-char">
|
||
<title>Character Devices</title>
|
||
|
||
<indexterm>
|
||
<primary>character devices</primary>
|
||
</indexterm>
|
||
<para>A character device driver is one that transfers data
|
||
directly to and from a user process. This is the most common
|
||
type of device driver and there are plenty of simple examples in
|
||
the source tree.</para>
|
||
|
||
<para>This simple example pseudo-device remembers whatever values
|
||
are written to it and can then echo them back when
|
||
read.</para>
|
||
|
||
<example>
|
||
<title>Example of a Sample Echo Pseudo-Device Driver for
|
||
&os; 10.X</title>
|
||
|
||
<programlisting>/*
|
||
* Simple Echo pseudo-device KLD
|
||
*
|
||
* Murray Stokely
|
||
* S<>ren (Xride) Straarup
|
||
* Eitan Adler
|
||
*/
|
||
|
||
#include <sys/types.h>
|
||
#include <sys/module.h>
|
||
#include <sys/systm.h> /* uprintf */
|
||
#include <sys/param.h> /* defines used in kernel.h */
|
||
#include <sys/kernel.h> /* types used in module initialization */
|
||
#include <sys/conf.h> /* cdevsw struct */
|
||
#include <sys/uio.h> /* uio struct */
|
||
#include <sys/malloc.h>
|
||
|
||
#define BUFFERSIZE 255
|
||
|
||
/* Function prototypes */
|
||
static d_open_t echo_open;
|
||
static d_close_t echo_close;
|
||
static d_read_t echo_read;
|
||
static d_write_t echo_write;
|
||
|
||
/* Character device entry points */
|
||
static struct cdevsw echo_cdevsw = {
|
||
.d_version = D_VERSION,
|
||
.d_open = echo_open,
|
||
.d_close = echo_close,
|
||
.d_read = echo_read,
|
||
.d_write = echo_write,
|
||
.d_name = "echo",
|
||
};
|
||
|
||
struct s_echo {
|
||
char msg[BUFFERSIZE + 1];
|
||
int len;
|
||
};
|
||
|
||
/* vars */
|
||
static struct cdev *echo_dev;
|
||
static struct s_echo *echomsg;
|
||
|
||
MALLOC_DECLARE(M_ECHOBUF);
|
||
MALLOC_DEFINE(M_ECHOBUF, "echobuffer", "buffer for echo module");
|
||
|
||
/*
|
||
* This function is called by the kld[un]load(2) system calls to
|
||
* determine what actions to take when a module is loaded or unloaded.
|
||
*/
|
||
static int
|
||
echo_loader(struct module *m __unused, int what, void *arg __unused)
|
||
{
|
||
int error = 0;
|
||
|
||
switch (what) {
|
||
case MOD_LOAD: /* kldload */
|
||
error = make_dev_p(MAKEDEV_CHECKNAME | MAKEDEV_WAITOK,
|
||
&echo_dev,
|
||
&echo_cdevsw,
|
||
0,
|
||
UID_ROOT,
|
||
GID_WHEEL,
|
||
0600,
|
||
"echo");
|
||
if (error != 0)
|
||
break;
|
||
|
||
echomsg = malloc(sizeof(*echomsg), M_ECHOBUF, M_WAITOK |
|
||
M_ZERO);
|
||
printf("Echo device loaded.\n");
|
||
break;
|
||
case MOD_UNLOAD:
|
||
destroy_dev(echo_dev);
|
||
free(echomsg, M_ECHOBUF);
|
||
printf("Echo device unloaded.\n");
|
||
break;
|
||
default:
|
||
error = EOPNOTSUPP;
|
||
break;
|
||
}
|
||
return (error);
|
||
}
|
||
|
||
static int
|
||
echo_open(struct cdev *dev __unused, int oflags __unused, int devtype __unused,
|
||
struct thread *td __unused)
|
||
{
|
||
int error = 0;
|
||
|
||
uprintf("Opened device \"echo\" successfully.\n");
|
||
return (error);
|
||
}
|
||
|
||
static int
|
||
echo_close(struct cdev *dev __unused, int fflag __unused, int devtype __unused,
|
||
struct thread *td __unused)
|
||
{
|
||
|
||
uprintf("Closing device \"echo\".\n");
|
||
return (0);
|
||
}
|
||
|
||
/*
|
||
* The read function just takes the buf that was saved via
|
||
* echo_write() and returns it to userland for accessing.
|
||
* uio(9)
|
||
*/
|
||
static int
|
||
echo_read(struct cdev *dev __unused, struct uio *uio, int ioflag __unused)
|
||
{
|
||
size_t amt;
|
||
int error;
|
||
|
||
/*
|
||
* How big is this read operation? Either as big as the user wants,
|
||
* or as big as the remaining data. Note that the 'len' does not
|
||
* include the trailing null character.
|
||
*/
|
||
amt = MIN(uio->uio_resid, uio->uio_offset >= echomsg->len + 1 ? 0 :
|
||
echomsg->len + 1 - uio->uio_offset);
|
||
|
||
if ((error = uiomove(echomsg->msg, amt, uio)) != 0)
|
||
uprintf("uiomove failed!\n");
|
||
|
||
return (error);
|
||
}
|
||
|
||
/*
|
||
* echo_write takes in a character string and saves it
|
||
* to buf for later accessing.
|
||
*/
|
||
static int
|
||
echo_write(struct cdev *dev __unused, struct uio *uio, int ioflag __unused)
|
||
{
|
||
size_t amt;
|
||
int error;
|
||
|
||
/*
|
||
* We either write from the beginning or are appending -- do
|
||
* not allow random access.
|
||
*/
|
||
if (uio->uio_offset != 0 && (uio->uio_offset != echomsg->len))
|
||
return (EINVAL);
|
||
|
||
/* This is a new message, reset length */
|
||
if (uio->uio_offset == 0)
|
||
echomsg->len = 0;
|
||
|
||
/* Copy the string in from user memory to kernel memory */
|
||
amt = MIN(uio->uio_resid, (BUFFERSIZE - echomsg->len));
|
||
|
||
error = uiomove(echomsg->msg + uio->uio_offset, amt, uio);
|
||
|
||
/* Now we need to null terminate and record the length */
|
||
echomsg->len = uio->uio_offset;
|
||
echomsg->msg[echomsg->len] = 0;
|
||
|
||
if (error != 0)
|
||
uprintf("Write failed: bad address!\n");
|
||
return (error);
|
||
}
|
||
|
||
DEV_MODULE(echo, echo_loader, NULL);</programlisting>
|
||
</example>
|
||
|
||
<para>With this driver loaded try:</para>
|
||
|
||
<screen>&prompt.root; <userinput>echo -n "Test Data" > /dev/echo</userinput>
|
||
&prompt.root; <userinput>cat /dev/echo</userinput>
|
||
Opened device "echo" successfully.
|
||
Test Data
|
||
Closing device "echo".</screen>
|
||
|
||
<para>Real hardware devices are described in the next
|
||
chapter.</para>
|
||
</sect1>
|
||
|
||
<sect1 xml:id="driverbasics-block">
|
||
<title>Block Devices (Are Gone)</title>
|
||
|
||
<indexterm><primary>block devices</primary></indexterm>
|
||
|
||
<para>Other &unix; systems may support a second type of disk
|
||
device known as block devices. Block devices are disk devices
|
||
for which the kernel provides caching. This caching makes
|
||
block-devices almost unusable, or at least dangerously
|
||
unreliable. The caching will reorder the sequence of write
|
||
operations, depriving the application of the ability to know the
|
||
exact disk contents at any one instant in time. This makes
|
||
predictable and reliable crash recovery of on-disk data
|
||
structures (filesystems, databases etc.) impossible. Since
|
||
writes may be delayed, there is no way the kernel can report to
|
||
the application which particular write operation encountered a
|
||
write error, this further compounds the consistency problem.
|
||
For this reason, no serious applications rely on block devices,
|
||
and in fact, almost all applications which access disks directly
|
||
take great pains to specify that character (or
|
||
<quote>raw</quote>) devices should always be used. Because the
|
||
implementation of the aliasing of each disk (partition) to two
|
||
devices with different semantics significantly complicated the
|
||
relevant kernel code &os; dropped support for cached disk
|
||
devices as part of the modernization of the disk I/O
|
||
infrastructure.</para>
|
||
</sect1>
|
||
|
||
<sect1 xml:id="driverbasics-net">
|
||
<title>Network Drivers</title>
|
||
|
||
<indexterm>
|
||
<primary>network devices</primary>
|
||
</indexterm>
|
||
<para>Drivers for network devices do not use device nodes in order
|
||
to be accessed. Their selection is based on other decisions
|
||
made inside the kernel and instead of calling open(), use of a
|
||
network device is generally introduced by using the system call
|
||
socket(2).</para>
|
||
|
||
<para>For more information see ifnet(9), the source of the
|
||
loopback device, and Bill Paul's network drivers.</para>
|
||
</sect1>
|
||
</chapter>
|