Writing Device Drivers

tran_start(9E)

The tran_start(9E) entry point for a SCSI HBA driver is called to transport a SCSI command to the addressed target. The SCSI command is described entirely within the scsi_pkt(9S) structure, which the target driver allocated through the HBA driver's tran_init_pkt(9E) entry point. If the command involves a data transfer, DMA resources must also have been allocated for the scsi_pkt(9S) structure.

The tran_start(9E) entry point is called when a target driver calls scsi_transport(9F).

tran_start(9E) should perform basic error checking along with whatever initialization the command requires, queue the command for execution on the HBA hardware, and return without blocking. If the hardware is idle, the command may be started immediately.

For commands with the FLAG_NOINTR bit set in the pkt_flags field of the scsi_packet(9S) structure, tran_start(9E) should not return until the command has been completed, and the HBA driver should not call the pkt completion routine.

Example 14-10 demonstrates how to handle the tran_start(9E) entry point. The ISP hardware provides a queue per-target device. For devices that can manage only one active outstanding command, the driver itself is typically required to manage a per-target queue and starts up a new command upon completion of the current command in a round-robin fashion.


Example 14-10 HBA Driver tran_start(9E) Entry Point

static int
isp_scsi_start(
	struct scsi_address						*ap,
	struct scsi_pkt						*pkt)
{
	struct isp_cmd						*sp;
	struct isp						*isp;
	struct isp_request						*req;
	u_long						cur_lbolt;
	int						xfercount;
	int						rval	= TRAN_ACCEPT;
	int						i;

	sp = (struct isp_cmd *)pkt->pkt_ha_private;
	isp = (struct isp *)ap->a_hba_tran->tran_hba_private;

	sp->cmd_flags = (sp->cmd_flags & ~CFLAG_TRANFLAG) |
			   			 CFLAG_IN_TRANSPORT;
	pkt->pkt_reason = CMD_CMPLT;

	/*
	 * set up request in cmd_isp_request area so it is ready to
	 * go once we have the request mutex
	 */
	req = &sp->cmd_isp_request;

	req->req_header.cq_entry_type = CQ_TYPE_REQUEST;
	req->req_header.cq_entry_count = 1;
	req->req_header.cq_flags			= 0;
	req->req_header.cq_seqno = 0;
	req->req_reserved = 0;
	req->req_token = (opaque_t)sp;
	req->req_target = TGT(sp);
	req->req_lun_trn = LUN(sp);
	req->req_time = pkt->pkt_time;
	ISP_SET_PKT_FLAGS(pkt->pkt_flags, req->req_flags);

	/*
	 * Set up dma transfers data segments.
	 */
	if (sp->cmd_flags & CFLAG_DMAVALID) {

		if (sp->cmd_flags & CFLAG_CMDIOPB) {
			(void) ddi_dma_sync(sp->cmd_dmahandle,
			    sp->cmd_dma_offset, sp->cmd_dma_len,
			    DDI_DMA_SYNC_FORDEV);
		}

		ASSERT(sp->cmd_cookiecnt > 0 &&
		    sp->cmd_cookiecnt <= ISP_NDATASEGS);

		xfercount = 0;
		req->req_seg_count = sp->cmd_cookiecnt;
		for (i = 0; i < sp->cmd_cookiecnt; i++) {
			req->req_dataseg[i].d_count =
				sp->cmd_dmacookies[i].dmac_size;
			req->req_dataseg[i].d_base =
				sp->cmd_dmacookies[i].dmac_address;
			xfercount +=
				sp->cmd_dmacookies[i].dmac_size;
		}

		for (; i < ISP_NDATASEGS; i++) {
			req->req_dataseg[i].d_count = 0;
			req->req_dataseg[i].d_base = 0;
		}

		pkt->pkt_resid = xfercount;

		if (sp->cmd_flags & CFLAG_DMASEND) {
			req->req_flags |= ISP_REQ_FLAG_DATA_WRITE;
		} else {
			req->req_flags |= ISP_REQ_FLAG_DATA_READ;
		}
	} else {
		req->req_seg_count = 0;
		req->req_dataseg[0].d_count = 0;
	}

	/*
	 * Set up cdb in the request
	 */
	req->req_cdblen = sp->cmd_cdblen;
	bcopy((caddr_t)pkt->pkt_cdbp, (caddr_t)req->req_cdb,
		sp->cmd_cdblen);

	/*
	 * Start the cmd.  If NO_INTR, must poll for cmd completion.
	 */
	if ((pkt->pkt_flags & FLAG_NOINTR) == 0) {
		mutex_enter(ISP_REQ_MUTEX(isp));
		rval = isp_i_start_cmd(isp, sp);
		mutex_exit(ISP_REQ_MUTEX(isp));
	} else {
		rval = isp_i_polled_cmd_start(isp, sp);
	}

	return (rval);
}