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:
Version 2 of the driver defines a state structure that holds information about each instance of the device.
Version 2 defines a cb_ops(9S) structure and a more complete dev_ops(9S) structure.
Version 2 defines open(9E), close(9E), read(9E), getinfo(9E), attach(9E), and detach(9E) entry points.
Version 1 uses the cmn_err(9F) function to write a constant string to a system log in the _init(9E) entry point of the driver. The _init(9E) entry point is called when the driver is loaded. Version 2 uses the uiomove(9F) function to copy the quotation from kernel memory. The copied data is returned by the read(9E) entry point. The read(9E) entry point is called when the driver is accessed for reading.
Version 2 of the driver uses ASSERT(9F) statements to check the validity of data.
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:
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.
The ddi_soft_state_zalloc(9F) function uses the state pointer and the device instance to create the state structure for this instance.
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.
The ddi_soft_state_free(9F) function uses the state pointer and the device instance to free the state structure for this instance.
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