Writing Device Drivers

Chapter 6 Driver Autoconfiguration

Autoconfiguration means the driver loads code and static data into memory. This information is then registered with the system. Autoconfiguration also involves attaching individual device instances that are controlled by the driver.

This chapter provides information on the following subjects:

Driver Loading and Unloading

The system loads driver binary modules from the drv subdirectory of the kernel module directory for autoconfiguration. See Copying the Driver to a Module Directory.

After a module is read into memory with all symbols resolved, the system calls the _init(9E) entry point for that module. The _init() function calls mod_install(9F), which actually loads the module.


Note –

During the call to mod_install(), other threads are able to call attach(9E) as soon as mod_install() is called. From a programming standpoint, all _init() initialization must occur before mod_install() is called. If mod_install() fails (that is a nonzero value is returned), then the initialization must be backed out.


Upon successful completion of _init(), the driver is properly registered with the system. At this point, the driver is not actively managing any device. Device management happens as part of device configuration.

The system unloads driver binary modules either to conserve system memory or at the explicit request of a user. Before deleting the driver code and data from memory, the _fini(9E) entry point of the driver is invoked. The driver is unloaded, if and only if _fini() returns success.

The following figure provides a structural overview of a device driver. The shaded area highlights the driver data structures and entry points. The upper half of the shaded area contains data structures and entry points that support driver loading and unloading. The lower half is concerned with driver configuration.

Figure 6–1 Module Loading and Autoconfiguration Entry Points

Diagram shows structures and entry points used in autoconfiguration
and module loading.

Data Structures Required for Drivers

To support autoconfiguration, drivers are required to statically initialize the following data structures:

The data structures in Figure 5-1 are relied on by the driver. These structures must be provided and be initialized correctly. Without these data structures, the driver might not load properly. As a result, the necessary routines might not be loaded. If an operation is not supported by the driver, the address of the nodev(9F) routine can be used as a placeholder. In some instances, the driver supports the entry point and only needs to return success or failure. In such cases, the address of the routine nulldev(9F) can be used.


Note –

These structures should be initialized at compile-time. The driver should not access or change the structures 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 that loads the subsystem. This field 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 provides information regarding installation of the module. This field should be set to &mod_driverops for driver modules. The second field is a string to be displayed by modinfo(1M). The second field should contain sufficient information for identifying the version of source code that generated the driver binary. The last field points to the driver's dev_ops structure defined in the following section.

dev_ops Structure

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

The dev_ops(9S) structure enables the kernel to find the autoconfiguration entry points of the device driver. The devo_rev field identifies the revision number of the structure. This field 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, except in the following cases:

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 */
    CB_REV,         /* cb_rev */
    xxaread,        /* aread(9E) */
    xxawrite        /* awrite(9E) */
};

The cb_ops(9S) structure contains the entry points for the character operations and block operations of the device driver. Any entry points that 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. 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 indicates whether the driver is STREAMS-based. Only the network device drivers that are discussed in Chapter 19, Drivers for Network Devices are STREAMS-based. All non-STREAMS-based drivers must set the streamtab field to NULL.

The cb_flag member contains the following flags:

cb_rev is the cb_ops structure revision number. This field must be set to CB_REV.

Loadable Driver Interfaces

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

Each device driver is required to implement _init(9E), _fini(9E), and _info(9E) entry points to support driver loading and unloading. The following example shows a typical implementation of loadable driver interfaces.


Example 6–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() Example

The following example shows a typical _init(9E) interface.


Example 6–2 _init() Function

