Device Driver Tutorial

Writing the Autoconfiguration Entry Points

Every character driver must define at least the following autoconfiguration entry points. The kernel calls these routines when the device driver is loaded.

In this section, the following code is added:

/* Device autoconfiguration entry points */
static int
dummy_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
    cmn_err(CE_NOTE, "Inside dummy_attach");
    switch(cmd) {
    case DDI_ATTACH:
        dummy_dip = dip;
        if (ddi_create_minor_node(dip, "0", S_IFCHR,
            ddi_get_instance(dip), DDI_PSEUDO,0)
            != DDI_SUCCESS) {
            cmn_err(CE_NOTE,
                "%s%d: attach: could not add character node.",
                "dummy", 0);
            return(DDI_FAILURE);
        } else
            return DDI_SUCCESS;
    default:
        return DDI_FAILURE;
    }
}

static int
dummy_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
    cmn_err(CE_NOTE, "Inside dummy_detach");
    switch(cmd) {
    case DDI_DETACH:
        dummy_dip = 0;
        ddi_remove_minor_node(dip, NULL);
        return DDI_SUCCESS;
    default:
        return DDI_FAILURE;
    }
}

static int
dummy_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, 
    void **resultp)
{
    cmn_err(CE_NOTE, "Inside dummy_getinfo");
    switch(cmd) {
    case DDI_INFO_DEVT2DEVINFO:
        *resultp = dummy_dip;
        return DDI_SUCCESS;
    case DDI_INFO_DEVT2INSTANCE:
        *resultp = 0;
        return DDI_SUCCESS;
    default:
        return DDI_FAILURE;
    }
}

static int
dummy_prop_op(dev_t dev, dev_info_t *dip, ddi_prop_op_t prop_op,
    int flags, char *name, caddr_t valuep, int *lengthp)
{
    cmn_err(CE_NOTE, "Inside dummy_prop_op");
    return(ddi_prop_op(dev,dip,prop_op,flags,name,valuep,lengthp));
}

Declaring the Autoconfiguration Entry Points

The attach(9E), detach(9E), getinfo(9E), and prop_op(9E) entry point routines need to be uniquely named for this driver. Choose a prefix to use with each entry point routine.


Note –

By convention, the prefix used for function and data names that are unique to this driver is either the name of this driver or an abbreviation of the name of this driver. Use the same prefix throughout the driver. This practice makes debugging much easier.


In the example shown in this chapter, dummy_ is used for the prefix to each function and data name that is unique to this example.

The following declarations are the autoconfiguration entry point declarations you should have in your dummy.c file. Note that each of these functions is declared static.

static int dummy_attach(dev_info_t *dip, ddi_attach_cmd_t cmd);
static int dummy_detach(dev_info_t *dip, ddi_detach_cmd_t cmd);
static int dummy_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg,
    void **resultp);
static int dummy_prop_op(dev_t dev, dev_info_t *dip, ddi_prop_op_t prop_op,
    int flags, char *name, caddr_t valuep, int *lengthp);

Defining the Device Attach Entry Point

The attach(9E) routine returns type int. The attach(9E) routine must return either DDI_SUCCESS or DDI_FAILURE. These two constants are defined in sunddi.h. All of the autoconfiguration entry point routines except for prop_op(9E) return either DDI_SUCCESS or DDI_FAILURE.

The attach(9E) routine takes two arguments. The first argument is a pointer to the dev_info structure for this driver. All of the autoconfiguration entry point routines take a dev_info argument. The second argument is a constant that specifies the attach type. The value that is passed through this second argument is either DDI_ATTACH or DDI_RESUME. Every attach(9E) routine must define behavior for at least DDI_ATTACH.

The DDI_ATTACH code must initialize a device instance. In a realistic driver, you define and manage multiple instances of the driver by using a state structure and the ddi_soft_state(9F) functions. Each instance of the driver has its own copy of the state structure that holds data specific to that instance. One of the pieces of data that is specific to each instance is the device instance pointer. Each instance of the device driver is represented by a separate device file in /devices. Each device instance file is pointed to by a separate device instance pointer. See Managing Device State for information about state structures and ddi_soft_state(9F) functions. See Devices as Files for information about device files and instances.

This dummy driver allows only one instance. Because this driver allows only one instance, this driver does not use a state structure. This driver still must declare a device instance pointer and initialize the pointer value in the attach(9E) routine. Enter the following code near the beginning of dummy.c to declare a device instance pointer for this driver:

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

The following code is the dummy_attach() routine that you should enter into your dummy.c file. You can copy the name portion of this function definition directly from the declaration you entered in Declaring the Autoconfiguration Entry Points.

