Writing Device Drivers

Chapter 5 Autoconfiguration

Autoconfiguration is the process of getting the driver's code and static data loaded into memory and registered with the system. Autoconfiguration also involves configuring (attaching) individual device instances that are controlled by the driver. "Loadable Driver Interfaces" and "Device Configuration Concepts" discuss these processes in more detail.

Driver Loading and Unloading

Figure 5-1 illustrates a structural overview of a device driver. The shaded area of this figure highlights the driver data structures required for driver loading and the interfaces exported by the driver, which is subdivided into two parts: driver loading (performed by the kernel) and driver configuration.

Figure 5-1 Module Loading and Autoconfiguration Entry Points

Graphic

Data Structures

Drivers are required to statically intitialize a number of data structures to support autoconfiguration. These strucutres include modlinkage(9S), modldrv(9S), dev_ops(9S), and cb_ops(9S) if the driver is not a SCSI HBA.

The data structures illustrated in Figure 5-1 must be provided and initialized correctly for the driver to load and for its routines to be called. If an operation is not supported by the driver, the address of the routine nodev(9F) can be used to fill it in. If the driver supports the entry point, but does not need to do anything except return success, the address of the routine nulldev(9F) can be used.


Note -

These structures should be initialized at compile-time. They should not be accessed or changed by the driver at any other time.


modlinkage Structure

static struct modlinkage xxmodlinkage = {
        MODREV_1,       /* ml_rev */
        &xxmodldrv,     /* ml_linkage[] */
        NULL            /* NULL termination */
};

The first field is the version number of the module loading subsystem and should be MODREV_1. The second field points to driver's modldrv structure defined next. The last element of the structure should always be NULL.

modldrv Structure

static struct modldrv xxmodldrv = {
        &mod_driverops,         /* drv_modops */
        "generic driver v1.1",  /* drv_linkinfo */
        &xx_dev_ops             /* drv_dev_ops */
};

This structure describes the module in more detail. The first field provide information on how to install and un-install the module. It should be set to &mod_driverops for driver modules. The second field is a string to be displayed by modinfo(1M). It should contain sufficient information for identifying the version of source code which generated the driver binary. The last field points to the driver's dev_ops structure defined next.

dev_ops Structure

static struct dev_ops xx_dev_ops = {
        DEVO_REV,       /* devo_rev, */
        0,              /* devo_refcnt  */
        xxgetinfo,      /* getinfo(9E) */
        nulldev,        /* identify(9E) */
        xxprobe,        /* probe(9E) */
        xxattach,       /* attach(9E) */
        xxdetach,       /* detach(9E) */
        nodev,          /* devo_reset */
        &xx_cb_ops,     /* devo_cb_ops */
        NULL,           /* devo_bus_ops */
        &xxpower        /* power(9E) */
};

The dev_ops(9S) structure allows the kernel to find the autoconfiguration entry points of the device driver. The devo_rev field identifies the revision number of the structure itself, and must be set to DEVO_REV. The devo_refcnt field must be initialized to zero. The function address fields should be filled in with the address of the appropriate driver entry point. Exceptions are:

The devo_cb_ops member should include the address of the cb_ops(9S) structure. The devo_bus_ops field must be set to NULL.

cb_ops Structure

static struct cb_ops xx_cb_ops = {
        xxopen,         /* open(9E) */
        xxclose,        /* close(9E) */
        xxstrategy,     /* strategy(9E) */
        xxprint,        /* print(9E) */
        xxdump,         /* dump(9E) */
        xxread,         /* read(9E) */
        xxwrite,        /* write(9E) */
        xxioctl,        /* ioctl(9E) */
        xxdevmap,       /* devmap(9E) */
        nodev,          /* mmap(9E) */
        xxsegmap,       /* segmap(9E) */
        xxchpoll,       /* chpoll(9E) */
        xxprop_op,      /* prop_op(9E) */
        NULL,           /* streamtab(9S) */
        D_MP | D_64BIT  /* cb_flag */
}; 

