Writing Device Drivers
  Buscar sólo este libro

Autoconfiguration

5

This chapter describes the support a driver must provide for autoconfiguration.

Overview

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. These processes are discussed in more detail in "Loadable Driver Interface" on page 98 and "Device Configuration" on page 100. The autoconfiguration process includes both of these processes and begins when the device is put into use.

State Structure

This section adds the following fields to the state structure. See "State Structure" on page 67 for more information.
int     instance;
ddi_iblock_cookie_t       iblock_cookie;
ddi_idevice_cookie_t      idevice_cookie;
ddi_acc_handle_t          data_access_handle;

Driver Loading and Configuration

Figure 5-1 illustrates a structural overview of a device driver. The shaded area of this figure shows the autoconfiguration process, which is subdivided into two parts: driver loading (performed by the kernel) and driver configuration.

Note - The third section (device access) is discussed in Chapter 8, "Drivers for Character Devices" and Chapter 9, "Drivers for Block Devices.

Gráfico

Figure 5-1

Data Structures

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()

int     ml_rev;
void    *ml_linkage[4];

The modlinkage(9S) structure is exported to the kernel when the driver is loaded. The ml_rev field indicates the revision number of the loadable module system, which should be set to MODREV_1. Drivers can only support one module, so only the first element of ml_linkage should be set to the address of a modldrv(9S) structure. ml_linkage[1] should be set to NULL.

modldrv()

struct mod_ops        *drv_modops;
char                  *drv_linkinfo;
struct dev_ops        *drv_dev_ops;

This structure describes the module in more detail. The drv_modops field points to a structure describing the module operations, which is &mod_driverops for a device driver. The drv_linkinfo field is displayed by the modinfo(1M) command and should be an informative string identifying the device driver. The drv_dev_ops field points to the next structure in the chain, the dev_ops(9S) structure.

dev_ops()

int     devo_rev;
int     devo_refcnt;
int     (*devo_getinfo)(dev_info_t *dip,ddi_info_cmd_t infocmd,
             void *arg, void **result);
int     (*devo_identify)(dev_info_t *dip);
int     (*devo_probe)(dev_info_t *dip);
int     (*devo_attach)(dev_info_t *dip, ddi_attach_cmd_t cmd);
int     (*devo_detach)(dev_info_t *dip, ddi_detach_cmd_t cmd);
int     (*devo_reset)(dev_info_t *dip, ddi_reset_cmd_t cmd);
struct cb_ops    *devo_cb_ops;
struct bus_ops *devo_bus_ops;

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:
  • If a probe(9E) routine is not needed, use nulldev(9F).
  • nodev(9F) can be used in devo_detach to prevent the driver from being unloaded.
  • devo_reset should be set to nodev(9F).
The devo_cb_ops member should contain the address of the cb_ops(9S) structure. The devo_bus_ops field must be set to NULL.

cb_ops

int     (*cb_open)(dev_t *devp, int flag, int otyp,
             cred_t *credp);
int     (*cb_close)(dev_t dev, int flag, int otyp,
             cred_t *credp);
int     (*cb_strategy)(struct buf *bp);
int     (*cb_print)(dev_t dev, char *str);
int     (*cb_dump)(dev_t dev, caddr_t addr, daddr_t blkno,
             int nblk);
int     (*cb_read)(dev_t dev, struct uio *uiop, cred_t *credp);
int     (*cb_write)(dev_t dev, struct uio *uiop, cred_t *credp);
int     (*cb_ioctl)(dev_t dev, int cmd, int arg, int mode,
             cred_t *credp, int *rvalp);
int     (*cb_devmap)();

int     (*cb_mmap)(dev_t dev, off_t off, int prot);
int     (*cb_segmap)(dev_t dev, off_t off, struct as *asp,
             addr_t *addrp, off_t len, unsigned int prot,
             unsigned int maxprot, unsigned int flags,
             cred_t *credp);
int     (*cb_chpoll)(dev_t dev, short events, int anyyet,
             short *reventsp, struct pollhead **phpp);
