Mostly Harmless

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.

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

Note

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.

echo_write Function

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.

Note

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.

echo_read Function

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.

Note

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.

echo_modevent Function

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.

DEV_MODULE Macro

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

Don’t Panic

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.

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

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