Mac Toolbox Mysteries
I’m making slow progress on Plus Too, unraveling the mysteries of the Mac’s operation little by little. I haven’t really worked on the IWM and floppy components yet, because I’ve been consumed with chasing down other details, and trying to learn how to create FPGA timing constraints. There has been some visible progress, though: you can now move the mouse cursor around the question-mark disk screen, and the question mark flashes on and off. Woo-hoo!
In order to learn why my hardware doesn’t work as expected, I usually have to disassemble and examine the portions of the Toolbox ROM routines that access the hardware, so I can understand how it’s supposed to work. This has been a slow and painful task, one I’d compare to swimming through mud. Certain parts of the ROM routines are beginning to make sense to me now, though, and I’ve discovered several “mysteries” in the routines that are either vintage Apple bugs, or else places where my understanding is flawed.
Broken Timers
The Macintosh VIA’s Timer 2 is configured as a one-shot 16-bit countdown timer. When the countdown reaches zero, the VIA triggers an interrupt. Because the VIA runs at 783.36 kHz, the maximum possible timeout delay = 65536 / 783360, or 84 milliseconds. To implement delays longer than that, the ROM routines repeatedly schedule T2 for successive 84 millisecond delays. Here’s a portion of the relevant code from the Toolbox ROM:
P700: Tst.L D1 ; D1 is a pointer to the timer parameters BEQ L3640 Move.L D1, A1 Move.L $A(A1), D1 ; get the desired timeout delay (in milliseconds) MoveQ.L $54, D0 Cmp.L D0, D1 BLE L3639 ; if the desired delay is more than 84 ms, then just do 84 ms Move.L D0, D1 L3639: Move.L (TimeVars), A1 Move.L D1, (A1) ; save the actual delay value MulU $30C, D1 ; convert milliseconds to VIA clock cycles Move.L (VIA), A1 ROR $8, D1 Move.B D1, $1200(A1) ; store high byte of delay in T2C-H ROR $8, D1 Move.B D1, $1000(A1) ; store low byte of delay in T2C-L Move.B $-60, $1C00(A1) ; enable the timer 2 timeout interrupt Lea.L P_VIAInt5_Timer2, A0 Move.L A0, (Lvl1DT.5) ; install the interrupt handler L3640: Rts
The important thing to notice here is that the high byte of the timer is loaded before the low byte. According to the VIA 6522 datasheet, this is wrong, and will generate incorrect timeouts. The datasheet explains that T2C-L (the low byte of the T2 counter) can not be directly written. Instead, a write to that register will write to a latch called T2L-L. Then when T2C-H is written, the VIA will simultaneously load T2C-L from T2L-L, and arm the timer. By writing the timer bytes in the opposite of the expected order, this ROM routine will end up using whatever low byte was intended for the previous invocation of T2, resulting in a timing error of up to 0.33 milliseconds. Probably no one attempts to use the timer for sub-millisecond timing, so the bug isn’t noticed in practice.
Question Mark Flash Rate
The question mark at the boot screen flashes on and off. I would have guessed that the flashing rate was governed by a timer, but apparently it’s just a hard-coded delay loop.
MoveQ.L $4, D0 Swap D0 L70: SubQ $1, D0 BNE L70
Because my TG68 soft-CPU requires fewer clock cycles per instruction than a real 68000, this loop runs faster, and the question mark flashes about 2-3 times the normal rate. It makes Plus Too look a little hyperactive. This could cause different behaviors on real Mac hardware as well. I’m not sure if this same ROM routine is used on the Mac II, but if it is, I would expect the question mark to flash faster than on a Plus, due to the faster CPU.
There’s another point about this code snippet that I don’t understand: D0 is initialzed to the long value $00000004, then the lower and upper halves are swapped, which changes the value to $00040000. But the loop performs a SubQ, not a SubQ.L, so only the lower 16 bits of D0 are used in the subtraction. That means the initial value of 4 is irrelevant, and the loop will execute 2^16 or 65536 times.
Mouse Button at the Question Mark Screen
Apparently if you hold down the mouse button while the Mac is at the question mark disk screen, it does… something. I discovered this by accident, because at first my mouse adapter behaved as if the button were always pressed. Try this on a real Mac Plus! It makes some seeking noises from the floppy drive, about once every second, as long as you hold down the mouse button. From a look at the ROM routines, it seems like it tries to force the Mac to read the disk, even if it doesn’t think there’s a disk inserted. Anyone know for sure what this feature is for?
Flickering Mouse Cursor
After I got the mouse movement working, one of the first things I noticed was that the cursor flickers when it’s in the bottom third of the screen. I assumed this was a problem with my hardware design, and spent quite a while trying to discover the root cause. After a lot of digging, I found that every time the question mark flashes on and off, the ROM routines call PlotDisk to draw and erase it, whose code looks like this:
P_mPlotDisk: _HideCursor Move.L (Ticks), D0 L72: Cmp.L (Ticks), D0 BEQ L72 ; wait until Ticks has changed ; ... disk icon drawing code is here L73: _ShowCursor Rts
First it hides the cursor, then it waits until Ticks has changed, which indicates that the VBlank interrupt routine has run. This looks like an attempt to ensure the disk icon drawing code happens during VBlank to avoid flicker, but it’s flawed. It could take up to a full 1/60th of a second for Ticks to change. During the time the routine is waiting for the change, the cursor has already been hidden. If the screen area containing the mouse is repainted during this time, the user will see cursor flicker. To fix the bug, the call to _HideCursor should be moved to after the wait loop, and just before the actual disk icon drawing code.
My moment of triumph came when I tried this on a real Mac 512Ke, and observed the same cursor flickering behavior. If the cursor is moved to the bottom portion of the screen, it will flicker every time the question mark flashes on and off. I’ve managed to reproduce the exact behavior of an obscure, timing dependent bug from the real Mac hardware. Try that, software emulators!
Read 7 comments and join the conversation7 Comments so far
Leave a reply. For customer support issues, please use the Customer Support link instead of writing comments.
On second thought, I don’t see any reason why a software emulator wouldn’t also faithfully reproduce the flickering mouse cursor bug. I retract my snarky comment!
IIRC, holding the mouse button at boot time ejects the floppy disk.
The mouse button behavior is carried over to modern computers too, except it ejects the CD drive:
http://support.apple.com/kb/TS2097
I think the disassembler you’re using may be making errors. The Question Mark Flash Rate disassembly should read ..
MOVEQ #4,d0
SWAP d0
loop
SUBQ.l #1,d0
BNE.s loop
.. which does make sense.
The mix-up with the timer register order makes me think this bit of code was written by someone used to working with Motorola peripherals, for example the 6840 programmable timer module. The concept is the same, but it’s the MSB that’s latched so the proper order there is MSB then LSB. This is because the 6800 and descendants were big endian and wrote 16-bit data in MSB then LSB order, which is opposite from the 6502 and its family of peripheral ICs such as the 6522.
> the disassembler you’re using may be making errors.
Sorry, it is. Not enough people are paying enough attention to FDisasm (especially me) for it to be relied upon.
But this particular bug was already reported and fixed. Unfortunately I neglected to update the version of the application in the images of formatting information for each ROM. Instead I now have just removed the copy of the application from these images.
If you notice any other bugs, please let me know.
I remember this bug when using an old Mac… vaguely since that would have been 20 years ago. Flicker flicker.. and it wasn’t just the Macs. If you get the CPU really busy in the Megaman games for the NES, you can see the sprites start to flicker the same way. This is a VERY old bug on any system with a raster display and interrupts. Don’t use buffering, do update the screen between v-blank cycles, and fall behind on an interrupt and you get ‘snow’ on some old programs. DOS was fun with programs written for a different video card, hehe.