int     (*cb_prop_op)(dev_t dev, dev_info_t *dip,
             ddi_prop_op_t prop_op, int mod_flags,
             char *name, caddr_t valuep, int *length);
struct streamtab      *cb_str;   /* STREAMS information */
int     cb_flag;
int     cb_rev;
int     (*cb_aread)(dev_t dev, struct aio_req *aio,
             cred_t *credp);
int     (*cb_awrite)(dev_t dev, struct aio_req *aio,
             cred_t *credp);

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).
The cb_str 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, cb_str must be set to NULL.
The cb_flag member indicates whether the driver is safe for multithreading (D_MP) and whether it is a new-style driver (D_NEW). All drivers are new-style drivers, and should properly handle the multithreaded environment, so cb_flag should be set to both (D_NEW | D_MP).
If the driver properly handles 64-bit offsets, it should also set the D_64BIT flag in the cb_flag field. This specifies that the driver will use the uio_loffset field of the uio(9S) structure.
cb_rev is the cb_ops(9S) structure revision number. This field must be set to CB_REV.

Loadable Driver Interface

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" on page 95 and the routines involved in loading the module.
Code 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,
    xxidentify,
    xxprobe,
    xxattach,
    xxdetach,
    nodev,
    &xx_cb_ops,
    (struct bus_ops *) NULL
};
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));
}

Any one-time resource allocation or data initialization should be performed during driver loading in _init(9E). For example, any mutexes global to the driver should be initialized here. Do 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 drive more than one printer at the same time, allocate resources specific to each printer instance in attach(9E).
Similarly, in _fini(9E), release only those resources allocated by _init(9E).

Note - Once _init(9E) has called mod_install(9F), none of the data structures hanging off of the modlinkage(9S) structure should be changed by the driver, as the system may make copies of them or change them.

Device Configuration

Each driver must provide five entry points that are used by the kernel for device configuration. They are:
  • identify(9E)
  • probe(9E)
  • attach(9E)
  • detach(9E)
  • getinfo(9E)
Every device driver must have an identify(9E), 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.

identify( )

The system calls identify(9E) to find out whether the driver drives the device specified by dip.
Code Example 5-2 identify(9E) routine
static int
xxidentify(dev_info_t *dip)
{
    if (strcmp(ddi_get_name(dip), "xx") == 0)
        return (DDI_IDENTIFIED);
    else
        return (DDI_NOT_IDENTIFIED);
}

If the device is known by several different names, identify(9E) should check for a match with each name before failing. The names must also have been passed with aliases to add_drv(1M) when the driver was installed. See Chapter 13, "Loading and Unloading Drivers."
identify(9E) should not maintain a device count, since the system does not guarantee that identify(9E) will be called for all device instances before attach(9E) is called for any device instance, nor does the system make any guarantees about the number of times identify(9E) will be called for any given device.

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). See Code Example 5-5 on page 105 for an example.
Instance numbers are derived in an implementation- specific manner from different properties for the different device types. The following properties are used to derive instance numbers:
The reg property is used for SBus, PCI, VMEbus, ISA, EISA, and MCA devices. Non-self-identifying device drivers provide this in the hardware configuration file. See sbus(4), pci(4), isa(4) and vme(4).
The target and lun properties are used for SCSI target devices. These are provided in the hardware configuration file. See scsi(4).
The instance property is used for pseudo-devices. This is provided in the hardware configuration file. See pseudo(4).

Persistent Instances

Once an instance number has been assigned to a particular physical device by the system, it stays the same even across reconfiguration and reboot. Because of this, instance numbers seen by a driver may not appear to be in consecutive order.

probe( )