static int
dummy_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
    cmn_err(CE_NOTE, "Inside dummy_attach");
    switch(cmd) {
    case DDI_ATTACH:
        dummy_dip = dip;
        if (ddi_create_minor_node(dip, "0", S_IFCHR,
            ddi_get_instance(dip), DDI_PSEUDO,0)
            != DDI_SUCCESS) {
            cmn_err(CE_NOTE,
                "%s%d: attach: could not add character node.",
                "dummy", 0);
            return(DDI_FAILURE);
        } else
            return DDI_SUCCESS;
    default:
        return DDI_FAILURE;
    }
}

First, use cmn_err(9F) to write a message to the system log, as you did in your _init(9E) entry point. Then provide DDI_ATTACH behavior. Within the DDI_ATTACH code, first assign the device instance pointer from the dummy_attach() argument to the dummy_dip variable that you declared above. You need to save this pointer value in the global variable so that you can use this pointer to get information about this instance from dummy_getinfo() and detach this instance in dummy_detach(). In this dummy_attach() routine, the device instance pointer is used by the ddi_get_instance(9F) function to return the instance number. The device instance pointer and the instance number both are used by ddi_create_minor_node(9F) to create a new device node.

A realistic driver probably would use the ddi_soft_state(9F) functions to create and manage a device node. This dummy driver uses the ddi_create_minor_node(9F) function to create a device node. The ddi_create_minor_node(9F) function takes six arguments. The first argument to the ddi_create_minor_node(9F) function is the device instance pointer that points to the dev_info structure of this device. The second argument is the name of this minor node. The third argument is S_IFCHR if this device is a character minor device or is S_IFBLK if this device is a block minor device. This dummy driver is a character driver.

The fourth argument to the ddi_create_minor_node(9F) function is the minor number of this minor device. This number is also called the instance number. The ddi_get_instance(9F) function returns this instance number. The fifth argument to the ddi_create_minor_node(9F) function is the node type. The ddi_create_minor_node(9F) man page lists the possible node types. The DDI_PSEUDO node type is for pseudo devices. The sixth argument to the ddi_create_minor_node(9F) function specifies whether this is a clone device. This is not a clone device, so set this argument value to 0.

If the ddi_create_minor_node(9F) call is not successful, write a message to the system log and return DDI_FAILURE. If the ddi_create_minor_node(9F) call is successful, return DDI_SUCCESS. If this dummy_attach() routine receives any cmd other than DDI_ATTACH, return DDI_FAILURE.

Defining the Device Detach Entry Point

The detach(9E) routine takes two arguments. The first argument is a pointer to the dev_info structure for this driver. The second argument is a constant that specifies the detach type. The value that is passed through this second argument is either DDI_DETACH or DDI_SUSPEND. Every detach(9E) routine must define behavior for at least DDI_DETACH.

The DDI_DETACH code must undo everything that the DDI_ATTACH code did. In the DDI_ATTACH code in your attach(9E) routine, you saved the address of a new dev_info structure and you called the ddi_create_minor_node(9F) function to create a new node. In the DDI_DETACH code in this detach(9E) routine, you need to reset the variable that pointed to the dev_info structure for this node. You also need to call the ddi_remove_minor_node(9F) function to remove this node. The detach(9E) routine must deallocate anything that was allocated, close anything that was opened, and destroy anything that was created in the attach(9E) routine.

The following code is the dummy_detach() routine that you should enter into your dummy.c file. You can copy the name portion of this function definition directly from the declaration you entered in Declaring the Autoconfiguration Entry Points.

static int
dummy_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
    cmn_err(CE_NOTE, "Inside dummy_detach");
    switch(cmd) {
    case DDI_DETACH:
        dummy_dip = 0;
        ddi_remove_minor_node(dip, NULL);
        return DDI_SUCCESS;
    default:
        return DDI_FAILURE;
    }
}

First, use cmn_err(9F) to write a message to the system log, as you did in your _init(9E) entry point. Then provide DDI_DETACH behavior. Within the DDI_DETACH code, first reset the dummy_dip variable that you set in dummy_attach() above. You cannot reset this device instance pointer unless you remove all instances of the device. This dummy driver supports only one instance.

Next, call the ddi_remove_minor_node(9F) function to remove this device node. The ddi_remove_minor_node(9F) function takes two arguments. The first argument is the device instance pointer that points to the dev_info structure of this device. The second argument is the name of the minor node you want to remove. If the value of the minor node argument is NULL, then ddi_remove_minor_node(9F) removes all instances of this device. Because the DDI_DETACH code of this driver always removes all instances, this dummy driver supports only one instance.

If the value of the cmd argument to this dummy_detach() routine is DDI_DETACH, remove all instances of this device and return DDI_SUCCESS. If this dummy_detach() routine receives any cmd other than DDI_DETACH, return DDI_FAILURE.

Defining the Get Driver Information Entry Point

The getinfo(9E) routine takes a pointer to a device number and returns a pointer to a device information structure or returns a device instance number. The return value of the getinfo(9E) routine is DDI_SUCCESS or DDI_FAILURE. The pointer or instance number requested from the getinfo(9E) routine is returned through a pointer argument.

