Device Driver Tutorial

Chapter 3 Reading and Writing Data in Kernel Memory

In this chapter, you will extend the very simple prototype driver you developed in the previous chapter. The driver you will develop in this chapter displays data read from kernel memory. The first version of this driver writes data to a system log every time the driver is loaded. The second version of this driver displays data at user request. In the third version of this driver, the user can write new data to the device.

Displaying Data Stored in Kernel Memory

The pseudo device driver presented in this section writes a constant string to a system log when the driver is loaded.

This first version of the Quote Of The Day driver (qotd_1) is even more simple than the dummy driver from the previous chapter. The dummy driver includes all functions that are required to drive hardware. This qotd_1 driver includes only the bare minimum functions it needs to make a string available to a user command. For example, this qotd_1 driver has no cb_ops(9S) structure. Therefore, this driver defines no open(9E), close(9E), read(9E), or write(9E) function. If you examine the dev_ops(9S) structure for this qotd_1 driver, you see that no getinfo(9E), attach(9E), or detach(9E) function is defined. This driver contains no function declarations because all the functions that are defined in this driver are declared in the modctl.h header file. You must include the modctl.h header file in your qotd_1.c file.

This qotd_1 driver defines a global variable to hold its text data. The _init(9E) entry point for this driver uses the cmn_err(9F) function to write the string to a system log. The dummy driver also uses the cmn_err(9F) function to display messages. The qotd_1 driver is different from the dummy driver because the qotd_1 driver stores its string in kernel memory.

Writing Quote Of The Day Version 1

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


Example 3–1 Quote Of The Day Version 1 Source File

#include <sys/modctl.h>
#include <sys/conf.h>
#include <sys/devops.h>
#include <sys/cmn_err.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>

#define QOTD_MAXLEN     128

static const char qotd[QOTD_MAXLEN]
        = "Be careful about reading health books. \
You may die of a misprint. - Mark Twain\n";

static struct dev_ops qotd_dev_ops = {
        DEVO_REV,               /* devo_rev */
        0,                      /* devo_refcnt */
        ddi_no_info,            /* devo_getinfo */
        nulldev,                /* devo_identify */
        nulldev,                /* devo_probe */
        nulldev,                /* devo_attach */
        nulldev,                /* devo_detach */
        nodev,                  /* devo_reset */
        (struct cb_ops *)NULL,  /* devo_cb_ops */
        (struct bus_ops *)NULL, /* devo_bus_ops */
        nulldev,                /* devo_power */
        ddi_quiesce_not_needed, /* devo_quiesce */
};

static struct modldrv modldrv = {
        &mod_driverops,
        "Quote of the Day 1.0",
        &qotd_dev_ops};

static struct modlinkage modlinkage = {
        MODREV_1,
        (void *)&modldrv,
        NULL
};

int
_init(void)
{
        cmn_err(CE_CONT, "QOTD: %s\n", qotd);
        return (mod_install(&modlinkage));
}

int
_info(struct modinfo *modinfop)
{
        return (mod_info(&modlinkage, modinfop));
}
int
_fini(void)
{
        return (mod_remove(&modlinkage));
}

Enter the configuration information shown in the following example into a text file named qotd_1.conf.


Example 3–2 Quote Of The Day Version 1 Configuration File

name="qotd_1" parent="pseudo" instance=0;

Building, Installing, and Using Quote Of The Day Version 1

Compile and link the driver. Use the -D_KERNEL option to indicate that this code defines a kernel module. The following example shows compiling and linking for a 32-bit architecture using the Oracle Solaris Studio C compiler:


% cc -D_KERNEL -c qotd_1.c
% ld -r -o qotd_1 qotd_1.o

Note that the name of the driver, qotd_1, must match the name property in the configuration file.

Make sure you are user root when you install the driver.

Copy the driver binary to the /tmp directory as discussed in Device Driver Testing Tips.


# cp qotd_1 /tmp
# ln -s /tmp/qotd_1 /usr/kernel/drv/qotd_1

Copy the configuration file to the kernel driver area of the system.


# cp qotd_1.conf /usr/kernel/drv

This qotd_1 driver writes a message to a system log each time the driver is loaded. The cmn_err(9F) function writes low priority messages such as the message defined in this qotd_1 driver to /dev/log. The syslogd(1M) daemon reads messages from /dev/log and writes low priority messages to /var/adm/messages.

To test this driver, watch for the message in /var/adm/messages. In a separate window, enter the following command:


% tail -f /var/adm/messages

Make sure you are user root when you load the driver. Use the add_drv(1M) command to load the driver:


# add_drv qotd_1

You should see the following messages in the window where you are viewing /var/adm/messages:


date time machine pseudo: [ID 129642 kern.info] pseudo-device: devinfo0
date time machine genunix: [ID 936769 kern.info] devinfo0 is /pseudo/devinfo@0
date time machine qotd: [ID 197678 kern.notice] QOTD_1: Be careful about
reading health books. You may die of a misprint. - Mark Twain

This last line is the content of the variable output by the cmn_err(9F) function in the _init(9E) entry point. The _init(9E) entry point is called when the driver is loaded.

Displaying Data on Demand

The sample code in this section creates a pseudo device that is controlled by the driver. The driver stores data in the device and makes the data available when the user accesses the device for reading.

This section first discusses the important code differences between these two versions of the Quote Of The Day driver. This section then shows you how you can access the device to cause the quotation to display.

Writing Quote Of The Day Version 2

The driver that controls the pseudo device is more complex than the driver shown in the previous section. This section first explains some important features of this version of the driver. This section then shows all the source for this driver.

The following list summarizes the differences between the two versions of the Quote Of The Day driver:

The following sections provide more detail about the additions and changes in Version 2 of the Quote Of The Day driver.

Managing Device State

The _init(9E) and _fini(9E) entry points and all six new entry points defined in this driver maintain a soft state for the device. Most device drivers maintain state information with each instance of the device they control. An instance usually is a sub-device. For example, a disk driver might communicate with a hardware controller device that has several disk drives attached. See Retrieving Driver Soft State Information in Writing Device Drivers for more information about soft states.

This sample driver allows only one instance. The instance number is assigned in the configuration file. See Example 3–4. Most device drivers allow any number of instances of a device to be created. The system manages the device instance numbers, and the DDI soft state functions manage the instances.

    The following flow gives an overview of how DDI soft state functions manage a state pointer and the state of a device instance:

  1. The ddi_soft_state_init(9F) function initializes the state pointer. The state pointer is an opaque handle that enables allocation, deallocation, and tracking of a state structure for each instance of a device. The state structure is a user-defined type that maintains data specific to this instance of the device. In this example, the state pointer and state structure are declared after the entry point declarations. See qotd_state_head and qotd_state in Example 3–3.

  2. The ddi_soft_state_zalloc(9F) function uses the state pointer and the device instance to create the state structure for this instance.

  3. The ddi_get_soft_state(9F) function uses the state pointer and the device instance to retrieve the state structure for this instance of the device.

  4. The ddi_soft_state_free(9F) function uses the state pointer and the device instance to free the state structure for this instance.

  5. The ddi_soft_state_fini(9F) function uses the state pointer to destroy the state pointer and the state structures for all instances of this device.

The ddi_soft_state_zalloc(9F), ddi_get_soft_state(9F), and ddi_soft_state_free(9F) functions coordinate access to the underlying data structures in a way that is safe for multithreading. No additional locks should be necessary.

Initializing and Unloading

The _init(9E) entry point first calls the ddi_soft_state_init(9F) function to initialize the soft state. If the soft state initialization fails, that error code is returned. If the soft state initialization succeeds, the _init(9E) entry point calls the mod_install(9F) function to load a new module. If the module install fails, the _init(9E) entry point calls the ddi_soft_state_fini(9F) function and returns the error code from the failed module install.

Your code must undo everything that it does. You must call ddi_soft_state_fini(9F) if the module install fails because the _init(9E) call succeeded and created a state pointer.

