Legacy Documentclose button

Important: The information in this document is obsolete and should not be used for new development.

Previous Book Contents Book Index Next

Inside Macintosh: Devices /
Chapter 3 - SCSI Manager


Using the SCSI Manager

Your device driver or application can use the SCSI Manager routines to transfer data to and from SCSI peripheral devices. This section begins with a simple example that illustrates the basic steps necessary to read data from a SCSI device. Next, the details of using transfer instruction blocks and command descriptor blocks are presented, followed by a complete program that uses these concepts.

Reading Data From a SCSI Device

This section shows you how to use the SCSI Manager routines to read data from a SCSI peripheral device. Your application or device driver follows these steps for reading data from a SCSI device:

  1. Create a command descriptor block (CDB) and a transfer instruction block (TIB).
  2. Call the SCSIGet function to arbitrate for the SCSI bus.
  3. Use the SCSISelect function to select the SCSI device to read from.
  4. Use the SCSICmd function to send a command descriptor block (CDB) containing a SCSI read command to the device.
  5. Call the SCSIRead function to transfer the data.
  6. Call the SCSIComplete function to get the status and message bytes that mark the end of a transaction over the SCSI bus.

Listing 3-1 shows code illustrating these steps. The example is simplified, in that it excludes the details of setting up the CDB and TIB data structures prior to initiating the read operation. That information is presented in the next section.

Listing 3-1 Reading data from a SCSI device

FUNCTION MyReadSCSI : OSErr;
CONST
   kCompletionTimeout = 300;  {value passed to SCSIComplete }
                              { 300 ticks = 5 seconds}
VAR
   CDB:        PACKED ARRAY [0..5] OF Byte;     {command descriptor block}
   CDBLen:     Integer;                         {length of CDB}
   TIB:        PACKED ARRAY [0..1] OF SCSIInstr;{transfer instruction block}
   scsiID:     Integer;                         {SCSI ID of the target}
   compStat:   Integer;                         {status from SCSIComplete}
   compMsg:    Integer;                         {message from SCSIComplete}
   compErr:    OSErr;                           {result from SCSIComplete}
   myErr:      OSErr;                           {cumulative error result}
BEGIN
   {Note: This example assumes the CDB, CDBLen, TIB, and scsiID variables }
   { already contain appropriate values.}
   myErr := SCSIGet;                            {arbitrate for the bus}
   IF myErr = noErr THEN
   BEGIN
      myErr := SCSISelect(scsiID);              {select the target}
      IF myErr = noErr THEN
      BEGIN
         myErr := SCSICmd(@CDB, CDBLen);        {send read command}
         IF myErr = noErr THEN
            myErr := SCSIRead(@TIB);            {polled read}
         {complete the transaction and release the bus}
         compErr := SCSIComplete(compStat, compMsg, kCompletionTimeout);
         {return the most informative error result}
         IF myErr = noErr THEN                  {if no prior errors, then }
            myErr := compErr;                   { return SCSIComplete result}
      END;
   END;
   MyReadSCSI := myErr;                         {return result code}
END;
The MyReadSCSI function follows the steps presented earlier in this section, starting with calling the SCSIGet and SCSISelect functions to select the target device, sending a read command using the SCSICmd function, and reading the data with the SCSIRead function. Finally, the SCSIComplete function is called to obtain the status and message bytes from the device and restore the bus to the bus free phase.

The MyReadSCSI function assumes these variables have already been set up properly:

Within its narrowed scope, the MyReadSCSI function is correct and complete. You can easily modify it to handle other operations, such as writing data, or conducting blind transfers.

The MyReadSCSI function shows one way of handling the error results returned by a series of SCSI Manager functions. The result codes returned by the SCSI Manager functions are put into the myErr local variable as each SCSI Manager function is called. Your code should likewise check the result codes and proceed only if there is no error. Calling the SCSIComplete function is the last step, and requires special handling. Your code should call the SCSIComplete function even if an earlier SCSI Manager routine has returned an error, because the SCSIComplete function takes whatever steps are necessary to restore the SCSI bus to the bus free phase. For more information, see "Using the SCSIComplete Function" on page 3-21.