The getinfo(9E) routine takes four arguments. The first argument is a pointer to the dev_info structure for this driver. This dev_info structure argument is obsolete and is no longer used by the getinfo(9E) routine.

The second argument to the getinfo(9E) routine is a constant that specifies what information the getinfo(9E) routine must return. The value of this second argument is either DDI_INFO_DEVT2DEVINFO or DDI_INFO_DEVT2INSTANCE. The third argument to the getinfo(9E) routine is a pointer to a device number. The fourth argument is a pointer to the place where the getinfo(9E) routine must store the requested information. The information stored at this location depends on the value you passed in the second argument to the getinfo(9E) routine.

The following table describes the relationship between the second and fourth arguments to the getinfo(9E) routine.

Table 2–1 Get Driver Information Entry Point Arguments

cmd

arg

resultp

DDI_INFO_DEVT2DEVINFO

Device number 

Device information structure pointer 

DDI_INFO_DEVT2INSTANCE

Device number 

Device instance number 

The following code is the dummy_getinfo() routine that you should enter into your dummy.c file. You can copy the name portion of this function definition directly from the declaration you entered in Declaring the Autoconfiguration Entry Points.

static int
dummy_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, 
    void **resultp)
{
    cmn_err(CE_NOTE, "Inside dummy_getinfo");
    switch(cmd) {
    case DDI_INFO_DEVT2DEVINFO:
        *resultp = dummy_dip;
        return DDI_SUCCESS;
    case DDI_INFO_DEVT2INSTANCE:
        *resultp = 0;
        return DDI_SUCCESS;
    default:
        return DDI_FAILURE;
    }
}

First, use cmn_err(9F) to write a message to the system log, as you did in your _init(9E) entry point. Then provide DDI_INFO_DEVT2DEVINFO behavior. A realistic driver would use arg to get the instance number of this device node. A realistic driver would then call the ddi_get_soft_state(9F) function and return the device information structure pointer from that state structure. This dummy driver supports only one instance and does not use a state structure. In the DDI_INFO_DEVT2DEVINFO code of this dummy_getinfo() routine, simply return the one device information structure pointer that the dummy_attach() routine saved.

Next, provide DDI_INFO_DEVT2INSTANCE behavior. Within the DDI_INFO_DEVT2INSTANCE code, simply return 0. This dummy driver supports only one instance. The instance number of that one instance is 0.

Defining the Report Driver Property Information Entry Point

The prop_op(9E) entry point is required for every driver. If your driver does not need to customize the behavior of the prop_op(9E) entry point, then your driver can use the ddi_prop_op(9F) function for the prop_op(9E) entry point. Drivers that create and manage their own properties need a custom prop_op(9E) routine. This dummy driver uses a prop_op(9E) routine to call cmn_err(9F) before calling the ddi_prop_op(9F) function.

The prop_op(9E) entry point and the ddi_prop_op(9F) function both require that you include the types.h header file. The prop_op(9E) entry point and the ddi_prop_op(9F) function both take the same seven arguments. These arguments are not discussed here because this dummy driver does not create and manage its own properties. See the prop_op(9E) man page to learn about the prop_op(9E) arguments.

The following code is the dummy_prop_op() routine that you should enter into your dummy.c file. You can copy the name portion of this function definition directly from the declaration you entered in Declaring the Autoconfiguration Entry Points.

static int
dummy_prop_op(dev_t dev, dev_info_t *dip, ddi_prop_op_t prop_op,
    int flags, char *name, caddr_t valuep, int *lengthp)
{
    cmn_err(CE_NOTE, "Inside dummy_prop_op");
    return(ddi_prop_op(dev,dip,prop_op,flags,name,valuep,lengthp));
}

First, use cmn_err(9F) to write a message to the system log, as you did in your _init(9E) entry point. Then call the ddi_prop_op(9F) function with exactly the same arguments as the dummy_prop_op() function.

Including Autoconfiguration Header Files

All of the autoconfiguration entry point routines and all of the user context entry point routines require that you include the ddi.h and sunddi.h header files. You already included these two header files for the cmn_err(9F) function.

The ddi_create_minor_node(9F) function requires the stat.h header file. The dummy_attach() routine calls the ddi_create_minor_node(9F) function. The prop_op(9E) and the ddi_prop_op(9F) functions require the types.h header file.

The following code is the list of header files that you now should have included in your dummy.c file for the four autoconfiguration routines you have written in this section and the three loadable module configuration routines you wrote in the previous section.

#include <sys/modctl.h>  /* used by _init, _info, _fini */
#include <sys/types.h>   /* used by prop_op, ddi_prop_op */
#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 ddi_get_instance, ddi_prop_op */
#include <sys/sunddi.h>  /* used by all entry points for this driver */
                         /* also used by ddi_create_minor_node, */
                         /* ddi_get_instance, and ddi_prop_op */