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