The _fini(9E) entry point must undo everything the _init(9E) entry point did. The _fini(9E) entry point first calls the mod_remove(9F) function to remove the module that the _init(9E) entry point installed. If the module remove fails, that error code is returned. If the module remove succeeds, the _fini(9E) entry point calls the ddi_soft_state_fini(9F) function to destroy the state pointer and the state structures for all instances of this device.

Attaching and Detaching

The attach(9E) entry point first calls the ddi_get_instance(9F) function to retrieve the instance number of the device information node. The attach(9E) entry point uses this instance number to call the ddi_soft_state_zalloc(9F), ddi_get_soft_state(9F), and ddi_create_minor_node(9F) functions.

The attach(9E) entry point calls the ddi_soft_state_zalloc(9F) function to create a state structure for this device instance. If creation of the soft state structure fails, attach(9E) writes an error message to a system log and returns failure. This device instance is not attached. If creation of the soft state structure succeeds, attach(9E) calls the ddi_get_soft_state(9F) function to retrieve the state structure for this device instance.

If retrieval of the state structure fails, attach(9E) writes an error message to a system log, calls the ddi_soft_state_free(9F) function to destroy the state structure that was created by ddi_soft_state_zalloc(9F), and returns failure. This device instance is not attached. If retrieval of the state structure succeeds, attach(9E) calls the ddi_create_minor_node(9F) function to create the device node.

At the top of this driver source file, a constant named QOTD_NAME is defined that holds the string name of the device. This constant is one of the arguments that is passed to ddi_create_minor_node(9F). If creation of the device node fails, attach(9E) writes an error message to a system log, calls the ddi_soft_state_free(9F) function to destroy the state structure that was created by ddi_soft_state_zalloc(9F), calls the ddi_remove_minor_node(9F) function, and returns failure. This device instance is not attached.

If creation of the device node succeeds, this device instance is attached. The attach(9E) entry point assigns the instance number that was retrieved with ddi_get_instance(9F) to the instance member of the state structure for this instance. Then attach(9E) assigns the dev_info structure pointer that was passed in the attach(9E) call to the dev_info structure pointer member of the state structure for this instance. The ddi_report_dev(9F) function writes a message in the system log file when the device is added or when the system is booted. The message announces this device as shown in the following example:


% dmesg
date time machine pseudo: [ID 129642 kern.info] pseudo-device: qotd_20
date time machine genunix: [ID 936769 kern.info] qotd_20 is /pseudo/qotd_2@0

The detach(9E) entry point first calls the ddi_get_instance(9F) function to retrieve the instance number of the device information node. The detach(9E) entry point uses this instance number to call the ddi_soft_state_free(9F) function to destroy the state structure that was created by ddi_soft_state_zalloc(9F) in the attach(9E) entry point. The detach(9E) entry point then calls the ddi_remove_minor_node(9F) function to remove the device that was created by ddi_create_minor_node(9F) in the attach(9E) entry point.

Opening the Device, Closing the Device, and Getting Module Information

The open(9E) and close(9E) entry points are identical in this sample driver. In each case, the entry point first calls the getminor(9F) function to retrieve the minor number of the device. Then each entry point uses this instance number to call the ddi_get_soft_state(9F) function to retrieve the state structure for this device instance. If no state structure is retrieved, an error code is returned. If a state structure is retrieved, the open(9E) and close(9E) entry points both verify the type of this device. If this device is not a character device, the EINVAL (invalid) error code is returned.

If the user wants device information for this device instance, the getinfo(9E) entry point returns the device information from the state structure. If the user wants the instance number of this device instance, the getinfo(9E) entry point uses the getminor(9F) function to return the minor number.

Reading the Data

The read(9E) entry point first calls the getminor(9F) function to retrieve the minor number of the device. The read(9E) entry point uses this instance number to call the ddi_get_soft_state(9F) function to retrieve the state structure for this device instance. If no state structure is retrieved, read(9E) returns an error code. If a state structure is retrieved, read(9E) calls the uiomove(9F) function to copy the quotation from the driver to the uio(9S) I/O request structure.

Checking Data Validity

Version 2 of the driver uses ASSERT(9F) statements to check the validity of data. If the asserted expression is true, the ASSERT(9F) statement does nothing. If the asserted expression is false, the ASSERT(9F) statement writes an error message to the console and causes the system to panic.

To use ASSERT(9F) statements, include the sys/debug.h header file in your source and define the DEBUG preprocessor symbol. If you do not define the DEBUG preprocessor symbol, then the ASSERT(9F) statements do nothing. Simply recompile to activate or inactivate ASSERT(9F) statements.

Quote Of The Day Version 2 Source

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


Example 3–3 Quote Of The Day Version 2 Source File

#include <sys/types.h>
#include <sys/file.h>
#include <sys/errno.h>
#include <sys/open.h>
#include <sys/cred.h>
#include <sys/uio.h>
#include <sys/stat.h>
#include <sys/modctl.h>
#include <sys/conf.h>
#include <sys/devops.h>
#include <sys/debug.h>
#include <sys/cmn_err.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>

#define QOTD_NAME       "qotd"
#define QOTD_MAXLEN     128

static const char qotd[QOTD_MAXLEN]
        = "You can't have everything. \
Where would you put it? - Steven Wright\n";

static void *qotd_state_head;

struct qotd_state {
        int             instance;
        dev_info_t      *devi;
};

static int qotd_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **);
static int qotd_attach(dev_info_t *, ddi_attach_cmd_t);
static int qotd_detach(dev_info_t *, ddi_detach_cmd_t);
static int qotd_open(dev_t *, int, int, cred_t *);
static int qotd_close(dev_t, int, int, cred_t *);
static int qotd_read(dev_t, struct uio *, cred_t *);

static struct cb_ops qotd_cb_ops = {
        qotd_open,              /* cb_open */
        qotd_close,             /* cb_close */
        nodev,                  /* cb_strategy */
        nodev,                  /* cb_print */
        nodev,                  /* cb_dump */
        qotd_read,              /* cb_read */
        nodev,                  /* cb_write */
        nodev,                  /* cb_ioctl */
        nodev,                  /* cb_devmap */
        nodev,                  /* cb_mmap */
        nodev,                  /* cb_segmap */
        nochpoll,               /* cb_chpoll */
        ddi_prop_op,            /* cb_prop_op */
        (struct streamtab *)NULL,       /* cb_str */
        D_MP | D_64BIT,         /* cb_flag */
        CB_REV,                 /* cb_rev */
        nodev,                  /* cb_aread */
        nodev                   /* cb_awrite */
};

static struct dev_ops qotd_dev_ops = {
        DEVO_REV,               /* devo_rev */
        0,                      /* devo_refcnt */
        qotd_getinfo,           /* devo_getinfo */
        nulldev,                /* devo_identify */
        nulldev,                /* devo_probe */
        qotd_attach,            /* devo_attach */
        qotd_detach,            /* devo_detach */
        nodev,                  /* devo_reset */
        &qotd_cb_ops,           /* devo_cb_ops */
        (struct bus_ops *)NULL, /* devo_bus_ops */
        nulldev,                /* devo_power */
        ddi_quiesce_not_needed, /* devo_quiesce */
};

static struct modldrv modldrv = {
        &mod_driverops,
        "Quote of the Day 2.0",
        &qotd_dev_ops};

static struct modlinkage modlinkage = {
        MODREV_1,
        (void *)&modldrv,
        NULL
};

int
_init(void)
{
        int retval;

        if ((retval = ddi_soft_state_init(&qotd_state_head,
            sizeof (struct qotd_state), 1)) != 0)
                return retval;
        if ((retval = mod_install(&modlinkage)) != 0) {
                ddi_soft_state_fini(&qotd_state_head);
                return (retval);
        }

        return (retval);
}

