KNF stands for "Kernel Normal Form" - it's a C coding
style documented in
/usr/share/misc/style
, which is
included in the source tree as src/share/misc/style
.
Always use the `packed'
attribute in
structures which describe wire protocol data formats.
printf()
for debugging (top)
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.
Ensure your kernel config file contains
'options DDB
', the file has
'#include "opt_ddb.h"
', then use
'Debugger()
'.
Every driver needs at least:
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:
src/sys/dev/pci/files.pci
- attach information (look for 'le at pci').src/sys/dev/pci/if_le_pci.c
- PCI bus attach code for the driver.
src/sys/conf/files
- MI core attach information (look for 'le:').src/sys/dev/ic/am7990.c
- MI driver 24bit access code.src/sys/dev/ic/am79900.c
- MI driver 32bit access code.src/sys/dev/ic/lance.c
- MI core driver code.
See also the autoconf explanation.
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:
struct cfattach foo_baz_ca = { sizeof(struct foo_baz_softc), foo_baz_match, foo_baz_attach }; |
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.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.
Example: Let's have a PCI ethernet device 'baz', kernel config chunk looks like this:
pci* at mainbus? baz* at pci? dev ? function ? |
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.
See also Adding a new driver.
Add an entry in syscalls.master
, and add
the syscall stub to the appropriate place in
.src/lib/libc/sys/Makefile.inc
See a posting answering this question on tech-kern.
Note that NetBSD 1.6 and up will have a special “vendor” sysctl category that is reserved for vendor specific entries. See sysctl(8) for more information.
Your device is most likely a character device, so you will be using the device pager (the VM system hides all of this from you, don't worry).
The first thing you need to do is pick some arbitrary offsets for your mmap interface. Something like "mmap offset 0-M gives object A, N-O gives object B", etc.
After that, your mmap routine would look something like this:
int foommap(dev_t dev, int off, int prot) { if (off & PAGE_MASK) panic("foommap"); if ((u_int)off >= FOO_REGION1_MMAP_OFFSET && (u_int)off < (FOO_REGION1_MMAP_OFFSET + FOO_REGION1_SIZE)) return (atop(FOO_REGION1_ADDR + ((u_int)off - FOO_REGION1_MMAP_OFFSET))); if ((u_int)off >= FOO_REGION2_MMAP_OFFSET && (u_int)off < (FOO_REGION2_MMAP_OFFSET + FOO_REGION2_SIZE)) return (atop(FOO_REGION1_ADDR + ((u_int)off - FOO_REGION2_MMAP_OFFSET))); /* Page not found. */ return (-1); } |
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.
Then it would look a bit more like this:
int foommap(dev_t dev, int off, int prot) { paddr_t pa; if (off & PAGE_MASK) panic("foommap: offset not page aligned"); if ((u_int)off >= FOO_REGION1_MMAP_OFFSET && (u_int)off < (FOO_REGION1_MMAP_OFFSET + FOO_REGION1_SIZE)) { if ((vaddr_t)foo_object1 & PAGE_MASK) panic("foommap: foo_object1 not page aligned"); if (pmap_extract(pmap_kernel(), foo_object1 + (u_int)off - FOO_REGION1_MMAP_OFFSET, &pa) == FALSE) panic("foommap: foo_object1 page not mapped"); return (atop(pa)); } if ((u_int)off >= FOO_REGION2_MMAP_OFFSET && (u_int)off < (FOO_REGION2_MMAP_OFFSET + FOO_REGION2_SIZE)) { if ((vaddr_t)foo_object2 & PAGE_MASK) panic("foommap: foo_object2 not page aligned"); if (pmap_extract(pmap_kernel(), foo_object2 + (u_int)off - FOO_REGION2_MMAP_OFFSET, &pa) == FALSE) panic("foommap: foo_object2 page not mapped"); return (atop(pa)); } /* Page not found. */ return (-1); } |
The canonical example for this is:
, which reads disk statistics.src/usr.bin/vmstat/dkstats.c
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 adresses of the registers of the serial or
parallel controller and passes it to the serial or
parallel driver.