Device Driver Tutorial

Exercising the Driver's I/O Controls

In addition to changes in the driver, Quote Of The Day Version 3 introduces a new utility program. The qotdctl command enables you to test the driver's I/O controls.

The source for this command is shown in Example 3–8. Compile the qotdctl utility as follows:


% cc -o qotdctl qotdctl.c

The qotdctl command has the following options:

-g

Get the size that is currently allocated. Call the ioctl(9E) entry point of the driver with the QOTDIOCGSZ request. The QOTDIOCGSZ request reports the current size of the space allocated for the quotation.

-s size

Set the new size to be allocated. Call the ioctl(9E) entry point of the driver with the QOTDIOCSSZ request. The QOTDIOCSSZ request sets a new size for the quotation space.

-r

Discard the contents and reset the device. Call the ioctl(9E) entry point of the driver with the QOTDIOCDISCARD request.

Invoking qotdctl with the -r option is the only way to unset the QOTD_CHANGED flag in the device. The device cannot be detached while the QOTD_CHANGED flag is set. This protects the contents of the ramdisk device from being unintentionally or automatically removed. For example, a device might be automatically removed by the automatic device unconfiguration thread.

When you are no longer interested in the contents of the device, run the qotdctl command with the -r option. Then you can remove the device.

-h

Display help text.

-V

Display the version number of the qotdctl command.

-d device

Specify the device node to use. The default value is /dev/qotd0.

Use the qotdctl command to test the driver's I/O controls:


# ./qotdctl -V
qotdctl 1.0
# ./qotdctl -h
Usage: ./qotdctl [-d device] {-g | -h | -r | -s size | -V}
# ./qotdctl -g
open: No such file or directory

By default, the qotdctl command accesses the /dev/qotd0 device. The qotd_3 device in this example is /devices/pseudo/qotd_3@0:qotd_3. Either define a link from /dev/qotd0 to /devices/pseudo/qotd_3@0:qotd_3 or use the -d option to specify the correct device:


# ./qotdctl -d /devices/pseudo/qotd_3@0:qotd_3 -g
128
# ./qotdctl -d /devices/pseudo/qotd_3@0:qotd_3 -s 512
# ./qotdctl -d /devices/pseudo/qotd_3@0:qotd_3 -g
512
# ./qotdctl -d /devices/pseudo/qotd_3@0:qotd_3 -r
# cat /devices/pseudo/qotd_3@0:qotd_3
On the whole, I'd rather be in Philadelphia. - W. C. Fields

If you try to remove the device now, you will receive an error message:


# rem_drv qotd_3
Device busy
Cannot unload module: qotd_3
Will be unloaded upon reboot.

The device is still marked busy because you have not told the driver that you are no longer interested in this device. Run the qotdctl command with the -r option to unset the QOTD_CHANGED flag in the driver and mark the device not busy:


# ./qotdctl -r

Enter the source code shown in the following example into a text file named qotdctl.c.


Example 3–8 Quote Of The Day I/O Control Command Source File

#include <sys/ioctl.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>

#include "qotd.h"

static const char *DEFAULT_DEVICE = "/dev/qotd0";
static const char *VERSION = "1.0";

static void show_usage(const char *);
static void get_size(const char *);
static void set_size(const char *, size_t);
static void reset_dev(const char *);

int
main(int argc, char *argv[])
{
        int op = -1;
        int opt;
        int invalid_usage = 0;
        size_t sz_arg;
        const char *device = DEFAULT_DEVICE;

        while ((opt = getopt(argc, argv,
            "d:(device)g(get-size)h(help)r(reset)s:(set-size)V(version)"))
            != -1) {
                switch (opt) {
                case 'd':
                        device = optarg;
                        break;
                case 'g':
                        if (op >= 0)
                                invalid_usage++;
                        op = QOTDIOCGSZ;
                        break;
                case 'h':
                        show_usage(argv[0]);
                        exit(0);
                        /*NOTREACHED*/
                case 'r':
                        if (op >= 0)
                                invalid_usage++;
                        op = QOTDIOCDISCARD;
                        break;
                case 's':
                        if (op >= 0)
                                invalid_usage++;
                        op = QOTDIOCSSZ;
                        sz_arg = (size_t)atol(optarg);
                        break;
                case 'V':
                        (void) printf("qotdctl %s\n", VERSION);
                        exit(0);
                        /*NOTREACHED*/
                default:
                        invalid_usage++;
                        break;
                }
        }

        if (invalid_usage > 0 || op < 0) {
                show_usage(argv[0]);
                exit(1);
        }

        switch (op) {
        case QOTDIOCGSZ:
                get_size(device);
                break;
        case QOTDIOCSSZ:
                set_size(device, sz_arg);
                break;
        case QOTDIOCDISCARD:
                reset_dev(device);
                break;
        default:
                (void) fprintf(stderr,
                    "internal error - invalid operation %d\n", op);
                exit(2);
        }

        return (0);
}

static void
show_usage(const char *execname)
{
        (void) fprintf(stderr,
         "Usage: %s [-d device] {-g | -h | -r | -s size | -V}\n", execname);
}

static void
get_size(const char *dev)
{
        size_t sz;
        int fd;

        if ((fd = open(dev, O_RDONLY)) < 0) {
                perror("open");
                exit(3);
        }

        if (ioctl(fd, QOTDIOCGSZ, &sz) < 0) {
                perror("QOTDIOCGSZ");
                exit(4);
        }

        (void) close(fd);

        (void) printf("%zu\n", sz);
}

static void
set_size(const char *dev, size_t sz)
{
        int fd;

        if ((fd = open(dev, O_RDWR)) < 0) {
                perror("open");
                exit(3);
        }

        if (ioctl(fd, QOTDIOCSSZ, &sz) < 0) {
                perror("QOTDIOCSSZ");
                exit(4);
        }

        (void) close(fd);
}

static void
reset_dev(const char *dev)
{
        int fd;

        if ((fd = open(dev, O_RDWR)) < 0) {
                perror("open");
                exit(3);
        }

        if (ioctl(fd, QOTDIOCDISCARD) < 0) {
                perror("QOTDIOCDISCARD");
                exit(4);
        }

        (void) close(fd);
}