The cb_ops(9S) structure contains the entry points for the character and block operations of the device driver. Any entry points the driver does not support should be initialized to nodev(9F). For example, character device drivers should set all the block-only fields, such as cb_stategy, to nodev(9F). Note that the mmap(9E) entry point is maintained for compatibility with previous releases, and drivers should use the devmap(9E) entry point for device memory mapping. If devmap(9E) is supported, set mmap(9E) to nodev(9F).

The streamtab field is used to determine if this is a STREAMS-based driver. The device drivers discussed in this book are not STREAMS based. For a non-STREAMS-based driver, it must be set to NULL.

The cb_flag member contains the following flags:

cb_rev is the cb_ops(9S) structure revision number. This field must be set to CB_REV.

Loadable Driver Interfaces

Device drivers must be dynamically loadable and should be unloadable to help conserve memory resources. Drivers that can be unloaded are also easier to test and debug.

Each device driver has a section of code that defines a loadable interface. This code section defines a static pointer for the soft state routines, the structures described in "Data Structures", and the routines involved in loading the module.


Example 5-1 Loadable Interface Section

static void *statep;                /* for soft state routines */
static struct cb_ops xx_cb_ops;                    /* forward reference */
static struct dev_ops xx_ops = {
     DEVO_REV,
     0,
     xxgetinfo,
     nulldev,
     xxprobe,
     xxattach,
     xxdetach,
     xxreset,
     nodev,
     &xx_cb_ops,
     NULL,
        xxpower
};

static struct modldrv modldrv = {
     &mod_driverops,
     "xx driver v1.0",
     &xx_ops
};

static struct modlinkage modlinkage = {
       MODREV_1,
     &modldrv,
     NULL
};

int
_init(void)
{
     int error;
     ddi_soft_state_init(&statep, sizeof (struct xxstate),
            estimated number of instances);
    further per-module initialization if necessary
        error = mod_install(&modlinkage);
        if (error != 0) {
            undo any per-module initialization done earlier
            ddi_soft_state_fini(&statep);
     }
     return (error);
}

int
_fini(void)
{
     int error;
     error = mod_remove(&modlinkage);
     if (error == 0) {
            release per-module resources if any were allocated
            ddi_soft_state_fini(&statep);
     }
     return (error);
}

int
_info(struct modinfo *modinfop)
{
     return (mod_info(&modlinkage, modinfop));
}

_init(9E)

static void *xxstatep;
int
_init(void)
{
        int error;
        const int max_instance = 20;    /* max possible device instances */

        ddi_soft_state_init(&xxstatep, sizeof (struct xxstate), max_instance);
        error = mod_install(&xxmodlinkage);
        if (error != 0) {
                /*
                 * Cleanup after a failure
                 */
                ddi_soft_state_fini(&xxstatep);
        }
        return (error);
}

The driver should perform any one-time resource allocation or data initialization during driver loading in _init(9E). For example, it should initialize any mutexes global to the driver in this routine. The driver should not, however, use _init(9E) to allocate or initialize anything that has to do with a particular instance of the device. Per-instance initialization must be done in attach(9E). For example, if a driver for a printer can handle more than one printer at the same time, it should allocate resources specific to each printer instance in attach(9E).


Note -

Once _init(9E) has called mod_install(9F), the driver should not change any of the data structures hanging off the modlinkage(9S) structure, as the system may make copies of them or change them.


_fini(9E)

int
_fini(void)
{
        int error;
        error = mod_remove(&modlinkage);
        if (error != 0) {
                return (error);
        }
        /*
         * Cleanup resources allocated in _init()
         */
        ddi_soft_state_fini(&xxstatep);
        return (0);
}

Similarly, in _fini(9E), the driver should release any resources that were allocated in _init() and must remove itself from the system module list.

_info(9E)

int
_info(struct modinfo *modinfop)
{
        return (mod_info(&xxmodlinkage, modinfop));
}

The driver is called to return module information. The entry point should be implemented as shown above.

Device Configuration Concepts

Each driver must define the following entry points that are used by the kernel for device configuration:

