Category Archives: Toolbox

First steps in QuickDraw implementation

Now that work on Memory Manager and Resource Managers has started, it’s time to focus on the next important part of Mac: The graphical user experience.

A5 World and InitGraf

As all QuickDraw operations depend on existence of QuickDraw globals, and they are addressed with the A5 register in an application’s “A5 world”, we needed to have a way to simulate this at boot time. It appears that real Macs use part of the boot-time stack space for temporary A5 world, so we decided to follow this path. At this point in boot process, we don’t have a 68K needing a stack, but we decided to simulate this anyway. At this point, system zone resides near the beginning of RAM (after low memory globals and trap tables), and the stack pointer is located somewhere just below the screen memory, a perfect place to stick QD globals and a mini A5 world into.

The InitGraf was pretty simple, and also OpenPort following it. With necessary QD globals set up, and a full-screen GrafPort available, next step was of course to fill the screen with the familiar gray pattern. It’s funny, how just filling a rectangle has a lot of dependencies to other quickdraw APIs, such as regions, and a common blitter routine which will be shared by a large number of future QuickDraw routines. For now, we just skipped the unused cases with “TODO” comments, and after a few days a hard work got this:

Unclipped, trivial pattern blitter implementation for FillRect, using patterns available in the QuickDraw globals

SDL2, the “Fake” ROM, and resource maps

Move to SDL2

Until now, the testbed was running inside a Cocoa application, but the need for a platform-agnostic implementation was already putting pressure on adding an abstraction layer between the host and the emulated environment. At this point, we took this as a possibility to switch to SDL2, changing the test environment at the same to the SDL2 library but also at the same time adding the abstraction layer on top of it.

This was accomplished by adding “Platform” layer, which abstracts the creation of sound devices, graphics context and input handling into a set of calls which the Toolbox emulator uses. In future, this layer can be extended to support other platforms, so someday we can add again Cocoa layer to handle platform-specific stuff like, like seamless desktop integration.

Emulated monochrome 512×342 video buffer at FA700/F2700 (1MB Mac memory map) being switched to alternate page using VIA emulation

At this point, we added page switching support to the VIA memory page handler, which is the “Classic Video” hardware abstraction layer to choose active video buffer. The above video demonstrates results of the page-switching test.

The “Fake” ROM

An interesting feature of the original Macintosh system is how the onboard ROM was able to contain majority of features of the operating system, reducing the unpatched code loaded from disk to a minimum. As we attempt to replicate system startup as closely as possible with our architecture, an interesting point came up: On real Mac, the boot process is kicked off by ROM doing a POST hardware check, initializing a bunch of stuff in the memory, and loading a stage 1 boot loader from the disk using the disk and file system drivers in ROM. As our goal is to actually use a virtual file system, we need to have a way to kick-start this filesystem. What we decided to do, is to actually simulate ROM resources, and add our native filesystem driver as a DRVR resource which will be loaded by the device manager, and which file manager will use to actually mount the virtualized disk.

Another benefit of the “Fake” ROM is, that it provides us with neat place to store the UPP records for our trap handlers. As trap dispatch tables contain only 32-bit pointers to the handlers, which for native calls only point to the UPP records, we could now install these dynamically into the “ROM” address space, with additional benefit that if any curious applications would snoop for the address of trap handlers, they would to be above ROMBase, like on a real Mac.

The “Fake” ROM format follows closely the one used by Apple, with the exception that we only use the ROM resources, and preallocate a certain size of image for the UPP records. Thanks to this, we were able to start implementation of first parts of Resource Manager support, starting with creation of ROZ (ROM resource zone), which is created inside system zone to hold the master pointers and resource map which are “mapped” to the ROM space using a very weird and clever hack like on real Mac

A fun fact: A Mac can have multiple types (24 vs 32 bits) of zones active at once – The ROM has resources formatted either to 24 or 32 bits, but as the ROM cannot be changed when the 32-bit mode is toggled on real Mac using the Memory control panel, the ROZ will always be in the format for which ROM resources were created for.