This entry point is not required for self-identifying devices such as SBus or PCI devices. nulldev(9F) may be used instead.
For non self-identifying devices (see "Device Identification" on page 22) this entry point should determine whether the hardware device is present on the system and return:
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 OR

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.
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.
When the driver's probe(9E) routine is called, it does not know if the device being probed exists on the bus. Therefore, it is possible that the driver may attempt to access device registers for a non-existent device. A bus fault may be generated on some busses as a result.
Buses such as ISA, EISA, and MCA do not generate bus faults as a result of such accesses. Code Example 5-3 is an sample probe(9E) routine for devices on these buses.
Code Example 5-3 probe(9E) routine
static int
xxprobe(dev_info_t *dip)
{
    int          instance;
    volatile caddr_t reg_addr;
    ddi_acc_handle_t data_access_handle;

    /* define device attributes */
    ddi_device_acc_attr_t access_attr = {
        DDI_DEVICE_ATTR_V0,
        DDI_STRUCTURE_BE_ACC,

    DDI_STRICTORDER_ACC
};
if (ddi_dev_is_sid(dip) == DDI_SUCCESS) /* no need to probe */
    return (DDI_PROBE_DONTCARE);
instance = ddi_get_instance(dip); /* assigned instance */
if (ddi_intr_hilevel(dip, inumber)) {
    cmn_err(CE_CONT,
        "?xx driver does not support high level interrupts."
        " Probe failed.");
    return (DDI_PROBE_FAILURE);
}

/* Map device registers and try to contact device.*/
if (ddi_regs_map_setup(dip, rnumber, &reg_addr, offset, len,
        &access_attr, &data_access_handle) != DDI_SUCCESS)
    return (DDI_PROBE_FAILURE);
if (ddi_getb(data_access_handle, (uchar_t *)reg_addr) !=
        some_value)
    goto failed;

free allocated resources
    ddi_regs_map_free(&data_access_handle);

if (device is present and ready for attach)
        return (DDI_PROBE_SUCCESS);
    else if (device is present but not ready for attach)
        return (DDI_PROBE_PARTIAL);
    else    /* device is not present */
        return (DDI_PROBE_FAILURE);
failed:

free allocated resources
    ddi_regs_map_free(&data_access_handle);
    return (DDI_PROBE_FAILURE);
}

The string printed in the high-level interrupt case begins with a '?' character. This causes the message to be printed only if the kernel was booted with the verbose (-v) flag. (See kernel(1M)). Otherwise the message only goes into the message log, where it can be seen by running dmesg(1M).
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.
For VME device drivers, a fault may occur as a result of attempting to access device registers for a device that is not present. In this case, the ddi_peek(9F) and ddi_poke(9F) family of routines must be used to access the device registers. Code Example 5-4 shows a probe(9E) routine that uses ddi_peek(9F) and ddi_poke(9F) to check for the existence of the device.
Code Example 5-4 probe(9E) routine using ddi_peek(9F)
static int
xxprobe(dev_info_t *dip)
{
    int          instance;
    volatile caddr_t reg_addr;
    if (ddi_dev_is_sid(dip) == DDI_SUCCESS) /* no need to probe */
        return (DDI_PROBE_DONTCARE);
    instance = ddi_get_instance(dip); /* assigned instance */
    if (ddi_intr_hilevel(dip, inumber)) {
        cmn_err(CE_CONT,
             "?xx driver does not support high level interrupts."
             " Probe failed.");
        return (DDI_PROBE_FAILURE);
    }
    /*
     * Map device registers and try to contact device.
     */
    if (ddi_map_regs(dip, rnumber, &reg_addr, offset, len) != 0)
        return (DDI_PROBE_FAILURE);
    if (ddi_peekc(dip, reg_addr, NULL) != DDI_SUCCESS)
        goto failed;

free allocated resources
    ddi_unmap_regs(dip, rnumber, &reg_addr, offset, len);
    if (device is present and ready for attach)
        return (DDI_PROBE_SUCCESS);
    else if (device is present but not ready for attach)
        return (DDI_PROBE_PARTIAL);
    else    /* device is not present */
        return (DDI_PROBE_FAILURE);

failed: free allocated resources
    ddi_unmap_regs(dip, rnumber, &reg_addr, offset, len);
    return (DDI_PROBE_FAILURE);
}

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

