diff --git a/en_US.ISO8859-1/books/arch-handbook/book.sgml b/en_US.ISO8859-1/books/arch-handbook/book.sgml index 1006c9f5d2..2cf7d725aa 100644 --- a/en_US.ISO8859-1/books/arch-handbook/book.sgml +++ b/en_US.ISO8859-1/books/arch-handbook/book.sgml @@ -1,7 +1,7 @@ <!-- The FreeBSD Documentation Project - $FreeBSD: doc/en_US.ISO_8859-1/books/developers-handbook/book.sgml,v 1.10 2000/11/28 19:07:40 asmodai Exp $ + $FreeBSD: doc/en_US.ISO_8859-1/books/developers-handbook/book.sgml,v 1.11 2000/11/29 04:15:17 jhb Exp $ --> <!DOCTYPE BOOK PUBLIC "-//FreeBSD//DTD DocBook V3.1-Based Extension//EN" [ @@ -10,6 +10,7 @@ <!ENTITY % man PUBLIC "-//FreeBSD//ENTITIES DocBook Manual Page Entities//EN"> %man; <!ENTITY % chapters SYSTEM "chapters.ent"> %chapters; +<!ENTITY % authors SYSTEM "../handbook/authors.ent"> %authors; ]> <book> @@ -31,6 +32,7 @@ <copyright> <year>2000</year> + <year>2001</year> <holder>The FreeBSD Documentation Project</holder> </copyright> @@ -319,6 +321,7 @@ &chap.driverbasics; &chap.pci; + &chap.scsi; <chapter id="usb"> <title>USB Devices</title> diff --git a/en_US.ISO8859-1/books/arch-handbook/chapters.ent b/en_US.ISO8859-1/books/arch-handbook/chapters.ent index c2149880e2..a367f40239 100644 --- a/en_US.ISO8859-1/books/arch-handbook/chapters.ent +++ b/en_US.ISO8859-1/books/arch-handbook/chapters.ent @@ -6,7 +6,7 @@ Chapters should be listed in the order in which they are referenced. - $FreeBSD: doc/en_US.ISO_8859-1/books/developers-handbook/chapters.ent,v 1.1 2000/11/28 18:03:54 asmodai Exp $ + $FreeBSD: doc/en_US.ISO_8859-1/books/developers-handbook/chapters.ent,v 1.2 2000/11/29 04:15:17 jhb Exp $ --> <!-- Part one --> @@ -44,6 +44,7 @@ <!-- Part eleven --> <!ENTITY chap.driverbasics SYSTEM "driverbasics/chapter.sgml"> <!ENTITY chap.pci SYSTEM "pci/chapter.sgml"> +<!ENTITY chap.scsi SYSTEM "scsi/chapter.sgml"> <!-- Part twelve --> <!-- No significant material yet, still in book.sgml --> diff --git a/en_US.ISO8859-1/books/arch-handbook/scsi/chapter.sgml b/en_US.ISO8859-1/books/arch-handbook/scsi/chapter.sgml new file mode 100644 index 0000000000..837a8fd60d --- /dev/null +++ b/en_US.ISO8859-1/books/arch-handbook/scsi/chapter.sgml @@ -0,0 +1,2079 @@ +<!-- + The FreeBSD Documentation Project + + $FreeBSD$ +--> + +<chapter id="scsi"> + <title>Common Access Method SCSI Controllers</title> + + <para><emphasis>This chapter was written by &a.babkin; + Modifications for the handbook made by + &a.murray;.</emphasis></para> + + <sect1> + <title>Synopsis</title> + + <para>This document assumes that the reader has a general + understanding of device drivers in FreeBSD and of the SCSI + protocol. Much of the information in this document was + extracted from the drivers :</para> + + <itemizedlist> + + <listitem><para>ncr (<filename>/sys/pci/ncr.c</filename>) by + Wolfgang Stanglmeier and Stefan Esser</para></listitem> + + <listitem><para>sym (<filename>/sys/pci/sym.c</filename>) by + Gerard Roudier</para></listitem> + + <listitem><para>aic7xxx + (<filename>/sys/dev/aic7xxx/aic7xxx.c</filename>) by Justin + T. Gibbs</listitem> + + </itemizedlist> + + <para>and from the CAM code itself (by Justing T. Gibbs, see + <filename>/sys/cam/*</filename>). When some solution looked the + most logical and was essentially verbatim extracted from the code + by Justin Gibbs, I marked it as "recommended".</para> + + <para>The document is illustrated with examples in + pseudo-code. Although sometimes the examples have many details + and look like real code, it's still pseudo-code. It was written + to demonstrate the concepts in an understandable way. For a real + driver other approaches may be more modular and efficient. It + also abstracts from the hardware details, as well as issues that + would cloud the demonstrated concepts or that are supposed to be + described in the other chapters of the developers handbook. Such + details are commonly shown as calls to functions with descriptive + names, comments or pseudo-statements. Fortunately real life + full-size examples with all the details can be found in the real + drivers.</para> + + </sect1> + + <sect1> + <title>General architecture</title> + + <para>CAM stands for Common Access Method. It's a generic way to + address the I/O buses in a SCSI-like way. This allows a + separation of the generic device drivers from the drivers + controlling the I/O bus: for example the disk driver becomes able + to control disks on both SCSI, IDE, and/or any other bus so the + disk driver portion does not have to be rewritten (or copied and + modified) for every new I/O bus. Thus the two most important + active entities are:</para> + + <itemizedlist> + <listitem><para><emphasis>Peripheral Modules</emphasis> - a + driver for peripheral devices (disk, tape, CDROM, + etc.)</para></listitem> + <listitem><para><emphasis>SCSI Interface Modules </emphasis>(SIM) + - a Host Bus Adapter drivers for connecting to an I/O bus such + as SCSI or IDE.</para></listitem> + </itemizedlist> + + <para>A peripheral driver receives requests from the OS, converts + them to a sequence of SCSI commands and passes these SCSI + commands to a SCSI Interface Module. The SCSI Interface Module + is responsible for passing these commands to the actual hardware + (or if the actual hardware is not SCSI but, for example, IDE + then also converting the SCSI commands to the native commands of + the hardware).</para> + + <para>Because we are interested in writing a SCSI adapter driver + here, from this point on we will consider everything from the + SIM standpoint.</para> + + <para>A typical SIM driver needs to include the following + CAM-related header files:</para> + +<programlisting> +#include <cam/cam.h> +#include <cam/cam_ccb.h> +#include <cam/cam_sim.h> +#include <cam/cam_xpt_sim.h> +#include <cam/cam_debug.h> +#include <cam/scsi/scsi_all.h> +</programlisting> + + <para>The first thing each SIM driver must do is register itself + with the CAM subsystem. This is done during the driver's + <function>xxx_attach()</function> function (here and further + xxx_ is used to denote the unique driver name prefix). The + <function>xxx_attach()</function> function itself is called by + the system bus auto-configuration code which we don't describe + here.</para> + + <para>This is achieved in multiple steps: first it's necessary to + allocate the queue of requests associated with this SIM:</para> + +<programlisting> + struct cam_devq *devq; + + if(( devq = cam_simq_alloc(SIZE) )==NULL) { + error; /* some code to handle the error */ + } +</programlisting> + + <para>Here SIZE is the size of the queue to be allocated, maximal + number of requests it could contain. It's the number of requests + that the SIM driver can handle in parallel on one SCSI + card. Commonly it can be calculated as:</para> + +<programlisting> +SIZE = NUMBER_OF_SUPPORTED_TARGETS * MAX_SIMULTANEOUS_COMMANDS_PER_TARGET +</programlisting> + + <para>Next we create a descriptor of our SIM:</para> + +<programlisting> + struct cam_sim *sim; + + if(( sim = cam_sim_alloc(action_func, poll_func, driver_name, + softc, unit, max_dev_transactions, + max_tagged_dev_transactions, devq) )==NULL) { + cam_simq_free(devq); + error; /* some code to handle the error */ + } +</programlisting> + + <para>Note that if we are not able to create a SIM descriptor we + free the <structname>devq</structname> also because we can do + nothing else with it and we want to conserve memory.</para> + + <para>If a SCSI card has multiple SCSI buses on it then each bus + requires its own <structname>cam_sim</structname> + structure.</para> + + <para>An interesting question is what to do if a SCSI card has + more than one SCSI bus, do we need one + <structname>devq</structname> structure per card or per SCSI + bus? The answer given in the comments to the CAM code is: + either way, as the driver's author prefers.</para> + + <para>The arguments are : + <itemizedlist> + + <listitem><para><function>action_func</function> - pointer to + the driver's <function>xxx_action</function> function. + <funcSynopsis><funcPrototype> + <funcDef>static void + <function>xxx_action</function> + </funcDef> + <paramdef> + <parameter>struct cam_sim *sim</parameter>, + <parameter>union ccb *ccb</parameter> + </paramdef> + </funcPrototype></funcSynopsis> + </para></listitem> + + <listitem><para><function>poll_func</function> - pointer to + the driver's <function>xxx_poll()</function> + <funcSynopsis><funcPrototype> + <funcDef>static void + <function>xxx_poll</function> + </funcDef> + <paramdef> + <parameter>struct cam_sim *sim</parameter> + </paramdef> + </funcPrototype></funcSynopsis> + </para></listitem> + + <listitem><para>driver_name - the name of the actual driver, + such as "ncr" or "wds"</para></listitem> + + <listitem><para><structName>softc</structName> - 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.</para></listitem> + + <listitem><para>unit - the controller unit number, for example + for controller "wds0" this number will be + 0</para></listitem> + + <listitem><para>max_dev_transactions - maximal number of + simultaneous transactions per SCSI target in the non-tagged + mode. This value will be almost universally equal to 1, with + possible exceptions only for the non-SCSI cards. Also the + drivers that hope to take advantage by preparing one + transaction while another one is executed may set it to 2 + but this does not seem to be worth the + complexity.</para></listitem> + + <listitem><para>max_tagged_dev_transactions - the same thing, + but in the tagged mode. Tags are the SCSI way to initiate + multiple transactions on a device: each transaction is + assigned a unique tag and the transaction is sent to the + device. When the device completes some transaction it sends + back the result together with the tag so that the SCSI + adapter (and the driver) can tell which transaction was + completed. This argument is also known as the maximal tag + depth. It depends on the abilities of the SCSI + adapter.</para></listitem> + </itemizedlist> + </para> + + <para>Finally we register the SCSI buses associated with our SCSI + adapter:</para> + +<programlisting> + if(xpt_bus_register(sim, bus_number) != CAM_SUCCESS) { + cam_sim_free(sim, /*free_devq*/ TRUE); + error; /* some code to handle the error */ + } +</programlisting> + + <para>If there is one <structName>devq</structName> structure per + SCSI bus (i.e. we consider a card with multiple buses as + multiple cards with one bus each) then the bus number will + always be 0, otherwise each bus on the SCSI card should be get a + distinct number. Each bus needs its own separate structure + cam_sim.</para> + + <para>After that our controller is completely hooked to the CAM + system. The value of <structName>devq</structName> can be + discarded now: sim will be passed as an argument in all further + calls from CAM and devq can be derived from it.</para> + + <para>CAM provides the framework for such asynchronous + events. Some events originate from the lower levels (the SIM + drivers), some events originate from the peripheral drivers, + some events originate from the CAM subsystem itself. Any driver + can register callbacks for some types of the asynchronous + events, so that it would be notified if these events + occur.</para> + + <para>A typical example of such an event is a device reset. Each + transaction and event identifies the devices to which it applies + by the means of "path". The target-specific events normally + occur during a transaction with this device. So the path from + that transaction may be re-used to report this event (this is + safe because the event path is copied in the event reporting + routine but not deallocated nor passed anywhere further). Also + it's safe to allocate paths dynamically at any time including + the interrupt routines, although that incurs certain overhead, + and a possible problem with this approach is that there may be + no free memory at that time. For a bus reset event we need to + define a wildcard path including all devices on the bus. So we + can create the path for the future bus reset events in advance + and avoid problems with the future memory shortage:</para> + +<programlisting> + struct cam_path *path; + + 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)); + cam_sim_free(sim, /*free_devq*/TRUE); + error; /* some code to handle the error */ + } + + softc->wpath = path; + softc->sim = sim; +</programlisting> + + <para>As you can see the path includes:</para> + + <itemizedlist> + <listitem><para>ID of the peripheral driver (NULL here because we have + none)</para></listitem> + + <listitem><para>ID of the SIM driver + (<function>cam_sim_path(sim)</function>)</listitem> + + <listitem><para>SCSI target number of the device (CAM_TARGET_WILDCARD + means "all devices")</para></listitem> + + <listitem><para>SCSI LUN number of the subdevice (CAM_LUN_WILDCARD means + "all LUNs")</para></listitem> + </itemizedlist> + + <para>If the driver can't allocate this path it won't be able to + work normally, so in that case we dismantle that SCSI + bus.</para> + + <para>And we save the path pointer in the + <structName>softc</structName> structure for future use. After + that we save the value of sim (or we can also discard it on the + exit from <function>xxx_probe()</function> if we wish).</para> + + <para>That's all for a minimalistic initialization. To do things + right there is one more issue left. </para> + + <para>For a SIM driver there is one particularly interesting + event: when a target device is considered lost. In this case + resetting the SCSI negotiations with this device may be a good + idea. So we register a callback for this event with CAM. The + request is passed to CAM by requesting CAM action on a CAM + control block for this type of request:</para> + +<programlisting> + struct ccb_setasync csa; + + xpt_setup_ccb(&csa.ccb_h, path, /*priority*/5); + csa.ccb_h.func_code = XPT_SASYNC_CB; + csa.event_enable = AC_LOST_DEVICE; + csa.callback = xxx_async; + csa.callback_arg = sim; + xpt_action((union ccb *)&csa); +</programlisting> + + <para>Now we take a look at the <function>xxx_action()</function> + and <function>xxx_poll()</function> driver entry points.</para> + + <para> + <funcSynopsis><funcPrototype> + <funcDef>static void + <function>xxx_action</function> + </funcDef> + <paramdef> + <parameter>struct cam_sim *sim</parameter>, + <parameter>union ccb *ccb</parameter> + </paramdef> + </funcPrototype></funcSynopsis> + </para> + + <para>Do some action on request of the CAM subsystem. Sim + describes the SIM for the request, CCB is the request + itself. CCB stands for "CAM Control Block". It is a union of + many specific instances, each describing arguments for some type + of transactions. All of these instances share the CCB header + where the common part of arguments is stored.</para> + + <para>CAM supports the SCSI controllers working in both initiator + ("normal") mode and target (simulating a SCSI device) mode. Here + we only consider the part relevant to the initiator mode.</para> + + <para>There are a few function and macros (in other words, + methods) defined to access the public data in the struct sim:</para> + + <itemizedlist> + <listitem><para><function>cam_sim_path(sim)</function> - the + path ID (see above)</para></listitem> + + <listitem><para><function>cam_sim_name(sim)</function> - the + name of the sim</para></listitem> + + <listitem><para><function>cam_sim_softc(sim)</function> - the + pointer to the softc (driver private data) + structure</para></listitem> + + <listitem><para><function> cam_sim_unit(sim)</function> - the + unit number</para></listitem> + + <listitem><para><function> cam_sim_bus(sim)</function> - the bus + ID</para></listitem> + </itemizedlist> + + <para>To identify the device, <function>xxx_action()</function> can + get the unit number and pointer to its structure softc using + these functions.</para> + + <para>The type of request is stored in + <structField>ccb->ccb_h.func_code</structField>. So generally + <function>xxx_action()</function> consists of a big + switch:</para> + +<programlisting> + struct xxx_softc *softc = (struct xxx_softc *) cam_sim_softc(sim); + struct ccb_hdr *ccb_h = &ccb->ccb_h; + int unit = cam_sim_unit(sim); + int bus = cam_sim_bus(sim); + + switch(ccb_h->func_code) { + case ...: + ... + default: + ccb_h->status = CAM_REQ_INVALID; + xpt_done(ccb); + break; + } +</programlisting> + + <para>As can be seen from the default case (if an unknown command + was received) the return code of the command is set into + <structField>ccb->ccb_h.status</structField> and the completed + CCB is returned back to CAM by calling + <function>xpt_done(ccb)</function>. </para> + + <para><function>xpt_done()</function> does not have to be called + from <function>xxx_action()</function>: For example an I/O + request may be enqueued inside the SIM driver and/or its SCSI + controller. Then when the device would post an interrupt + signaling that the processing of this request is complete + <function>xpt_done()</function> may be called from the interrupt + handling routine.</para> + + <para>Actually, the CCB status is not only assigned as a return + code but a CCB has some status all the time. Before CCB is + passed to the <function>xxx_action()</function> routine it gets + the status CCB_REQ_INPROG meaning that it's in progress. There + are a surprising number of status values defined in + <filename>/sys/cam/cam.h</filename> which should be able to + represent the status of a request in great detail. More + interesting yet, the status is in fact a "bitwise or" of an + enumerated status value (the lower 6 bits) and possible + additional flag-like bits (the upper bits). The enumerated + values will be discussed later in more detail. The summary of + them can be found in the Errors Summary section. The possible + status flags are:</para> + + <itemizedlist> + + <listitem><para><emphasis>CAM_DEV_QFRZN</emphasis> - if the + SIM driver gets a serious error (for example, the device does + not respond to the selection or breaks the SCSI protocol) when + processing a CCB it should freeze the request queue by calling + <function>xpt_freeze_simq()</function>, return the other + enqueued but not processed yet CCBs for this device back to + the CAM queue, then set this flag for the troublesome CCB and + call <function>xpt_done()</function>. This flag causes the CAM + subsystem to unfreeze the queue after it handles the + error.</para></listitem> + + <listitem><para><emphasis>CAM_AUTOSNS_VALID</emphasis> - if + the device returned an error condition and the flag + CAM_DIS_AUTOSENSE is not set in CCB the SIM driver must + execute the REQUEST SENSE command automatically to extract the + sense (extended error information) data from the device. If + this attempt was successful the sense data should be saved in + the CCB and this flag set.</para></listitem> + + <listitem><para><emphasis>CAM_RELEASE_SIMQ</emphasis> - like + CAM_DEV_QFRZN but used in case there is some problem (or + resource shortage) with the SCSI controller itself. Then all + the future requests to the controller should be stopped by + <function>xpt_freeze_simq()</function>. The controller queue + will be restarted after the SIM driver overcomes the shortage + and informs CAM by returning some CCB with this flag + set.</para></listitem> + + <listitem><para><emphasis>CAM_SIM_QUEUED</emphasis> - when SIM + puts a CCB into its request queue this flag should be set (and + removed when this CCB gets dequeued before being returned back + to CAM). This flag is not used anywhere in the CAM code now, + so its purpose is purely diagnostic.</para></listitem> + + </itemizedlist> + + <para>The function <function>xxx_action()</function> is not + allowed to sleep, so all the synchronization for resource access + must be done using SIM or device queue freezing. Besides the + aforementioned flags the CAM subsystem provides functions + <function>xpt_selease_simq()</function> and + <function>xpt_release_devq()</function> to unfreeze the queues + directly, without passing a CCB to CAM.</para> + + <para>The CCB header contains the following fields:</para> + + <itemizedlist> + + <listitem><para><emphasis>path</emphasis> - path ID for the + request</para></listitem> + + <listitem><para><emphasis>target_id</emphasis> - target device + ID for the request</para></listitem> + + <listitem><para><emphasis>target_lun</emphasis> - LUN ID of + the target device</para></listitem> + + <listitem><para><emphasis>timeout</emphasis> - timeout + interval for this command, in milliseconds</para></listitem> + + <listitem><para><emphasis>timeout_ch</emphasis> - a + convenience place for the SIM driver to store the timeout handle + (the CAM subsystem itself does not make any assumptions about + it)</para></listitem> + + <listitem><para><emphasis>flags</emphasis> - various bits of + information about the request spriv_ptr0, spriv_ptr1 - fields + reserved for private use by the SIM driver (such as linking to + the SIM queues or SIM private control blocks); actually, they + exist as unions: spriv_ptr0 and spriv_ptr1 have the type (void + *), spriv_field0 and spriv_field1 have the type unsigned long, + sim_priv.entries[0].bytes and sim_priv.entries[1].bytes are byte + arrays of the size consistent with the other incarnations of the + union and sim_priv.bytes is one array, twice + bigger.</para></listitem> + + </itemizedlist> + + <para>The recommended way of using the SIM private fields of CCB + is to define some meaningful names for them and use these + meaningful names in the driver, like:</para> + +<programlisting> +#define ccb_some_meaningful_name sim_priv.entries[0].bytes +#define ccb_hcb spriv_ptr1 /* for hardware control block */ +</programlisting> + + <para>The most common initiator mode requests are:</para> + <itemizedlist> + <listitem><para><emphasis>XPT_SCSI_IO</emphasis> - execute an + I/O transaction</para> + + <para>The instance "struct ccb_scsiio csio" of the union ccb is + used to transfer the arguments. They are:</para> + + <itemizedlist> + <listitem><para><emphasis>cdb_io</emphasis> - pointer to + the SCSI command buffer or the buffer + itself</para></listitem> + + <listitem><para><emphasis>cdb_len</emphasis> - SCSI + command length</para></listitem> + + <listitem><para><emphasis>data_ptr</emphasis> - pointer to + the data buffer (gets a bit complicated if scatter/gather is + used)</para></listitem> + + <listitem><para><emphasis>dxfer_len</emphasis> - length of + the data to transfer</para></listitem> + + <listitem><para><emphasis>sglist_cnt</emphasis> - counter + of the scatter/gather segments</para></listitem> + + <listitem><para><emphasis>scsi_status</emphasis> - place + to return the SCSI status</para></listitem> + + <listitem><para><emphasis>sense_data</emphasis> - 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)</para></listitem> + + <listitem><para><emphasis>sense_len</emphasis> - 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.</para></listitem> + + <listitem><para><emphasis>tag_action</emphasis> - the kind + of tag to use: + + <itemizedlist> + <listitem><para>CAM_TAG_ACTION_NONE - don't use tags for this + transaction</para></listitem> + <listitem><para>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</para></listitem> + </itemizedlist> + + </para></listitem> + + </itemizedlist> + + <para>The general logic of handling this request is the + following:</para> + + <para>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:</para> + +<programlisting> + struct ccb_scsiio *csio = &ccb->csio; + + if ((ccb_h->status & CAM_STATUS_MASK) != CAM_REQ_INPROG) { + xpt_done(ccb); + return; + } +</programlisting> + + <para>Also we check that the device is supported at all by our + controller:</para> + +<programlisting> + 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) { + ccb_h->status = CAM_LUN_INVALID; + xpt_done(ccb); + return; + } +</programlisting> + + <para>Then allocate whatever data structures (such as + card-dependent hardware control block) we need to process this + request. If we can't 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.</para> + +<programlisting> + struct xxx_hcb *hcb = allocate_hcb(softc, unit, bus); + + if(hcb == NULL) { + softc->flags |= RESOURCE_SHORTAGE; + xpt_freeze_simq(sim, /*count*/1); + ccb_h->status = CAM_REQUEUE_REQ; + xpt_done(ccb); + return; + } + + hcb->ccb = ccb; ccb_h->ccb_hcb = (void *)hcb; + ccb_h->status |= CAM_SIM_QUEUED; +</programlisting> + + <para>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.</para> + +<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 ) + generate_unique_tag_message(hcb, ccb_h->tag_action); + if( !target_negotiated(hcb) ) + generate_negotiation_messages(hcb); +</programlisting> + + <para>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.</para> + + <para>A NOT-QUITE RELATED NOTE: Normally this is done by a call + to vtophys(), but for the PCI device (which account for most + of the SCSI controllers now) drivers' portability to the Alpha + architecture the conversion must be done by vtobus() instead + due to special Alpha quirks. [IMHO it would be much better to + have two separate functions, vtop() and ptobus() then vtobus() + would be a simple superposition of them.] In case if a + physical address is requested it's OK to return the CCB with + the status CAM_REQ_INVALID, the current drivers do that. But + it's also possible to compile the Alpha-specific piece of + code, as in this example (there should be a more direct way to + do that, without conditional compilation in the drivers). If + necessary a physical address can be also converted or mapped + back to a virtual address but with big pain, so we don't do + that.</para> + +<programlisting> + if(ccb_h->flags & CAM_CDB_POINTER) { + /* CDB is a pointer */ + if(!(ccb_h->flags & CAM_CDB_PHYS)) { + /* CDB pointer is virtual */ + hcb->cmd = vtobus(csio->cdb_io.cdb_ptr); + } else { + /* CDB pointer is physical */ +#if defined(__alpha__) + hcb->cmd = csio->cdb_io.cdb_ptr | alpha_XXX_dmamap_or ; +#else + hcb->cmd = csio->cdb_io.cdb_ptr ; +#endif + } + } else { + /* CDB is in the ccb (buffer) */ + hcb->cmd = vtobus(csio->cdb_io.cdb_bytes); + } + hcb->cmdlen = csio->cdb_len; +</programlisting> + + <para>Now it's 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:</para> + +<programlisting> + int dir = (ccb_h->flags & CAM_DIR_MASK); + + if (dir == CAM_DIR_NONE) + goto end_data; +</programlisting> + + <para>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. We use a special + function to return the CCB to handle in one place the HCB + resource shortages. The functions to add chunks are + driver-dependent, and here we leave them without detailed + implementation. See description of the SCSI command (CDB) + handling for the details on the address-translation issues. + If some variation is too difficult or impossible to implement + with a particular card it's 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's actively used by CAM.</para> + +<programlisting> + int rv; + + initialize_hcb_for_data(hcb); + + if((!(ccb_h->flags & CAM_SCATTER_VALID)) { + /* single buffer */ + if(!(ccb_h->flags & CAM_DATA_PHYS)) { + rv = add_virtual_chunk(hcb, csio->data_ptr, csio->dxfer_len, dir); + } + } else { + rv = add_physical_chunk(hcb, csio->data_ptr, csio->dxfer_len, dir); + } + } else { + int i; + struct bus_dma_segment *segs; + segs = (struct bus_dma_segment *)csio->data_ptr; + + if ((ccb_h->flags & CAM_SG_LIST_PHYS) != 0) { + /* The SG list pointer is physical */ + rv = setup_hcb_for_physical_sg_list(hcb, segs, csio->sglist_cnt); + } else if (!(ccb_h->flags & CAM_DATA_PHYS)) { + /* SG buffer pointers are virtual */ + for (i = 0; i < csio->sglist_cnt; i++) { + rv = add_virtual_chunk(hcb, segs[i].ds_addr, + segs[i].ds_len, dir); + if (rv != CAM_REQ_CMP) + break; + } + } else { + /* SG buffer pointers are physical */ + for (i = 0; i < csio->sglist_cnt; i++) { + rv = add_physical_chunk(hcb, segs[i].ds_addr, + segs[i].ds_len, dir); + if (rv != CAM_REQ_CMP) + break; + } + } + } + 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), + * CAM_REQ_INVALID in case of other troubles + */ + free_hcb_and_ccb_done(hcb, ccb, rv); + return; + } + end_data: +</programlisting> + + <para>If disconnection is disabled for this CCB we pass this + information to the hcb:</para> + +<programlisting> + if(ccb_h->flags & CAM_DIS_DISCONNECT) + hcb_disable_disconnect(hcb); +</programlisting> + + <para>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.</para> + + <para>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).</para> + +<programlisting> + ccb_h->timeout_ch = timeout(xxx_timeout, (caddr_t) hcb, + (ccb_h->timeout * hz) / 1000); /* convert milliseconds to ticks */ + put_hcb_into_hardware_queue(hcb); + return; +</programlisting> + + <para>And here is a possible implementation of the function + returning CCB:</para> + +<programlisting> + static void + free_hcb_and_ccb_done(struct xxx_hcb *hcb, union ccb *ccb, u_int32_t status) + { + struct xxx_softc *softc = hcb->softc; + + ccb->ccb_h.ccb_hcb = 0; + 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) { + softc->flags &= ~RESOURCE_SHORTAGE; + status |= CAM_RELEASE_SIMQ; + } + free_hcb(hcb); /* also removes hcb from any internal lists */ + } + ccb->ccb_h.status = status | + (ccb->ccb_h.status & ~(CAM_STATUS_MASK|CAM_SIM_QUEUED)); + xpt_done(ccb); + } +</programlisting> + </listitem> + + <listitem><para><emphasis>XPT_RESET_DEV</emphasis> - send the SCSI "BUS + DEVICE RESET" message to a device</para> + + <para>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).</para> + + <para>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.</para></listitem> + + <listitem><para><emphasis>XPT_RESET_BUS</emphasis> - send the RESET signal + to the SCSI bus</para> + + <para>No arguments are passed in the CCB, the only interesting + argument is the SCSI bus indicated by the struct sim + pointer.</para> + + <para>A minimalistic implementation would forget the SCSI + negotiations for all the devices on the bus and return the + status CAM_REQ_CMP.</para> + + <para>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:</para> + +<programlisting> + int targ, lun; + struct xxx_hcb *h, *hh; + struct ccb_trans_settings neg; + struct cam_path *path; + + /* The SCSI bus reset may take a long time, in this case its completion + * should be checked by interrupt or timeout. But for simplicity + * we assume here that it's really fast. + */ + reset_scsi_bus(softc); + + /* drop all enqueued CCBs */ + 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); + } + + /* the clean values of negotiations to report */ + neg.bus_width = 8; + neg.sync_period = neg.sync_offset = 0; + neg.valid = (CCB_TRANS_BUS_WIDTH_VALID + | 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++) { + clean_negotiations(softc, targ); + + /* report the event if possible */ + 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) { + hh=h->next; + free_hcb_and_ccb_done(h, h->ccb, CAM_SCSI_BUS_RESET); + } + } + + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + + /* report the event */ + xpt_async(AC_BUS_RESET, softc->wpath, NULL); + return; +</programlisting> + + <para>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.</para></listitem> + + <listitem><para><emphasis>XPT_ABORT</emphasis> - abort the specified + CCB</para> + + <para>The arguments are transferred in the instance "struct + ccb_abort cab" of the union ccb. The only argument field in it + is:</para> + + <para><emphasis>abort_ccb</emphasis> - pointer to the CCB to be + aborted</para> + + <para>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.</para> + + <para>The hard way is to implement this request honestly. First + check that abort applies to a SCSI transaction:</para> + +<programlisting> + struct ccb *abort_ccb; + abort_ccb = ccb->cab.abort_ccb; + + if(abort_ccb->ccb_h.func_code != XPT_SCSI_IO) { + ccb->ccb_h.status = CAM_UA_ABORT; + xpt_done(ccb); + return; + } +</programlisting> + + <para>Then it's 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:</para> + +<programlisting> + struct xxx_hcb *hcb, *h; + + hcb = NULL; + + /* We assume that softc->first_hcb is the head of the list of all + * 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) { + hcb = h; + break; + } + } + + if(hcb == NULL) { + /* no such CCB in our queue */ + ccb->ccb_h.status = CAM_PATH_INVALID; + xpt_done(ccb); + return; + } + + hcb=found_hcb; +</programlisting> + + <para>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 don't 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.</para> + +<programlisting> + int hstatus; + + /* shown as a function, in case special action is needed to make + * this flag visible to hardware + */ + set_hcb_flags(hcb, HCB_BEING_ABORTED); + + abort_again: + + hstatus = get_hcb_status(hcb); + switch(hstatus) { + case HCB_SITTING_IN_QUEUE: + remove_hcb_from_hardware_queue(hcb); + /* FALLTHROUGH */ + case HCB_COMPLETED: + /* this is an easy case */ + free_hcb_and_ccb_done(hcb, abort_ccb, CAM_REQ_ABORTED); + break; +</programlisting> + + <para>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. + Because 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).</para> + +<programlisting> + case HCB_BEING_TRANSFERRED: + untimeout(xxx_timeout, (caddr_t) hcb, abort_ccb->ccb_h.timeout_ch); + abort_ccb->ccb_h.timeout_ch = + timeout(xxx_timeout, (caddr_t) hcb, 10 * hz); + abort_ccb->ccb_h.status = CAM_REQ_ABORTED; + /* ask the controller to abort that HCB, then generate + * an interrupt and stop + */ + 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; + } + + break; +</programlisting> + + <para>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.</para> + +<programlisting> + case HCB_DISCONNECTED: + untimeout(xxx_timeout, (caddr_t) hcb, abort_ccb->ccb_h.timeout_ch); + abort_ccb->ccb_h.timeout_ch = + timeout(xxx_timeout, (caddr_t) hcb, 10 * hz); + put_abort_message_into_hcb(hcb); + put_hcb_at_the_front_of_hardware_queue(hcb); + break; + } + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + return; +</programlisting> + + <para>That's all for the ABORT request, although there is one more + issue. Because 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.</para> + + <para>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:</para> + +<programlisting> + case XPT_ABORT: + struct ccb *abort_ccb; + abort_ccb = ccb->cab.abort_ccb; + + 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) + /* no such CCB in our queue */ + ccb->ccb_h.status = CAM_PATH_INVALID; + else + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + return; +</programlisting> + </listitem> + + <listitem><para><emphasis>XPT_SET_TRAN_SETTINGS</emphasis> - explicitly + set values of SCSI transfer settings</para> + + <para>The arguments are transferred in the instance "struct ccb_trans_setting cts" +of the union ccb:</para> + + <itemizedlist> + <listitem><para><emphasis>valid</emphasis> - a bitmask showing + which settings should be updated:</para></listitem> + + <listitem><para><emphasis>CCB_TRANS_SYNC_RATE_VALID</emphasis> + - synchronous transfer rate</para></listitem> + + <listitem><para><emphasis>CCB_TRANS_SYNC_OFFSET_VALID</emphasis> + - synchronous offset</para></listitem> + + <listitem><para><emphasis>CCB_TRANS_BUS_WIDTH_VALID</emphasis> + - bus width</para></listitem> + + <listitem><para><emphasis>CCB_TRANS_DISC_VALID</emphasis> - + set enable/disable disconnection</para></listitem> + + <listitem><para><emphasis>CCB_TRANS_TQ_VALID</emphasis> - set + enable/disable tagged queuing</para></listitem> + + <listitem><para><emphasis>flags</emphasis> - consists of two + parts, binary arguments and identification of + sub-operations. The binary arguments are :</para> + <itemizedlist> + <listitem><para><emphasis>CCB_TRANS_DISC_ENB</emphasis> - enable disconnection</para></listitem> + <listitem><para><emphasis>CCB_TRANS_TAG_ENB</emphasis> - + enable tagged queuing</para></listitem> + </itemizedlist> + </listitem> + + <listitem><para>the sub-operations are:</para> + <itemizedlist> + <listitem><para><emphasis>CCB_TRANS_CURRENT_SETTINGS</emphasis> + - change the current negotiations</para></listitem> + + <listitem><para><emphasis>CCB_TRANS_USER_SETTINGS</emphasis> + - 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)</para></listitem> + </itemizedlist> + </listitem> + + </itemizedlist> + + <para>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's essentially the + top boundary.</para> + + <para>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.</para> + + <para>One caveat is that the bus width and synchronous parameters + are per target while the disconnection and tag enabling + parameters are per lun.</para> + + <para>The recommended implementation is to keep 3 sets of + negotiated (bus width and synchronous transfer) + parameters:</para> + + <itemizedlist> + <listitem><para><emphasis>user</emphasis> - the user set, as + above</para></listitem> + + <listitem><para><emphasis>current</emphasis> - those actually + in effect</para></listitem> + + <listitem><para><emphasis>goal</emphasis> - those requested by + setting of the "current" parameters</para></listitem> + </itemizedlist> + + <para>The code looks like:</para> + +<programlisting> + struct ccb_trans_settings *cts; + int targ, lun; + int flags; + + cts = &ccb->cts; + 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) + softc->user_sync_period[targ] = cts->sync_period; + if(flags & CCB_TRANS_SYNC_OFFSET_VALID) + softc->user_sync_offset[targ] = cts->sync_offset; + if(flags & CCB_TRANS_BUS_WIDTH_VALID) + softc->user_bus_width[targ] = cts->bus_width; + + 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) { + 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) + softc->goal_sync_period[targ] = + max(cts->sync_period, OUR_MIN_SUPPORTED_PERIOD); + 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) + softc->goal_bus_width[targ] = min(cts->bus_width, OUR_BUS_WIDTH); + + 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) { + softc->current_tflags[targ][lun] &= ~CCB_TRANS_TQ_ENB; + softc->current_tflags[targ][lun] |= flags & CCB_TRANS_TQ_ENB; + } + } + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + return; +</programlisting> + + <para>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:</para> + +<programlisting> + int + target_negotiated(struct xxx_hcb *hcb) + { + struct softc *softc = hcb->softc; + int targ = hcb->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] ) + return 0; /* FALSE */ + else + return 1; /* TRUE */ + } +</programlisting> + + <para>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 <function>target_negotiated()</function> would return + TRUE. When the card is initialized (in + <function>xxx_attach()</function>) 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.</para></listitem> + + <listitem><para><emphasis>XPT_GET_TRAN_SETTINGS</emphasis> - get values of + SCSI transfer settings</para> + + <para>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.</para></listitem> + + <listitem><para><emphasis>XPT_CALC_GEOMETRY</emphasis> - calculate logical + (BIOS) geometry of the disk</para> + + <para>The arguments are transferred in the instance "struct + ccb_calc_geometry ccg" of the union ccb:</para> + + <itemizedlist> + + <listitem><para><emphasis>block_size</emphasis> - input, block + (A.K.A sector) size in bytes</para></listitem> + + <listitem><para><emphasis>volume_size</emphasis> - input, + volume size in bytes</para></listitem> + + <listitem><para><emphasis>cylinders</emphasis> - output, + logical cylinders</para></listitem> + + <listitem><para><emphasis>heads</emphasis> - output, logical + heads</para></listitem> + + <listitem><para><emphasis>secs_per_track</emphasis> - output, + logical sectors per track</para></listitem> + + </itemizedlist> + + <para>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:</para> + +<programlisting> + struct ccb_calc_geometry *ccg; + u_int32_t size_mb; + u_int32_t secs_per_cylinder; + int extended; + + ccg = &ccb->ccg; + size_mb = ccg->volume_size + / ((1024L * 1024L) / ccg->block_size); + extended = check_cards_EEPROM_for_extended_geometry(softc); + + if (size_mb > 1024 && extended) { + ccg->heads = 255; + ccg->secs_per_track = 63; + } else { + ccg->heads = 64; + ccg->secs_per_track = 32; + } + secs_per_cylinder = ccg->heads * ccg->secs_per_track; + ccg->cylinders = ccg->volume_size / secs_per_cylinder; + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + return; +</programlisting> + + <para>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:</para> + +<programlisting> + 128 heads, 63 sectors - Symbios controllers + 16 heads, 63 sectors - old controllers +</programlisting> + + <para>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.</para> + </listitem> + + <listitem><para><emphasis>XPT_PATH_INQ</emphasis> - path inquiry, in other + words get the SIM driver and SCSI controller (also known as HBA + - Host Bus Adapter) properties</para> + + <para>The properties are returned in the instance "struct +ccb_pathinq cpi" of the union ccb:</para> + + <itemizedlist> + + <listitem><para>version_num - the SIM driver version number, now + all drivers use 1</para></listitem> + + <listitem><para>hba_inquiry - bitmask of features supported by + the controller:</para></listitem> + + <listitem><para>PI_MDP_ABLE - supports MDP message (something + from SCSI3?)</para></listitem> + + <listitem><para>PI_WIDE_32 - supports 32 bit wide + SCSI</para></listitem> + + <listitem><para>PI_WIDE_16 - supports 16 bit wide + SCSI</para></listitem> + + <listitem><para>PI_SDTR_ABLE - can negotiate synchronous + transfer rate</para></listitem> + + <listitem><para>PI_LINKED_CDB - supports linked + commands</para></listitem> + + <listitem><para>PI_TAG_ABLE - supports tagged + commands</para></listitem> + + <listitem><para>PI_SOFT_RST - supports soft reset alternative + (hard reset and soft reset are mutually exclusive within a + SCSI bus)</para></listitem> + + <listitem><para>target_sprt - flags for target mode support, 0 + if unsupported</para></listitem> + + <listitem><para>hba_misc - miscellaneous controller + features:</para></listitem> + + <listitem><para>PIM_SCANHILO - bus scans from high ID to low + ID</para></listitem> + + <listitem><para>PIM_NOREMOVE - removable devices not included in + scan</para></listitem> + + <listitem><para>PIM_NOINITIATOR - initiator role not + supported</para></listitem> + + <listitem><para>PIM_NOBUSRESET - user has disabled initial BUS + RESET</para></listitem> + + <listitem><para>hba_eng_cnt - mysterious HBA engine count, + something related to compression, now is always set to + 0</para></listitem> + + <listitem><para>vuhba_flags - vendor-unique flags, unused + now</para></listitem> + + <listitem><para>max_target - maximal supported target ID (7 for + 8-bit bus, 15 for 16-bit bus, 127 for Fibre + Channel)</para></listitem> + + <listitem><para>max_lun - maximal supported LUN ID (7 for older + SCSI controllers, 63 for newer ones)</para></listitem> + + <listitem><para>async_flags - bitmask of installed Async + handler, unused now</para></listitem> + + <listitem><para>hpath_id - highest Path ID in the subsystem, + unused now</para></listitem> + + <listitem><para>unit_number - the controller unit number, + cam_sim_unit(sim)</para></listitem> + + <listitem><para>bus_id - the bus number, + cam_sim_bus(sim)</para></listitem> + + <listitem><para>initiator_id - the SCSI ID of the controller + itself</para></listitem> + + <listitem><para>base_transfer_speed - nominal transfer speed in + KB/s for asynchronous narrow transfers, equals to 3300 for + SCSI</para></listitem> + + <listitem><para>sim_vid - SIM driver's vendor id, a + zero-terminated string of maximal length SIM_IDLEN including + the terminating zero</para></listitem> + + <listitem><para>hba_vid - SCSI controller's vendor id, a + zero-terminated string of maximal length HBA_IDLEN including + the terminating zero</para></listitem> + + <listitem><para>dev_name - device driver name, a zero-terminated + string of maximal length DEV_IDLEN including the terminating + zero, equal to cam_sim_name(sim)</para></listitem> + + </itemizedlist> + + <para>The recommended way of setting the string fields is using + strncpy, like:</para> + +<programlisting> + strncpy(cpi->dev_name, cam_sim_name(sim), DEV_IDLEN); +</programlisting> + + <para>After setting the values set the status to CAM_REQ_CMP and mark the +CCB as done.</para> + </listitem> + </itemizedlist> + + </sect1> + + <sect1> + <title>Polling</title> + + <funcSynopsis><funcPrototype> + <funcDef>static void + <function>xxx_poll</function> + </funcDef> + <paramdef> + <parameter>struct cam_sim *sim</parameter> + </paramdef> + </funcPrototype></funcSynopsis> + + <para>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. So all it needs to do is to call the interrupt + routine (or the other way around, the poll routine may be doing + the real action and the interrupt routine would just call the + poll routine). Why bother about a separate function then ? + Because of different calling conventions. The + <function>xxx_poll</function> routine gets the struct cam_sim + pointer as its argument when the PCI interrupt routine by common + convention gets pointer to the struct + <structName>xxx_softc</structName> and the ISA interrupt routine + gets just the the device unit number. So the poll routine would + normally look as:</para> + +<programlisting> +static void +xxx_poll(struct cam_sim *sim) +{ + xxx_intr((struct xxx_softc *)cam_sim_softc(sim)); /* for PCI device */ +} +</programlisting> + + <para>or</para> + +<programlisting> +static void +xxx_poll(struct cam_sim *sim) +{ + xxx_intr(cam_sim_unit(sim)); /* for ISA device */ +} +</programlisting> + + </sect1> + + <sect1> + <title>Asynchronous Events</title> + + <para>If an asynchronous event callback has been set up then the + callback function should be defined.</para> + +<programlisting> +static void +ahc_async(void *callback_arg, u_int32_t code, struct cam_path *path, void *arg) +</programlisting> + + <itemizedlist> + <listitem><para>callback_arg - the value supplied when registering the + callback</para></listitem> + + <listitem><para>code - identifies the type of event</para></listitem> + + <listitem><para>path - identifies the devices to which the event + applies</para></listitem> + + <listitem><para>arg - event-specific argument</para></listitem> + </itemizedlist> + + <para>Implementation for a single type of event, AC_LOST_DEVICE, + looks like:</para> + +<programlisting> + struct xxx_softc *softc; + struct cam_sim *sim; + int targ; + struct ccb_trans_settings neg; + + sim = (struct cam_sim *)callback_arg; + softc = (struct xxx_softc *)cam_sim_softc(sim); + switch (code) { + case AC_LOST_DEVICE: + targ = xpt_path_target_id(path); + if(targ <= OUR_MAX_SUPPORTED_TARGET) { + clean_negotiations(softc, targ); + /* send indication to CAM */ + neg.bus_width = 8; + neg.sync_period = neg.sync_offset = 0; + neg.valid = (CCB_TRANS_BUS_WIDTH_VALID + | CCB_TRANS_SYNC_RATE_VALID | CCB_TRANS_SYNC_OFFSET_VALID); + xpt_async(AC_TRANSFER_NEG, path, &neg); + } + break; + default: + break; + } +</programlisting> + + </sect1> + + <sect1> + <title>Interrupts</title> + + <para>The exact type of the interrupt routine depends on the type + of the peripheral bus (PCI, ISA and so on) to which the SCSI + controller is connected.</para> + + <para>The interrupt routines of the SIM drivers run at the + interrupt level splcam. So <function>splcam()</function> should + be used in the driver to synchronize activity between the + interrupt routine and the rest of the driver (for a + multiprocessor-aware driver things get yet more interesting but + we ignore this case here). The pseudo-code in this document + happily ignores the problems of synchronization. The real code + must not ignore them. A simple-minded approach is to set + <function>splcam()</function> on the entry to the other routines + and reset it on return thus protecting them by one big critical + section. To make sure that the interrupt level will be always + restored a wrapper function can be defined, like:</para> + +<programlisting> + static void + xxx_action(struct cam_sim *sim, union ccb *ccb) + { + int s; + s = splcam(); + xxx_action1(sim, ccb); + splx(s); + } + + static void + xxx_action1(struct cam_sim *sim, union ccb *ccb) + { + ... process the request ... + } +</programlisting> + + <para>This approach is simple and robust but the problem with it + is that interrupts may get blocked for a relatively long time + and this would negatively affect the system's performance. On + the other hand the functions of the <function>spl()</function> + family have rather high overhead, so vast amount of tiny + critical sections may not be good either.</para> + + <para>The conditions handled by the interrupt routine and the + details depend very much on the hardware. We consider the set of + "typical" conditions.</para> + + <para>First, we check if a SCSI reset was encountered on the bus + (probably caused by another SCSI controller on the same SCSI + bus). If so we drop all the enqueued and disconnected requests, + report the events and re-initialize our SCSI controller. It is + important that during this initialization the controller won't + issue another reset or else two controllers on the same SCSI bus + could ping-pong resets forever. The case of fatal controller + error/hang could be handled in the same place, but it will + probably need also sending RESET signal to the SCSI bus to reset + the status of the connections with the SCSI devices.</para> + +<programlisting> + int fatal=0; + struct ccb_trans_settings neg; + struct cam_path *path; + + if( detected_scsi_reset(softc) + || (fatal = detected_fatal_controller_error(softc)) ) { + int targ, lun; + struct xxx_hcb *h, *hh; + + /* drop all enqueued CCBs */ + 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); + } + + /* the clean values of negotiations to report */ + neg.bus_width = 8; + neg.sync_period = neg.sync_offset = 0; + neg.valid = (CCB_TRANS_BUS_WIDTH_VALID + | 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++) { + clean_negotiations(softc, targ); + + /* report the event if possible */ + 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) { + hh=h->next; + 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); + } + } + + /* report the event */ + xpt_async(AC_BUS_RESET, softc->wpath, NULL); + + /* re-initialization may take a lot of time, in such case + * its completion should be signaled by another interrupt or + * checked on timeout - but for simplicity we assume here that + * it's really fast + */ + if(!fatal) { + reinitialize_controller_without_scsi_reset(softc); + } else { + reinitialize_controller_with_scsi_reset(softc); + } + schedule_next_hcb(softc); + return; + } +</programlisting> + + <para>If interrupt is not caused by a controller-wide condition + then probably something has happened to the current hardware + control block. Depending on the hardware there may be other + non-HCB-related events, we just do not consider them here. Then + we analyze what happened to this HCB:</para> + +<programlisting> + struct xxx_hcb *hcb, *h, *hh; + int hcb_status, scsi_status; + int ccb_status; + int targ; + int lun_to_freeze; + + hcb = get_current_hcb(softc); + if(hcb == NULL) { + /* either stray interrupt or something went very wrong + * or this is something hardware-dependent + */ + handle as necessary; + return; + } + + targ = hcb->target; + hcb_status = get_status_of_current_hcb(softc); +</programlisting> + + <para>First we check if the HCB has completed and if so we check + the returned SCSI status.</para> + +<programlisting> + if(hcb_status == COMPLETED) { + scsi_status = get_completion_status(hcb); +</programlisting> + + <para>Then look if this status is related to the REQUEST SENSE + command and if so handle it in a simple way.</para> + +<programlisting> + 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 { + autosense_failed: + free_hcb_and_ccb_done(hcb, hcb->ccb, CAM_AUTOSENSE_FAIL); + } + schedule_next_hcb(softc); + return; + } +</programlisting> + + <para>Else the command itself has completed, pay more attention to + details. If auto-sense is not disabled for this CCB and the + command has failed with sense data then run REQUEST SENSE + command to receive that data.</para> + +<programlisting> + 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) ) { + /* start auto-SENSE */ + hcb->flags |= DOING_AUTOSENSE; + setup_autosense_command_in_hcb(hcb); + restart_current_hcb(softc); + return; + } + 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); + schedule_next_hcb(softc); + return; + } +</programlisting> + + <para>One typical thing would be negotiation events: negotiation + messages received from a SCSI target (in answer to our + negotiation attempt or by target's initiative) or the target is + unable to negotiate (rejects our negotiation messages or does + not answer them).</para> + +<programlisting> + switch(hcb_status) { + case TARGET_REJECTED_WIDE_NEG: + /* revert to 8-bit bus */ + softc->current_bus_width[targ] = softc->goal_bus_width[targ] = 8; + /* report the event */ + neg.bus_width = 8; + neg.valid = CCB_TRANS_BUS_WIDTH_VALID; + xpt_async(AC_TRANSFER_NEG, hcb->ccb.ccb_h.path_id, &neg); + continue_current_hcb(softc); + return; + case TARGET_ANSWERED_WIDE_NEG: + { + int wd; + + wd = get_target_bus_width_request(softc); + if(wd <= softc->goal_bus_width[targ]) { + /* answer is acceptable */ + softc->current_bus_width[targ] = + softc->goal_bus_width[targ] = neg.bus_width = wd; + + /* report the event */ + neg.valid = CCB_TRANS_BUS_WIDTH_VALID; + xpt_async(AC_TRANSFER_NEG, hcb->ccb.ccb_h.path_id, &neg); + } else { + prepare_reject_message(hcb); + } + } + continue_current_hcb(softc); + return; + case TARGET_REQUESTED_WIDE_NEG: + { + int wd; + + wd = get_target_bus_width_request(softc); + wd = min (wd, OUR_BUS_WIDTH); + wd = min (wd, softc->user_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; + + /* report the event */ + neg.valid = CCB_TRANS_BUS_WIDTH_VALID; + xpt_async(AC_TRANSFER_NEG, hcb->ccb.ccb_h.path_id, &neg); + } + prepare_width_nego_rsponse(hcb, wd); + } + continue_current_hcb(softc); + return; + } +</programlisting> + + <para>Then we handle any errors that could have happened during + auto-sense in the same simple-minded way as before. Otherwise we + look closer at the details again.</para> + +<programlisting> + if(hcb->flags & DOING_AUTOSENSE) + goto autosense_failed; + + switch(hcb_status) { +</programlisting> + + <para>The next event we consider is unexpected disconnect. Which + is considered normal after an ABORT or BUS DEVICE RESET message + and abnormal in other cases.</para> + +<programlisting> + case UNEXPECTED_DISCONNECT: + 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]; + 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)) { + 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]; + h != NULL; h = hh) { + hh=h->next; + free_hcb_and_ccb_done(h, h->ccb, CAM_SCSI_BUS_RESET); + } + + /* send event */ + xpt_async(AC_SENT_BDR, hcb->ccb->ccb_h.path_id, NULL); + + /* this was the CAM_RESET_DEV request itself, it's completed */ + ccb_status = CAM_REQ_CMP; + } else { + calculate_residue(hcb); + ccb_status = CAM_UNEXP_BUSFREE; + /* request the further code to freeze the queue */ + hcb->ccb->ccb_h.status |= CAM_DEV_QFRZN; + lun_to_freeze = hcb->lun; + } + break; +</programlisting> + + <para>If the target refuses to accept tags we notify CAM about + that and return back all commands for this LUN:</para> + +<programlisting> + case TAGS_REJECTED: + /* report the event */ + neg.flags = 0 & ~CCB_TRANS_TAG_ENB; + neg.valid = CCB_TRANS_TQ_VALID; + xpt_async(AC_TRANSFER_NEG, hcb->ccb.ccb_h.path_id, &neg); + + ccb_status = CAM_MSG_REJECT_REC; + /* request the further code to freeze the queue */ + hcb->ccb->ccb_h.status |= CAM_DEV_QFRZN; + lun_to_freeze = hcb->lun; + break; +</programlisting> + + <para>Then we check a number of other conditions, with processing + basically limited to setting the CCB status:</para> + +<programlisting> + case SELECTION_TIMEOUT: + ccb_status = CAM_SEL_TIMEOUT; + /* request the further code to freeze the queue */ + hcb->ccb->ccb_h.status |= CAM_DEV_QFRZN; + lun_to_freeze = CAM_LUN_WILDCARD; + break; + case PARITY_ERROR: + ccb_status = CAM_UNCOR_PARITY; + break; + case DATA_OVERRUN: + case ODD_WIDE_TRANSFER: + ccb_status = CAM_DATA_RUN_ERR; + break; + default: + /* all other errors are handled in a generic way */ + ccb_status = CAM_REQ_CMP_ERR; + /* request the further code to freeze the queue */ + hcb->ccb->ccb_h.status |= CAM_DEV_QFRZN; + lun_to_freeze = CAM_LUN_WILDCARD; + break; + } +</programlisting> + + <para>Then we check if the error was serious enough to freeze the + input queue until it gets proceeded and do so if it is:</para> + +<programlisting> + 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) { + hh = h->next; + + 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); + } + } + free_hcb_and_ccb_done(hcb, hcb->ccb, ccb_status); + schedule_next_hcb(softc); + return; +</programlisting> + + <para>This concludes the generic interrupt handling although + specific controllers may require some additions.</para> + + </sect1> + + <sect1> + <title>Errors Summary</title> + + <para>When executing an I/O request many things may go wrong. The + reason of error can be reported in the CCB status with great + detail. Examples of use are spread throughout this document. For + completeness here is the summary of recommended responses for + the typical error conditions:</para> + + <itemizedlist> + + <listitem><para><emphasis>CAM_RESRC_UNAVAIL</emphasis> - some + resource is temporarily unavailable and the SIM driver can not + generate an event when it will become available. An example of + this resource would be some intra-controller hardware resource + for which the controller does not generate an interrupt when + it becomes available.</para></listitem> + + <listitem><para><emphasis>CAM_UNCOR_PARITY</emphasis> - + unrecovered parity error occurred</para></listitem> + + <listitem><para><emphasis>CAM_DATA_RUN_ERR</emphasis> - data + overrun or unexpected data phase (going in other direction + than specified in CAM_DIR_MASK) or odd transfer length for + wide transfer</para></listitem> + + <listitem><para><emphasis>CAM_SEL_TIMEOUT</emphasis> - selection + timeout occurred (target does not respond)</para></listitem> + + <listitem><para><emphasis>CAM_CMD_TIMEOUT</emphasis> - command + timeout occurred (the timeout function ran)</para></listitem> + + <listitem><para><emphasis>CAM_SCSI_STATUS_ERROR</emphasis> - the + device returned error</para></listitem> + + <listitem><para><emphasis>CAM_AUTOSENSE_FAIL</emphasis> - the + device returned error and the REQUEST SENSE COMMAND + failed</para></listitem> + + <listitem><para><emphasis>CAM_MSG_REJECT_REC</emphasis> - MESSAGE + REJECT message was received</para></listitem> + + <listitem><para><emphasis>CAM_SCSI_BUS_RESET</emphasis> - received + SCSI bus reset</para></listitem> + + <listitem><para><emphasis>CAM_REQ_CMP_ERR</emphasis> - + "impossible" SCSI phase occurred or something else as weird or + just a generic error if further detail is not + available</para></listitem> + + <listitem><para><emphasis>CAM_UNEXP_BUSFREE</emphasis> - + unexpected disconnect occurred</para></listitem> + + <listitem><para><emphasis>CAM_BDR_SENT</emphasis> - BUS DEVICE + RESET message was sent to the target</para></listitem> + + <listitem><para><emphasis>CAM_UNREC_HBA_ERROR</emphasis> - + unrecoverable Host Bus Adapter Error</para></listitem> + + <listitem><para><emphasis>CAM_REQ_TOO_BIG</emphasis> - the request + was too large for this controller</para></listitem> + + <listitem><para><emphasis>CAM_REQUEUE_REQ</emphasis> - this + request should be re-queued to preserve transaction ordering. + This typically occurs when the SIM recognizes an error that + should freeze the queue and must place other queued requests + for the target at the sim level back into the XPT + queue. Typical cases of such errors are selection timeouts, + command timeouts and other like conditions. In such cases the + troublesome command returns the status indicating the error, + the and the other commands which have not be sent to the bus + yet get re-queued.</para></listitem> + + <listitem><para><emphasis>CAM_LUN_INVALID</emphasis> - the LUN + ID in the request is not supported by the SCSI + controller</para></listitem> + + <listitem><para><emphasis>CAM_TID_INVALID</emphasis> - the + target ID in the request is not supported by the SCSI + controller</para></listitem> + </itemizedlist> + </sect1> + + <sect1> + <title>Timeout Handling</title> + + <para>When the timeout for an HCB expires that request should be + aborted, just like with an XPT_ABORT request. The only + difference is that the returned status of aborted request should + be CAM_CMD_TIMEOUT instead of CAM_REQ_ABORTED (that's why + implementation of the abort better be done as a function). But + there is one more possible problem: what if the abort request + itself will get stuck? In this case the SCSI bus should be + reset, just like with an XPT_RESET_BUS request (and the idea + about implementing it as a function called from both places + applies here too). Also we should reset the whole SCSI bus if a + device reset request got stuck. So after all the timeout + function would look like:</para> + +<programlisting> +static void +xxx_timeout(void *arg) +{ + struct xxx_hcb *hcb = (struct xxx_hcb *)arg; + struct xxx_softc *softc; + struct ccb_hdr *ccb_h; + + softc = hcb->softc; + ccb_h = &hcb->ccb->ccb_h; + + 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); + } +} +</programlisting> + + <para>When we abort a request all the other disconnected requests + to the same target/LUN get aborted too. So there appears a + question, should we return them with status CAM_REQ_ABORTED or + CAM_CMD_TIMEOUT ? The current drivers use CAM_CMD_TIMEOUT. This + seems logical because if one request got timed out then probably + something really bad is happening to the device, so if they + would not be disturbed they would time out by themselves.</para> + + </sect1> + +</chapter> diff --git a/en_US.ISO8859-1/books/developers-handbook/book.sgml b/en_US.ISO8859-1/books/developers-handbook/book.sgml index 1006c9f5d2..2cf7d725aa 100644 --- a/en_US.ISO8859-1/books/developers-handbook/book.sgml +++ b/en_US.ISO8859-1/books/developers-handbook/book.sgml @@ -1,7 +1,7 @@ <!-- The FreeBSD Documentation Project - $FreeBSD: doc/en_US.ISO_8859-1/books/developers-handbook/book.sgml,v 1.10 2000/11/28 19:07:40 asmodai Exp $ + $FreeBSD: doc/en_US.ISO_8859-1/books/developers-handbook/book.sgml,v 1.11 2000/11/29 04:15:17 jhb Exp $ --> <!DOCTYPE BOOK PUBLIC "-//FreeBSD//DTD DocBook V3.1-Based Extension//EN" [ @@ -10,6 +10,7 @@ <!ENTITY % man PUBLIC "-//FreeBSD//ENTITIES DocBook Manual Page Entities//EN"> %man; <!ENTITY % chapters SYSTEM "chapters.ent"> %chapters; +<!ENTITY % authors SYSTEM "../handbook/authors.ent"> %authors; ]> <book> @@ -31,6 +32,7 @@ <copyright> <year>2000</year> + <year>2001</year> <holder>The FreeBSD Documentation Project</holder> </copyright> @@ -319,6 +321,7 @@ &chap.driverbasics; &chap.pci; + &chap.scsi; <chapter id="usb"> <title>USB Devices</title> diff --git a/en_US.ISO8859-1/books/developers-handbook/chapters.ent b/en_US.ISO8859-1/books/developers-handbook/chapters.ent index c2149880e2..a367f40239 100644 --- a/en_US.ISO8859-1/books/developers-handbook/chapters.ent +++ b/en_US.ISO8859-1/books/developers-handbook/chapters.ent @@ -6,7 +6,7 @@ Chapters should be listed in the order in which they are referenced. - $FreeBSD: doc/en_US.ISO_8859-1/books/developers-handbook/chapters.ent,v 1.1 2000/11/28 18:03:54 asmodai Exp $ + $FreeBSD: doc/en_US.ISO_8859-1/books/developers-handbook/chapters.ent,v 1.2 2000/11/29 04:15:17 jhb Exp $ --> <!-- Part one --> @@ -44,6 +44,7 @@ <!-- Part eleven --> <!ENTITY chap.driverbasics SYSTEM "driverbasics/chapter.sgml"> <!ENTITY chap.pci SYSTEM "pci/chapter.sgml"> +<!ENTITY chap.scsi SYSTEM "scsi/chapter.sgml"> <!-- Part twelve --> <!-- No significant material yet, still in book.sgml --> diff --git a/en_US.ISO8859-1/books/developers-handbook/scsi/chapter.sgml b/en_US.ISO8859-1/books/developers-handbook/scsi/chapter.sgml new file mode 100644 index 0000000000..837a8fd60d --- /dev/null +++ b/en_US.ISO8859-1/books/developers-handbook/scsi/chapter.sgml @@ -0,0 +1,2079 @@ +<!-- + The FreeBSD Documentation Project + + $FreeBSD$ +--> + +<chapter id="scsi"> + <title>Common Access Method SCSI Controllers</title> + + <para><emphasis>This chapter was written by &a.babkin; + Modifications for the handbook made by + &a.murray;.</emphasis></para> + + <sect1> + <title>Synopsis</title> + + <para>This document assumes that the reader has a general + understanding of device drivers in FreeBSD and of the SCSI + protocol. Much of the information in this document was + extracted from the drivers :</para> + + <itemizedlist> + + <listitem><para>ncr (<filename>/sys/pci/ncr.c</filename>) by + Wolfgang Stanglmeier and Stefan Esser</para></listitem> + + <listitem><para>sym (<filename>/sys/pci/sym.c</filename>) by + Gerard Roudier</para></listitem> + + <listitem><para>aic7xxx + (<filename>/sys/dev/aic7xxx/aic7xxx.c</filename>) by Justin + T. Gibbs</listitem> + + </itemizedlist> + + <para>and from the CAM code itself (by Justing T. Gibbs, see + <filename>/sys/cam/*</filename>). When some solution looked the + most logical and was essentially verbatim extracted from the code + by Justin Gibbs, I marked it as "recommended".</para> + + <para>The document is illustrated with examples in + pseudo-code. Although sometimes the examples have many details + and look like real code, it's still pseudo-code. It was written + to demonstrate the concepts in an understandable way. For a real + driver other approaches may be more modular and efficient. It + also abstracts from the hardware details, as well as issues that + would cloud the demonstrated concepts or that are supposed to be + described in the other chapters of the developers handbook. Such + details are commonly shown as calls to functions with descriptive + names, comments or pseudo-statements. Fortunately real life + full-size examples with all the details can be found in the real + drivers.</para> + + </sect1> + + <sect1> + <title>General architecture</title> + + <para>CAM stands for Common Access Method. It's a generic way to + address the I/O buses in a SCSI-like way. This allows a + separation of the generic device drivers from the drivers + controlling the I/O bus: for example the disk driver becomes able + to control disks on both SCSI, IDE, and/or any other bus so the + disk driver portion does not have to be rewritten (or copied and + modified) for every new I/O bus. Thus the two most important + active entities are:</para> + + <itemizedlist> + <listitem><para><emphasis>Peripheral Modules</emphasis> - a + driver for peripheral devices (disk, tape, CDROM, + etc.)</para></listitem> + <listitem><para><emphasis>SCSI Interface Modules </emphasis>(SIM) + - a Host Bus Adapter drivers for connecting to an I/O bus such + as SCSI or IDE.</para></listitem> + </itemizedlist> + + <para>A peripheral driver receives requests from the OS, converts + them to a sequence of SCSI commands and passes these SCSI + commands to a SCSI Interface Module. The SCSI Interface Module + is responsible for passing these commands to the actual hardware + (or if the actual hardware is not SCSI but, for example, IDE + then also converting the SCSI commands to the native commands of + the hardware).</para> + + <para>Because we are interested in writing a SCSI adapter driver + here, from this point on we will consider everything from the + SIM standpoint.</para> + + <para>A typical SIM driver needs to include the following + CAM-related header files:</para> + +<programlisting> +#include <cam/cam.h> +#include <cam/cam_ccb.h> +#include <cam/cam_sim.h> +#include <cam/cam_xpt_sim.h> +#include <cam/cam_debug.h> +#include <cam/scsi/scsi_all.h> +</programlisting> + + <para>The first thing each SIM driver must do is register itself + with the CAM subsystem. This is done during the driver's + <function>xxx_attach()</function> function (here and further + xxx_ is used to denote the unique driver name prefix). The + <function>xxx_attach()</function> function itself is called by + the system bus auto-configuration code which we don't describe + here.</para> + + <para>This is achieved in multiple steps: first it's necessary to + allocate the queue of requests associated with this SIM:</para> + +<programlisting> + struct cam_devq *devq; + + if(( devq = cam_simq_alloc(SIZE) )==NULL) { + error; /* some code to handle the error */ + } +</programlisting> + + <para>Here SIZE is the size of the queue to be allocated, maximal + number of requests it could contain. It's the number of requests + that the SIM driver can handle in parallel on one SCSI + card. Commonly it can be calculated as:</para> + +<programlisting> +SIZE = NUMBER_OF_SUPPORTED_TARGETS * MAX_SIMULTANEOUS_COMMANDS_PER_TARGET +</programlisting> + + <para>Next we create a descriptor of our SIM:</para> + +<programlisting> + struct cam_sim *sim; + + if(( sim = cam_sim_alloc(action_func, poll_func, driver_name, + softc, unit, max_dev_transactions, + max_tagged_dev_transactions, devq) )==NULL) { + cam_simq_free(devq); + error; /* some code to handle the error */ + } +</programlisting> + + <para>Note that if we are not able to create a SIM descriptor we + free the <structname>devq</structname> also because we can do + nothing else with it and we want to conserve memory.</para> + + <para>If a SCSI card has multiple SCSI buses on it then each bus + requires its own <structname>cam_sim</structname> + structure.</para> + + <para>An interesting question is what to do if a SCSI card has + more than one SCSI bus, do we need one + <structname>devq</structname> structure per card or per SCSI + bus? The answer given in the comments to the CAM code is: + either way, as the driver's author prefers.</para> + + <para>The arguments are : + <itemizedlist> + + <listitem><para><function>action_func</function> - pointer to + the driver's <function>xxx_action</function> function. + <funcSynopsis><funcPrototype> + <funcDef>static void + <function>xxx_action</function> + </funcDef> + <paramdef> + <parameter>struct cam_sim *sim</parameter>, + <parameter>union ccb *ccb</parameter> + </paramdef> + </funcPrototype></funcSynopsis> + </para></listitem> + + <listitem><para><function>poll_func</function> - pointer to + the driver's <function>xxx_poll()</function> + <funcSynopsis><funcPrototype> + <funcDef>static void + <function>xxx_poll</function> + </funcDef> + <paramdef> + <parameter>struct cam_sim *sim</parameter> + </paramdef> + </funcPrototype></funcSynopsis> + </para></listitem> + + <listitem><para>driver_name - the name of the actual driver, + such as "ncr" or "wds"</para></listitem> + + <listitem><para><structName>softc</structName> - 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.</para></listitem> + + <listitem><para>unit - the controller unit number, for example + for controller "wds0" this number will be + 0</para></listitem> + + <listitem><para>max_dev_transactions - maximal number of + simultaneous transactions per SCSI target in the non-tagged + mode. This value will be almost universally equal to 1, with + possible exceptions only for the non-SCSI cards. Also the + drivers that hope to take advantage by preparing one + transaction while another one is executed may set it to 2 + but this does not seem to be worth the + complexity.</para></listitem> + + <listitem><para>max_tagged_dev_transactions - the same thing, + but in the tagged mode. Tags are the SCSI way to initiate + multiple transactions on a device: each transaction is + assigned a unique tag and the transaction is sent to the + device. When the device completes some transaction it sends + back the result together with the tag so that the SCSI + adapter (and the driver) can tell which transaction was + completed. This argument is also known as the maximal tag + depth. It depends on the abilities of the SCSI + adapter.</para></listitem> + </itemizedlist> + </para> + + <para>Finally we register the SCSI buses associated with our SCSI + adapter:</para> + +<programlisting> + if(xpt_bus_register(sim, bus_number) != CAM_SUCCESS) { + cam_sim_free(sim, /*free_devq*/ TRUE); + error; /* some code to handle the error */ + } +</programlisting> + + <para>If there is one <structName>devq</structName> structure per + SCSI bus (i.e. we consider a card with multiple buses as + multiple cards with one bus each) then the bus number will + always be 0, otherwise each bus on the SCSI card should be get a + distinct number. Each bus needs its own separate structure + cam_sim.</para> + + <para>After that our controller is completely hooked to the CAM + system. The value of <structName>devq</structName> can be + discarded now: sim will be passed as an argument in all further + calls from CAM and devq can be derived from it.</para> + + <para>CAM provides the framework for such asynchronous + events. Some events originate from the lower levels (the SIM + drivers), some events originate from the peripheral drivers, + some events originate from the CAM subsystem itself. Any driver + can register callbacks for some types of the asynchronous + events, so that it would be notified if these events + occur.</para> + + <para>A typical example of such an event is a device reset. Each + transaction and event identifies the devices to which it applies + by the means of "path". The target-specific events normally + occur during a transaction with this device. So the path from + that transaction may be re-used to report this event (this is + safe because the event path is copied in the event reporting + routine but not deallocated nor passed anywhere further). Also + it's safe to allocate paths dynamically at any time including + the interrupt routines, although that incurs certain overhead, + and a possible problem with this approach is that there may be + no free memory at that time. For a bus reset event we need to + define a wildcard path including all devices on the bus. So we + can create the path for the future bus reset events in advance + and avoid problems with the future memory shortage:</para> + +<programlisting> + struct cam_path *path; + + 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)); + cam_sim_free(sim, /*free_devq*/TRUE); + error; /* some code to handle the error */ + } + + softc->wpath = path; + softc->sim = sim; +</programlisting> + + <para>As you can see the path includes:</para> + + <itemizedlist> + <listitem><para>ID of the peripheral driver (NULL here because we have + none)</para></listitem> + + <listitem><para>ID of the SIM driver + (<function>cam_sim_path(sim)</function>)</listitem> + + <listitem><para>SCSI target number of the device (CAM_TARGET_WILDCARD + means "all devices")</para></listitem> + + <listitem><para>SCSI LUN number of the subdevice (CAM_LUN_WILDCARD means + "all LUNs")</para></listitem> + </itemizedlist> + + <para>If the driver can't allocate this path it won't be able to + work normally, so in that case we dismantle that SCSI + bus.</para> + + <para>And we save the path pointer in the + <structName>softc</structName> structure for future use. After + that we save the value of sim (or we can also discard it on the + exit from <function>xxx_probe()</function> if we wish).</para> + + <para>That's all for a minimalistic initialization. To do things + right there is one more issue left. </para> + + <para>For a SIM driver there is one particularly interesting + event: when a target device is considered lost. In this case + resetting the SCSI negotiations with this device may be a good + idea. So we register a callback for this event with CAM. The + request is passed to CAM by requesting CAM action on a CAM + control block for this type of request:</para> + +<programlisting> + struct ccb_setasync csa; + + xpt_setup_ccb(&csa.ccb_h, path, /*priority*/5); + csa.ccb_h.func_code = XPT_SASYNC_CB; + csa.event_enable = AC_LOST_DEVICE; + csa.callback = xxx_async; + csa.callback_arg = sim; + xpt_action((union ccb *)&csa); +</programlisting> + + <para>Now we take a look at the <function>xxx_action()</function> + and <function>xxx_poll()</function> driver entry points.</para> + + <para> + <funcSynopsis><funcPrototype> + <funcDef>static void + <function>xxx_action</function> + </funcDef> + <paramdef> + <parameter>struct cam_sim *sim</parameter>, + <parameter>union ccb *ccb</parameter> + </paramdef> + </funcPrototype></funcSynopsis> + </para> + + <para>Do some action on request of the CAM subsystem. Sim + describes the SIM for the request, CCB is the request + itself. CCB stands for "CAM Control Block". It is a union of + many specific instances, each describing arguments for some type + of transactions. All of these instances share the CCB header + where the common part of arguments is stored.</para> + + <para>CAM supports the SCSI controllers working in both initiator + ("normal") mode and target (simulating a SCSI device) mode. Here + we only consider the part relevant to the initiator mode.</para> + + <para>There are a few function and macros (in other words, + methods) defined to access the public data in the struct sim:</para> + + <itemizedlist> + <listitem><para><function>cam_sim_path(sim)</function> - the + path ID (see above)</para></listitem> + + <listitem><para><function>cam_sim_name(sim)</function> - the + name of the sim</para></listitem> + + <listitem><para><function>cam_sim_softc(sim)</function> - the + pointer to the softc (driver private data) + structure</para></listitem> + + <listitem><para><function> cam_sim_unit(sim)</function> - the + unit number</para></listitem> + + <listitem><para><function> cam_sim_bus(sim)</function> - the bus + ID</para></listitem> + </itemizedlist> + + <para>To identify the device, <function>xxx_action()</function> can + get the unit number and pointer to its structure softc using + these functions.</para> + + <para>The type of request is stored in + <structField>ccb->ccb_h.func_code</structField>. So generally + <function>xxx_action()</function> consists of a big + switch:</para> + +<programlisting> + struct xxx_softc *softc = (struct xxx_softc *) cam_sim_softc(sim); + struct ccb_hdr *ccb_h = &ccb->ccb_h; + int unit = cam_sim_unit(sim); + int bus = cam_sim_bus(sim); + + switch(ccb_h->func_code) { + case ...: + ... + default: + ccb_h->status = CAM_REQ_INVALID; + xpt_done(ccb); + break; + } +</programlisting> + + <para>As can be seen from the default case (if an unknown command + was received) the return code of the command is set into + <structField>ccb->ccb_h.status</structField> and the completed + CCB is returned back to CAM by calling + <function>xpt_done(ccb)</function>. </para> + + <para><function>xpt_done()</function> does not have to be called + from <function>xxx_action()</function>: For example an I/O + request may be enqueued inside the SIM driver and/or its SCSI + controller. Then when the device would post an interrupt + signaling that the processing of this request is complete + <function>xpt_done()</function> may be called from the interrupt + handling routine.</para> + + <para>Actually, the CCB status is not only assigned as a return + code but a CCB has some status all the time. Before CCB is + passed to the <function>xxx_action()</function> routine it gets + the status CCB_REQ_INPROG meaning that it's in progress. There + are a surprising number of status values defined in + <filename>/sys/cam/cam.h</filename> which should be able to + represent the status of a request in great detail. More + interesting yet, the status is in fact a "bitwise or" of an + enumerated status value (the lower 6 bits) and possible + additional flag-like bits (the upper bits). The enumerated + values will be discussed later in more detail. The summary of + them can be found in the Errors Summary section. The possible + status flags are:</para> + + <itemizedlist> + + <listitem><para><emphasis>CAM_DEV_QFRZN</emphasis> - if the + SIM driver gets a serious error (for example, the device does + not respond to the selection or breaks the SCSI protocol) when + processing a CCB it should freeze the request queue by calling + <function>xpt_freeze_simq()</function>, return the other + enqueued but not processed yet CCBs for this device back to + the CAM queue, then set this flag for the troublesome CCB and + call <function>xpt_done()</function>. This flag causes the CAM + subsystem to unfreeze the queue after it handles the + error.</para></listitem> + + <listitem><para><emphasis>CAM_AUTOSNS_VALID</emphasis> - if + the device returned an error condition and the flag + CAM_DIS_AUTOSENSE is not set in CCB the SIM driver must + execute the REQUEST SENSE command automatically to extract the + sense (extended error information) data from the device. If + this attempt was successful the sense data should be saved in + the CCB and this flag set.</para></listitem> + + <listitem><para><emphasis>CAM_RELEASE_SIMQ</emphasis> - like + CAM_DEV_QFRZN but used in case there is some problem (or + resource shortage) with the SCSI controller itself. Then all + the future requests to the controller should be stopped by + <function>xpt_freeze_simq()</function>. The controller queue + will be restarted after the SIM driver overcomes the shortage + and informs CAM by returning some CCB with this flag + set.</para></listitem> + + <listitem><para><emphasis>CAM_SIM_QUEUED</emphasis> - when SIM + puts a CCB into its request queue this flag should be set (and + removed when this CCB gets dequeued before being returned back + to CAM). This flag is not used anywhere in the CAM code now, + so its purpose is purely diagnostic.</para></listitem> + + </itemizedlist> + + <para>The function <function>xxx_action()</function> is not + allowed to sleep, so all the synchronization for resource access + must be done using SIM or device queue freezing. Besides the + aforementioned flags the CAM subsystem provides functions + <function>xpt_selease_simq()</function> and + <function>xpt_release_devq()</function> to unfreeze the queues + directly, without passing a CCB to CAM.</para> + + <para>The CCB header contains the following fields:</para> + + <itemizedlist> + + <listitem><para><emphasis>path</emphasis> - path ID for the + request</para></listitem> + + <listitem><para><emphasis>target_id</emphasis> - target device + ID for the request</para></listitem> + + <listitem><para><emphasis>target_lun</emphasis> - LUN ID of + the target device</para></listitem> + + <listitem><para><emphasis>timeout</emphasis> - timeout + interval for this command, in milliseconds</para></listitem> + + <listitem><para><emphasis>timeout_ch</emphasis> - a + convenience place for the SIM driver to store the timeout handle + (the CAM subsystem itself does not make any assumptions about + it)</para></listitem> + + <listitem><para><emphasis>flags</emphasis> - various bits of + information about the request spriv_ptr0, spriv_ptr1 - fields + reserved for private use by the SIM driver (such as linking to + the SIM queues or SIM private control blocks); actually, they + exist as unions: spriv_ptr0 and spriv_ptr1 have the type (void + *), spriv_field0 and spriv_field1 have the type unsigned long, + sim_priv.entries[0].bytes and sim_priv.entries[1].bytes are byte + arrays of the size consistent with the other incarnations of the + union and sim_priv.bytes is one array, twice + bigger.</para></listitem> + + </itemizedlist> + + <para>The recommended way of using the SIM private fields of CCB + is to define some meaningful names for them and use these + meaningful names in the driver, like:</para> + +<programlisting> +#define ccb_some_meaningful_name sim_priv.entries[0].bytes +#define ccb_hcb spriv_ptr1 /* for hardware control block */ +</programlisting> + + <para>The most common initiator mode requests are:</para> + <itemizedlist> + <listitem><para><emphasis>XPT_SCSI_IO</emphasis> - execute an + I/O transaction</para> + + <para>The instance "struct ccb_scsiio csio" of the union ccb is + used to transfer the arguments. They are:</para> + + <itemizedlist> + <listitem><para><emphasis>cdb_io</emphasis> - pointer to + the SCSI command buffer or the buffer + itself</para></listitem> + + <listitem><para><emphasis>cdb_len</emphasis> - SCSI + command length</para></listitem> + + <listitem><para><emphasis>data_ptr</emphasis> - pointer to + the data buffer (gets a bit complicated if scatter/gather is + used)</para></listitem> + + <listitem><para><emphasis>dxfer_len</emphasis> - length of + the data to transfer</para></listitem> + + <listitem><para><emphasis>sglist_cnt</emphasis> - counter + of the scatter/gather segments</para></listitem> + + <listitem><para><emphasis>scsi_status</emphasis> - place + to return the SCSI status</para></listitem> + + <listitem><para><emphasis>sense_data</emphasis> - 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)</para></listitem> + + <listitem><para><emphasis>sense_len</emphasis> - 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.</para></listitem> + + <listitem><para><emphasis>tag_action</emphasis> - the kind + of tag to use: + + <itemizedlist> + <listitem><para>CAM_TAG_ACTION_NONE - don't use tags for this + transaction</para></listitem> + <listitem><para>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</para></listitem> + </itemizedlist> + + </para></listitem> + + </itemizedlist> + + <para>The general logic of handling this request is the + following:</para> + + <para>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:</para> + +<programlisting> + struct ccb_scsiio *csio = &ccb->csio; + + if ((ccb_h->status & CAM_STATUS_MASK) != CAM_REQ_INPROG) { + xpt_done(ccb); + return; + } +</programlisting> + + <para>Also we check that the device is supported at all by our + controller:</para> + +<programlisting> + 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) { + ccb_h->status = CAM_LUN_INVALID; + xpt_done(ccb); + return; + } +</programlisting> + + <para>Then allocate whatever data structures (such as + card-dependent hardware control block) we need to process this + request. If we can't 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.</para> + +<programlisting> + struct xxx_hcb *hcb = allocate_hcb(softc, unit, bus); + + if(hcb == NULL) { + softc->flags |= RESOURCE_SHORTAGE; + xpt_freeze_simq(sim, /*count*/1); + ccb_h->status = CAM_REQUEUE_REQ; + xpt_done(ccb); + return; + } + + hcb->ccb = ccb; ccb_h->ccb_hcb = (void *)hcb; + ccb_h->status |= CAM_SIM_QUEUED; +</programlisting> + + <para>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.</para> + +<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 ) + generate_unique_tag_message(hcb, ccb_h->tag_action); + if( !target_negotiated(hcb) ) + generate_negotiation_messages(hcb); +</programlisting> + + <para>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.</para> + + <para>A NOT-QUITE RELATED NOTE: Normally this is done by a call + to vtophys(), but for the PCI device (which account for most + of the SCSI controllers now) drivers' portability to the Alpha + architecture the conversion must be done by vtobus() instead + due to special Alpha quirks. [IMHO it would be much better to + have two separate functions, vtop() and ptobus() then vtobus() + would be a simple superposition of them.] In case if a + physical address is requested it's OK to return the CCB with + the status CAM_REQ_INVALID, the current drivers do that. But + it's also possible to compile the Alpha-specific piece of + code, as in this example (there should be a more direct way to + do that, without conditional compilation in the drivers). If + necessary a physical address can be also converted or mapped + back to a virtual address but with big pain, so we don't do + that.</para> + +<programlisting> + if(ccb_h->flags & CAM_CDB_POINTER) { + /* CDB is a pointer */ + if(!(ccb_h->flags & CAM_CDB_PHYS)) { + /* CDB pointer is virtual */ + hcb->cmd = vtobus(csio->cdb_io.cdb_ptr); + } else { + /* CDB pointer is physical */ +#if defined(__alpha__) + hcb->cmd = csio->cdb_io.cdb_ptr | alpha_XXX_dmamap_or ; +#else + hcb->cmd = csio->cdb_io.cdb_ptr ; +#endif + } + } else { + /* CDB is in the ccb (buffer) */ + hcb->cmd = vtobus(csio->cdb_io.cdb_bytes); + } + hcb->cmdlen = csio->cdb_len; +</programlisting> + + <para>Now it's 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:</para> + +<programlisting> + int dir = (ccb_h->flags & CAM_DIR_MASK); + + if (dir == CAM_DIR_NONE) + goto end_data; +</programlisting> + + <para>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. We use a special + function to return the CCB to handle in one place the HCB + resource shortages. The functions to add chunks are + driver-dependent, and here we leave them without detailed + implementation. See description of the SCSI command (CDB) + handling for the details on the address-translation issues. + If some variation is too difficult or impossible to implement + with a particular card it's 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's actively used by CAM.</para> + +<programlisting> + int rv; + + initialize_hcb_for_data(hcb); + + if((!(ccb_h->flags & CAM_SCATTER_VALID)) { + /* single buffer */ + if(!(ccb_h->flags & CAM_DATA_PHYS)) { + rv = add_virtual_chunk(hcb, csio->data_ptr, csio->dxfer_len, dir); + } + } else { + rv = add_physical_chunk(hcb, csio->data_ptr, csio->dxfer_len, dir); + } + } else { + int i; + struct bus_dma_segment *segs; + segs = (struct bus_dma_segment *)csio->data_ptr; + + if ((ccb_h->flags & CAM_SG_LIST_PHYS) != 0) { + /* The SG list pointer is physical */ + rv = setup_hcb_for_physical_sg_list(hcb, segs, csio->sglist_cnt); + } else if (!(ccb_h->flags & CAM_DATA_PHYS)) { + /* SG buffer pointers are virtual */ + for (i = 0; i < csio->sglist_cnt; i++) { + rv = add_virtual_chunk(hcb, segs[i].ds_addr, + segs[i].ds_len, dir); + if (rv != CAM_REQ_CMP) + break; + } + } else { + /* SG buffer pointers are physical */ + for (i = 0; i < csio->sglist_cnt; i++) { + rv = add_physical_chunk(hcb, segs[i].ds_addr, + segs[i].ds_len, dir); + if (rv != CAM_REQ_CMP) + break; + } + } + } + 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), + * CAM_REQ_INVALID in case of other troubles + */ + free_hcb_and_ccb_done(hcb, ccb, rv); + return; + } + end_data: +</programlisting> + + <para>If disconnection is disabled for this CCB we pass this + information to the hcb:</para> + +<programlisting> + if(ccb_h->flags & CAM_DIS_DISCONNECT) + hcb_disable_disconnect(hcb); +</programlisting> + + <para>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.</para> + + <para>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).</para> + +<programlisting> + ccb_h->timeout_ch = timeout(xxx_timeout, (caddr_t) hcb, + (ccb_h->timeout * hz) / 1000); /* convert milliseconds to ticks */ + put_hcb_into_hardware_queue(hcb); + return; +</programlisting> + + <para>And here is a possible implementation of the function + returning CCB:</para> + +<programlisting> + static void + free_hcb_and_ccb_done(struct xxx_hcb *hcb, union ccb *ccb, u_int32_t status) + { + struct xxx_softc *softc = hcb->softc; + + ccb->ccb_h.ccb_hcb = 0; + 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) { + softc->flags &= ~RESOURCE_SHORTAGE; + status |= CAM_RELEASE_SIMQ; + } + free_hcb(hcb); /* also removes hcb from any internal lists */ + } + ccb->ccb_h.status = status | + (ccb->ccb_h.status & ~(CAM_STATUS_MASK|CAM_SIM_QUEUED)); + xpt_done(ccb); + } +</programlisting> + </listitem> + + <listitem><para><emphasis>XPT_RESET_DEV</emphasis> - send the SCSI "BUS + DEVICE RESET" message to a device</para> + + <para>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).</para> + + <para>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.</para></listitem> + + <listitem><para><emphasis>XPT_RESET_BUS</emphasis> - send the RESET signal + to the SCSI bus</para> + + <para>No arguments are passed in the CCB, the only interesting + argument is the SCSI bus indicated by the struct sim + pointer.</para> + + <para>A minimalistic implementation would forget the SCSI + negotiations for all the devices on the bus and return the + status CAM_REQ_CMP.</para> + + <para>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:</para> + +<programlisting> + int targ, lun; + struct xxx_hcb *h, *hh; + struct ccb_trans_settings neg; + struct cam_path *path; + + /* The SCSI bus reset may take a long time, in this case its completion + * should be checked by interrupt or timeout. But for simplicity + * we assume here that it's really fast. + */ + reset_scsi_bus(softc); + + /* drop all enqueued CCBs */ + 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); + } + + /* the clean values of negotiations to report */ + neg.bus_width = 8; + neg.sync_period = neg.sync_offset = 0; + neg.valid = (CCB_TRANS_BUS_WIDTH_VALID + | 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++) { + clean_negotiations(softc, targ); + + /* report the event if possible */ + 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) { + hh=h->next; + free_hcb_and_ccb_done(h, h->ccb, CAM_SCSI_BUS_RESET); + } + } + + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + + /* report the event */ + xpt_async(AC_BUS_RESET, softc->wpath, NULL); + return; +</programlisting> + + <para>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.</para></listitem> + + <listitem><para><emphasis>XPT_ABORT</emphasis> - abort the specified + CCB</para> + + <para>The arguments are transferred in the instance "struct + ccb_abort cab" of the union ccb. The only argument field in it + is:</para> + + <para><emphasis>abort_ccb</emphasis> - pointer to the CCB to be + aborted</para> + + <para>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.</para> + + <para>The hard way is to implement this request honestly. First + check that abort applies to a SCSI transaction:</para> + +<programlisting> + struct ccb *abort_ccb; + abort_ccb = ccb->cab.abort_ccb; + + if(abort_ccb->ccb_h.func_code != XPT_SCSI_IO) { + ccb->ccb_h.status = CAM_UA_ABORT; + xpt_done(ccb); + return; + } +</programlisting> + + <para>Then it's 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:</para> + +<programlisting> + struct xxx_hcb *hcb, *h; + + hcb = NULL; + + /* We assume that softc->first_hcb is the head of the list of all + * 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) { + hcb = h; + break; + } + } + + if(hcb == NULL) { + /* no such CCB in our queue */ + ccb->ccb_h.status = CAM_PATH_INVALID; + xpt_done(ccb); + return; + } + + hcb=found_hcb; +</programlisting> + + <para>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 don't 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.</para> + +<programlisting> + int hstatus; + + /* shown as a function, in case special action is needed to make + * this flag visible to hardware + */ + set_hcb_flags(hcb, HCB_BEING_ABORTED); + + abort_again: + + hstatus = get_hcb_status(hcb); + switch(hstatus) { + case HCB_SITTING_IN_QUEUE: + remove_hcb_from_hardware_queue(hcb); + /* FALLTHROUGH */ + case HCB_COMPLETED: + /* this is an easy case */ + free_hcb_and_ccb_done(hcb, abort_ccb, CAM_REQ_ABORTED); + break; +</programlisting> + + <para>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. + Because 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).</para> + +<programlisting> + case HCB_BEING_TRANSFERRED: + untimeout(xxx_timeout, (caddr_t) hcb, abort_ccb->ccb_h.timeout_ch); + abort_ccb->ccb_h.timeout_ch = + timeout(xxx_timeout, (caddr_t) hcb, 10 * hz); + abort_ccb->ccb_h.status = CAM_REQ_ABORTED; + /* ask the controller to abort that HCB, then generate + * an interrupt and stop + */ + 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; + } + + break; +</programlisting> + + <para>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.</para> + +<programlisting> + case HCB_DISCONNECTED: + untimeout(xxx_timeout, (caddr_t) hcb, abort_ccb->ccb_h.timeout_ch); + abort_ccb->ccb_h.timeout_ch = + timeout(xxx_timeout, (caddr_t) hcb, 10 * hz); + put_abort_message_into_hcb(hcb); + put_hcb_at_the_front_of_hardware_queue(hcb); + break; + } + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + return; +</programlisting> + + <para>That's all for the ABORT request, although there is one more + issue. Because 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.</para> + + <para>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:</para> + +<programlisting> + case XPT_ABORT: + struct ccb *abort_ccb; + abort_ccb = ccb->cab.abort_ccb; + + 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) + /* no such CCB in our queue */ + ccb->ccb_h.status = CAM_PATH_INVALID; + else + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + return; +</programlisting> + </listitem> + + <listitem><para><emphasis>XPT_SET_TRAN_SETTINGS</emphasis> - explicitly + set values of SCSI transfer settings</para> + + <para>The arguments are transferred in the instance "struct ccb_trans_setting cts" +of the union ccb:</para> + + <itemizedlist> + <listitem><para><emphasis>valid</emphasis> - a bitmask showing + which settings should be updated:</para></listitem> + + <listitem><para><emphasis>CCB_TRANS_SYNC_RATE_VALID</emphasis> + - synchronous transfer rate</para></listitem> + + <listitem><para><emphasis>CCB_TRANS_SYNC_OFFSET_VALID</emphasis> + - synchronous offset</para></listitem> + + <listitem><para><emphasis>CCB_TRANS_BUS_WIDTH_VALID</emphasis> + - bus width</para></listitem> + + <listitem><para><emphasis>CCB_TRANS_DISC_VALID</emphasis> - + set enable/disable disconnection</para></listitem> + + <listitem><para><emphasis>CCB_TRANS_TQ_VALID</emphasis> - set + enable/disable tagged queuing</para></listitem> + + <listitem><para><emphasis>flags</emphasis> - consists of two + parts, binary arguments and identification of + sub-operations. The binary arguments are :</para> + <itemizedlist> + <listitem><para><emphasis>CCB_TRANS_DISC_ENB</emphasis> - enable disconnection</para></listitem> + <listitem><para><emphasis>CCB_TRANS_TAG_ENB</emphasis> - + enable tagged queuing</para></listitem> + </itemizedlist> + </listitem> + + <listitem><para>the sub-operations are:</para> + <itemizedlist> + <listitem><para><emphasis>CCB_TRANS_CURRENT_SETTINGS</emphasis> + - change the current negotiations</para></listitem> + + <listitem><para><emphasis>CCB_TRANS_USER_SETTINGS</emphasis> + - 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)</para></listitem> + </itemizedlist> + </listitem> + + </itemizedlist> + + <para>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's essentially the + top boundary.</para> + + <para>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.</para> + + <para>One caveat is that the bus width and synchronous parameters + are per target while the disconnection and tag enabling + parameters are per lun.</para> + + <para>The recommended implementation is to keep 3 sets of + negotiated (bus width and synchronous transfer) + parameters:</para> + + <itemizedlist> + <listitem><para><emphasis>user</emphasis> - the user set, as + above</para></listitem> + + <listitem><para><emphasis>current</emphasis> - those actually + in effect</para></listitem> + + <listitem><para><emphasis>goal</emphasis> - those requested by + setting of the "current" parameters</para></listitem> + </itemizedlist> + + <para>The code looks like:</para> + +<programlisting> + struct ccb_trans_settings *cts; + int targ, lun; + int flags; + + cts = &ccb->cts; + 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) + softc->user_sync_period[targ] = cts->sync_period; + if(flags & CCB_TRANS_SYNC_OFFSET_VALID) + softc->user_sync_offset[targ] = cts->sync_offset; + if(flags & CCB_TRANS_BUS_WIDTH_VALID) + softc->user_bus_width[targ] = cts->bus_width; + + 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) { + 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) + softc->goal_sync_period[targ] = + max(cts->sync_period, OUR_MIN_SUPPORTED_PERIOD); + 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) + softc->goal_bus_width[targ] = min(cts->bus_width, OUR_BUS_WIDTH); + + 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) { + softc->current_tflags[targ][lun] &= ~CCB_TRANS_TQ_ENB; + softc->current_tflags[targ][lun] |= flags & CCB_TRANS_TQ_ENB; + } + } + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + return; +</programlisting> + + <para>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:</para> + +<programlisting> + int + target_negotiated(struct xxx_hcb *hcb) + { + struct softc *softc = hcb->softc; + int targ = hcb->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] ) + return 0; /* FALSE */ + else + return 1; /* TRUE */ + } +</programlisting> + + <para>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 <function>target_negotiated()</function> would return + TRUE. When the card is initialized (in + <function>xxx_attach()</function>) 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.</para></listitem> + + <listitem><para><emphasis>XPT_GET_TRAN_SETTINGS</emphasis> - get values of + SCSI transfer settings</para> + + <para>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.</para></listitem> + + <listitem><para><emphasis>XPT_CALC_GEOMETRY</emphasis> - calculate logical + (BIOS) geometry of the disk</para> + + <para>The arguments are transferred in the instance "struct + ccb_calc_geometry ccg" of the union ccb:</para> + + <itemizedlist> + + <listitem><para><emphasis>block_size</emphasis> - input, block + (A.K.A sector) size in bytes</para></listitem> + + <listitem><para><emphasis>volume_size</emphasis> - input, + volume size in bytes</para></listitem> + + <listitem><para><emphasis>cylinders</emphasis> - output, + logical cylinders</para></listitem> + + <listitem><para><emphasis>heads</emphasis> - output, logical + heads</para></listitem> + + <listitem><para><emphasis>secs_per_track</emphasis> - output, + logical sectors per track</para></listitem> + + </itemizedlist> + + <para>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:</para> + +<programlisting> + struct ccb_calc_geometry *ccg; + u_int32_t size_mb; + u_int32_t secs_per_cylinder; + int extended; + + ccg = &ccb->ccg; + size_mb = ccg->volume_size + / ((1024L * 1024L) / ccg->block_size); + extended = check_cards_EEPROM_for_extended_geometry(softc); + + if (size_mb > 1024 && extended) { + ccg->heads = 255; + ccg->secs_per_track = 63; + } else { + ccg->heads = 64; + ccg->secs_per_track = 32; + } + secs_per_cylinder = ccg->heads * ccg->secs_per_track; + ccg->cylinders = ccg->volume_size / secs_per_cylinder; + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + return; +</programlisting> + + <para>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:</para> + +<programlisting> + 128 heads, 63 sectors - Symbios controllers + 16 heads, 63 sectors - old controllers +</programlisting> + + <para>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.</para> + </listitem> + + <listitem><para><emphasis>XPT_PATH_INQ</emphasis> - path inquiry, in other + words get the SIM driver and SCSI controller (also known as HBA + - Host Bus Adapter) properties</para> + + <para>The properties are returned in the instance "struct +ccb_pathinq cpi" of the union ccb:</para> + + <itemizedlist> + + <listitem><para>version_num - the SIM driver version number, now + all drivers use 1</para></listitem> + + <listitem><para>hba_inquiry - bitmask of features supported by + the controller:</para></listitem> + + <listitem><para>PI_MDP_ABLE - supports MDP message (something + from SCSI3?)</para></listitem> + + <listitem><para>PI_WIDE_32 - supports 32 bit wide + SCSI</para></listitem> + + <listitem><para>PI_WIDE_16 - supports 16 bit wide + SCSI</para></listitem> + + <listitem><para>PI_SDTR_ABLE - can negotiate synchronous + transfer rate</para></listitem> + + <listitem><para>PI_LINKED_CDB - supports linked + commands</para></listitem> + + <listitem><para>PI_TAG_ABLE - supports tagged + commands</para></listitem> + + <listitem><para>PI_SOFT_RST - supports soft reset alternative + (hard reset and soft reset are mutually exclusive within a + SCSI bus)</para></listitem> + + <listitem><para>target_sprt - flags for target mode support, 0 + if unsupported</para></listitem> + + <listitem><para>hba_misc - miscellaneous controller + features:</para></listitem> + + <listitem><para>PIM_SCANHILO - bus scans from high ID to low + ID</para></listitem> + + <listitem><para>PIM_NOREMOVE - removable devices not included in + scan</para></listitem> + + <listitem><para>PIM_NOINITIATOR - initiator role not + supported</para></listitem> + + <listitem><para>PIM_NOBUSRESET - user has disabled initial BUS + RESET</para></listitem> + + <listitem><para>hba_eng_cnt - mysterious HBA engine count, + something related to compression, now is always set to + 0</para></listitem> + + <listitem><para>vuhba_flags - vendor-unique flags, unused + now</para></listitem> + + <listitem><para>max_target - maximal supported target ID (7 for + 8-bit bus, 15 for 16-bit bus, 127 for Fibre + Channel)</para></listitem> + + <listitem><para>max_lun - maximal supported LUN ID (7 for older + SCSI controllers, 63 for newer ones)</para></listitem> + + <listitem><para>async_flags - bitmask of installed Async + handler, unused now</para></listitem> + + <listitem><para>hpath_id - highest Path ID in the subsystem, + unused now</para></listitem> + + <listitem><para>unit_number - the controller unit number, + cam_sim_unit(sim)</para></listitem> + + <listitem><para>bus_id - the bus number, + cam_sim_bus(sim)</para></listitem> + + <listitem><para>initiator_id - the SCSI ID of the controller + itself</para></listitem> + + <listitem><para>base_transfer_speed - nominal transfer speed in + KB/s for asynchronous narrow transfers, equals to 3300 for + SCSI</para></listitem> + + <listitem><para>sim_vid - SIM driver's vendor id, a + zero-terminated string of maximal length SIM_IDLEN including + the terminating zero</para></listitem> + + <listitem><para>hba_vid - SCSI controller's vendor id, a + zero-terminated string of maximal length HBA_IDLEN including + the terminating zero</para></listitem> + + <listitem><para>dev_name - device driver name, a zero-terminated + string of maximal length DEV_IDLEN including the terminating + zero, equal to cam_sim_name(sim)</para></listitem> + + </itemizedlist> + + <para>The recommended way of setting the string fields is using + strncpy, like:</para> + +<programlisting> + strncpy(cpi->dev_name, cam_sim_name(sim), DEV_IDLEN); +</programlisting> + + <para>After setting the values set the status to CAM_REQ_CMP and mark the +CCB as done.</para> + </listitem> + </itemizedlist> + + </sect1> + + <sect1> + <title>Polling</title> + + <funcSynopsis><funcPrototype> + <funcDef>static void + <function>xxx_poll</function> + </funcDef> + <paramdef> + <parameter>struct cam_sim *sim</parameter> + </paramdef> + </funcPrototype></funcSynopsis> + + <para>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. So all it needs to do is to call the interrupt + routine (or the other way around, the poll routine may be doing + the real action and the interrupt routine would just call the + poll routine). Why bother about a separate function then ? + Because of different calling conventions. The + <function>xxx_poll</function> routine gets the struct cam_sim + pointer as its argument when the PCI interrupt routine by common + convention gets pointer to the struct + <structName>xxx_softc</structName> and the ISA interrupt routine + gets just the the device unit number. So the poll routine would + normally look as:</para> + +<programlisting> +static void +xxx_poll(struct cam_sim *sim) +{ + xxx_intr((struct xxx_softc *)cam_sim_softc(sim)); /* for PCI device */ +} +</programlisting> + + <para>or</para> + +<programlisting> +static void +xxx_poll(struct cam_sim *sim) +{ + xxx_intr(cam_sim_unit(sim)); /* for ISA device */ +} +</programlisting> + + </sect1> + + <sect1> + <title>Asynchronous Events</title> + + <para>If an asynchronous event callback has been set up then the + callback function should be defined.</para> + +<programlisting> +static void +ahc_async(void *callback_arg, u_int32_t code, struct cam_path *path, void *arg) +</programlisting> + + <itemizedlist> + <listitem><para>callback_arg - the value supplied when registering the + callback</para></listitem> + + <listitem><para>code - identifies the type of event</para></listitem> + + <listitem><para>path - identifies the devices to which the event + applies</para></listitem> + + <listitem><para>arg - event-specific argument</para></listitem> + </itemizedlist> + + <para>Implementation for a single type of event, AC_LOST_DEVICE, + looks like:</para> + +<programlisting> + struct xxx_softc *softc; + struct cam_sim *sim; + int targ; + struct ccb_trans_settings neg; + + sim = (struct cam_sim *)callback_arg; + softc = (struct xxx_softc *)cam_sim_softc(sim); + switch (code) { + case AC_LOST_DEVICE: + targ = xpt_path_target_id(path); + if(targ <= OUR_MAX_SUPPORTED_TARGET) { + clean_negotiations(softc, targ); + /* send indication to CAM */ + neg.bus_width = 8; + neg.sync_period = neg.sync_offset = 0; + neg.valid = (CCB_TRANS_BUS_WIDTH_VALID + | CCB_TRANS_SYNC_RATE_VALID | CCB_TRANS_SYNC_OFFSET_VALID); + xpt_async(AC_TRANSFER_NEG, path, &neg); + } + break; + default: + break; + } +</programlisting> + + </sect1> + + <sect1> + <title>Interrupts</title> + + <para>The exact type of the interrupt routine depends on the type + of the peripheral bus (PCI, ISA and so on) to which the SCSI + controller is connected.</para> + + <para>The interrupt routines of the SIM drivers run at the + interrupt level splcam. So <function>splcam()</function> should + be used in the driver to synchronize activity between the + interrupt routine and the rest of the driver (for a + multiprocessor-aware driver things get yet more interesting but + we ignore this case here). The pseudo-code in this document + happily ignores the problems of synchronization. The real code + must not ignore them. A simple-minded approach is to set + <function>splcam()</function> on the entry to the other routines + and reset it on return thus protecting them by one big critical + section. To make sure that the interrupt level will be always + restored a wrapper function can be defined, like:</para> + +<programlisting> + static void + xxx_action(struct cam_sim *sim, union ccb *ccb) + { + int s; + s = splcam(); + xxx_action1(sim, ccb); + splx(s); + } + + static void + xxx_action1(struct cam_sim *sim, union ccb *ccb) + { + ... process the request ... + } +</programlisting> + + <para>This approach is simple and robust but the problem with it + is that interrupts may get blocked for a relatively long time + and this would negatively affect the system's performance. On + the other hand the functions of the <function>spl()</function> + family have rather high overhead, so vast amount of tiny + critical sections may not be good either.</para> + + <para>The conditions handled by the interrupt routine and the + details depend very much on the hardware. We consider the set of + "typical" conditions.</para> + + <para>First, we check if a SCSI reset was encountered on the bus + (probably caused by another SCSI controller on the same SCSI + bus). If so we drop all the enqueued and disconnected requests, + report the events and re-initialize our SCSI controller. It is + important that during this initialization the controller won't + issue another reset or else two controllers on the same SCSI bus + could ping-pong resets forever. The case of fatal controller + error/hang could be handled in the same place, but it will + probably need also sending RESET signal to the SCSI bus to reset + the status of the connections with the SCSI devices.</para> + +<programlisting> + int fatal=0; + struct ccb_trans_settings neg; + struct cam_path *path; + + if( detected_scsi_reset(softc) + || (fatal = detected_fatal_controller_error(softc)) ) { + int targ, lun; + struct xxx_hcb *h, *hh; + + /* drop all enqueued CCBs */ + 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); + } + + /* the clean values of negotiations to report */ + neg.bus_width = 8; + neg.sync_period = neg.sync_offset = 0; + neg.valid = (CCB_TRANS_BUS_WIDTH_VALID + | 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++) { + clean_negotiations(softc, targ); + + /* report the event if possible */ + 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) { + hh=h->next; + 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); + } + } + + /* report the event */ + xpt_async(AC_BUS_RESET, softc->wpath, NULL); + + /* re-initialization may take a lot of time, in such case + * its completion should be signaled by another interrupt or + * checked on timeout - but for simplicity we assume here that + * it's really fast + */ + if(!fatal) { + reinitialize_controller_without_scsi_reset(softc); + } else { + reinitialize_controller_with_scsi_reset(softc); + } + schedule_next_hcb(softc); + return; + } +</programlisting> + + <para>If interrupt is not caused by a controller-wide condition + then probably something has happened to the current hardware + control block. Depending on the hardware there may be other + non-HCB-related events, we just do not consider them here. Then + we analyze what happened to this HCB:</para> + +<programlisting> + struct xxx_hcb *hcb, *h, *hh; + int hcb_status, scsi_status; + int ccb_status; + int targ; + int lun_to_freeze; + + hcb = get_current_hcb(softc); + if(hcb == NULL) { + /* either stray interrupt or something went very wrong + * or this is something hardware-dependent + */ + handle as necessary; + return; + } + + targ = hcb->target; + hcb_status = get_status_of_current_hcb(softc); +</programlisting> + + <para>First we check if the HCB has completed and if so we check + the returned SCSI status.</para> + +<programlisting> + if(hcb_status == COMPLETED) { + scsi_status = get_completion_status(hcb); +</programlisting> + + <para>Then look if this status is related to the REQUEST SENSE + command and if so handle it in a simple way.</para> + +<programlisting> + 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 { + autosense_failed: + free_hcb_and_ccb_done(hcb, hcb->ccb, CAM_AUTOSENSE_FAIL); + } + schedule_next_hcb(softc); + return; + } +</programlisting> + + <para>Else the command itself has completed, pay more attention to + details. If auto-sense is not disabled for this CCB and the + command has failed with sense data then run REQUEST SENSE + command to receive that data.</para> + +<programlisting> + 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) ) { + /* start auto-SENSE */ + hcb->flags |= DOING_AUTOSENSE; + setup_autosense_command_in_hcb(hcb); + restart_current_hcb(softc); + return; + } + 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); + schedule_next_hcb(softc); + return; + } +</programlisting> + + <para>One typical thing would be negotiation events: negotiation + messages received from a SCSI target (in answer to our + negotiation attempt or by target's initiative) or the target is + unable to negotiate (rejects our negotiation messages or does + not answer them).</para> + +<programlisting> + switch(hcb_status) { + case TARGET_REJECTED_WIDE_NEG: + /* revert to 8-bit bus */ + softc->current_bus_width[targ] = softc->goal_bus_width[targ] = 8; + /* report the event */ + neg.bus_width = 8; + neg.valid = CCB_TRANS_BUS_WIDTH_VALID; + xpt_async(AC_TRANSFER_NEG, hcb->ccb.ccb_h.path_id, &neg); + continue_current_hcb(softc); + return; + case TARGET_ANSWERED_WIDE_NEG: + { + int wd; + + wd = get_target_bus_width_request(softc); + if(wd <= softc->goal_bus_width[targ]) { + /* answer is acceptable */ + softc->current_bus_width[targ] = + softc->goal_bus_width[targ] = neg.bus_width = wd; + + /* report the event */ + neg.valid = CCB_TRANS_BUS_WIDTH_VALID; + xpt_async(AC_TRANSFER_NEG, hcb->ccb.ccb_h.path_id, &neg); + } else { + prepare_reject_message(hcb); + } + } + continue_current_hcb(softc); + return; + case TARGET_REQUESTED_WIDE_NEG: + { + int wd; + + wd = get_target_bus_width_request(softc); + wd = min (wd, OUR_BUS_WIDTH); + wd = min (wd, softc->user_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; + + /* report the event */ + neg.valid = CCB_TRANS_BUS_WIDTH_VALID; + xpt_async(AC_TRANSFER_NEG, hcb->ccb.ccb_h.path_id, &neg); + } + prepare_width_nego_rsponse(hcb, wd); + } + continue_current_hcb(softc); + return; + } +</programlisting> + + <para>Then we handle any errors that could have happened during + auto-sense in the same simple-minded way as before. Otherwise we + look closer at the details again.</para> + +<programlisting> + if(hcb->flags & DOING_AUTOSENSE) + goto autosense_failed; + + switch(hcb_status) { +</programlisting> + + <para>The next event we consider is unexpected disconnect. Which + is considered normal after an ABORT or BUS DEVICE RESET message + and abnormal in other cases.</para> + +<programlisting> + case UNEXPECTED_DISCONNECT: + 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]; + 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)) { + 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]; + h != NULL; h = hh) { + hh=h->next; + free_hcb_and_ccb_done(h, h->ccb, CAM_SCSI_BUS_RESET); + } + + /* send event */ + xpt_async(AC_SENT_BDR, hcb->ccb->ccb_h.path_id, NULL); + + /* this was the CAM_RESET_DEV request itself, it's completed */ + ccb_status = CAM_REQ_CMP; + } else { + calculate_residue(hcb); + ccb_status = CAM_UNEXP_BUSFREE; + /* request the further code to freeze the queue */ + hcb->ccb->ccb_h.status |= CAM_DEV_QFRZN; + lun_to_freeze = hcb->lun; + } + break; +</programlisting> + + <para>If the target refuses to accept tags we notify CAM about + that and return back all commands for this LUN:</para> + +<programlisting> + case TAGS_REJECTED: + /* report the event */ + neg.flags = 0 & ~CCB_TRANS_TAG_ENB; + neg.valid = CCB_TRANS_TQ_VALID; + xpt_async(AC_TRANSFER_NEG, hcb->ccb.ccb_h.path_id, &neg); + + ccb_status = CAM_MSG_REJECT_REC; + /* request the further code to freeze the queue */ + hcb->ccb->ccb_h.status |= CAM_DEV_QFRZN; + lun_to_freeze = hcb->lun; + break; +</programlisting> + + <para>Then we check a number of other conditions, with processing + basically limited to setting the CCB status:</para> + +<programlisting> + case SELECTION_TIMEOUT: + ccb_status = CAM_SEL_TIMEOUT; + /* request the further code to freeze the queue */ + hcb->ccb->ccb_h.status |= CAM_DEV_QFRZN; + lun_to_freeze = CAM_LUN_WILDCARD; + break; + case PARITY_ERROR: + ccb_status = CAM_UNCOR_PARITY; + break; + case DATA_OVERRUN: + case ODD_WIDE_TRANSFER: + ccb_status = CAM_DATA_RUN_ERR; + break; + default: + /* all other errors are handled in a generic way */ + ccb_status = CAM_REQ_CMP_ERR; + /* request the further code to freeze the queue */ + hcb->ccb->ccb_h.status |= CAM_DEV_QFRZN; + lun_to_freeze = CAM_LUN_WILDCARD; + break; + } +</programlisting> + + <para>Then we check if the error was serious enough to freeze the + input queue until it gets proceeded and do so if it is:</para> + +<programlisting> + 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) { + hh = h->next; + + 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); + } + } + free_hcb_and_ccb_done(hcb, hcb->ccb, ccb_status); + schedule_next_hcb(softc); + return; +</programlisting> + + <para>This concludes the generic interrupt handling although + specific controllers may require some additions.</para> + + </sect1> + + <sect1> + <title>Errors Summary</title> + + <para>When executing an I/O request many things may go wrong. The + reason of error can be reported in the CCB status with great + detail. Examples of use are spread throughout this document. For + completeness here is the summary of recommended responses for + the typical error conditions:</para> + + <itemizedlist> + + <listitem><para><emphasis>CAM_RESRC_UNAVAIL</emphasis> - some + resource is temporarily unavailable and the SIM driver can not + generate an event when it will become available. An example of + this resource would be some intra-controller hardware resource + for which the controller does not generate an interrupt when + it becomes available.</para></listitem> + + <listitem><para><emphasis>CAM_UNCOR_PARITY</emphasis> - + unrecovered parity error occurred</para></listitem> + + <listitem><para><emphasis>CAM_DATA_RUN_ERR</emphasis> - data + overrun or unexpected data phase (going in other direction + than specified in CAM_DIR_MASK) or odd transfer length for + wide transfer</para></listitem> + + <listitem><para><emphasis>CAM_SEL_TIMEOUT</emphasis> - selection + timeout occurred (target does not respond)</para></listitem> + + <listitem><para><emphasis>CAM_CMD_TIMEOUT</emphasis> - command + timeout occurred (the timeout function ran)</para></listitem> + + <listitem><para><emphasis>CAM_SCSI_STATUS_ERROR</emphasis> - the + device returned error</para></listitem> + + <listitem><para><emphasis>CAM_AUTOSENSE_FAIL</emphasis> - the + device returned error and the REQUEST SENSE COMMAND + failed</para></listitem> + + <listitem><para><emphasis>CAM_MSG_REJECT_REC</emphasis> - MESSAGE + REJECT message was received</para></listitem> + + <listitem><para><emphasis>CAM_SCSI_BUS_RESET</emphasis> - received + SCSI bus reset</para></listitem> + + <listitem><para><emphasis>CAM_REQ_CMP_ERR</emphasis> - + "impossible" SCSI phase occurred or something else as weird or + just a generic error if further detail is not + available</para></listitem> + + <listitem><para><emphasis>CAM_UNEXP_BUSFREE</emphasis> - + unexpected disconnect occurred</para></listitem> + + <listitem><para><emphasis>CAM_BDR_SENT</emphasis> - BUS DEVICE + RESET message was sent to the target</para></listitem> + + <listitem><para><emphasis>CAM_UNREC_HBA_ERROR</emphasis> - + unrecoverable Host Bus Adapter Error</para></listitem> + + <listitem><para><emphasis>CAM_REQ_TOO_BIG</emphasis> - the request + was too large for this controller</para></listitem> + + <listitem><para><emphasis>CAM_REQUEUE_REQ</emphasis> - this + request should be re-queued to preserve transaction ordering. + This typically occurs when the SIM recognizes an error that + should freeze the queue and must place other queued requests + for the target at the sim level back into the XPT + queue. Typical cases of such errors are selection timeouts, + command timeouts and other like conditions. In such cases the + troublesome command returns the status indicating the error, + the and the other commands which have not be sent to the bus + yet get re-queued.</para></listitem> + + <listitem><para><emphasis>CAM_LUN_INVALID</emphasis> - the LUN + ID in the request is not supported by the SCSI + controller</para></listitem> + + <listitem><para><emphasis>CAM_TID_INVALID</emphasis> - the + target ID in the request is not supported by the SCSI + controller</para></listitem> + </itemizedlist> + </sect1> + + <sect1> + <title>Timeout Handling</title> + + <para>When the timeout for an HCB expires that request should be + aborted, just like with an XPT_ABORT request. The only + difference is that the returned status of aborted request should + be CAM_CMD_TIMEOUT instead of CAM_REQ_ABORTED (that's why + implementation of the abort better be done as a function). But + there is one more possible problem: what if the abort request + itself will get stuck? In this case the SCSI bus should be + reset, just like with an XPT_RESET_BUS request (and the idea + about implementing it as a function called from both places + applies here too). Also we should reset the whole SCSI bus if a + device reset request got stuck. So after all the timeout + function would look like:</para> + +<programlisting> +static void +xxx_timeout(void *arg) +{ + struct xxx_hcb *hcb = (struct xxx_hcb *)arg; + struct xxx_softc *softc; + struct ccb_hdr *ccb_h; + + softc = hcb->softc; + ccb_h = &hcb->ccb->ccb_h; + + 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); + } +} +</programlisting> + + <para>When we abort a request all the other disconnected requests + to the same target/LUN get aborted too. So there appears a + question, should we return them with status CAM_REQ_ABORTED or + CAM_CMD_TIMEOUT ? The current drivers use CAM_CMD_TIMEOUT. This + seems logical because if one request got timed out then probably + something really bad is happening to the device, so if they + would not be disturbed they would time out by themselves.</para> + + </sect1> + +</chapter> diff --git a/en_US.ISO_8859-1/books/developers-handbook/book.sgml b/en_US.ISO_8859-1/books/developers-handbook/book.sgml index 1006c9f5d2..2cf7d725aa 100644 --- a/en_US.ISO_8859-1/books/developers-handbook/book.sgml +++ b/en_US.ISO_8859-1/books/developers-handbook/book.sgml @@ -1,7 +1,7 @@ <!-- The FreeBSD Documentation Project - $FreeBSD: doc/en_US.ISO_8859-1/books/developers-handbook/book.sgml,v 1.10 2000/11/28 19:07:40 asmodai Exp $ + $FreeBSD: doc/en_US.ISO_8859-1/books/developers-handbook/book.sgml,v 1.11 2000/11/29 04:15:17 jhb Exp $ --> <!DOCTYPE BOOK PUBLIC "-//FreeBSD//DTD DocBook V3.1-Based Extension//EN" [ @@ -10,6 +10,7 @@ <!ENTITY % man PUBLIC "-//FreeBSD//ENTITIES DocBook Manual Page Entities//EN"> %man; <!ENTITY % chapters SYSTEM "chapters.ent"> %chapters; +<!ENTITY % authors SYSTEM "../handbook/authors.ent"> %authors; ]> <book> @@ -31,6 +32,7 @@ <copyright> <year>2000</year> + <year>2001</year> <holder>The FreeBSD Documentation Project</holder> </copyright> @@ -319,6 +321,7 @@ &chap.driverbasics; &chap.pci; + &chap.scsi; <chapter id="usb"> <title>USB Devices</title> diff --git a/en_US.ISO_8859-1/books/developers-handbook/chapters.ent b/en_US.ISO_8859-1/books/developers-handbook/chapters.ent index c2149880e2..a367f40239 100644 --- a/en_US.ISO_8859-1/books/developers-handbook/chapters.ent +++ b/en_US.ISO_8859-1/books/developers-handbook/chapters.ent @@ -6,7 +6,7 @@ Chapters should be listed in the order in which they are referenced. - $FreeBSD: doc/en_US.ISO_8859-1/books/developers-handbook/chapters.ent,v 1.1 2000/11/28 18:03:54 asmodai Exp $ + $FreeBSD: doc/en_US.ISO_8859-1/books/developers-handbook/chapters.ent,v 1.2 2000/11/29 04:15:17 jhb Exp $ --> <!-- Part one --> @@ -44,6 +44,7 @@ <!-- Part eleven --> <!ENTITY chap.driverbasics SYSTEM "driverbasics/chapter.sgml"> <!ENTITY chap.pci SYSTEM "pci/chapter.sgml"> +<!ENTITY chap.scsi SYSTEM "scsi/chapter.sgml"> <!-- Part twelve --> <!-- No significant material yet, still in book.sgml --> diff --git a/en_US.ISO_8859-1/books/developers-handbook/scsi/chapter.sgml b/en_US.ISO_8859-1/books/developers-handbook/scsi/chapter.sgml new file mode 100644 index 0000000000..837a8fd60d --- /dev/null +++ b/en_US.ISO_8859-1/books/developers-handbook/scsi/chapter.sgml @@ -0,0 +1,2079 @@ +<!-- + The FreeBSD Documentation Project + + $FreeBSD$ +--> + +<chapter id="scsi"> + <title>Common Access Method SCSI Controllers</title> + + <para><emphasis>This chapter was written by &a.babkin; + Modifications for the handbook made by + &a.murray;.</emphasis></para> + + <sect1> + <title>Synopsis</title> + + <para>This document assumes that the reader has a general + understanding of device drivers in FreeBSD and of the SCSI + protocol. Much of the information in this document was + extracted from the drivers :</para> + + <itemizedlist> + + <listitem><para>ncr (<filename>/sys/pci/ncr.c</filename>) by + Wolfgang Stanglmeier and Stefan Esser</para></listitem> + + <listitem><para>sym (<filename>/sys/pci/sym.c</filename>) by + Gerard Roudier</para></listitem> + + <listitem><para>aic7xxx + (<filename>/sys/dev/aic7xxx/aic7xxx.c</filename>) by Justin + T. Gibbs</listitem> + + </itemizedlist> + + <para>and from the CAM code itself (by Justing T. Gibbs, see + <filename>/sys/cam/*</filename>). When some solution looked the + most logical and was essentially verbatim extracted from the code + by Justin Gibbs, I marked it as "recommended".</para> + + <para>The document is illustrated with examples in + pseudo-code. Although sometimes the examples have many details + and look like real code, it's still pseudo-code. It was written + to demonstrate the concepts in an understandable way. For a real + driver other approaches may be more modular and efficient. It + also abstracts from the hardware details, as well as issues that + would cloud the demonstrated concepts or that are supposed to be + described in the other chapters of the developers handbook. Such + details are commonly shown as calls to functions with descriptive + names, comments or pseudo-statements. Fortunately real life + full-size examples with all the details can be found in the real + drivers.</para> + + </sect1> + + <sect1> + <title>General architecture</title> + + <para>CAM stands for Common Access Method. It's a generic way to + address the I/O buses in a SCSI-like way. This allows a + separation of the generic device drivers from the drivers + controlling the I/O bus: for example the disk driver becomes able + to control disks on both SCSI, IDE, and/or any other bus so the + disk driver portion does not have to be rewritten (or copied and + modified) for every new I/O bus. Thus the two most important + active entities are:</para> + + <itemizedlist> + <listitem><para><emphasis>Peripheral Modules</emphasis> - a + driver for peripheral devices (disk, tape, CDROM, + etc.)</para></listitem> + <listitem><para><emphasis>SCSI Interface Modules </emphasis>(SIM) + - a Host Bus Adapter drivers for connecting to an I/O bus such + as SCSI or IDE.</para></listitem> + </itemizedlist> + + <para>A peripheral driver receives requests from the OS, converts + them to a sequence of SCSI commands and passes these SCSI + commands to a SCSI Interface Module. The SCSI Interface Module + is responsible for passing these commands to the actual hardware + (or if the actual hardware is not SCSI but, for example, IDE + then also converting the SCSI commands to the native commands of + the hardware).</para> + + <para>Because we are interested in writing a SCSI adapter driver + here, from this point on we will consider everything from the + SIM standpoint.</para> + + <para>A typical SIM driver needs to include the following + CAM-related header files:</para> + +<programlisting> +#include <cam/cam.h> +#include <cam/cam_ccb.h> +#include <cam/cam_sim.h> +#include <cam/cam_xpt_sim.h> +#include <cam/cam_debug.h> +#include <cam/scsi/scsi_all.h> +</programlisting> + + <para>The first thing each SIM driver must do is register itself + with the CAM subsystem. This is done during the driver's + <function>xxx_attach()</function> function (here and further + xxx_ is used to denote the unique driver name prefix). The + <function>xxx_attach()</function> function itself is called by + the system bus auto-configuration code which we don't describe + here.</para> + + <para>This is achieved in multiple steps: first it's necessary to + allocate the queue of requests associated with this SIM:</para> + +<programlisting> + struct cam_devq *devq; + + if(( devq = cam_simq_alloc(SIZE) )==NULL) { + error; /* some code to handle the error */ + } +</programlisting> + + <para>Here SIZE is the size of the queue to be allocated, maximal + number of requests it could contain. It's the number of requests + that the SIM driver can handle in parallel on one SCSI + card. Commonly it can be calculated as:</para> + +<programlisting> +SIZE = NUMBER_OF_SUPPORTED_TARGETS * MAX_SIMULTANEOUS_COMMANDS_PER_TARGET +</programlisting> + + <para>Next we create a descriptor of our SIM:</para> + +<programlisting> + struct cam_sim *sim; + + if(( sim = cam_sim_alloc(action_func, poll_func, driver_name, + softc, unit, max_dev_transactions, + max_tagged_dev_transactions, devq) )==NULL) { + cam_simq_free(devq); + error; /* some code to handle the error */ + } +</programlisting> + + <para>Note that if we are not able to create a SIM descriptor we + free the <structname>devq</structname> also because we can do + nothing else with it and we want to conserve memory.</para> + + <para>If a SCSI card has multiple SCSI buses on it then each bus + requires its own <structname>cam_sim</structname> + structure.</para> + + <para>An interesting question is what to do if a SCSI card has + more than one SCSI bus, do we need one + <structname>devq</structname> structure per card or per SCSI + bus? The answer given in the comments to the CAM code is: + either way, as the driver's author prefers.</para> + + <para>The arguments are : + <itemizedlist> + + <listitem><para><function>action_func</function> - pointer to + the driver's <function>xxx_action</function> function. + <funcSynopsis><funcPrototype> + <funcDef>static void + <function>xxx_action</function> + </funcDef> + <paramdef> + <parameter>struct cam_sim *sim</parameter>, + <parameter>union ccb *ccb</parameter> + </paramdef> + </funcPrototype></funcSynopsis> + </para></listitem> + + <listitem><para><function>poll_func</function> - pointer to + the driver's <function>xxx_poll()</function> + <funcSynopsis><funcPrototype> + <funcDef>static void + <function>xxx_poll</function> + </funcDef> + <paramdef> + <parameter>struct cam_sim *sim</parameter> + </paramdef> + </funcPrototype></funcSynopsis> + </para></listitem> + + <listitem><para>driver_name - the name of the actual driver, + such as "ncr" or "wds"</para></listitem> + + <listitem><para><structName>softc</structName> - 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.</para></listitem> + + <listitem><para>unit - the controller unit number, for example + for controller "wds0" this number will be + 0</para></listitem> + + <listitem><para>max_dev_transactions - maximal number of + simultaneous transactions per SCSI target in the non-tagged + mode. This value will be almost universally equal to 1, with + possible exceptions only for the non-SCSI cards. Also the + drivers that hope to take advantage by preparing one + transaction while another one is executed may set it to 2 + but this does not seem to be worth the + complexity.</para></listitem> + + <listitem><para>max_tagged_dev_transactions - the same thing, + but in the tagged mode. Tags are the SCSI way to initiate + multiple transactions on a device: each transaction is + assigned a unique tag and the transaction is sent to the + device. When the device completes some transaction it sends + back the result together with the tag so that the SCSI + adapter (and the driver) can tell which transaction was + completed. This argument is also known as the maximal tag + depth. It depends on the abilities of the SCSI + adapter.</para></listitem> + </itemizedlist> + </para> + + <para>Finally we register the SCSI buses associated with our SCSI + adapter:</para> + +<programlisting> + if(xpt_bus_register(sim, bus_number) != CAM_SUCCESS) { + cam_sim_free(sim, /*free_devq*/ TRUE); + error; /* some code to handle the error */ + } +</programlisting> + + <para>If there is one <structName>devq</structName> structure per + SCSI bus (i.e. we consider a card with multiple buses as + multiple cards with one bus each) then the bus number will + always be 0, otherwise each bus on the SCSI card should be get a + distinct number. Each bus needs its own separate structure + cam_sim.</para> + + <para>After that our controller is completely hooked to the CAM + system. The value of <structName>devq</structName> can be + discarded now: sim will be passed as an argument in all further + calls from CAM and devq can be derived from it.</para> + + <para>CAM provides the framework for such asynchronous + events. Some events originate from the lower levels (the SIM + drivers), some events originate from the peripheral drivers, + some events originate from the CAM subsystem itself. Any driver + can register callbacks for some types of the asynchronous + events, so that it would be notified if these events + occur.</para> + + <para>A typical example of such an event is a device reset. Each + transaction and event identifies the devices to which it applies + by the means of "path". The target-specific events normally + occur during a transaction with this device. So the path from + that transaction may be re-used to report this event (this is + safe because the event path is copied in the event reporting + routine but not deallocated nor passed anywhere further). Also + it's safe to allocate paths dynamically at any time including + the interrupt routines, although that incurs certain overhead, + and a possible problem with this approach is that there may be + no free memory at that time. For a bus reset event we need to + define a wildcard path including all devices on the bus. So we + can create the path for the future bus reset events in advance + and avoid problems with the future memory shortage:</para> + +<programlisting> + struct cam_path *path; + + 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)); + cam_sim_free(sim, /*free_devq*/TRUE); + error; /* some code to handle the error */ + } + + softc->wpath = path; + softc->sim = sim; +</programlisting> + + <para>As you can see the path includes:</para> + + <itemizedlist> + <listitem><para>ID of the peripheral driver (NULL here because we have + none)</para></listitem> + + <listitem><para>ID of the SIM driver + (<function>cam_sim_path(sim)</function>)</listitem> + + <listitem><para>SCSI target number of the device (CAM_TARGET_WILDCARD + means "all devices")</para></listitem> + + <listitem><para>SCSI LUN number of the subdevice (CAM_LUN_WILDCARD means + "all LUNs")</para></listitem> + </itemizedlist> + + <para>If the driver can't allocate this path it won't be able to + work normally, so in that case we dismantle that SCSI + bus.</para> + + <para>And we save the path pointer in the + <structName>softc</structName> structure for future use. After + that we save the value of sim (or we can also discard it on the + exit from <function>xxx_probe()</function> if we wish).</para> + + <para>That's all for a minimalistic initialization. To do things + right there is one more issue left. </para> + + <para>For a SIM driver there is one particularly interesting + event: when a target device is considered lost. In this case + resetting the SCSI negotiations with this device may be a good + idea. So we register a callback for this event with CAM. The + request is passed to CAM by requesting CAM action on a CAM + control block for this type of request:</para> + +<programlisting> + struct ccb_setasync csa; + + xpt_setup_ccb(&csa.ccb_h, path, /*priority*/5); + csa.ccb_h.func_code = XPT_SASYNC_CB; + csa.event_enable = AC_LOST_DEVICE; + csa.callback = xxx_async; + csa.callback_arg = sim; + xpt_action((union ccb *)&csa); +</programlisting> + + <para>Now we take a look at the <function>xxx_action()</function> + and <function>xxx_poll()</function> driver entry points.</para> + + <para> + <funcSynopsis><funcPrototype> + <funcDef>static void + <function>xxx_action</function> + </funcDef> + <paramdef> + <parameter>struct cam_sim *sim</parameter>, + <parameter>union ccb *ccb</parameter> + </paramdef> + </funcPrototype></funcSynopsis> + </para> + + <para>Do some action on request of the CAM subsystem. Sim + describes the SIM for the request, CCB is the request + itself. CCB stands for "CAM Control Block". It is a union of + many specific instances, each describing arguments for some type + of transactions. All of these instances share the CCB header + where the common part of arguments is stored.</para> + + <para>CAM supports the SCSI controllers working in both initiator + ("normal") mode and target (simulating a SCSI device) mode. Here + we only consider the part relevant to the initiator mode.</para> + + <para>There are a few function and macros (in other words, + methods) defined to access the public data in the struct sim:</para> + + <itemizedlist> + <listitem><para><function>cam_sim_path(sim)</function> - the + path ID (see above)</para></listitem> + + <listitem><para><function>cam_sim_name(sim)</function> - the + name of the sim</para></listitem> + + <listitem><para><function>cam_sim_softc(sim)</function> - the + pointer to the softc (driver private data) + structure</para></listitem> + + <listitem><para><function> cam_sim_unit(sim)</function> - the + unit number</para></listitem> + + <listitem><para><function> cam_sim_bus(sim)</function> - the bus + ID</para></listitem> + </itemizedlist> + + <para>To identify the device, <function>xxx_action()</function> can + get the unit number and pointer to its structure softc using + these functions.</para> + + <para>The type of request is stored in + <structField>ccb->ccb_h.func_code</structField>. So generally + <function>xxx_action()</function> consists of a big + switch:</para> + +<programlisting> + struct xxx_softc *softc = (struct xxx_softc *) cam_sim_softc(sim); + struct ccb_hdr *ccb_h = &ccb->ccb_h; + int unit = cam_sim_unit(sim); + int bus = cam_sim_bus(sim); + + switch(ccb_h->func_code) { + case ...: + ... + default: + ccb_h->status = CAM_REQ_INVALID; + xpt_done(ccb); + break; + } +</programlisting> + + <para>As can be seen from the default case (if an unknown command + was received) the return code of the command is set into + <structField>ccb->ccb_h.status</structField> and the completed + CCB is returned back to CAM by calling + <function>xpt_done(ccb)</function>. </para> + + <para><function>xpt_done()</function> does not have to be called + from <function>xxx_action()</function>: For example an I/O + request may be enqueued inside the SIM driver and/or its SCSI + controller. Then when the device would post an interrupt + signaling that the processing of this request is complete + <function>xpt_done()</function> may be called from the interrupt + handling routine.</para> + + <para>Actually, the CCB status is not only assigned as a return + code but a CCB has some status all the time. Before CCB is + passed to the <function>xxx_action()</function> routine it gets + the status CCB_REQ_INPROG meaning that it's in progress. There + are a surprising number of status values defined in + <filename>/sys/cam/cam.h</filename> which should be able to + represent the status of a request in great detail. More + interesting yet, the status is in fact a "bitwise or" of an + enumerated status value (the lower 6 bits) and possible + additional flag-like bits (the upper bits). The enumerated + values will be discussed later in more detail. The summary of + them can be found in the Errors Summary section. The possible + status flags are:</para> + + <itemizedlist> + + <listitem><para><emphasis>CAM_DEV_QFRZN</emphasis> - if the + SIM driver gets a serious error (for example, the device does + not respond to the selection or breaks the SCSI protocol) when + processing a CCB it should freeze the request queue by calling + <function>xpt_freeze_simq()</function>, return the other + enqueued but not processed yet CCBs for this device back to + the CAM queue, then set this flag for the troublesome CCB and + call <function>xpt_done()</function>. This flag causes the CAM + subsystem to unfreeze the queue after it handles the + error.</para></listitem> + + <listitem><para><emphasis>CAM_AUTOSNS_VALID</emphasis> - if + the device returned an error condition and the flag + CAM_DIS_AUTOSENSE is not set in CCB the SIM driver must + execute the REQUEST SENSE command automatically to extract the + sense (extended error information) data from the device. If + this attempt was successful the sense data should be saved in + the CCB and this flag set.</para></listitem> + + <listitem><para><emphasis>CAM_RELEASE_SIMQ</emphasis> - like + CAM_DEV_QFRZN but used in case there is some problem (or + resource shortage) with the SCSI controller itself. Then all + the future requests to the controller should be stopped by + <function>xpt_freeze_simq()</function>. The controller queue + will be restarted after the SIM driver overcomes the shortage + and informs CAM by returning some CCB with this flag + set.</para></listitem> + + <listitem><para><emphasis>CAM_SIM_QUEUED</emphasis> - when SIM + puts a CCB into its request queue this flag should be set (and + removed when this CCB gets dequeued before being returned back + to CAM). This flag is not used anywhere in the CAM code now, + so its purpose is purely diagnostic.</para></listitem> + + </itemizedlist> + + <para>The function <function>xxx_action()</function> is not + allowed to sleep, so all the synchronization for resource access + must be done using SIM or device queue freezing. Besides the + aforementioned flags the CAM subsystem provides functions + <function>xpt_selease_simq()</function> and + <function>xpt_release_devq()</function> to unfreeze the queues + directly, without passing a CCB to CAM.</para> + + <para>The CCB header contains the following fields:</para> + + <itemizedlist> + + <listitem><para><emphasis>path</emphasis> - path ID for the + request</para></listitem> + + <listitem><para><emphasis>target_id</emphasis> - target device + ID for the request</para></listitem> + + <listitem><para><emphasis>target_lun</emphasis> - LUN ID of + the target device</para></listitem> + + <listitem><para><emphasis>timeout</emphasis> - timeout + interval for this command, in milliseconds</para></listitem> + + <listitem><para><emphasis>timeout_ch</emphasis> - a + convenience place for the SIM driver to store the timeout handle + (the CAM subsystem itself does not make any assumptions about + it)</para></listitem> + + <listitem><para><emphasis>flags</emphasis> - various bits of + information about the request spriv_ptr0, spriv_ptr1 - fields + reserved for private use by the SIM driver (such as linking to + the SIM queues or SIM private control blocks); actually, they + exist as unions: spriv_ptr0 and spriv_ptr1 have the type (void + *), spriv_field0 and spriv_field1 have the type unsigned long, + sim_priv.entries[0].bytes and sim_priv.entries[1].bytes are byte + arrays of the size consistent with the other incarnations of the + union and sim_priv.bytes is one array, twice + bigger.</para></listitem> + + </itemizedlist> + + <para>The recommended way of using the SIM private fields of CCB + is to define some meaningful names for them and use these + meaningful names in the driver, like:</para> + +<programlisting> +#define ccb_some_meaningful_name sim_priv.entries[0].bytes +#define ccb_hcb spriv_ptr1 /* for hardware control block */ +</programlisting> + + <para>The most common initiator mode requests are:</para> + <itemizedlist> + <listitem><para><emphasis>XPT_SCSI_IO</emphasis> - execute an + I/O transaction</para> + + <para>The instance "struct ccb_scsiio csio" of the union ccb is + used to transfer the arguments. They are:</para> + + <itemizedlist> + <listitem><para><emphasis>cdb_io</emphasis> - pointer to + the SCSI command buffer or the buffer + itself</para></listitem> + + <listitem><para><emphasis>cdb_len</emphasis> - SCSI + command length</para></listitem> + + <listitem><para><emphasis>data_ptr</emphasis> - pointer to + the data buffer (gets a bit complicated if scatter/gather is + used)</para></listitem> + + <listitem><para><emphasis>dxfer_len</emphasis> - length of + the data to transfer</para></listitem> + + <listitem><para><emphasis>sglist_cnt</emphasis> - counter + of the scatter/gather segments</para></listitem> + + <listitem><para><emphasis>scsi_status</emphasis> - place + to return the SCSI status</para></listitem> + + <listitem><para><emphasis>sense_data</emphasis> - 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)</para></listitem> + + <listitem><para><emphasis>sense_len</emphasis> - 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.</para></listitem> + + <listitem><para><emphasis>tag_action</emphasis> - the kind + of tag to use: + + <itemizedlist> + <listitem><para>CAM_TAG_ACTION_NONE - don't use tags for this + transaction</para></listitem> + <listitem><para>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</para></listitem> + </itemizedlist> + + </para></listitem> + + </itemizedlist> + + <para>The general logic of handling this request is the + following:</para> + + <para>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:</para> + +<programlisting> + struct ccb_scsiio *csio = &ccb->csio; + + if ((ccb_h->status & CAM_STATUS_MASK) != CAM_REQ_INPROG) { + xpt_done(ccb); + return; + } +</programlisting> + + <para>Also we check that the device is supported at all by our + controller:</para> + +<programlisting> + 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) { + ccb_h->status = CAM_LUN_INVALID; + xpt_done(ccb); + return; + } +</programlisting> + + <para>Then allocate whatever data structures (such as + card-dependent hardware control block) we need to process this + request. If we can't 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.</para> + +<programlisting> + struct xxx_hcb *hcb = allocate_hcb(softc, unit, bus); + + if(hcb == NULL) { + softc->flags |= RESOURCE_SHORTAGE; + xpt_freeze_simq(sim, /*count*/1); + ccb_h->status = CAM_REQUEUE_REQ; + xpt_done(ccb); + return; + } + + hcb->ccb = ccb; ccb_h->ccb_hcb = (void *)hcb; + ccb_h->status |= CAM_SIM_QUEUED; +</programlisting> + + <para>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.</para> + +<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 ) + generate_unique_tag_message(hcb, ccb_h->tag_action); + if( !target_negotiated(hcb) ) + generate_negotiation_messages(hcb); +</programlisting> + + <para>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.</para> + + <para>A NOT-QUITE RELATED NOTE: Normally this is done by a call + to vtophys(), but for the PCI device (which account for most + of the SCSI controllers now) drivers' portability to the Alpha + architecture the conversion must be done by vtobus() instead + due to special Alpha quirks. [IMHO it would be much better to + have two separate functions, vtop() and ptobus() then vtobus() + would be a simple superposition of them.] In case if a + physical address is requested it's OK to return the CCB with + the status CAM_REQ_INVALID, the current drivers do that. But + it's also possible to compile the Alpha-specific piece of + code, as in this example (there should be a more direct way to + do that, without conditional compilation in the drivers). If + necessary a physical address can be also converted or mapped + back to a virtual address but with big pain, so we don't do + that.</para> + +<programlisting> + if(ccb_h->flags & CAM_CDB_POINTER) { + /* CDB is a pointer */ + if(!(ccb_h->flags & CAM_CDB_PHYS)) { + /* CDB pointer is virtual */ + hcb->cmd = vtobus(csio->cdb_io.cdb_ptr); + } else { + /* CDB pointer is physical */ +#if defined(__alpha__) + hcb->cmd = csio->cdb_io.cdb_ptr | alpha_XXX_dmamap_or ; +#else + hcb->cmd = csio->cdb_io.cdb_ptr ; +#endif + } + } else { + /* CDB is in the ccb (buffer) */ + hcb->cmd = vtobus(csio->cdb_io.cdb_bytes); + } + hcb->cmdlen = csio->cdb_len; +</programlisting> + + <para>Now it's 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:</para> + +<programlisting> + int dir = (ccb_h->flags & CAM_DIR_MASK); + + if (dir == CAM_DIR_NONE) + goto end_data; +</programlisting> + + <para>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. We use a special + function to return the CCB to handle in one place the HCB + resource shortages. The functions to add chunks are + driver-dependent, and here we leave them without detailed + implementation. See description of the SCSI command (CDB) + handling for the details on the address-translation issues. + If some variation is too difficult or impossible to implement + with a particular card it's 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's actively used by CAM.</para> + +<programlisting> + int rv; + + initialize_hcb_for_data(hcb); + + if((!(ccb_h->flags & CAM_SCATTER_VALID)) { + /* single buffer */ + if(!(ccb_h->flags & CAM_DATA_PHYS)) { + rv = add_virtual_chunk(hcb, csio->data_ptr, csio->dxfer_len, dir); + } + } else { + rv = add_physical_chunk(hcb, csio->data_ptr, csio->dxfer_len, dir); + } + } else { + int i; + struct bus_dma_segment *segs; + segs = (struct bus_dma_segment *)csio->data_ptr; + + if ((ccb_h->flags & CAM_SG_LIST_PHYS) != 0) { + /* The SG list pointer is physical */ + rv = setup_hcb_for_physical_sg_list(hcb, segs, csio->sglist_cnt); + } else if (!(ccb_h->flags & CAM_DATA_PHYS)) { + /* SG buffer pointers are virtual */ + for (i = 0; i < csio->sglist_cnt; i++) { + rv = add_virtual_chunk(hcb, segs[i].ds_addr, + segs[i].ds_len, dir); + if (rv != CAM_REQ_CMP) + break; + } + } else { + /* SG buffer pointers are physical */ + for (i = 0; i < csio->sglist_cnt; i++) { + rv = add_physical_chunk(hcb, segs[i].ds_addr, + segs[i].ds_len, dir); + if (rv != CAM_REQ_CMP) + break; + } + } + } + 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), + * CAM_REQ_INVALID in case of other troubles + */ + free_hcb_and_ccb_done(hcb, ccb, rv); + return; + } + end_data: +</programlisting> + + <para>If disconnection is disabled for this CCB we pass this + information to the hcb:</para> + +<programlisting> + if(ccb_h->flags & CAM_DIS_DISCONNECT) + hcb_disable_disconnect(hcb); +</programlisting> + + <para>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.</para> + + <para>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).</para> + +<programlisting> + ccb_h->timeout_ch = timeout(xxx_timeout, (caddr_t) hcb, + (ccb_h->timeout * hz) / 1000); /* convert milliseconds to ticks */ + put_hcb_into_hardware_queue(hcb); + return; +</programlisting> + + <para>And here is a possible implementation of the function + returning CCB:</para> + +<programlisting> + static void + free_hcb_and_ccb_done(struct xxx_hcb *hcb, union ccb *ccb, u_int32_t status) + { + struct xxx_softc *softc = hcb->softc; + + ccb->ccb_h.ccb_hcb = 0; + 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) { + softc->flags &= ~RESOURCE_SHORTAGE; + status |= CAM_RELEASE_SIMQ; + } + free_hcb(hcb); /* also removes hcb from any internal lists */ + } + ccb->ccb_h.status = status | + (ccb->ccb_h.status & ~(CAM_STATUS_MASK|CAM_SIM_QUEUED)); + xpt_done(ccb); + } +</programlisting> + </listitem> + + <listitem><para><emphasis>XPT_RESET_DEV</emphasis> - send the SCSI "BUS + DEVICE RESET" message to a device</para> + + <para>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).</para> + + <para>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.</para></listitem> + + <listitem><para><emphasis>XPT_RESET_BUS</emphasis> - send the RESET signal + to the SCSI bus</para> + + <para>No arguments are passed in the CCB, the only interesting + argument is the SCSI bus indicated by the struct sim + pointer.</para> + + <para>A minimalistic implementation would forget the SCSI + negotiations for all the devices on the bus and return the + status CAM_REQ_CMP.</para> + + <para>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:</para> + +<programlisting> + int targ, lun; + struct xxx_hcb *h, *hh; + struct ccb_trans_settings neg; + struct cam_path *path; + + /* The SCSI bus reset may take a long time, in this case its completion + * should be checked by interrupt or timeout. But for simplicity + * we assume here that it's really fast. + */ + reset_scsi_bus(softc); + + /* drop all enqueued CCBs */ + 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); + } + + /* the clean values of negotiations to report */ + neg.bus_width = 8; + neg.sync_period = neg.sync_offset = 0; + neg.valid = (CCB_TRANS_BUS_WIDTH_VALID + | 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++) { + clean_negotiations(softc, targ); + + /* report the event if possible */ + 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) { + hh=h->next; + free_hcb_and_ccb_done(h, h->ccb, CAM_SCSI_BUS_RESET); + } + } + + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + + /* report the event */ + xpt_async(AC_BUS_RESET, softc->wpath, NULL); + return; +</programlisting> + + <para>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.</para></listitem> + + <listitem><para><emphasis>XPT_ABORT</emphasis> - abort the specified + CCB</para> + + <para>The arguments are transferred in the instance "struct + ccb_abort cab" of the union ccb. The only argument field in it + is:</para> + + <para><emphasis>abort_ccb</emphasis> - pointer to the CCB to be + aborted</para> + + <para>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.</para> + + <para>The hard way is to implement this request honestly. First + check that abort applies to a SCSI transaction:</para> + +<programlisting> + struct ccb *abort_ccb; + abort_ccb = ccb->cab.abort_ccb; + + if(abort_ccb->ccb_h.func_code != XPT_SCSI_IO) { + ccb->ccb_h.status = CAM_UA_ABORT; + xpt_done(ccb); + return; + } +</programlisting> + + <para>Then it's 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:</para> + +<programlisting> + struct xxx_hcb *hcb, *h; + + hcb = NULL; + + /* We assume that softc->first_hcb is the head of the list of all + * 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) { + hcb = h; + break; + } + } + + if(hcb == NULL) { + /* no such CCB in our queue */ + ccb->ccb_h.status = CAM_PATH_INVALID; + xpt_done(ccb); + return; + } + + hcb=found_hcb; +</programlisting> + + <para>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 don't 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.</para> + +<programlisting> + int hstatus; + + /* shown as a function, in case special action is needed to make + * this flag visible to hardware + */ + set_hcb_flags(hcb, HCB_BEING_ABORTED); + + abort_again: + + hstatus = get_hcb_status(hcb); + switch(hstatus) { + case HCB_SITTING_IN_QUEUE: + remove_hcb_from_hardware_queue(hcb); + /* FALLTHROUGH */ + case HCB_COMPLETED: + /* this is an easy case */ + free_hcb_and_ccb_done(hcb, abort_ccb, CAM_REQ_ABORTED); + break; +</programlisting> + + <para>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. + Because 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).</para> + +<programlisting> + case HCB_BEING_TRANSFERRED: + untimeout(xxx_timeout, (caddr_t) hcb, abort_ccb->ccb_h.timeout_ch); + abort_ccb->ccb_h.timeout_ch = + timeout(xxx_timeout, (caddr_t) hcb, 10 * hz); + abort_ccb->ccb_h.status = CAM_REQ_ABORTED; + /* ask the controller to abort that HCB, then generate + * an interrupt and stop + */ + 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; + } + + break; +</programlisting> + + <para>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.</para> + +<programlisting> + case HCB_DISCONNECTED: + untimeout(xxx_timeout, (caddr_t) hcb, abort_ccb->ccb_h.timeout_ch); + abort_ccb->ccb_h.timeout_ch = + timeout(xxx_timeout, (caddr_t) hcb, 10 * hz); + put_abort_message_into_hcb(hcb); + put_hcb_at_the_front_of_hardware_queue(hcb); + break; + } + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + return; +</programlisting> + + <para>That's all for the ABORT request, although there is one more + issue. Because 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.</para> + + <para>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:</para> + +<programlisting> + case XPT_ABORT: + struct ccb *abort_ccb; + abort_ccb = ccb->cab.abort_ccb; + + 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) + /* no such CCB in our queue */ + ccb->ccb_h.status = CAM_PATH_INVALID; + else + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + return; +</programlisting> + </listitem> + + <listitem><para><emphasis>XPT_SET_TRAN_SETTINGS</emphasis> - explicitly + set values of SCSI transfer settings</para> + + <para>The arguments are transferred in the instance "struct ccb_trans_setting cts" +of the union ccb:</para> + + <itemizedlist> + <listitem><para><emphasis>valid</emphasis> - a bitmask showing + which settings should be updated:</para></listitem> + + <listitem><para><emphasis>CCB_TRANS_SYNC_RATE_VALID</emphasis> + - synchronous transfer rate</para></listitem> + + <listitem><para><emphasis>CCB_TRANS_SYNC_OFFSET_VALID</emphasis> + - synchronous offset</para></listitem> + + <listitem><para><emphasis>CCB_TRANS_BUS_WIDTH_VALID</emphasis> + - bus width</para></listitem> + + <listitem><para><emphasis>CCB_TRANS_DISC_VALID</emphasis> - + set enable/disable disconnection</para></listitem> + + <listitem><para><emphasis>CCB_TRANS_TQ_VALID</emphasis> - set + enable/disable tagged queuing</para></listitem> + + <listitem><para><emphasis>flags</emphasis> - consists of two + parts, binary arguments and identification of + sub-operations. The binary arguments are :</para> + <itemizedlist> + <listitem><para><emphasis>CCB_TRANS_DISC_ENB</emphasis> - enable disconnection</para></listitem> + <listitem><para><emphasis>CCB_TRANS_TAG_ENB</emphasis> - + enable tagged queuing</para></listitem> + </itemizedlist> + </listitem> + + <listitem><para>the sub-operations are:</para> + <itemizedlist> + <listitem><para><emphasis>CCB_TRANS_CURRENT_SETTINGS</emphasis> + - change the current negotiations</para></listitem> + + <listitem><para><emphasis>CCB_TRANS_USER_SETTINGS</emphasis> + - 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)</para></listitem> + </itemizedlist> + </listitem> + + </itemizedlist> + + <para>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's essentially the + top boundary.</para> + + <para>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.</para> + + <para>One caveat is that the bus width and synchronous parameters + are per target while the disconnection and tag enabling + parameters are per lun.</para> + + <para>The recommended implementation is to keep 3 sets of + negotiated (bus width and synchronous transfer) + parameters:</para> + + <itemizedlist> + <listitem><para><emphasis>user</emphasis> - the user set, as + above</para></listitem> + + <listitem><para><emphasis>current</emphasis> - those actually + in effect</para></listitem> + + <listitem><para><emphasis>goal</emphasis> - those requested by + setting of the "current" parameters</para></listitem> + </itemizedlist> + + <para>The code looks like:</para> + +<programlisting> + struct ccb_trans_settings *cts; + int targ, lun; + int flags; + + cts = &ccb->cts; + 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) + softc->user_sync_period[targ] = cts->sync_period; + if(flags & CCB_TRANS_SYNC_OFFSET_VALID) + softc->user_sync_offset[targ] = cts->sync_offset; + if(flags & CCB_TRANS_BUS_WIDTH_VALID) + softc->user_bus_width[targ] = cts->bus_width; + + 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) { + 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) + softc->goal_sync_period[targ] = + max(cts->sync_period, OUR_MIN_SUPPORTED_PERIOD); + 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) + softc->goal_bus_width[targ] = min(cts->bus_width, OUR_BUS_WIDTH); + + 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) { + softc->current_tflags[targ][lun] &= ~CCB_TRANS_TQ_ENB; + softc->current_tflags[targ][lun] |= flags & CCB_TRANS_TQ_ENB; + } + } + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + return; +</programlisting> + + <para>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:</para> + +<programlisting> + int + target_negotiated(struct xxx_hcb *hcb) + { + struct softc *softc = hcb->softc; + int targ = hcb->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] ) + return 0; /* FALSE */ + else + return 1; /* TRUE */ + } +</programlisting> + + <para>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 <function>target_negotiated()</function> would return + TRUE. When the card is initialized (in + <function>xxx_attach()</function>) 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.</para></listitem> + + <listitem><para><emphasis>XPT_GET_TRAN_SETTINGS</emphasis> - get values of + SCSI transfer settings</para> + + <para>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.</para></listitem> + + <listitem><para><emphasis>XPT_CALC_GEOMETRY</emphasis> - calculate logical + (BIOS) geometry of the disk</para> + + <para>The arguments are transferred in the instance "struct + ccb_calc_geometry ccg" of the union ccb:</para> + + <itemizedlist> + + <listitem><para><emphasis>block_size</emphasis> - input, block + (A.K.A sector) size in bytes</para></listitem> + + <listitem><para><emphasis>volume_size</emphasis> - input, + volume size in bytes</para></listitem> + + <listitem><para><emphasis>cylinders</emphasis> - output, + logical cylinders</para></listitem> + + <listitem><para><emphasis>heads</emphasis> - output, logical + heads</para></listitem> + + <listitem><para><emphasis>secs_per_track</emphasis> - output, + logical sectors per track</para></listitem> + + </itemizedlist> + + <para>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:</para> + +<programlisting> + struct ccb_calc_geometry *ccg; + u_int32_t size_mb; + u_int32_t secs_per_cylinder; + int extended; + + ccg = &ccb->ccg; + size_mb = ccg->volume_size + / ((1024L * 1024L) / ccg->block_size); + extended = check_cards_EEPROM_for_extended_geometry(softc); + + if (size_mb > 1024 && extended) { + ccg->heads = 255; + ccg->secs_per_track = 63; + } else { + ccg->heads = 64; + ccg->secs_per_track = 32; + } + secs_per_cylinder = ccg->heads * ccg->secs_per_track; + ccg->cylinders = ccg->volume_size / secs_per_cylinder; + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + return; +</programlisting> + + <para>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:</para> + +<programlisting> + 128 heads, 63 sectors - Symbios controllers + 16 heads, 63 sectors - old controllers +</programlisting> + + <para>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.</para> + </listitem> + + <listitem><para><emphasis>XPT_PATH_INQ</emphasis> - path inquiry, in other + words get the SIM driver and SCSI controller (also known as HBA + - Host Bus Adapter) properties</para> + + <para>The properties are returned in the instance "struct +ccb_pathinq cpi" of the union ccb:</para> + + <itemizedlist> + + <listitem><para>version_num - the SIM driver version number, now + all drivers use 1</para></listitem> + + <listitem><para>hba_inquiry - bitmask of features supported by + the controller:</para></listitem> + + <listitem><para>PI_MDP_ABLE - supports MDP message (something + from SCSI3?)</para></listitem> + + <listitem><para>PI_WIDE_32 - supports 32 bit wide + SCSI</para></listitem> + + <listitem><para>PI_WIDE_16 - supports 16 bit wide + SCSI</para></listitem> + + <listitem><para>PI_SDTR_ABLE - can negotiate synchronous + transfer rate</para></listitem> + + <listitem><para>PI_LINKED_CDB - supports linked + commands</para></listitem> + + <listitem><para>PI_TAG_ABLE - supports tagged + commands</para></listitem> + + <listitem><para>PI_SOFT_RST - supports soft reset alternative + (hard reset and soft reset are mutually exclusive within a + SCSI bus)</para></listitem> + + <listitem><para>target_sprt - flags for target mode support, 0 + if unsupported</para></listitem> + + <listitem><para>hba_misc - miscellaneous controller + features:</para></listitem> + + <listitem><para>PIM_SCANHILO - bus scans from high ID to low + ID</para></listitem> + + <listitem><para>PIM_NOREMOVE - removable devices not included in + scan</para></listitem> + + <listitem><para>PIM_NOINITIATOR - initiator role not + supported</para></listitem> + + <listitem><para>PIM_NOBUSRESET - user has disabled initial BUS + RESET</para></listitem> + + <listitem><para>hba_eng_cnt - mysterious HBA engine count, + something related to compression, now is always set to + 0</para></listitem> + + <listitem><para>vuhba_flags - vendor-unique flags, unused + now</para></listitem> + + <listitem><para>max_target - maximal supported target ID (7 for + 8-bit bus, 15 for 16-bit bus, 127 for Fibre + Channel)</para></listitem> + + <listitem><para>max_lun - maximal supported LUN ID (7 for older + SCSI controllers, 63 for newer ones)</para></listitem> + + <listitem><para>async_flags - bitmask of installed Async + handler, unused now</para></listitem> + + <listitem><para>hpath_id - highest Path ID in the subsystem, + unused now</para></listitem> + + <listitem><para>unit_number - the controller unit number, + cam_sim_unit(sim)</para></listitem> + + <listitem><para>bus_id - the bus number, + cam_sim_bus(sim)</para></listitem> + + <listitem><para>initiator_id - the SCSI ID of the controller + itself</para></listitem> + + <listitem><para>base_transfer_speed - nominal transfer speed in + KB/s for asynchronous narrow transfers, equals to 3300 for + SCSI</para></listitem> + + <listitem><para>sim_vid - SIM driver's vendor id, a + zero-terminated string of maximal length SIM_IDLEN including + the terminating zero</para></listitem> + + <listitem><para>hba_vid - SCSI controller's vendor id, a + zero-terminated string of maximal length HBA_IDLEN including + the terminating zero</para></listitem> + + <listitem><para>dev_name - device driver name, a zero-terminated + string of maximal length DEV_IDLEN including the terminating + zero, equal to cam_sim_name(sim)</para></listitem> + + </itemizedlist> + + <para>The recommended way of setting the string fields is using + strncpy, like:</para> + +<programlisting> + strncpy(cpi->dev_name, cam_sim_name(sim), DEV_IDLEN); +</programlisting> + + <para>After setting the values set the status to CAM_REQ_CMP and mark the +CCB as done.</para> + </listitem> + </itemizedlist> + + </sect1> + + <sect1> + <title>Polling</title> + + <funcSynopsis><funcPrototype> + <funcDef>static void + <function>xxx_poll</function> + </funcDef> + <paramdef> + <parameter>struct cam_sim *sim</parameter> + </paramdef> + </funcPrototype></funcSynopsis> + + <para>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. So all it needs to do is to call the interrupt + routine (or the other way around, the poll routine may be doing + the real action and the interrupt routine would just call the + poll routine). Why bother about a separate function then ? + Because of different calling conventions. The + <function>xxx_poll</function> routine gets the struct cam_sim + pointer as its argument when the PCI interrupt routine by common + convention gets pointer to the struct + <structName>xxx_softc</structName> and the ISA interrupt routine + gets just the the device unit number. So the poll routine would + normally look as:</para> + +<programlisting> +static void +xxx_poll(struct cam_sim *sim) +{ + xxx_intr((struct xxx_softc *)cam_sim_softc(sim)); /* for PCI device */ +} +</programlisting> + + <para>or</para> + +<programlisting> +static void +xxx_poll(struct cam_sim *sim) +{ + xxx_intr(cam_sim_unit(sim)); /* for ISA device */ +} +</programlisting> + + </sect1> + + <sect1> + <title>Asynchronous Events</title> + + <para>If an asynchronous event callback has been set up then the + callback function should be defined.</para> + +<programlisting> +static void +ahc_async(void *callback_arg, u_int32_t code, struct cam_path *path, void *arg) +</programlisting> + + <itemizedlist> + <listitem><para>callback_arg - the value supplied when registering the + callback</para></listitem> + + <listitem><para>code - identifies the type of event</para></listitem> + + <listitem><para>path - identifies the devices to which the event + applies</para></listitem> + + <listitem><para>arg - event-specific argument</para></listitem> + </itemizedlist> + + <para>Implementation for a single type of event, AC_LOST_DEVICE, + looks like:</para> + +<programlisting> + struct xxx_softc *softc; + struct cam_sim *sim; + int targ; + struct ccb_trans_settings neg; + + sim = (struct cam_sim *)callback_arg; + softc = (struct xxx_softc *)cam_sim_softc(sim); + switch (code) { + case AC_LOST_DEVICE: + targ = xpt_path_target_id(path); + if(targ <= OUR_MAX_SUPPORTED_TARGET) { + clean_negotiations(softc, targ); + /* send indication to CAM */ + neg.bus_width = 8; + neg.sync_period = neg.sync_offset = 0; + neg.valid = (CCB_TRANS_BUS_WIDTH_VALID + | CCB_TRANS_SYNC_RATE_VALID | CCB_TRANS_SYNC_OFFSET_VALID); + xpt_async(AC_TRANSFER_NEG, path, &neg); + } + break; + default: + break; + } +</programlisting> + + </sect1> + + <sect1> + <title>Interrupts</title> + + <para>The exact type of the interrupt routine depends on the type + of the peripheral bus (PCI, ISA and so on) to which the SCSI + controller is connected.</para> + + <para>The interrupt routines of the SIM drivers run at the + interrupt level splcam. So <function>splcam()</function> should + be used in the driver to synchronize activity between the + interrupt routine and the rest of the driver (for a + multiprocessor-aware driver things get yet more interesting but + we ignore this case here). The pseudo-code in this document + happily ignores the problems of synchronization. The real code + must not ignore them. A simple-minded approach is to set + <function>splcam()</function> on the entry to the other routines + and reset it on return thus protecting them by one big critical + section. To make sure that the interrupt level will be always + restored a wrapper function can be defined, like:</para> + +<programlisting> + static void + xxx_action(struct cam_sim *sim, union ccb *ccb) + { + int s; + s = splcam(); + xxx_action1(sim, ccb); + splx(s); + } + + static void + xxx_action1(struct cam_sim *sim, union ccb *ccb) + { + ... process the request ... + } +</programlisting> + + <para>This approach is simple and robust but the problem with it + is that interrupts may get blocked for a relatively long time + and this would negatively affect the system's performance. On + the other hand the functions of the <function>spl()</function> + family have rather high overhead, so vast amount of tiny + critical sections may not be good either.</para> + + <para>The conditions handled by the interrupt routine and the + details depend very much on the hardware. We consider the set of + "typical" conditions.</para> + + <para>First, we check if a SCSI reset was encountered on the bus + (probably caused by another SCSI controller on the same SCSI + bus). If so we drop all the enqueued and disconnected requests, + report the events and re-initialize our SCSI controller. It is + important that during this initialization the controller won't + issue another reset or else two controllers on the same SCSI bus + could ping-pong resets forever. The case of fatal controller + error/hang could be handled in the same place, but it will + probably need also sending RESET signal to the SCSI bus to reset + the status of the connections with the SCSI devices.</para> + +<programlisting> + int fatal=0; + struct ccb_trans_settings neg; + struct cam_path *path; + + if( detected_scsi_reset(softc) + || (fatal = detected_fatal_controller_error(softc)) ) { + int targ, lun; + struct xxx_hcb *h, *hh; + + /* drop all enqueued CCBs */ + 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); + } + + /* the clean values of negotiations to report */ + neg.bus_width = 8; + neg.sync_period = neg.sync_offset = 0; + neg.valid = (CCB_TRANS_BUS_WIDTH_VALID + | 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++) { + clean_negotiations(softc, targ); + + /* report the event if possible */ + 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) { + hh=h->next; + 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); + } + } + + /* report the event */ + xpt_async(AC_BUS_RESET, softc->wpath, NULL); + + /* re-initialization may take a lot of time, in such case + * its completion should be signaled by another interrupt or + * checked on timeout - but for simplicity we assume here that + * it's really fast + */ + if(!fatal) { + reinitialize_controller_without_scsi_reset(softc); + } else { + reinitialize_controller_with_scsi_reset(softc); + } + schedule_next_hcb(softc); + return; + } +</programlisting> + + <para>If interrupt is not caused by a controller-wide condition + then probably something has happened to the current hardware + control block. Depending on the hardware there may be other + non-HCB-related events, we just do not consider them here. Then + we analyze what happened to this HCB:</para> + +<programlisting> + struct xxx_hcb *hcb, *h, *hh; + int hcb_status, scsi_status; + int ccb_status; + int targ; + int lun_to_freeze; + + hcb = get_current_hcb(softc); + if(hcb == NULL) { + /* either stray interrupt or something went very wrong + * or this is something hardware-dependent + */ + handle as necessary; + return; + } + + targ = hcb->target; + hcb_status = get_status_of_current_hcb(softc); +</programlisting> + + <para>First we check if the HCB has completed and if so we check + the returned SCSI status.</para> + +<programlisting> + if(hcb_status == COMPLETED) { + scsi_status = get_completion_status(hcb); +</programlisting> + + <para>Then look if this status is related to the REQUEST SENSE + command and if so handle it in a simple way.</para> + +<programlisting> + 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 { + autosense_failed: + free_hcb_and_ccb_done(hcb, hcb->ccb, CAM_AUTOSENSE_FAIL); + } + schedule_next_hcb(softc); + return; + } +</programlisting> + + <para>Else the command itself has completed, pay more attention to + details. If auto-sense is not disabled for this CCB and the + command has failed with sense data then run REQUEST SENSE + command to receive that data.</para> + +<programlisting> + 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) ) { + /* start auto-SENSE */ + hcb->flags |= DOING_AUTOSENSE; + setup_autosense_command_in_hcb(hcb); + restart_current_hcb(softc); + return; + } + 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); + schedule_next_hcb(softc); + return; + } +</programlisting> + + <para>One typical thing would be negotiation events: negotiation + messages received from a SCSI target (in answer to our + negotiation attempt or by target's initiative) or the target is + unable to negotiate (rejects our negotiation messages or does + not answer them).</para> + +<programlisting> + switch(hcb_status) { + case TARGET_REJECTED_WIDE_NEG: + /* revert to 8-bit bus */ + softc->current_bus_width[targ] = softc->goal_bus_width[targ] = 8; + /* report the event */ + neg.bus_width = 8; + neg.valid = CCB_TRANS_BUS_WIDTH_VALID; + xpt_async(AC_TRANSFER_NEG, hcb->ccb.ccb_h.path_id, &neg); + continue_current_hcb(softc); + return; + case TARGET_ANSWERED_WIDE_NEG: + { + int wd; + + wd = get_target_bus_width_request(softc); + if(wd <= softc->goal_bus_width[targ]) { + /* answer is acceptable */ + softc->current_bus_width[targ] = + softc->goal_bus_width[targ] = neg.bus_width = wd; + + /* report the event */ + neg.valid = CCB_TRANS_BUS_WIDTH_VALID; + xpt_async(AC_TRANSFER_NEG, hcb->ccb.ccb_h.path_id, &neg); + } else { + prepare_reject_message(hcb); + } + } + continue_current_hcb(softc); + return; + case TARGET_REQUESTED_WIDE_NEG: + { + int wd; + + wd = get_target_bus_width_request(softc); + wd = min (wd, OUR_BUS_WIDTH); + wd = min (wd, softc->user_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; + + /* report the event */ + neg.valid = CCB_TRANS_BUS_WIDTH_VALID; + xpt_async(AC_TRANSFER_NEG, hcb->ccb.ccb_h.path_id, &neg); + } + prepare_width_nego_rsponse(hcb, wd); + } + continue_current_hcb(softc); + return; + } +</programlisting> + + <para>Then we handle any errors that could have happened during + auto-sense in the same simple-minded way as before. Otherwise we + look closer at the details again.</para> + +<programlisting> + if(hcb->flags & DOING_AUTOSENSE) + goto autosense_failed; + + switch(hcb_status) { +</programlisting> + + <para>The next event we consider is unexpected disconnect. Which + is considered normal after an ABORT or BUS DEVICE RESET message + and abnormal in other cases.</para> + +<programlisting> + case UNEXPECTED_DISCONNECT: + 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]; + 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)) { + 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]; + h != NULL; h = hh) { + hh=h->next; + free_hcb_and_ccb_done(h, h->ccb, CAM_SCSI_BUS_RESET); + } + + /* send event */ + xpt_async(AC_SENT_BDR, hcb->ccb->ccb_h.path_id, NULL); + + /* this was the CAM_RESET_DEV request itself, it's completed */ + ccb_status = CAM_REQ_CMP; + } else { + calculate_residue(hcb); + ccb_status = CAM_UNEXP_BUSFREE; + /* request the further code to freeze the queue */ + hcb->ccb->ccb_h.status |= CAM_DEV_QFRZN; + lun_to_freeze = hcb->lun; + } + break; +</programlisting> + + <para>If the target refuses to accept tags we notify CAM about + that and return back all commands for this LUN:</para> + +<programlisting> + case TAGS_REJECTED: + /* report the event */ + neg.flags = 0 & ~CCB_TRANS_TAG_ENB; + neg.valid = CCB_TRANS_TQ_VALID; + xpt_async(AC_TRANSFER_NEG, hcb->ccb.ccb_h.path_id, &neg); + + ccb_status = CAM_MSG_REJECT_REC; + /* request the further code to freeze the queue */ + hcb->ccb->ccb_h.status |= CAM_DEV_QFRZN; + lun_to_freeze = hcb->lun; + break; +</programlisting> + + <para>Then we check a number of other conditions, with processing + basically limited to setting the CCB status:</para> + +<programlisting> + case SELECTION_TIMEOUT: + ccb_status = CAM_SEL_TIMEOUT; + /* request the further code to freeze the queue */ + hcb->ccb->ccb_h.status |= CAM_DEV_QFRZN; + lun_to_freeze = CAM_LUN_WILDCARD; + break; + case PARITY_ERROR: + ccb_status = CAM_UNCOR_PARITY; + break; + case DATA_OVERRUN: + case ODD_WIDE_TRANSFER: + ccb_status = CAM_DATA_RUN_ERR; + break; + default: + /* all other errors are handled in a generic way */ + ccb_status = CAM_REQ_CMP_ERR; + /* request the further code to freeze the queue */ + hcb->ccb->ccb_h.status |= CAM_DEV_QFRZN; + lun_to_freeze = CAM_LUN_WILDCARD; + break; + } +</programlisting> + + <para>Then we check if the error was serious enough to freeze the + input queue until it gets proceeded and do so if it is:</para> + +<programlisting> + 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) { + hh = h->next; + + 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); + } + } + free_hcb_and_ccb_done(hcb, hcb->ccb, ccb_status); + schedule_next_hcb(softc); + return; +</programlisting> + + <para>This concludes the generic interrupt handling although + specific controllers may require some additions.</para> + + </sect1> + + <sect1> + <title>Errors Summary</title> + + <para>When executing an I/O request many things may go wrong. The + reason of error can be reported in the CCB status with great + detail. Examples of use are spread throughout this document. For + completeness here is the summary of recommended responses for + the typical error conditions:</para> + + <itemizedlist> + + <listitem><para><emphasis>CAM_RESRC_UNAVAIL</emphasis> - some + resource is temporarily unavailable and the SIM driver can not + generate an event when it will become available. An example of + this resource would be some intra-controller hardware resource + for which the controller does not generate an interrupt when + it becomes available.</para></listitem> + + <listitem><para><emphasis>CAM_UNCOR_PARITY</emphasis> - + unrecovered parity error occurred</para></listitem> + + <listitem><para><emphasis>CAM_DATA_RUN_ERR</emphasis> - data + overrun or unexpected data phase (going in other direction + than specified in CAM_DIR_MASK) or odd transfer length for + wide transfer</para></listitem> + + <listitem><para><emphasis>CAM_SEL_TIMEOUT</emphasis> - selection + timeout occurred (target does not respond)</para></listitem> + + <listitem><para><emphasis>CAM_CMD_TIMEOUT</emphasis> - command + timeout occurred (the timeout function ran)</para></listitem> + + <listitem><para><emphasis>CAM_SCSI_STATUS_ERROR</emphasis> - the + device returned error</para></listitem> + + <listitem><para><emphasis>CAM_AUTOSENSE_FAIL</emphasis> - the + device returned error and the REQUEST SENSE COMMAND + failed</para></listitem> + + <listitem><para><emphasis>CAM_MSG_REJECT_REC</emphasis> - MESSAGE + REJECT message was received</para></listitem> + + <listitem><para><emphasis>CAM_SCSI_BUS_RESET</emphasis> - received + SCSI bus reset</para></listitem> + + <listitem><para><emphasis>CAM_REQ_CMP_ERR</emphasis> - + "impossible" SCSI phase occurred or something else as weird or + just a generic error if further detail is not + available</para></listitem> + + <listitem><para><emphasis>CAM_UNEXP_BUSFREE</emphasis> - + unexpected disconnect occurred</para></listitem> + + <listitem><para><emphasis>CAM_BDR_SENT</emphasis> - BUS DEVICE + RESET message was sent to the target</para></listitem> + + <listitem><para><emphasis>CAM_UNREC_HBA_ERROR</emphasis> - + unrecoverable Host Bus Adapter Error</para></listitem> + + <listitem><para><emphasis>CAM_REQ_TOO_BIG</emphasis> - the request + was too large for this controller</para></listitem> + + <listitem><para><emphasis>CAM_REQUEUE_REQ</emphasis> - this + request should be re-queued to preserve transaction ordering. + This typically occurs when the SIM recognizes an error that + should freeze the queue and must place other queued requests + for the target at the sim level back into the XPT + queue. Typical cases of such errors are selection timeouts, + command timeouts and other like conditions. In such cases the + troublesome command returns the status indicating the error, + the and the other commands which have not be sent to the bus + yet get re-queued.</para></listitem> + + <listitem><para><emphasis>CAM_LUN_INVALID</emphasis> - the LUN + ID in the request is not supported by the SCSI + controller</para></listitem> + + <listitem><para><emphasis>CAM_TID_INVALID</emphasis> - the + target ID in the request is not supported by the SCSI + controller</para></listitem> + </itemizedlist> + </sect1> + + <sect1> + <title>Timeout Handling</title> + + <para>When the timeout for an HCB expires that request should be + aborted, just like with an XPT_ABORT request. The only + difference is that the returned status of aborted request should + be CAM_CMD_TIMEOUT instead of CAM_REQ_ABORTED (that's why + implementation of the abort better be done as a function). But + there is one more possible problem: what if the abort request + itself will get stuck? In this case the SCSI bus should be + reset, just like with an XPT_RESET_BUS request (and the idea + about implementing it as a function called from both places + applies here too). Also we should reset the whole SCSI bus if a + device reset request got stuck. So after all the timeout + function would look like:</para> + +<programlisting> +static void +xxx_timeout(void *arg) +{ + struct xxx_hcb *hcb = (struct xxx_hcb *)arg; + struct xxx_softc *softc; + struct ccb_hdr *ccb_h; + + softc = hcb->softc; + ccb_h = &hcb->ccb->ccb_h; + + 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); + } +} +</programlisting> + + <para>When we abort a request all the other disconnected requests + to the same target/LUN get aborted too. So there appears a + question, should we return them with status CAM_REQ_ABORTED or + CAM_CMD_TIMEOUT ? The current drivers use CAM_CMD_TIMEOUT. This + seems logical because if one request got timed out then probably + something really bad is happening to the device, so if they + would not be disturbed they would time out by themselves.</para> + + </sect1> + +</chapter>