diff options
-rw-r--r-- | documentation/content/en/books/arch-handbook/scsi/_index.adoc | 453 |
1 files changed, 223 insertions, 230 deletions
diff --git a/documentation/content/en/books/arch-handbook/scsi/_index.adoc b/documentation/content/en/books/arch-handbook/scsi/_index.adoc index c9a889df66..7c225d88fb 100644 --- a/documentation/content/en/books/arch-handbook/scsi/_index.adoc +++ b/documentation/content/en/books/arch-handbook/scsi/_index.adoc @@ -68,6 +68,8 @@ The SCSI Interface Module is responsible for passing these commands to the actua As we are interested in writing a SCSI adapter driver here, from this point on we will consider everything from the SIM standpoint. +== Globals and Boilerplate + A typical SIM driver needs to include the following CAM-related header files: [.programlisting] @@ -80,6 +82,8 @@ A typical SIM driver needs to include the following CAM-related header files: #include <cam/scsi/scsi_all.h> .... +== Device configuration: xxx_attach + The first thing each SIM driver must do is register itself with the CAM subsystem. This is done during the driver's `xxx_attach()` function (here and further xxx_ is used to denote the unique driver name prefix). The `xxx_attach()` function itself is called by the system bus auto-configuration code which we do not describe here. @@ -90,7 +94,7 @@ This is achieved in multiple steps: first it is necessary to allocate the queue .... struct cam_devq *devq; - if(( devq = cam_simq_alloc(SIZE) )==NULL) { + if ((devq = cam_simq_alloc(SIZE)) == NULL) { error; /* some code to handle the error */ } .... @@ -110,9 +114,9 @@ Next we create a descriptor of our SIM: .... struct cam_sim *sim; - if(( sim = cam_sim_alloc(action_func, poll_func, driver_name, + if ((sim = cam_sim_alloc(action_func, poll_func, driver_name, softc, unit, mtx, max_dev_transactions, - max_tagged_dev_transactions, devq) )==NULL) { + max_tagged_dev_transactions, devq)) == NULL) { cam_simq_free(devq); error; /* some code to handle the error */ } @@ -128,21 +132,17 @@ The answer given in the comments to the CAM code is: either way, as the driver's The arguments are: * `action_func` - pointer to the driver's `xxx_action` function. -+ -[source,c] ----- -static void - xxx_action - (); ----- + +[.programlisting] +.... +static void xxx_action(struct cam_sim *, union ccb *); +.... * `poll_func` - pointer to the driver's `xxx_poll()` + -[source,c] ----- -static void - xxx_poll - (); ----- +[.programlisting] +.... +static void xxx_poll(struct cam_sim *); +.... * driver_name - the name of the actual driver, such as "ncr" or "wds". * `softc` - pointer to the driver's internal descriptor for this SCSI card. This pointer will be used by the driver in future to get private data. @@ -164,7 +164,7 @@ Finally we register the SCSI buses associated with our SCSI adapter: [.programlisting] .... - if(xpt_bus_register(sim, softc, bus_number) != CAM_SUCCESS) { + if (xpt_bus_register(sim, softc, bus_number) != CAM_SUCCESS) { cam_sim_free(sim, /*free_devq*/ TRUE); error; /* some code to handle the error */ } @@ -192,7 +192,7 @@ So we can create the path for the future bus reset events in advance and avoid p .... struct cam_path *path; - if(xpt_create_path(&path, /*periph*/NULL, + if (xpt_create_path(&path, /*periph*/NULL, cam_sim_path(sim), CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD) != CAM_REQ_CMP) { xpt_bus_deregister(cam_sim_path(sim)); @@ -236,14 +236,12 @@ The request is passed to CAM by requesting CAM action on a CAM control block for xpt_action((union ccb *)&csa); .... -Now we take a look at the `xxx_action()` and `xxx_poll()` driver entry points. +== Processing CAM messages: xxx_action -[source,c] ----- -static void - xxx_action - (); ----- +[.programlisting] +.... +static void xxx_action(struct cam_sim *sim, union ccb *ccb); +.... Do some action on request of the CAM subsystem. Sim describes the SIM for the request, CCB is the request itself. @@ -274,7 +272,7 @@ So generally `xxx_action()` consists of a big switch: int unit = cam_sim_unit(sim); int bus = cam_sim_bus(sim); - switch(ccb_h->func_code) { + switch (ccb_h->func_code) { case ...: ... default: @@ -330,30 +328,30 @@ The recommended way of using the SIM private fields of CCB is to define some mea The most common initiator mode requests are: -* _XPT_SCSI_IO_ - execute an I/O transaction -+ +=== _XPT_SCSI_IO_ - execute an I/O transaction + The instance "struct ccb_scsiio csio" of the union ccb is used to transfer the arguments. They are: -** _cdb_io_ - pointer to the SCSI command buffer or the buffer itself -** _cdb_len_ - SCSI command length -** _data_ptr_ - pointer to the data buffer (gets a bit complicated if scatter/gather is used) -** _dxfer_len_ - length of the data to transfer -** _sglist_cnt_ - counter of the scatter/gather segments -** _scsi_status_ - place to return the SCSI status -** _sense_data_ - buffer for the SCSI sense information if the command returns an error (the SIM driver is supposed to run the REQUEST SENSE command automatically in this case if the CCB flag CAM_DIS_AUTOSENSE is not set) -** _sense_len_ - the length of that buffer (if it happens to be higher than size of sense_data the SIM driver must silently assume the smaller value) resid, sense_resid - if the transfer of data or SCSI sense returned an error these are the returned counters of the residual (not transferred) data. +* _cdb_io_ - pointer to the SCSI command buffer or the buffer itself +* _cdb_len_ - SCSI command length +* _data_ptr_ - pointer to the data buffer (gets a bit complicated if scatter/gather is used) +* _dxfer_len_ - length of the data to transfer +* _sglist_cnt_ - counter of the scatter/gather segments +* _scsi_status_ - place to return the SCSI status +* _sense_data_ - buffer for the SCSI sense information if the command returns an error (the SIM driver is supposed to run the REQUEST SENSE command automatically in this case if the CCB flag CAM_DIS_AUTOSENSE is not set) +* _sense_len_ - the length of that buffer (if it happens to be higher than size of sense_data the SIM driver must silently assume the smaller value) +* _resid_, _sense_resid_ - if the transfer of data or SCSI sense returned an error these are the returned counters of the residual (not transferred) data. They do not seem to be especially meaningful, so in a case when they are difficult to compute (say, counting bytes in the SCSI controller's FIFO buffer) an approximate value will do as well. For a successfully completed transfer they must be set to zero. -** _tag_action_ - the kind of tag to use: +* _tag_action_ - the kind of tag to use: +** CAM_TAG_ACTION_NONE - do not use tags for this transaction +** MSG_SIMPLE_Q_TAG, MSG_HEAD_OF_Q_TAG, MSG_ORDERED_Q_TAG - value equal to the appropriate tag message (see /sys/cam/scsi/scsi_message.h); this gives only the tag type, the SIM driver must assign the tag value itself -*** CAM_TAG_ACTION_NONE - do not use tags for this transaction -*** MSG_SIMPLE_Q_TAG, MSG_HEAD_OF_Q_TAG, MSG_ORDERED_Q_TAG - value equal to the appropriate tag message (see /sys/cam/scsi/scsi_message.h); this gives only the tag type, the SIM driver must assign the tag value itself -+ The general logic of handling this request is the following: -+ + The first thing to do is to check for possible races, to make sure that the command did not get aborted when it was sitting in the queue: -+ + [.programlisting] .... struct ccb_scsiio *csio = &ccb->csio; @@ -363,34 +361,34 @@ The first thing to do is to check for possible races, to make sure that the comm return; } .... -+ + Also we check that the device is supported at all by our controller: -+ + [.programlisting] .... - if(ccb_h->target_id > OUR_MAX_SUPPORTED_TARGET_ID + if (ccb_h->target_id > OUR_MAX_SUPPORTED_TARGET_ID || cch_h->target_id == OUR_SCSI_CONTROLLERS_OWN_ID) { ccb_h->status = CAM_TID_INVALID; xpt_done(ccb); return; } - if(ccb_h->target_lun > OUR_MAX_SUPPORTED_LUN) { + if (ccb_h->target_lun > OUR_MAX_SUPPORTED_LUN) { ccb_h->status = CAM_LUN_INVALID; xpt_done(ccb); return; } .... -+ + Then allocate whatever data structures (such as card-dependent hardware control block) we need to process this request. If we can not then freeze the SIM queue and remember that we have a pending operation, return the CCB back and ask CAM to re-queue it. Later when the resources become available the SIM queue must be unfrozen by returning a ccb with the `CAM_SIMQ_RELEASE` bit set in its status. Otherwise, if all went well, link the CCB with the hardware control block (HCB) and mark it as queued. -+ + [.programlisting] .... struct xxx_hcb *hcb = allocate_hcb(softc, unit, bus); - if(hcb == NULL) { + if (hcb == NULL) { softc->flags |= RESOURCE_SHORTAGE; xpt_freeze_simq(sim, /*count*/1); ccb_h->status = CAM_REQUEUE_REQ; @@ -401,34 +399,34 @@ Otherwise, if all went well, link the CCB with the hardware control block (HCB) hcb->ccb = ccb; ccb_h->ccb_hcb = (void *)hcb; ccb_h->status |= CAM_SIM_QUEUED; .... -+ + Extract the target data from CCB into the hardware control block. Check if we are asked to assign a tag and if yes then generate an unique tag and build the SCSI tag messages. The SIM driver is also responsible for negotiations with the devices to set the maximal mutually supported bus width, synchronous rate and offset. -+ + [.programlisting] .... hcb->target = ccb_h->target_id; hcb->lun = ccb_h->target_lun; generate_identify_message(hcb); - if( ccb_h->tag_action != CAM_TAG_ACTION_NONE ) + if (ccb_h->tag_action != CAM_TAG_ACTION_NONE) generate_unique_tag_message(hcb, ccb_h->tag_action); - if( !target_negotiated(hcb) ) + if (!target_negotiated(hcb)) generate_negotiation_messages(hcb); .... -+ + Then set up the SCSI command. The command storage may be specified in the CCB in many interesting ways, specified by the CCB flags. The command buffer can be contained in CCB or pointed to, in the latter case the pointer may be physical or virtual. Since the hardware commonly needs physical address we always convert the address to the physical one, typically using the busdma API. -+ + In case if a physical address is requested it is OK to return the CCB with the status `CAM_REQ_INVALID`, the current drivers do that. If necessary a physical address can be also converted or mapped back to a virtual address but with big pain, so we do not do that. -+ + [.programlisting] .... - if(ccb_h->flags & CAM_CDB_POINTER) { + if (ccb_h->flags & CAM_CDB_POINTER) { /* CDB is a pointer */ - if(!(ccb_h->flags & CAM_CDB_PHYS)) { + if (!(ccb_h->flags & CAM_CDB_PHYS)) { /* CDB pointer is virtual */ hcb->cmd = vtobus(csio->cdb_io.cdb_ptr); } else { @@ -441,12 +439,12 @@ If necessary a physical address can be also converted or mapped back to a virtua } hcb->cmdlen = csio->cdb_len; .... -+ + Now it is time to set up the data. Again, the data storage may be specified in the CCB in many interesting ways, specified by the CCB flags. First we get the direction of the data transfer. The simplest case is if there is no data to transfer: -+ + [.programlisting] .... int dir = (ccb_h->flags & CAM_DIR_MASK); @@ -454,7 +452,7 @@ The simplest case is if there is no data to transfer: if (dir == CAM_DIR_NONE) goto end_data; .... -+ + Then we check if the data is in one chunk or in a scatter-gather list, and the addresses are physical or virtual. The SCSI controller may be able to handle only a limited number of chunks of limited length. If the request hits this limitation we return an error. @@ -464,16 +462,16 @@ See description of the SCSI command (CDB) handling for the details on the addres If some variation is too difficult or impossible to implement with a particular card it is OK to return the status `CAM_REQ_INVALID`. Actually, it seems like the scatter-gather ability is not used anywhere in the CAM code now. But at least the case for a single non-scattered virtual buffer must be implemented, it is actively used by CAM. -+ + [.programlisting] .... int rv; initialize_hcb_for_data(hcb); - if((!(ccb_h->flags & CAM_SCATTER_VALID)) { + if ((!(ccb_h->flags & CAM_SCATTER_VALID)) { /* single buffer */ - if(!(ccb_h->flags & CAM_DATA_PHYS)) { + if (!(ccb_h->flags & CAM_DATA_PHYS)) { rv = add_virtual_chunk(hcb, csio->data_ptr, csio->dxfer_len, dir); } } else { @@ -505,7 +503,7 @@ But at least the case for a single non-scattered virtual buffer must be implemen } } } - if(rv != CAM_REQ_CMP) { + if (rv != CAM_REQ_CMP) { /* we expect that add_*_chunk() functions return CAM_REQ_CMP * if they added a chunk successfully, CAM_REQ_TOO_BIG if * the request is too big (too many bytes or too many chunks), @@ -516,19 +514,19 @@ But at least the case for a single non-scattered virtual buffer must be implemen } end_data: .... -+ + If disconnection is disabled for this CCB we pass this information to the hcb: -+ + [.programlisting] .... - if(ccb_h->flags & CAM_DIS_DISCONNECT) + if (ccb_h->flags & CAM_DIS_DISCONNECT) hcb_disable_disconnect(hcb); .... -+ + If the controller is able to run REQUEST SENSE command all by itself then the value of the flag CAM_DIS_AUTOSENSE should also be passed to it, to prevent automatic REQUEST SENSE if the CAM subsystem does not want it. -+ + The only thing left is to set up the timeout, pass our hcb to the hardware and return, the rest will be done by the interrupt handler (or timeout handler). -+ + [.programlisting] .... ccb_h->timeout_ch = timeout(xxx_timeout, (caddr_t) hcb, @@ -536,9 +534,9 @@ The only thing left is to set up the timeout, pass our hcb to the hardware and r put_hcb_into_hardware_queue(hcb); return; .... -+ + And here is a possible implementation of the function returning CCB: -+ + [.programlisting] .... static void @@ -547,10 +545,10 @@ And here is a possible implementation of the function returning CCB: struct xxx_softc *softc = hcb->softc; ccb->ccb_h.ccb_hcb = 0; - if(hcb != NULL) { + if (hcb != NULL) { untimeout(xxx_timeout, (caddr_t) hcb, ccb->ccb_h.timeout_ch); /* we're about to free a hcb, so the shortage has ended */ - if(softc->flags & RESOURCE_SHORTAGE) { + if (softc->flags & RESOURCE_SHORTAGE) { softc->flags &= ~RESOURCE_SHORTAGE; status |= CAM_RELEASE_SIMQ; } @@ -562,23 +560,23 @@ And here is a possible implementation of the function returning CCB: } .... -* _XPT_RESET_DEV_ - send the SCSI "BUS DEVICE RESET" message to a device -+ +=== _XPT_RESET_DEV_ - send the SCSI "BUS DEVICE RESET" message to a device + There is no data transferred in CCB except the header and the most interesting argument of it is target_id. Depending on the controller hardware a hardware control block just like for the XPT_SCSI_IO request may be constructed (see XPT_SCSI_IO request description) and sent to the controller or the SCSI controller may be immediately programmed to send this RESET message to the device or this request may be just not supported (and return the status `CAM_REQ_INVALID`). Also on completion of the request all the disconnected transactions for this target must be aborted (probably in the interrupt routine). -+ + Also all the current negotiations for the target are lost on reset, so they might be cleaned too. Or they clearing may be deferred, because anyway the target would request re-negotiation on the next transaction. -* _XPT_RESET_BUS_ - send the RESET signal to the SCSI bus -+ + +=== _XPT_RESET_BUS_ - send the RESET signal to the SCSI bus + No arguments are passed in the CCB, the only interesting argument is the SCSI bus indicated by the struct sim pointer. -+ + A minimalistic implementation would forget the SCSI negotiations for all the devices on the bus and return the status CAM_REQ_CMP. -+ + The proper implementation would in addition actually reset the SCSI bus (possible also reset the SCSI controller) and mark all the CCBs being processed, both those in the hardware queue and those being disconnected, as done with the status CAM_SCSI_BUS_RESET. Like: -+ [.programlisting] .... int targ, lun; @@ -593,7 +591,7 @@ Like: reset_scsi_bus(softc); /* drop all enqueued CCBs */ - for(h = softc->first_queued_hcb; h != NULL; h = hh) { + for (h = softc->first_queued_hcb; h != NULL; h = hh) { hh = h->next; free_hcb_and_ccb_done(h, h->ccb, CAM_SCSI_BUS_RESET); } @@ -605,19 +603,19 @@ Like: | CCB_TRANS_SYNC_RATE_VALID | CCB_TRANS_SYNC_OFFSET_VALID); /* drop all disconnected CCBs and clean negotiations */ - for(targ=0; targ <= OUR_MAX_SUPPORTED_TARGET; targ++) { + for (targ=0; targ <= OUR_MAX_SUPPORTED_TARGET; targ++) { clean_negotiations(softc, targ); /* report the event if possible */ - if(xpt_create_path(&path, /*periph*/NULL, + if (xpt_create_path(&path, /*periph*/NULL, cam_sim_path(sim), targ, CAM_LUN_WILDCARD) == CAM_REQ_CMP) { xpt_async(AC_TRANSFER_NEG, path, &neg); xpt_free_path(path); } - for(lun=0; lun <= OUR_MAX_SUPPORTED_LUN; lun++) - for(h = softc->first_discon_hcb[targ][lun]; h != NULL; h = hh) { + for (lun=0; lun <= OUR_MAX_SUPPORTED_LUN; lun++) + for (h = softc->first_discon_hcb[targ][lun]; h != NULL; h = hh) { hh=h->next; free_hcb_and_ccb_done(h, h->ccb, CAM_SCSI_BUS_RESET); } @@ -630,37 +628,37 @@ Like: xpt_async(AC_BUS_RESET, softc->wpath, NULL); return; .... -+ + Implementing the SCSI bus reset as a function may be a good idea because it would be re-used by the timeout function as a last resort if the things go wrong. -* _XPT_ABORT_ - abort the specified CCB -+ + +=== _XPT_ABORT_ - abort the specified CCB + The arguments are transferred in the instance "struct ccb_abort cab" of the union ccb. The only argument field in it is: -+ -_abort_ccb_ - pointer to the CCB to be aborted -+ + +* _abort_ccb_ - pointer to the CCB to be aborted + If the abort is not supported just return the status CAM_UA_ABORT. This is also the easy way to minimally implement this call, return CAM_UA_ABORT in any case. -+ + The hard way is to implement this request honestly. First check that abort applies to a SCSI transaction: -+ + [.programlisting] .... struct ccb *abort_ccb; abort_ccb = ccb->cab.abort_ccb; - if(abort_ccb->ccb_h.func_code != XPT_SCSI_IO) { + if (abort_ccb->ccb_h.func_code != XPT_SCSI_IO) { ccb->ccb_h.status = CAM_UA_ABORT; xpt_done(ccb); return; } .... -+ Then it is necessary to find this CCB in our queue. This can be done by walking the list of all our hardware control blocks in search for one associated with this CCB: -+ + [.programlisting] .... struct xxx_hcb *hcb, *h; @@ -671,14 +669,14 @@ This can be done by walking the list of all our hardware control blocks in searc * HCBs associated with this bus, including those enqueued for * processing, being processed by hardware and disconnected ones. */ - for(h = softc->first_hcb; h != NULL; h = h->next) { - if(h->ccb == abort_ccb) { + for (h = softc->first_hcb; h != NULL; h = h->next) { + if (h->ccb == abort_ccb) { hcb = h; break; } } - if(hcb == NULL) { + if (hcb == NULL) { /* no such CCB in our queue */ ccb->ccb_h.status = CAM_PATH_INVALID; xpt_done(ccb); @@ -687,11 +685,11 @@ This can be done by walking the list of all our hardware control blocks in searc hcb=found_hcb; .... -+ + Now we look at the current processing status of the HCB. It may be either sitting in the queue waiting to be sent to the SCSI bus, being transferred right now, or disconnected and waiting for the result of the command, or actually completed by hardware but not yet marked as done by software. To make sure that we do not get in any races with hardware we mark the HCB as being aborted, so that if this HCB is about to be sent to the SCSI bus the SCSI controller will see this flag and skip it. -+ + [.programlisting] .... int hstatus; @@ -704,7 +702,7 @@ To make sure that we do not get in any races with hardware we mark the HCB as be abort_again: hstatus = get_hcb_status(hcb); - switch(hstatus) { + switch (hstatus) { case HCB_SITTING_IN_QUEUE: remove_hcb_from_hardware_queue(hcb); /* FALLTHROUGH */ @@ -713,13 +711,13 @@ To make sure that we do not get in any races with hardware we mark the HCB as be free_hcb_and_ccb_done(hcb, abort_ccb, CAM_REQ_ABORTED); break; .... -+ + If the CCB is being transferred right now we would like to signal to the SCSI controller in some hardware-dependent way that we want to abort the current transfer. The SCSI controller would set the SCSI ATTENTION signal and when the target responds to it send an ABORT message. We also reset the timeout to make sure that the target is not sleeping forever. If the command would not get aborted in some reasonable time like 10 seconds the timeout routine would go ahead and reset the whole SCSI bus. Since the command will be aborted in some reasonable time we can just return the abort request now as successfully completed, and mark the aborted CCB as aborted (but not mark it as done yet). -+ + [.programlisting] .... case HCB_BEING_TRANSFERRED: @@ -730,7 +728,7 @@ Since the command will be aborted in some reasonable time we can just return the /* ask the controller to abort that HCB, then generate * an interrupt and stop */ - if(signal_hardware_to_abort_hcb_and_stop(hcb) < 0) { + if (signal_hardware_to_abort_hcb_and_stop(hcb) < 0) { /* oops, we missed the race with hardware, this transaction * got off the bus before we aborted it, try again */ goto abort_again; @@ -738,10 +736,10 @@ Since the command will be aborted in some reasonable time we can just return the break; .... -+ + If the CCB is in the list of disconnected then set it up as an abort request and re-queue it at the front of hardware queue. Reset the timeout and report the abort request to be completed. -+ + [.programlisting] .... case HCB_DISCONNECTED: @@ -756,27 +754,27 @@ Reset the timeout and report the abort request to be completed. xpt_done(ccb); return; .... -+ + That is all for the ABORT request, although there is one more issue. As the ABORT message cleans all the ongoing transactions on a LUN we have to mark all the other active transactions on this LUN as aborted. That should be done in the interrupt routine, after the transaction gets aborted. -+ + Implementing the CCB abort as a function may be quite a good idea, this function can be re-used if an I/O transaction times out. The only difference would be that the timed out transaction would return the status CAM_CMD_TIMEOUT for the timed out request. Then the case XPT_ABORT would be small, like that: -+ + [.programlisting] .... case XPT_ABORT: struct ccb *abort_ccb; abort_ccb = ccb->cab.abort_ccb; - if(abort_ccb->ccb_h.func_code != XPT_SCSI_IO) { + if (abort_ccb->ccb_h.func_code != XPT_SCSI_IO) { ccb->ccb_h.status = CAM_UA_ABORT; xpt_done(ccb); return; } - if(xxx_abort_ccb(abort_ccb, CAM_REQ_ABORTED) < 0) + if (xxx_abort_ccb(abort_ccb, CAM_REQ_ABORTED) < 0) /* no such CCB in our queue */ ccb->ccb_h.status = CAM_PATH_INVALID; else @@ -785,47 +783,44 @@ Then the case XPT_ABORT would be small, like that: return; .... -* _XPT_SET_TRAN_SETTINGS_ - explicitly set values of SCSI transfer settings -+ +=== _XPT_SET_TRAN_SETTINGS_ - explicitly set values of SCSI transfer settings + The arguments are transferred in the instance "struct ccb_trans_setting cts" of the union ccb: -** _valid_ - a bitmask showing which settings should be updated: +* _valid_ - a bitmask showing which settings should be updated: ** _CCB_TRANS_SYNC_RATE_VALID_ - synchronous transfer rate ** _CCB_TRANS_SYNC_OFFSET_VALID_ - synchronous offset ** _CCB_TRANS_BUS_WIDTH_VALID_ - bus width ** _CCB_TRANS_DISC_VALID_ - set enable/disable disconnection ** _CCB_TRANS_TQ_VALID_ - set enable/disable tagged queuing -** _flags_ - consists of two parts, binary arguments and identification of sub-operations. +* _flags_ - consists of two parts, binary arguments and identification of sub-operations. The binary arguments are: +** _CCB_TRANS_DISC_ENB_ - enable disconnection +** _CCB_TRANS_TAG_ENB_ - enable tagged queuing +* the sub-operations are: +** _CCB_TRANS_CURRENT_SETTINGS_ - change the current negotiations +** _CCB_TRANS_USER_SETTINGS_ - remember the desired user values sync_period, sync_offset - self-explanatory, if sync_offset==0 then the asynchronous mode is requested bus_width - bus width, in bits (not bytes) -*** _CCB_TRANS_DISC_ENB_ - enable disconnection -*** _CCB_TRANS_TAG_ENB_ - enable tagged queuing - -** the sub-operations are: - -*** _CCB_TRANS_CURRENT_SETTINGS_ - change the current negotiations -*** _CCB_TRANS_USER_SETTINGS_ - remember the desired user values sync_period, sync_offset - self-explanatory, if sync_offset==0 then the asynchronous mode is requested bus_width - bus width, in bits (not bytes) -+ Two sets of negotiated parameters are supported, the user settings and the current settings. The user settings are not really used much in the SIM drivers, this is mostly just a piece of memory where the upper levels can store (and later recall) its ideas about the parameters. Setting the user parameters does not cause re-negotiation of the transfer rates. But when the SCSI controller does a negotiation it must never set the values higher than the user parameters, so it is essentially the top boundary. -+ + The current settings are, as the name says, current. Changing them means that the parameters must be re-negotiated on the next transfer. Again, these "new current settings" are not supposed to be forced on the device, just they are used as the initial step of negotiations. Also they must be limited by actual capabilities of the SCSI controller: for example, if the SCSI controller has 8-bit bus and the request asks to set 16-bit wide transfers this parameter must be silently truncated to 8-bit transfers before sending it to the device. -+ + One caveat is that the bus width and synchronous parameters are per target while the disconnection and tag enabling parameters are per lun. -+ + The recommended implementation is to keep 3 sets of negotiated (bus width and synchronous transfer) parameters: -** _user_ - the user set, as above -** _current_ - those actually in effect -** _goal_ - those requested by setting of the "current" parameters -+ +* _user_ - the user set, as above +* _current_ - those actually in effect +* _goal_ - those requested by setting of the "current" parameters + The code looks like: -+ + [.programlisting] .... struct ccb_trans_settings *cts; @@ -836,38 +831,38 @@ The code looks like: targ = ccb_h->target_id; lun = ccb_h->target_lun; flags = cts->flags; - if(flags & CCB_TRANS_USER_SETTINGS) { - if(flags & CCB_TRANS_SYNC_RATE_VALID) + if (flags & CCB_TRANS_USER_SETTINGS) { + if (flags & CCB_TRANS_SYNC_RATE_VALID) softc->user_sync_period[targ] = cts->sync_period; - if(flags & CCB_TRANS_SYNC_OFFSET_VALID) + if (flags & CCB_TRANS_SYNC_OFFSET_VALID) softc->user_sync_offset[targ] = cts->sync_offset; - if(flags & CCB_TRANS_BUS_WIDTH_VALID) + if (flags & CCB_TRANS_BUS_WIDTH_VALID) softc->user_bus_width[targ] = cts->bus_width; - if(flags & CCB_TRANS_DISC_VALID) { + if (flags & CCB_TRANS_DISC_VALID) { softc->user_tflags[targ][lun] &= ~CCB_TRANS_DISC_ENB; softc->user_tflags[targ][lun] |= flags & CCB_TRANS_DISC_ENB; } - if(flags & CCB_TRANS_TQ_VALID) { + if (flags & CCB_TRANS_TQ_VALID) { softc->user_tflags[targ][lun] &= ~CCB_TRANS_TQ_ENB; softc->user_tflags[targ][lun] |= flags & CCB_TRANS_TQ_ENB; } } - if(flags & CCB_TRANS_CURRENT_SETTINGS) { - if(flags & CCB_TRANS_SYNC_RATE_VALID) + if (flags & CCB_TRANS_CURRENT_SETTINGS) { + if (flags & CCB_TRANS_SYNC_RATE_VALID) softc->goal_sync_period[targ] = max(cts->sync_period, OUR_MIN_SUPPORTED_PERIOD); - if(flags & CCB_TRANS_SYNC_OFFSET_VALID) + if (flags & CCB_TRANS_SYNC_OFFSET_VALID) softc->goal_sync_offset[targ] = min(cts->sync_offset, OUR_MAX_SUPPORTED_OFFSET); - if(flags & CCB_TRANS_BUS_WIDTH_VALID) + if (flags & CCB_TRANS_BUS_WIDTH_VALID) softc->goal_bus_width[targ] = min(cts->bus_width, OUR_BUS_WIDTH); - if(flags & CCB_TRANS_DISC_VALID) { + if (flags & CCB_TRANS_DISC_VALID) { softc->current_tflags[targ][lun] &= ~CCB_TRANS_DISC_ENB; softc->current_tflags[targ][lun] |= flags & CCB_TRANS_DISC_ENB; } - if(flags & CCB_TRANS_TQ_VALID) { + if (flags & CCB_TRANS_TQ_VALID) { softc->current_tflags[targ][lun] &= ~CCB_TRANS_TQ_ENB; softc->current_tflags[targ][lun] |= flags & CCB_TRANS_TQ_ENB; } @@ -876,10 +871,10 @@ The code looks like: xpt_done(ccb); return; .... -+ + Then when the next I/O request will be processed it will check if it has to re-negotiate, for example by calling the function target_negotiated(hcb). It can be implemented like this: -+ + [.programlisting] .... int @@ -888,37 +883,37 @@ It can be implemented like this: struct softc *softc = hcb->softc; int targ = hcb->targ; - if( softc->current_sync_period[targ] != softc->goal_sync_period[targ] + if (softc->current_sync_period[targ] != softc->goal_sync_period[targ] || softc->current_sync_offset[targ] != softc->goal_sync_offset[targ] - || softc->current_bus_width[targ] != softc->goal_bus_width[targ] ) + || softc->current_bus_width[targ] != softc->goal_bus_width[targ]) return 0; /* FALSE */ else return 1; /* TRUE */ } .... -+ + After the values are re-negotiated the resulting values must be assigned to both current and goal parameters, so for future I/O transactions the current and goal parameters would be the same and `target_negotiated()` would return TRUE. When the card is initialized (in `xxx_attach()`) the current negotiation values must be initialized to narrow asynchronous mode, the goal and current values must be initialized to the maximal values supported by controller. -+ -_XPT_GET_TRAN_SETTINGS_ - get values of SCSI transfer settings -+ + +=== _XPT_GET_TRAN_SETTINGS_ - get values of SCSI transfer settings + This operations is the reverse of XPT_SET_TRAN_SETTINGS. Fill up the CCB instance "struct ccb_trans_setting cts" with data as requested by the flags CCB_TRANS_CURRENT_SETTINGS or CCB_TRANS_USER_SETTINGS (if both are set then the existing drivers return the current settings). Set all the bits in the valid field. -+ -_XPT_CALC_GEOMETRY_ - calculate logical (BIOS) geometry of the disk -+ + +=== _XPT_CALC_GEOMETRY_ - calculate logical (BIOS) geometry of the disk + The arguments are transferred in the instance "struct ccb_calc_geometry ccg" of the union ccb: -** _block_size_ - input, block (A.K.A sector) size in bytes -** _volume_size_ - input, volume size in bytes -** _cylinders_ - output, logical cylinders -** _heads_ - output, logical heads -** _secs_per_track_ - output, logical sectors per track -+ +* _block_size_ - input, block (A.K.A sector) size in bytes +* _volume_size_ - input, volume size in bytes +* _cylinders_ - output, logical cylinders +* _heads_ - output, logical heads +* _secs_per_track_ - output, logical sectors per track + If the returned geometry differs much enough from what the SCSI controller BIOS thinks and a disk on this SCSI controller is used as bootable the system may not be able to boot. The typical calculation example taken from the aic7xxx driver is: -+ + [.programlisting] .... struct ccb_calc_geometry *ccg; @@ -944,24 +939,25 @@ The typical calculation example taken from the aic7xxx driver is: xpt_done(ccb); return; .... -+ + This gives the general idea, the exact calculation depends on the quirks of the particular BIOS. If BIOS provides no way set the "extended translation" flag in EEPROM this flag should normally be assumed equal to 1. Other popular geometries are: -+ + [.programlisting] .... 128 heads, 63 sectors - Symbios controllers 16 heads, 63 sectors - old controllers .... -+ + Some system BIOSes and SCSI BIOSes fight with each other with variable success, for example a combination of Symbios 875/895 SCSI and Phoenix BIOS can give geometry 128/63 after power up and 255/63 after a hard reset or soft reboot. -* _XPT_PATH_INQ_ - path inquiry, in other words get the SIM driver and SCSI controller (also known as HBA - Host Bus Adapter) properties -+ + +=== _XPT_PATH_INQ_ - path inquiry, in other words get the SIM driver and SCSI controller (also known as HBA - Host Bus Adapter) properties + The properties are returned in the instance "struct ccb_pathinq cpi" of the union ccb: -** version_num - the SIM driver version number, now all drivers use 1 -** hba_inquiry - bitmask of features supported by the controller: +* version_num - the SIM driver version number, now all drivers use 1 +* hba_inquiry - bitmask of features supported by the controller: ** PI_MDP_ABLE - supports MDP message (something from SCSI3?) ** PI_WIDE_32 - supports 32 bit wide SCSI ** PI_WIDE_16 - supports 16 bit wide SCSI @@ -969,44 +965,42 @@ The properties are returned in the instance "struct ccb_pathinq cpi" of the unio ** PI_LINKED_CDB - supports linked commands ** PI_TAG_ABLE - supports tagged commands ** PI_SOFT_RST - supports soft reset alternative (hard reset and soft reset are mutually exclusive within a SCSI bus) -** target_sprt - flags for target mode support, 0 if unsupported -** hba_misc - miscellaneous controller features: +* target_sprt - flags for target mode support, 0 if unsupported +* hba_misc - miscellaneous controller features: ** PIM_SCANHILO - bus scans from high ID to low ID ** PIM_NOREMOVE - removable devices not included in scan ** PIM_NOINITIATOR - initiator role not supported ** PIM_NOBUSRESET - user has disabled initial BUS RESET -** hba_eng_cnt - mysterious HBA engine count, something related to compression, now is always set to 0 -** vuhba_flags - vendor-unique flags, unused now -** max_target - maximal supported target ID (7 for 8-bit bus, 15 for 16-bit bus, 127 for Fibre Channel) -** max_lun - maximal supported LUN ID (7 for older SCSI controllers, 63 for newer ones) -** async_flags - bitmask of installed Async handler, unused now -** hpath_id - highest Path ID in the subsystem, unused now -** unit_number - the controller unit number, cam_sim_unit(sim) -** bus_id - the bus number, cam_sim_bus(sim) -** initiator_id - the SCSI ID of the controller itself -** base_transfer_speed - nominal transfer speed in KB/s for asynchronous narrow transfers, equals to 3300 for SCSI -** sim_vid - SIM driver's vendor id, a zero-terminated string of maximal length SIM_IDLEN including the terminating zero -** hba_vid - SCSI controller's vendor id, a zero-terminated string of maximal length HBA_IDLEN including the terminating zero -** dev_name - device driver name, a zero-terminated string of maximal length DEV_IDLEN including the terminating zero, equal to cam_sim_name(sim) -+ +* hba_eng_cnt - mysterious HBA engine count, something related to compression, now is always set to 0 +* vuhba_flags - vendor-unique flags, unused now +* max_target - maximal supported target ID (7 for 8-bit bus, 15 for 16-bit bus, 127 for Fibre Channel) +* max_lun - maximal supported LUN ID (7 for older SCSI controllers, 63 for newer ones) +* async_flags - bitmask of installed Async handler, unused now +* hpath_id - highest Path ID in the subsystem, unused now +* unit_number - the controller unit number, cam_sim_unit(sim) +* bus_id - the bus number, cam_sim_bus(sim) +* initiator_id - the SCSI ID of the controller itself +* base_transfer_speed - nominal transfer speed in KB/s for asynchronous narrow transfers, equals to 3300 for SCSI +* sim_vid - SIM driver's vendor id, a zero-terminated string of maximal length SIM_IDLEN including the terminating zero +* hba_vid - SCSI controller's vendor id, a zero-terminated string of maximal length HBA_IDLEN including the terminating zero +* dev_name - device driver name, a zero-terminated string of maximal length DEV_IDLEN including the terminating zero, equal to cam_sim_name(sim) + The recommended way of setting the string fields is using strncpy, like: -+ + [.programlisting] .... strncpy(cpi->dev_name, cam_sim_name(sim), DEV_IDLEN); .... -+ + After setting the values set the status to CAM_REQ_CMP and mark the CCB as done. [[scsi-polling]] -== Polling +== Polling xxx_poll -[source,c] ----- -static void - xxx_poll - (); ----- +[.programlisting] +.... +static void xxx_poll(struct cam_sim *); +.... The poll function is used to simulate the interrupts when the interrupt subsystem is not functioning (for example, when the system has crashed and is creating the system dump). The CAM subsystem sets the proper interrupt level before calling the poll routine. @@ -1066,7 +1060,7 @@ Implementation for a single type of event, AC_LOST_DEVICE, looks like: switch (code) { case AC_LOST_DEVICE: targ = xpt_path_target_id(path); - if(targ <= OUR_MAX_SUPPORTED_TARGET) { + if (targ <= OUR_MAX_SUPPORTED_TARGET) { clean_negotiations(softc, targ); /* send indication to CAM */ neg.bus_width = 8; @@ -1128,8 +1122,8 @@ The case of fatal controller error/hang could be handled in the same place, but struct ccb_trans_settings neg; struct cam_path *path; - if( detected_scsi_reset(softc) - || (fatal = detected_fatal_controller_error(softc)) ) { + if (detected_scsi_reset(softc) + || (fatal = detected_fatal_controller_error(softc))) { int targ, lun; struct xxx_hcb *h, *hh; @@ -1146,21 +1140,21 @@ The case of fatal controller error/hang could be handled in the same place, but | CCB_TRANS_SYNC_RATE_VALID | CCB_TRANS_SYNC_OFFSET_VALID); /* drop all disconnected CCBs and clean negotiations */ - for(targ=0; targ <= OUR_MAX_SUPPORTED_TARGET; targ++) { + for (targ=0; targ <= OUR_MAX_SUPPORTED_TARGET; targ++) { clean_negotiations(softc, targ); /* report the event if possible */ - if(xpt_create_path(&path, /*periph*/NULL, + if (xpt_create_path(&path, /*periph*/NULL, cam_sim_path(sim), targ, CAM_LUN_WILDCARD) == CAM_REQ_CMP) { xpt_async(AC_TRANSFER_NEG, path, &neg); xpt_free_path(path); } - for(lun=0; lun <= OUR_MAX_SUPPORTED_LUN; lun++) - for(h = softc->first_discon_hcb[targ][lun]; h != NULL; h = hh) { + for (lun=0; lun <= OUR_MAX_SUPPORTED_LUN; lun++) + for (h = softc->first_discon_hcb[targ][lun]; h != NULL; h = hh) { hh=h->next; - if(fatal) + if (fatal) free_hcb_and_ccb_done(h, h->ccb, CAM_UNREC_HBA_ERROR); else free_hcb_and_ccb_done(h, h->ccb, CAM_SCSI_BUS_RESET); @@ -1175,7 +1169,7 @@ The case of fatal controller error/hang could be handled in the same place, but * checked on timeout - but for simplicity we assume here that * it is really fast */ - if(!fatal) { + if (!fatal) { reinitialize_controller_without_scsi_reset(softc); } else { reinitialize_controller_with_scsi_reset(softc); @@ -1198,7 +1192,7 @@ Then we analyze what happened to this HCB: int lun_to_freeze; hcb = get_current_hcb(softc); - if(hcb == NULL) { + if (hcb == NULL) { /* either stray interrupt or something went very wrong * or this is something hardware-dependent */ @@ -1214,7 +1208,7 @@ First we check if the HCB has completed and if so we check the returned SCSI sta [.programlisting] .... - if(hcb_status == COMPLETED) { + if (hcb_status == COMPLETED) { scsi_status = get_completion_status(hcb); .... @@ -1222,8 +1216,8 @@ Then look if this status is related to the REQUEST SENSE command and if so handl [.programlisting] .... - if(hcb->flags & DOING_AUTOSENSE) { - if(scsi_status == GOOD) { /* autosense was successful */ + if (hcb->flags & DOING_AUTOSENSE) { + if (scsi_status == GOOD) { /* autosense was successful */ hcb->ccb->ccb_h.status |= CAM_AUTOSNS_VALID; free_hcb_and_ccb_done(hcb, hcb->ccb, CAM_SCSI_STATUS_ERROR); } else { @@ -1243,16 +1237,16 @@ If auto-sense is not disabled for this CCB and the command has failed with sense hcb->ccb->csio.scsi_status = scsi_status; calculate_residue(hcb); - if( (hcb->ccb->ccb_h.flags & CAM_DIS_AUTOSENSE)==0 - && ( scsi_status == CHECK_CONDITION - || scsi_status == COMMAND_TERMINATED) ) { + if ((hcb->ccb->ccb_h.flags & CAM_DIS_AUTOSENSE)==0 + && (scsi_status == CHECK_CONDITION + || scsi_status == COMMAND_TERMINATED)) { /* start auto-SENSE */ hcb->flags |= DOING_AUTOSENSE; setup_autosense_command_in_hcb(hcb); restart_current_hcb(softc); return; } - if(scsi_status == GOOD) + if (scsi_status == GOOD) free_hcb_and_ccb_done(hcb, hcb->ccb, CAM_REQ_CMP); else free_hcb_and_ccb_done(hcb, hcb->ccb, CAM_SCSI_STATUS_ERROR); @@ -1265,7 +1259,7 @@ One typical thing would be negotiation events: negotiation messages received fro [.programlisting] .... - switch(hcb_status) { + switch (hcb_status) { case TARGET_REJECTED_WIDE_NEG: /* revert to 8-bit bus */ softc->current_bus_width[targ] = softc->goal_bus_width[targ] = 8; @@ -1280,7 +1274,7 @@ One typical thing would be negotiation events: negotiation messages received fro int wd; wd = get_target_bus_width_request(softc); - if(wd <= softc->goal_bus_width[targ]) { + if (wd <= softc->goal_bus_width[targ]) { /* answer is acceptable */ softc->current_bus_width[targ] = softc->goal_bus_width[targ] = neg.bus_width = wd; @@ -1302,7 +1296,7 @@ One typical thing would be negotiation events: negotiation messages received fro wd = min (wd, OUR_BUS_WIDTH); wd = min (wd, softc->user_bus_width[targ]); - if(wd != softc->current_bus_width[targ]) { + if (wd != softc->current_bus_width[targ]) { /* the bus width has changed */ softc->current_bus_width[targ] = softc->goal_bus_width[targ] = neg.bus_width = wd; @@ -1323,10 +1317,10 @@ Otherwise we look closer at the details again. [.programlisting] .... - if(hcb->flags & DOING_AUTOSENSE) + if (hcb->flags & DOING_AUTOSENSE) goto autosense_failed; - switch(hcb_status) { + switch (hcb_status) { .... The next event we consider is unexpected disconnect. @@ -1335,25 +1329,25 @@ Which is considered normal after an ABORT or BUS DEVICE RESET message and abnorm [.programlisting] .... case UNEXPECTED_DISCONNECT: - if(requested_abort(hcb)) { + if (requested_abort(hcb)) { /* abort affects all commands on that target+LUN, so * mark all disconnected HCBs on that target+LUN as aborted too */ - for(h = softc->first_discon_hcb[hcb->target][hcb->lun]; + for (h = softc->first_discon_hcb[hcb->target][hcb->lun]; h != NULL; h = hh) { hh=h->next; free_hcb_and_ccb_done(h, h->ccb, CAM_REQ_ABORTED); } ccb_status = CAM_REQ_ABORTED; - } else if(requested_bus_device_reset(hcb)) { + } else if (requested_bus_device_reset(hcb)) { int lun; /* reset affects all commands on that target, so * mark all disconnected HCBs on that target+LUN as reset */ - for(lun=0; lun <= OUR_MAX_SUPPORTED_LUN; lun++) - for(h = softc->first_discon_hcb[hcb->target][lun]; + for (lun=0; lun <= OUR_MAX_SUPPORTED_LUN; lun++) + for (h = softc->first_discon_hcb[hcb->target][lun]; h != NULL; h = hh) { hh=h->next; free_hcb_and_ccb_done(h, h->ccb, CAM_SCSI_BUS_RESET); @@ -1422,17 +1416,17 @@ Then we check if the error was serious enough to freeze the input queue until it [.programlisting] .... - if(hcb->ccb->ccb_h.status & CAM_DEV_QFRZN) { + if (hcb->ccb->ccb_h.status & CAM_DEV_QFRZN) { /* freeze the queue */ xpt_freeze_devq(ccb->ccb_h.path, /*count*/1); /* re-queue all commands for this target/LUN back to CAM */ - for(h = softc->first_queued_hcb; h != NULL; h = hh) { + for (h = softc->first_queued_hcb; h != NULL; h = hh) { hh = h->next; - if(targ == h->targ - && (lun_to_freeze == CAM_LUN_WILDCARD || lun_to_freeze == h->lun) ) + if (targ == h->targ + && (lun_to_freeze == CAM_LUN_WILDCARD || lun_to_freeze == h->lun)) free_hcb_and_ccb_done(h, h->ccb, CAM_REQUEUE_REQ); } } @@ -1495,8 +1489,7 @@ xxx_timeout(void *arg) softc = hcb->softc; ccb_h = &hcb->ccb->ccb_h; - if(hcb->flags & HCB_BEING_ABORTED - || ccb_h->func_code == XPT_RESET_DEV) { + if (hcb->flags & HCB_BEING_ABORTED || ccb_h->func_code == XPT_RESET_DEV) { xxx_reset_bus(softc); } else { xxx_abort_ccb(hcb->ccb, CAM_CMD_TIMEOUT); |