Enter the source code shown in the following example into a text file named qotd_3.c.
#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.
#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.
name="qotd_3" parent="pseudo" instance=0;