attach( )

The system calls attach(9E) to attach a device instance to the system. The responsibilities of the DDI_ATTACH case of attach(9E) include:
  • Optionally allocating a soft state structure for the instance
  • Registering an interrupt handler
  • Mapping device registers
  • Initializing per- instance mutexes and condition variables
  • Creating minor device nodes for the instance
Code Example 5-5 is an example of an attach(9E) routine.
Code Example 5-5 attach(9E) routine
static int
xxattach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
    struct xxstate *xsp;
    int     instance;

    /* define device attributes */
    ddi_device_acc_attr_t access_attr = {
        DDI_DEVICE_ATTR_V0,
        DDI_STRUCTURE_BE_ACC,
        DDI_STRICTORDER_ACC
    };
    switch (cmd) {
    case DDI_ATTACH:

        /* get assigned instance number */
        instance = ddi_get_instance(dip);
        if (ddi_soft_state_zalloc(statep, instance) != 0)
             return (DDI_FAILURE);
        xsp = ddi_get_soft_state(statep, instance);

        /* retrieve interrupt block cookie */
        if (ddi_get_iblock_cookie(dip, inumber,
                 &xsp->iblock_cookie) != DDI_SUCCESS) {

        ddi_soft_state_free(statep, instance);
        return (DDI_FAILURE);
    }
    /* initialize locks. Note that mutex_init wants a */
    /* ddi_iblock_cookie, not the _address_ of one, */
    /* as the fourth argument.*/
    mutex_init(&xsp->mu, "xx mutex", MUTEX_DRIVER,
        (void *)xsp->iblock_cookie);
    cv_init(&xsp->cv, "xx cv", CV_DRIVER, NULL);

    /* set up interrupt handler for the device */
    if (ddi_add_intr(dip, inumber, NULL,
        &xsp->idevice_cookie, NULL, intr_handler,intr_handler_arg)
        != DDI_SUCCESS) {
        ddi_soft_state_free(statep, instance);
        return (DDI_FAILURE);
    }
    /* map device registers */
    if (ddi_regs_map_setup(dip, rnumber, &xsp->regp,
        offset, sizeof(struct device_reg), &access_attr,
        &xsp->data_access_handle) != DDI_SUCCESS) {
        ddi_remove_intr(dip, inumber, xsp->iblock_cookie);
        ddi_soft_state_free(statep, instance);
        return (DDI_FAILURE);
    }
    xsp->dip = dip;

initialize the rest of the software state structure;
    make device quiescent;             /* device-specific */
    /*
     * for devices with programmable bus interrupt level
     */
    program device interrupt level using xsp->idevice_cookie;
    if (ddi_create_minor_node(dip, "minor name", S_IFCHR,
        minor_number, node_type, 0) != DDI_SUCCESS)
        goto failed;

initialize driver data, prepare for a later open of the device;/*device-specific */
    ddi_report_dev(dip);
    return (DDI_SUCCESS);
default:
    return (DDI_FAILURE);
}

failed:

free allocated resources
    ddi_regs_map_free(&xsp->data_access_handle);
    ddi_remove_intr(dip, inumber, xsp->iblock_cookie);
    cv_destroy(&xsp->cv);
    mutex_destroy(&xsp->mu);
    ddi_soft_state_free(statep, instance);
    return (DDI_FAILURE);
}

attach(9E) first checks for the DDI_ATTACH command, which is the only one it handles. Future releases may support additional commands; consequently, it is important that drivers return DDI_FAILURE for all the commands they do not recognize. attach(9E) then calls ddi_get_instance(9F) to get the instance number the system has assigned to the dev_info node indicated by dip.
Since the driver must be able to return a pointer to its dev_info node for each instance, attach(9E) must save dip, usually in a field of a per-instance state structure. The example also requires DMA capability, so ddi_slaveonly(9F) is called to check if the slot is capable of DMA. See Chapter 2, "Hardware Overview" for more information on SBus.
The section discusses one example of such SBus hardware.
If any of the resource allocation routines fail, the code at the failed label should free any resources that had already been allocated before returning DDI_FAILURE. This can be done with a series of checks that look like this:
if (xsp->regp)
    ddi_regs_map_free(&xsp->data_access_handle);

