440 lines
13 KiB
XML
440 lines
13 KiB
XML
<?xml version="1.0" encoding="iso-8859-1"?>
|
||
<!--
|
||
The FreeBSD Documentation Project
|
||
|
||
$FreeBSD$
|
||
-->
|
||
|
||
<chapter id="driverbasics">
|
||
<chapterinfo>
|
||
<authorgroup>
|
||
<author>
|
||
<firstname>Murray</firstname>
|
||
<surname>Stokely</surname>
|
||
<contrib>Written by </contrib>
|
||
</author>
|
||
</authorgroup>
|
||
<authorgroup>
|
||
<author>
|
||
<firstname>Jörg</firstname>
|
||
<surname>Wunsch</surname>
|
||
<contrib>Based on intro(4) manual page by </contrib>
|
||
</author>
|
||
</authorgroup>
|
||
</chapterinfo>
|
||
<title>Writing FreeBSD Device Drivers</title>
|
||
|
||
<sect1 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>
|
||
<indexterm><primary>MAKEDEV</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 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>
|
||
|
||
<para>The kld interface is used through the following
|
||
privileged commands:
|
||
|
||
<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>
|
||
<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 the currently loaded
|
||
modules</simpara></listitem>
|
||
</itemizedlist>
|
||
</para>
|
||
|
||
<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>FreeBSD provides a makefile include that you can use to
|
||
quickly compile your kernel addition.</para>
|
||
|
||
<programlisting>SRCS=skeleton.c
|
||
KMOD=skeleton
|
||
|
||
.include <bsd.kmod.mk></programlisting>
|
||
|
||
<para>Simply running <command>make</command> with this makefile
|
||
will create a file <filename>skeleton.ko</filename> that can
|
||
be loaded into your system by typing:
|
||
<screen>&prompt.root; <userinput>kldload -v ./skeleton.ko</userinput></screen>
|
||
</para>
|
||
</sect2>
|
||
</sect1>
|
||
|
||
<sect1 id="driverbasics-access">
|
||
<title>Accessing a device driver</title>
|
||
|
||
<para>&unix; provides a common set of system calls for user
|
||
applications to use. The upper layers of the kernel dispatch
|
||
these calls to the corresponding device driver when a user
|
||
accesses a device node. The <command>/dev/MAKEDEV</command>
|
||
script makes most of the device nodes for your system but if you
|
||
are doing your own driver development it may be necessary to
|
||
create your own device nodes with <command>mknod</command>.
|
||
</para>
|
||
|
||
<sect2>
|
||
<title>Creating static device nodes</title>
|
||
|
||
<indexterm><primary>device nodes</primary><secondary>static</secondary></indexterm>
|
||
<indexterm><primary>mknod</primary></indexterm>
|
||
|
||
<para>The <command>mknod</command> command requires four
|
||
arguments to create a device node. You must specify the name
|
||
of the device node, the type of device, the major number of
|
||
the device, and the minor number of the device.</para>
|
||
</sect2>
|
||
|
||
<sect2>
|
||
<title>Dynamic device nodes</title>
|
||
|
||
<indexterm><primary>device nodes</primary><secondary>dynamic</secondary></indexterm>
|
||
<indexterm><primary>devfs</primary></indexterm>
|
||
|
||
<para>The device filesystem, or devfs, provides access to the
|
||
kernel's device namespace in the global filesystem namespace.
|
||
This eliminates the problems of potentially having a device
|
||
driver without a static device node, or a device node without
|
||
an installed device driver. Devfs is still a work in
|
||
progress, but it is already working quite nicely.</para>
|
||
</sect2>
|
||
|
||
</sect1>
|
||
|
||
<sect1 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
|
||
you write to it and can then supply them back to you when you
|
||
read from it.</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 256
|
||
|
||
/* 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];
|
||
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;
|
||
|
||
/* kmalloc memory for use by this driver */
|
||
echomsg = malloc(sizeof(*echomsg), M_ECHOBUF, M_WAITOK);
|
||
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 *p __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 *p __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)
|
||
{
|
||
int error, amt;
|
||
|
||
/*
|
||
* How big is this read operation? Either as big as the user wants,
|
||
* or as big as the remaining data
|
||
*/
|
||
|
||
amt = MIN(uio->uio_resid, echomsg->len - uio->uio_offset);
|
||
uio->uio_offset += amt;
|
||
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)
|
||
{
|
||
int error, amt;
|
||
|
||
/* Copy the string in from user memory to kernel memory */
|
||
|
||
/*
|
||
* 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 new message, reset length
|
||
*/
|
||
if (uio->uio_offset == 0)
|
||
echomsg->len = 0;
|
||
|
||
/* NULL character should be overridden */
|
||
if (echomsg->len != 0)
|
||
echomsg->len--;
|
||
|
||
/* 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, then record the length */
|
||
echomsg->len += amt + 1;
|
||
uio->uio_offset += amt + 1;
|
||
echomsg->msg[echomsg->len - 1] = 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 you should now be able to type
|
||
something like:</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 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 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>
|