Modifying SCSI
Volume Number: 4
Issue Number: 1
Column Tag: Advanced Mac'ing
Modifying Apple's SCSI Driver
By Jörg Langowski, MacTutor Editorial Board, Grenoble, France
How to Make Apple’s ‘non-functional’ SCSI Driver, Functional!
Happy New Year and welcome to our fourth volume. We’ll step right in the middle
of things with a SCSI driver project that will take us two columns.
You’ve seen several articles on SCSI device handling in MacTutor; Tim Standing’s
contributions in V3#2 and V3#6, and my own column in V3#3. We showed you how to
hook up the electronics of a generic SCSI disk to the Mac, how to play with SCSI
commands and do raw data transfers, and how to format the SCSI disk. One crucial thing
has always been missing: the SCSI driver itself.
History
It’s been over a year now that I hooked up my own 80 MByte disk, a Quantum
Q280, to my Mac (Plus at that time), and was confronted with all those problems,
including getting a driver that would make my disk work. That last part turned out to
be the hard one, at that time.
All information that I could then get hold of was the source of a ‘generic’ SCSI
driver published by Apple in a Software Supplement, and also available on various
bulletin boards. The problem with this driver was that it wouldn’t quite work as
promised. In this column I’ll explain the modifications that I had to do to the Apple
driver to make it boot and run on the Quantum Q280 disk. I am not claiming that it will
drive any SCSI disk, although in theory it should drive many of them; in practice,
however, a Seagate ST225N will run with the original code supplied by Apple, but not
with my code. However, by describing my implementation and its differences to the
original, I hope to give you some hints what can be done to make an arbitrary SCSI disk
work with a fully or partially home-brew driver.
Some of you might find this information out of date, since by now the HD SC
installer has been released by Apple. Alas: I lost a lot of time backing up some 63
Megabytes to run the HD SC installer on my Q280; only to find out that the drive
wasn’t recognized and I had to re-install my old driver. Which, as you’ll see soon, is
admittedly some kind of a kludge, but has performed without problems for over one
year now.
It is for exactly the reason that, after all, SCSI devices are not that generic, that
David Smith asked me to write about my SCSI driver [Apple Expo, Paris Sept 30: “You
really have a SCUZZY driver? Well, publish it!”]. Therefore, not much Forth this
month. Rather assembler, and general ramblings. Next month I’ll continue with a
re-implementation of Apple’s SCSI driver in Forth. Yes, that’s right. Just to get all
you assembly/ hardware guys out there to buy your copies of Mach2.
Apple’s ‘generic’ SCSI driver
There are two problems with printing the full, commented source listing of
Apple’s driver here (I already mentioned this in the March 87 article). First,
although it is almost two years old, it is copyrighted by Apple. Second, the source is
quite long, (some 12 pages with comments) and it is available through APDA, Delphi,
Compuserve and who knows where. I think it is much simpler that you download a copy
of the [heavily commented] driver’s source - I got mine from Delphi - and follow the
explanations in this article to understand what it’s doing. The parts of the driver that I
modified are printed in the listing. The complete source of a SCSI driver, written in
Forth, will follow next month.
[The SCSI driver example is a product of APDA, part # KMSSDP, which sells for
$10. The APDA catalog says, and I quote: ‘Contains a heavily commented, but
non-functional, sample scsi driver written in Assembly language’. Jörg’s article
should help make it functional! We can’t distribute it since APDA is selling it as a
product, so please contact them for this source. You’ll also want to make sure you have
the 2.0b1 MPW Shell release, which contains the latest assembler equates including
the SCSI equates. Included with the scsi example is a eight page handout which says: ‘It
is provided to shows how such a driver might be written (by someone with not a lot of
time to tell developers how to write drivers!)’. And again, this little gem:
‘Unfortunately, if you happen to have gotten a good deal on an old 370 meg drive that
you want to hook up to your Macintosh, we can’t really help you (there are only six of
us for all Macintosh development, and four thousand of you!)’. The comments go on to
state that the Technical Support Group’s charter is to help developer’s in creating
products for market, so I assume any subject that does not appear to lead to product
development is given little developer support! I have always disagreed with this
narrow view of software development. The amount of Macintosh software developed
in-house far exceeds what little commercial “MS Word” type program development
that is going on, and this will only increase as the Mac II moves into big-time
engineering departments. Computers were tools for problem solving long before they
were a means to help Apple sell more computers. -Ed]
The structure of a SCSI driver
Like any driver, the SCSI driver will have a header before the actual driver code.
This header looks like the following:
Flags [word] $4F00
;read, write, control, status, needs lock
[word] $0 ;no periodic action
[word] $0 ;no event mask
[word] $0 ;no menu
; Entry point offset table
[word] offset to Open routine
[word] offset to Prime routine
[word] offset to Control routine
[word] offset to Status routine
[word] offset to Close routine
[counted string] the driver name
The block number at which the driver can be found on the disk, and its length, are
given in the device descriptor map (DDM) on block 0 of the SCSI disk. On bootup, the
driver is found (hopefully) at the location given in the DDM, read from the disk, and a
jump is made to the beginning of the driver. Therefore, an SCSI driver must contain
executable code before the header. Usually, a JMP or BRA instruction is contained here
that jumps to a piece of code which does the actual initialization and installation.
SCSI driver installation
The procedure by which the Macintosh will install the driver on bootup is partly
described in IM IV-293, partly in the comments to Apple’s SCSI driver source. I’ll
summarize the relevant information here.
On bootup, the Macintosh will try to select a device on the SCSI bus by its ID,
starting with ID=6 and going downward. In the older systems, the highest ID found was
used as the number of the startup device; in the new system releases, the control panel
allows you to select where you want to boot from.
The driver descriptor and device partition maps are then read from block 0 and 1
of the startup device. Their signatures are checked; the first word of block 0 must be
$4552, and of block 1, $5453. The driver descriptor map (DDM) must contain an
entry that corresponds to a driver for the Macintosh (processor type = 1, as explained
in V3#3). The driver is read from the blocks given in the DDM and put into memory
allocated in the system heap. Then the Macintosh jumps to the beginning of the driver
code.
The driver code is supposed to ‘install’ itself. In Apple’s code, the installation
routine is located at the very end of the driver. It takes three parameters on entry:
- A0, a pointer to the device partition map
- D5, the SCSI ID of the device for this driver
- D7, default data start area’, not used in the Apple driver.
I’ll explain the steps the installer goes through. First, we have to let the system
know that we exist. The table of existing drivers is the unit table (IM II-191), a
pointer to which is available through the system global UTableBase ($11C). The unit
table contains a handle to the unit’s device control entry at
UTableBase+unitnumber*4. By definition, the unit number of the SCSI device is its
SCSI ID + 32. The driver reference number for a device is the two’s complement of its
unit number.
We therefore know our unit number and reference number when the installation
routine is entered. The trap DrvrInstall (Toolbox $A03D, and not documented in IM),
called with the driver reference number in D0, will allocate a new device control
entry for the driver and put a handle to this DCE into the unit table. The format of a
DCE, as given in IM II-190, looks like:
TYPE DCtlEntry = RECORD
DCtlDriver : Ptr ;
{ptr to ROM or handle to RAM driver}
DCtlFlags : Integer ; {flags}
DCtlQHdr : QHdr ; {driver’s i/o queue}
DCtlPosition : LongInt ;
{byte pos used by read and write calls}
DCtlStorage : Handle ;
{hndl to RAM drivers private storage}
DCtlRefNum : Integer ;
{driver’s reference number}
DCtlCurTicks : LongInt ;
{long counter for timing system task calls}
DCtlWindow : Ptr ;
{ptr to driver’s window if any}
DCtlDelay : Integer ;
{number of ticks btwn sysTask calls}
DCtlEMask : Integer ;
{desk acessory event mask}
DCtlMenu : Integer ;
{menu ID of menu associated with driver}
END ;
After the DCE has been created, some of its fields (DCtlDriver, DCtlFlags,
DCtlDelay, DCtlEmask, and DCtlMenu) are filled using the information in the driver
header. The driver will be marked “ROM based” (bit 6 of dCtlFlags = 0), because the
header contains a pointer to the driver.
Next, the Open trap is called with the driver’s name as its parameter. If the
driver cannot be opened for some reason, DrvrRemove (Toolbox $A03E, also
undocumented) is called with the reference number in D0, and the memory allocated to
the driver is disposed with a call to DisposPtr.
If the driver has been opened correctly, the device partition map is checked for a
partition with the file system ID ‘TFS1’. The offset to this partition and its size are
remembered in the local variables area of the driver. It is this partition which will be
mounted as a Macintosh volume.
Finally, if the install routine has been called at boot time, the dNeedTime flag is
set in the DCtlFlags word; this way the driver gets called by the desk manager when the
system comes up. This will allow us to post a disk-insert event for this volume at that
time. The reason to do this is that on boot-up the system remembers only the drive
that was actually used to boot from. Thus, in case the boot was made from another drive
(such as a floppy), our SCSI drive will have to be re-mounted, even though the driver
has already been installed.
The Open routine
The Open routine will get memory for the driver’s local variables and add the
SCSI drive to the drive queue. Entry parameters to this routine are, as for all five
driver routines (Open, Control, Prime, Status, Close) a pointer to a parameter block
in A0 and to the device control entry in A1.
First, a block is reserved in the system heap for the local variables and its
pointer stored in the driver’s DCE. The drive number is initially set to 5, and the
drive queue is traversed, starting at its head (contained in the system global
DrvQHdr+QHead = $30A), to see whether the drive number is already in use. If it is,
it is incremented by 1 and the queue checked again until an unused drive number has
been found. This drive number is stored in a local variable. The drive queue element
(IM II-127) for our drive is also set up in the local variable space and some of its
fields are preset (see TN36, which also describes a routine that adds a drive to the
drive queue in a very similar way):
The qType field is set to 1. This indicates that the drive size may be greater than
32 MBytes and that one more 16-bit word is following the dQDriveSize field of the
drive queue element; this word indicates the high-order word of a 32-bit block count.
With the old definition of the drive size (qType = 0), a 16-bit block count was used,
allowing a maximum size of only 32 MBytes. We therefore have to remember that the
high- and low-order words of the drive size are reversed when we are working with
this variable later. The dqDrvSize is initialized to zero; this field will be filled by the
Install routine after the driver is open. The dQFSID field is also set to 0 to indicate a
normal HFS volume. The four bytes preceding the drive queue element contain flags
(IM II-128); byte 1 of these flags is set to 8 to indicate a non-ejectable disk. The
remaining flags are cleared.
The drive is added to the drive queue using the AddDrive trap ($A04E, not
documented in IM) with D0 containing the drive number in its high order word and the
driver reference number in its low order word, and A0 containing a pointer to the
drive queue element.
The last action of the Open routine is to set up an SCSI pseudo program for
read/write transfers in the local variable space. We use a very simple one:
SCNOINC buffer, #bytes
SCSTOP
and this is where we first deviate from the Apple driver. Apple’s example uses (and
this works with the ST225N):
@1 SCINC buffer, #bytes
SCLOOP @1, count
SCSTOP
which will transfer count blocks of #bytes each starting at address buffer. Our driver,
on the contrary, will transfer all bytes requested in one single chunk. The reason for
this will be explained later. First, keep yourself content with the fact that one method
works on the Q280 and the other on the ST225N.
The Control routine
The SCSI driver must be able to respond to several control calls, distinguished by
their csCode parameter; some of them are described in TN28. They are:
KillIo csCode=1
Verify csCode=5
Format csCode=6
Eject csCode=7
getIcon csCode=21
AccRun csCode=65
SCSICode csCode=77
KillIo is ignored (the driver operates synchronously and won’t return before the
I/O operation is completed), as are Verify and Format.
Eject must be supported in some way. First, when the ROM code cannot boot off a
volume, it will try to eject that volume; the driver code has to return with an error
here, otherwise the Macintosh would keep trying to boot from this drive. Also, a disk