Every device driver must have an attach(9E) and getinfo(9E) routine. probe(9E) is only required for non self-identifying devices. For self-identifying devices an explicit probe routine may be provided or nulldev(9F) may be specified in the dev_ops structure for the probe(9E) entry point.

Device Instances and Instance Numbers

The system assigns an instance number to each device. The driver may not reliably predict the value of the instance number assigned to a particular device. The driver should retrieve the particular instance number that has been assigned by calling ddi_get_instance(9F).

Instance numbers represent the system's notion of devices. Each dev_info (that is, each node in the device tree) for a particular driver is assigned an instance number by the kernel. Furthermore, instance numbers provide a convenient mechanism for indexing data specific to a particular physical device. The most common usage for this is ddi_get_soft_state(9F) which uses an instance number to retrieve soft state data for a particular physical device.

Minor Nodes and Minor Numbers

Drivers are allowed to manage their minor number name space. For example the sd driver needs to export 16 minor nodes (8 character, 8 block) to the file system for each disk. Each represents a different piece of the same disk, or a different interface to the same data (character/block). However, the driver still needs to be able to retrieve the instance number of the device in order to get soft state, and so forth.

probe(9E)

For non-self-identifying devices, this entry point should determine whether the hardware device is present on the system.

For probe to determine whether the instance of the device is present, probe(9E) may need to do many of the things also commonly done by attach(9E). In particular, it may need to map the device registers.

Probing the device registers is device specific. The driver probably has to perform a series of tests of the hardware to assure that the hardware is really there. The test criteria must be rigorous enough to avoid misidentifying devices. It may, for example, appear that the device is present when in fact it is not, because a different device appears to behave like the expected device.

DDI_PROBE_SUCCESS if the probe was successful

DDI_PROBE_FAILURE if the probe failed

DDI_PROBE_DONTCARE if the probe was unsuccessful, yet attach(9E) should still be called

DDI_PROBE_PARTIAL if the instance is not present now, but may be present in the future

For a given device instance, attach(9E) will not be called before probe(9E) has succeeded at least once on that device.

It is important that probe(9E) free all the resources it allocates, because it may be called multiple times; however, attach(9E) will not necessarily be called even if probe(9E) succeeds.

ddi_dev_is_sid(9F) may be used in a driver's probe(9E) routine to determine if the device is self-identifying. This is useful in drivers written for self-identifying and non-self-identifying versions of the same device.

Example 5-2 is a sample probe(9E) routine for devices on these buses.


Example 5-2 probe(9E) Routine

static int
xxprobe(dev_info_t *dip)
{

    ddi_acc_handle_t dev_hdl;
    ddi_device_acc_attr_t dev_attr;
    Pio_csr *csrp;
    uint8_t csrval;

    /*
     * if the device is self identifying, no need to probe
     */
    if (ddi_dev_is_sid(dip) == DDI_SUCCESS)
        return (DDI_PROBE_DONTCARE);

    /*
     * Initalize the device access attrributes and map in
     * the devices CSR register (register 0)
     */
    dev_attr.devacc_attr_version - DDI_DEVICE_ATTR_V0;
    dev_attr.devacc_attr_endian_flags = DDI_STRUCTURE_LE_ACC;
    dev_attr.devacc_attr_dataorder = DDI_STRICTORDER_ACC;

    if (ddi_regs_map_setup(dip, 0, (caddr_t *)&csrp, 0, sizeof (Pio_csr),
        &dev_attr, &dev_hdl) != DDI_SUCCESS)
        return (DDI_PROBE_FAILURE);

    /*
     * Reset the device
     * Once the reset completes the CSR should read back
     * (PIO_DEV_READY | PIO_IDLE_INTR)
     */
    ddi_put8(dev_hdl, csrp, PIO_RESET);
    csrval = ddi_get8(dev_hdl, csrp);


    /*
     * tear down the mappings and return probe success/failure
     */
    ddi_regs_map_free(&dev_hdl);
    if ((csrval & 0xff) == (PIO_DEV_READY | PIO_IDLE_INTR))
        return (DDI_PROBE_SUCCESS);
    else
        return (DDI_PROBE_FAILURE);
}

