Device Driver Tutorial

Writing the Driver Data Structures

All of the data structures described in this section are required for every device driver. All drivers must define a dev_ops(9S) device operations structure. Because the dev_ops(9S) structure includes a pointer to the cb_ops(9S) character and block operations structure, you must define the cb_ops(9S) structure first. The modldrv(9S) linkage structure for loadable drivers includes a pointer to the dev_ops(9S) structure. The modlinkage(9S) module linkage structure includes a pointer to the modldrv(9S) structure.

Except for the loadable module configuration entry points, all of the required entry points for a driver are initialized in the character and block operations structure or in the device operations structure. Some optional entry points and other related data also are initialized in these data structures. Initializing the entry points in these data structures enables the driver to be dynamically loaded.

The loadable module configuration entry points are not initialized in driver data structures. The _init(9E), _info(9E), and _fini(9E) entry points are required for all kernel modules and are not specific to device driver modules.

In this section, the following code is added:

/* cb_ops structure */
static struct cb_ops dummy_cb_ops = {
    dummy_open,
    dummy_close,
    nodev,              /* no strategy - nodev returns ENXIO */
    nodev,              /* no print */
    nodev,              /* no dump */
    dummy_read,
    dummy_write,
    nodev,              /* no ioctl */
    nodev,              /* no devmap */
    nodev,              /* no mmap */
    nodev,              /* no segmap */
    nochpoll,           /* returns ENXIO for non-pollable devices */
    dummy_prop_op,
    NULL,               /* streamtab struct; if not NULL, all above */
                        /* fields are ignored */
    D_NEW | D_MP,       /* compatibility flags: see conf.h */
    CB_REV,             /* cb_ops revision number */
    nodev,              /* no aread */
    nodev               /* no awrite */
};

/* dev_ops structure */
static struct dev_ops dummy_dev_ops = {
    DEVO_REV,
    0,                         /* reference count */
    dummy_getinfo,             /* no getinfo(9E) */
    nulldev,                   /* no identify(9E) - nulldev returns 0 */
    nulldev,                   /* no probe(9E) */
    dummy_attach,
    dummy_detach,
    nodev,                     /* no reset - nodev returns ENXIO */
    &dummy_cb_ops,
    (struct bus_ops *)NULL,
    nodev,                     /* no power(9E) */
    ddi_quiesce_not_needed,    /* no quiesce(9E) */
};

/* modldrv structure */
static struct modldrv md = {
    &mod_driverops,     /* Type of module. This is a driver. */
    "dummy driver",     /* Name of the module. */
    &dummy_dev_ops
};

/* modlinkage structure */
static struct modlinkage ml = {
    MODREV_1,
    &md,
    NULL
};

/* dev_info structure */
dev_info_t *dummy_dip;  /* keep track of one instance */

Defining the Character and Block Operations Structure

The cb_ops(9S) structure initializes standard character and block interfaces. See the cb_ops(9S) man page to learn what each element is and what the value of each element should be. This dummy driver does not use all of the elements in the cb_ops(9S) structure. See the description that follows the code sample.

When you name this structure, use the same dummy_ prefix that you used for the names of the autoconfiguration routines and the names of the user context routines. Prepend the static type modifier to the declaration.

The following code is the cb_ops(9S) structure that you should enter into your dummy.c file:

static struct cb_ops dummy_cb_ops = {
    dummy_open,
    dummy_close,
    nodev,              /* no strategy - nodev returns ENXIO */
    nodev,              /* no print */
    nodev,              /* no dump */
    dummy_read,
    dummy_write,
    nodev,              /* no ioctl */
    nodev,              /* no devmap */
    nodev,              /* no mmap */
    nodev,              /* no segmap */
    nochpoll,           /* returns ENXIO for non-pollable devices */
    dummy_prop_op,
    NULL,               /* streamtab struct; if not NULL, all above */
                        /* fields are ignored */
    D_NEW | D_MP,       /* compatibility flags: see conf.h */
    CB_REV,             /* cb_ops revision number */
    nodev,              /* no aread */
    nodev               /* no awrite */
};