Debug dump of the “mapped” ROM resource map in Xcode, with Hex dump in 0xED shown below

For now, we just use the “fake” ROM to hold fonts and cursors, adding more stuff there as needed.

Trap Dispatcher, native interface, and Memory Manager

Although having graphical output in the framebuffer is neat, and for a graphical system such as Mac a required element, the most critical components of the entire Toolbox emulation are definitely the Memory Manager and Trap Dispatcher:

Trap dispatcher

In a rather clever way, Macintosh system programmers used the A-line (line 1010) handler in the 68K CPU to implement all the Toolbox API calls. How this works is that any 68K instruction, which is of the hex form Axxx (from A000 to AFFF), triggers an A-line exception in the CPU. In this situation, the CPU looks up the exception handler routine address from vector table at index #10 (32-bit address at low memory location 0x28), sets up the exception frame, and calls the exception handler.

On the Mac, the trap dispatcher is responsible for handling these A-line exceptions. The actual A-line opcodes, which triggered the trap dispatcher, are divided to OS traps and Toolbox traps based on bit 11, which are handled differently based on their type. More detailed information about how trap dispatcher works is available in Part 3A of the Mac Almanac II at http://www.mac.linux-m68k.org/devel/macalmanac.php

Native interface

As the Toolbox will be implemented in C, another challenge is interfacing the emulated 68K code with C, especially due to the requirement that a 68K application might want to add their own trap handlers, and also call existing native handlers from the 68K code! To solve this, luckily Apple already did a lot of groundwork by introducing the Mixed Mode Manager in the early 1990s, to help interfacing 68K code with the PowerPC code when Macs switched from 68K to PowerPC CPUs. In this emulator, we will attempt to implement the native calls using Mixed Mode-type UPPs, adding the C calls as a completely new “emulator” ISA type (in addition to Apple’s predefined 68K and PowerPC ISAs).

Memory Manager

With the previously added 32 kilobyte RAM block, we could start work on the Memory Manager. As stated in the goals, the first phase aims for 24-bit compatibility, as the “Classic Mac” macs are anyway limited to 24-bit addressing due to the 68000 CPU’s 24-bit bus, and a lot of software from that era is also not 32-bit clean. The groundwork for Memory Manager was done in such a way, that adding 32-bit support next to the 24-bit implementation should be relatively easy. At this point, we don’t yet have a working trap dispatcher, or a 68K CPU for that matter, but rather use direct C method calls to the required Toolbox API calls.

Output from the DumpZone call of one of the first 24-bit memory manager C prototypes

Laying out the Foundation

The very first step was to figure out how to handle the most core abstraction of the emulation; how to handle memory interfacing. The Mac has a very strict memory map layout, with 68000 vectors residing in the beginning of address space, and all Toolbox low memory globals following them mixed with trap dispatcher routine addresses.

In a pure 32-bit environment, a cheap option might be just to allocate a contiguous area of memory to use as the emulated RAM, and offset the accesses so that emulated address 0x0 (zero) would be actually first byte of this memory block, and access any memory addresses from native code by just offsetting the native pointers. However, the recent transition to 64-bit causes new kind of headache; emulated pointers would be 32-bit while native pointers would require a double the space of 64 bits, not only requiring the offset, but also conversion from different data storage size.

To solve this, a “virtual” memory mapping scheme was devised, in which the emulated RAM would be divided into a number of “blocks”, each of which would have its own handler routine, which would handle the memory access. An additional benefit of this is, that for example VIA space can be mapped to a different handler, which would immediately trigger changes to emulated VIA registers without need to poll them from the generic system RAM; additionally, emulated “fake” ROM space could be implemented to have read-only access.

With this vision, we got a very simple Cocoa-based test app running, which did not yet emulate a real Mac address space, but rather a fixed 32KB RAM and 38KB VRAM for 640×480 monochrome buffer, with a static predefined test image (actually converted to C header with GraphicConverter!) shown in the video below:

A simple 640×480 monochrome framebuffer with static pre-defined raw image being scrolled