When the driver's probe(9E) routine is called, it does not know whether the device being probed exists on the bus. Therefore, it is possible that the driver may attempt to access device registers for a nonexistent device. A bus fault may be generated on some buses as a result.

Example 5-3 shows a probe(9E) routine that uses ddi_peek(9F) to check for the existence of the device.


Example 5-3 probe(9E) Routine Using ddi_poke8(9F)

static int
xxprobe(dev_info_t *dip)
{

    ddi_acc_handle_t dev_hdl;
    ddi_device_acc_attr_t dev_attr;
    Pio_csr *csrp;
    uint8_t csrval;

    /*
     * if the device is self identifying, no need to probe
     */
    if (ddi_dev_is_sid(dip) == DDI_SUCCESS)
        return (DDI_PROBE_DONTCARE);

    /*
     * Initalize the device access attrributes and map in
     * the devices CSR register (register 0)
     */
    dev_attr.devacc_attr_version - DDI_DEVICE_ATTR_V0;
    dev_attr.devacc_attr_endian_flags = DDI_STRUCTURE_LE_ACC;
    dev_attr.devacc_attr_dataorder = DDI_STRICTORDER_ACC;

    if (ddi_regs_map_setup(dip, 0, (caddr_t *)&csrp, 0, sizeof (Pio_csr),
        &dev_attr, &dev_hdl) != DDI_SUCCESS)
        return (DDI_PROBE_FAILURE);

    /*
     * The bus can generate a fault when probing for devices which
     * do not exist.  Use ddi_poke8(9f) to handle any faults which
     * may occur.
     *
     * Reset the device.  Once the reset completes the CSR should read
     * back (PIO_DEV_READY | PIO_IDLE_INTR)
     */
    if (ddi_poke8(dip, csrp, PIO_RESET) != DDI_SUCCESS) {
        ddi_regs_map_free(&dev_hdl);
        return (DDI_FAILURE);


    csrval = ddi_get8(dev_hdl, csrp);
    /*
     * tear down the mappings and return probe success/failure
     */
    ddi_regs_map_free(&dev_hdl);
    if ((csrval & 0xff) == (PIO_DEV_READY | PIO_IDLE_INTR))
        return (DDI_PROBE_SUCCESS);
    else
        return (DDI_PROBE_FAILURE);
}

In this example, ddi_regs_map_setup(9F) is used to map the device registers. ddi_peek8(9F) reads a single character from the location reg_addr.

attach(9E)

The kernel calls a drivers attach(9E) entry point to attach an instance of a device or to resume operation for an instance of a device which has been suspended or shut down by the power management framework. In this section we will only discuss the operation of attaching device instances, the power management discussion is left to Chapter 9, Power Management.

A drivers attach(9E) entry point is called to attach each instance of a device that is bound to the driver. The entry point is called with the instance of the device node to attach, with DDI_ATTACH specified as the cmd argument to attach(9E). The attach entry point will typically include the following types of processing:

Driver Soft State Management

To assist device driver writers in allocating state structures, the Solaris 8 DDI/DKI provides a set of memory management routines called the software state management routines (also known as the soft state routines). These routines dynamically allocate, retrieve, and destroy memory items of a specified size, and hide the details of list management. An instance number is used to identify the desired memory item; this number can be (and usually is) the instance number assigned by the system.

Drivers will typically allocate a soft state structure for each device instance which attaches to the driver by calling ddi_soft_state_zalloc(9F), passing the instance number of the device. Since it is considered an error for there to be two device nodes with the same instance number, ddi_soft_state_zalloc(9F) will fail if an allocation already exists for a given instance number.

A drivers character or block entry point (cb_ops(9S)) will reference a particular soft state structure by first decoding the devices instance number from the dev_t argument that is passed to the entry point function. The driver then calls ddi_get_soft_state(9F) passing the per-driver soft state list and the instance number that was derived. If ddi_get_soft_state(9F) returns a NULL value, the driver should treat this as the device does not exist and return the appropriate code.

See "Creating Minor Device Nodes" for additional information on how instance numbers and device numbers, or dev_t's, are related.

Lock and Conditional Variable Initialization

Drivers should initialize any per-instance locks and condition variables during attach. The initialization of any locks which are acquired by the drivers interrupt handler must be initialized prior to adding any interrupt handlers. See Chapter 3, Multithreading for a complete description of lock initialization and usage. See Chapter 7, Interrupt Handlers for a discussion of the issues surrounding interrupt handler and locks.

Creating Minor Device Nodes

An important part of the attach process is the creation of minor nodes for the device instance. A minor node contains the information exported by the device and the DDI framework which the system uses to create a special file for the minor node under /devices.

Minor nodes are created when the driver calls ddi_create_minor_node(9F). The driver supplies a minor number, a minor name, a minor node type, and weather the minor node represents a block or character device.

Drivers can choose to create as many or as few minor nodes for a device as it wants to. Solaris expects certain classes of devices to have minor nodes created in a particular format. For example, disk drivers are expected to create 16 minor nodes for each physical disk instance attached; 8 minor nodes are created, representing the a - h block device interfaces, with an additional 8 minor nodes for the a,raw - h,raw character device interfaces.

The minor number passed to ddi_create_minor_node(9F) is defined wholly by the driver itself; the minor number is usually an encoding of the devices instance number with a minor node identifier. Taking the above example, the driver creates minor numbers for each of the minor nodes by taking the devices instance number, shifting it left 3 bits, and OR'ing in the minor node index whose values range from 0 to 15.

The minor node type passed to ddi_create_minor_node(9F) classifies the type of device, such as disks, tapes, network interfaces, frame buffers, and so forth.

Table 5-1 Possible Node Types

Constant 

Description 

DDI_NT_SERIAL

Serial port 

DDI_NT_SERIAL_DO

Dialout ports 

DDI_NT_BLOCK

Hard disks 

DDI_NT_BLOCK_CHAN

Hard disks with channel or target numbers 

DDI_NT_CD

ROM drives (CD-ROM) 

DDI_NT_CD_CHAN

ROM drives with channel or target numbers 

DDI_NT_FD

Floppy disks 

DDI_NT_TAPE

Tape drives 

DDI_NT_NET

Network devices 

DDI_NT_DISPLAY

Display devices 

DDI_NT_MOUSE

Mouse 

DDI_NT_KEYBOARD

Keyboard 

DDI_NT_AUDIO

Audio Device 

DDI_PSEUDO

General pseudo devices 

The node types DDI_NT_BLOCK, DDI_NT_BLOCK_CHAN, DDI_NT_CD, and DDI_NT_CD_CHAN cause devfsadm(1M) to identify the device instance as a disk and to create a symbolic link in the /dev/dsk or /dev/rdsk directory pointing to the device node in the /devices directory tree.

The node type DDI_NT_TAPE causes devfsadm(1M) to identify the device instance as a tape and to create a symbolic link from the /dev/rmt directory to the device node in the /devices directory tree.

The node types DDI_NT_SERIAL and DDI_NT_SERIAL_DO causes ports(1M) to identify the device instance as a serial port and to create symbolic links from the /dev/term and /dev/cua directories to the device node in the /devices directory tree and to entries to the port monitor database/etc/inittab.

Vendor-supplied strings should include an identifying value to make them unique, such as their name or stock symbol (if appropriate). The string can be used in conjunction with devfsadm(1M) and devlink.tab(4) to create logical names in /dev.

Deferred Attach

open(9E) might be called on a minor device before attach(9E) has succeeded on the corresponding instance. open(9E) must then return ENXIO, which will cause the system to attempt to attach the device. If the attach succeeds, the open is retried automatically.


Example 5-4 Example attach(9E) Entry Point

/*
 * Attach an instance of the driver.  We take all the knowledge we
 * have about our board and check it against what has been filled in for
 * us from our FCode or from our driver.conf(4) file.
 */
static int
xxattach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
    int instance;
    Pio *pio_p;
    ddi_device_acc_attr_t   da_attr;
    static int pio_validate_device(dev_info_t *);

    switch (cmd) {
    case DDI_ATTACH:

        /*
         * first validate the device conforms to a configuration this driver
         * supports
         */
        if (pio_validate_device(dip) == 0)
            return (DDI_FAILURE);

        /*
         * Allocate a soft state structure for this device instance
         * Store a pointer to the device node in our soft state structure
         * and a reference to the soft state structure in the device
         * node.
         */
        instance = ddi_get_instance(dip);
        if (ddi_soft_state_zalloc(pio_softstate, instance) != 0)
            return (DDI_FAILURE);
        pio_p = ddi_get_soft_state(pio_softstate, instance);
        ddi_set_driver_private(dip, (caddr_t)pio_p);
        pio_p->dip = dip;

        /*
         * Before adding the interrupt, get the interrupt block
         * cookie associated with the interrupt specification to
         * initialize the mutex used by the interrupt handler.
         */
        if (ddi_get_iblock_cookie(dip, 0, &pio_p->iblock_cookie) !=
          DDI_SUCCESS) {
            ddi_soft_state_free(pio_softstate, instance);
            return (DDI_FAILURE);
        }

        mutex_init(&pio_p->mutex, NULL, MUTEX_DRIVER, pio_p->iblock_cookie);

        /*
         * Now that the mutex is initialized, add the interrupt itself.
         */
        if (ddi_add_intr(dip, 0, NULL, NULL, pio_intr, (caddr_t)instance) !=
          DDI_SUCCESS) {
            mutex_destroy(&pio_p>mutex);
            ddi_soft_state_free(pio_softstate, instance);
            return (DDI_FAILURE);
        }


        /*
         * Initialize the device access attributes for the register
         * mapping
         */
        dev_acc_attr.devacc_attr_version = DDI_DEVICE_ATTR_V0;
        dev_acc_attr.devacc_attr_endian_flags = DDI_STRUCTURE_LE_ACC;
        dev_acc_attr.devacc_attr_dataorder = DDI_STRICTORDER_ACC;

        /*
         * Map in the csr register (register 0)
         */
        if (ddi_regs_map_setup(dip, 0, (caddr_t *)&(pio_p->csr), 0,
            sizeof (Pio_csr), &dev_acc_attr, &pio_p->csr_handle) !=
            DDI_SUCCESS) {
            ddi_remove_intr(pio_p->dip, 0, pio_p->iblock_cookie);
            mutex_destroy(&pio_p->mutex);
            ddi_soft_state_free(pio_softstate, instance);
            return (DDI_FAILURE);
        }

        /*
         * Map in the data register (register 1)
         */
        if (ddi_regs_map_setup(dip, 1, (caddr_t *)&(pio_p->data), 0,
            sizeof (uchar_t), &dev_acc_attr, &pio_p->data_handle) !=
            DDI_SUCCESS) {
            ddi_remove_intr(pio_p->dip, 0, pio_p->iblock_cookie);
            ddi_regs_map_free(&pio_p->csr_handle);
            mutex_destroy(&pio_p->mutex);
            ddi_soft_state_free(pio_softstate, instance);
            return (DDI_FAILURE);
        }

        /*
         * Create an entry in /devices for user processes to open(2)
         * This driver will create a minor node entry in /devices
         * of the form:  /devices/..../pio@X,Y:pio
         */
        if (ddi_create_minor_node(dip, ddi_get_name(dip), S_IFCHR,
            instance, DDI_PSEUDO, 0) == DDI_FAILURE) {
            ddi_remove_intr(pio_p->dip, 0, pio_p->iblock_cookie);
            ddi_regs_map_free(&pio_p->csr_handle);
            ddi_regs_map_free(&pio_p->data_handle);
            mutex_destroy(&pio_p->mutex);
            ddi_soft_state_free(pio_softstate, instance);
            return (DDI_FAILURE);
        }

        /*
         * reset device (including disabling interrupts)
         */
        ddi_put8(pio_p->csr_handle, pio_p->csr, PIO_RESET);

        /*
         * report the name of the device instance which has attached
         */
        ddi_report_dev(dip);
        return (DDI_SUCCESS);

    case DDI_RESUME:
        return (DDI_SUCCESS);

    default:
        return (DDI_FAILURE);
    }

}