Enter the names of the open(9E) and close(9E) entry points for this driver as the values of the first two elements of this structure. Enter the names of the read(9E) and write(9E) entry points for this driver as the values of the sixth and seventh elements of this structure. Enter the name of the prop_op(9E) entry point for this driver as the value of the thirteenth element in this structure.

The strategy(9E), print(9E), and dump(9E) routines are for block drivers only. This dummy driver does not define these three routines because this driver is a character driver. This driver does not define an ioctl(9E) entry point because this driver does not use I/O control commands. This driver does not define devmap(9E), mmap(9E), or segmap(9E) entry points because this driver does not support memory mapping. This driver does not does not define aread(9E) or awrite(9E) entry points because this driver does not perform any asynchronous reads or writes. Initialize all of these unused function elements to nodev(9F). The nodev(9F) function returns the ENXIO error code.

Specify the nochpoll(9F) function for the chpoll(9E) element of the cb_ops(9S) structure because this driver is not for a pollable device. Specify NULL for the streamtab(9S) STREAMS entity declaration structure because this driver is not a STREAMS driver.

The compatibility flags are defined in the conf.h header file. The D_NEW flag means this driver is a new-style driver. The D_MP flag means this driver safely allows multiple threads of execution. All drivers must be multithreaded-safe, and must specify this D_MP flag. The D_64BIT flag means this driver supports 64-bit offsets and block numbers. See the conf.h header file for more compatibility flags.

The CB_REV element of the cb_ops(9S) structure is the cb_ops(9S) revision number. CB_REV is defined in the devops.h header file.

Defining the Device Operations Structure

The dev_ops(9S) structure initializes interfaces that are used for operations such as attaching and detaching the driver. See the dev_ops(9S) man page to learn what each element is and what the value of each element should be. This dummy driver does not use all of the elements in the dev_ops(9S) structure. See the description that follows the code sample.

When you name this structure, use the same dummy_ prefix that you used for the names of the autoconfiguration routines and the names of the user context routines. Prepend the static type modifier to the declaration.

The following code is the dev_ops(9S) structure that you should enter into your dummy.c file:

static struct dev_ops dummy_dev_ops = {
    DEVO_REV,
    0,                         /* reference count */
    dummy_getinfo,             /* no getinfo(9E) */
    nulldev,                   /* no identify(9E) - nulldev returns 0 */
    nulldev,                   /* no probe(9E) */
    dummy_attach,
    dummy_detach,
    nodev,                     /* no reset - nodev returns ENXIO */
    &dummy_cb_ops,
    (struct bus_ops *)NULL,
    nodev,                     /* no power(9E) */
    ddi_quiesce_not_needed,    /* no quiesce(9E) */
};

The DEVO_REV element of the dev_ops(9S) structure is the driver build version. DEVO_REV is defined in the devops.h header file. The second element in this structure is the driver reference count. Initialize this value to zero. The driver reference count is the number of instances of this driver that are currently open. The driver cannot be unloaded if any instances of the driver are still open.

The next six elements of the dev_ops(9S) structure are the names of the getinfo(9E), identify(9E), probe(9E), attach(9E), detach(9E), and reset() functions for this particular driver. The identify(9E) function is obsolete. Initialize this structure element to nulldev(9F). The probe(9E) function determines whether the corresponding device exists and is valid. This dummy driver does not define a probe(9E) function. Initialize this structure element to nulldev. The nulldev(9F) function returns success. The reset() function is obsolete. Initialize the reset() function to nodev(9F).

The next element of the dev_ops(9S) structure is a pointer to the cb_ops(9S) structure for this driver. You initialized the cb_ops(9S) structure for this driver in Defining the Character and Block Operations Structure. Enter &dummy_cb_ops for the value of the pointer to the cb_ops(9S) structure.