Using CDB and TIB Structures

The command descriptor block (CDB) is a data structure defined by the SCSI specification for communicating commands to SCSI devices. The SCSI Manager does not interpret the commands in a CDB, it simply transfers them to the selected device.

You send a CDB to a SCSI device using the SCSICmd function. The size of the CDB structure can be 6, 10, or 12 bytes, depending on the number of parameters required by the command. The first byte specifies the command, and the remaining bytes contain parameters.

The SCSI specification includes a set of standard commands that all SCSI devices must implement, and a wide range of commands for specific device types. In addition, manufacturers can define proprietary command codes for their devices. You should refer to the manufacturer's documentation for information about the commands supported by a particular device.

You use the transfer instruction block (TIB) data structure to pass instructions to the SCSI Manager SCSIRead, SCSIRBlind, SCSIWrite, and SCSIWBlind functions. The TIB structure is defined by the SCSIInstr data type. The scOpcode field contains a transfer operation code, and the scParam1 and scParam2 fields contain parameters to the command. The instruction set consists of eight operation codes that allow you to transfer data, increment a counter, and form iterative loops. See "SCSI Manager TIB Instructions," beginning on page 3-27, for details of the TIB instruction set.

Listing 3-2 shows an example of how you can use CDB and TIB instructions to send a command and read information from a SCSI peripheral device. The MySCSIInquiry program uses the SCSI INQUIRY command to obtain a 256-byte record of information from a target device. This information includes the target's device type, vendor ID, product ID, revision data, and other vendor-specific information. The INQUIRY command is one of the standard commands that all SCSI devices must support.

Listing 3-2 Using TIB and CDB structures

PROGRAM MySCSIInquiry;
USES SCSI;

CONST
   kInquiryCmd = $12;         {SCSI command code for the INQUIRY command}
   kVendorIDSize = 8;         {size of the Vendor ID string}
   kProductIDSize = 16;       {size of the Product ID string}
   kRevisionSize = 4;         {size of the Revision string}
   kCompletionTimeout = 300;  {timeout value passed to SCSIComplete}
   kMySCSIID = 0;             {SCSI ID of the target device}

{This structure duplicates the format of the SCSI INQUIRY response record, }
{ as described in the SCSI-2 specification. The first 5 bytes are required }
{ for SCSI-1 devices. The first 36 bytes are required for SCSI-2 devices. }
{ The AdditionalLength field contains the length of the vendor-specific }
{ information, if any, beyond the 5 bytes required for all devices.}
TYPE MyInquiryRecord =
PACKED RECORD
   DeviceType:       Byte;    {SCSI device type code (disk, tape, etc.)}
   DeviceQualifier:  Byte;    {7-bit vendor-specific code}
   Version:          Byte;    {version of ANSI standard (SCSI-1 or SCSI-2)}
   ResponseFormat:   Byte;
   AdditionalLength: Byte;    {length of vendor-specific information}
   VendorUse1:       Byte;
   Reserved1:        Integer;
   VendorID:         PACKED ARRAY [1..kVendorIDSize] OF Char;  {manufacturer}
   ProductID:        PACKED ARRAY [1..kProductIDSize] OF Char; {product code}
   Revision:         PACKED ARRAY [1..kRevisionSize] OF Char;  {firmware rev}
   VendorUse2:       PACKED ARRAY [1..20] OF Byte;
   Reserved2:        PACKED ARRAY [1..42] OF Byte;
   VendorUse3:       PACKED ARRAY [1..158] OF Byte;
