Character drivers are basically KLDs that create character devices. As mentioned previously, character devices provide either a character-stream-oriented I/O interface or, alternatively, an unstructured (raw) interface. These (character-device) interfaces establish the conventions for accessing a device, which include the set of procedures that can be called to do I/O operations (McKusick and Neville-Neil, 2005). In short, character drivers produce character devices, which provide device access. For example, the lpt(4)
driver creates the /dev/lpt0
character device, which is used to access the parallel port printer. In FreeBSD 4.0 and later, most devices have a character-device interface.
In general, three components are common to all character drivers:
The d_foo
functions
A character device switch table
A make_dev
and destroy_dev
function call
The d_foo
functions, whose function prototypes are defined in the <sys/conf.h>
header, are the I/O operations that a process can execute on a device. These I/O operations are mostly associated with the file I/O system calls and are accordingly named d_open
, d_read
, and so on. A character driver’s d_foo
function is called when “foo” is done on its device. For example, d_read
is called when a process reads from a device.
Table 1-1 provides a brief description of each d_foo
function.
Table 1-1. d_foo Functions
Function | Description |
---|---|
| Called to open the device in preparation for I/O operations |
| Called to close the device |
| Called to read data from the device |
| Called to write data to the device |
| Called to perform an operation other than a read or a write |
| Called to check the device to see whether data is available for reading or space is available for writing |
| Called to map a device offset into a memory address |
| Called to register the device with a kernel event list |
| Called to start a read or write operation and then immediately return |
| Called to write all physical memory to the device |
If you don’t understand some of these operations, don’t worry; we’ll describe them in detail later when we implement them.
A character device switch table, struct cdevsw
, specifies which d_foo
functions a character driver implements. It is defined in the <sys/conf.h>
header as follows:
struct cdevsw { int d_version; u_int d_flags; const char *d_name; d_open_t *d_open; d_fdopen_t *d_fdopen; d_close_t *d_close; d_read_t *d_read; d_write_t *d_write; d_ioctl_t *d_ioctl; d_poll_t *d_poll; d_mmap_t *d_mmap; d_strategy_t *d_strategy; dumper_t *d_dump; d_kqfilter_t *d_kqfilter; d_purge_t *d_purge; d_spare2_t *d_spare2; uid_t d_uid; gid_t d_gid; mode_t d_mode; const char *d_kind; /* These fields should not be messed with by drivers. */ LIST_ENTRY(cdevsw) d_list; LIST_HEAD(, cdev) d_devs; int d_spare3; struct cdevsw *d_gianttrick; };
Here is an example character device switch table for a read/write device:
static struct cdevsw echo_cdevsw = { .d_version = D_VERSION, .d_open = echo_open, .d_close = echo_close, .d_read = echo_read, .d_write = echo_write, .d_name = "echo" };
As you can see, not every d_foo
function or attribute needs to be defined. If a d_foo
function is undefined, the corresponding operation is unsupported (for example, a character device switch table for a read-only device would not define d_write
).
Unsurprisingly, d_version
(which denotes the version of FreeBSD this driver supports) and d_name
(which is the driver’s name) must be defined. Generally, d_version
is set to D_VERSION
, which is a macro substitution for whichever version of FreeBSD it’s compiled on.
The make_dev
function takes a character device switch table and creates a character device node under /dev. Here is its function prototype:
#include <sys/param.h> #include <sys/conf.h> struct cdev * make_dev(struct cdevsw *cdevsw, int minor, uid_t uid, gid_t gid, int perms, const char *fmt, ...);
Conversely, the destroy_dev
function takes the cdev
structure returned by make_dev
and destroys the character device node. Here is its function prototype:
#include <sys/param.h> #include <sys/conf.h> void destroy_dev(struct cdev *dev);
3.14.15.94