Writing Device Drivers

Autoconfiguration

SCSI target drivers must implement the standard autoconfiguration routines _init(9E), _fini(9E), and _info(9E). See Chapter 5, Autoconfiguration, for more information.

probe(9E), attach(9E), and getinfo(9E) are also required, but they must perform SCSI (and SCSA) specific processing.

probe(9E)

SCSI target devices are not self-identifying, so target drivers must have a probe(9E) routine. This routine must determine whether the expected type of device is present and responding.

The general structure and return codes of the probe(9E) routine are the same as those of other device drivers. SCSI target drivers must use the scsi_probe(9F) routine in their probe(9E) entry point. scsi_probe(9F) sends a SCSI inquiry command to the device and returns a code indicating the result. If the SCSI inquiry command is successful, scsi_probe(9F) allocates a scsi_inquiry(9S) structure and fills it in with the device's inquiry data. Upon return from scsi_probe(9F), the sd_inq field of the scsi_device(9S) structure points to this scsi_inquiry(9S) structure.

Because probe(9E) must be stateless, the target driver must call scsi_unprobe(9F) before probe(9E) returns, even if scsi_probe(9F) fails.

Example 13-1 shows a typical probe(9E) routine. It retrieves its scsi_device(9S) structure from the private field of its dev_info structure. It also retrieves the device's SCSI target and logical unit numbers so that it can print them in messages. The probe(9E) routine then calls scsi_probe(9F) to verify that the expected device (a printer in this case) is present.

If scsi_probe(9F) succeeds, it has attached the device's SCSI inquiry data in a scsi_inquiry(9S) structure to the sd_inq field of the scsi_device(9S) structure. The driver can then determine if the device type is a printer (reported in the inq_dtype field). If it is, the type is reported with scsi_log(9F), using scsi_dname(9F) to convert the device type into a string.


Example 13-1 SCSI Target Driver probe(9E) Routine

static int
xxprobe(dev_info_t *dip)
{
	struct scsi_device *sdp;
	int rval, target, lun;
	/*
	 * Get a pointer to the scsi_device(9S) structure
	 */
	sdp = (struct scsi_device *)ddi_get_driver_private(dip);

	target = sdp->sd_address.a_target;
	lun = sdp->sd_address.a_lun;
	/*
	 * Call scsi_probe(9F) to send the Inquiry command. It will
	 * fill in the sd_inq field of the scsi_device structure.
	 */
	switch (scsi_probe(sdp, NULL_FUNC)) {
	case SCSIPROBE_FAILURE:
	case SCSIPROBE_NORESP:
	case SCSIPROBE_NOMEM:
	   	/*
		    * In these cases, device may be powered off,
		    * in which case we may be able to successfully
		    * probe it at some future time - referred to
		    * as `deferred attach'.
		    */
	    	rval = DDI_PROBE_PARTIAL;
	    	break;
	case SCSIPROBE_NONCCS:
	default:
	    	/*
		     * Device isn't of the type we can deal with,
		     * and/or it will never be usable.
		     */
	    	rval = DDI_PROBE_FAILURE;
	    	break;
	case SCSIPROBE_EXISTS:
	    	/*
		     * There is a device at the target/lun address. Check
		     * inq_dtype to make sure that it is the right device
		     * type. See scsi_inquiry(9S)for possible device types.
		     */
	    	switch (sdp->sd_inq->inq_dtype) {
		    case DTYPE_PRINTER:
			    scsi_log(sdp, "xx", SCSI_DEBUG,
				   "found %s device at target%d, lun%d\n",
				    scsi_dname((int)sdp->sd_inq->inq_dtype),
				    target, lun);
			    rval = DDI_PROBE_SUCCESS;
			    break;
	    	case DTYPE_NOTPRESENT:
	    	default:
			    rval = DDI_PROBE_FAILURE;
			    break; 	
	    	}	
	}
	scsi_unprobe(sdp);
	return (rval);
}

A more thorough probe(9E) routine could also check other fields of the scsi_inquiry(9S) structure as necessary to make sure that the device is of the type expected by a particular driver.

attach(9E)

After the probe(9E) routine has verified that the expected device is present, attach(9E) is called. This routine allocates and initializes any per-instance data, creates minor device node information, and restores the hardware state of a device when the device or the system has been suspended. See "attach()" for details of this. In addition to these steps, a SCSI target driver again calls scsi_probe(9F) to retrieve the device's inquiry data and also creates a SCSI request sense packet. If the attach is successful, the attach function should not call scsi_unprobe(9F).

Three routines are used to create the request sense packet: scsi_alloc_consistent_buf(9F), scsi_init_pkt(9F), and scsi_setup_cdb(9F). scsi_alloc_consistent_buf(9F) allocates a buffer suitable for consistent DMA and returns a pointer to a buf(9S) structure. The advantage of a consistent buffer is that no explicit synchronization of the data is required. In other words, the target driver can access the data after the callback. The sd_sense element of the device's scsi_device(9S) structure must be initialized with the address of the sense buffer. scsi_init_pkt(9F) creates and partially initializes a scsi_pkt(9S) structure. scsi_setup_cdb(9F) creates a SCSI command descriptor block, in this case creating a SCSI request sense command.

Note that since a SCSI device is not self-identifying and does not have a reg property, the driver must set the pm-hardware-state property to inform the framework that this device needs to be suspended and resumed.

Example 13-2 shows the SCSI target driver's attach(9E) routine.