There should be such a check and a deallocation operation for each allocation operation that may have been performed.

Registering Interrupts Overview

In the call to ddi_add_intr(9F), inumber specifies which of several possible interrupt specifications is to be handled by intr_handler. For example, if the device interrupts at only one level, pass 0 for inumber.The interrupt specifications being referred to by inumber are described by the interrupts property (see driver.conf(4), isa(4), eisa(4), mca(4), sysbus(4), vme(4), and sbus(4)). intr_handler is a pointer to a function, in this case xxintr(), to
be called when the device issues the specified interrupt. intr_handler_arg is an argument of type caddr_t to be passed to intr_handler. intr_handler_arg may be a pointer to a data structure representing the device instance that issued the interrupt. ddi_add_intr(9F) returns a device cookie in xsp->idevice_cookie for use with devices having programmable bus-interrupt levels. The device cookie contains the following fields:
u_short      idev_vector;
u_short      idev_priority;

The idev_priority field of the returned structure contains the bus interrupt priority level, and the idev_vector field contains the vector number for vectored bus architectures such as VMEbus.

Note - There is a potential race condition in attach(9E). The interrupt routine is eligible to be called as soon as ddi_add_intr(9F) returns. This may result in the interrupt routine being called before any mutexes have been initialized with the interrupt block cookie. If the interrupt routine acquires the mutex before it has been initialized, undefined behavior may result. See "Registering Interrupts" on page 120 for a solution to this problem.

Mapping Device Drivers

In the ddi_regs_map_setup(9F) call, dip is the dev_info pointer passed to attach(9E). rnumber specifies which register set to map if there is more than one. For devices with only one register set, pass 0 for rnumber.The register specifications referred to by rnumber are described by the reg property (see driver.conf(4), isa(4), eisa(4), mca(4), sysbus(4), vme(4), sbus(4) and pci(4)). ddi_regs_map_setup(9F) maps a device register set (register specification) and returns a bus address base in xsp->regp. This address is offset bytes from the base of the device register set, and the mapping extends sizeof(struct device_reg) bytes beyond that. To map all of a register set, pass zero for offset and the length.

Minor Device Nodes

