Chapter 3. Device Communication and Control

image with no caption

In Chapter 1 we constructed a driver that could read from and write to a device. In addition to reading and writing, most drivers need to perform other I/O operations, such as reporting error information, ejecting removable media, or activating self-destruct sequences. This chapter details how to make drivers do those things.

We’ll start by describing the ioctl interface, also known as the input/output control interface. This interface is commonly used for device communication and control. Then we’ll describe the sysctl interface, also known as the system control interface. This interface is used to dynamically change or examine the kernel’s parameters, which includes device drivers.

ioctl

The ioctl interface is the catchall of I/O operations (Stevens, 1992). Any operation that cannot be expressed using d_read or d_write (that is, any operation that’s not a data transfer) is supported by d_ioctl.[3] For example, the CD-ROM driver’s d_ioctl function performs 29 distinct operations, such as ejecting the CD, starting audio playback, stopping audio playback, muting the audio, and so on.

The function prototype for d_ioctl is defined in the <sys/conf.h> header as follows:

typedef int d_ioctl_t(struct cdev *dev, u_long cmd, caddr_t data,
                      int fflag, struct thread *td);

Here, cmd is an ioctl command passed from user space. ioctl commands are driver-defined numeric constants that identify the different I/O operations that a d_ioctl function can perform. Generally, you’d use the cmd argument in a switch statement to set up a code block for each I/O operation. Any arguments required for an I/O operation are passed through data.

Here is an example d_ioctl function:

Note

Just concentrate on the structure of this code and ignore what it does.

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);
}

Notice how the cmd argument is the expression for the switch statement. The constants ECHO_CLEAR_BUFFER and ECHO_SET_BUFFER_SIZE are (obviously) the ioctl commands. All ioctl commands are defined using one of four macros. I’ll discuss these macros in the following section.

Additionally, notice how the data argument is cast—as an integer pointer—before it is dereferenced. This is because data is fundamentally a “pointer to void.”

Note

Pointers to void can hold any pointer type, so they must be cast before they’re dereferenced. In fact, you can’t directly dereference a pointer to void.

Finally, according to the POSIX standard, when an inappropriate ioctl command is received, the error code ENOTTY should be returned (Corbet et al., 2005). Hence, the default block sets error to ENOTTY.

Note

At one point in time, only TTY drivers had an ioctl function, which is why ENOTTY means “error: inappropriate ioctl for device” (Corbet et al., 2005).

Now that you’ve examined the structure of a d_ioctl function, I’ll explain how to define an ioctl command.



[3] The d_ioctl function was first introduced in d_foo Functions in Character Drivers.

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

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