A device driver can be either statically compiled into the system or dynamically loaded using a loadable kernel module (KLD).
Most operating systems call a loadable kernel module an LKM—FreeBSD just had to be different.
A KLD is a kernel subsystem that can be loaded, unloaded, started, and stopped after bootup. In other words, a KLD can add functionality to the kernel and later remove said functionality while the system is running. Needless to say, our “functionality” will be device drivers.
In general, two components are common to all KLDs:
A module event handler
A DECLARE_MODULE
macro call
A module event handler is the function that handles the initialization and shutdown of a KLD. This function is executed when a KLD is loaded into the kernel or unloaded from the kernel, or when the system is shut down. Its function prototype is defined in the <sys/module.h>
header as follows:
typedef int (*modeventhand_t)(module_t, int /* modeventtype_t */, void *);
Here, modeventtype_t
is defined in the <sys/module.h>
header like so:
typedef enum modeventtype { MOD_LOAD, /* Set when module is loaded. */ MOD_UNLOAD, /* Set when module is unloaded. */ MOD_SHUTDOWN, /* Set on shutdown. */ MOD_QUIESCE /* Set when module is about to be unloaded. */ } modeventtype_t;
As you can see, modeventtype_t
labels whether the KLD is being loaded into the kernel or unloaded from the kernel, or whether the system is about to shut down. (For now, ignore the value at ; we’ll discuss it in Chapter 4.)
Generally, you’d use the modeventtype_t
argument in a switch
statement to set up different code blocks for each situation. Some example code should help clarify what I mean:
static int modevent(module_t mod __unused, int event, void *arg __unused) { int error = 0; switch (event) { case MOD_LOAD: uprintf("Hello, world! "); break; case MOD_UNLOAD: uprintf("Good-bye, cruel world! "); break; default: error = EOPNOTSUPP; break; } return (error); }
Notice how the second argument is the expression for the switch
statement. Thus, this module event handler prints “Hello, world!” when the KLD is loaded into the kernel, prints “Good-bye, cruel world!” when the KLD is unloaded from the kernel, and returns EOPNOTSUPP
(which stands for error: operation not supported) prior to system shutdown.
The DECLARE_MODULE
macro registers a KLD and its module event handler with the system. Here is its function prototype:
#include <sys/param.h> #include <sys/kernel.h> #include <sys/module.h> DECLARE_MODULE(name, moduledata_t data, sub, order);
The arguments expected by this macro are as follows.
The data
argument expects a filled-out moduledata_t
structure, which is defined in the <sys/module.h>
header as follows:
typedef struct moduledata { const char *name; modeventhand_t evhand; void *priv; } moduledata_t;
Here, name
is the official module name, evhand
is the KLD’s module event handler, and priv
is a pointer to private data (if any exists).
The sub
argument specifies the kernel subsystem that the KLD belongs in. Valid values for this argument are defined in the sysinit_sub_id
enumeration, found in <sys/kernel.h>
.
enum sysinit_sub_id { SI_SUB_DUMMY = 0x0000000, /* Not executed. */ SI_SUB_DONE = 0x0000001, /* Processed. */ SI_SUB_TUNABLES = 0x0700000, /* Tunable values. */ SI_SUB_COPYRIGHT = 0x0800001, /* First console use. */ SI_SUB_SETTINGS = 0x0880000, /* Check settings. */ SI_SUB_MTX_POOL_STATIC = 0x0900000, /* Static mutex pool. */ SI_SUB_LOCKMGR = 0x0980000, /* Lock manager. */ SI_SUB_VM = 0x1000000, /* Virtual memory. */ ... SI_SUB_DRIVERS = 0x3100000, /* Device drivers. */ ... };
For obvious reasons, we’ll almost always set sub
to SI_SUB_DRIVERS
, which is the device driver subsystem.
The order
argument specifies the KLD’s order of initialization within the sub
subsystem. Valid values for this argument are defined in the sysinit_elem_order
enumeration, found in <sys/kernel.h>
.
enum sysinit_elem_order { SI_ORDER_FIRST = 0x0000000, /* First. */ SI_ORDER_SECOND = 0x0000001, /* Second. */ SI_ORDER_THIRD = 0x0000002, /* Third. */ SI_ORDER_FOURTH = 0x0000003, /* Fourth. */ SI_ORDER_MIDDLE = 0x1000000, /* Somewhere in the middle. */ SI_ORDER_ANY = 0xfffffff /* Last. */ };
18.223.237.29