Example 1-2 is a complete character driver (based on code written by Murray Stokely and Søren Straarup) that manipulates a memory area as though it were a device. This pseudo (or memory) device lets you write and read a single character string to and from it.
Take a quick look at this code and try to discern some of its structure. If you don’t understand all of it, don’t worry; an explanation follows.
Example 1-2. echo.c
#include <sys/param.h> #include <sys/module.h> #include <sys/kernel.h> #include <sys/systm.h> #include <sys/conf.h> #include <sys/uio.h> #include <sys/malloc.h> #define BUFFER_SIZE 256 /* Forward declarations. */ static d_open_t echo_open; static d_close_t echo_close; static d_read_t echo_read; static d_write_t echo_write; 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" }; typedef struct echo { char buffer[BUFFER_SIZE]; int length; } echo_t; static echo_t *echo_message; static struct cdev *echo_dev; static int echo_open(struct cdev *dev, int oflags, int devtype, struct thread *td) { uprintf("Opening echo device. "); return (0); } static int echo_close(struct cdev *dev, int fflag, int devtype, struct thread *td) { uprintf("Closing echo device. "); return (0); } static int echo_write(struct cdev *dev, struct uio *uio, int ioflag) { int error = 0; error = copyin(uio->uio_iov->iov_base, echo_message->buffer, MIN(uio->uio_iov->iov_len, BUFFER_SIZE - 1)); if (error != 0) { uprintf("Write failed. "); return (error); } *(echo_message->buffer + MIN(uio->uio_iov->iov_len, BUFFER_SIZE - 1)) = 0; echo_message->length = MIN(uio->uio_iov->iov_len, BUFFER_SIZE - 1); return (error); } static int echo_read(struct cdev *dev, struct uio *uio, int ioflag) { int error = 0; int amount; amount = MIN(uio->uio_resid, (echo_message->length - uio->uio_offset > 0) ? echo_message->length - uio->uio_offset : 0); error = uiomove(echo_message->buffer + uio->uio_offset, amount, uio); if (error != 0) uprintf("Read failed. "); return (error); } static int echo_modevent(module_t mod __unused, int event, void *arg __unused) { int error = 0; switch (event) { case MOD_LOAD: echo_message = malloc(sizeof(echo_t), M_TEMP, M_WAITOK); echo_dev = make_dev(&echo_cdevsw, 0, UID_ROOT, GID_WHEEL, 0600, "echo"); uprintf("Echo driver loaded. "); break; case MOD_UNLOAD: destroy_dev(echo_dev); free(echo_message, M_TEMP); uprintf("Echo driver unloaded. "); break; default: error = EOPNOTSUPP; break; } return (error); } DEV_MODULE(echo, echo_modevent, NULL);
This driver starts by defining a character device switch table, which contains four d_foo
functions named echo_foo
, where foo
equals to open
, close
, read
, and write
. Consequently, the ensuing character device will support only these four I/O operations.
Next, there are two variable declarations: an echo
structure pointer named echo_message
(which will contain a character string and its length) and a cdev
structure pointer named echo_dev
(which will maintain the cdev
returned by the make_dev
call).
Then, the d_foo
functions echo_open
and echo_close
are defined—each just prints a debug message. Generally, the d_open
function prepares a device for I/O, while d_close
breaks apart those preparations.
There is a difference between “preparing a device for I/O” and “preparing (or initializing) a device.” For pseudo-devices like Example 1-2, device initialization is done in the module event handler.
The remaining bits—echo_write
, echo_read
, echo_modevent
, and DEV_MODULE
—require a more in-depth explanation and are therefore described in their own sections.
The echo_write
function acquires a character string from user space and stores it. Here is its function definition (again):
static int echo_write(struct cdev *dev, struct uio *uio, int ioflag) { int error = 0; error = copyin(uio->uio_iov->iov_base, echo_message->buffer, MIN(uio->uio_iov->iov_len, BUFFER_SIZE - 1)); if (error != 0) { uprintf("Write failed. "); return (error); } *(echo_message->buffer + MIN(uio->uio_iov->iov_len, BUFFER_SIZE - 1)) = 0; echo_message->length = MIN(uio->uio_iov->iov_len, BUFFER_SIZE - 1); return (error); }
Here, struct uio
describes a character string in motion—the variables iov_base
and iov_len
specify the character string’s base address and length, respectively.
So, this function starts by copying a character string from user space to kernel space. At most, 'BUFFER_SIZE - 1'
bytes of data are copied. Once this is done, the character string is null-terminated, and its length (minus the null terminator) is recorded.
This isn’t the proper way to copy data from user space to kernel space. I should’ve used uiomove
instead of copyin
. However, copyin
is easier to understand, and at this point, I just want to cover the basic structure of a character driver.
The echo_read
function returns the stored character string to user space. Here is its function definition (again):
static int echo_read(struct cdev *dev, struct uio *uio, int ioflag) { int error = 0; int amount; amount = MIN(uio->uio_resid, (echo_message->length - uio->uio_offset > 0) ? echo_message->length - uio->uio_offset : 0); error = uiomove(echo_message->buffer + uio->uio_offset, amount, uio); if (error != 0) uprintf("Read failed. "); return (error); }
Here, the variables uio_resid
and uio_offset
specify the amount of data remaining to be transferred and an offset into the character string, respectively.
So, this function first determines the number of characters to return—either the amount the user requests or all of it. Then echo_read
transfers that number from kernel space to user space.
For more on copying data between user and kernel space, see the copy(9)
and uio(9)
manual pages. I’d also recommend the OpenBSD uiomove(9)
manual page.
The echo_modevent
function is the module event handler for this character driver. Here is its function definition (again):
static int echo_modevent(module_t mod __unused, int event, void *arg __unused) { int error = 0; switch (event) { case MOD_LOAD: echo_message = malloc(sizeof(echo_t), M_TEMP, M_WAITOK); echo_dev = make_dev(&echo_cdevsw, 0, UID_ROOT, GID_WHEEL, 0600, "echo"); uprintf("Echo driver loaded. "); break; case MOD_UNLOAD: destroy_dev(echo_dev); free(echo_message, M_TEMP); uprintf("Echo driver unloaded. "); break; default: error = EOPNOTSUPP; break; } return (error); }
On module load, this function first calls malloc
to allocate sizeof(echo_t)
bytes of memory. Then it calls make_dev
to create a character device node named echo
under /dev. Note that when make_dev
returns, the character device is “live” and its d_foo
functions can be executed. Consequently, if I had called make_dev
ahead of malloc
, echo_write
or echo_read
could be executed before echo_message
points to valid memory, which would be disastrous. The point is: Unless your driver is completely ready, don’t call make_dev
.
On module unload, this function first calls destroy_dev
to destroy the echo
device node. Then it calls free
to release the allocated memory.
The DEV_MODULE
macro is defined in the <sys/conf.h>
header as follows:
#define DEV_MODULE(name, evh, arg) static moduledata_t name##_mod = { #name, evh, arg }; DECLARE_MODULE(name, name##_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE)
As you can see, DEV_MODULE
merely wraps DECLARE_MODULE
. So Example 1-2 could have called DECLARE_MODULE
, but DEV_MODULE
is cleaner (and it saves you some keystrokes).
Now that we’ve walked through Example 1-2, let’s give it a try:
$sudo kldload ./echo.ko
Echo driver loaded. $ls -l /dev/echo
crw------- 1 root wheel 0, 95 Jun 4 23:23 /dev/echo $su
Password: #echo "DON'T PANIC" > /dev/echo
Opening echo device. Closing echo device. #cat /dev/echo
Opening echo device. DON'T PANIC Closing echo device.
Unsurprisingly, it works. Before this chapter is concluded, a crucial topic bears mentioning.
18.223.106.100