int
_info(struct modinfo *modinfop)
{
        return (mod_info(&modlinkage, modinfop));
}

int
_fini(void)
{
        int retval;

        if ((retval = mod_remove(&modlinkage)) != 0)
                return (retval);
        ddi_soft_state_fini(&qotd_state_head);

        return (retval);
}

/*ARGSUSED*/
static int
qotd_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **resultp)
{
        struct qotd_state *qsp;
        int retval = DDI_FAILURE;

        ASSERT(resultp != NULL);

        switch (cmd) {
        case DDI_INFO_DEVT2DEVINFO:
                if ((qsp = ddi_get_soft_state(qotd_state_head,
                    getminor((dev_t)arg))) != NULL) {
                        *resultp = qsp->devi;
                        retval = DDI_SUCCESS;
                } else
                        *resultp = NULL;
                break;
        case DDI_INFO_DEVT2INSTANCE:
                *resultp = (void *)getminor((dev_t)arg);
                retval = DDI_SUCCESS;
                break;
        }

        return (retval);
}

static int
qotd_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
        int instance = ddi_get_instance(dip);
        struct qotd_state *qsp;

        switch (cmd) {
        case DDI_ATTACH:
                if (ddi_soft_state_zalloc(qotd_state_head, instance)
                    != DDI_SUCCESS) {
                        cmn_err(CE_WARN, "Unable to allocate state for %d",
                            instance);
                        return (DDI_FAILURE);
                }
                if ((qsp = ddi_get_soft_state(qotd_state_head, instance))
                    == NULL) {
                        cmn_err(CE_WARN, "Unable to obtain state for %d",
                            instance);
                        ddi_soft_state_free(dip, instance);
                        return (DDI_FAILURE);
                }
                if (ddi_create_minor_node(dip, QOTD_NAME, S_IFCHR, instance,
                    DDI_PSEUDO, 0) != DDI_SUCCESS) {
                        cmn_err(CE_WARN, "Cannot create minor node for %d",
                            instance);
                        ddi_soft_state_free(dip, instance);
                        ddi_remove_minor_node(dip, NULL);
                        return (DDI_FAILURE);
                }
                qsp->instance = instance;
                qsp->devi = dip;

                ddi_report_dev(dip);
                return (DDI_SUCCESS);
        case DDI_RESUME:
                return (DDI_SUCCESS);
        default:
                return (DDI_FAILURE);
        }
}

static int
qotd_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
        int instance = ddi_get_instance(dip);

        switch (cmd) {
        case DDI_DETACH:
                ddi_soft_state_free(qotd_state_head, instance);
                ddi_remove_minor_node(dip, NULL);
                return (DDI_SUCCESS);
        case DDI_SUSPEND:
                return (DDI_SUCCESS);
        default:
                return (DDI_FAILURE);
        }
}

/*ARGSUSED*/
static int
qotd_open(dev_t *devp, int flag, int otyp, cred_t *credp)
{
        int instance = getminor(*devp);
        struct qotd_state *qsp;

        if ((qsp = ddi_get_soft_state(qotd_state_head, instance)) == NULL)
                return (ENXIO);

        ASSERT(qsp->instance == instance);

        if (otyp != OTYP_CHR)
                return (EINVAL);

        return (0);
}

/*ARGSUSED*/
static int
qotd_close(dev_t dev, int flag, int otyp, cred_t *credp)
{
        struct qotd_state *qsp;
        int instance = getminor(dev);

        if ((qsp = ddi_get_soft_state(qotd_state_head, instance)) == NULL)
                return (ENXIO);

        ASSERT(qsp->instance == instance);

        if (otyp != OTYP_CHR)
                return (EINVAL);

        return (0);
}

/*ARGSUSED*/
static int
qotd_read(dev_t dev, struct uio *uiop, cred_t *credp)
{
        struct qotd_state *qsp;
        int instance = getminor(dev);

        if ((qsp = ddi_get_soft_state(qotd_state_head, instance)) == NULL)
                return (ENXIO);

        ASSERT(qsp->instance == instance);

        return (uiomove((void *)qotd, min(uiop->uio_resid, strlen(qotd)),
            UIO_READ, uiop));
}

Enter the configuration information shown in the following example into a text file named qotd_2.conf.


Example 3–4 Quote Of The Day Version 2 Configuration File

name="qotd_2" parent="pseudo" instance=0;

Building, Installing, and Using Quote Of The Day Version 2

Version 2 of the driver uses ASSERT(9F) statements to check the validity of data. To use ASSERT(9F) statements, include the sys/debug.h header file in your source and define the DEBUG preprocessor symbol.

Compile and link the driver. If you use ASSERT(9F) statements to check the validity of data, you must define the DEBUG preprocessor symbol:


% cc -D_KERNEL -DDEBUG -c qotd_2.c
% ld -r -o qotd_2 qotd_2.o

The following example shows compiling and linking for a 32-bit architecture if you are not using ASSERT(9F) statements:


% cc -D_KERNEL -c qotd_2.c
% ld -r -o qotd_2 qotd_2.o

Make sure you are user root when you install the driver.

Copy the driver binary to the /tmp directory as discussed in Building and Installing the Template Driver.


# cp qotd_2 /tmp
# ln -s /tmp/qotd_2 /usr/kernel/drv/qotd_2

Copy the configuration file to the kernel driver area of the system.


# cp qotd_2.conf /usr/kernel/drv

In a separate window, enter the following command:


% tail -f /var/adm/messages

Make sure you are user root when you load the driver. Use the add_drv(1M) command to load the driver:


# add_drv qotd_2

You should see the following messages in the window where you are viewing /var/adm/messages:


date time machine pseudo: [ID 129642 kern.info] pseudo-device: devinfo0
date time machine genunix: [ID 936769 kern.info] devinfo0 is /pseudo/devinfo@0
date time machine pseudo: [ID 129642 kern.info] pseudo-device: qotd_20
date time machine genunix: [ID 936769 kern.info] qotd_20 is /pseudo/qotd_2@0

When this version of the Quote Of The Day driver loads, it does not display its quotation. The qotd_1 driver wrote a message to a system log through its _init(9E) entry point. This qotd_2 driver stores its data and makes the data available through its read(9E) entry point.

You can use the modinfo(1M) command to display the module information for this version of the Quote Of The Day driver. The module name is the value you entered for the second member of the modldrv structure. The value 96 is the major number of this module.


% modinfo | grep qotd
182 ed115948    754  96   1  qotd_2 (Quote of the Day 2.0)
% grep qotd /etc/name_to_major
qotd_1 94
qotd_2 96

This driver also is the most recent module listed by prtconf(1M) in the pseudo device section:


% prtconf -P | grep qotd
        qotd_1, instance #0 (driver not attached)
        qotd_2, instance #0

When you access this qotd_2 device for reading, the command you use to access the device retrieves the data from the device node. The command then displays the data in the same way that the command displays any other input. To get the name of the device special file, look in the /devices directory:


% ls -l /devices/pseudo/qotd*
crw-------   1 root   sys   96, 0 date time /devices/pseudo/qotd_2@0:qotd

This output shows that qotd_2@0:qotd is a character device. This listing also shows that only the root user has permission to read or write this device. Make sure you are user root when you test this driver. To test the qotd_2 driver, you can use the more(1) command to access the device file for reading:


# more /devices/pseudo/qotd_2@0:qotd
You can't have everything. Where would you put it? - Steven Wright
You can't have everything. Where would you put it? - Steven Wright

Modifying Data Stored in Kernel Memory

In this third version of the Quote Of The Day driver, the user can write to the data that is stored in kernel memory. The pseudo device that is created in this section is a pseudo-disk device or ramdisk device. A ramdisk device simulates a disk device by allocating kernel memory that is subsequently used as data storage. See ramdisk(7D) for more information about ramdisk devices.

As in Version 2 of the Quote Of The Day driver, this Version 3 driver stores its data and makes the data available through its read(9E) entry point. This Version 3 driver overwrites characters from the beginning of the data when the user writes to the device.

