Probably the simplest way of generating debugging
information from a kernel driver is to use
printf(). The kernel printf will send output to
the console, so beware of generating too much output and
making the system unusable.

xxxprobe() (
during which NetBSD will attempt to determine if the
device is present)

xxxattach()
routine which will configure and attach the
device.

Once probe and attach routines have been written, add
an entry to
/usr/src/sys/arch/<your-arch>/<your-arch>/conf.c.

There are two tables:

cdevsw for character devices.

bdevsw for block devices (for those
that also perform "block" I/O and use a strategy
routine).

Most entries will be of the form
cdev_xxx_init(), which
is a macro handling prototyping of the standard Unix
device switch routines.

The probe/attach routines are called at boot time. The
open(), close(),
read(), and write() routines are
called when you open up the device special file who's major
number corresponds to the index into that table. For
example, if you open up a device who's major number is 18,
the "open" routine for device number 18 in
cdevsw[]/bdevsw will be called.

Most drivers are split between bus specific attach code, and
a machine independent core. As an example, the driver for
the PCI lance ethernet chip has entries in the following files:

The autoconf machinery is quite simple once you figure
out the way it works. If you want to ignore the exact
details of how the device probe tree is built and walked
on runtime, the bits needed for each individual
“leaf” driver are like this:

each driver specifies a structure holding
three things - size of its private structure, probe
function and attach function; this is compiled in and
used in runtime - example:

on kernel startup, once the time comes to
attach the device, autoconf code calls device's
probe routine and passes it pointer to parent
(struct device *parent), pointer
to attach tag structure (void *aux),
and appropriate autoconf node (struct cfdata
*cf). The driver is expected to find out if
it's where it's supposed to be (commonly, the location
and configuration information is passed by the attach
tag). If yes, the probe routine should return 1. If
device is not there, probe routine has to return 0.
NO STATE SHOULD BE KEPT
in either case.

if probe returned success, autoconf allocates
chunk of memory sized as specified in device's *_ca
and calls its attach routine, passing it pointer to
parent (struct device *parent),
pointer to the freshly allocated memory
(struct device *self) and the attach tag
(void *aux). Driver is expected to find
out exact ports and memory, allocate resources and
initialize its internal structure
accordingly. Preferably, all driver instance specific
information should be kept in the allocated
memory.

At runtime, autoconf iterates over all physical devices
present on machine's PCI bus. For each physical device, it
iterates over all devices registered in kernel to be on
pci bus, and calls drivers' probe routine. If any probe
routine claims the device by returning 1, autoconf stops
iterating and does the job described under 3). Once the
attach function returns, autoconf continues with next
physical device.

Now, this is slightly more complicated by the fact
that you are going to be mmap'ing what are simply
kernel memory objects (it is a pseudo-device after
all).

In order to make this work, you're going to want
to make sure you allocate the memory objects to be
mmap'd on page-aligned boundaries. If you are
allocating something >= PAGE_SIZE in
size, this is guaranteed. Otherwise, you are going to
have to use uvm_km_alloc(), and round
your allocation size up to page size.

You can look at
sys/dev/pci/puc.c, which is one of
the simplest drivers. PUCs are devices with one or more
serial or parallel ports on it, usually using standard
chips (e.g. 16550 UART for serial). This driver just
locates the I/O addresses of the registers of the serial or
parallel controller and passes it to the serial or
parallel driver.