static void *xxstatep;
int
_init(void)
{
    int error;
    const int max_instance = 20;    /* estimated max 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(). For example, the driver 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, that driver should allocate resources specific to each printer instance in attach().


Note –

Once _init(9E) has called mod_install(9F), the driver should not change any of the data structures attached to the modlinkage(9S) structure because the system might make copies or change the data structures.


_fini() Example

The following example demonstrates the _fini() routine.

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(), the driver should release any resources that were allocated in _init(). The driver must remove itself from the system module list.


Note –

_fini() might be called when the driver is attached to hardware instances. In this case, mod_remove(9F) returns failure. Therefore, driver resources should not be released until mod_remove() returns success.


_info() Example

The following example demonstrates the _info(9E) routine.

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

For each node in the kernel device tree, the system selects a driver for the node based on the node name and the compatible property (see Binding a Driver to a Device). The same driver might bind to multiple device nodes. The driver can differentiate different nodes by instance numbers assigned by the system.

After a driver is selected for a device node, the driver's probe(9E) entry point is called to determine the presence of the device on the system. If probe() is successful, the driver's attach(9E) entry point is invoked to set up and manage the device. The device can be opened if and only if attach() returns success (see attach() Entry Point).

A device might be unconfigured to conserve system memory resources or to enable the device to be removed while the system is still running. To enable the device to be unconfigured, the system first checks whether the device instance is referenced. This check involves calling the driver's getinfo(9E) entry point to obtain information known only to the driver (see getinfo() Entry Point). If the device instance is not referenced, the driver's detach(9E) routine is invoked to unconfigure the device (see detach() Entry Point).

To recap, each driver must define the following entry points that are used by the kernel for device configuration:

Note that attach(), detach(), and getinfo() are required. probe() is only required for devices that cannot identify themselves. For self-identifying devices, an explicit probe() routine can be provided, or nulldev(9F) can be specified in the dev_ops structure for the probe() entry point.

Device Instances and Instance Numbers

The system assigns an instance number to each device. The driver might 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 use of instance numbers is ddi_get_soft_state(9F), which uses instance numbers to retrieve soft state data for specific physical devices.


Caution – Caution –

For pseudo devices, that is, the children of pseudo nexuses, the instance numbers are defined in the driver.conf(4) file using the instance property. If the driver.conf file does not contain the instance property, the behavior is undefined. For hardware device nodes, the system assigns instance numbers when the device is first seen by the OS. The instance numbers persist across system reboots and OS upgrades.


Minor Nodes and Minor Numbers

Drivers are responsible for managing their minor number namespace. For example, the sd driver needs to export eight character minor nodes and eight block minor nodes to the file system for each disk. Each minor node represents either a block interface or a character interface to a portion of the disk. The getinfo(9E) entry point informs the system about the mapping from minor number to device instance (see getinfo() Entry Point).

probe() Entry Point

For non-self-identifying devices, the probe(9E) 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() needs to perform many tasks that are also commonly done by attach(9E). In particular, probe() might need to map the device registers.

Probing the device registers is device-specific. The driver often has to perform a series of tests of the hardware to assure that the hardware is really present. The test criteria must be rigorous enough to avoid misidentifying devices. For example, a device might appear to be present when in fact that device is not available, because a different device seems to behave like the expected device.

The test returns the following flags:

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

probe(9E) must free all the resources that probe() has allocated, because probe() might be called multiple times. However, attach(9E) is not necessarily called even if probe(9E) has succeeded

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

The following example is a sample probe() routine.


Example 6–3 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 attributes 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, the driver does not know whether the device being probed exists on the bus. Therefore, the driver might attempt to access device registers for a nonexistent device. A bus fault might be generated on some buses as a result.

The following example shows a probe(9E) routine that uses ddi_poke8(9F) to check for the existence of the device. ddi_poke8() cautiously attempts to write a value to a specified virtual address, using the parent nexus driver to assist in the process where necessary. If the address is not valid or the value cannot be written without an error occurring, an error code is returned. See also ddi_peek(9F).

In this example, ddi_regs_map_setup(9F) is used to map the device registers.


Example 6–4 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);

    /*
     * Initialize the device access attrributes and map in
     * the device's 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 that
     * do not exist.  Use ddi_poke8(9f) to handle any faults that
     * might 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);
}

attach() Entry Point

The kernel calls a driver's attach(9E) entry point to attach an instance of a device or to resume operation for an instance of a device that has been suspended or has been shut down by the power management framework. This section discusses only the operation of attaching device instances. Power management is discussed in Chapter 12, Power Management.

A driver's 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 typically includes the following types of processing:

Driver Soft-State Management

To assist device driver writers in allocating state structures, the Solaris DDI/DKI provides a set of memory management routines called software state management routines, which are 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 identifies the desired memory item. This number is typically the instance number assigned by the system.

Drivers typically allocate a soft-state structure for each device instance that attaches to the driver by calling ddi_soft_state_zalloc(9F), passing the instance number of the device. Because no two device nodes can have the same instance number, ddi_soft_state_zalloc(9F) fails if an allocation already exists for a given instance number.

A driver's character or block entry point (cb_ops(9S)) references a particular soft state structure by first decoding the device's 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. A NULL return value indicates that effectively the device does not exist and the appropriate code should be returned by the driver.

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

Lock Variable and Conditional Variable Initialization

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

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. The system uses this information 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 whether the minor node represents a block or character device.

Drivers can create any number of minor nodes for a device. The Solaris DDI/DKI 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. Eight minor nodes are created, representing the a - h block device interfaces, with an additional eight 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. The minor number is usually an encoding of the instance number of the device with a minor node identifier. In the preceding example, the driver creates minor numbers for each of the minor nodes by shifting the instance number of the device left by three bits and using the OR of that result with the minor node index. The values of the minor node index range from 0 to 7. Note that minor nodes a and a,raw share the same minor number. These minor nodes are distinguished by the spec_type argument passed to ddi_create_minor_node().

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.

The following table lists the types of possible nodes that might be created.

Table 6–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 names in the /dev/dsk or /dev/rdsk directory.

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

The node types DDI_NT_SERIAL and DDI_NT_SERIAL_DO cause devfsadm(1M) to perform these actions:

Vendor-supplied strings should include an identifying value such as a name or stock symbol to make the strings unique. The string can be used in conjunction with devfsadm(1M) and the devlinks.tab file (see the devlinks(1M) man page) 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() must then return ENXIO, which causes the system to attempt to attach the device. If the attach() succeeds, the open() is retried automatically.


Example 6–5 Typical attach() 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);
    }
}


Note –

The attach() routine must not make any assumptions about the order of invocations on different device instances. The system might invoke attach() concurrently on different device instances. The system might also invoke attach() and detach() concurrently on different device instances.


detach() Entry Point

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. This section discusses the operation of detaching device instances. Refer to Chapter 12, Power Management for a discussion of power management issues.

A driver's detach() 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 be detached and with DDI_DETACH, which is 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 that 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 callbacks that can be canceled and those that cannot be canceled. 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. The driver must either block in detach() until the callback completes or else fail the request to detach.


Example 6–6 Typical detach() Entry Point

/*
 * 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);
    return (DDI_SUCCESS);

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

getinfo() Entry Point

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.

The getinfo() function can take either DDI_INFO_DEVT2INSTANCE or DDI_INFO_DEVT2DEVINFO as its infocmd argument. The DDI_INFO_DEVT2INSTANCE command requests the instance number of a device. The DDI_INFO_DEVT2DEVINFO command requests a pointer to the dev_info structure of a device.

In the DDI_INFO_DEVT2INSTANCE case, arg is a dev_t, and getinfo() must translate the minor number in dev_t to an instance number. In the following example, the minor number is the instance number, so getinfo() simply passes back the minor number. In this case, the driver must not assume that a state structure is available, since getinfo() might be called before attach(). The mapping defined by the driver between the minor device number and the 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() first decodes the instance number for the device. getinfo() then passes back the dev_info pointer saved in the driver's soft state structure for the appropriate device, as shown in the following example.


Example 6–7 Typical getinfo() Entry Point

/*
 * 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 instance 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);
}


Note –

The getinfo() routine must be kept in sync with the minor nodes that the driver creates. If the minor nodes get out of sync, any hotplug operations might fail and cause a system panic.


Using Device IDs

The Solaris DDI interfaces enable drivers to provide the device ID, a persistent unique identifier for a device. The device ID can be used to identify or locate a device. The device ID is independent of the /devices name or device 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, the driver needs to verify the device is capable of either providing a unique ID or of storing a host-generated unique ID in a not normally accessible area. WWN (world-wide number) is an example of a unique ID that is provided by the device. Device NVRAM and reserved sectors are examples of non-accessible areas where host-generated unique IDs can be safely stored.

Registering Device IDs

Drivers typically initialize and register device IDs in the driver's attach(9E) handler. As mentioned above, the driver is responsible for registering a device ID that is persistent. As such, the driver might be required to handle both devices that 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 device's 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 might also register device IDs for devices that do not directly supply a unique ID. Registering these IDs requires the device to be capable of storing and retrieving a small amount of data in a reserved area. The driver can then 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 device's 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 typically unregister and free any device IDs that are allocated as part of the detach(9E) handling. The driver first calls 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), and then passing the handle that had been returned by ddi_devid_init(9F). The driver is responsible for managing any space allocated for WWN or Serial Number data.