END;                          {a total of 256 bytes of data may be returned}
VAR
   CDB:        PACKED ARRAY [0..5] OF Byte;     {command descriptor block}
   TIB:        PACKED ARRAY [0..1] OF SCSIInstr; {transfer instruction block}
   Response:   MyInquiryRecord;  {holds target's response}
   compStat:   Integer;          {status information from SCSIComplete}
   compMsg:    Integer;          {message information from SCSIComplete}
   compErr:    OSErr;            {result from SCSIComplete}
   myErr:      OSErr;            {error result}
   i:          Integer;          {loop counter}
BEGIN
   {Set up the command buffer with the SCSI INQUIRY command.}
   CDB[0] := kInquiryCmd;     {SCSI command code for the INQUIRY command}
   CDB[1] := 0;               {unused parameter}
   CDB[2] := 0;               {unused parameter}
   CDB[3] := 0;               {unused parameter}
   CDB[4] := 5;               {maximum number of bytes target should return}
   CDB[5] := 0;               {unused parameter}

   {Set up the two TIB structures; one to read, the other as terminator.}
   TIB[0].scOpcode := scNoInc;            {specify the scNoInc instruction}
   TIB[0].scParam1 := LongInt(@Response); {pointer to buffer}
   TIB[0].scParam2 := 5;                  {number of bytes to move}
   TIB[1].scOpcode := scStop;             {specify the scStop instruction}
   TIB[1].scParam1 := LongInt(NIL);       {unused parameter}
   TIB[1].scParam2 := LongInt(NIL);       {unused parameter}

   WRITELN('SCSI inquiry example. Testing SCSI ID:', kMySCSIID);

   {Send the INQUIRY command twice. The first time to obtain the }
   { AdditionalLength value in the fifth byte of the INQUIRY response }
   { record and the second time to read that additional amount. Notice }
   { that SCSIComplete is always called if SCSISelect was successful.}
   FOR i := 1 to 2 DO
   BEGIN
      myErr := SCSIGet;                   {arbitrate for the bus}
      IF myErr = noErr THEN
         myErr := SCSISelect(kMySCSIID);  {select the target}
      IF myErr <> noErr THEN
      BEGIN
         WRITELN('Error result from SCSIGet or SCSISelect:', myErr);
         EXIT(MySCSIInquiry);
      END;
      myErr := SCSICmd(@CDB, 6);    {send INQUIRY command to the target}
      IF myErr = noErr THEN
      BEGIN
         myErr := SCSIRead(@TIB);   {read the INQUIRY response record}
         IF myErr = noErr THEN      {if there was no error, and }
            IF i = 1 THEN           { if this is the first time through }
            BEGIN                   { the loop, get the AdditionalLength}
               CDB[4] := CDB[4] + Response.AdditionalLength;
               TIB[0].scParam2 := TIB[0].scParam2 +
                                  Response.AdditionalLength;
            END;
      END;

      {Call SCSIComplete to clean up. Results are ignored in this example.}
      compErr := SCSIComplete(compStat, compMsg, kCompletionTimeout);
      IF myErr <> noErr THEN
      BEGIN
         WRITELN('Error result from SCSICmd or SCSIRead:', myErr);
         EXIT(MySCSIInquiry);
      END;
   END;  {FOR loop}
   
   {Display the information.}
   IF Response.AdditionalLength > 0 THEN
   BEGIN
      WITH Response DO
      BEGIN
         WRITE('VendorID:');
         FOR i := 1 TO kVendorIDSize DO
            WRITE(VendorID[i]);
         WRITELN;
         WRITE('ProductID:');
         FOR i := 1 TO kProductIDSize DO
            WRITE(ProductID[i]);
         WRITELN;
         WRITE('Revision:');
         FOR i := 1 TO kRevisionSize DO
            WRITE(Revision[i]);
         WRITELN;
      END;
   END;
END.
The MySCSIInquiry program first defines various constants, including the kInquiryCmd constant, which contains the operation code for the SCSI INQUIRY command. Next the MyInquiryRecord data type is declared, a 256-byte structure that holds the information returned by the target. The fields of this record are based on the SCSI-2 specification. The SCSI-1 specification requires that devices return at least the first 5 bytes of information (DeviceType through AdditionalLength), however, many SCSI-1 devices and all SCSI-2 devices return at least the first 36 bytes (DeviceType through Revision).

In the 6-byte CDB used by the SCSI INQUIRY command, the first byte contains the operation code and the fifth byte specifies the maximum number of bytes the target is allowed to send in response to the inquiry. Restricting the target's response to a specified number of bytes prevents it from overflowing the buffer the initiator has set aside to accept the data.

This program uses two transfer instruction blocks, both of which are relatively simple. The first TIB is an scNoInc instruction, whose parameters specify a data transfer into the Response record. The second TIB is an scStop instruction, which terminates the SCSI Manager processing that occurs inside the SCSIRead function.

The body of the MySCSIInquiry program consists of a loop that performs the
arbitrate/select/command/transfer/complete sequence described in "Reading Data From a SCSI Device" on page 3-15. The loop executes this sequence of SCSI Manager functions twice. The first time sends the SCSI INQUIRY command to the target and requests only the standard 5 bytes of information supplied by all SCSI devices. The value of the fifth byte (returned in the AdditionalLength field of the Response record) indicates the amount of additional information the device is capable of returning. Before going through the loop a second time, both the CDB and the TIB are modified to reflect the additional size of the inquiry information.

The program checks for errors at each stage in the SCSI Manager calling sequence. If either the SCSIGet or SCSISelect function returns an error, the program exits. If the SCSICmd function returns an error, SCSIRead is not called. To complete the transaction and release the bus, the SCSIComplete function is always called if SCSISelect was successful.

Using the SCSIComplete Function

The SCSIComplete function completes a SCSI transaction and restores the bus to the bus free phase. You must call this function at the end of every transaction that proceeds past the selection phase, even if the transaction does not complete successfully.

The SCSIComplete function waits a specified number of ticks for the current transaction to complete, and then returns one byte of status information and one byte of message information from the target device. The function returns one of the following result codes:

Choosing Polled or Blind Transfers

The SCSI Manager supports two data transfer methods: polled and blind. During a polled transfer, the SCSI Manager senses the state of the Macintosh SCSI controller hardware to determine when the controller is ready to transfer another byte. In a blind transfer, the SCSI Manager assumes that the SCSI controller (and the target device) can keep up with a specified transfer rate, and does not explicitly sense whether the hardware is ready.

Note
These transfer modes are specific to the Macintosh SCSI interface hardware implementation and are not part of the SCSI protocol.
When the SCSI Manager retrieves data from the SCSI controller, it can explicitly verify that a byte was received by the controller and is ready for transfer. The SCSI Manager does this by polling a status register in the controller. Alternatively, the SCSI Manager can assume that a byte is available and can attempt to read it without checking first. As long as a SCSI device can supply data to the SCSI controller faster than the SCSI Manager can retrieve it, blind transfers work reliably. If the SCSI device cannot keep up, timeout errors and other problems can occur.

For example, in the Macintosh Plus (the first model to include a SCSI interface), if the SCSI Manager reads a byte from the SCSI controller chip before the chip receives a byte from the target, the read operation completes but the data is invalid. The SCSIComplete function does not always return an error result in this case.

Newer Macintosh models include hardware support for handshaking, allowing blind transfers to be both fast and reliable. This handshaking allows the SCSI controller to defer the CPU if no data is available to transfer. If the data doesn't arrive within a specified period, the SCSI Manager returns the scBusTOErr result. The timeout period varies for each Macintosh model. This type of timeout error does not occur when using polled transfers.

Polled transfers work reliably with all SCSI peripheral devices, and are a good choice for slow or unpredictable devices such as printers and scanners. You should also use polled transfers if you are unfamiliar with the characteristics of a particular device. You use the SCSIRead and SCSIWrite functions to initiate polled transfers.

For disk drives and other high-speed devices, blind transfers can significantly increase data throughput. As long as the device does not incur any delays during a transfer, or the delays occur at predictable times, blind transfers are a good choice. You use the SCSIRBlind and SCSIWBlind functions to initiate blind transfers.

Because the first byte transferred by each TIB instruction is always polled, even in blind mode, you can work around predictable delays using an appropriate sequence of TIB instructions. For example, if a peripheral device always pauses at a specific byte within a transfer, you can divide the transfer into blocks so that the delayed byte is located at the start of a TIB instruction. The SCSI Manager polls the controller before the first byte, then reads the remaining bytes using a blind transfer. For disk drives, predictable delays generally occur at sector boundaries, so you can compensate by dividing your transfers into sector-sized blocks.


Previous Book Contents Book Index Next

© Apple Computer, Inc.
3 JUL 1996