This section first discusses the important code differences between this version and the previous version of the Quote Of The Day driver. This section then shows you how you can modify and display the quotation.

In addition to changes in the driver, Quote Of The Day Version 3 introduces a header file and an auxiliary program. The header file is discussed in the following section. The utility program is discussed in Using Quote Of The Day Version 3.

Writing Quote Of The Day Version 3

This third version of the Quote Of The Day driver is more complex than the second version because this third version enables a user to change the text that is stored in the device.

This section first explains some important features of this version of the driver. This section then shows all the source for this driver, including the header file and the configuration file.

The following list summarizes the new features in Version 3 of the Quote Of The Day driver:

The following sections provide more detail about the additions and changes in Version 3 of the Quote Of The Day driver. The dev_ops(9S) structure and the modlinkage(9S) structure are the same as they were in Version 2 of the driver. The modldrv(9S) structure has not changed except for the version number of the driver. The _init(9E), _info(9E), _fini(9E), getinfo(9E), open(9E), and close(9E) functions are the same as in Version 2 of the driver.

Attaching, Allocating Memory, and Initializing a Mutex and a Condition Variable

The qotd_attach() entry point first allocates and gets the device soft state. The qotd_attach() routine then creates a minor node. All of this code is the same as in Version 2 of the Quote Of The Day driver. If the call to ddi_create_minor_node(9F) is successful, the qotd_attach() routine sets the QOTD_DIDMINOR flag in the new flags member of the qotd_state state structure.

Version 3 of the Quote Of The Day driver defines four new constants that keep track of four different events. A routine can test these flags to determine whether to deallocate, close, or remove resources. All four of these flags are set in the qotd_attach() entry point. All four of these conditions are checked in the qotd_detach() entry point, and the appropriate action is taken for each condition.

Note that operations are undone in the qotd_detach() entry point in the opposite order in which they were done in the qotd_attach() entry point. The qotd_attach() routine creates a minor node, allocates memory for the quotation, initializes a mutex, and initializes a condition variable. The qotd_detach() routine destroys the condition variable, destroys the mutex, frees the memory, and removes the minor node.

After the minor node is created, the qotd_attach() routine allocates memory for the quotation. For information on allocating and freeing memory in this driver, see Allocating and Freeing Kernel Memory. If memory is allocated, the qotd_attach() routine sets the QOTD_DIDALLOC flag in the flags member of the state structure.

The qotd_attach() routine then calls the mutex_init(9F) function to initialize a mutex. If this operation is successful, the qotd_attach() routine sets the QOTD_DIDMUTEX flag. The qotd_attach() routine then calls the cv_init(9F) function to initialize a condition variable. If this operation is successful, the qotd_attach() routine sets the QOTD_DIDCV flag.

The qotd_attach() routine then calls the strlcpy(9F) function to copy the initial quotation string to the new quotation member of the device state structure. Note that the strlcpy(9F) function is used instead of the strncpy(9F) function. The strncpy(9F) function can be wasteful because it always copies n characters, even if the destination is smaller than n characters. Try using strncpy(9F) instead of strlcpy(9F) and note the difference in the behavior of the driver.

Finally, the initial quotation length is copied to the new quotation length member of the state structure. The remainder of the qotd_attach() routine is the same as in Version 2.

Checking for Changes, Cleaning Up, and Detaching

The qotd_detach() routine is almost all new. The qotd_detach() routine must first get the soft state because the qotd_detach() routine needs to check the flags member of the state structure.

The first flag the qotd_detach() routine checks is the QOTD_CHANGED flag. The QOTD_CHANGED flag indicates whether the device is in the initial state. The QOTD_CHANGED flag is set in the qotd_rw() routine and in the qotd_ioctl() entry point. The QOTD_CHANGED flag is set any time the user does anything to the device other than simply inspect the device. If the QOTD_CHANGED flag is set, the size or content of the storage buffer has been modified. See Writing New Data for more information on the QOTD_CHANGED flag. When the QOTD_CHANGED flag is set, the detach operation fails because the device might contain data that is valuable to the user and the device should not be removed. If the QOTD_CHANGED flag is set, the qotd_detach() routine returns an error that the device is busy.

Once the quotation has been modified, the device cannot be detached until the user runs the qotdctl command with the -r option. The -r option reinitializes the quotation and indicates that the user is no longer interested in the contents of the device. See Exercising the Driver's I/O Controls for more information about the qotdctl command.

The qotd_detach() routine then checks the four flags that were set in the qotd_attach() routine. If the QOTD_DIDCV flag is set, the qotd_detach() routine calls the cv_destroy(9F) function. If the QOTD_DIDMUTEX flag is set, the qotd_detach() routine calls the mutex_destroy(9F) function. If the QOTD_DIDALLOC flag is set, the qotd_detach() routine calls the ddi_umem_free(9F) function. Finally, if the QOTD_DIDMINOR flag is set, the qotd_detach() routine calls the ddi_remove_minor_node(9F) function.

Allocating and Freeing Kernel Memory

One of the new members of the device state structure supports memory allocation and deallocation. The qotd_cookie member receives a value from the ddi_umem_alloc(9F) function. The qotd_cookie value is then used by the ddi_umem_free(9F) function to free the memory.

Version 3 of the Quote Of The Day driver allocates kernel memory in three places:

The qotd_attach() routine allocates memory after the minor node is created. Memory must be allocated to enable the user to modify the quotation. The qotd_attach() routine calls the ddi_umem_alloc(9F) function with the DDI_UMEM_NOSLEEP flag so that the ddi_umem_alloc(9F) function will return immediately. If the requested amount of memory is not available, ddi_umem_alloc(9F) returns NULL immediately and does not wait for memory to become available. If no memory is allocated, qotd_attach() calls qotd_detach() and returns an error. If memory is allocated, qotd_attach() sets the QOTD_DIDALLOC flag so that this memory will be freed by qotd_detach() later.

The second place the driver allocates memory is in the QOTDIOCSSZ case of the qotd_ioctl() entry point. The QOTDIOCSSZ case sets a new size for the device. A new size is set when the user runs the qotdctl command with the -s option. See Exercising the Driver's I/O Controls for more information about the qotdctl command. This time, the ddi_umem_alloc(9F) function is called with the DDI_UMEM_SLEEP flag so that ddi_umem_alloc(9F) will wait for the requested amount of memory to be available. When the ddi_umem_alloc(9F) function returns, the requested memory has been allocated.

Note that you cannot always use the DDI_UMEM_SLEEP flag. See the CONTEXT sections of the ddi_umem_alloc(9F), kmem_alloc(9F), and kmem_zalloc(9F) man pages. Also note the behavioral differences among these three functions. The kmem_zalloc(9F) function is more efficient for small amounts of memory. The ddi_umem_alloc(9F) function is faster and better for large allocations. The ddi_umem_alloc(9F) function is used in this qotd_3 driver because ddi_umem_alloc(9F) allocates whole pages of memory. The kmem_zalloc(9F) function might save memory because it might allocate smaller chunks of memory. This qotd_3 driver demonstrates a ramdisk device. In a production ramdisk device, you would use ddi_umem_alloc(9F) to allocate page-aligned memory.

After the current quotation is copied to the new space, the qotd_ioctl() routine calls the ddi_umem_free(9F) function to free the memory that was previously allocated.

The third place the driver allocates memory is in the QOTDIOCDISCARD case of the qotd_ioctl() entry point. The QOTDIOCDISCARD case is called from the qotdctl command. The qotdctl command with the -r option sets the quotation back to its initial value. If the number of bytes allocated for the current quotation is different from the initial number of bytes, then new memory is allocated to reinitialize the quotation. Again, the DDI_UMEM_SLEEP flag is used so that when the ddi_umem_alloc(9F) function returns, the requested memory has been allocated. The qotd_ioctl() routine then calls the ddi_umem_free(9F) function to free the memory that was previously allocated.