The next element of the dev_ops(9S) structure is a pointer to the bus operations structure. Only nexus drivers have bus operations structures. This dummy driver is not a nexus driver. Set this value to NULL because this driver is a leaf driver.

The next element of the dev_ops(9S) structure is the name of the power(9E) routine for this driver. The power(9E) routine operates on a hardware device. This driver does not drive a hardware device. Set the value of this structure element to nodev.

The last element of the dev_ops(9S) structure is the name of the quiesce(9E) routine for this driver. The quiesce(9E) routine operates on a hardware device. This driver does not drive a hardware device. Set the value of this structure element to ddi_quiesce_not_needed()(9F).

Defining the Module Linkage Structures

Two other module loading structures are required for every driver. The modlinkage(9S) module linkage structure is used by the _init(9E), _info(9E), and _fini(9E) routines to install, remove, and retrieve information from a module. The modldrv(9S) linkage structure for loadable drivers exports driver-specific information to the kernel. See the man pages for each structure to learn what each element is and what the value of each element should be.

The following code defines the modldrv(9S) and modlinkage(9S) structures for the driver shown in this chapter:

static struct modldrv md = {
    &mod_driverops,     /* Type of module. This is a driver. */
    "dummy driver",     /* Name of the module. */
    &dummy_dev_ops
};

static struct modlinkage ml = {
    MODREV_1,
    &md,
    NULL
};

The first element in the modldrv(9S) structure is a pointer to a structure that tells the kernel what kind of module this is. Set this value to the address of the mod_driverops structure. The mod_driverops structure tells the kernel that the dummy.c module is a loadable driver module. The mod_driverops structure is declared in the modctl.h header file. You already included the modctl.h header file in your dummy.c file, so do not declare the mod_driverops structure in dummy.c. The mod_driverops structure is defined in the modctl.c source file.

The second element in the modldrv(9S) structure is a string that describes this module. Usually this string contains the name of this module and the version number of this module. The last element of the modldrv(9S) structure is a pointer to the dev_ops(9S) structure for this driver. You initialized the dev_ops(9S) structure for this driver in Defining the Device Operations Structure.

The first element in the modlinkage(9S) structure is the revision number of the loadable modules system. Set this value to MODREV_1. The next element of the modlinkage(9S) structure is the address of a null-terminated array of pointers to linkage structures. Driver modules have only one linkage structure. Enter the address of the md structure for the value of this element of the modlinkage(9S) structure. Enter the value NULL to terminate this list of linkage structures.

Including Data Structures Header Files

The cb_ops(9S) and dev_ops(9S) structures require you to include the conf.h and devops.h header files. The modlinkage(9S) and modldrv(9S) structures require you to include the modctl.h header file. You already included the modctl.h header file for the loadable module configuration entry points.

The following code is the complete list of header files that you now should have included in your dummy.c file:

#include <sys/devops.h>  /* used by dev_ops */
#include <sys/conf.h>    /* used by dev_ops and cb_ops */
#include <sys/modctl.h>  /* used by modlinkage, modldrv, _init, _info, */
                         /* and _fini */
#include <sys/types.h>   /* used by open, close, read, write, prop_op, */
                         /* and ddi_prop_op */
#include <sys/file.h>    /* used by open, close */
#include <sys/errno.h>   /* used by open, close, read, write */
#include <sys/open.h>    /* used by open, close, read, write */
#include <sys/cred.h>    /* used by open, close, read */
#include <sys/uio.h>     /* used by read */
#include <sys/stat.h>    /* defines S_IFCHR used by ddi_create_minor_node */
#include <sys/cmn_err.h> /* used by all entry points for this driver */
#include <sys/ddi.h>     /* used by all entry points for this driver */
                         /* also used by cb_ops, ddi_get_instance, and */
                         /* ddi_prop_op */
#include <sys/sunddi.h>  /* used by all entry points for this driver */
                         /* also used by cb_ops, ddi_create_minor_node, */
                         /* ddi_get_instance, and ddi_prop_op */