Some Macs have a diagnostic mode that was apparently used at the factory, and probably by the TechStep diagnostic tool.
The Diagnostic Mode uses the Modem serial port at 9600 baud, 8 data bits, no parity, and 2 stop bits. Depending on the machine and how the diagnostic mode was entered, the serial port may be outputting something like *APPLE*876543210000*1*. The *1* there is a machine identifier. Otherwise, the serial port may not be outputting anything, but will still accept and echo commands.
Below is a table of known identifiers:
|1||Mac II(256kb ROM family)|
|O||Indicates the IIvx family, but the TechStep will dump the byte in memory located at $5f ff ff fc to differentiate between specific models (IIvi, P600)|
The machine will continuously output this string, even as you enter more commands.
At this point, new commands may be entered. There are 2 classes of commands, those prefixed with a * and those prefixed with a !.
Outputs the version of the Diagnostics and System ROM. For the IIsi ROM, it outputs:
STM Version 2.0, Scott Smyers
ROM Version 7C12F1
|*S||Service Mode||This makes the ROM stop continuously outputting the *APPLE* message. Commands can still be entered.|
|*A||ASCII Mode||Some commands take arguments. This tells the ROM that those arguments will be entered in ASCII encoded hexadecimal representation. Leading 0's are not automatically filled, so if you have a command that takes a 2 byte argument, and you want to enter '4', you would enter 0004.|
|*H||Hex Mode||This is really binary mode. It specifies that when arguments are provided to commands, they will be binary. This is the default.|
This will return the current value contained in the ROM's diagnostic status register (D6), along with the current flag settings (bottom word of D7). Output will be in the form of "000000000000", 12 hexadecimal digits. The first 8 digits are the 32bits of the status, and the last 4 digits are the major error code.
The last 16bits (4 hex digits) mean:
|*T||Run Critical Test|
This runs a preprogramed test. It takes 6 bytes of arguments. The first two bytes are the test number to run. The second two bytes indicate the number of times the test should be performed. The third two bytes are options to the test.
|*N||Run Non-critical Test|
Runs non-critical test. It gets run like: *N008400010000
In this case, the 84 represents the test to run in hex, and the 1 represents the number of times the test is to be performed. The test will exit on the first failure. The test returns a code in the form of: 020004030000
Typically test return codes have the top byte representing the phase of the test that had the error, and some portion of the remainder of the value representing expected and actual values for the test. Exact error results vary from test to test. The return value 0xFFFFFFFF0000 usually represents a skipped test. Tests can be skipping for a variety of reasons, including the hardware not being present on the current system.
This is a list of known tests:
|*L||LoadAddr||Loads address to be used by subequent commands (like GetData)|
The number of bytes to use for the subsequent command
Tells the ROM to get the specified data (this is a put instead of a get from the perspective of the TechStep). This command takes the data as an argument, with the amount of data having previously been specified by *B, and the ROM puts the data into the address previously specified by *L.
The checksum of the data received is placed in register D6, which is returned by *R
|*M||MemDump||Dumps the memory located at the address previously set by *L, for the number of bytes previously set by *B|
|*C||CheckSum||Checksums the memory located at the address previously set by *L, for the number of bytes previously set by *B. The result is stored in D6, which is retrievable with *R|
|*G||Execute||Takes a 32bit argument of the address to jump to. The code is executed via a BSR6 macro|
|*0||LoadA0||Takes a 32bit argument, which is first loaded into register D6, then A0|
|*1||LoadA1||Takes a 32bit argument, which is first loaded into register D6, then A1|
|*2||SetCache||Takes a 32bit argument, which is loaded into cache control register CACR|
|*3||MMUOff||Disables the MMU|
|*4||ClearResult||Clears the result of any previously performed test|
|*5||StartBootMsg||schedules the *APPLE* boot message|
|*6||CPUReset||Does what it says|
As an example, to run the ROM test, I would do the following. Since what gets echoed back is not necessarily what gets entered, I have divided this into two columns.
STM Version 2.0, Scott Smyers
ROM Version 7C12F1
Entering Diagnostic Mode
Diagnostic mode should be entered whenever the machine has a Sad Mac at boot time. However, there are a couple known ways to force this to occur. The TechStep uses the ADB method on machines that support it, and the SCSI method on all others (and as a fallback in case of ADB failure).
To enter the diagnostic mode, line PA0 of VIA1 (that's data register A, pin 0) needs to be grounded. This pin is exposed on the edge connector on the logic board on at least the IIx, IIci, and IIsi. Here is a picture of VIA1 on the IIx, with the PA0 pin labeled. Find this IC on your motherboard, and check for continuity between this pin and pads on the edge connector to find which pin on your logic board is the appropriate one:
When the system boots with this pin pulled to ground, the system will chime, but not display any video.
On machines with the Egret ADB transceiver (>= IIsi?), diagnostic mode can be entered via ADB. Unfortunately, this can't be done with a normal keyboard, and requires a special device to send the proper code. Here is a Saleae Logic capture of the sequence: TechStep ADB TM Enter.logicdata
To summarize, it follows the following steps:
- Start with the machine off
- Power on the machine via ADB by bringing the POWER.ON line low
- Once +5V comes on, release the POWER.ON line, allowing it to be pulled high by the host. This usually takes ~200ms
- Give the machine some time to boot. ~800ms
- Lower the POWER.ON line again
- Wait for the host to issue a TALK to address 2 (keyboard), register 2 (modifier keys)
- Respond with 0xE6
- For the next 10 TALK addr 2 reg 2 requests, respond with 0xEE.
- The computer should give bad chimes and be in diagnostic mode.
The TechStep presents a hard disk to the host at SCSI ID 6. The "disk" consists of 5 blocks of 512 bytes, with block 0 consisting of the driver descriptor record, blocks 1-3 consisting of an Apple Partition Map that has a fake Apple_HFS volume, and an Apple_Driver partition. The Apple_Driver partition and block 0 point to block 4 for the driver. The driver consists of the following code as captured while booting a Performa 600 into diagnostic mode from a TechStep via SCSI:
move.a.w #$4000,a7 ; putting a new value in the stack pointer
movel #$8046fc, (a7+) ; putting something on the stack
pmove (a6), tc ; functionally disables translation, forcing 32bit mode, for the following instruction
movea.l #$50f26000, a7 ; RBV VIA2 address for vBuf2
moveb #$47, (a7+)
movea.w #9984, a7
movew #8193, d0
movec d0, cacr ; instruction cache and write allocate only
movel #504462720, d7 ; d7 is used in diagnostic mode to keep some flags, so presumably this is just initializing things
movel $7c, d0 ; The following two lines setup the NMI exception handler for trap 15
movel d0, $bc ;
trap #15 ; equivalent of NMI?
ori.b #0, d0 ; probably bad. this is just 0x0000, so kinda not trusting anything after that trap
movea.w #$4000, a7
move.l #8388608, (a7+)
Essentially, this is a fancy way of hitting the NMI button. This image contains the 5 blocks the TechStep sends: techstep_disk.dsk This is for a IIvx, IIvi, or Performa 600.
The pseudo-disk the TechStep presents seems to be different depending on the machine it is configured to be talking to. Here is one for a IIci: techstep_iici_disk.dsk
Hitting the Programmer's Interrupt switch shortly after powering the machine on, such as when the cursor is displayed but before booting is attempted, will cause the machine to have the "chimes of death" and display a Sad Mac.
On later machines that don't have a programmer's switch, pressing cmd-power can have the same effect.
Sample TechStep Transaction
Below are some sample transactions observed from the TechStep. In these tables, even if the protocol is in binary mode (*H was specified), I am writing the hexadecimal notation as if it were in ASCII (*A) mode. This means it will always have two characters per byte, I'm not omitting leading 0's.
When arguments are specified to commands, the arguments are loaded into register D1. If more than 4 bytes are specified, the first four bytes are usually read in, then transferred to another register (D0 in the case of *T), and then the remaining bytes are read into D1. So for instance, if *200000000 is specified to load the value into register A1, the value is also loaded into D1 as a side effect, which can be used by subsequent commands that don't explicitly take arguments, like *3.
The TechStep does not send CR or LF, although the ROM will ignore them if sent. The TechStep will, by default, send them when it echos commands back.
This conversation seems to precede any other actual tests being performed, just to verify communication is fine:
|*A||Enter ASCII Mode|
|*H||Enter binary mode|
|*4||Clears any existing status value|
|*000000000||The command is 0, which means to load the following value into A0. Since we're in binary mode (*H above), the value is four bytes of zeros.|
Command is *T, which means to execute the specified test. The argument is 2 bytes of test number, 2 bytes of number of times to run the test, and 2 bytes of options. Again, since we're in binary mode, the argument is specified as binary of test #1, 1 iteration, and an option of 1.
Test #1 tests the data bus.
|*R||Requests the return value of the test|
|000000000000*R||The computer sends the return value before echoing the command. We're in binary mode, so the return code is binary, consisting of 6 bytes of 0's (if successful).|
TechStep Requests CPUID on IIsi
|*L00044000||Says to load the address 0x00044000|
|*B0008||Specifies a byte count of 8|
Puts the specified data (8 bytes as specified by the earlier *B) at the address specified by the previous *L.
Basically put 0x000E0000 on the stack and return
|*G00044000||Execute via BSR6 starting at the address specified|
|*G||Echos after execution is complete|
|000001DC0000*R||000001DC is the contents of D6, which is the checksum from *D earlier. The trailing 0000 is the lower word of D7.|
|*200000000||Sets CACR to 0|
|*000000000||Loads A0 with 0's|
|*T000100010001||Run databus test once|
|*004000000||Loads A0 with 0x04000000|
STM Version 2.0, Scott Smyers
ROM Version 7C12F1
|*5||Start continuously outputting the boot message (*APPLE*000000000000*C*)|
|*S||Stop continuously outputting the boot message.|