Managing Thread Synchronization

The Quote Of The Day Version 3 driver uses condition variables and mutual exclusion locks (mutexes) together to manage thread synchronization. See the Multithreaded Programming Guide for more information about mutexes, condition variables, and thread synchronization.

In this driver, the mutex and condition variable both are initialized in the qotd_attach() entry point and destroyed in the qotd_detach() entry point. The condition variable is tested in the qotd_rw() routine and in the qotd_ioctl() entry point.

The condition variable waits on the QOTD_BUSY condition. This condition is needed because the driver must do some operations that rely on exclusive access to internal structures without holding a lock. Accessing the storage buffer or its metadata requires mutual exclusion, but the driver cannot hold a lock if the operation might sleep. Instead of holding a lock in this case, the driver waits on the QOTD_BUSY condition.

The driver acquires a mutex when the driver tests the condition variable and when the driver accesses the storage buffer. The mutex protects the storage buffer. Failure to use a mutual exclusion when accessing the storage buffer could allow one user process to resize the buffer while another user process tries to read the buffer, for example. The result of unprotected buffer access could be data corruption or a panic.

The condition variable is used when functions are called that might need to sleep. The ddi_copyin(9F), ddi_copyout(9F), and uiomove(9F) functions can sleep. Memory allocation can sleep if you use the SLEEP flag. Functions must not hold a mutex while they are sleeping. Sleeping while holding a mutex can cause deadlock. When a function might sleep, set the QOTD_BUSY flag and take the condition variable, which drops the mutex. To avoid race conditions, the QOTD_BUSY flag can be set or cleared only when holding the mutex. For more information on deadlock, see Using Mutual Exclusion Locks in Multithreaded Programming Guide and Avoiding Deadlock in Multithreaded Programming Guide.

Locking Rules for Quote Of The Day Version 3

    The locking rules for this qotd_3 driver are as follows:

  1. You must have exclusive access to do any of the following operations. To have exclusive access, you must own the mutex or you must set QOTD_BUSY. Threads must wait on QOTD_BUSY.

    • Test the contents of the storage buffer.

    • Modify the contents of the storage buffer.

    • Modify the size of the storage buffer.

    • Modify variables that refer to the address of the storage buffer.

    1. If your operation does not need to sleep, do the following actions:

    2. Acquire the mutex.

    3. Wait until QOTD_BUSY is cleared. When the thread that set QOTD_BUSY clears QOTD_BUSY, that thread also should signal threads waiting on the condition variable and then drop the mutex.

    4. Perform your operation. You do not need to set QOTD_BUSY before you perform your operation.

    5. Drop the mutex.

    The following code sample illustrates this rule:

    mutex_enter(&qsp->lock);
    while (qsp->flags & QOTD_BUSY) {
            if (cv_wait_sig(&qsp->cv, &qsp->lock) == 0) {
                    mutex_exit(&qsp->lock);
                    ddi_umem_free(new_cookie);
                    return (EINTR);
            }
    }
    memcpy(new_qotd, qsp->qotd, min(qsp->qotd_len, new_len));
    ddi_umem_free(qsp->qotd_cookie);
    qsp->qotd = new_qotd;
    qsp->qotd_cookie = new_cookie;
    qsp->qotd_len = new_len;
    qsp->flags |= QOTD_CHANGED;
    mutex_exit(&qsp->lock);
    1. If your operation must sleep, do the following actions:

    2. Acquire the mutex.

    3. Set QOTD_BUSY.

    4. Drop the mutex.

    5. Perform your operation.

    6. Reacquire the mutex.

    7. Signal any threads waiting on the condition variable.

    8. Drop the mutex.

These locking rules are very simple. These three rules ensure consistent access to the buffer and its metadata. Realistic drivers probably have more complex locking requirements. For example, drivers that use ring buffers or drivers that manage multiple register sets or multiple devices have more complex locking requirements.

Lock and Condition Variable Members of the State Structure

The device state structure for Version 3 of the Quote Of The Day driver contains two new members to help manage thread synchronization:

Creating and Destroying Locks and Condition Variables

Version 3 of the Quote Of The Day driver defines two constants to make sure the mutex and condition variable are destroyed when the driver is finished with them. The driver uses these constants to set and reset the new flags member of the device state structure.

Waiting on Signals

In the qotd_rw() and qotd_ioctl() routines, the cv_wait_sig(9F) calls wait until the condition variable is signaled to proceed or until a signal(3C) is received. Either the cv_signal(9F) function or the cv_broadcast(9F) function signals the cv condition variable to proceed.

A thread can wait on a condition variable until either the condition variable is signaled or a signal(3C) is received by the process. The cv_wait(9F) function waits until the condition variable is signaled but ignores signal(3C) signals. This driver uses the cv_wait_sig(9F) function instead of the cv_wait(9F) function because this driver responds if a signal is received by the process performing the operation. If a signal(3C) is taken by the process, this driver returns an interrupt error and does not complete the operation. The cv_wait_sig(9F) function usually is preferred to the cv_wait(9F) function because this implementation offers the user program more precise response. The signal(3C) causes an effect closer to the point at which the process was executing when the signal(3C) was received.

In some cases, you cannot use the cv_wait_sig(9F) function because your driver cannot be interrupted by a signal(3C). For example, you cannot use the cv_wait_sig(9F) function during a DMA transfer that will result in an interrupt later. In this case, if you abandon the cv_wait_sig(9F) call, you have nowhere to put the data when the DMA transfer is finished, and your driver will panic.

Writing New Data

The cb_ops(9S) structure for Version 3 of the Quote Of The Day driver declares two new entry points that support modifying the quotation. The two new entry points are write(9E) and ioctl(9E). The qotd_rw() routine is a third new routine in Version 3 of the driver. The qotd_rw() routine is called by both the read(9E) entry point and the write(9E) entry point.

The device state structure for Version 3 of the Quote Of The Day driver contains two new members that are used to modify the quotation. The qotd string holds the quotation for the current instance of the device. The qotd_len member holds the length in bytes of the current quotation.

Version 3 of the driver also defines two new constants that support modifying the quotation. In place of QOTD_MAXLEN, Version 3 of the driver defines QOTD_MAX_LEN. QOTD_MAX_LEN is used in the qotd_ioctl() entry point to test whether the user has entered a string that is too long. Version 3 of the driver also defines QOTD_CHANGED. The QOTD_CHANGED flag is set in the qotd_rw() routine and in the qotd_ioctl() entry point when a new quotation is copied from the user.

When the qotd_3 device is opened for writing, the kernel calls the qotd_write() entry point. The qotd_write() entry point then calls the qotd_rw() routine and passes a UIO_WRITE flag. The new qotd_read() entry point is exactly the same as the qotd_write() entry point, except that the qotd_read() entry point passes a UIO_READ flag. The qotd_rw() routine supports both reading and writing the device and thereby eliminates much duplicate code.

The qotd_rw() routine first gets the device soft state. Then the qotd_rw() routine checks the length of the I/O request in the uio(9S) I/O request structure. If this length is zero, the qotd_rw() routine returns zero. If this length is not zero, the qotd_rw() routine enters a mutex.

While the device is busy, the qotd_rw() routine checks whether the condition variable has been signaled or a signal(3C) is pending. If either of these conditions is true, the qotd_rw() routine exits the mutex and returns an error.

When the device is not busy, the qotd_rw() routine checks whether the data offset in the uio(9S) I/O request structure is valid. If the offset is not valid, the qotd_rw() routine exits the mutex and returns an error. If the offset is valid, the local length variable is set to the difference between the offset in the I/O request structure and the length in the device state structure. If this difference is zero, the qotd_rw() routine exits the mutex and returns. If the device was opened for writing, the qotd_rw() routine returns a space error. Otherwise, the qotd_rw() routine returns zero.

