Example 3-1 is a revision of Example 2-1 that adds in a d_ioctl
function. As you’ll see, this d_ioctl
function handles two ioctl commands.
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 3-1. echo-3.0.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> #include <sys/ioccom.h> MALLOC_DEFINE(M_ECHO, "echo_buffer", "buffer for echo driver"); #define ECHO_CLEAR_BUFFER _IO('E', 1) #define ECHO_SET_BUFFER_SIZE _IOW('E', 2, int) 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 d_ioctl_t echo_ioctl; 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_ioctl = echo_ioctl, .d_name = "echo" }; typedef struct echo { int buffer_size; char *buffer; 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; int amount; amount = MIN(uio->uio_resid, (echo_message->buffer_size - 1 - uio->uio_offset > 0) ? echo_message->buffer_size - 1 - uio->uio_offset : 0); if (amount == 0) return (error); error = uiomove(echo_message->buffer, amount, uio); if (error != 0) { uprintf("Write failed. "); return (error); } echo_message->buffer[amount] = ' '; echo_message->length = amount; 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_set_buffer_size(int size) { int error = 0; if (echo_message->buffer_size == size) return (error); if (size >= 128 && size <= 512) { echo_message->buffer = realloc(echo_message->buffer, size, M_ECHO, M_WAITOK); echo_message->buffer_size = size; if (echo_message->length >= size) { echo_message->length = size - 1; echo_message->buffer[size - 1] = ' '; } } else error = EINVAL; return (error); } static int echo_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag, struct thread *td) { int error = 0; switch (cmd) { case ECHO_CLEAR_BUFFER: memset(echo_message->buffer, ' ', echo_message->buffer_size); echo_message->length = 0; uprintf("Buffer cleared. "); break; case ECHO_SET_BUFFER_SIZE: error = echo_set_buffer_size(*(int *)data); if (error == 0) uprintf("Buffer resized. "); break; default: error = ENOTTY; break; } 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_ECHO, M_WAITOK); echo_message->buffer_size = 256; echo_message->buffer = malloc(echo_message->buffer_size, M_ECHO, 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->buffer, M_ECHO); free(echo_message, M_ECHO); uprintf("Echo driver unloaded. "); break; default: error = EOPNOTSUPP; break; } return (error); } DEV_MODULE(echo, echo_modevent, NULL);
This driver starts by defining two ioctl commands: ECHO_CLEAR_BUFFER
(which clears the memory buffer) and ECHO_SET_BUFFER_SIZE
(which takes an integer to resize the memory buffer).
Usually, ioctl commands are defined in a header file—they were defined in Example 3-1 solely to simplify this discussion.
Obviously, to accommodate adding in a d_ioctl
function, the character device switch table was adapted. Moreover, struct echo
was adjusted to include a variable ( buffer_size
) to maintain the buffer size (because it can be changed now). Naturally, Example 3-1 was altered to use this new variable.
Interestingly, only echo_write
had to be altered. The echo_open
, echo_close
, and echo_read
functions remain the same.
The echo_write
, echo_set_buffer_size
, echo_ioctl
, and echo_modevent
functions call for a more in-depth explanation and are therefore described in their own sections.
As mentioned above, the echo_write
function was altered from its Example 2-1 (and Example 1-2) form. Here is its function definition (again):
static int echo_write(struct cdev *dev, struct uio *uio, int ioflag) { int error = 0; int amount; amount = MIN(uio->uio_resid, (echo_message->buffer_size - 1 - uio->uio_offset > 0) ? echo_message->buffer_size - 1 - uio->uio_offset : 0); if (amount == 0) return (error); error = uiomove(echo_message->buffer, amount, uio); if (error != 0) { uprintf("Write failed. "); return (error); } echo_message->buffer[amount] = ' '; echo_message->length = amount; return (error); }
This version of echo_write
uses uiomove
(as described in Chapter 1) instead of copyin
. Note that uiomove
decrements uio->uio_resid
(by one) and increments uio->uio_offset
(by one) for each byte copied. This lets multiple calls to uiomove
effortlessly copy a chunk of data.
You’ll recall that uio->uio_resid
and uio->uio_offset
denote the number of bytes remaining to be transferred and an offset into the data (that is, the character string), respectively.
This function starts by determining the number of bytes to copy—either the amount the user sent or whatever the buffer can accommodate. Then it transfers that amount from user space to kernel space.
The remainder of this function should be self-explanatory.
As its name implies, the echo_set_buffer_size
function takes an integer to resize the memory buffer echo_message->buffer
. Here is its function definition (again):
static int echo_set_buffer_size(int size) { int error = 0; if (echo_message->buffer_size == size) return (error); if (size >= 128 && size <= 512) { echo_message->buffer = realloc(echo_message->buffer, size, M_ECHO, M_WAITOK); echo_message->buffer_size = size; if (echo_message->length >= size) { echo_message->length = size - 1; echo_message->buffer[size - 1] = ' '; } } else error = EINVAL; return (error); }
This function can be split into three parts. The first part confirms that the current and proposed buffer sizes are distinct (or else nothing needs to occur).
The second part changes the size of the memory buffer. Then it records the new buffer size. Note that if the data stored in the buffer is longer than the proposed buffer size, the resize operation (that is, realloc
) will truncate that data.
The third part comes about only if the data stored in the buffer was truncated. It begins by correcting the stored data’s length. Then it null-terminates the data.
The echo_ioctl
function is the d_ioctl
function for Example 3-1. Here is its function definition (again):
static int echo_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag, struct thread *td) { int error = 0; switch (cmd) { case ECHO_CLEAR_BUFFER: memset(echo_message->buffer, ' ', echo_message->buffer_size); echo_message->length = 0; uprintf("Buffer cleared. "); break; case ECHO_SET_BUFFER_SIZE: error = echo_set_buffer_size(*(int *)data); if (error == 0) uprintf("Buffer resized. "); break; default: error = ENOTTY; break; } return (error); }
This function can perform one of two ioctl-based operations. The first clears the memory buffer. It begins by zeroing the buffer. Then it sets the data length to 0
.
The second resizes the memory buffer by calling echo_set_buffer_size
. Note that this operation requires an argument: the proposed buffer size. This argument is obtained from user space through data
.
Remember that you must cast data
before it can be dereferenced.
As you know, the echo_modevent
function is the module event handler. Like echo_write
, this function had to be altered to accommodate adding in echo_ioctl
. 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_ECHO, M_WAITOK); echo_message->buffer_size = 256; echo_message->buffer = malloc(echo_message->buffer_size, M_ECHO, 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->buffer, M_ECHO); free(echo_message, M_ECHO); uprintf("Echo driver unloaded. "); break; default: error = EOPNOTSUPP; break; } return (error); }
This version of echo_modevent
allocates memory for the echo
structure and memory buffer individually—that’s the only change. Previously, the memory buffer couldn’t be resized. So, individual memory allocations were unnecessary.
Now that we’ve walked through Example 3-1, let’s give it a try:
$sudo kldload ./echo-3.0.ko
Echo driver loaded. $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.
Apparently it works. But how do we invoke echo_ioctl
?
3.14.251.57