SCSI Intro
Volume Number: 3
Issue Number: 3
Column Tag: Forth Forum
Introduction to SCSI Devices
By Jörg Langowski, MacTutor Editorial Board, Grenoble, France
In this article Jörg introduces us to the SCSI routines. Last month, Tim Standing
began a series on building a hard disk. Next month, Tim Standing will continue his
series with the SCSI driver and formatting program that detects bad disk blocks for his
hrad disk project. Until then, this article should help us prepare for the world of SCSI
devices from the Forth perspective.
If you have investigated the possibility of building the hard disk published last
month, then you know that prices for SCSI hard disks have gone down rapidly even as I
write this. And, starting with a naked hard disk drive, a controller card and a power
supply, it shouldn't be that difficult to get a hard disk system going, since an example
of a (buggy) SCSI driver has already been published several months ago in the
Software Supplement by Apple. While we wait for Tim Standing's SCSI example, here
are some of my thoughts on the several obstacles that have to be overcome by the
naive-thinking person (like me) who just wants to plug it all together and have those
20, 40 or more megabytes of extra space (in my case, it was an 80 megabyte unit
from Quantum Co. with an integrated SCSI interface). The first problem, and the major
one, is to write a driver for the particular SCSI device. Even though starting from
Apple's example makes the job easier, it is by no means trivial to adapt the generic
driver to your particular kind of disk reliably.
We'll return to that point later; first, I have to apologize for not being able to
print the source code of Apple's SCSI driver in our magazine (for copyright reasons).
However, for anybody interested this code should be easy to come by, either from the
Software Supplement or by downloading it from Delphi or Compuserve. For those of
you familiar with drivers, I'll give a short description of what it does; we'll start with
a review of the organization of an SCSI device (also given in IM).
SCSI devices on the Mac+
The SCSI driver resides on the SCSI disk at a position given in the device
descriptor map (DDM), which is always block 0 of the SCSI device. Here, the positions
and lengths of the drivers available on the disk are stored. Inside Mac describes its
format:
( ***The layout of blk 0 of a bootable SCSI device*** )
( Forth Format )
HEX
4552 CONSTANT SBSigWord ( block 0 signature )
0 CONSTANT SBSig ( signature word )
2 CONSTANT SBBlkSize ( block size of device )
4 CONSTANT SBBlkCount ( # blocks on device )
8 CONSTANT SBDevType ( device type code )
A CONSTANT SBDevID
C CONSTANT SBData ( start of data section )
10 CONSTANT SBDrvCount ( # drivers to follow )
12 CONSTANT SBDrvrs ( start of driver descriptors )
( Driver descriptors)
0 CONSTANT DDBlock ( physical block of driver )
4 CONSTANT DDSize ( block count of driver )
6 CONSTANT DDType ( Processor type of driver )
[TOKEN:10272]Macintosh = 1 )
Block 1 of the disk is supposed to contain the device partition map (DPM), which
describes the various partitions into which the disk may be split up. (In our case,
we'll work with one partition only). The format of the DPM is as follows:
( ***The layout of blk 1 of a bootable SCSI device*** )
( in Forth format )
HEX
5453 CONSTANT PDSigWord ( block 1 signature )
0 CONSTANT PDSig ( PDSigWord goes here )
2 CONSTANT PDStart ( starting block of partition )
6 CONSTANT PDSize ( # blocks in partition )
A CONSTANT PDFSID
( File System ID of partition creator or NIL )
( 'TFS1' for Macintosh )
In the case of a disk which will only be accessed by a Macintosh, block 2 and the
following blocks then contain the disk driver code.
As you may recall, the standard format of a driver consists of a header in which
offsets to the five driver routines Open, Close, Control, Status and Prime are kept,
followed by the routines themselves (see my last article). The SCSI driver, however,
must contain not only those routines, but also some means to install itself at the time
the system boots up, so that the hard disk will be bootable.
Therefore the SCSI driver code starts with a JMP instruction to the installation
routine. The routine that Apple provides will check whether the SCSI disk is
compatible with the Macintosh operating system ( that means that the 'signatures' of
block 0 and 1 are correct, the driver type is =1, and the file system ID is 'TFS1'), and
if so, create a device control entry for the driver and install it. The system then knows
that a new disk is present and will make it available for use.
Other routines provided in the generic driver code are those for opening, block
read/write, eject and getting the SCSI disk icon. See Apple's source code for a detailed
description.
The important part that will concern us here is how to read or write a block on
an SCSI disk, which leads us to the second problem that one encounters when trying to
hook up a hard disk to the Mac Plus.
SCSI input/output - a quick overview
Apple, in its infinite wisdom, has decided to leave two important things open as an
'exercise to the reader' (sound familiar?). First, there is no utility that detects
(maybe on boot-up?) whether an SCSI device is present, and initialize it using the
standard initialization dialog. For doing this, of course, a general purpose SCSI driver
would have to be contained in either the System file or in ROM that can then be used for
accessing the disk. But since SCSI devices are not quite standardized well enough, Apple
might have left that out on purpose.
Second, the SCSI routines in ROM provide only very low-level support. Since, as
you'll soon see, the same SCSI command sequences occur over and over again when
working with the SCSI bus, Apple should have provided a routine that puts it all
together (and they promise to do so, actually, in one of their next System releases,
perhaps in the new Macs, recently announced).
Since such support was not available at the time I wrote this, I wrote some
routines that simplify SCSI handling; this shall be the subject of this month's column.
SCSI command sequence
The sequence of commands that has to be executed to transfer information to or
from an SCSI device is quite complicated. This has to do with the fact the the SCSI bus
can be in different 'phases' depending on the state of the control lines. I'll not discuss
the electronic details here, but just give a description of the events as they happen one
after the other.
First, the device which initiates the transfer (the 'initiator') has to get control
of the bus. This is done by executing the SCSIGet command from the SCSI manager. Only
one device can have control of the bus at each time; in our case this will most probably
be the Macintosh. The initiator then selects a target to transfer data to or from; this is
done by calling SCSISelect with the target's address as a parameter. Each address
corresponds to one bit of the data bus, so there may be a maximum of eight SCSI devices
on one bus. The Macintosh has address 7, corresponding to the highest bit. Usually, if
only one SCSI device is connected, it'll have address 6. All of the following examples
refer to this setup.
Once SCSIGet and SCSISelect have been called, the link between two devices is
established and data transfer may begin. The story becomes more complicated here
since there are four types of data that are transferred: commands, 'real' data, status
information and messages. Data are always exchanged byte-wise using two command
lines for a handshake protocol. The type of the data is determined by the setting of other
control lines on the bus.
The transfer sequence generally proceeds in the following way: The initiator
sends a command to the target. After the command has been accepted by the target there
can be an exchange of 'real' data on the bus, either from the target to the initiator
(Read) or the other way (Write). There are also commands that are not followed by
any data transfer.
When the data has been exchanged, the target will have some additional
information ready as to whether the command has been completed successfully, and for
more complicated commands, extra messages.
Again, SCSI manager routines are provided for each step of this sequence. The
command is sent by the means of the SCSICommand routine, which accepts as
parameters the address of a command block and its total length in bytes. After
execution, it will return a status code which is zero if the command has been sent
successfully.
Typical command blocks are given in listing 1. All the blocks defined there are
preceded by their length; a convention that is used by my routines. The SCSI commands
are standardized in some way: a Read command, for any device, will be six bytes long,
the command code in the first byte is always $08, and the start position and the
number of bytes to transfer are always kept in the same fields of the command block.
When a Read command has been issued, the target will be ready for transferring
the requested number of bytes. The Macintosh gets the data through either of two
commands: SCSIRead or SCSIRBlind. For a normal Read a handshake is performed on
every byte transfer; for a blind Read the Mac just assumes that the target is ready to
provide the data at the maximum speed with which it can be read, which is 384
KBytes/sec. This often works, but has to be tested in each individual case.
Commands that transfer data from the initiator to the target are handled by
SCSIWrite or SCSIWBlind in an analogous manner.
The parameter that is passed to the SCSI read/write routines is the address of a
transfer instruction block, a short sequence of instructions, each 10 bytes long, that
form a pseudoprogram. That way one single read may be executed in chunks of - for
example - 512 bytes each. As an example let's look at the SCInc instruction: it consists
of the command word ($0002), a long word buffer address addr, and a long word byte
count n. When this instruction is executed by the SCSIRead command, n bytes will be
transferred to/from the buffer starting at addr, and the buffer address - in the
pseudoprogram - will be incremented by n. For the SCNoInc instruction, the buffer
address is left unchanged.
The pseudoprogram may contain looping instructions so that the same sequence of
transfers is executed a defined number of times. In the simple example, I'm not using
any of those instructions, but only an SCNoInc followed by an SCStop. This is what
Apple recommends you to do in any case, because there is a bug in the SCSI handler in
ROM that screws up multi-block transfers. This bug is supposed to be fixed by a patch
in System 3.2, but I still had problems even with 3.2.
In summary, to read n bytes from an SCSI device into a buffer starting at addr
one would have to:
- store addr and n in the appropriate fields of the pseudoprogram block,
- call SCSICmd with a pointer to the command block for the Read command and the
command length on the stack,
- call SCSIRead or SCSIRBlind with a pointer to the pseudoprogram block on the
stack.
After executing this sequence, the transfer would either have been completed
successfully, or the target would have some extra information waiting about what went
wrong. In order to check successful execution of the command, SCSIComplete is called.
This routine takes three parameters, a tick count, and the addresses of two variables,
message and status. The tick count is the timeout that the Macintosh should allow for
completion of the command (successful or non-successful). After completion, two
additional types of information are transferred over the bus, namely status
information and messages from the target to the identifier. The status information is
always one byte which is zero when everything went o.k., the message may consist of
several bytes, but in our case is always one byte =0.
The most common status apart from zero (=OK) is status=2, which means check
condition. You should the issue a Request Sense command to the SCSI device in that case
to find out exactly what unusual condition occurred. The format and meaning of the data
returned by Request Sense varies depending on the device. A common condition that
occurs with some newer SCSI controllers directly after a reset of the bus (by the
SCSIReset command) is Unit attention, which is meant to tell the system that this
particular unit needs attention because - for example - it just came back from a reset
or power-up. The problem here is that on boot-up, the Macintosh resets the SCSI bus
and expects to be able to read immediately from the hard disk. Since no Request Sense
has been issued, however, the read fails and the Mac can never boot from the hard disk.
The only remedy is to use a controller on which the Unit Attention feature either isn't
installed or can be disabled.
After a successfully completed SCSI command, however, SCSIComplete should
return zeroes in both message and status.
Let's summarize the complete SCSI command sequence:
• SCSIGet gets control of the bus for the Mac,
• SCSISelect selects the target device,
• SCSICmd issues a command to the target,
• SCSIRead, SCSIRBlind, SCSIWrite, SCSIWblind are used - if necessary - to
transfer data to/from the target,
• SCSIComplete waits until the data transfer is completed or a timeout has expired
and returns the status and message bytes of the target.
In order to facilitate the handling of this command sequence, five higher-level
words are defined in listing 1: doscsi for commands that require no data transfer,
doscsi.r and doscsi.rb for normal and blind reads, and doscsi.w and doscsi.wb for
normal and blind writes. The parameters to those routines are, from bottom to top on
the stack:
- the timeout tick count;
- a pointer to the command block, the first byte of this block must contain the
length of the command following it;
- the device number;
and in addition for the data transferring commands
- a pointer to the data buffer;
- the number of bytes to transfer.
A number of standard SCSI command blocks are also predefined in the listing.