The qotd_rw() routine then sets the QOTD_BUSY flag in the flags member of the device state structure and exits the mutex. The qotd_rw() routine then calls the uiomove(9F) function to copy the quotation. If the rw argument is UIO_READ, then the quotation is transferred from the state structure to the I/O request structure. If the rw argument is UIO_WRITE, then the quotation is transferred from the I/O request structure to the state structure.

The qotd_rw() routine then enters a mutex again. If the device was opened for writing, the qotd_rw() routine sets the QOTD_CHANGED flag. The qotd_rw() routine then sets the device to not busy, calls cv_broadcast(9F) to unblock any threads that were blocked on this condition variable, and exits the mutex.

Finally, the qotd_rw() routine returns the quotation. The quotation is written to the device node.

Reporting and Setting Device Size and Re-initializing the Device

The behavior of the ioctl(9E) entry point depends on the command value passed in to the entry point. These constants are defined in the new qotd.h header file. The qotd_ioctl() routine reports the size of the space allocated for the quotation, sets a new amount of space to allocate for the quotation, or resets the quotation back to its initial value.

If the request is to report the size of the space allocated for the quotation, then the qotd_ioctl() routine first sets a local size variable to the value of the quotation length in the state structure. If the device was not opened for reading, the qotd_ioctl() routine returns an error.

Because the qotd_ioctl() routine transfers data between kernel space and user space, the qotd_ioctl() routine must check whether both spaces are using the same data model. If the return value of the ddi_model_convert_from(9F) function is DDI_MODEL_ILP32, then the driver must convert to 32-bit data before calling ddi_copyout(9F) to transfer the current size of the quotation space. If the return value of the ddi_model_convert_from(9F) function is DDI_MODEL_NONE, then no data type conversion is necessary.

If the request is to set a new size for the space allocated for the quotation, then the qotd_ioctl() routine first sets local variables for the new size, the new quotation, and a new memory allocation cookie. If the device was not opened for writing, the qotd_ioctl() routine returns an error.

The qotd_ioctl() routine then checks again for data model mismatch. If the return value of the ddi_model_convert_from(9F) function is DDI_MODEL_ILP32, then the driver declares a 32-bit size variable to receive the new size from ddi_copyin(9F). When the new size is received, the size is converted to the data type of the kernel space.

If the new size is zero or is greater than QOTD_MAX_LEN, the qotd_ioctl() routine returns an error. If the new size is valid, then the qotd_ioctl() routine allocates new memory for the quotation and enters a mutex.

While the device is busy, the qotd_ioctl() routine checks whether the condition variable has been signaled or a signal(3C) is pending. If either of these conditions is true, the qotd_ioctl() routine exits the mutex, frees the new memory it allocated, and returns an error.

When the device is not busy, the qotd_ioctl() routine uses memcpy(9F) to copy the quotation from the driver's state structure to the new space. The qotd_ioctl() routine then frees the memory currently pointed to by the state structure, and updates the state structure members to the new values. The qotd_ioctl() routine then sets the QOTD_CHANGED flag, exits the mutex, and returns.

If the request is to discard the current quotation and reset to the initial quotation, then the qotd_ioctl() routine first sets local variables for the new quotation and a new memory allocation cookie. If the device was not opened for writing, the qotd_ioctl() routine returns an error. If the space allocated for the current quotation is different from the space allocated for the initial quotation, then the qotd_ioctl() routine allocates new memory that is the size of the initial space and enters a mutex.

While the device is busy, the qotd_ioctl() routine checks whether the condition variable has been signaled or a signal(3C) is pending. If either of these conditions is true, the qotd_ioctl() routine exits the mutex, frees the new memory it allocated, and returns an error.

When the device is not busy, the qotd_ioctl() routine frees the memory currently pointed to by the state structure, updates the memory state structure members to the new values, and resets the length to its initial value. If the size of the current quotation space was the same as the initial size and no new memory was allocated, then qotd_ioctl() calls bzero(9F) to clear the current quotation. The qotd_ioctl() routine then calls the strlcpy(9F) function to copy the initial quotation string to the quotation member of the state structure. The qotd_ioctl() routine then unsets the QOTD_CHANGED flag, exits the mutex, and returns.

Once the QOTD_CHANGED flag has been set, the only way to unset it is to run the qotdctl command with the -r option. See Exercising the Driver's I/O Controls for more information about the qotdctl command.

Quote Of The Day Version 3 Source

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


Example 3–5 Quote Of The Day Version 3 Source File

#include <sys/types.h>
#include <sys/file.h>
#include <sys/errno.h>
#include <sys/open.h>
#include <sys/cred.h>
#include <sys/uio.h>
#include <sys/stat.h>
#include <sys/ksynch.h>
#include <sys/modctl.h>
#include <sys/conf.h>
#include <sys/devops.h>
#include <sys/debug.h>
#include <sys/cmn_err.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>

#include "qotd.h"

#define QOTD_NAME       "qotd_3"

static const char init_qotd[]
        = "On the whole, I'd rather be in Philadelphia. - W. C. Fields\n";
static const size_t init_qotd_len = 128;

#define QOTD_MAX_LEN    65536           /* Maximum quote in bytes */
#define QOTD_CHANGED    0x1             /* User has made modifications */
#define QOTD_DIDMINOR   0x2             /* Created minors */
#define QOTD_DIDALLOC   0x4             /* Allocated storage space */
#define QOTD_DIDMUTEX   0x8             /* Created mutex */
#define QOTD_DIDCV      0x10            /* Created cv */
#define QOTD_BUSY       0x20            /* Device is busy */

static void *qotd_state_head;

struct qotd_state {
        int             instance;
        dev_info_t      *devi;
        kmutex_t        lock;
        kcondvar_t      cv;
        char            *qotd;
        size_t          qotd_len;
        ddi_umem_cookie_t qotd_cookie;
        int             flags;
};

static int qotd_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **);
static int qotd_attach(dev_info_t *, ddi_attach_cmd_t);
static int qotd_detach(dev_info_t *, ddi_detach_cmd_t);
static int qotd_open(dev_t *, int, int, cred_t *);
static int qotd_close(dev_t, int, int, cred_t *);
static int qotd_read(dev_t, struct uio *, cred_t *);
static int qotd_write(dev_t, struct uio *, cred_t *);
static int qotd_rw(dev_t, struct uio *, enum uio_rw);
static int qotd_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);

static struct cb_ops qotd_cb_ops = {
        qotd_open,              /* cb_open */
        qotd_close,             /* cb_close */
        nodev,                  /* cb_strategy */
        nodev,                  /* cb_print */
        nodev,                  /* cb_dump */
        qotd_read,              /* cb_read */
        qotd_write,             /* cb_write */
        qotd_ioctl,             /* cb_ioctl */
        nodev,                  /* cb_devmap */
        nodev,                  /* cb_mmap */
        nodev,                  /* cb_segmap */
        nochpoll,               /* cb_chpoll */
        ddi_prop_op,            /* cb_prop_op */
        (struct streamtab *)NULL,       /* cb_str */
        D_MP | D_64BIT,         /* cb_flag */
        CB_REV,                 /* cb_rev */
        nodev,                  /* cb_aread */
        nodev                   /* cb_awrite */
};

static struct dev_ops qotd_dev_ops = {
        DEVO_REV,               /* devo_rev */
        0,                      /* devo_refcnt */
        qotd_getinfo,           /* devo_getinfo */
        nulldev,                /* devo_identify */
        nulldev,                /* devo_probe */
        qotd_attach,            /* devo_attach */
        qotd_detach,            /* devo_detach */
        nodev,                  /* devo_reset */
        &qotd_cb_ops,           /* devo_cb_ops */
        (struct bus_ops *)NULL, /* devo_bus_ops */
        nulldev,                /* devo_power */
        ddi_quiesce_not_needed, /* devo_quiesce */
};

