[comp.os.minix] Minix port to the Mac

CRAIG%UREGINA1.BITNET@cunyvm.cuny.edu (Craig Knelsen) (01/25/88)

        In the fall of 1987, I took an operating systems class whose project
objective was to port Minix to the Macintosh. After initially beginning this
operation alone, I merged my progress with two fellow classmates since we
foresaw that it would was not feasible to expect a full port by one person
within one semester (we had about 2 1/2 months after beginning).

Port Environment: Mac SE with either 20Mb HD or two internal floppy drives
                  Mac +, internal and external floppy drives
                  Aztec C 1.06I  (Manx Software)

WARNING: The following discussion assumes familiarity with Mac programming
         and 68000 architecture and is somewhat technical in nature.

A summary of how we were able to get a prompt follows:

(1) I began by converting the sendrec.s file to 68000. A trap 0 instruction
replaced the PC INT and the trap 0 address (0x80) was set to call s_call().
A simple C program was used to test/debug the routines.

(2) Conversion of lock(), unlock() and restore() in klib88.s. The Aztec
debugger 'db' was quite useful in making sure that these routines worked to
perfection.

(3) In order for all keyboard input to be funneled to the keyboard() routine
in the tty task, PostEvent() in the Mac ROM needed to be replaced.
Fortunately, the Mac's trap dispatch table is copied to RAM on startup which
allows patches to current Mac ROM routines be installed by new versions of
the system file or application programs (Minix in this case) to install
their own versions. By using GetTrapAddress() and SetTrapAddress(), a new
routine called post_han() in mpx68k.s was installed to catch all postevent
traps. This routine pushes the event type in a0 and the event message in d0
onto the stack and calls a C function my_post() which in turn calls
keyboard() if a key down/up or auto key event occurred, effectively disables
mouse clicks by ignoring mouse down/up events and panics for any other event
type (handling windows, network events, etc was not a goal of this
project).

(4) The save() and restart() routines were converted. A process save is
accomplished as follows:
        - push a5 and a6 onto the stack
        - restore the kernel's a5 and set the Mac global CurrentA5 (0x904)
          to that of the kernel
        - make a6 point to current process table entry using proc_ptr
        - save registers d0-d7, a0-a4 using a move multiple instruction with
          a6 as the base
        - move interrupted process's a5 and a6 from the stack into the
          process table
        - save the sr and program counter, which begin four bytes away (the
          top of the stack contains the return address) from the stack top,
          for the interrupted process into the process table
        - save the process's stack pointer (sp) at time of interrupt
        - pop off the return address into a0 because we will be changing sp
        - move the sp into a1 so that save()'s caller can access any
          arguments that were pushed onto the stack by the caller of save's
          caller.
        - make the stack the kernel stack (k_stack[]) and set the stack
          pointer limit (not used at present).
        - return to caller using the address in a0
The restart() procedure just needs to restore the registers from the process
table indexed by cur_proc if cur_proc is not set to IDLE. The sr and pc are
pushed onto the stack so that an rte (return from exception) can be used to
restore the sr at time of interrupt.

(5) Tasks were modified to eliminate any IBM PC dependent code (eg. clicks,
virtual addressing). The floppy, hard disk and printer tasks did an
immediate receive -- the real floppy driver was added later when FS was
ready to be tested. Any remaining necessary routines from mpx88.s and
klib88.s were converted to their 68000 equivalents.

(6) The 6522 Versatile Interface Adapter (VIA) proved to be a problem in the
development of the kernel. You must replace the level 1 interrupt vector
address (0x64) with your own routine so that a proper process save can be
made. Basically, ours did the same things as save(), modified the stack so
that control passed to our restart() when the Mac ROM VIA IH did an rte and
then jumped to the Mac ROM VIA IH to continue servicing the interrupt.

(7) The vertical retrace (VR) IH was replaced. The VR IH performs such tasks
as checking that the stack and heap have not collided (aka 'stack sniffer')
or mouse status changes and then executes the tasks in the vertical retrace
queue (eg. whether a disk has been inserted). Since our loader places the
heap above the stack, using the ROM VR IH would be a major problem (there
are other reasons). Originally, clock_int() was installed into the vertical
retrace queue but was later changed so that clock_int() was called directly
as the result of a vertical retrace interrupt.

(8) During kernel development, debugging output was needed either because
the tty task was not reliable enough or there were problems with initializing
Minix before the tty task was initialized. Three routines were written
(mac_init() opened a grafport and prepared it for printing, mac_quit()
cleaned it up, while mac_putc() wrote a character to the grafport). printk()
was modified to call mac_putc() by inserting:
        #define mac_putc(c)    putc(c)
Once the tty task was working properly, the tty task was called for all
output, however these routines were put to good use in the standalone
bootstrap program (mboot).

(9) Once the kernel seemed to be initializing all tasks properly, accepting
keyboard input and echoing it, the time had come to try loading our kernel
with our loader instead of using Aztec. Our loader was written as a
subroutine (we planned on having FS call this loader for exec()), so we
needed a standalone bootstrap program (called 'mboot') which could be executed
from the Finder or Aztec shell. Our loader places process information into a
parameter block so mboot was able to pass a structure address on the stack
which pointed to process info for the kernel (and later on, MM, FS and INIT).
The Minix entry point sets argc to 1 and argv[0] to this structure
address and then calls main().

