Important: The information in this document is obsolete and should not be used for new development.
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:
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.
- Create a command descriptor block (CDB) and a transfer instruction block (TIB).
- Call the
SCSIGet
function to arbitrate for the SCSI bus.- Use the
SCSISelect
function to select the SCSI device to read from.- Use the
SCSICmd
function to send a command descriptor block (CDB) containing a SCSI read command to the device.- Call the
SCSIRead
function to transfer the data.- 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 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;TheMyReadSCSI
function follows the steps presented earlier in this section, starting with calling theSCSIGet
andSCSISelect
functions to select the target device, sending a read command using theSCSICmd
function, and reading the data with theSCSIRead
function. Finally, theSCSIComplete
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
- a SCSI command descriptor block (the
CDB
variable)- an integer specifying the length of the command descriptor block (the
CDBLen
variable)- a transfer instruction block (the
TIB
variable)- an integer specifying the SCSI ID of the target device (the
scsiID
variable)
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 themyErr
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 theSCSIComplete
function is the last step, and requires special handling. Your code should call theSCSIComplete
function even if an earlier SCSI Manager routine has returned an error, because theSCSIComplete
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
, andSCSIWBlind
functions. The TIB structure is defined by theSCSIInstr
data type. ThescOpcode
field contains a transfer operation code, and thescParam1
andscParam2
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 SCSIINQUIRY
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. TheINQUIRY
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.TheMySCSIInquiry
program first defines various constants, including thekInquiryCmd
constant, which contains the operation code for the SCSIINQUIRY
command. Next theMyInquiryRecord
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
throughAdditionalLength
), however, many SCSI-1 devices and all SCSI-2 devices return at least the first 36 bytes (DeviceType
throughRevision
).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 theResponse
record. The second TIB is anscStop
instruction, which terminates the SCSI Manager processing that occurs inside theSCSIRead
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 SCSIINQUIRY
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 theAdditionalLength
field of theResponse
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
orSCSISelect
function returns an error, the program exits. If theSCSICmd
function returns an error,SCSIRead
is not called. To complete the transaction and release the bus, theSCSIComplete
function is always called ifSCSISelect
was successful.Using the SCSIComplete Function
TheSCSIComplete
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:
noErr
. TheSCSIComplete
function was able to obtain both the status and message bytes successfully. This result code indicates that the information is valid.scComplPhaseErr
. Upon entry, theSCSIComplete
function detected that the target was ready to transfer information (that is, the /REQ signal was asserted) but the SCSI bus was not in the status phase. The SCSI Manager performed corrective action to bring the bus into the status phase. For example, accepting bytes from the target without passing them to your program ("bit-bucketing"), or sending an arbitrary number of bytes to the target. Once in status phase, theSCSIComplete
function was able to transfer the status and message bytes successfully, and this information is valid.scPhaseErr
. TheSCSIComplete
function could not force the SCSI bus into the status phase. The status and message bytes should be considered invalid. You may need to reset the bus to restore proper operation.scCommErr
. This result code covers any other error conditions encountered by theSCSIComplete
function, such as the timeout that occurs if the transaction does not complete within the specified number of ticks.
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.
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.
- Note
- These transfer modes are specific to the Macintosh SCSI interface hardware implementation and are not part of the SCSI protocol.
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
andSCSIWrite
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
andSCSIWBlind
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.
© Apple Computer, Inc.
3 JUL 1996