static struct modldrv modldrv = {
        &mod_driverops,
        "Quote of the day 3.0",
        &qotd_dev_ops};

static struct modlinkage modlinkage = {
        MODREV_1,
        (void *)&modldrv,
        NULL
};

int
_init(void)
{
        int retval;

        if ((retval = ddi_soft_state_init(&qotd_state_head,
            sizeof (struct qotd_state), 1)) != 0)
                return retval;
        if ((retval = mod_install(&modlinkage)) != 0) {
                ddi_soft_state_fini(&qotd_state_head);
                return (retval);
        }

        return (retval);
}

int
_info(struct modinfo *modinfop)
{
        return (mod_info(&modlinkage, modinfop));
}

int
_fini(void)
{
        int retval;

        if ((retval = mod_remove(&modlinkage)) != 0)
                return (retval);
        ddi_soft_state_fini(&qotd_state_head);

        return (retval);
}

/*ARGSUSED*/
static int
qotd_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **resultp)
{
        struct qotd_state *qsp;
        int retval = DDI_FAILURE;

        ASSERT(resultp != NULL);

        switch (cmd) {
        case DDI_INFO_DEVT2DEVINFO:
                if ((qsp = ddi_get_soft_state(qotd_state_head,
                    getminor((dev_t)arg))) != NULL) {
                        *resultp = qsp->devi;
                        retval = DDI_SUCCESS;
                } else
                        *resultp = NULL;
                break;
        case DDI_INFO_DEVT2INSTANCE:
                *resultp = (void *)getminor((dev_t)arg);
                retval = DDI_SUCCESS;
                break;
        }

        return (retval);
}

static int
qotd_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
        int instance = ddi_get_instance(dip);
        struct qotd_state *qsp;

        switch (cmd) {
        case DDI_ATTACH:
                if (ddi_soft_state_zalloc(qotd_state_head, instance)
                    != DDI_SUCCESS) {
                        cmn_err(CE_WARN, "Unable to allocate state for %d",
                            instance);
                        return (DDI_FAILURE);
                }
                if ((qsp = ddi_get_soft_state(qotd_state_head, instance))
                    == NULL) {
                        cmn_err(CE_WARN, "Unable to obtain state for %d",
                            instance);
                        ddi_soft_state_free(dip, instance);
                        return (DDI_FAILURE);
                }
                if (ddi_create_minor_node(dip, QOTD_NAME, S_IFCHR, instance,
                    DDI_PSEUDO, 0) != DDI_SUCCESS) {
                        cmn_err(CE_WARN, "Unable to create minor node for %d",
                            instance);
                        (void)qotd_detach(dip, DDI_DETACH);
                        return (DDI_FAILURE);
                }
                qsp->flags |= QOTD_DIDMINOR;
                qsp->qotd = ddi_umem_alloc(init_qotd_len, DDI_UMEM_NOSLEEP,
                    &qsp->qotd_cookie);
                if (qsp->qotd == NULL) {
                        cmn_err(CE_WARN, "Unable to allocate storage for %d",
                            instance);
                        (void)qotd_detach(dip, DDI_DETACH);
                        return (DDI_FAILURE);
                }
                qsp->flags |= QOTD_DIDALLOC;
                mutex_init(&qsp->lock, NULL, MUTEX_DRIVER, NULL);
                qsp->flags |= QOTD_DIDMUTEX;
                cv_init(&qsp->cv, NULL, CV_DRIVER, NULL);
                qsp->flags |= QOTD_DIDCV;

                (void)strlcpy(qsp->qotd, init_qotd, init_qotd_len);
                qsp->qotd_len = init_qotd_len;
                qsp->instance = instance;
                qsp->devi = dip;

                ddi_report_dev(dip);
                return (DDI_SUCCESS);
        case DDI_RESUME:
                return (DDI_SUCCESS);
        default:
                return (DDI_FAILURE);
        }
}


static int
qotd_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
        int instance = ddi_get_instance(dip);
        struct qotd_state *qsp;

        switch (cmd) {
        case DDI_DETACH:
                qsp = ddi_get_soft_state(qotd_state_head, instance);
                if (qsp != NULL) {
                        ASSERT(!(qsp->flags & QOTD_BUSY));
                        if (qsp->flags & QOTD_CHANGED)
                                return (EBUSY);
                        if (qsp->flags & QOTD_DIDCV)
                                cv_destroy(&qsp->cv);
                        if (qsp->flags & QOTD_DIDMUTEX)
                                mutex_destroy(&qsp->lock);
                        if (qsp->flags & QOTD_DIDALLOC) {
                                ASSERT(qsp->qotd != NULL);
                                ddi_umem_free(qsp->qotd_cookie);
                        }
                        if (qsp->flags & QOTD_DIDMINOR)
                                ddi_remove_minor_node(dip, NULL);
                }
                ddi_soft_state_free(qotd_state_head, instance);
                return (DDI_SUCCESS);
        case DDI_SUSPEND:
                return (DDI_SUCCESS);
        default:
                return (DDI_FAILURE);
        }
}

/*ARGSUSED*/
static int
qotd_open(dev_t *devp, int flag, int otyp, cred_t *credp)
{
        int instance = getminor(*devp);
        struct qotd_state *qsp;

        if ((qsp = ddi_get_soft_state(qotd_state_head, instance)) == NULL)
                return (ENXIO);

        ASSERT(qsp->instance == instance);

        if (otyp != OTYP_CHR)
                return (EINVAL);

        return (0);
}

/*ARGSUSED*/
static int
qotd_close(dev_t dev, int flag, int otyp, cred_t *credp)
{
        struct qotd_state *qsp;
        int instance = getminor(dev);

        if ((qsp = ddi_get_soft_state(qotd_state_head, instance)) == NULL)
                return (ENXIO);

        ASSERT(qsp->instance == instance);

        if (otyp != OTYP_CHR)
                return (EINVAL);

        return (0);
}

/*ARGSUSED*/
static int
qotd_read(dev_t dev, struct uio *uiop, cred_t *credp)
{
        return qotd_rw(dev, uiop, UIO_READ);
}

/*ARGSUSED*/
static int
qotd_write(dev_t dev, struct uio *uiop, cred_t *credp)
{
        return qotd_rw(dev, uiop, UIO_WRITE);
}

static int
qotd_rw(dev_t dev, struct uio *uiop, enum uio_rw rw)
{
        struct qotd_state *qsp;
        int instance = getminor(dev);
        size_t len = uiop->uio_resid;
        int retval;

        if ((qsp = ddi_get_soft_state(qotd_state_head, instance)) == NULL)
                return (ENXIO);

        ASSERT(qsp->instance == instance);

        if (len == 0)
                return (0);

        mutex_enter(&qsp->lock);

        while (qsp->flags & QOTD_BUSY) {
                if (cv_wait_sig(&qsp->cv, &qsp->lock) == 0) {
                        mutex_exit(&qsp->lock);
                        return (EINTR);
                }
        }

        if (uiop->uio_offset < 0 || uiop->uio_offset > qsp->qotd_len) {
                mutex_exit(&qsp->lock);
                return (EINVAL);
        }

        if (len > qsp->qotd_len - uiop->uio_offset)
                len = qsp->qotd_len - uiop->uio_offset;

        if (len == 0) {
                mutex_exit(&qsp->lock);
                return (rw == UIO_WRITE ? ENOSPC : 0);
        }

        qsp->flags |= QOTD_BUSY;
        mutex_exit(&qsp->lock);

        retval = uiomove((void *)(qsp->qotd + uiop->uio_offset), len, rw, uiop);

        mutex_enter(&qsp->lock);
        if (rw == UIO_WRITE)
                qsp->flags |= QOTD_CHANGED;
        qsp->flags &= ~QOTD_BUSY;
        cv_broadcast(&qsp->cv);
        mutex_exit(&qsp->lock);

        return (retval);
}