(10) Once the kernel was successfully loaded and running from our loader, it
was now time to have two programs in memory, namely the kernel and MM. MM
was modified to use the Mac Memory Manager by removing alloc.c and replacing
all calls made to functions in that file to Mac MM routines. No significant
problems were encountered in getting MM initialized (a printk() was used to
ensure that MM was being entered).

(11) Two hacks were inserted into the kernel's main() to get MM, FS and INIT
initialized properly:
     - The first entry in the jump table for these is at 34 bytes offset
       from A5 for the process. Thus, when MM, FS and INIT are being set up
       in the process table, their starting pc is set to their A5 + 34.
     - In order for FS to tell MM where INIT is (so MM can set INIT's memory
       map), it needs the process information passed to the kernel by mboot.
       Thus, when FS is being set up in the process table, this structure
       address is pushed onto the top of FS's stack. This enables main() in
       FS to pick it up as an argument and pass it to MM in the BRK2
       message.

(12) MKFS was modified to use Mac ROM device driver calls (PBRead(),
PBWrite()). A root file system was then created using a prototype file.

(13) We were ready to write INIT but we needed the Minix C library for
linking. All the needed routines compiled without any problems except those
dealing with standard I/O. These routines cause a 257 code resource to be
generated when it set up the buffer address at compile time. This problem
was resolved by creating a routine called fileinit() which set these
addresses at run-time (and thus eliminating the 257) although all routines
wanting to do standard I/O must call fileinit() first.

(14) Since fork() and exec() are not yet supported by MM and FS, INIT was
hacked to act as both login and shell. INIT calls fileinit(), opens
/dev/tty0 for standard I/O prints "Welcome to Minix" and accepts a login
name and password (echo is disabled). If name and password are incorrect, it
prints "Login incorrect" and loops. Otherwise, it prints a prompt and
accepts input although it is ignored.

Problems encountered
--------------------
A. The Aztec C compiler will generate a 257 code resource if it needs to
adjust values in the initialized global data area (given by code resource
256) at load time. For example, a 257 will be generated when initializing an
array of function pointers (eg. *task[]()) at compile time. The Aztec manual
contained no information on how to interpret this resource (we were able
to get a general idea of how it worked by stepping through the C runtime
startoff by Aztec but time was a precious commodity) so we decided
that our loader would balk if it finds one. To accomodate this, all
global/static variable pointers were changed to be initialized at run-time
instead of at compile time (this meant converting the initialization in
table.c for the kernel, MM and FS to run-time routines).

B. Pointers are 32 bits on the 68000, but printk() was only using 16
when trying to print a string using %s.

C. In our first attempt to get output from INIT via FS, Minix would hang/crash.
The problem was that we had constructed /dev/tty0 wrong and accidentally gave
it a minor number of 1 instead of 0. Since init_tty() in tty.c had only been
set up for a maximum minor number of 0 (NR_TTYS = 1), do_write() in tty.c
jumped to physical address 0. A check was added to main() in tty.c to ensure
that a passed minor number did not exceed NR_TTYS - 1.

Other Information
-----------------
A. Our loader will place a process into its own heap zone if it has been
compiled with the proper define set. This is so that when the process
allocates memory, it will be restricted to any remaining heap space in its
assigned heap zone. When the process dies, MM simply needs to free that
heap zone and any memory allocated by the process is freed as well. The
kernel has some support for heap zones but was not fully implemented due
to local environmental problems.

B. The floppy driver uses asynchronous device driver calls (PBRead() and
PBWrite()) to read/write from a Minix floppy. The I/O completion routine
calls interrupt() to emulate a hardware disk interrupt.

C. You will have a real problem trying to implement virtual addressing with
the 68000 (those with Mac II's and their 68020's are another story -- but
then A/UX may be a suitable alternative). As a result, our version of Minix
uses only physical addressing. A solution the class had agreed on for
implementing fork() and exec() was to combine the two into a single call
-- forkexec(). In this way, the new child process would not be able to affect
any variables in the parent. There are of course limitations that occur as a
result of this proposed solution. However, time ran out before anyone was
able to proceed this far.

D. The tty driver uses Quickdraw calls.

E. The 68000 is always in supervisor state.

F. The exception vectors (eg. address error, bus error, zero divide) were
set to call the kernel. Further work would have resulted in these generating
a signal to the offending process.
========================================================================

        This was only one implementation of three seriously attempted ports
although ours got the furthest (however, to be fair, the other two were
single person attempts). I am in no way implying that the above described
steps are the most effective way of porting Minix to the Mac.

DISCLAIMER:
  This posting does not imply that I will respond to any requests for source
  or answer any questions that anyone may have about the port. However, I
  am willing to receive any such requests and respond if a lack of time
  and/or other commitments do not conflict.


Craig Knelsen
Dept. of Computer Science
University of Regina
Regina, Sask
Canada  S4S 0A2

UUCP: {ihnp4 | utcsri | alberta} !sask!regina!craig
NetNorth (BITNET): craig@uregina2   <-- preferred
                   craig@uregina1