A minor device node contains the information exported by the device that the system uses to create a special file for the device under /devices in the filesystem.
In the call to ddi_create_minor_node(9F), the minor name is the character string that is the last part of the base name of the special file to be created for this minor device number; for example, "b,raw" in
"fd@1,f7200000:b,raw". S_IFCHR means create a character special file. Finally, the node type is one of the following system macros, or any string constant that does not conflict with the values of these macros (See ddi_create_minor_node(9F) for more information).
Table 5-1
ConstantDescription
DDI_NT_SERIALSerial port
DDI_NT_SERIAL_DODialout ports
DDI_NT_BLOCKHard disks
DDI_NT_BLOCK_CHANHard disks with channel or target numbers
DDI_NT_CDROM drives (CDROM)
DDI_NT_CD_CHANROM drives with channel or target numbers
DDI_NT_FDFloppy disks
DDI_NT_TAPETape drives
DDI_NT_NETNetwork devices
DDI_NT_DISPLAYDisplay devices
DDI_PSEUDOGeneral pseudo devices
The node types DDI_NT_BLOCK, DDI_NT_BLOCK_CHAN, DDI_NT_CD and DDI_NT_CD_CHAN causes disks(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 tapes(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 type DDI_NT_SERIAL 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 add a new entry to /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 (along with the other node types not consumed by disks(1M), tapes(1M), or ports(1M) can be used in conjunction with devlinks(1M) and devlink.tab(4) to create logical names in /dev.

Deferred Attach

open(9E) might be called before attach(9E) has succeeded. 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.

detach( )

detach(9E) is the inverse operation to attach(9E). It is called for each device instance, receiving a command of DDI_DETACH, when the system attempts to unload a driver module. The system only calls the DDI_DETACH case of detach(9E) for a device instance if the device instance is not open. No calls to other driver entry points for that device instance occurs during detach(9E), although interrupts and time-outs may occur.
The main purpose of detach(9E) is to free resources allocated by attach(9E) for the specified device. For example, detach(9E) should unmap any mapped device registers, remove any interrupts registered with the system, and free the soft state structure for this device instance.
If the detach(9E) routine entry in the dev_ops(9S) structure is initialized to nodev, it implies that detach(9E) always fails, and the driver will not be unloaded. This is the simplest way to specify that a driver is not unloadable.
Code Example 5-6 detach(9E) routine
static int
xxdetach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
    struct xxstate *xsp;
    int     instance;

    switch (cmd) {
    case DDI_DETACH:
        instance = ddi_get_instance(dip);
        xsp = ddi_get_soft_state(statep, instance);

        make device quiescent;         /* device-specific */
        ddi_remove_minor_node(dip, NULL);
        ddi_regs_map_free(&xsp->data_access_handle);
        ddi_remove_intr(dip, inumber, xsp->iblock_cookie);
        mutex_destroy(&xsp->mu);
        cv_destroy(&xsp->cv);
        ddi_soft_state_free(statep, instance);
        return (DDI_SUCCESS);
    default:
        return (DDI_FAILURE);
    }
}

In the call to ddi_regs_map_free(9F), xsp->data_access_handle is the data access handle previously allocated by the call to ddi_regs_map_setup(9F) in attach(9E). Similarly, in the call to ddi_remove_intr(9F), inumber is the same value that was passed to ddi_add_intr(9F).

Callbacks

The detach(9E) routine must not return DDI_SUCCESS while it has callback functions pending. This is only critical for callbacks registered for device instances that are not currently open, since the DDI_DETACH case is not entered if the device is open.
There are two types of callback routines of interest: callbacks that can be cancelled, and callbacks that must run to completion.
Callbacks that can be cancelled do not pose a problem; just remember to cancel the callback before detach(9E) returns DDI_SUCCESS. Each of the callback cancellation routines in Table 5-2 atomically cancels callbacks so that a callback routine does not run while it is being cancelled.
Table 5-2
FunctionCancelling function
timeout(9F)untimeout(9F)
bufcall(9F)unbufcall(9F)
esbbcall(9F)unbufcall(9F)
Some callbacks cannot be cancelled--for these it is necessary to wait until the callback has been called. In some cases, such as ddi_dma_setup(9F), the callback must also be prevented from rescheduling itself. See "Cancelling DMA Callbacks" on page 148 for an example.
Following is a list of some functions that may establish callbacks that cannot be cancelled:
  • esballoc(9F)
  • ddi_dma_setup(9F)
  • ddi_dma_addr_setup(9F)
  • ddi_dma_buf_setup(9F)
  • scsi_dmaget(9F)
  • scsi_resalloc(9F)
  • scsi_pktalloc(9F)
  • scsi_init_pkt(9F)

getinfo( )

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 the following code sample.
static int
xxgetinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg,
  void **result)
{
    struct xxstate *xsp;
    dev_t   dev;
    int     instance, error;
    switch (infocmd) {
    case DDI_INFO_DEVT2INSTANCE:
        dev = (dev_t) arg;
        *result = (void *) getminor(dev);
        error = DDI_SUCCESS;
        break;
    case DDI_INFO_DEVT2DEVINFO:
        dev = (dev_t) arg;
        instance = getminor(dev);
        xsp = ddi_get_soft_state(statep, instance);
        if (xsp == NULL)
             return (DDI_FAILURE);
        *result = (void *) xsp->dip;
        error = DDI_SUCCESS;
        break;
    default:
        error = DDI_FAILURE;
        break;
    }
    return (error);
}