/*ARGSUSED*/
static int
qotd_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp,
    int *rvalp)
{
        struct qotd_state *qsp;
        int instance = getminor(dev);

        if ((qsp = ddi_get_soft_state(qotd_state_head, instance)) == NULL)
                return (ENXIO);

        ASSERT(qsp->instance == instance);

        switch (cmd) {
        case QOTDIOCGSZ: {
                /* We are not guaranteed that ddi_copyout(9F) will read
                 * automatically anything larger than a byte.  Therefore we
                 * must duplicate the size before copying it out to the user.
                 */
                size_t sz = qsp->qotd_len;

                if (!(mode & FREAD))
                        return (EACCES);

#ifdef _MULTI_DATAMODEL
                switch (ddi_model_convert_from(mode & FMODELS)) {
                case DDI_MODEL_ILP32: {
                        size32_t sz32 = (size32_t)sz;
                        if (ddi_copyout(&sz32, (void *)arg, sizeof (size32_t),
                            mode) != 0)
                                return (EFAULT);
                        return (0);
                }
                case DDI_MODEL_NONE:
                        if (ddi_copyout(&sz, (void *)arg, sizeof (size_t),
                            mode) != 0)
                                return (EFAULT);
                        return (0);
                default:
                        cmn_err(CE_WARN, "Invalid data model %d in ioctl\n",
                            ddi_model_convert_from(mode & FMODELS));
                        return (ENOTSUP);
                }
#else /* ! _MULTI_DATAMODEL */
                if (ddi_copyout(&sz, (void *)arg, sizeof (size_t), mode) != 0)
                        return (EFAULT);
                return (0);
#endif /* _MULTI_DATAMODEL */
        }
        case QOTDIOCSSZ: {
                size_t new_len;
                char *new_qotd;
                ddi_umem_cookie_t new_cookie;
                uint_t model;

                if (!(mode & FWRITE))
                        return (EACCES);

#ifdef _MULTI_DATAMODEL
                model = ddi_model_convert_from(mode & FMODELS);

                switch (model) {
                case DDI_MODEL_ILP32: {
                        size32_t sz32;
                        if (ddi_copyin((void *)arg, &sz32, sizeof (size32_t),
                            mode) != 0)
                                return (EFAULT);
                        new_len = (size_t)sz32;
                        break;
                }
                case DDI_MODEL_NONE:
                        if (ddi_copyin((void *)arg, &new_len, sizeof (size_t),
                            mode) != 0)
                                return (EFAULT);
                        break;
                default:
                        cmn_err(CE_WARN, "Invalid data model %d in ioctl\n",
                            model);
                        return (ENOTSUP);
                }
#else /* ! _MULTI_DATAMODEL */
                if (ddi_copyin((void *)arg, &new_len, sizeof (size_t),
                    mode) != 0)
                        return (EFAULT);
#endif /* _MULTI_DATAMODEL */

                if (new_len == 0 || new_len > QOTD_MAX_LEN)
                        return (EINVAL);

                new_qotd = ddi_umem_alloc(new_len, DDI_UMEM_SLEEP, &new_cookie);

                mutex_enter(&qsp->lock);
                while (qsp->flags & QOTD_BUSY) {
                        if (cv_wait_sig(&qsp->cv, &qsp->lock) == 0) {
                                mutex_exit(&qsp->lock);
                                ddi_umem_free(new_cookie);
                                return (EINTR);
                        }
                }
                memcpy(new_qotd, qsp->qotd, min(qsp->qotd_len, new_len));
                ddi_umem_free(qsp->qotd_cookie);
                qsp->qotd = new_qotd;
                qsp->qotd_cookie = new_cookie;
                qsp->qotd_len = new_len;
                qsp->flags |= QOTD_CHANGED;
                mutex_exit(&qsp->lock);

                return (0);
        }
        case QOTDIOCDISCARD: {
                char *new_qotd = NULL;
                ddi_umem_cookie_t new_cookie;

                if (!(mode & FWRITE))
                        return (EACCES);

                if (qsp->qotd_len != init_qotd_len) {
                        new_qotd = ddi_umem_alloc(init_qotd_len,
                            DDI_UMEM_SLEEP, &new_cookie);
                }

                mutex_enter(&qsp->lock);
                while (qsp->flags & QOTD_BUSY) {
                        if (cv_wait_sig(&qsp->cv, &qsp->lock) == 0) {
                                mutex_exit(&qsp->lock);
                                if (new_qotd != NULL)
                                        ddi_umem_free(new_cookie);
                                return (EINTR);
                        }
                }
                if (new_qotd != NULL) {
                        ddi_umem_free(qsp->qotd_cookie);
                        qsp->qotd = new_qotd;
                        qsp->qotd_cookie = new_cookie;
                        qsp->qotd_len = init_qotd_len;
                } else {
                        bzero(qsp->qotd, qsp->qotd_len);
                }
                (void)strlcpy(qsp->qotd, init_qotd, init_qotd_len);
                qsp->flags &= ~QOTD_CHANGED;
                mutex_exit(&qsp->lock);

                return (0);
        }
        default:
                return (ENOTTY);
        }
}

Enter the definitions shown in the following example into a text file named qotd.h.


Example 3–6 Quote Of The Day Version 3 Header File

#ifndef _SYS_QOTD_H
#define _SYS_QOTD_H

#define QOTDIOC         ('q' << 24 | 't' << 16 | 'd' << 8)

#define QOTDIOCGSZ      (QOTDIOC | 1)   /* Get quote buffer size */
#define QOTDIOCSSZ      (QOTDIOC | 2)   /* Set new quote buffer size */
#define QOTDIOCDISCARD  (QOTDIOC | 3)   /* Discard quotes and reset */

#endif /* _SYS_QOTD_H */

Enter the configuration information shown in the following example into a text file named qotd_3.conf.


Example 3–7 Quote Of The Day Version 3 Configuration File

name="qotd_3" parent="pseudo" instance=0;

Building and Installing Quote Of The Day Version 3

Compile and link the driver. The following example shows compiling and linking for a 32-bit architecture:


% cc -D_KERNEL -c qotd_3.c
% ld -r -o qotd_3 qotd_3.o

Make sure you are user root when you install the driver.

Copy the driver binary to the /tmp directory as discussed in Building and Installing the Template Driver.


# cp qotd_3 /tmp
# ln -s /tmp/qotd_3 /usr/kernel/drv/qotd_3

Copy the configuration file to the kernel driver area of the system.


# cp qotd_3.conf /usr/kernel/drv

In a separate window, enter the following command:


% tail -f /var/adm/messages

Make sure you are user root when you load the driver. Use the add_drv(1M) command to load the driver:


# add_drv qotd_3

You should see the following messages in the window where you are viewing /var/adm/messages:


date time machine pseudo: [ID 129642 kern.info] pseudo-device: qotd_30
date time machine genunix: [ID 936769 kern.info] qotd_30 is /pseudo/qotd_3@0

Using Quote Of The Day Version 3

This section describes how to read and write the qotd_3 device and how to test the driver's I/O controls. The I/O controls include retrieving the size of the storage buffer, setting a new size for the storage buffer, and reinitializing the storage buffer size and contents.

Reading the Device

When you access this qotd_3 device for reading, the command you use to access the device retrieves the data from the device node. The command then displays the data in the same way that the command displays any other input. To get the name of the device special file, look in the /devices directory:


% ls -l /devices/pseudo/qotd*
crw-------   1 root   sys   122,  0 date time /devices/pseudo/qotd_3@0:qotd_3

To read the qotd_3 device, you can use the cat(1) command:


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

Writing the Device

To write to the qotd_3 device, you can redirect command-line input:


# echo "A life is not important except in the impact it has on others. 
- Jackie Robinson" >> /devices/pseudo/qotd_3@0:qotd_3
# cat /devices/pseudo/qotd_3@0:qotd_3
A life is not important except in the impact it has on others. - Jackie
Robinson

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