detach(9E)

The kernel calls a driver's detach(9E) entry point to detach an instance of a device or to suspend operation for an instance of a device by power management. In this section we discuss the operation of detaching device instances, refer to Chapter 9, Power Management for a discussion of power management issues.

A drivers detach(9E) entry point is called to detach an instance of a device that is bound to the driver. The entry point is called with the instance of the device node to detach and DDI_DETACH specified as the cmd argument to the entry point.

A driver is required to cancel or wait for any time-outs or callbacks to complete, then release any resources which are allocated to the device instance before returning. If for some reason a driver cannot cancel outstanding callbacks for free resources, the driver is required to return the device to its original state and return DDI_FAILURE from the entry point, leaving the device instance in the attached state.

There are two types of callback routines - those which can be canceled and those which cannot. timeout(9F) and bufcall(9F) callbacks can be atomically cancelled by the driver during detach(9E). Other types of callbacks such as scsi_init_pkt(9F) and ddi_dma_buf_bind_handle(9F) cannot be canceled, requiring the driver to either block in detach(9E) until the callback completes or to fail the request to detach.


Example 5-5 detach(9E) Routine

/*
 * detach(9e)
 * free the resources that were allocated in attach(9e)
 */
static int
xxdetach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
    Pio     *pio_p;
    int     instance;

    switch (cmd) {
    case DDI_DETACH:

        instance = ddi_get_instance(dip);
        pio_p = ddi_get_soft_state(pio_softstate, instance);

        /*
         * turn off the device
         * free any resources allocated in attach
         */
        ddi_put8(pio_p->csr_handle, pio_p->csr, PIO_RESET);

        ddi_remove_minor_node(dip, NULL);
        ddi_regs_map_free(&pio_p->csr_handle);
        ddi_regs_map_free(&pio_p->data_handle);
        ddi_remove_intr(pio_p->dip, 0, pio_p->iblock_cookie);
        mutex_destroy(&pio_p->mutex);
        ddi_soft_state_free(pio_softstate, instance);
        /* FALLTHRU */

    case DDI_SUSPEND:
    default:
        return (DDI_FAILURE);
    }
}