Example 13-2 SCSI Target Driver attach(9E) Routine

static int
xxattach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
	struct xxstate					*xsp;
	struct scsi_pkt					*rqpkt = NULL;
	struct scsi_device					*sdp;
	struct buf					*bp = NULL;
	int		instance;
	instance = ddi_get_instance(dip);
	switch (cmd) {
		   case DDI_ATTACH:
			  break;
	   	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);
	}
	allocate a state structure and initialize it
	...
	xsp = ddi_get_soft_state(statep, instance);
	sdp = (struct scsi_device *)ddi_get_driver_private(dip);
	/*
	 * Cross-link the state and scsi_device(9S) structures.
	 */
	sdp->sd_private = (caddr_t)xsp;
	xsp->sdp = sdp;
	call scsi_probe(9F) again to get and validate inquiry data
	/*
	 * Allocate a request sense buffer. The buf(9S) structure
	 * is set to NULL to tell the routine to allocate a new
	 * one. The callback function is set to NULL_FUNC to tell
	 * the routine to return failure immediately if no
	 * resources are available.
	 */
	bp = scsi_alloc_consistent_buf(&sdp->sd_address, NULL,
		SENSE_LENGTH, B_READ, NULL_FUNC, NULL);
	if (bp == NULL)
	    	goto failed;
	/*
	 * Create a Request Sense scsi_pkt(9S) structure.
	 */
	rqpkt = scsi_init_pkt(&sdp->sd_address, NULL, bp,
		CDB_GROUP0, 1, 0, PKT_CONSISTENT, NULL_FUNC, NULL);
	if (rqpkt == NULL)
	    	goto failed;
	/*
	 * scsi_alloc_consistent_buf(9F) returned a buf(9S)
structure.
	 * The actual buffer address is in b_un.b_addr.
	 */
	sdp->sd_sense = (struct scsi_extended_sense *)bp-
>b_un.b_addr;
	/*
	 * Create a Group0 CDB for the Request Sense command
	 */
	if (scsi_setup_cdb((union scsi_cdb *)rqpkt->pkt_cdbp,
	    	SCMD_REQUEST_SENSE, 0, SENSE__LENGTH, 0) == 0)
	     	goto failed;;
	/*
	 * Fill in the rest of the scsi_pkt structure.
	 * xxcallback() is the private command completion routine.
	 */
	rqpkt->pkt_comp = xxcallback;
	rqpkt->pkt_time = 30; /* 30 second command timeout */
	rqpkt->pkt_flags |= FLAG_SENSING;
	xsp->rqs = rqpkt;
	xsp->rqsbuf = bp;
	create minor nodes, report device, and do any other initialization
	/*
	 * Since the device does not have the 'reg' property,
	 * cpr will not call its DDI_SUSPEND/DDI_RESUME entries.
	 * The following code is to tell cpr that this device
	 * needs to be suspended and resumed.
	 */
    (void) ddi_prop_create(device, dip, DDI_PROP_CANSLEEP,
	 	"pm-hardware-state", (caddr_t)"needs-suspend-resume",
		strlen("needs-suspend-resume") + 1);
	xsp->open = 0;
	return (DDI_SUCCESS);
failed:
	if (bp)
	    	scsi_free_consistent_buf(bp);
	if (rqpkt)
	    	scsi_destroy_pkt(rqpkt);
	sdp->sd_private = (caddr_t)NULL;
	sdp->sd_sense = NULL;
	scsi_unprobe(sdp);
    free any other resources, such as the state structure
	return (DDI_FAILURE);
}

detach(9E)

The detach(9E) entry point is the inverse of attach(9E); it must free all resources that were allocated in attach(9E). If successful, the detach should call scsi_unprobe(9F). Example 13-3 shows a target driver detach(9E) routine.


Example 13-3 SCSI Target Driver detach(9E) Routine

static int
xxdetach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
	struct xxstate *xsp;
	switch (cmd) {
	case DDI_DETACH:
	  	normal detach(9E) operations, such as getting a 
		pointer to the state structure
	  	...
	  	scsi_free_consistent_buf(xsp->rqsbuf);
	  	scsi_destroy_pkt(xsp->rqs);
	  	xsp->sdp->sd_private = (caddr_t)NULL;
	  	xsp->sdp->sd_sense = NULL;
	  	scsi_unprobe(xsp->sdp);
	   	remove minor nodes
	   	free resources, such as the state structure and properties
	   	remove power managed components
 	  	return (DDI_SUCCESS);
	case DDI_SUSPEND:
	  	For information, see Chapter 8, Power Management	case DDI_PM_SUSPEND:
		For information, see Chapter 8, Power Management	default:
		return (DDI_FAILURE);
	}
}

getinfo(9E)

The getinfo(9E) routine for SCSI target drivers is much the same as for other drivers; see "getinfo()" for more information on DDI_INFO_DEVT2INSTANCE case. However, in the DDI_INFO_DEVT2DEVINFO case of the getinfo(9E) routine, the target driver must return a pointer to its dev_info node. This pointer can be saved in the driver state structure or can be retrieved from the sd_dev field of the scsi_device(9S) structure. Example 13-4shows an alternative SCSI target driver getinfo(9E) code fragment.


Example 13-4 Alternative SCSI Target Driver getinfo(9E) Code Fragment

...
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->sdp->sd_dev;
		return (DDI_SUCCESS);
...