First significant batch of content for the Developer's Handbook.
* Added introductory content for device driver chapter. * Included some example pseudo device drivers with (not-enough) commentary. * Added new chapters to the framework * Included pointers to more information in some of the unwritten chapters.
This commit is contained in:
parent
3c78aa06a4
commit
87fddebcea
Notes:
svn2git
2020-12-08 03:00:23 +00:00
svn path=/head/; revision=8296
3 changed files with 1866 additions and 18 deletions
|
@ -1,7 +1,7 @@
|
|||
<!--
|
||||
The FreeBSD Documentation Project
|
||||
|
||||
$FreeBSD: doc/en_US.ISO_8859-1/books/developers-handbook/book.sgml,v 1.2 2000/10/03 07:34:34 murray Exp $
|
||||
$FreeBSD: doc/en_US.ISO_8859-1/books/developers-handbook/book.sgml,v 1.3 2000/10/06 15:36:45 phantom Exp $
|
||||
-->
|
||||
|
||||
<!DOCTYPE BOOK PUBLIC "-//FreeBSD//DTD DocBook V3.1-Based Extension//EN" [
|
||||
|
@ -317,12 +317,609 @@
|
|||
<part id="devicedrivers">
|
||||
<title>Device Drivers</title>
|
||||
|
||||
<chapter id="oldbsddriver">
|
||||
<title>4.4BSD Driver Writing</title>
|
||||
<chapter id="driverbasics">
|
||||
<title>Writing FreeBSD Device Drivers</title>
|
||||
|
||||
<para>old ways, newbus, character/block devices, etc</para>
|
||||
<para>This chapter was written by Murray Stokely with selections from
|
||||
a variety of sources including the intro(4) man page by Joerg
|
||||
Wunsch.</para>
|
||||
|
||||
<sect1>
|
||||
<title>Introduction</title>
|
||||
<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
|
||||
behaviour 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>
|
||||
|
||||
<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 file system hierarchy. Until
|
||||
devfs is fully integrated into FreeBSD, each device node must
|
||||
be created statically and independent of the existence of the
|
||||
associated device driver. Most device nodes on the system are
|
||||
created by running <command>MAKEDEV</command>.</para>
|
||||
|
||||
<para>Device drivers can roughly be broken down into three
|
||||
categories; character (unbuffered), block (buffered), and
|
||||
network drivers.</para>
|
||||
</sect1>
|
||||
|
||||
<sect1>
|
||||
<title>Dynamic Kernel Linker Facility - KLD</title>
|
||||
<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
|
||||
administrator commands :
|
||||
<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 loadded
|
||||
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 = EINVAL;
|
||||
break;
|
||||
}
|
||||
return(err);
|
||||
}
|
||||
|
||||
/* Declare this module to the rest of the kernel */
|
||||
|
||||
DECLARE_MODULE(skeleton, skel_loader, 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 kldload -v ./skeleton.ko
|
||||
</screen>
|
||||
</para>
|
||||
</sect2>
|
||||
</sect1>
|
||||
|
||||
<sect1>
|
||||
<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>
|
||||
<para>The <command>mknod</command> command requires four
|
||||
arguments to create a device node. You must specify the
|
||||
name of this 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>
|
||||
<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. Unfortunately, devfs is still a
|
||||
work in progress.</para>
|
||||
</sect2>
|
||||
|
||||
</sect1>
|
||||
|
||||
<sect1>
|
||||
<title>Character Devices</title>
|
||||
<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>
|
||||
<programlisting>
|
||||
/*
|
||||
* Simple `echo' pseudo-device KLD
|
||||
*
|
||||
* Murray Stokely
|
||||
*/
|
||||
|
||||
#define MIN(a,b) (((a) < (b)) ? (a) : (b))
|
||||
|
||||
#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 */
|
||||
#include <sys/conf.h> /* cdevsw struct */
|
||||
#include <sys/uio.h> /* uio struct */
|
||||
#include <sys/malloc.h>
|
||||
|
||||
#define BUFFERSIZE 256
|
||||
|
||||
/* Function prototypes */
|
||||
d_open_t echo_open;
|
||||
d_close_t echo_close;
|
||||
d_read_t echo_read;
|
||||
d_write_t echo_write;
|
||||
|
||||
/* Character device entry points */
|
||||
static struct cdevsw echo_cdevsw = {
|
||||
echo_open,
|
||||
echo_close,
|
||||
echo_read,
|
||||
echo_write,
|
||||
noioctl,
|
||||
nopoll,
|
||||
nommap,
|
||||
nostrategy,
|
||||
"echo",
|
||||
33, /* reserved for lkms - /usr/src/sys/conf/majors */
|
||||
nodump,
|
||||
nopsize,
|
||||
D_TTY,
|
||||
-1
|
||||
};
|
||||
|
||||
typedef struct s_echo {
|
||||
char msg[BUFFERSIZE];
|
||||
int len;
|
||||
} t_echo;
|
||||
|
||||
/* vars */
|
||||
static dev_t sdev;
|
||||
static int len;
|
||||
static int count;
|
||||
static t_echo *echomsg;
|
||||
|
||||
MALLOC_DECLARE(M_ECHOBUF);
|
||||
MALLOC_DEFINE(M_ECHOBUF, "echobuffer", "buffer for echo module");
|
||||
|
||||
/*
|
||||
* This function acts 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, int what, void *arg)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
switch (what) {
|
||||
case MOD_LOAD: /* kldload */
|
||||
sdev = make_dev(<literal>&</literal>echo_cdevsw,
|
||||
0,
|
||||
UID_ROOT,
|
||||
GID_WHEEL,
|
||||
0600,
|
||||
"echo");
|
||||
/* kmalloc memory for use by this driver */
|
||||
/* malloc(256,M_ECHOBUF,M_WAITOK); */
|
||||
MALLOC(echomsg, t_echo *, sizeof(t_echo), M_ECHOBUF, M_WAITOK);
|
||||
printf("Echo device loaded.\n");
|
||||
break;
|
||||
case MOD_UNLOAD:
|
||||
destroy_dev(sdev);
|
||||
FREE(echomsg,M_ECHOBUF);
|
||||
printf("Echo device unloaded.\n");
|
||||
break;
|
||||
default:
|
||||
err = EINVAL;
|
||||
break;
|
||||
}
|
||||
return(err);
|
||||
}
|
||||
|
||||
int
|
||||
echo_open(dev_t dev, int oflags, int devtype, struct proc *p)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
uprintf("Opened device \"echo\" successfully.\n");
|
||||
return(err);
|
||||
}
|
||||
|
||||
int
|
||||
echo_close(dev_t dev, int fflag, int devtype, struct proc *p)
|
||||
{
|
||||
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)
|
||||
*/
|
||||
|
||||
int
|
||||
echo_read(dev_t dev, struct uio *uio, int ioflag)
|
||||
{
|
||||
int err = 0;
|
||||
int 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 > 0) ? echomsg->len - uio->uio_offset : 0);
|
||||
if ((err = uiomove(echomsg->msg + uio->uio_offset,amt,uio)) != 0) {
|
||||
uprintf("uiomove failed!\n");
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* echo_write takes in a character string and saves it
|
||||
* to buf for later accessing.
|
||||
*/
|
||||
|
||||
int
|
||||
echo_write(dev_t dev, struct uio *uio, int ioflag)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
/* Copy the string in from user memory to kernel memory */
|
||||
err = copyin(uio->uio_iov->iov_base, echomsg->msg, MIN(uio->uio_iov->iov_len,BUFFERSIZE));
|
||||
|
||||
/* Now we need to null terminate */
|
||||
*(echomsg->msg + MIN(uio->uio_iov->iov_len,BUFFERSIZE)) = 0;
|
||||
/* Record the length */
|
||||
echomsg->len = MIN(uio->uio_iov->iov_len,BUFFERSIZE);
|
||||
|
||||
if (err != 0) {
|
||||
uprintf("Write failed: bad address!\n");
|
||||
}
|
||||
|
||||
count++;
|
||||
return(err);
|
||||
}
|
||||
|
||||
DEV_MODULE(echo,echo_loader,NULL);
|
||||
</programlisting>
|
||||
|
||||
<para>To install this driver you will first need to make a node on
|
||||
your filesystem with a command such as : </para>
|
||||
<screen>
|
||||
&prompt.root mknod /dev/echo c 33 0
|
||||
</screen>
|
||||
<para>With this driver loaded you should now be able to type something
|
||||
like :</para>
|
||||
<screen>
|
||||
&prompt.root echo -n "Test Data" > /dev/echo
|
||||
&prompt.root cat /dev/echo
|
||||
Test Data
|
||||
</screen>
|
||||
<para>Real hardware devices in the next chapter..</para>
|
||||
|
||||
<para>Additional Resources
|
||||
<itemizedlist>
|
||||
<listitem><simpara><ulink
|
||||
url="http://www.daemonnews.org/200010/blueprints.html">Dynamic
|
||||
Kernel Linker (KLD) Facility Programming Tutorial</ulink> -
|
||||
<ulink url="http://www.daemonnews.org">Daemonnews</ulink> October 2000</simpara></listitem>
|
||||
<listitem><simpara><ulink
|
||||
url="http://www.daemonnews.org/200007/newbus-intro.html">How
|
||||
to Write Kernel Drivers with NEWBUS</ulink> - <ulink
|
||||
url="http://www.daemonnews.org">Daemonnews</ulink> July
|
||||
2000</simpara></listitem>
|
||||
</itemizedlist>
|
||||
</para>
|
||||
</sect1>
|
||||
|
||||
<sect1>
|
||||
<title>Block Devices</title>
|
||||
<para>A block device driver transfers data to and from the
|
||||
operating system's buffer cache. They are solely intended to
|
||||
layer a file system on top of them. For this reason they are
|
||||
normally implemented for disks and disk-like devices only.</para>
|
||||
|
||||
<para>Example test data generator ... </para>
|
||||
|
||||
<para>Example ramdisk device ... </para>
|
||||
|
||||
<para>Real hardware devices in the next chapter..</para>
|
||||
</sect1>
|
||||
|
||||
<sect1>
|
||||
<title>Network Drivers</title>
|
||||
<para>Drivers for network devices do not use device nodes in
|
||||
ord 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>man ifnet(), loopback device, Bill Pauls drivers, etc..</para>
|
||||
</sect1>
|
||||
|
||||
</chapter>
|
||||
|
||||
<chapter id="pci">
|
||||
<title>PCI Devices</title>
|
||||
|
||||
<para>This chapter will talk about the FreeBSD mechanisms for
|
||||
writing a device driver for a device on a PCI bus.</para>
|
||||
|
||||
<sect1><title>Probe and Attach</title>
|
||||
|
||||
<para>Information here about how the PCI bus code iterates
|
||||
through the unattached devices and see if a newly loaded kld
|
||||
will attach to any of them.</para>
|
||||
<programlisting>
|
||||
/*
|
||||
* Simple KLD to play with the PCI functions.
|
||||
*
|
||||
* Murray Stokely
|
||||
*/
|
||||
|
||||
#define MIN(a,b) (((a) < (b)) ? (a) : (b))
|
||||
|
||||
#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 */
|
||||
#include <sys/conf.h> /* cdevsw struct */
|
||||
#include <sys/uio.h> /* uio struct */
|
||||
#include <sys/malloc.h>
|
||||
#include <sys/bus.h> /* structs, prototypes for pci bus stuff */
|
||||
|
||||
#include <pci/pcivar.h> /* For get_pci macros! */
|
||||
|
||||
/* Function prototypes */
|
||||
d_open_t mypci_open;
|
||||
d_close_t mypci_close;
|
||||
d_read_t mypci_read;
|
||||
d_write_t mypci_write;
|
||||
|
||||
/* Character device entry points */
|
||||
|
||||
static struct cdevsw mypci_cdevsw = {
|
||||
mypci_open,
|
||||
mypci_close,
|
||||
mypci_read,
|
||||
mypci_write,
|
||||
noioctl,
|
||||
nopoll,
|
||||
nommap,
|
||||
nostrategy,
|
||||
"mypci",
|
||||
36, /* reserved for lkms - /usr/src/sys/conf/majors */
|
||||
nodump,
|
||||
nopsize,
|
||||
D_TTY,
|
||||
-1
|
||||
};
|
||||
|
||||
/* vars */
|
||||
static dev_t sdev;
|
||||
|
||||
/* We're more interested in probe/attach than with
|
||||
open/close/read/write at this point */
|
||||
|
||||
int
|
||||
mypci_open(dev_t dev, int oflags, int devtype, struct proc *p)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
uprintf("Opened device \"mypci\" successfully.\n");
|
||||
return(err);
|
||||
}
|
||||
|
||||
int
|
||||
mypci_close(dev_t dev, int fflag, int devtype, struct proc *p)
|
||||
{
|
||||
int err=0;
|
||||
|
||||
uprintf("Closing device \"mypci.\"\n");
|
||||
return(err);
|
||||
}
|
||||
|
||||
int
|
||||
mypci_read(dev_t dev, struct uio *uio, int ioflag)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
uprintf("mypci read!\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
int
|
||||
mypci_write(dev_t dev, struct uio *uio, int ioflag)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
uprintf("mypci write!\n");
|
||||
return(err);
|
||||
}
|
||||
|
||||
/* PCI Support Functions */
|
||||
|
||||
/*
|
||||
* Return identification string if this is device is ours.
|
||||
*/
|
||||
static int
|
||||
mypci_probe(device_t dev)
|
||||
{
|
||||
uprintf("MyPCI Probe\n"
|
||||
"Vendor ID : 0x%x\n"
|
||||
"Device ID : 0x%x\n",pci_get_vendor(dev),pci_get_device(dev));
|
||||
|
||||
if (pci_get_vendor(dev) == 0x11c1) {
|
||||
uprintf("We've got the Winmodem, probe successful!\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ENXIO;
|
||||
}
|
||||
|
||||
/* Attach function is only called if the probe is successful */
|
||||
|
||||
static int
|
||||
mypci_attach(device_t dev)
|
||||
{
|
||||
uprintf("MyPCI Attach for : deviceID : 0x%x\n",pci_get_vendor(dev));
|
||||
sdev = make_dev(<literal>&</literal>mypci_cdevsw,
|
||||
0,
|
||||
UID_ROOT,
|
||||
GID_WHEEL,
|
||||
0600,
|
||||
"mypci");
|
||||
uprintf("Mypci device loaded.\n");
|
||||
return ENXIO;
|
||||
}
|
||||
|
||||
/* Detach device. */
|
||||
|
||||
static int
|
||||
mypci_detach(device_t dev)
|
||||
{
|
||||
uprintf("Mypci detach!\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Called during system shutdown after sync. */
|
||||
|
||||
static int
|
||||
mypci_shutdown(device_t dev)
|
||||
{
|
||||
uprintf("Mypci shutdown!\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Device suspend routine.
|
||||
*/
|
||||
static int
|
||||
mypci_suspend(device_t dev)
|
||||
{
|
||||
uprintf("Mypci suspend!\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Device resume routine.
|
||||
*/
|
||||
|
||||
static int
|
||||
mypci_resume(device_t dev)
|
||||
{
|
||||
uprintf("Mypci resume!\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static device_method_t mypci_methods[] = {
|
||||
/* Device interface */
|
||||
DEVMETHOD(device_probe, mypci_probe),
|
||||
DEVMETHOD(device_attach, mypci_attach),
|
||||
DEVMETHOD(device_detach, mypci_detach),
|
||||
DEVMETHOD(device_shutdown, mypci_shutdown),
|
||||
DEVMETHOD(device_suspend, mypci_suspend),
|
||||
DEVMETHOD(device_resume, mypci_resume),
|
||||
|
||||
{ 0, 0 }
|
||||
};
|
||||
|
||||
static driver_t mypci_driver = {
|
||||
"mypci",
|
||||
mypci_methods,
|
||||
0,
|
||||
/* sizeof(struct mypci_softc), */
|
||||
};
|
||||
|
||||
static devclass_t mypci_devclass;
|
||||
|
||||
DRIVER_MODULE(mypci, pci, mypci_driver, mypci_devclass, 0, 0);
|
||||
</programlisting>
|
||||
|
||||
<para>Additional Resources
|
||||
<itemizedlist>
|
||||
<listitem><simpara><ulink
|
||||
url="http://www.pcisig.org">PCI Special Interest
|
||||
Group</ulink></simpara></listitem>
|
||||
<listitem><simpara>PCI System Architecture, Fourth Edition by
|
||||
Tom Shanley, et al.</simpara></listitem>
|
||||
</itemizedlist>
|
||||
</para>
|
||||
</sect1>
|
||||
</chapter>
|
||||
|
||||
<chapter id="usb">
|
||||
<title>USB Devices</title>
|
||||
|
||||
<para>This chapter will talk about the FreeBSD mechanisms for
|
||||
writing a device driver for a device on a USB bus.</para>
|
||||
</chapter>
|
||||
|
||||
<chapter id="newbus">
|
||||
<title>NewBus</title>
|
||||
|
||||
<para>This chapter will talk about the FreeBSD NewBus
|
||||
architecture.</para>
|
||||
</chapter>
|
||||
|
||||
</part>
|
||||
|
||||
<part id="architectures">
|
||||
|
@ -331,8 +928,27 @@
|
|||
<chapter id="ia32">
|
||||
<title>IA-32</title>
|
||||
|
||||
<para>Detail the (major) differences between IA-32, IA-64, PPC,
|
||||
ARM, Sparc, Alpha, etc</para>
|
||||
<para>Talk about the architectural specifics of FreeBSD/x86.</para>
|
||||
|
||||
</chapter>
|
||||
|
||||
<chapter id="alpha">
|
||||
<title>Alpha</title>
|
||||
|
||||
<para>Talk about the architectural specifics of
|
||||
FreeBSD/alpha.</para>
|
||||
|
||||
<para>Explanation of allignment errors, how to fix, how to
|
||||
ignore.</para>
|
||||
|
||||
<para>Example assembly language code for FreeBSD/alpha.</para>
|
||||
</chapter>
|
||||
|
||||
<chapter id="ia64">
|
||||
<title>IA-64</title>
|
||||
|
||||
<para>Talk about the architectural specifics of
|
||||
FreeBSD/ia64.</para>
|
||||
|
||||
</chapter>
|
||||
</part>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<!--
|
||||
The FreeBSD Documentation Project
|
||||
|
||||
$FreeBSD: doc/en_US.ISO_8859-1/books/developers-handbook/book.sgml,v 1.2 2000/10/03 07:34:34 murray Exp $
|
||||
$FreeBSD: doc/en_US.ISO_8859-1/books/developers-handbook/book.sgml,v 1.3 2000/10/06 15:36:45 phantom Exp $
|
||||
-->
|
||||
|
||||
<!DOCTYPE BOOK PUBLIC "-//FreeBSD//DTD DocBook V3.1-Based Extension//EN" [
|
||||
|
@ -317,12 +317,609 @@
|
|||
<part id="devicedrivers">
|
||||
<title>Device Drivers</title>
|
||||
|
||||
<chapter id="oldbsddriver">
|
||||
<title>4.4BSD Driver Writing</title>
|
||||
<chapter id="driverbasics">
|
||||
<title>Writing FreeBSD Device Drivers</title>
|
||||
|
||||
<para>old ways, newbus, character/block devices, etc</para>
|
||||
<para>This chapter was written by Murray Stokely with selections from
|
||||
a variety of sources including the intro(4) man page by Joerg
|
||||
Wunsch.</para>
|
||||
|
||||
<sect1>
|
||||
<title>Introduction</title>
|
||||
<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
|
||||
behaviour 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>
|
||||
|
||||
<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 file system hierarchy. Until
|
||||
devfs is fully integrated into FreeBSD, each device node must
|
||||
be created statically and independent of the existence of the
|
||||
associated device driver. Most device nodes on the system are
|
||||
created by running <command>MAKEDEV</command>.</para>
|
||||
|
||||
<para>Device drivers can roughly be broken down into three
|
||||
categories; character (unbuffered), block (buffered), and
|
||||
network drivers.</para>
|
||||
</sect1>
|
||||
|
||||
<sect1>
|
||||
<title>Dynamic Kernel Linker Facility - KLD</title>
|
||||
<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
|
||||
administrator commands :
|
||||
<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 loadded
|
||||
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 = EINVAL;
|
||||
break;
|
||||
}
|
||||
return(err);
|
||||
}
|
||||
|
||||
/* Declare this module to the rest of the kernel */
|
||||
|
||||
DECLARE_MODULE(skeleton, skel_loader, 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 kldload -v ./skeleton.ko
|
||||
</screen>
|
||||
</para>
|
||||
</sect2>
|
||||
</sect1>
|
||||
|
||||
<sect1>
|
||||
<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>
|
||||
<para>The <command>mknod</command> command requires four
|
||||
arguments to create a device node. You must specify the
|
||||
name of this 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>
|
||||
<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. Unfortunately, devfs is still a
|
||||
work in progress.</para>
|
||||
</sect2>
|
||||
|
||||
</sect1>
|
||||
|
||||
<sect1>
|
||||
<title>Character Devices</title>
|
||||
<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>
|
||||
<programlisting>
|
||||
/*
|
||||
* Simple `echo' pseudo-device KLD
|
||||
*
|
||||
* Murray Stokely
|
||||
*/
|
||||
|
||||
#define MIN(a,b) (((a) < (b)) ? (a) : (b))
|
||||
|
||||
#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 */
|
||||
#include <sys/conf.h> /* cdevsw struct */
|
||||
#include <sys/uio.h> /* uio struct */
|
||||
#include <sys/malloc.h>
|
||||
|
||||
#define BUFFERSIZE 256
|
||||
|
||||
/* Function prototypes */
|
||||
d_open_t echo_open;
|
||||
d_close_t echo_close;
|
||||
d_read_t echo_read;
|
||||
d_write_t echo_write;
|
||||
|
||||
/* Character device entry points */
|
||||
static struct cdevsw echo_cdevsw = {
|
||||
echo_open,
|
||||
echo_close,
|
||||
echo_read,
|
||||
echo_write,
|
||||
noioctl,
|
||||
nopoll,
|
||||
nommap,
|
||||
nostrategy,
|
||||
"echo",
|
||||
33, /* reserved for lkms - /usr/src/sys/conf/majors */
|
||||
nodump,
|
||||
nopsize,
|
||||
D_TTY,
|
||||
-1
|
||||
};
|
||||
|
||||
typedef struct s_echo {
|
||||
char msg[BUFFERSIZE];
|
||||
int len;
|
||||
} t_echo;
|
||||
|
||||
/* vars */
|
||||
static dev_t sdev;
|
||||
static int len;
|
||||
static int count;
|
||||
static t_echo *echomsg;
|
||||
|
||||
MALLOC_DECLARE(M_ECHOBUF);
|
||||
MALLOC_DEFINE(M_ECHOBUF, "echobuffer", "buffer for echo module");
|
||||
|
||||
/*
|
||||
* This function acts 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, int what, void *arg)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
switch (what) {
|
||||
case MOD_LOAD: /* kldload */
|
||||
sdev = make_dev(<literal>&</literal>echo_cdevsw,
|
||||
0,
|
||||
UID_ROOT,
|
||||
GID_WHEEL,
|
||||
0600,
|
||||
"echo");
|
||||
/* kmalloc memory for use by this driver */
|
||||
/* malloc(256,M_ECHOBUF,M_WAITOK); */
|
||||
MALLOC(echomsg, t_echo *, sizeof(t_echo), M_ECHOBUF, M_WAITOK);
|
||||
printf("Echo device loaded.\n");
|
||||
break;
|
||||
case MOD_UNLOAD:
|
||||
destroy_dev(sdev);
|
||||
FREE(echomsg,M_ECHOBUF);
|
||||
printf("Echo device unloaded.\n");
|
||||
break;
|
||||
default:
|
||||
err = EINVAL;
|
||||
break;
|
||||
}
|
||||
return(err);
|
||||
}
|
||||
|
||||
int
|
||||
echo_open(dev_t dev, int oflags, int devtype, struct proc *p)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
uprintf("Opened device \"echo\" successfully.\n");
|
||||
return(err);
|
||||
}
|
||||
|
||||
int
|
||||
echo_close(dev_t dev, int fflag, int devtype, struct proc *p)
|
||||
{
|
||||
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)
|
||||
*/
|
||||
|
||||
int
|
||||
echo_read(dev_t dev, struct uio *uio, int ioflag)
|
||||
{
|
||||
int err = 0;
|
||||
int 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 > 0) ? echomsg->len - uio->uio_offset : 0);
|
||||
if ((err = uiomove(echomsg->msg + uio->uio_offset,amt,uio)) != 0) {
|
||||
uprintf("uiomove failed!\n");
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* echo_write takes in a character string and saves it
|
||||
* to buf for later accessing.
|
||||
*/
|
||||
|
||||
int
|
||||
echo_write(dev_t dev, struct uio *uio, int ioflag)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
/* Copy the string in from user memory to kernel memory */
|
||||
err = copyin(uio->uio_iov->iov_base, echomsg->msg, MIN(uio->uio_iov->iov_len,BUFFERSIZE));
|
||||
|
||||
/* Now we need to null terminate */
|
||||
*(echomsg->msg + MIN(uio->uio_iov->iov_len,BUFFERSIZE)) = 0;
|
||||
/* Record the length */
|
||||
echomsg->len = MIN(uio->uio_iov->iov_len,BUFFERSIZE);
|
||||
|
||||
if (err != 0) {
|
||||
uprintf("Write failed: bad address!\n");
|
||||
}
|
||||
|
||||
count++;
|
||||
return(err);
|
||||
}
|
||||
|
||||
DEV_MODULE(echo,echo_loader,NULL);
|
||||
</programlisting>
|
||||
|
||||
<para>To install this driver you will first need to make a node on
|
||||
your filesystem with a command such as : </para>
|
||||
<screen>
|
||||
&prompt.root mknod /dev/echo c 33 0
|
||||
</screen>
|
||||
<para>With this driver loaded you should now be able to type something
|
||||
like :</para>
|
||||
<screen>
|
||||
&prompt.root echo -n "Test Data" > /dev/echo
|
||||
&prompt.root cat /dev/echo
|
||||
Test Data
|
||||
</screen>
|
||||
<para>Real hardware devices in the next chapter..</para>
|
||||
|
||||
<para>Additional Resources
|
||||
<itemizedlist>
|
||||
<listitem><simpara><ulink
|
||||
url="http://www.daemonnews.org/200010/blueprints.html">Dynamic
|
||||
Kernel Linker (KLD) Facility Programming Tutorial</ulink> -
|
||||
<ulink url="http://www.daemonnews.org">Daemonnews</ulink> October 2000</simpara></listitem>
|
||||
<listitem><simpara><ulink
|
||||
url="http://www.daemonnews.org/200007/newbus-intro.html">How
|
||||
to Write Kernel Drivers with NEWBUS</ulink> - <ulink
|
||||
url="http://www.daemonnews.org">Daemonnews</ulink> July
|
||||
2000</simpara></listitem>
|
||||
</itemizedlist>
|
||||
</para>
|
||||
</sect1>
|
||||
|
||||
<sect1>
|
||||
<title>Block Devices</title>
|
||||
<para>A block device driver transfers data to and from the
|
||||
operating system's buffer cache. They are solely intended to
|
||||
layer a file system on top of them. For this reason they are
|
||||
normally implemented for disks and disk-like devices only.</para>
|
||||
|
||||
<para>Example test data generator ... </para>
|
||||
|
||||
<para>Example ramdisk device ... </para>
|
||||
|
||||
<para>Real hardware devices in the next chapter..</para>
|
||||
</sect1>
|
||||
|
||||
<sect1>
|
||||
<title>Network Drivers</title>
|
||||
<para>Drivers for network devices do not use device nodes in
|
||||
ord 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>man ifnet(), loopback device, Bill Pauls drivers, etc..</para>
|
||||
</sect1>
|
||||
|
||||
</chapter>
|
||||
|
||||
<chapter id="pci">
|
||||
<title>PCI Devices</title>
|
||||
|
||||
<para>This chapter will talk about the FreeBSD mechanisms for
|
||||
writing a device driver for a device on a PCI bus.</para>
|
||||
|
||||
<sect1><title>Probe and Attach</title>
|
||||
|
||||
<para>Information here about how the PCI bus code iterates
|
||||
through the unattached devices and see if a newly loaded kld
|
||||
will attach to any of them.</para>
|
||||
<programlisting>
|
||||
/*
|
||||
* Simple KLD to play with the PCI functions.
|
||||
*
|
||||
* Murray Stokely
|
||||
*/
|
||||
|
||||
#define MIN(a,b) (((a) < (b)) ? (a) : (b))
|
||||
|
||||
#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 */
|
||||
#include <sys/conf.h> /* cdevsw struct */
|
||||
#include <sys/uio.h> /* uio struct */
|
||||
#include <sys/malloc.h>
|
||||
#include <sys/bus.h> /* structs, prototypes for pci bus stuff */
|
||||
|
||||
#include <pci/pcivar.h> /* For get_pci macros! */
|
||||
|
||||
/* Function prototypes */
|
||||
d_open_t mypci_open;
|
||||
d_close_t mypci_close;
|
||||
d_read_t mypci_read;
|
||||
d_write_t mypci_write;
|
||||
|
||||
/* Character device entry points */
|
||||
|
||||
static struct cdevsw mypci_cdevsw = {
|
||||
mypci_open,
|
||||
mypci_close,
|
||||
mypci_read,
|
||||
mypci_write,
|
||||
noioctl,
|
||||
nopoll,
|
||||
nommap,
|
||||
nostrategy,
|
||||
"mypci",
|
||||
36, /* reserved for lkms - /usr/src/sys/conf/majors */
|
||||
nodump,
|
||||
nopsize,
|
||||
D_TTY,
|
||||
-1
|
||||
};
|
||||
|
||||
/* vars */
|
||||
static dev_t sdev;
|
||||
|
||||
/* We're more interested in probe/attach than with
|
||||
open/close/read/write at this point */
|
||||
|
||||
int
|
||||
mypci_open(dev_t dev, int oflags, int devtype, struct proc *p)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
uprintf("Opened device \"mypci\" successfully.\n");
|
||||
return(err);
|
||||
}
|
||||
|
||||
int
|
||||
mypci_close(dev_t dev, int fflag, int devtype, struct proc *p)
|
||||
{
|
||||
int err=0;
|
||||
|
||||
uprintf("Closing device \"mypci.\"\n");
|
||||
return(err);
|
||||
}
|
||||
|
||||
int
|
||||
mypci_read(dev_t dev, struct uio *uio, int ioflag)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
uprintf("mypci read!\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
int
|
||||
mypci_write(dev_t dev, struct uio *uio, int ioflag)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
uprintf("mypci write!\n");
|
||||
return(err);
|
||||
}
|
||||
|
||||
/* PCI Support Functions */
|
||||
|
||||
/*
|
||||
* Return identification string if this is device is ours.
|
||||
*/
|
||||
static int
|
||||
mypci_probe(device_t dev)
|
||||
{
|
||||
uprintf("MyPCI Probe\n"
|
||||
"Vendor ID : 0x%x\n"
|
||||
"Device ID : 0x%x\n",pci_get_vendor(dev),pci_get_device(dev));
|
||||
|
||||
if (pci_get_vendor(dev) == 0x11c1) {
|
||||
uprintf("We've got the Winmodem, probe successful!\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ENXIO;
|
||||
}
|
||||
|
||||
/* Attach function is only called if the probe is successful */
|
||||
|
||||
static int
|
||||
mypci_attach(device_t dev)
|
||||
{
|
||||
uprintf("MyPCI Attach for : deviceID : 0x%x\n",pci_get_vendor(dev));
|
||||
sdev = make_dev(<literal>&</literal>mypci_cdevsw,
|
||||
0,
|
||||
UID_ROOT,
|
||||
GID_WHEEL,
|
||||
0600,
|
||||
"mypci");
|
||||
uprintf("Mypci device loaded.\n");
|
||||
return ENXIO;
|
||||
}
|
||||
|
||||
/* Detach device. */
|
||||
|
||||
static int
|
||||
mypci_detach(device_t dev)
|
||||
{
|
||||
uprintf("Mypci detach!\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Called during system shutdown after sync. */
|
||||
|
||||
static int
|
||||
mypci_shutdown(device_t dev)
|
||||
{
|
||||
uprintf("Mypci shutdown!\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Device suspend routine.
|
||||
*/
|
||||
static int
|
||||
mypci_suspend(device_t dev)
|
||||
{
|
||||
uprintf("Mypci suspend!\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Device resume routine.
|
||||
*/
|
||||
|
||||
static int
|
||||
mypci_resume(device_t dev)
|
||||
{
|
||||
uprintf("Mypci resume!\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static device_method_t mypci_methods[] = {
|
||||
/* Device interface */
|
||||
DEVMETHOD(device_probe, mypci_probe),
|
||||
DEVMETHOD(device_attach, mypci_attach),
|
||||
DEVMETHOD(device_detach, mypci_detach),
|
||||
DEVMETHOD(device_shutdown, mypci_shutdown),
|
||||
DEVMETHOD(device_suspend, mypci_suspend),
|
||||
DEVMETHOD(device_resume, mypci_resume),
|
||||
|
||||
{ 0, 0 }
|
||||
};
|
||||
|
||||
static driver_t mypci_driver = {
|
||||
"mypci",
|
||||
mypci_methods,
|
||||
0,
|
||||
/* sizeof(struct mypci_softc), */
|
||||
};
|
||||
|
||||
static devclass_t mypci_devclass;
|
||||
|
||||
DRIVER_MODULE(mypci, pci, mypci_driver, mypci_devclass, 0, 0);
|
||||
</programlisting>
|
||||
|
||||
<para>Additional Resources
|
||||
<itemizedlist>
|
||||
<listitem><simpara><ulink
|
||||
url="http://www.pcisig.org">PCI Special Interest
|
||||
Group</ulink></simpara></listitem>
|
||||
<listitem><simpara>PCI System Architecture, Fourth Edition by
|
||||
Tom Shanley, et al.</simpara></listitem>
|
||||
</itemizedlist>
|
||||
</para>
|
||||
</sect1>
|
||||
</chapter>
|
||||
|
||||
<chapter id="usb">
|
||||
<title>USB Devices</title>
|
||||
|
||||
<para>This chapter will talk about the FreeBSD mechanisms for
|
||||
writing a device driver for a device on a USB bus.</para>
|
||||
</chapter>
|
||||
|
||||
<chapter id="newbus">
|
||||
<title>NewBus</title>
|
||||
|
||||
<para>This chapter will talk about the FreeBSD NewBus
|
||||
architecture.</para>
|
||||
</chapter>
|
||||
|
||||
</part>
|
||||
|
||||
<part id="architectures">
|
||||
|
@ -331,8 +928,27 @@
|
|||
<chapter id="ia32">
|
||||
<title>IA-32</title>
|
||||
|
||||
<para>Detail the (major) differences between IA-32, IA-64, PPC,
|
||||
ARM, Sparc, Alpha, etc</para>
|
||||
<para>Talk about the architectural specifics of FreeBSD/x86.</para>
|
||||
|
||||
</chapter>
|
||||
|
||||
<chapter id="alpha">
|
||||
<title>Alpha</title>
|
||||
|
||||
<para>Talk about the architectural specifics of
|
||||
FreeBSD/alpha.</para>
|
||||
|
||||
<para>Explanation of allignment errors, how to fix, how to
|
||||
ignore.</para>
|
||||
|
||||
<para>Example assembly language code for FreeBSD/alpha.</para>
|
||||
</chapter>
|
||||
|
||||
<chapter id="ia64">
|
||||
<title>IA-64</title>
|
||||
|
||||
<para>Talk about the architectural specifics of
|
||||
FreeBSD/ia64.</para>
|
||||
|
||||
</chapter>
|
||||
</part>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<!--
|
||||
The FreeBSD Documentation Project
|
||||
|
||||
$FreeBSD: doc/en_US.ISO_8859-1/books/developers-handbook/book.sgml,v 1.2 2000/10/03 07:34:34 murray Exp $
|
||||
$FreeBSD: doc/en_US.ISO_8859-1/books/developers-handbook/book.sgml,v 1.3 2000/10/06 15:36:45 phantom Exp $
|
||||
-->
|
||||
|
||||
<!DOCTYPE BOOK PUBLIC "-//FreeBSD//DTD DocBook V3.1-Based Extension//EN" [
|
||||
|
@ -317,12 +317,609 @@
|
|||
<part id="devicedrivers">
|
||||
<title>Device Drivers</title>
|
||||
|
||||
<chapter id="oldbsddriver">
|
||||
<title>4.4BSD Driver Writing</title>
|
||||
<chapter id="driverbasics">
|
||||
<title>Writing FreeBSD Device Drivers</title>
|
||||
|
||||
<para>old ways, newbus, character/block devices, etc</para>
|
||||
<para>This chapter was written by Murray Stokely with selections from
|
||||
a variety of sources including the intro(4) man page by Joerg
|
||||
Wunsch.</para>
|
||||
|
||||
<sect1>
|
||||
<title>Introduction</title>
|
||||
<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
|
||||
behaviour 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>
|
||||
|
||||
<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 file system hierarchy. Until
|
||||
devfs is fully integrated into FreeBSD, each device node must
|
||||
be created statically and independent of the existence of the
|
||||
associated device driver. Most device nodes on the system are
|
||||
created by running <command>MAKEDEV</command>.</para>
|
||||
|
||||
<para>Device drivers can roughly be broken down into three
|
||||
categories; character (unbuffered), block (buffered), and
|
||||
network drivers.</para>
|
||||
</sect1>
|
||||
|
||||
<sect1>
|
||||
<title>Dynamic Kernel Linker Facility - KLD</title>
|
||||
<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
|
||||
administrator commands :
|
||||
<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 loadded
|
||||
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 = EINVAL;
|
||||
break;
|
||||
}
|
||||
return(err);
|
||||
}
|
||||
|
||||
/* Declare this module to the rest of the kernel */
|
||||
|
||||
DECLARE_MODULE(skeleton, skel_loader, 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 kldload -v ./skeleton.ko
|
||||
</screen>
|
||||
</para>
|
||||
</sect2>
|
||||
</sect1>
|
||||
|
||||
<sect1>
|
||||
<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>
|
||||
<para>The <command>mknod</command> command requires four
|
||||
arguments to create a device node. You must specify the
|
||||
name of this 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>
|
||||
<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. Unfortunately, devfs is still a
|
||||
work in progress.</para>
|
||||
</sect2>
|
||||
|
||||
</sect1>
|
||||
|
||||
<sect1>
|
||||
<title>Character Devices</title>
|
||||
<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>
|
||||
<programlisting>
|
||||
/*
|
||||
* Simple `echo' pseudo-device KLD
|
||||
*
|
||||
* Murray Stokely
|
||||
*/
|
||||
|
||||
#define MIN(a,b) (((a) < (b)) ? (a) : (b))
|
||||
|
||||
#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 */
|
||||
#include <sys/conf.h> /* cdevsw struct */
|
||||
#include <sys/uio.h> /* uio struct */
|
||||
#include <sys/malloc.h>
|
||||
|
||||
#define BUFFERSIZE 256
|
||||
|
||||
/* Function prototypes */
|
||||
d_open_t echo_open;
|
||||
d_close_t echo_close;
|
||||
d_read_t echo_read;
|
||||
d_write_t echo_write;
|
||||
|
||||
/* Character device entry points */
|
||||
static struct cdevsw echo_cdevsw = {
|
||||
echo_open,
|
||||
echo_close,
|
||||
echo_read,
|
||||
echo_write,
|
||||
noioctl,
|
||||
nopoll,
|
||||
nommap,
|
||||
nostrategy,
|
||||
"echo",
|
||||
33, /* reserved for lkms - /usr/src/sys/conf/majors */
|
||||
nodump,
|
||||
nopsize,
|
||||
D_TTY,
|
||||
-1
|
||||
};
|
||||
|
||||
typedef struct s_echo {
|
||||
char msg[BUFFERSIZE];
|
||||
int len;
|
||||
} t_echo;
|
||||
|
||||
/* vars */
|
||||
static dev_t sdev;
|
||||
static int len;
|
||||
static int count;
|
||||
static t_echo *echomsg;
|
||||
|
||||
MALLOC_DECLARE(M_ECHOBUF);
|
||||
MALLOC_DEFINE(M_ECHOBUF, "echobuffer", "buffer for echo module");
|
||||
|
||||
/*
|
||||
* This function acts 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, int what, void *arg)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
switch (what) {
|
||||
case MOD_LOAD: /* kldload */
|
||||
sdev = make_dev(<literal>&</literal>echo_cdevsw,
|
||||
0,
|
||||
UID_ROOT,
|
||||
GID_WHEEL,
|
||||
0600,
|
||||
"echo");
|
||||
/* kmalloc memory for use by this driver */
|
||||
/* malloc(256,M_ECHOBUF,M_WAITOK); */
|
||||
MALLOC(echomsg, t_echo *, sizeof(t_echo), M_ECHOBUF, M_WAITOK);
|
||||
printf("Echo device loaded.\n");
|
||||
break;
|
||||
case MOD_UNLOAD:
|
||||
destroy_dev(sdev);
|
||||
FREE(echomsg,M_ECHOBUF);
|
||||
printf("Echo device unloaded.\n");
|
||||
break;
|
||||
default:
|
||||
err = EINVAL;
|
||||
break;
|
||||
}
|
||||
return(err);
|
||||
}
|
||||
|
||||
int
|
||||
echo_open(dev_t dev, int oflags, int devtype, struct proc *p)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
uprintf("Opened device \"echo\" successfully.\n");
|
||||
return(err);
|
||||
}
|
||||
|
||||
int
|
||||
echo_close(dev_t dev, int fflag, int devtype, struct proc *p)
|
||||
{
|
||||
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)
|
||||
*/
|
||||
|
||||
int
|
||||
echo_read(dev_t dev, struct uio *uio, int ioflag)
|
||||
{
|
||||
int err = 0;
|
||||
int 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 > 0) ? echomsg->len - uio->uio_offset : 0);
|
||||
if ((err = uiomove(echomsg->msg + uio->uio_offset,amt,uio)) != 0) {
|
||||
uprintf("uiomove failed!\n");
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* echo_write takes in a character string and saves it
|
||||
* to buf for later accessing.
|
||||
*/
|
||||
|
||||
int
|
||||
echo_write(dev_t dev, struct uio *uio, int ioflag)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
/* Copy the string in from user memory to kernel memory */
|
||||
err = copyin(uio->uio_iov->iov_base, echomsg->msg, MIN(uio->uio_iov->iov_len,BUFFERSIZE));
|
||||
|
||||
/* Now we need to null terminate */
|
||||
*(echomsg->msg + MIN(uio->uio_iov->iov_len,BUFFERSIZE)) = 0;
|
||||
/* Record the length */
|
||||
echomsg->len = MIN(uio->uio_iov->iov_len,BUFFERSIZE);
|
||||
|
||||
if (err != 0) {
|
||||
uprintf("Write failed: bad address!\n");
|
||||
}
|
||||
|
||||
count++;
|
||||
return(err);
|
||||
}
|
||||
|
||||
DEV_MODULE(echo,echo_loader,NULL);
|
||||
</programlisting>
|
||||
|
||||
<para>To install this driver you will first need to make a node on
|
||||
your filesystem with a command such as : </para>
|
||||
<screen>
|
||||
&prompt.root mknod /dev/echo c 33 0
|
||||
</screen>
|
||||
<para>With this driver loaded you should now be able to type something
|
||||
like :</para>
|
||||
<screen>
|
||||
&prompt.root echo -n "Test Data" > /dev/echo
|
||||
&prompt.root cat /dev/echo
|
||||
Test Data
|
||||
</screen>
|
||||
<para>Real hardware devices in the next chapter..</para>
|
||||
|
||||
<para>Additional Resources
|
||||
<itemizedlist>
|
||||
<listitem><simpara><ulink
|
||||
url="http://www.daemonnews.org/200010/blueprints.html">Dynamic
|
||||
Kernel Linker (KLD) Facility Programming Tutorial</ulink> -
|
||||
<ulink url="http://www.daemonnews.org">Daemonnews</ulink> October 2000</simpara></listitem>
|
||||
<listitem><simpara><ulink
|
||||
url="http://www.daemonnews.org/200007/newbus-intro.html">How
|
||||
to Write Kernel Drivers with NEWBUS</ulink> - <ulink
|
||||
url="http://www.daemonnews.org">Daemonnews</ulink> July
|
||||
2000</simpara></listitem>
|
||||
</itemizedlist>
|
||||
</para>
|
||||
</sect1>
|
||||
|
||||
<sect1>
|
||||
<title>Block Devices</title>
|
||||
<para>A block device driver transfers data to and from the
|
||||
operating system's buffer cache. They are solely intended to
|
||||
layer a file system on top of them. For this reason they are
|
||||
normally implemented for disks and disk-like devices only.</para>
|
||||
|
||||
<para>Example test data generator ... </para>
|
||||
|
||||
<para>Example ramdisk device ... </para>
|
||||
|
||||
<para>Real hardware devices in the next chapter..</para>
|
||||
</sect1>
|
||||
|
||||
<sect1>
|
||||
<title>Network Drivers</title>
|
||||
<para>Drivers for network devices do not use device nodes in
|
||||
ord 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>man ifnet(), loopback device, Bill Pauls drivers, etc..</para>
|
||||
</sect1>
|
||||
|
||||
</chapter>
|
||||
|
||||
<chapter id="pci">
|
||||
<title>PCI Devices</title>
|
||||
|
||||
<para>This chapter will talk about the FreeBSD mechanisms for
|
||||
writing a device driver for a device on a PCI bus.</para>
|
||||
|
||||
<sect1><title>Probe and Attach</title>
|
||||
|
||||
<para>Information here about how the PCI bus code iterates
|
||||
through the unattached devices and see if a newly loaded kld
|
||||
will attach to any of them.</para>
|
||||
<programlisting>
|
||||
/*
|
||||
* Simple KLD to play with the PCI functions.
|
||||
*
|
||||
* Murray Stokely
|
||||
*/
|
||||
|
||||
#define MIN(a,b) (((a) < (b)) ? (a) : (b))
|
||||
|
||||
#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 */
|
||||
#include <sys/conf.h> /* cdevsw struct */
|
||||
#include <sys/uio.h> /* uio struct */
|
||||
#include <sys/malloc.h>
|
||||
#include <sys/bus.h> /* structs, prototypes for pci bus stuff */
|
||||
|
||||
#include <pci/pcivar.h> /* For get_pci macros! */
|
||||
|
||||
/* Function prototypes */
|
||||
d_open_t mypci_open;
|
||||
d_close_t mypci_close;
|
||||
d_read_t mypci_read;
|
||||
d_write_t mypci_write;
|
||||
|
||||
/* Character device entry points */
|
||||
|
||||
static struct cdevsw mypci_cdevsw = {
|
||||
mypci_open,
|
||||
mypci_close,
|
||||
mypci_read,
|
||||
mypci_write,
|
||||
noioctl,
|
||||
nopoll,
|
||||
nommap,
|
||||
nostrategy,
|
||||
"mypci",
|
||||
36, /* reserved for lkms - /usr/src/sys/conf/majors */
|
||||
nodump,
|
||||
nopsize,
|
||||
D_TTY,
|
||||
-1
|
||||
};
|
||||
|
||||
/* vars */
|
||||
static dev_t sdev;
|
||||
|
||||
/* We're more interested in probe/attach than with
|
||||
open/close/read/write at this point */
|
||||
|
||||
int
|
||||
mypci_open(dev_t dev, int oflags, int devtype, struct proc *p)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
uprintf("Opened device \"mypci\" successfully.\n");
|
||||
return(err);
|
||||
}
|
||||
|
||||
int
|
||||
mypci_close(dev_t dev, int fflag, int devtype, struct proc *p)
|
||||
{
|
||||
int err=0;
|
||||
|
||||
uprintf("Closing device \"mypci.\"\n");
|
||||
return(err);
|
||||
}
|
||||
|
||||
int
|
||||
mypci_read(dev_t dev, struct uio *uio, int ioflag)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
uprintf("mypci read!\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
int
|
||||
mypci_write(dev_t dev, struct uio *uio, int ioflag)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
uprintf("mypci write!\n");
|
||||
return(err);
|
||||
}
|
||||
|
||||
/* PCI Support Functions */
|
||||
|
||||
/*
|
||||
* Return identification string if this is device is ours.
|
||||
*/
|
||||
static int
|
||||
mypci_probe(device_t dev)
|
||||
{
|
||||
uprintf("MyPCI Probe\n"
|
||||
"Vendor ID : 0x%x\n"
|
||||
"Device ID : 0x%x\n",pci_get_vendor(dev),pci_get_device(dev));
|
||||
|
||||
if (pci_get_vendor(dev) == 0x11c1) {
|
||||
uprintf("We've got the Winmodem, probe successful!\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ENXIO;
|
||||
}
|
||||
|
||||
/* Attach function is only called if the probe is successful */
|
||||
|
||||
static int
|
||||
mypci_attach(device_t dev)
|
||||
{
|
||||
uprintf("MyPCI Attach for : deviceID : 0x%x\n",pci_get_vendor(dev));
|
||||
sdev = make_dev(<literal>&</literal>mypci_cdevsw,
|
||||
0,
|
||||
UID_ROOT,
|
||||
GID_WHEEL,
|
||||
0600,
|
||||
"mypci");
|
||||
uprintf("Mypci device loaded.\n");
|
||||
return ENXIO;
|
||||
}
|
||||
|
||||
/* Detach device. */
|
||||
|
||||
static int
|
||||
mypci_detach(device_t dev)
|
||||
{
|
||||
uprintf("Mypci detach!\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Called during system shutdown after sync. */
|
||||
|
||||
static int
|
||||
mypci_shutdown(device_t dev)
|
||||
{
|
||||
uprintf("Mypci shutdown!\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Device suspend routine.
|
||||
*/
|
||||
static int
|
||||
mypci_suspend(device_t dev)
|
||||
{
|
||||
uprintf("Mypci suspend!\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Device resume routine.
|
||||
*/
|
||||
|
||||
static int
|
||||
mypci_resume(device_t dev)
|
||||
{
|
||||
uprintf("Mypci resume!\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static device_method_t mypci_methods[] = {
|
||||
/* Device interface */
|
||||
DEVMETHOD(device_probe, mypci_probe),
|
||||
DEVMETHOD(device_attach, mypci_attach),
|
||||
DEVMETHOD(device_detach, mypci_detach),
|
||||
DEVMETHOD(device_shutdown, mypci_shutdown),
|
||||
DEVMETHOD(device_suspend, mypci_suspend),
|
||||
DEVMETHOD(device_resume, mypci_resume),
|
||||
|
||||
{ 0, 0 }
|
||||
};
|
||||
|
||||
static driver_t mypci_driver = {
|
||||
"mypci",
|
||||
mypci_methods,
|
||||
0,
|
||||
/* sizeof(struct mypci_softc), */
|
||||
};
|
||||
|
||||
static devclass_t mypci_devclass;
|
||||
|
||||
DRIVER_MODULE(mypci, pci, mypci_driver, mypci_devclass, 0, 0);
|
||||
</programlisting>
|
||||
|
||||
<para>Additional Resources
|
||||
<itemizedlist>
|
||||
<listitem><simpara><ulink
|
||||
url="http://www.pcisig.org">PCI Special Interest
|
||||
Group</ulink></simpara></listitem>
|
||||
<listitem><simpara>PCI System Architecture, Fourth Edition by
|
||||
Tom Shanley, et al.</simpara></listitem>
|
||||
</itemizedlist>
|
||||
</para>
|
||||
</sect1>
|
||||
</chapter>
|
||||
|
||||
<chapter id="usb">
|
||||
<title>USB Devices</title>
|
||||
|
||||
<para>This chapter will talk about the FreeBSD mechanisms for
|
||||
writing a device driver for a device on a USB bus.</para>
|
||||
</chapter>
|
||||
|
||||
<chapter id="newbus">
|
||||
<title>NewBus</title>
|
||||
|
||||
<para>This chapter will talk about the FreeBSD NewBus
|
||||
architecture.</para>
|
||||
</chapter>
|
||||
|
||||
</part>
|
||||
|
||||
<part id="architectures">
|
||||
|
@ -331,8 +928,27 @@
|
|||
<chapter id="ia32">
|
||||
<title>IA-32</title>
|
||||
|
||||
<para>Detail the (major) differences between IA-32, IA-64, PPC,
|
||||
ARM, Sparc, Alpha, etc</para>
|
||||
<para>Talk about the architectural specifics of FreeBSD/x86.</para>
|
||||
|
||||
</chapter>
|
||||
|
||||
<chapter id="alpha">
|
||||
<title>Alpha</title>
|
||||
|
||||
<para>Talk about the architectural specifics of
|
||||
FreeBSD/alpha.</para>
|
||||
|
||||
<para>Explanation of allignment errors, how to fix, how to
|
||||
ignore.</para>
|
||||
|
||||
<para>Example assembly language code for FreeBSD/alpha.</para>
|
||||
</chapter>
|
||||
|
||||
<chapter id="ia64">
|
||||
<title>IA-64</title>
|
||||
|
||||
<para>Talk about the architectural specifics of
|
||||
FreeBSD/ia64.</para>
|
||||
|
||||
</chapter>
|
||||
</part>
|
||||
|
|
Loading…
Reference in a new issue