getinfo(9E)

The system calls getinfo(9E) to obtain configuration information that only the driver knows. The mapping of minor numbers to device instances is entirely under the control of the driver. The system sometimes needs to ask the driver which device a particular dev_t represents.

getinfo(9E) is called during module loading and at other times during the life of the driver. It can take one of two commands as its infocmd argument: DDI_INFO_DEVT2INSTANCE, which asks for a device's instance number, and DDI_INFO_DEVT2DEVINFO, which asks for pointer to the device's dev_info structure.

In the DDI_INFO_DEVT2INSTANCE case, arg is a dev_t, and getinfo(9E) must translate the minor number to an instance number. In the following example, the minor number is the instance number, so it simply passes back the minor number. In this case, the driver must not assume that a state structure is available, since getinfo(9E) may be called before attach(9E). The mapping the driver defines between minor device number and instance number does not necessarily follow the mapping shown in the example. In all cases, however, the mapping must be static.

In the DDI_INFO_DEVT2DEVINFO case, arg is again a dev_t, so getinfo(9E) first decodes the instance number for the device. It then passes back the dev_info pointer saved in the driver's soft state structure for the appropriate device. This is shown in Example 5-6.


Example 5-6 getinfo(9E) Routine

/*
 * getinfo(9e)
 * Return the instance number or device node given a dev_t
 */
