NetBSD Documentation: Kernel Programming FAQ
Misc
- What is KNF
- Using the `packed' attribute
- Using printf() for debugging
- Forcing code to enter DDB
- Adding a new driver to the kernel
- How does all this autoconf stuff work?
- Adding a system call
- Adding a sysctl
- How to implement mmap(2) in a pseudo-device
- Accessing a kernel structure from userland
- Is there a simple PCI driver I can use as an example?
- Other related links
Misc
What is KNF
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.
Using the `packed' attribute
Always use the `packed' attribute in
	  structures which describe wire protocol data formats.
Using printf() for debugging
	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.
Forcing code to enter DDB
Ensure your kernel config file contains
	  'options DDB', the file has
	  '#include "opt_ddb.h"', then use
	  'Debugger()'.
Adding a new driver to the kernel
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:
- 
cdevswfor character devices.
- 
bdevswfor 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.
How does all this autoconf stuff work?
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:
	      struct cfattach foo_baz_ca = { sizeof(struct foo_baz_softc), foo_baz_match, foo_baz_attach };
- 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.
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.
Adding a system call
Add an entry in syscalls.master, and add
	  the syscall stub to the appropriate place in 
	  src/lib/libc/sys/Makefile.inc
See the HOWTO and related documentation in the NetBSD Internals Guide for more information.
Adding a sysctl
See a posting answering this question on tech-kern.
Note that NetBSD 1.6 and up has a special “vendor” sysctl category that is reserved for vendor specific entries. See sysctl(8) for more information.
How to implement mmap(2) in a pseudo-device
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);
}
      
	Accessing a kernel structure from userland
The canonical example for this is:  
	    
	  , which reads disk statistics.src/usr.bin/vmstat/dkstats.c
Is there a simple PCI driver I can use as an example?
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.
Other related links
- driver(9) - NetBSD autoconfiguration interface utilised by device drivers
- autoconf(9) - General description on the NetBSD autoconfiguration framework
- config(9) - The autoconfiguration framework ``device definition'' language
- bus_dma(9) - NetBSD's bus and machine independent DMA framework, described in its own paper (64k, PDF)
- bus_space(9) - NetBSD's bus space manipulation interface
- How SCSI DMA works - by Tohru Nishimura
- How lazy FPU context switch works - by Tohru Nishimura
- Converting ancient BSD Ethernet drivers to NetBSD-1.2D and later
- Notes on porting FreeBSD network drivers to NetBSD
Back to NetBSD Documentation: Kernel
