NAME
driver —
description of a device
driver
SYNOPSIS
#include <sys/param.h>
#include <sys/device.h>
#include <sys/errno.h>
static int
foo_match(
device_t
parent,
cfdata_t
match,
void *aux);
static void
foo_attach(
device_t
parent,
device_t
self,
void *aux);
static int
foo_detach(
device_t
self,
int flags);
static int
foo_activate(
device_t
self,
enum devact
act);
DESCRIPTION
This page briefly describes the basic
NetBSD
autoconfiguration interface used by device drivers. For a detailed overview of
the autoconfiguration framework see
autoconf(9).
Each device driver must present to the system a standard autoconfiguration
interface. This interface is provided by the
cfattach
structure. The interface to the driver is constant and is defined statically
inside the driver. For example, the interface to driver “foo” is
defined with:
CFATTACH_DECL_NEW(foo, /* driver name */
sizeof(struct foo_softc), /* size of instance data */
foo_match, /* match/probe function */
foo_attach, /* attach function */
foo_detach, /* detach function */
foo_activate); /* activate function */
For each device instance controlled by the driver, the autoconfiguration
framework allocates a block of memory to record device-instance-specific
driver variables. The size of this memory block is specified by the second
argument in the
CFATTACH_DECL macro. The memory block is
referred to as the driver's
softc structure. The
softc structure is only accessed within the driver, so its
definition is local to the driver. Nevertheless, the
softc
structure should adopt the standard
NetBSD
configuration and naming conventions. For example, the
softc
structure for driver “foo” is defined with:
struct foo_softc {
device_t sc_dev; /* generic device info */
/* device-specific state */
};
The autoconfiguration framework mandates that the first member of the
softc structure must be the driver-independent
device_t. Probably its most useful aspect to the driver is
that it contains the device-instance name
dv_xname.
If a driver has character device interfaces accessed from userland, the driver
must define the
cdevsw structure. The structure is constant
and is defined inside the driver. For example, the
cdevsw
structure for driver “foo” is defined with:
const struct cdevsw foo_cdevsw {
int (*d_open)(dev_t, int, int, struct lwp *);
int (*d_close)(dev_t, int, int, struct lwp *);
int (*d_read)(dev_t, struct uio *, int);
int (*d_write)(dev_t, struct uio *, int);
int (*d_ioctl)(dev_t, u_long, void *, int, struct lwp *);
void (*d_stop)(struct tty *, int);
struct tty *(*d_tty)(dev_t);
int (*d_poll)(dev_t, int, struct lwp *);
paddr_t (*d_mmap)(dev_t, off_t, int);
int (*d_kqfilter)(dev_t, struct knote *);
int d_flag;
};
The structure variable must be named foo_cdevsw by appending the letters
“_cdevsw” to the driver's base name. This convention is mandated
by the autoconfiguration framework.
If the driver “foo” has also block device interfaces, the driver
must define the
bdevsw structure. The structure is constant
and is defined inside the driver. For example, the
bdevsw
structure for driver “foo” is defined with:
const struct bdevsw foo_bdevsw {
int (*d_open)(dev_t, int, int, struct lwp *);
int (*d_close)(dev_t, int, int, struct lwp *);
void (*d_strategy)(struct buf *);
int (*d_ioctl)(dev_t, u_long, void *, int, struct lwp *);
int (*d_dump)(dev_t, daddr_t, void *, size_t);
int (*d_psize)(dev_t);
int d_flag;
};
The structure variable must be named foo_bdevsw by appending the letters
“_bdevsw” to the driver's base name. This convention is mandated
by the autoconfiguration framework.
During system bootstrap, the autoconfiguration framework searches the system for
devices. For each device driver, its match function is called (via its
cfattach structure) to match the driver with a device
instance. The match function is called with three arguments. This first
argument
parent is a pointer to the driver's parent
device structure. The second argument
match is a pointer
to a data structure describing the autoconfiguration framework's understanding
of the driver. Both the
parent and
match arguments are ignored by most drivers. The third
argument
aux contains a pointer to a structure
describing a potential device-instance. It is passed to the driver from the
parent. The match function would type-cast the
aux
argument to its appropriate attachment structure and use its contents to
determine whether it supports the device. Depending on the device hardware,
the contents of the attachment structure may contain “locators” to
locate the device instance so that the driver can probe it for its identity.
If the probe process identifies additional device properties, it may modify
the members of the attachment structure. For these devices, the
NetBSD convention is to call the match routine
foo_probe() instead of
foo_match() to make
this distinction clear. Either way, the match function returns a nonzero
integer indicating the confidence of supporting this device and a value of 0
if the driver doesn't support the device. Generally, only a single driver
exists for a device, so the match function returns 1 for a positive match.
The autoconfiguration framework will call the attach function (via its
cfattach structure) of the driver which returns the highest
value from its match function. The attach function is called with three
arguments. The attach function performs the necessary process to initialise
the device for operation. The first argument
parent is a
pointer to the driver's parent device structure. The second argument
self is a pointer to the driver's device structure. It
is also a pointer to our
softc structure since the device
structure is its first member. The third argument
aux is
a pointer to the attachment structure. The
parent and
aux arguments are the same as passed to the match
function.
The driver's attach function is called before system interrupts are enabled. If
interrupts are required during initialisation, then the attach function should
make use of
config_interrupts() (see
autoconf(9)).
Some devices can be removed from the system without requiring a system reboot.
The autoconfiguration framework calls the driver's detach function (via its
cfattach structure) during device detachment. If the device
does not support detachment, then the driver does not have to provide a detach
function. The detach function is used to relinquish resources allocated to the
driver which are no longer needed. The first argument
self is a pointer to the driver's device structure. It
is the same structure as passed to the attach function. The second argument
flags contains detachment flags. Valid values are
DETACH_FORCE
(force detachment; hardware gone) and
DETACH_QUIET
(do not print a notice).
The autoconfiguration framework may call the driver's activate function to
notify the driver of a change in the resources that have been allocated to it.
For example, an Ethernet driver has to be notified if the network stack is
being added or removed from the kernel. The first argument to the activate
function
self is a pointer to the driver's device
structure. It is the same argument as passed to the attach function. The
second argument
act describes the action. Valid actions
are
DVACT_ACTIVATE
(activate the device) and
DVACT_DEACTIVATE
(deactivate the device). If the
action is not supported the activate function should return
EOPNOTSUPP
. The
DVACT_DEACTIVATE
call will only be made if the
DVACT_ACTIVATE
call was successful. The activate
function is called in interrupt context.
Most drivers will want to make use of interrupt facilities. Interrupt locators
provided through the attachment structure should be used to establish
interrupts within the system. Generally, an interrupt interface is provided by
the parent. The interface will require a handler and a driver-specific
argument to be specified. This argument is usually a pointer to the
device-instance-specific softc structure. When a hardware interrupt for the
device occurs the handler is called with the argument. Interrupt handlers
should return 0 for “interrupt not for me”, 1 for “I took
care of it”, or -1 for “I guess it was mine, but I wasn't
expecting it”.
For a driver to be compiled into the kernel,
config(1) must be aware of its
existence. This is done by including an entry in files.<bus> in the
directory containing the driver. For example, the driver “foo”
attaching to bus “bar” with dependency on kernel module
“baz” has the entry:
device foo: baz
attach foo at bar
file dev/bar/foo.c foo
An entry can now be added to the machine description file:
For device interfaces of a driver to be compiled into the kernel,
config(1) must be aware of its
existence. This is done by including an entry in majors.<arch>. For
example, the driver “foo” with character device interfaces, a
character major device number “cmaj”, block device interfaces, a
block device major number “bmaj” and dependency on kernel module
“baz” has the entry:
device-major foo char cmaj block bmaj baz
For a detailed description of the machine description file and the “device
definition” language see
config(9).
SEE ALSO
config(1),
autoconf(9),
config(9),
devsw(9),
pmf(9)