static int
xxgetinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
{
    int error;
    Pio *pio_p;
    int instance = getminor((dev_t)arg);

    switch (infocmd) {

    /*
     * return the device node if the driver has attached the
     * device instace identified by the dev_t value which was passed
     */
    case DDI_INFO_DEVT2DEVINFO:
        pio_p = ddi_get_soft_state(pio_softstate, instance);
        if (pio_p == NULL) {
            *result = NULL;
            error = DDI_FAILURE;
        } else {
            mutex_enter(&pio_p->mutex);
            *result = pio_p->dip;
            mutex_exit(&pio_p->mutex);
            error = DDI_SUCCESS;
        }
        break;

    /*
     * the driver can always return the instance number given a dev_t
     * value, even if the instance is not attached.
     */
    case DDI_INFO_DEVT2INSTANCE:
        *result = (void *)instance;
        error = DDI_SUCCESS;
        break;
    default:
        *result = NULL;
        error = DDI_FAILURE;
    }

    return (error);
}

Device IDs

The Solaris DDI provides interfaces allows drivers to provide a persistent unique identifier for a device, a device ID, which can be used to identify or locate a device and which is independent of the devices name or number (dev_t). Applications can use the functions defined in libdevid(3LIB) to read and manipulate the device IDs registered by the drivers.

