Writing Device Drivers

The 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 shut down by the power management framework. This section discusses only the operation of attaching device instances; the power management discussion is left to Chapter 9, 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 will typically include the following types of processing:

Driver Soft State Management

To assist device driver writers in allocating state structures, the Solaris 9 DDI/DKI provides a set of memory management routines called 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 identifies 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 that attaches to the driver by calling ddi_soft_state_zalloc(9F), passing the instance number of the device. Because there cannot 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 driver's character or block entry point (cb_ops(9S)) will reference 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. If ddi_get_soft_state(9F) returns a NULL value, the driver should treat this as if 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 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 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 whether the minor node represents a block or character device.

Drivers can choose to create any number of minor nodes for a device. The Solaris DDI/DDK 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 device's 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 7. Note that minor nodes a and a,raw share the same minor number. They 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. Table 5–1 lists the types of possible nodes that may be created.

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 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 causesdevfsadm(1M) to identify the device instance as a serial port and to create names in the /dev/term and /dev/cua directories and to add entries in /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() 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–5 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);
    }

}


Note –

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