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
|totally work-in-progress closed.
E.g. every now and then my theories about what a certain code does changes, I learn new things and all the sudden whole blocks of code make sense… so this post will change/grow, too.
Bolle did a lot of hardware research and in the end it became clear that the INIT/Driver has nothing to do with the non-function of the Carrera in an SE/30. After Bolle modified his adapter, the Carrera 040 is happily running in my ’30 now.
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 #$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
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?
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
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
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
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_220.127.116.11,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
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_18.104.22.168 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.