Associated with each device driver is a dev_ops(9S) structure, which allows the kernel to locate the autoconfiguration entry points of the driver. A complete description of these autoconfiguration routines is given in Chapter 5, Autoconfiguration. This section describes only those entry points associated with operations performed by SCSI HBA drivers. These include attach(9E) and detach(9E).
The attach(9E) entry point for a SCSI HBA driver must perform a number of tasks to configure and attach an instance of the driver for the device. For a typical driver of real devices, the following operating system and hardware concerns must be addressed:
Soft-state structure
DMA
Transport structure
Attaching an HBA driver
Register mapping
Interrupt specification
Interrupt handling
Create power manageable components
Report attachment status
The driver should allocate the per-device-instance soft state structure, being careful to clean up properly if an error occurs.
instance = ddi_get_instance(dip); if (ddi_soft_state_zalloc(isp_state, instance) != DDI_SUCCESS) { return (DDI_FAILURE); } isp = ddi_get_soft_state(isp_state, instance);
If the driver provides DMA, for example, it must specify DMA attributes describing the capabilities and limitations of its DMA engine.
In the Solaris 7 system, the HBA driver must provide DMA.
static ddi_dma_attr_t isp_dma_attr = { DMA_ATTR_V0, /* ddi_dma_attr version */ 0, /* low address */ 0xffffffff, /* high address */ 0x00ffffff, /* counter upper bound */ 1, /* alignment requirements */ 0x3f, /* burst sizes */ 1, /* minimum DMA access */ 0xffffffff, /* maximum DMA access */ (1<<24)-1, /* segment boundary restrictions */ 1, /* scatter/gather list length */ 512, /* device granularity */ 0 /* DMA flags */ };
The driver, if providing DMA, should also check that its hardware is installed in a DMA-capable slot:
if (ddi_slaveonly(dip) == DDI_SUCCESS) { return (DDI_FAILURE); }
The driver should further allocate and initialize a transport structure for this instance. The tran_hba_private field is set to point to this instance's soft-state structure. tran_tgt_probe may be set to NULL to achieve the default behavior, if no special probe customization is needed.
tran = scsi_hba_tran_alloc(dip, SCSI_HBA_CANSLEEP); isp->isp_tran = tran; isp->isp_dip = dip; tran->tran_hba_private = isp; tran->tran_tgt_private = NULL; tran->tran_tgt_init = isp_tran_tgt_init; tran->tran_tgt_probe = scsi_hba_probe; tran->tran_tgt_free = (void (*)())NULL; tran->tran_start = isp_scsi_start; tran->tran_abort = isp_scsi_abort; tran->tran_reset = isp_scsi_reset; tran->tran_getcap = isp_scsi_getcap; tran->tran_setcap = isp_scsi_setcap; tran->tran_init_pkt = isp_scsi_init_pkt; tran->tran_destroy_pkt = isp_scsi_destroy_pkt; tran->tran_dmafree = isp_scsi_dmafree; tran->tran_sync_pkt = isp_scsi_sync_pkt; tran->tran_reset_notify = isp_scsi_reset_notify;
The driver should attach this instance of the device and perform error cleanup if necessary.
i = scsi_hba_attach_setup(dip, &isp_dma_attr, tran, 0); if (i != DDI_SUCCESS) { do error recovery return (DDI_FAILURE); }
The driver should map in its device's registers, specifying the index of the register set, the data access characteristics of the device and the size of the register set to be mapped.
ddi_device_acc_attr_t dev_attributes; dev_attributes.devacc_attr_version = DDI_DEVICE_ATTR_V0; dev_attributes.devacc_attr_dataorder = DDI_STRICTORDER_ACC; dev_attributes.devacc_attr_endian_flags = DDI_STRUCTURE_LE_ACC; if (ddi_regs_map_setup(dip, 0, (caddr_t *)&isp->isp_reg, 0, sizeof (struct ispregs), &dev_attributes, &isp->isp_acc_handle) != DDI_SUCCESS) { do error recovery return (DDI_FAILURE); }
The driver should determine if a high-level interrupt handler is required. If a high-level handler is required and the driver is not coded to provide one, the driver must be rewritten to either include a high-level interrupt or fail the attach.
In the following example, a high-level interrupt is required but not provided by the driver. Consequently, the driver fails the attach.
if (ddi_intr_hilevel(dip, 0) != 0) { return (DDI_FAILURE); }
The driver must first obtain the iblock cookie to initialize mutexes used in the driver handler. Only after those mutexes have been initialized can the interrupt handler be added.
i = ddi_get_iblock_cookie(dip, 0, &isp->iblock_cookie}; if (i != DDI_SUCCESS) { do error recovery return (DDI_FAILURE); } mutex_init(&isp->mutex, "isp_mutex", MUTEX_DRIVER, (void *)isp->iblock_cookie); i = ddi_add_intr(dip, 0, &isp->iblock_cookie, 0, isp_intr, (caddr_t)isp); if (i != DDI_SUCCESS) { do error recovery return (DDI_FAILURE); }
Finally, the driver should report that this instance of the device is attached and return success.
ddi_report_dev(dip); return (DDI_SUCCESS);
The Solaris 7 DDI/DKI does not support detaching an HBA driver, although target driver children of an HBA can detach. For best results, the HBA driver should fail a detach request. It's better to fail the detach than to include code that cannot be tested.
Example 14-3 provides an example of the xx_detach() function.
static int isp_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) { switch (cmd) { case DDI_DETACH: /* * At present, detaching HBA drivers is not supported */ return (DDI_FAILURE); case DDI_PM_RESUME: For information, see Chapter 8, Power Management case DDI_RESUME: For information, see Chapter 8, Power Management default: return (DDI_FAILURE); } }