Device Driver Tutorial

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 */
};

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;