Implementing ioctl

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.

Note

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).

Note

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.

Note

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.

echo_write Function

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.

Note

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.

echo_set_buffer_size Function

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.

echo_ioctl Function

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.

Note

Remember that you must cast data before it can be dereferenced.

echo_modevent Function

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.

Don’t Panic

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?

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.219.239.118