1142 lines
39 KiB
Text
1142 lines
39 KiB
Text
<!DOCTYPE linuxdoc PUBLIC "-//FreeBSD//DTD linuxdoc//EN">
|
|
|
|
<!--
|
|
++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
++ file: /home/erich/lib/src/sgml/ddwg.sgml
|
|
++
|
|
++ Copyright Eric L. Hernes - Wednesday, August 2, 1995
|
|
++
|
|
++ $Id: ddwg.sgml,v 1.4 1997-10-03 20:53:38 wosch Exp $
|
|
++
|
|
++ Sgml doc for something
|
|
-->
|
|
|
|
<article>
|
|
|
|
<title>FreeBSD Device Driver Writer's Guide
|
|
<author>Eric L. Hernes, <tt/erich@rrnet.com/
|
|
<date>Wednesday, May 29, 1996
|
|
|
|
<abstract>
|
|
|
|
This document describes how to add a device driver to FreeBSD. It is
|
|
<it/not/ intended to be a tutorial on UNIX device drivers in general.
|
|
It is intended for device driver authors, familiar with the UNIX
|
|
device driver model, to work on FreeBSD.
|
|
|
|
</abstract>
|
|
|
|
<toc>
|
|
|
|
<sect> Overview
|
|
|
|
<p> <it>
|
|
The FreeBSD kernel is very well documented, unfortunately it's all
|
|
in `C'.
|
|
</it>
|
|
|
|
<sect> Types of drivers.
|
|
|
|
<sect1> Character
|
|
|
|
<sect2> Data Structures
|
|
<p> <tt/struct cdevsw/ Structure
|
|
|
|
<sect2> Entry Points
|
|
<sect3> d_open()
|
|
<p>
|
|
d_open() takes several arguments, the formal list looks something like:
|
|
<code>
|
|
int
|
|
d_open(dev_t dev, int flag, int mode, struct proc *p)
|
|
</code>
|
|
d_open() is called on <em/every/ open of the device.
|
|
<p>
|
|
|
|
The <tt/dev/ argument contains the major and minor number of the
|
|
device opened. These are available through the macros <tt/major()/ and
|
|
<tt/minor()/
|
|
<p>
|
|
|
|
The <tt/flag/ and <tt/mode/ arguments are as described in the
|
|
<htmlurl url="http://www.freebsd.org/cgi/man.cgi?open(2)" name="open(2)">
|
|
manual page. It is recommended that you check these for access modes
|
|
in <sys/fcntl.h> and do what is required. For example if <tt/flag/
|
|
is (O_NONBLOCK | O_EXLOCK) the open should fail if either it would
|
|
block, or exclusive access cannot be granted.
|
|
<p>
|
|
|
|
The <tt/p/ argument contains all the information about the current
|
|
process.
|
|
|
|
<sect3> d_close()
|
|
<p>
|
|
d_close() takes the same argument list as d_open():
|
|
<code>
|
|
int
|
|
d_close(dev_t dev , int flag , int mode , struct proc *p)
|
|
</code>
|
|
|
|
d_close() is only called on the last close of your device (per minor
|
|
device). For example in the following code fragment, d_open() is called
|
|
3 times, but d_close() is called only once.
|
|
<code>
|
|
...
|
|
fd1=open("/dev/mydev", O_RDONLY);
|
|
fd2=open("/dev/mydev", O_RDONLY);
|
|
fd3=open("/dev/mydev", O_RDONLY);
|
|
...
|
|
<useful stuff with fd1, fd2, fd3 here>
|
|
...
|
|
close(fd1);
|
|
close(fd2);
|
|
close(fd3);
|
|
...
|
|
</code>
|
|
|
|
The arguments are similar to those described above for
|
|
d_open().
|
|
|
|
<sect3> d_read() and d_write()
|
|
<p>
|
|
d_read() and d_write take the following argument lists:
|
|
<code>
|
|
int
|
|
d_read(dev_t dev, struct uio *uio, int flat)
|
|
int
|
|
d_write(dev_t dev, struct uio *uio, int flat)
|
|
</code>
|
|
|
|
The d_read() and d_write() entry points are called when
|
|
<htmlurl url="http://www.freebsd.org/cgi/man.cgi?read(2)" name="read(2)"> and
|
|
<htmlurl url="http://www.freebsd.org/cgi/man.cgi?write(2)" name="write(2)">
|
|
are called on your device from user-space. The transfer
|
|
of data can be handled through the kernel support routine uiomove().
|
|
|
|
<sect3> d_ioctl()
|
|
<p>
|
|
It's argument list is as follows:
|
|
<code>
|
|
int
|
|
d_ioctl(dev_t dev, int cmd, caddr_t arg, int flag, struct proc *p)
|
|
</code>
|
|
|
|
d_ioctl() is a catch-all for operations which don't make sense in
|
|
a read/write paradigm. Probably the most famous of all ioctl's is on
|
|
tty devices, through
|
|
<htmlurl url="http://www.freebsd.org/cgi/man.cgi?stty(1)" name="stty(1)">.
|
|
The ioctl entry point is called from
|
|
ioctl() in sys/kern/sys_generic.c<p>
|
|
|
|
There are four different types of ioctl's which can be implemented.
|
|
<sys/ioccom.h> contains convenience macros for defining these ioctls.
|
|
|
|
<tt/_IO(g,n)/ for control type operations. &nl;
|
|
|
|
<tt/_IOR(g,n,t)/ for operations that read data from a device. &nl;
|
|
|
|
<tt/_IOW(g,n,t)/ for operations that write data to a device. &nl;
|
|
|
|
<tt/_IOWR(g,n,t)/ for operations that write to a device, and then
|
|
read data back. &nl;
|
|
|
|
Here <tt/g/ refers to a <em/group/. This is an 8-bit value, typically
|
|
indicative of the device; for example, 't' is used in tty ioctls.
|
|
<tt/n/ refers to the number of the ioctl within the group. On SCO, this
|
|
number alone denotes the ioctl. <tt/t/ is the data type which will
|
|
get passed to the driver; this gets handed to a sizeof() operator in
|
|
the kernel. The ioctl() system call will either copyin() or copyout()
|
|
or both for your driver, then hand you a pointer to the data structure
|
|
in the <tt/arg/ argument of the d_ioctl call. Currently the data size
|
|
is limited to one page (4k on the i386).
|
|
|
|
<sect3> d_stop()
|
|
<sect3> d_reset()
|
|
<sect3> d_devtotty()
|
|
<sect3> d_select()
|
|
<sect3> d_mmap()
|
|
<sect3> d_strategy()
|
|
<p>
|
|
d_strategy()'s argument list is as follows:
|
|
<code>
|
|
void
|
|
d_strategy(struct buf *bp)
|
|
</code>
|
|
|
|
<p> d_strategy() is used for devices which use some form of scatter-gather
|
|
io. It is most common in a block device. This is significantly different
|
|
than the System V model, where only the block driver performs scatter-gather
|
|
io. Under BSD, character devices are sometimes requested to perform
|
|
scatter-gather io via the readv() and writev() system calls.
|
|
|
|
<sect2> Header Files
|
|
|
|
<sect1> Block
|
|
<sect2> Data Structures
|
|
<p> <tt/struct bdevsw/ Structure
|
|
<p> <tt/struct buf/ Structure
|
|
|
|
<sect2> Entry Points
|
|
<sect3> d_open()
|
|
<p> Described in the Character device section.
|
|
|
|
<sect3> d_close()
|
|
<p> Described in the Character device section.
|
|
|
|
<sect3> d_strategy()
|
|
<p> Described in the Character device section.
|
|
|
|
<sect3> d_ioctl()
|
|
<p> Described in the Character device section.
|
|
|
|
<sect3> d_dump()
|
|
|
|
<sect3> d_psize()
|
|
|
|
<sect2> Header Files
|
|
|
|
<sect1> Network
|
|
<sect2> Data Structures
|
|
<p> <tt/struct ifnet/ Structure
|
|
|
|
<sect2> Entry Points
|
|
<sect3> if_init()
|
|
<sect3> if_output()
|
|
<sect3> if_start()
|
|
<sect3> if_done()
|
|
<sect3> if_ioctl()
|
|
<sect3> if_watchdog()
|
|
|
|
<sect2> Header Files
|
|
|
|
<sect1> Line Discipline
|
|
<sect2> Data Structures
|
|
|
|
<p> <tt/struct linesw/ Structure
|
|
|
|
<sect2> Entry Points
|
|
<sect3> l_open()
|
|
<sect3> l_close()
|
|
<sect3> l_read()
|
|
<sect3> l_write()
|
|
<sect3> l_ioctl()
|
|
<sect3> l_rint()
|
|
<sect3> l_start()
|
|
<sect3> l_modem()
|
|
|
|
<sect2> Header Files
|
|
|
|
<sect> Supported Busses
|
|
|
|
<sect1> ISA -- Industry Standard Architecture
|
|
<sect2> Data Structures
|
|
|
|
<sect3> <tt/struct isa_device/ Structure
|
|
<p>
|
|
This structure is required, but generally it is created by
|
|
<htmlurl url="http://www.freebsd.org/cgi/man.cgi?config(8)" name="config(8)">
|
|
from the kernel configuration file. It is required on a per-device
|
|
basis, meaning that if you have a driver which controls two serial
|
|
boards, you will have two isa_device structures. If you build a
|
|
device as an LKM, you must create your own isa_device structure to
|
|
reflect your configuration. (lines 85 - 131 in pcaudio_lkm.c) There is
|
|
nearly a direct mapping between the config file and the isa_device
|
|
structure. The definition from /usr/src/sys/i386/isa/isa_device.h is:
|
|
<code>
|
|
struct isa_device {
|
|
int id_id; /* device id */
|
|
struct isa_driver *id_driver;
|
|
int id_iobase; /* base i/o address */
|
|
u_short id_irq; /* interrupt request */
|
|
short id_drq; /* DMA request */
|
|
caddr_t id_maddr; /* physical i/o memory address on bus (if any)*/
|
|
int id_msize; /* size of i/o memory */
|
|
inthand2_t *id_intr; /* interrupt interface routine */
|
|
int id_unit; /* unit number */
|
|
int id_flags; /* flags */
|
|
int id_scsiid; /* scsi id if needed */
|
|
int id_alive; /* device is present */
|
|
#define RI_FAST 1 /* fast interrupt handler */
|
|
u_int id_ri_flags; /* flags for register_intr() */
|
|
int id_reconfig; /* hot eject device support (such as PCMCIA) */
|
|
int id_enabled; /* is device enabled */
|
|
int id_conflicts; /* we're allowed to conflict with things */
|
|
struct isa_device *id_next; /* used in isa_devlist in userconfig() */
|
|
};
|
|
</code>
|
|
|
|
<!-- XXX add stuff here -->
|
|
<sect3> <tt/struct isa_driver/ Structure
|
|
|
|
<p>
|
|
This structure is defined in ``/usr/src/sys/i386/isa/isa_device.h''.
|
|
These are required on a per-driver basis. The definition is:
|
|
<code>
|
|
struct isa_driver {
|
|
int (*probe) __P((struct isa_device *idp));
|
|
/* test whether device is present */
|
|
int (*attach) __P((struct isa_device *idp));
|
|
/* setup driver for a device */
|
|
char *name; /* device name */
|
|
int sensitive_hw; /* true if other probes confuse us */
|
|
};
|
|
</code>
|
|
|
|
This is the structure used by the probe/attach code to detect and
|
|
initialize your device. The <tt/probe/ member is a pointer to your
|
|
device probe function; the <tt/attach/ member is a pointer to your
|
|
attach function. The <tt/name/ member is a character pointer to the
|
|
two or three letter name for your driver. This is the name reported
|
|
during the probe/attach process (and probably also in
|
|
<htmlurl url="http://www.freebsd.org/cgi/man.cgi?lsdev(8)" name="lsdev(8)">). The
|
|
<tt/sensitive_hw/ member is a flag which helps the probe code
|
|
determine probing order.
|
|
|
|
A typical instantiation is:
|
|
<code>
|
|
struct isa_driver mcddriver = { mcd_probe, mcd_attach, "mcd" };
|
|
</code>
|
|
|
|
<sect2> Entry Points
|
|
|
|
<sect3> probe()
|
|
<p>
|
|
probe() takes a <tt/struct isa_device/ pointer as an argument and returns
|
|
an int. The return value is ``zero'' or ``non-zero'' as to the absence
|
|
or presence of your device. This entry point may (and probably should)
|
|
be declared as <tt/static/ because it is accessed via the <tt/probe/ member
|
|
of the <tt/struct isa_driver/ structure. This function is intended
|
|
to detect the presence of your device only; it should not do any
|
|
configuration of the device itself.
|
|
|
|
<sect3> attach()
|
|
<p>
|
|
attach() also takes a <tt/struct isa_device/ pointer as an argument and
|
|
returns an int. The return value is also ``zero'' or ``non-zero'' indicating
|
|
whether or not the attach was successful. This function is intended
|
|
to do any special initialization of the device as well as confirm that
|
|
the device is usable. It too should be declared <tt/static/ because
|
|
it is accessed through the <tt/attach/ member of the <tt/isa_driver/
|
|
structure.
|
|
|
|
<sect2> Header Files
|
|
|
|
<sect1> EISA -- Extended Industry Standard Architecture
|
|
|
|
<sect2> Data Structures
|
|
|
|
<p> <tt/struct eisa_dev/ Structure
|
|
<p> <tt/struct isa_driver/ Structure
|
|
|
|
<sect2> Entry Points
|
|
|
|
<sect3> probe()
|
|
<p> Described in the ISA device section.
|
|
|
|
<sect3> attach()
|
|
<p> Described in the ISA device section.
|
|
|
|
<sect2> Header Files
|
|
|
|
<sect1> PCI -- Peripheral Computer Interconnect
|
|
<sect2> Data Structures
|
|
|
|
<p> <tt/struct pci_device/ Structure
|
|
|
|
name: The short device name.
|
|
|
|
probe: Checks if the driver can support a device
|
|
with this type. The tag may be used to get
|
|
more info with pci_read_conf(). See below.
|
|
It returns a string with the device's name,
|
|
or a NULL pointer, if the driver cannot
|
|
support this device.
|
|
|
|
attach: Allocate a control structure and prepare
|
|
it. This function may use the PCI mapping
|
|
functions. See below.
|
|
(configuration id) or type.
|
|
|
|
count: A pointer to a unit counter.
|
|
It's used by the PCI configurator to
|
|
allocate unit numbers.
|
|
|
|
<sect2> Entry Points
|
|
|
|
<sect3> probe()
|
|
|
|
<sect3> attach()
|
|
|
|
<sect3> shutdown()
|
|
|
|
<sect2> Header Files
|
|
|
|
<sect1> SCSI -- Small Computer Systems Interface
|
|
<sect2> Data Structures
|
|
|
|
<p> <tt/struct scsi_adapter/ Structure
|
|
<p> <tt/struct scsi_device/ Structure
|
|
<p> <tt/struct scsi_ctlr_config/ Structure
|
|
<p> <tt/struct scsi_device_config/ Structure
|
|
<p> <tt/struct scsi_link/ Structure
|
|
|
|
<sect2> Entry Points
|
|
<sect3> attach()
|
|
<sect3> init()
|
|
|
|
<sect2> Header Files
|
|
|
|
<sect1> PCCARD (PCMCIA)
|
|
<sect2> Data Structures
|
|
<p> <tt/struct slot_cont/ Structure
|
|
<p> <tt/struct pccard_drv/ Structure
|
|
<p> <tt/struct pccard_dev/ Structure
|
|
<p> <tt/struct slot/ Structure
|
|
|
|
<sect2> Entry Points
|
|
<sect3> handler()
|
|
<sect3> unload()
|
|
<sect3> suspend()
|
|
<sect3> init()
|
|
|
|
<sect2> Header Files
|
|
a. <pccard/slot.h>
|
|
|
|
<sect> Linking Into the Kernel.
|
|
|
|
<p>
|
|
In FreeBSD, support for the ISA and EISA busses is i386 specific.
|
|
While FreeBSD itself is presently available on the i386 platform,
|
|
some effort has been made to make the PCI, PCCARD, and SCSI code
|
|
portable. The ISA and EISA specific code resides in
|
|
/usr/src/sys/i386/isa and /usr/src/sys/i386/eisa respectively.
|
|
The machine independent PCI, PCCARD, and SCSI code reside in
|
|
/usr/src/sys/{pci,pccard,scsi}. The i386 specific code for these
|
|
reside in /usr/src/sys/i386/{pci,pccard,scsi}.
|
|
|
|
<p>
|
|
In FreeBSD, a device driver can be either binary or source. There is
|
|
no ``official'' place for binary drivers to reside. BSD/OS uses
|
|
something like sys/i386/OBJ. Since most drivers are distributed
|
|
in source, the following discussion refers to a source driver.
|
|
Binary only drivers are sometimes provided by hardware vendors
|
|
who wish to maintain the source as proprietary.
|
|
|
|
<p>
|
|
A typical driver has the source code in one c-file, say dev.c. The
|
|
driver also can have some include files; devreg.h typically contains
|
|
public device register declarations, macros, and other driver
|
|
specific declarations. Some drivers call this devvar.h instead.
|
|
Some drivers, such as the dgb (for the Digiboard PC/Xe),
|
|
require microcode to be loaded onto the board. For the dgb driver
|
|
the microcode is compiled and dumped into a header file ala
|
|
<htmlurl url="http://www.freebsd.org/cgi/man.cgi?file2c(1)" name="file2c(1)">.
|
|
|
|
<p>
|
|
If the driver has data structures and ioctl's which are specific to
|
|
the driver/device, and need to be accessible from user-space, they
|
|
should be put in a separate include file which will reside in
|
|
/usr/include/machine/ (some of these reside in /usr/include/sys/).
|
|
These are typically named something like ioctl_dev.h or devio.h.
|
|
|
|
<p>
|
|
If a driver is being written which, from user space is
|
|
identical to a device which already exists, care should be taken to
|
|
use the same ioctl interface and data structures. For example, from
|
|
user space, a SCSI CDROM drive should be identical to an IDE cdrom
|
|
drive; or a serial line on an intelligent multiport card (Digiboard,
|
|
Cyclades, ...) should be identical to the sio devices. These devices
|
|
have a fairly well defined interface which should be used.
|
|
|
|
<p>
|
|
There are two methods for linking a driver into the kernel, static and
|
|
the LKM model. The first method is fairly standard across the
|
|
*BSD family. The other method was originally developed by Sun
|
|
(I believe), and has been implemented into BSD using the Sun model.
|
|
I don't believe that the current implementation uses any Sun code.
|
|
|
|
<sect1> Standard Model
|
|
|
|
<p>
|
|
The steps required to add your driver to the standard FreeBSD kernel are
|
|
<itemize>
|
|
<item> Add to the driver list
|
|
<item> Add an entry to the [bc]devsw
|
|
<item> Add the driver entry to the kernel config file
|
|
<item> <htmlurl url="http://www.freebsd.org/cgi/man.cgi?config(8)" name="config(8)">,
|
|
compile, and install the kernel
|
|
<item> make required nodes.
|
|
<item> reboot.
|
|
</itemize>
|
|
|
|
<sect2> Adding to the driver list.
|
|
<p>
|
|
The standard model for adding a device driver to the Berkeley kernel
|
|
is to add your driver to the list of known devices. This list is
|
|
dependent on the CPU architecture. If the device is not i386 specific
|
|
(PCCARD, PCI, SCSI), the file is in ``/usr/src/sys/conf/files''.
|
|
If the device is i386 specific, use ``/usr/src/sys/i386/conf/files.i386''.
|
|
A typical line looks like:
|
|
<tscreen><code>
|
|
i386/isa/joy.c optional joy device-driver
|
|
</code></tscreen>
|
|
|
|
The first field is the pathname of the driver module relative to
|
|
/usr/src/sys. For the case of a binary driver the path would be
|
|
something like ``i386/OBJ/joy.o''.
|
|
|
|
The second field tells
|
|
<htmlurl url="http://www.freebsd.org/cgi/man.cgi?config(8)" name="config(8)">
|
|
that this is an optional driver. Some
|
|
devices are required for the kernel to even be built.
|
|
|
|
The third field is the name of the device.
|
|
|
|
The fourth field tells config that it's a device driver (as opposed to
|
|
just optional). This causes config to create entries for the device
|
|
in some structures in /usr/src/sys/compile/KERNEL/ioconf.c.
|
|
|
|
It is also possible to create a file
|
|
``/usr/src/sys/i386/conf/files.KERNEL'' whose contents will override
|
|
the default files.i386, but only for the kernel ``KERNEL''.
|
|
|
|
<sect2>Make room in conf.c
|
|
<p>
|
|
Now you must edit ``/usr/src/sys/i386/i386/conf.c'' to make an entry
|
|
for your driver. Somewhere near the top, you need to declare your
|
|
entry points. The entry for the joystick driver is:
|
|
<code>
|
|
#include "joy.h"
|
|
#if NJOY > 0
|
|
d_open_t joyopen;
|
|
d_close_t joyclose;
|
|
d_rdwr_t joyread;
|
|
d_ioctl_t joyioctl;
|
|
#else
|
|
#define joyopen nxopen
|
|
#define joyclose nxclose
|
|
#define joyread nxread
|
|
#define joyioctl nxioctl
|
|
#endif
|
|
</code>
|
|
|
|
This either defines your entry points, or null entry points which
|
|
will return ENXIO when called (the #else clause).
|
|
|
|
The include file ``joy.h'' is automatically generated by
|
|
<htmlurl url="http://www.freebsd.org/cgi/man.cgi?config(8)" name="config(8)"> when
|
|
the kernel build tree is created. This usually has only one line like:
|
|
<code>
|
|
#define NJOY 1
|
|
</code>
|
|
or
|
|
<code>
|
|
#define NJOY 0
|
|
</code>
|
|
which defines the number of your devices in your kernel.
|
|
|
|
You must additionally add a slot to either cdevsw[&rsqb, or to
|
|
bdevsw[&rsqb, depending on whether it is a character device or
|
|
a block device, or both if it is a block device with a raw interface.
|
|
The entry for the joystick driver is:
|
|
|
|
<code>
|
|
/* open, close, read, write, ioctl, stop, reset, ttys, select, mmap, strat */
|
|
struct cdevsw cdevsw[] =
|
|
{
|
|
...
|
|
{ joyopen, joyclose, joyread, nowrite, /*51*/
|
|
joyioctl, nostop, nullreset, nodevtotty,/*joystick */
|
|
seltrue, nommap, NULL},
|
|
...
|
|
}
|
|
</code>
|
|
|
|
Order is what determines the major number of your device. Which is why
|
|
there will always be an entry for your driver, either null entry
|
|
points, or actual entry points. It is probably worth noting that this
|
|
is significantly different from SCO and other system V derivatives,
|
|
where any device can (in theory) have any major number. This is
|
|
largely a convenience on FreeBSD, due to the way device nodes are
|
|
created. More on this later.
|
|
|
|
<sect2>Adding your device to the config file.
|
|
<p>
|
|
This is simply adding a line describing your device.
|
|
The joystick description line is:
|
|
<verb>
|
|
device joy0 at isa? port "IO_GAME"
|
|
</verb>
|
|
This says we have a device called ``joy0'' on the isa bus using
|
|
io-port ``IO_GAME'' (IO_GAME is a macro defined in
|
|
/usr/src/sys/i386/isa/isa.h).
|
|
|
|
A slightly more complicated entry is for the ``ix'' driver:
|
|
<verb>
|
|
device ix0 at isa? port 0x300 net irq 10 iomem 0xd0000 iosiz 32768 vector ixintr
|
|
</verb>
|
|
This says that we have a device called `ix0' on the ISA bus. It uses
|
|
io-port 0x300. It's interrupt will be masked with other devices in
|
|
the network class. It uses interrupt 10. It uses
|
|
32k of shared memory at physical address 0xd0000. It also defines
|
|
it's interrupt handler to be ``ixintr()''
|
|
|
|
<sect2><htmlurl url="http://www.freebsd.org/cgi/man.cgi?config(8)" name="config(8)">
|
|
the kernel.
|
|
<p>
|
|
Now with our config file in hand, we can create a kernel compile directory.
|
|
This is done by simply typing:
|
|
<verb>
|
|
# config KERNEL
|
|
</verb>
|
|
where KERNEL is the name of your config file. Config creates a
|
|
compile tree for you kernel in /usr/src/sys/compile/KERNEL. It
|
|
creates the Makefile, some .c files, and some .h files with macros
|
|
defining the number of each device in your kernel.
|
|
|
|
Now you can go to the compile directory and build. Each time you run
|
|
config, your previous build tree will be removed, unless you config
|
|
with a -n. If you have config'ed and compiled a GENERIC kernel, you can
|
|
``make links'' to avoid compiling a few files on each iteration. I typically
|
|
run
|
|
<verb>
|
|
# make depend links all
|
|
</verb>
|
|
followed by a ``make install'' when the kernel is done to my liking.
|
|
|
|
<sect2>Making device nodes.
|
|
<p>
|
|
On FreeBSD, you are responsible for making your own device nodes. The
|
|
major number of your device is determined by the slot number in the
|
|
device switch. Minor number is driver dependent, of course. You can
|
|
either run the mknod's from the command line, or add a section to
|
|
/dev/MAKEDEV.local, or even /dev/MAKEDEV to do the work. I sometimes
|
|
create a MAKEDEV.dev script that can be run stand-alone or pasted
|
|
into /dev/MAKEDEV.local
|
|
|
|
<sect2>Reboot.
|
|
<p>
|
|
This is the easy part. There are a number of ways to do this, reboot,
|
|
fastboot, shutdown -r, cycle the power, etc. Upon bootup you should
|
|
see your XXprobe() called, and if all is successful, your XXattach()
|
|
too.
|
|
|
|
<sect1> Loadable Kernel Module (LKM)
|
|
|
|
<p>
|
|
There are really no defined procedures for writing an LKM driver. The
|
|
following is my own conception after experimenting with the LKM device
|
|
interface and looking at the standard device driver model, this is one
|
|
way of adding an LKM interface to an existing driver without touching
|
|
the original driver source (or binary). It is recommended though,
|
|
that if you plan to release source to your driver, the LKM specific
|
|
parts should be part of the driver itself, conditionally compiled
|
|
on the LKM macro (i.e. #ifdef LKM).
|
|
|
|
This section will focus on writing the LKM specific part of the driver. We
|
|
will assume that we have written a driver which will drop into the standard
|
|
device driver model, which we would now like to implement as an LKM. We will
|
|
use the pcaudio driver as a sample driver, and develop an LKM front-end. The
|
|
source and makefile for the pcaudio LKM, ``pcaudio_lkm.c'' and ``Makefile'',
|
|
should be placed in /usr/src/lkm/pcaudio. What follows is a breakdown of
|
|
pcaudio_lkm.c.
|
|
|
|
Lines 17 - 26
|
|
|
|
-- This includes the file ``pca.h'' and conditionally compiles the rest
|
|
of the LKM on whether or not we have a pcaudio device defined. This
|
|
mimics the behavior of config. In a standard device driver,
|
|
<htmlurl url="http://www.freebsd.org/cgi/man.cgi?config(8)" name="config(8)">
|
|
generates the pca.h file from the number pca devices in the config file.
|
|
<code>
|
|
17 /*
|
|
18 * figure out how many devices we have..
|
|
19 */
|
|
20
|
|
21 #include "pca.h"
|
|
22
|
|
23 /*
|
|
24 * if we have at least one ...
|
|
25 */
|
|
26 #if NPCA > 0
|
|
</code>
|
|
|
|
Lines 27 - 37
|
|
|
|
-- Includes required files from various include directories.
|
|
<code>
|
|
27 #include <sys/param.h>
|
|
28 #include <sys/systm.h>
|
|
29 #include <sys/exec.h>
|
|
30 #include <sys/conf.h>
|
|
31 #include <sys/sysent.h>
|
|
32 #include <sys/lkm.h>
|
|
33 #include <sys/errno.h>
|
|
34 #include <i386/isa/isa_device.h>
|
|
35 #include <i386/isa/isa.h>
|
|
36
|
|
37
|
|
</code>
|
|
|
|
Lines 38 - 51
|
|
|
|
-- Declares the device driver entry points as external.
|
|
<code>
|
|
38 /*
|
|
39 * declare your entry points as externs
|
|
40 */
|
|
41
|
|
42 extern int pcaprobe(struct isa_device *);
|
|
43 extern int pcaattach(struct isa_device *);
|
|
44 extern int pcaopen(dev_t, int, int, struct proc *);
|
|
45 extern int pcaclose(dev_t, int, int, struct proc *);
|
|
46 extern int pcawrite(dev_t, struct uio *, int);
|
|
47 extern int pcaioctl(dev_t, int, caddr_t);
|
|
48 extern int pcaselect(dev_t, int, struct proc *);
|
|
49 extern void pcaintr(struct clockframe *);
|
|
50 extern struct isa_driver pcadriver;
|
|
51
|
|
</code>
|
|
|
|
Lines 52 - 70
|
|
|
|
-- This is creates the device switch entry table for your driver.
|
|
This table gets swapped wholesale into the system device switch at
|
|
the location specified by your major number. In the standard model,
|
|
these are in /usr/src/sys/i386/i386/conf.c. NOTE: you cannot pick a
|
|
device major number higher than what exists in conf.c, for example at
|
|
present, conf.c rev 1.85, there are 67 slots for character devices,
|
|
you cannot use a (character) major device number 67 or greater,
|
|
without first reserving space in conf.c.
|
|
<code>
|
|
52 /*
|
|
53 * build your device switch entry table
|
|
54 */
|
|
55
|
|
56 static struct cdevsw pcacdevsw = {
|
|
57 (d_open_t *) pcaopen, /* open */
|
|
58 (d_close_t *) pcaclose, /* close */
|
|
59 (d_rdwr_t *) enodev, /* read */
|
|
60 (d_rdwr_t *) pcawrite, /* write */
|
|
61 (d_ioctl_t *) pcaioctl, /* ioctl */
|
|
62 (d_stop_t *) enodev, /* stop?? */
|
|
63 (d_reset_t *) enodev, /* reset */
|
|
64 (d_ttycv_t *) enodev, /* ttys */
|
|
65 (d_select_t *) pcaselect, /* select */
|
|
66 (d_mmap_t *) enodev, /* mmap */
|
|
67 (d_strategy_t *) enodev /* strategy */
|
|
68 };
|
|
69
|
|
70
|
|
</code>
|
|
|
|
Lines 71 - 131
|
|
|
|
-- This section is analogous to the config file declaration of your
|
|
device. The members of the isa_device structure are filled in by what
|
|
is known about your device, I/O port, shared memory segment, etc. We
|
|
will probably never have a need for two pcaudio devices in the kernel,
|
|
but this example shows how multiple devices can be supported.
|
|
<code>
|
|
71 /*
|
|
72 * this lkm arbitrarily supports two
|
|
73 * instantiations of the pc-audio device.
|
|
74 *
|
|
75 * this is for illustration purposes
|
|
76 * only, it doesn't make much sense
|
|
77 * to have two of these beasts...
|
|
78 */
|
|
79
|
|
80
|
|
81 /*
|
|
82 * these have a direct correlation to the
|
|
83 * config file entries...
|
|
84 */
|
|
85 struct isa_device pcadev[NPCA] = {
|
|
86 {
|
|
87 11, /* device id */
|
|
88 &pcadriver, /* driver pointer */
|
|
89 IO_TIMER1, /* base io address */
|
|
90 -1, /* interrupt */
|
|
91 -1, /* dma channel */
|
|
92 (caddr_t)-1, /* physical io memory */
|
|
93 0, /* size of io memory */
|
|
94 pcaintr , /* interrupt interface */
|
|
95 0, /* unit number */
|
|
96 0, /* flags */
|
|
97 0, /* scsi id */
|
|
98 0, /* is alive */
|
|
99 0, /* flags for register_intr */
|
|
100 0, /* hot eject device support */
|
|
101 1 /* is device enabled */
|
|
102 },
|
|
103 #if NPCA >1
|
|
104 {
|
|
105
|
|
106 /*
|
|
107 * these are all zeros, because it doesn't make
|
|
108 * much sense to be here
|
|
109 * but it may make sense for your device
|
|
110 */
|
|
111
|
|
112 0, /* device id */
|
|
113 &pcadriver, /* driver pointer */
|
|
114 0, /* base io address */
|
|
115 -1, /* interrupt */
|
|
116 -1, /* dma channel */
|
|
117 -1, /* physical io memory */
|
|
118 0, /* size of io memory */
|
|
119 NULL, /* interrupt interface */
|
|
120 1, /* unit number */
|
|
121 0, /* flags */
|
|
122 0, /* scsi id */
|
|
123 0, /* is alive */
|
|
124 0, /* flags for register_intr */
|
|
125 0, /* hot eject device support */
|
|
126 1 /* is device enabled */
|
|
127 },
|
|
128 #endif
|
|
129
|
|
130 };
|
|
131
|
|
</code>
|
|
|
|
Lines 132 - 139
|
|
|
|
-- This calls the C-preprocessor macro MOD_DEV, which sets up an LKM device
|
|
driver, as opposed to an LKM filesystem, or an LKM system call.
|
|
<code>
|
|
132 /*
|
|
133 * this macro maps to a function which
|
|
134 * sets the LKM up for a driver
|
|
135 * as opposed to a filesystem, system call, or misc
|
|
136 * LKM.
|
|
137 */
|
|
138 MOD_DEV("pcaudio_mod", LM_DT_CHAR, 24, &pcacdevsw);
|
|
139
|
|
</code>
|
|
|
|
Lines 140 - 168
|
|
|
|
-- This is the function which will be called when the driver is
|
|
loaded. This function tries to work like sys/i386/isa/isa.c
|
|
which does the probe/attach calls for a driver at boot time. The
|
|
biggest trick here is that it maps the physical address of the shared
|
|
memory segment, which is specified in the isa_device structure to a
|
|
kernel virtual address. Normally the physical address is put in the
|
|
config file which builds the isa_device structures in
|
|
/usr/src/sys/compile/KERNEL/ioconf.c. The probe/attach sequence of
|
|
/usr/src/sys/isa/isa.c translates the physical address to a virtual
|
|
one so that in your probe/attach routines you can do things like
|
|
<verb>
|
|
(int *)id->id_maddr = something;
|
|
</verb>
|
|
and just refer to the shared memory segment via pointers.
|
|
<code>
|
|
140 /*
|
|
141 * this function is called when the module is
|
|
142 * loaded; it tries to mimic the behavior
|
|
143 * of the standard probe/attach stuff from
|
|
144 * isa.c
|
|
145 */
|
|
146 int
|
|
147 pcaload(){
|
|
148 int i;
|
|
149 uprintf("PC Audio Driver Loaded\n");
|
|
150 for (i=0; i<NPCA; i++){
|
|
151 /*
|
|
152 * this maps the shared memory address
|
|
153 * from physical to virtual, to be
|
|
154 * consistent with the way
|
|
155 * /usr/src/sys/i386/isa.c handles it.
|
|
156 */
|
|
157 pcadev[i].id_maddr -=0xa0000;
|
|
158 pcadev[i].id_maddr += atdevbase;
|
|
159 if ((*pcadriver.probe)(pcadev+i)) {
|
|
160 (*(pcadriver.attach))(pcadev+i);
|
|
161 } else {
|
|
162 uprintf("PC Audio Probe Failed\n");
|
|
163 return(1);
|
|
164 }
|
|
165 }
|
|
166 return 0;
|
|
167 }
|
|
168
|
|
</code>
|
|
|
|
Lines 169 - 179
|
|
|
|
-- This is the function called when your driver is unloaded; it just displays
|
|
a message to that effect.
|
|
<code>
|
|
169 /*
|
|
170 * this function is called
|
|
171 * when the module is unloaded
|
|
172 */
|
|
173
|
|
174 int
|
|
175 pcaunload(){
|
|
176 uprintf("PC Audio Driver Unloaded\n");
|
|
177 return 0;
|
|
178 }
|
|
179
|
|
</code>
|
|
|
|
Lines 180 - 190
|
|
|
|
-- This is the entry point which is specified on the command line of the
|
|
modload. By convention it is named <dev>_mod. This is how it is
|
|
defined in bsd.lkm.mk, the makefile which builds the LKM. If you name your
|
|
module following this convention, you can do ``make load'' and ``make
|
|
unload'' from /usr/src/lkm/pcaudio. <p>
|
|
Note: this has gone through <em/many/ revisions from release 2.0 to 2.1.
|
|
It may or may not be possible to write a module which is portable across
|
|
all three releases. <p>
|
|
<code>
|
|
180 /*
|
|
181 * this is the entry point specified
|
|
182 * on the modload command line
|
|
183 */
|
|
184
|
|
185 int
|
|
186 pcaudio_mod(struct lkm_table *lkmtp, int cmd, int ver)
|
|
187 {
|
|
188 DISPATCH(lkmtp, cmd, ver, pcaload, pcaunload, nosys);
|
|
189 }
|
|
190
|
|
191 #endif /* NICP > 0 */
|
|
</code>
|
|
|
|
<sect1> Device Type Idiosyncrasies
|
|
<sect2> Character
|
|
<sect2> Block
|
|
<sect2> Network
|
|
<sect2> Line Discipline
|
|
|
|
<sect1> Bus Type Idiosyncrasies
|
|
<sect2> ISA
|
|
<sect2> EISA
|
|
<sect2> PCI
|
|
<sect2> SCSI
|
|
<sect2> PCCARD
|
|
|
|
<sect> Kernel Support
|
|
|
|
<sect1> Data Structures
|
|
|
|
<sect2> <tt/struct kern_devconf/ Structure
|
|
<p>
|
|
|
|
This structure contains some information about the state of the device
|
|
and driver. It is defined in /usr/src/sys/sys/devconf.h as:
|
|
<code>
|
|
struct devconf {
|
|
char dc_name[MAXDEVNAME]; /* name */
|
|
char dc_descr[MAXDEVDESCR]; /* description */
|
|
int dc_unit; /* unit number */
|
|
int dc_number; /* unique id */
|
|
char dc_pname[MAXDEVNAME]; /* name of the parent device */
|
|
int dc_punit; /* unit number of the parent */
|
|
int dc_pnumber; /* unique id of the parent */
|
|
struct machdep_devconf dc_md; /* machine-dependent stuff */
|
|
enum dc_state dc_state; /* state of the device (see above) */
|
|
enum dc_class dc_class; /* type of device (see above) */
|
|
size_t dc_datalen; /* length of data */
|
|
char dc_data[1]; /* variable-length data */
|
|
};
|
|
</code>
|
|
|
|
<sect2> <tt/struct proc/ Structure
|
|
<p>
|
|
|
|
This structure contains all the information about a process.
|
|
It is defined in /usr/src/sys/sys/proc.h:
|
|
<code>
|
|
/*
|
|
* Description of a process.
|
|
*
|
|
* This structure contains the information needed to manage a thread of
|
|
* control, known in UN*X as a process; it has references to substructures
|
|
* containing descriptions of things that the process uses, but may share
|
|
* with related processes. The process structure and the substructures
|
|
* are always addressable except for those marked "(PROC ONLY)" below,
|
|
* which might be addressable only on a processor on which the process
|
|
* is running.
|
|
*/
|
|
struct proc {
|
|
struct proc *p_forw; /* Doubly-linked run/sleep queue. */
|
|
struct proc *p_back;
|
|
struct proc *p_next; /* Linked list of active procs */
|
|
struct proc **p_prev; /* and zombies. */
|
|
|
|
/* substructures: */
|
|
struct pcred *p_cred; /* Process owner's identity. */
|
|
struct filedesc *p_fd; /* Ptr to open files structure. */
|
|
struct pstats *p_stats; /* Accounting/statistics (PROC ONLY). */ struct plimit *p_limit; /* Process limits. */
|
|
struct vmspace *p_vmspace; /* Address space. */
|
|
struct sigacts *p_sigacts; /* Signal actions, state (PROC ONLY). */
|
|
|
|
#define p_ucred p_cred->pc_ucred
|
|
#define p_rlimit p_limit->pl_rlimit
|
|
|
|
int p_flag; /* P_* flags. */
|
|
char p_stat; /* S* process status. */
|
|
char p_pad1[3];
|
|
|
|
pid_t p_pid; /* Process identifier. */
|
|
struct proc *p_hash; /* Hashed based on p_pid for kill+exit+... */
|
|
struct proc *p_pgrpnxt; /* Pointer to next process in process group. */
|
|
struct proc *p_pptr; /* Pointer to process structure of parent. */
|
|
struct proc *p_osptr; /* Pointer to older sibling processes. */
|
|
|
|
/* The following fields are all zeroed upon creation in fork. */
|
|
#define p_startzero p_ysptr
|
|
struct proc *p_ysptr; /* Pointer to younger siblings. */
|
|
struct proc *p_cptr; /* Pointer to youngest living child. */
|
|
pid_t p_oppid; /* Save parent pid during ptrace. XXX */
|
|
int p_dupfd; /* Sideways return value from fdopen. XXX */
|
|
|
|
/* scheduling */
|
|
u_int p_estcpu; /* Time averaged value of p_cpticks. */
|
|
int p_cpticks; /* Ticks of cpu time. */
|
|
fixpt_t p_pctcpu; /* %cpu for this process during p_swtime */
|
|
void *p_wchan; /* Sleep address. */
|
|
char *p_wmesg; /* Reason for sleep. */
|
|
u_int p_swtime; /* Time swapped in or out. */
|
|
u_int p_slptime; /* Time since last blocked. */
|
|
|
|
struct itimerval p_realtimer; /* Alarm timer. */
|
|
struct timeval p_rtime; /* Real time. */
|
|
u_quad_t p_uticks; /* Statclock hits in user mode. */
|
|
u_quad_t p_sticks; /* Statclock hits in system mode. */
|
|
u_quad_t p_iticks; /* Statclock hits processing intr. */
|
|
|
|
int p_traceflag; /* Kernel trace points. */
|
|
struct vnode *p_tracep; /* Trace to vnode. */
|
|
|
|
int p_siglist; /* Signals arrived but not delivered. */
|
|
|
|
struct vnode *p_textvp; /* Vnode of executable. */
|
|
|
|
char p_lock; /* Process lock (prevent swap) count. */
|
|
char p_pad2[3]; /* alignment */
|
|
|
|
/* End area that is zeroed on creation. */
|
|
#define p_endzero p_startcopy
|
|
|
|
/* The following fields are all copied upon creation in fork. */
|
|
#define p_startcopy p_sigmask
|
|
|
|
sigset_t p_sigmask; /* Current signal mask. */
|
|
sigset_t p_sigignore; /* Signals being ignored. */
|
|
sigset_t p_sigcatch; /* Signals being caught by user. */
|
|
|
|
u_char p_priority; /* Process priority. */
|
|
u_char p_usrpri; /* User-priority based on p_cpu and p_nice. */
|
|
char p_nice; /* Process "nice" value. */
|
|
char p_comm[MAXCOMLEN+1];
|
|
|
|
struct pgrp *p_pgrp; /* Pointer to process group. */
|
|
|
|
struct sysentvec *p_sysent; /* System call dispatch information. */
|
|
|
|
struct rtprio p_rtprio; /* Realtime priority. */
|
|
/* End area that is copied on creation. */
|
|
#define p_endcopy p_addr
|
|
struct user *p_addr; /* Kernel virtual addr of u-area (PROC ONLY). */
|
|
struct mdproc p_md; /* Any machine-dependent fields. */
|
|
|
|
u_short p_xstat; /* Exit status for wait; also stop signal. */
|
|
u_short p_acflag; /* Accounting flags. */
|
|
struct rusage *p_ru; /* Exit information. XXX */
|
|
};
|
|
</code>
|
|
|
|
<sect2> <tt/struct buf/ Structure
|
|
<p>
|
|
The <tt/struct buf/ structure is used to interface with the buffer cache.
|
|
It is defined in /usr/src/sys/sys/buf.h:
|
|
|
|
<code>
|
|
/*
|
|
* The buffer header describes an I/O operation in the kernel.
|
|
*/
|
|
struct buf {
|
|
LIST_ENTRY(buf) b_hash; /* Hash chain. */
|
|
LIST_ENTRY(buf) b_vnbufs; /* Buffer's associated vnode. */
|
|
TAILQ_ENTRY(buf) b_freelist; /* Free list position if not active. */
|
|
struct buf *b_actf, **b_actb; /* Device driver queue when active. */
|
|
struct proc *b_proc; /* Associated proc; NULL if kernel. */
|
|
volatile long b_flags; /* B_* flags. */
|
|
int b_qindex; /* buffer queue index */
|
|
int b_error; /* Errno value. */
|
|
long b_bufsize; /* Allocated buffer size. */
|
|
long b_bcount; /* Valid bytes in buffer. */
|
|
long b_resid; /* Remaining I/O. */
|
|
dev_t b_dev; /* Device associated with buffer. */
|
|
struct {
|
|
caddr_t b_addr; /* Memory, superblocks, indirect etc. */
|
|
} b_un;
|
|
void *b_saveaddr; /* Original b_addr for physio. */
|
|
daddr_t b_lblkno; /* Logical block number. */
|
|
daddr_t b_blkno; /* Underlying physical block number. */
|
|
/* Function to call upon completion. */
|
|
void (*b_iodone) __P((struct buf *));
|
|
/* For nested b_iodone's. */
|
|
struct iodone_chain *b_iodone_chain;
|
|
struct vnode *b_vp; /* Device vnode. */
|
|
int b_pfcent; /* Center page when swapping cluster. */
|
|
int b_dirtyoff; /* Offset in buffer of dirty region. */
|
|
int b_dirtyend; /* Offset of end of dirty region. */
|
|
struct ucred *b_rcred; /* Read credentials reference. */
|
|
struct ucred *b_wcred; /* Write credentials reference. */
|
|
int b_validoff; /* Offset in buffer of valid region. */
|
|
int b_validend; /* Offset of end of valid region. */
|
|
daddr_t b_pblkno; /* physical block number */
|
|
caddr_t b_savekva; /* saved kva for transfer while bouncing
|
|
*/
|
|
void *b_driver1; /* for private use by the driver */
|
|
void *b_driver2; /* for private use by the driver */
|
|
void *b_spc;
|
|
struct vm_page *b_pages[(MAXPHYS + PAGE_SIZE - 1)/PAGE_SIZE];
|
|
int b_npages;
|
|
};
|
|
</code>
|
|
|
|
<sect2> <tt/struct uio/ Structure
|
|
<p>
|
|
This structure is used for moving data between the kernel and user spaces
|
|
through read() and write() system calls. It is defined in
|
|
/usr/src/sys/sys/uio.h:
|
|
<code>
|
|
struct uio {
|
|
struct iovec *uio_iov;
|
|
int uio_iovcnt;
|
|
off_t uio_offset;
|
|
int uio_resid;
|
|
enum uio_seg uio_segflg;
|
|
enum uio_rw uio_rw;
|
|
struct proc *uio_procp;
|
|
};
|
|
|
|
</code>
|
|
|
|
<sect1> Functions
|
|
lots of 'em
|
|
|
|
<sect> References.
|
|
|
|
<p> FreeBSD Kernel Sources http://www.freebsd.org
|
|
<p> NetBSD Kernel Sources http://www.netbsd.org
|
|
<p> Writing Device Drivers: Tutorial and Reference;
|
|
Tim Burke, Mark A. Parenti, Al, Wojtas;
|
|
Digital Press, ISBN 1-55558-141-2.
|
|
|
|
<p> Writing A Unix Device Driver;
|
|
Janet I. Egan, Thomas J. Teixeira;
|
|
John Wiley & Sons, ISBN 0-471-62859-X.
|
|
|
|
<p> Writing Device Drivers for SCO Unix;
|
|
Peter Kettle;
|
|
|
|
</article>
|