It's been a goal of mine for a while to understand how FreeBSD device drivers work. So, firstly I wrote a simple module; it's here. The next step is to write the skeleton of a device driver. That is, a device driver that does nothing. The resulting source code is here.
The Makefile is very similar to that of a module. In fact, it's basically identical:
SRCS+=ofw_bus_if.h bus_if.h device_if.h foodev.c KMOD=foodev .include <bsd.kmod.mk>
I include the headers I need, to inform the make to build them from .m files, and I include my C file which contains my driver. The last line includes everything else I need to make a driver.
The source code for my driver, for the imaginary "foo" device is here.
Firstly, I declared a struct called "foodev_softc". This struct defines the state data for my driver. Every driver in FreeBSD can have a softc, containing the drivers state data.
struct foodev_softc { device_t sc_dev; };
In the case of my driver I'm storing only the "device_t" that the kernel passed for this device. However other drivers store resources here such as memory, irqs or DMA.
There are three functions declared "foodev_probe", "foodev_attach" and "foodev_detach". You can learn about those here. They are called by the kernel, in order when the device is probed, attached and detached.
Next, I declare an array of type "device_method_t", which contains the entry points of the probe, attach and detach methods for my driver:
static device_method_t foodev_methods[] = { DEVMETHOD(device_probe, foodev_probe), DEVMETHOD(device_attach, foodev_attach), DEVMETHOD(device_detach, foodev_detach), DEVMETHOD_END };
device_method_t is defined in "bus.h"
Similar to a module, there is a struct that defines a driver. Here is mine:
static driver_t foodev_driver = { "foodev", foodev_methods, sizeof(struct foodev_softc), };
This struct contains the name of my module, the struct which contains my module's entry points, and the size of the foodev_softc struct, since the kernel needs to know how many bytes of memory to allocate for my driver's state data. "driver_t" is defined in bus.h also.
Finally, also similar to a module, I use a macro to declare my driver:
DRIVER_MODULE(foodev, simplebus, foodev_driver, foodev_devclass, 0, 0);
"DRIVER_MODULE" is defined in bus.h
The last piece which remains to be explained is this:
static devclass_t foodev_devclass;
Every driver defines a static instance of "devclass_t", and "devclass_t" is defined in bus.h. A "devclass_t" is a pointer to a "devclass", which you will find in subr_bus.c. So, the declaration above simply declares a pointer to a "devclass" which the kernel will allocate and use to store information such as the parent of the device and all devices owned by the device.
The results of loading the driver on my Pi look like this:
root@raspberry-pi:/home/pi/foodev # kldload ./foodev.ko foodev probe foodev probe foodev attach foodev probe foodev probe foodev attach foodev probe foodev probe foodev attach foodev probe foodev probe foodev attach root@raspberry-pi:/home/pi/foodev # kldstat -v | grep foo 5 1 0xc262b000 9000 foodev.ko (./foodev.ko) 108 simplebus/foodev root@raspberry-pi:/home/pi/foodev # kldunload ./foodev.k foodev.kld foodev.ko* root@raspberry-pi:/home/pi/foodev # kldunload ./foodev.ko root@raspberry-pi:/home/pi/foodev # kldstat -v | grep foo
dmesg shows this:
foodev0 mem 0x2000b400-0x2000b423 irq 0 on simplebus0 foodev1 mem 0x2000b800-0x2000b84f irq 2 on simplebus0 foodev2 mem 0-0xdec0addd on simplebus1 foodev3 mem 0x3-0xdec0ade0 on simplebus1 foodev0: detached foodev1: detached foodev2: detached foodev3: detached