The disassembled code of the Micromac Carrera 040 control panel is quite big: 6000+ lines of 68030/40 assembly…
While these posts might be entertaining and giving you an insight into classic MacOS driver code, they are also meant as a notebook to myself to get into the source quickly – especially after some weeks or months of distraction 😉
That said, I will not discuss each and every line of code. There are many parts which aren’t important (for now) or just not reached yet.
Still, it will take several parts/chapters to cover everything I worked on.
The complete code is available over here on GitHub and will updated every time I’m working on it.
Whenever I’m mentioning addresses I’m referring to this code on GitHub. NB: I will never use line-numbers as these might change during editing the source.
Also, when you’ll see a light-bulb 💡 somewhere, this is where I’m not sure and happy about enlightenment or comments from you 😉
|This article is
But still, this series of posts is definitely worth reading, especially if you’re into reverse engineering 68k assembly code.
What’s the main job of this code? From a 30000ft perspective the simple answer is “switching the Carrera040 on and off”, i.e. toggling between the hosts slower on-board 68030 and the insanely fast 68040 on the C040. At boot-time… as well as during the system is running (by user interaction).
Sounds pretty simple, huh? Lowering our flight altitude to 3000ft more things come into play:
Identify the hosting Macintosh. As mentioned in the previous chapter, the C040 was able to run in a Mac II, IIx, IIcx, IIvx, IIvi, IIvm, IIsi, IIci, LC and LCII… all of them different in many places. These differences have to be handled…
Down at 30ft we have to admit that there are differences between a 68030 and his younger brother 68040, mainly concerning caches, FPU and the MMU.
Finally hitting the ground, it’s becoming clear that it is everything but trivial to halt a running processor, save its complete context and start another (slightly different) processor with that. And back again…
Some given things before we start:
- We will concentrate on the IIx “branch” as this machine is closest to the SE/30 like not-32bit-clean, memory-map, the GLUE chip, two real VIAs with the same register layout etc.
- I learned from the code that the C040 is memory-mapped at 0x53000000 in some of the supported models, especially the IIx and IIci. This means 32bit addressing is a must (-> need “mode32” INIT or clean ROM)
- I tried to comment as much as possible/understood inline (i.e in the code) – a good bit of 68k machine language knowledge is still required 😉
- If something needs more explanation, I’ll try to provide this before the code quote or afterwards.
So this is the main routine (at 0x21FC):
main MOVEM.L A4-A6,-(A7) MOVE.L D0,D7 MOVE.L #$31E,D0 ; need 798 bytes _NewPtr ,CL_SY ; allocate requested amount of memory (D0) in system ; heap (returned in A0) and initialize to zeroes TST D0 ; success? BNE.S lae_6 ; nope, exit. LEA data2,A1 ; else MOVE.L A0,(A1) ; Init A5 world and save into data2 MOVEA.L A0,A5 MOVE #$A89F,D0 ; UnimplTrap _GetTrapAddress ; (D0/trapNum:Word):A0\ProcPtr MOVE.L A0,$29C(A5) ; save the trap addr into 2 places MOVE.L A0,$2A0(A5) ; in the A5 world BSR sysDetect ; Jump to Machine detection routine BNE.S lae_6 ; success? MOVE.L D7,D0 JSR 4(A6) ; We jump to the subroutine set in the detection routine ; for the second time, this time offset 4... ; i.e. we skip the 1st 'BRA' there BNE.S lae_6 ; success? BSR instFPSP ; Install Motos FPSP BNE.S lae_6 ; success? JSR 8(A6) ; That's the 3rd call in the handler call cascade (needs hack for MacsBug!) BNE.S lae_6 ; success? BSR proc32 ; works (get some RSC strings) BSR proc43 ; install traps BNE.S lae_6 ; success? JSR 12(A6) ; That's the 4th call in the handler call cascade BNE.S lae_6 ; success? BSR proc41 ; works (atalk?) BSR proc42 ; VIA stuff and such - BOOM BSR proc29 MOVEM.L (A7)+,A4-A6 MOVEQ #0,D0
As you can see, there are 10 calls to subroutines- currently it crashes inside the 8th subroutine, currently called proc42… But let’s check these subroutines one by one.
This is the subroutine I had to “patch” to initially make the driver work with an SE/30. It starts at 0x2022 and does these things:
- Check if the ‘Gestalt‘ trap is available at all (very good style!) else throw an error
- If it is, read the machines Gestalt code into
D0, throw an error if zero
- Decide which ‘handler’ to choose given the Gestalt code.
Based on their Gestalt codes there are four groups of Macs defined in the following lines (0x204C – 0x20AC):
- Mac II/IIx/IIcx — “dirty Macs”, not 32bit clean, no PDS
- “Expansion I/O Space” from 0x51000000 to 0x5FFFFFFF
- the C040 installs with an adapter right into the CPU socket in the II/IIx/IIcx
- SE/30 is also “dirty”, need mode32 or IIsi ROM in slot
- these machines also use the GLUE chip to emulate the VIA2 like the SE/30
- Mac IIvx, IIvi, IIvm — special kind of PDS slot
- there’s no mentioning of support on the MicroMac page
- Mac IIsi, IIci
- Kind of interesting because the si has the same PDS slot like the SE/30
- Uses the RBV (Ram Based Video) controller which emulates the VIA2
- Therefore totally different memory layout (VRAM at 0x00000000 mapped by the MMU etc.)
- Mac LC, LCII, Color Classic
- These share the same LC-PDS slot
If your Mac is one of those (or patched at 0x2058), you’ll branch into
sys_check: (0x20BA) which will make sure you run at least System 6.0.5, have virtual memory switched off and jumps into the selected handler code (address saved in
A6) at 0x20EA for the first time.
Here’s the code of what’s discussed above:
2022: sysDetect: MOVE.L #$A0AD,D0 ; Gestalt 2028: _GetTrapAddress newOS ; (D0/trapNum:Word):A0\ProcPtr 202A: MOVE.L A0,D2 202C: MOVE.L #$A89F,D0 ; UnimplTrap 2032: _GetTrapAddress newTool; (D0/trapNum:Word):A0\ProcPtr 2034: CMP.L A0,D2 2036: BEQ OS_bad 203A: MOVE.L #'mach',D0 2040: _Gestalt ; (A0/selector:OSType):D0\OSErr 2042: BNE bad_conf ; If we can't read it, fire general Error Msg 2046: MOVE.L A0,D0 2048: MOVE.L D0,2(A5) ; Check for several Mac models which are grouped into 3, each having its own handler routine. ; 1) Mac II/IIx/IIcx ; 2) IIvx, IIvi, IIvm ; 3) IIsi, IIci ; 4) LC, LCII, Color Classic 204C: LEA MacII_handler,A6 ; -- The dirty gang 2050: CMPI.L #6,D0 ; MacII 2056: BEQ.S sys_check 2058: CMPI.L #7,D0 ; MacIIx - we replace this by the SE/30 #9 205E: BEQ.S sys_check 2060: CMPI.L #8,D0 ; IIcx 2066: BEQ.S sys_check 2068: LEA V_handler,A6 ; -- The "V" Macs. 206C: CMPI.L #48,D0 ; IIvx 2072: BEQ.S sys_check 2074: CMPI.L #44,D0 ; IIvi 207A: BEQ.S sys_check 207C: CMPI.L #45,D0 ; IIvm 2082: BEQ.S sys_check 2084: LEA IIci_handler,A6 ; -- IIci and IIsi ; BOTH share the same "Expansion I/O Space" (0x5300 0000) 2088: CMPI.L #11,D0 ; IIci 208E: BEQ.S sys_check 2090: CMPI.L #18,D0 ; IIsi 2096: BEQ.S sys_check 2098: LEA LC_handler,A6 ; -- The LC-PDS family 209C: CMPI.L #19,D0 ; LC 20A2: BEQ.S sys_check 20A4: CMPI.L #37,D0 ; LCII 20AA: BEQ.S sys_check 20AC: CMPI.L #49,D0 ; Color Classic 20B2: BEQ.S sys_check ; Any other Model/Gestalt will bring up an error alert-box 20B4: MOVE #$1B5B,D0 ; "Carrera040 does not support this Macintosh model." 20B8: BRA.S RET_err ; -> "TST D0 & RTS" ; We found a supported model, so keep on going checking for the OS version... 20BA: sys_check: MOVE.L #'sysv',D0 ; Check OS version 20C0: _Gestalt ; (A0/selector:OSType):D0\OSErr 20C2: BNE.S bad_conf ; If we can't read it, fire general Error Msg 20C4: MOVE.L A0,D0 20C6: CMPI #$605,D0 ; System 6.0.5 20CA: BGE.S OS_ok ; or greater 20CC: OS_bad: MOVE #$1B5C,D0 ; "Carrera040 does not work with this version of the operating system." 20D0: BRA.S RET_err ; 20D2: OS_ok: MOVE.L #'vm ',D0 ; Check for enabled Virtual Memory 20D8: _Gestalt ; (A0/selector:OSType):D0\OSErr 20DA: BNE.S bad_conf ; If we can't read it, fire general Error Msg 20DC: MOVE.L A0,D0 20DE: BTST #0,D0 20E2: BEQ.S VM_ok 20E4: MOVE #$1B5D,D0 ; "Carrera040 does not work with Virtual Memory turned on. ; Please turn off Virtual Memory in the Memory control panel and restart your Mac." 20E8: BRA.S RET_err 20EA: VM_ok: JSR (A6) ; This is the actual HANDLER CALL, been set in $204C-$2098 20EC: BNE.S RET_err 20EE: MOVE.B 34(A5),D0 ; 34(A5) seems to contanin the Jumper settings at the lowest 3 bits and only three of them are valid: 20F2: CMPI.B #7,D0 ; 7 -> 111 20F6: BEQ.S RET_ok 20F8: CMPI.B #6,D0 ; 6 -> 110 20FC: BEQ.S RET_ok 20FE: CMPI.B #5,D0 ; and 5 -> 101 2102: BEQ.S RET_ok 2104: MOVE #$1B5E,D0 ; "Carrera040 does not recognize the jumper settings on the Speedster card. ; Please check the settings against the manual. 2108: BRA.S RET_err 210A: RET_ok: MOVEQ #0,D0 ; clear D0 (no errors) 210C:RET_err: TST D0 ; Set the Z-Flag (D0 contains Err-Code) and 210E: RTS ; return from Subroutine 2110:bad_conf:MOVE #$1B5A,D0 ; "Carrera040 does not support your system configuration." 2114: BRA RET_err
Yes, there’s also stuff after the call to the handler, but let’s check that handler first.
As said in the beginning, I chose to take the “IIx route”. The
MacII_handler code is actually just another vector jump-table which will later be used with offsets:
414E: MacII_handler: BRA MacII_1st ; From II, IIx & IIcx 4152: BRA MacII_2nd 4156: BRA MacII_3rd 415A: BRA MacII_4th
Let’s have a look into the first call
3D14: MacII_1st MOVEM.L D1-D3/A0-A2/A6,-(A7) ; 1st call from MacII handler 3D18: PUSH.L 8 3D1C: LEA data105,A0 3D20: MOVE.L A0,8 ; Is that the Bus Error Handler at 0x00000008? 3D24: MOVE #$1B5F,D3 ; 7007 3D28: MOVEQ #1,D0 3D2A: _SwapMMUMode 3D2C: PUSH.B D0 3D2E: MOVEA.L A7,A6 3D30: BSR read_5300k2 3D34: MOVEQ #0,D3 3D36: data105 MOVEA.L A6,A7 3D38: POP.B D0 3D3A: _SwapMMUMode 3D3C: POP.L 8 3D40: MOVEQ #0,D0 ; ? 3D42: MOVE D3,D0 ; overwriting? 3D44: BNE.S lae_153 3D46: MOVEM.L D1-D2/A0-A2,-(A7) 3D4A: LEA 53_cmd_0,A0 3D4E: MOVE.L A0,6(A5) 3D52: LEA 53_cmd_1x,A0 3D56: MOVE.L A0,10(A5) 3D5A: LEA read_5300k2,A0 3D5E: MOVE.L A0,14(A5) 3D62: LEA 53_cmd_5.3,A0 3D66: MOVE.L A0,18(A5) 3D6A: LEA 53_cmd_5.1,A0 3D6E: MOVE.L A0,26(A5) 3D72: LEA 53_cmd_18.104.22.168,A0 3D76: MOVE.L A0,22(A5) 3D7A: MOVEM.L (A7)+,D1-D2/A0-A2 3D7E: BSR read_5300k2 3D82: ANDI.B #7,D0 3D86: MOVE.B D0,34(A5) 3D8A: MOVEQ #0,D0 3D8C: lae_153: MOVEM.L (A7)+,D1-D3/A0-A2/A6 3D90: TST D0 3D92: RTS
As you can see, even in such simple and short subroutines are some things I just don’t get? For example why is the effective address of data105 written to 0x8? Is that replacing the Error Handler in the VBR?
Anyhow, I think I got the overall meaning of the rest of it. What happens is this:
After switching into 32bit mode (
_SwapMMUMode) it reads a longword from 0x53000000. As initially mentioned, the C040 is mapped to this address. There are 2 identical functions to read from there, that’s why this one here called
It looks like reading is sufficient because the result (returned in
D7) is immediately overwritten by a
pop. Also that BNE after two moves is beyond me (0x3D40)… OTOH the rest of the code is pretty clear: It’s ‘populating’ the A5-world with subroutines I’d call 53-commands. These commands write a specific byte sequence to 0x53000000, obviously communicating with the C040. For better understanding I’ve named them e.g.
53_cmd_22.214.171.124 meaning writing 5, then 3, then 5 and finally 1 to this address.
At the end, 0x5300k is read again, this time the result is masked to the last bit and written to
34(A5) – this represents the C040 jumper-settings by the way. Return from Subroutine…
sys_check: this jumper-setting will be checked immediately for three valid settings: 111, 110 or 101 representing the supported CPU types (68040,68LC040,68EC040). If the setting is ok we’re done with
sysDetect:and return to
Located at 0x3D94 this is kind of a ’50/50 subroutine’. One half is totally obvious (check RAM, ROM and addressing mode) and the other half is all greek to me… e.g. what is all that PUSHing about? There’s not a single POP inside this routine (or subroutines call from within). Here’s a wild guess of mine:
It looks like 1 to 4 ‘RAM range triplets’ being pushed onto the stack and after that
gestaltPhysicalRAMSize (#’ram ‘) is called, for example:
3D9A: CLR.L -(A7) ; faster 'PUSH.L #00000000' 3D9C: CLR.L -(A7) ; PUSH.L #00000000 3D9E: CLR.L -(A7) ; PUSH.L #00000000 3DA0: PUSH.L #$100000 3DA6: PUSH.L #$50F00041 3DAC: PUSH.L #$50F00000 3DB2: PUSH.L #$2000 3DB8: PUSH.L #$53000041 3DBE: PUSH.L #$53000000 3DC4: PUSH.L #$2000 3DCA: PUSH.L #1 3DD0: PUSH.L #$53002000 3DD6: MOVE.L #'ram ',D0 ; Returns the number of bytes of the physical RAM 3DDC: _Gestalt ; (A0/selector:OSType):D0\OSErr
gestaltPhysicalRAMSize call does not take parameters and simply returns the amount of available RAM.
The good thing is, this sub-routine works flawlessly on the SE/30 and we can move on…
instFPSP is the next call in line. I’m not going to discuss this code in detail because it actually doesn’t do much. Still there are many inline comments in this routine if you like to know more. Here’s the background:
The FPU in the 68040 was made incapable of IEEE transcendental functions, which had been supported by both the 68881 and 68882 and were used by the popular fractal generating software of the time and little else. The Motorola floating point support package (FPSP) emulated these instructions in software under interrupt. As this was an exception handler, heavy use of the transcendental functions caused severe performance penalties.
TLDR; Check for FPU(type) and load the FPSP code from the resource-fork into RAM. Done. Return to
Phew, that’s it for now. In the next post/chapter we’ll touch the 3rd handler, which was really hard to decipher but interesting stuff to learn, too.