Before a driver can export a device ID, it needs to verify that the device is capable of either providing a unique ID, such as WWN, or is capable of storing a host-generated unique ID in an area not accessible through normal operations, such as device NVRAM, reserved sectors, etc.

Registering Device IDs

Drivers will typically initialize and register device IDs in the drivers attach(9E) handler. As mentioned above, the driver is responsible for registering a device ID which is persistent. As such, the driver may be required to handle both devices which can provide a unique ID directly (WWN), and devices where fabricated IDs are written to and read from stable storage.

Registering a Device-Supplied ID

If the device can supply the driver with an identifier that is unique, the driver can simply initialize the device ID with this identifier and register the ID with the Solaris DDI.

/*
 * The device provides a guaranteed unique identifier,
 * in this case a SCSI3-WWN.  The WWN for the device has been
 * stored in the devices soft state.
 */
if (ddi_devid_init(dip, DEVID_SCSI3_WWN, un->un_wwn_len, un->un_wwn,
    &un->un_devid) != DDI_SUCCESS)
        return (DDI_FAILURE);

(void) ddi_devid_register(dip, un->un_devid);

Registering a Fabricated ID

A driver may also register device IDs for devices which do not directly supply a unique ID. If the device is capable of storing and retrieving a small amount of data in a reserved area, the driver can create a fabricated device ID and write it to the reserved area.

/*
 * the device doesn't supply a unique ID, attempt to read
 * a fabricated ID from the devices reserved data.
 */

if (xxx_read_deviceid(un, &devid_buf) == XXX_OK) {
        if (ddi_devid_valid(devid_buf) == DDI_SUCCESS) {
                devid_sz = ddi_devi_sizeof(devid_buf);
                un->un_devid = kmem_alloc(devid_sz, KM_SLEEP);
                bcopy(devid_buf, un->un_devid, devid_sz);
                ddi_devid_register(dip, un->un_devid);
                return (XXX_OK);
        }
}

/*
 * we failed to read a valid device ID from the device
 * fabricate an ID, store it on the device, and register
 * it with the DDI
 */

if (ddi_devid_init(dip, DEVID_FAB, 0, NULL, &un->un_devid)
    == DDI_FAILURE) {
        return (XXX_FAILURE);
}

if (xxx_write_deviceid(un) != XXX_OK) {
        ddi_devid_free(un->un_devid);
        un->un_devid = NULL;
        return (XXX_FAILURE);
}

ddi_devid_register(dip, un->un_devid);
return (XXX_OK);

Unregistering Device IDs

Drivers will typically unregister and free any device IDs they allocated as part of the detach(9E) handling. The driver will first call ddi_devid_unregister(9F) to unregister the device ID for the device instance. The driver must then free the device ID handle itself by calling ddi_devid_free(9F), passing the handle which had been returned by ddi_devid_init(9F). The driver is responsible for managing any space allocated for WWN or Serial Number data.