sys7.1-doc-wip/LinkedPatches/LinkedPatchLoader.a

2417 lines
104 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

;
; Hacks to match MacOS (most recent first):
;
; <Sys7.1> 8/3/92 Elliot make this change
; 9/2/94 SuperMario ROM source dump (header preserved below)
;
;
; File: LinkedPatchLoader.a
;
; Contains: code to load linked patch resources
;
; Note that this patch loader is closely tied to the patch macros (LinkedPatchMacros.a)
; and patch linker (LinkPatch tool). Use care in changing one of these three without
; looking at the others.
;
; Written by: Darin Adler
;
; Copyright: © 1990—1991 by Apple Computer, Inc., all rights reserved.
;
; Change History (most recent first):
;
; <63> 8/1/92 DTY Change a bsr.s to a bsr.w to keep this file building for linked
; patch INITs. (Dont ask me while it compiles differently for
; the two.)
; <62> 7/31/92 DTY Allow use of link patch debugging globals in linked patch INITs
; because we cant think of a good reason why this was
; conditionalized out in the first place. Also added comments to
; document which low mem globals to set to stop on a particular
; link patch entry, or set of entries on boot.
; <61> 7/7/92 JSM Add comment about 'ptbl' being passed in via A3, rename
; g.patchTable global to g.patchRangeTable to avoid terminology
; confusion, document format of patch range table in header of
; CheckPatchTableRange.
; <60> 6/3/92 DTY #1026928: If were loading linked patches from ROM, ROMMapInsert
; needs to be set for all GetResource calls, not just the first
; one. Since CurMap gets reset for the ROM case, the value needs
; to be saved in a register before loading 'lpch' resources.
; <59> 4/30/92 DTY #1023455 <JSM>: The offset from change <51> should be $14, not
; #14.
; <58> 4/8/92 DTY #1026928,<FM>: Check CurMap before loading a 'lpch' resource. If
; its 1, set ROMMapInsert to true to get linked patches from ROM.
; <57> 4/7/92 pvh Calculate end of boot globals using (a5+BootGlobals.size)
; <56> 4/6/92 DTY #1024666,<JSM>: Three lines were lumped into one of the NOT
; INITVERSION conditionals, breaking linked patch INITs. Fix this
; up, and added judicious comments which Fred was too whipped to
; do because his ride was here.
; <55> 4/4/92 FM #1024666: Made LinkedPatches more memory friendly by
; copying the 'lpch' resources up above the boot globals and
; releasing the resources. This was necessary because there wasn't
; enough room for Mac II class machines with 2 megs to load their
; linked patches. This copying over the boot globals isn't done
; with the init version.
; <54> 3/26/92 DTY #1024282,<gbm>: Doing a _MoveHHi isnt good enough, so just take
; it out. Were going to load the Process Manager segments in
; BeforePatches.a ('PTCH' 0) so theyll be even lower, and theyll
; load before linked patches.
; <53> 3/25/92 DC FM - Heeeyyyy! somebody fogot to set up a0 to point to the
; 'ptbl' resource for the HLock call. As a result gPatchTable
; doesn't get set up and linked patch inits fail.
; <52> 3/16/92 DTY Dont try to release the patch range table for linked patch
; INITs, since we dont know whether it came from a resource or
; from the static table. Well, we do know, but its a pain to
; figure out again.
; <51> 3/13/92 DTY #1023455: In CheckTerror, set hasTerror if this is a Horror ROM
; as well.
; <50> 3/10/92 DTY #1024282: Before locking the 'lpch' resources down, move them
; high so free block coalescing will be better. Among other
; things, this allows the Process Manager segments to load a
; little lower.
; <49> 2/27/92 DTY For linked patch inits, if a 'ptbl' resource couldnt be found,
; use the static patch range table at the end of the loader which
; causes all linked patches to load.
; <48> 2/12/92 DTY Using _GetResource to fetch 'lpch' resources would get 'lpch'
; resources from the System file if a Gibbly doesnt have that
; particular 'lpch' resource. Always use _Get1Resource now to get
; 'lpch's so this wont happen.
; <47> 2/4/92 JSM (with FM) Move patchTable global above residentTop so we can use
; the same version of whip for 7.0 and CubeE.
; <46> 2/3/92 JSM (with FM) Add more comments so we wont break the whip dcmd in
; the future.
; <45> 1/31/92 JSM Update comments on format of 'lpch' resources to show extra
; field thats only in those resources for a single ROM.
; <44> 1/27/92 DTY Add some boundary conditions to CheckPatchTableRange: Provide a
; way out for empty range lists.
; <43> 1/6/92 DTY Linked patch INITs dont get passed a patch table, causing traps
; to point to wierd places, resident code not being there, and
; other strange things. If the loader is being built as part of an
; INIT, read in a 'ptbl' resource explicitly instead of expecting
; a pointer to one in A3.
; <42> 1/3/92 DTY Check the jump table entry number when making modules resident,
; so that entries that a Gibbly wants to skip will not be loaded.
; <41> 12/12/91 DTY Nick boo-bood in AllocateResidentBlock. D6 wasnt getting set
; up with the block pointer in the non-INIT case.
; <40> 12/12/91 ngk Use locked handle instead of pointer for code when compiling for
; INIT version. This is so MacsBug symbols show up.
; <39> 11/12/91 DTY Add support for patch table in InstallOrExecute. Check the
; routine number, and see if its in a range specified in the
; patch table before installing the patch.
; <38> 10/28/91 SAM/KSM Rolled in Regatta changes:
; Adding hasC96 and hasPwrMgr conditionals and made hasTerror
; identify Zydeco ROM too.
; Added code for hasTERROR and notTERROR.
; <37> 8/14/91 JSM Completely remove debugging support for old version of whip,
; add/change lots and lots more comments, especially in the areas
; of ForPatchTable, ForAllReferences, and Find/MakeResident mess.
; <36> 8/12/91 JSM Add lots more comments, especially in the area of unpacking the
; jump table.
; <35> 8/8/91 FM Delete obsolete code and fixing up comments. Nuked 32-bitQD
; check, 'bnvs' resource, and HasSystem6LinkedPatches
; <34> 8/6/91 FM Fixed bug in recursion for Install Procs. Required splitting the
; jump table into two parts. Also made it possible for references
; to use the “real thing” if an island was out of range. Added
; some comments to help readability. Also added another field in
; the CurApName debug info that tells whip that the jump tables
; have changed in this version of the loader.
; <33> 4/2/91 dba KIP: add new hasEricksonSoundMgr conditional, correct
; notEricksonSoundMgr
; <32> 12/20/90 gbm (dba) Change so that debugging information is kept out of the
; way of people who check first long of CurApName to see if it is
; boot time.
; <31> 12/18/90 gbm (dba) Adding conditional for preventing loading of the patch
; protector twice. Check for 32-bit addressing with Gestalt
; instead of low memory.
; <30> 12/14/90 BBM (dba) add two conditionals for linked patching, using24BitHeaps,
; using32BitHeaps
; <29> 10/12/90 JDR (dba) turn off more of the debugging stuff in the INITVERSION
; <28> 10/8/90 JDR Add notVM, change hasPMMUNoVM to hasPMMU
; <27> 9/26/90 JDR Made pre-7.0 INITs save the current zone, since INITs would run
; in an application heap and not the System heap. (Darin told me
; what to do.)
; <26> 9/25/90 KIP (really dba) Added new conditional noEricksonSoundMgr.
; <25> 9/7/90 stb & vl. add conditional test for INIT builds
; <24> 8/31/90 stb conditionalize out debugger if building with -init
; <23> 8/21/90 csd Added code for hasMemoryDispatch condition.
; <22> 8/19/90 dba add six new loading conditions that were needed for Sony and MMU
; patching; add new debugging features that work through low
; memory globals $912-91B; fixed greater than one byte conditions
; case that is used now that we have eleven conditions total
; <21> 8/10/90 DTY (w/ dba) Make PatchProc take offsets into ExpandMem.
; <20> 8/2/90 MR Fix initversion bug in FlushInstructionCache to reload registers
; before rts
; <19> 7/31/90 DTY Check the vector at jCacheFlush to make sure its an implemented
; routine before jumping to it for linked patch INITs.
; <18> 7/30/90 dnf (really dba) fix recursion for FindResident; this caused
; “force-resident” stuff to be ineffective, except in the topmost
; level of an InstallProc
; <17> 7/16/90 dba add support for counting the number of patches
; <16> 7/11/90 dba get dcImport to work
; <15> 7/10/90 DTY Test for presence of 32 Bit Color Quickdraw, and set not32BitCQD
; conditional flag appropriately. This flag is only defined for
; System 6 builds which have linked patches, or the patch is being
; built as an INIT.
; <14> 7/6/90 dba add a case for 32-bit references
; <13> 6/22/90 dba fix island case for LEA and PEA
; <12> 6/22/90 dba stuff the number of the patch being loaded into low memory so
; that we can debug more easily
; <11> 6/6/90 dba make code for islands
; <10> 5/1/90 BBM enable linked patches on 6.0 world
; <9> 4/15/90 dba delete unnecessary FlushInstructionCache from GetPatchAddress
; <8> 4/11/90 BBH remove flashmenu/sysbeep from INIT version
; <7> 4/10/90 dba make A5 world to pass to linked patches
; <6> 4/9/90 dba change so FlushInstructionCache does not trash registers
; <5> 4/9/90 BBH added compiler directive INITVERSION so that we can make a
; standalone INIT version of this code (see SysObj.make for build
; rules for LinkedPatchLoaderINIT.rsrc and the example
; CommToolboxPatchTest)
; <4> 4/9/90 dba make FlushInstructionCache routine that uses the jCacheFlush
; vector (not jFlushCache, which is for the file system cache)
; <3> 3/22/90 dba change name of loading routine; fix case with no lpchs
; <2> 3/21/90 dba fix range check so that it will work from -32768 to 32767
; <1> 3/9/90 KSM First checked in.
; 2/19/90 dba add judicious flushing of instruction cache
; 2/11/90 dba change so it loads a set of resources (for minimal install)
; 2/1/90 dba integrate changes from code review; move resident to high bit
; 1/30/90 dba patch and install in link order
; 1/25/90 dba added a header
;
; This file contains a patch loader that completes the linking begun by the LinkPatch tool.
; The code consists of a number of modules with entry points, references to other modules and
; entry points, and references to ROM addresses. The loader reads a table in the patch which
; specifies which patches and install routines should be executed on the machine. The routines
; which need to remain resident after patches are loaded are moved into a block in the system
; heap, and references within this block are changed to be PC-relative. The ROM references are
; fixed up to point to the appropriate ROM, and a jump table is built at A5 so that non-resident
; code can reference resident code (which may be more than 32K away). After that, all the patches
; are installed, and all of the install code is executed. Then the non-resident code and the jump
; table are deleted, and we return.
;
; ———————————————————————
; a note about conditions
; ———————————————————————
;
; A “ROM set” is a bitmap of ROMs that a particular ROM bind applies to. It may occupy 1-4 bytes
; depending on the number of ROMs. The loader is reassembled if the number of ROMs changes
;
; A “condition set” is a ROM set combined with bits for other conditions. It too may occupy 1-4
; bytes (always at least as many as a ROM set). Each condition must have code in this loader to
; determine if the condition is true (at label GetNonROMConditions).
;
; ——————————————————
; some of the limits
; ——————————————————
;
; ROMs must be less than 2^23 bytes long (8M)
; we can patch up to 31 total different ROMs and conditions total
;
; —————————————————————————————
; format of the 'lpch' resource
; —————————————————————————————
;
; all references to ROM are linked through the code, but when the reference is a distance
; greater than 128K, there is an entry in the ROM reference exception table; the word following
; the link in the code contains the ROM table entry number; this is 0 for the first entry, 1 for
; the next, etc.
;
; all references within a module are linked in a list; the head of this list is encoded in
; the packed jump table; the low bit of the link is set to 1 for “force resident” references;
; if a “force resident” reference is encountered in install code, the referenced module is moved
; into resident space in the system heap
;
; † 2 bytes number of 'lpch' resources for this ROM
; * 2 bytes number of entries in the bound ROM address table
; * 2 bytes number of entries in the jump table
; 4 bytes size of the code
; code
; 2 bytes first ROM table entry number (-1 means no packed ROM table entry)
; packed ROM table:
; 3 bytes per ROM ROM offset for each ROM (high bit set for end of table)
; ROM reference exception table:
; 3 bytes per exception 0 entry in code means exception (0 means end of table)
; packed jump table:
; 1 byte size
; 0-251 distance from current position in the code to next entry or
; module specified in the packed jump table, divided by 2 (i.e. 0-502 bytes)
; 252 means skip entries in the jump table
; 0 means end of packed jump table
; 1-254 means number of jump table entries to skip
; 255 means word follows with number of jump table entries to skip
; 253 means previous was reference list head for this module
; 254 means previous was an entry, not a new module
; 255 means word distance from current position in the code to next
; entry or module specified in the packed jump table follows
; * packed patch table (sorted in jump table order):
; * 1-4 bytes condition set for which the following patches/installs apply
; * 1 byte number of jump table entries to the next patch
; * 255 means word follows (0 word is end of table)
; * 254 means new condition set follows
; * 2 bytes trap number (0 means an install rather than a patch)
;
; † - Means only in 'lpch' resources that are for a single ROM (e.g. those with resource id 1, 2, 4, 8, etc.)
; * - Means only in the universal 'lpch' resource. The universal 'lpch' resource is the
; resource that is loaded on all known ROMs, that is, its resource id has the bit for each ROM set to one.
;
; ————————————————————————————
; thumbnail sketch of the code
; ————————————————————————————
;
; LoadPatch
; load code from resource and detach it
; allocate A5 world and jump table and large block for resident code
; unpack ROM addresses into jump table
; fix ROM references to point to ROM (traverse long linked list of references)
; unpack real jump table (store size of routine and start of reference list for each proc)
; traverse patch table:
; for each triggered routine:
; FindResident or MakeResident (depending on whether it is a install routine or patch)
; cut back resident code
; traverse patch table:
; for each triggered patch routine (not install routine):
; traverse routine:
; put in old address wherever we see the magic cookie
; install patch
; for each triggered install routine:
; MakeNonResident
; execute
; dispose non-resident code and A5 world
;
; FindResident(entry)
; walk back the jump table until you reach a proc
; traverse linked list of references
; if the reference is marked resident:
; MakeResident(entry)
;
; MakeResident(entry)
; do nothing if this entry is already active
; walk back the jump table until you reach a proc
; allocate space in the resident area (size is in proc)
; copy routine to the resident area
; assign addresses to all items in the jump table up to the next proc
; traverse linked list of references
; MakeResident(entry)
; fix up that pesky reference
;
; MakeNonResident(entry)
; do nothing if this entry is already active
; walk back the jump table until you reach a proc
; assign addresses to all items in the jump table up to the next proc
; traverse linked list of references
; MakeNonResident(entry)
; fix up that pesky reference
;
print push,off
load 'StandardEqu.d'
include 'MMUEqu.a'
include 'BootEqu.a'
include 'GestaltEqu.a'
include 'HardwarePrivateEqu.a'
include 'UniversalEqu.a'
include 'Traps.a'
include 'LinkedPatchMacros.a'
print pop
dsLinkedPatchReferenceTooFar equ 97 ; *** move into SysErr.a
k32BitCQDTrap equ $AB03 ; trap number for 32-bit QuickDraw
kUnimplementedTrap equ $A89F ; trap number for unimplemented trap
kGestaltTrap equ $A0AD ; trap number for Gestalt
kMemoryDispatchTrap equ $A05C ; trap number for MemoryDispatch
if &type('INITVERSION') = 'UNDEFINED' then ; <5>
INITVERSION: equ 0
endif
;————————————————————————————————————————————————————————————————————————————————————————————————————
; special low-memory used in conjunction with the whip dcmd for debugging linked patches
LinkedPatchDebuggerPtr equ CurApName+8 ; call this routine so dcmd can snarf symbols
NewLoaderActive equ CurApName+$16 ; set to zero so that dcmd knows the new loader is active
; special low-memory used for debugging linked patches.
; loader will drop into debugger when about to load specified jump table entry or range of entries
StashJumpTableEntryNum equ CurApName+$10 ; place jump table entry number here as we go <32>
StopOnJumpTableEntryNum equ CurApName+$12 ; drop into debugger when loading this entry <32>
StopOnJumpTableEntryNumHighest equ CurApName+$14 ; or loading this range if this one is ¬ffff <32>
;————————————————————————————————————————————————————————————————————————————————————————————————————
; equates from the patch linker
kAbsoluteOpcode equ 15 ; this opcode means an absolute address
;————————————————————————————————————————————————————————————————————————————————————————————————————
; entry in the jump table
; note loader depends on the fact that both jump table and ParallelJumpTableEntryInfo
; are equal in size! The parallel jump table entry info is walked in step with the
; jump table. By keeping the records equal in size the parallel info will always be a fixed
; offset away from the corresponding jump table entry.
; This is the record for a jump table entry. It allows non-resident install code
; to call the resident patch proc routines. We also use the opcode field to
; indicate when a routine has been "nailed-down".
JumpTableEntry record 0 ; for each entry in the jump table:
opcode ds.w 1 ; initially 0 but becomes a JMP.L once this entry
; is nailed down.
address ds.l 1 ; address, in either resident non-resident block
size equ *
endr
; This is a record for additional information pertaining to each jump table entry.
; Keeping this extra information in a seperate record allows more jump table
; entries.
ParallelJumpTableEntryInfo record 0 ; Extra info for each entry in the jump table:
referenceListHead ds.w 1 ; head of list (even) or 1 for no list (odd) it
; is an offset into the code of the 'lpch' resource
flags ds.w 1 ; flags for indicating islands and for preventing
; cycles in FindResident and MakeResident recursion.
routineSize ds.w 1 ; size of the routine is here; 0 for entry point
size equ *
endr
; these bits are set in the flags field of the parallel info reord.
islandFlagBit equ 0 ; this is the bit that identifies an island
findSeenFlagBit equ 1 ; this is the bit set during FindResident
; to prevent cycles during the recursion
makeSeenFlagBit equ 2 ; this is the bit set during a MakeResident
; to prevent cycles during the recursion
; constants used in the manipulation of jump table information
kNailedDown equ $4EF9 ; A JMP instructiion. Stuck in the opcode field
; of a jump table entry when it is nailed-down
kInitialReferenceListHead equ 1 ; A good initial value because there cannot be
; an odd offset. Couldnt use zero cause thats a valid offset
kInitialRoutineSize equ -1 ; unused jump table entries (low bit clear)
;————————————————————————————————————————————————————————————————————————————————————————————————————
; All 'lpch' resources that need to be loaded are referenced by PatchResourceEntry records.
; The records are kept on the stack. The last record is followed by a 0 (i.e. endOfList is 0).
; Whenever we apply a function to all patches we can walk down the stack until we hit a 0.
PatchResourceEntry record 0 ; each entry on the stack
endOfList equ * ; list of entries is terminated by 0 on the stack
romSet ds.w 1 ; number of the ROM set
resourceHandle ds.l 1 ; handle to the 'lpch' resource
ptr ds.l 1 ; pointer into the resource (advances as we go)
codePtr ds.l 1 ; pointer to code in the resource
size equ *
endr
;————————————————————————————————————————————————————————————————————————————————————————————————————
; globals
g record {qdGlobalsPtr} ; an A5 world
start equ *
romNumber ds.w 1 ; ROM index number
ParallelJumpTableEntryInfoTableOffset ds.l 1 ; Offset to extra info for jump table entries
; normally held in register D3
nonROMConditions ds.l 1 ; other loading conditions (like VM, sound, etc.)
patchRangeTable ds.l 1 ; <39> Saved pointer to a patch range table ('ptbl')
; NOTE: whip depends on the location of residentTop
; if you add more globals, add them •before• here
residentTop ds.l 1 ; pointer to available space in resident block
; Bumped up as we go so we know how far to cut
; back the resident block when were done.
offset equ *-start
qdGlobalsPtr ds.l 1 ; QuickDraw globals
romTable ds.b 0 ; place for ROM table to start
; NOTE: whip depends on the location of jumpTable
jumpTable ds.b 0 ; place for jump table to start
size equ *-start
endr
;————————————————————————————————————————————————————————————————————————————————————————————————————
LoadLinkedPatches proc export
; Load a patch from a resource.
;
; In:
; a3 pointer to patch range table ('ptbl') from 'boot' 3
; if this is a linked patch INIT, uses static table instead
;
; Out:
; can trash d0-d2/a0-a1
import ForPatchTable
import FindOrMakeResident
import InstallOrExecute
import ROMVersions
LoadLinkedPatchesRegisters reg d3-d7/a2-a5
movem.l LoadLinkedPatchesRegisters,-(sp)
GetROMNumber
; get the number of this ROM
moveq #NumROMs$-1,d3 ; ROM number counter
lea ROMVersions,a0 ; point to a table of ROM versions
move.l ROMBase,a1 ; get ROM version word from ROM
move.w 8(a1),d1 ; now we have the ROM version
@next
cmp.w (a0)+,d1 ; is this the version?
dbeq d3,@next ; keep looking
beq.s @ok ; yes, ROM found
moveq #dsNoPatch,d0
_SysError
@ok
;————————————————————————————————————————————————————————————————————————————————————————————————————
; There used to be essentially two copies of the linked patches in memory, one in the detached
; 'lpch' resources, and a copy of all the resident routines in the resident block. This is fine except
; that this doesnt leave enough memory to boot on 2 Meg machines. Fred was astute enough
; to realize that we have half of memory not being used sitting above MemTop/2 + 1K,
; so we now copy the 'lpch' resources into this area and disposes the detached resource
; handle. While we still have two copies around, one is not in the system heap,
; leaving more free space there for the patches themselves to load and waste memory.
LoadPatchResources
; load the patch resources
if NOT INITVERSION then
move.l MemTop,-(sp) ; <55>
; calculate end of boot globals in a1 (a5+BootGlobals.size)
move.l a5,a1 ; <57>
add.l #BootGlobals.size,a1 ; <57> Skip past globals
endif
moveq #0,d7 ; calculate the total size of resources
moveq #0,d4 ; count the number of resources
clr.w -(sp) ; mark end of PatchResourceEntries
; <60> Save CurMap in case its 1 for the ROM map, since it will get set to
; SysMap by the Resource Manager when leaving a GetResource call.
move.w CurMap,d6 ; <60>
moveq #0,d1 ; start going through the resources
@next
addq.w #1,d1 ; advance to the next resource
btst #NumROMs$,d1 ; have we done all of the ROMs?
bnz.s @done
btst d3,d1 ; is this resource needed for this ROM?
bz.s @next ; no, go on
cmp.w #1,d6 ; <58> <60> Check if CurMap is 1.
bne.s @linkedPatchNotInROM ; <58> If its not 1, its a resource in a real file
move.w #MapTrue,ROMMapInsert ; <58> Set ROMMapInsert so the call to _Get1Resource will look in the ROM resource map for the 'lpch'
@linkedPatchNotInROM ; <58>
subq.l #4,sp ; make room for the resource
move.l #'lpch',-(sp)
move.w d1,-(sp)
_Get1Resource ; <5> BBH - Init Version does 1 deep search ONLY!
move.l (sp)+,d2
bnz.s @ok ; yes, enough memory to load the patch
subq.l #2,sp ; make room for error
_ResError ; check if the error is real
move.w (sp)+,d0 ; get the error code
bz.s @notFound ; no error is OK
cmp.w #resNotFound,d0 ; resNotFound?
beq.s @notFound ; yes, resNotFound is also OK
moveq #dsMemFullErr,d0
_SysError
@notFound
tst.w d4 ; is this the first resource (i.e. the one for this ROM only)?
bnz.s @next ; no, its OK that it be missing
moveq #dsNoPatch,d0 ; yes, if it is missing there is an error
_SysError
@ok
addq.w #1,d4 ; count the resources
move.l d2,-(sp)
_DetachResource ; detach the resource
move.l d2,a0
_GetHandleSize ; add its size in
add.l d0,d7 ; calculate total size of all resources
if NOT INITVERSION then
; Ensure that each copy of the linked patch data starts at an even address by copying
; an even number of bytes.
btst #0,d0 ; <56> Is the size of the 'lpch' resource odd?
bz.s @copyLinkedPatchAboveBootGlobals ; <56>
addq.l #1,d0 ; <55> If it is, make it even
; Copy the 'lpch' resource data up above the boot globals. A1 is set up
; to be the address where the data should be copied to. Its initially MemTop/2 + 1K.
; After each _BlockMove, it gets bumped up, so the next 'lpch' is copied above this
; one.
;
; Before moving the data, we make space for a faked up handle to point to it:
;
; +------------------+
; + lpch +
; + data +
; +------------------+
; + ptr to lpch data +
; +------------------+
; + . +
; + . +
; + . +
; +------------------+
; + boot globals +
; +------------------+
@copyLinkedPatchAboveBootGlobals
addq.l #4,a1 ; <55> Leave space for a faked up handle
move.l d0,-(sp) ; <56> Save size of 'lpch' resource
move.l (a0),a0 ; <55> Get pointer to data
_BlockMove ; <55> Copy it above the boot globals
move.l (sp)+,d0 ; <56> Restore size.
lea -4(a1),a0 ; <55> Get longword we reserved for the fake handle
move.l a1,(a0) ; <55> And point it to the 'lpch' data
add.l d0,a1 ; <55> Bump A1 to the address the next 'lpch' resource should be copied to.
; Create the PatchResourceEntry record for this 'lpch' resource
subq.l #8,sp ; <55> make room for codePtr (used later)
move.l a0,-(sp) ; <55> push “Handle”
move.w d1,-(sp) ; <55> also push the romSet (same as resource ID)
move.l d2,a0 ; <56>
_DisposeHandle ; <56> Dispose of the handle to the 'lpch' resource (now that we have a copy)
else
subq.l #8,sp ; make room for codePtr and ptr (used later)
move.l d2,-(sp) ; push resourceHandle
move.w d1,-(sp) ; also push the romSet (same as resource ID)
endif
bra.s @next
@done
tst.w PatchResourceEntry.endOfList(sp) ; did we load any?
bz Done ; no, get out of here
; Inside Mac IV p. 257 states that BufPtr may not go lower than MemTop/2 + 1K. For the algebraically
; impaired, that means that MemTop = (minBufPtr - 1K) * 2. In case some linked patches change
; BufPtr, move MemTop higher so that patches that want to can move BufPtr down without trashing
; the 'lpch' data we copied above the boot globals.
if NOT INITVERSION then ; <56>
sub.w #1024,a1 ; <55> end of lpch data - 1024
add.l a1,a1 ; <55> 2*(end of lpch data - 1024) <30>
move.l a1,MemTop ; <55> MemTop := 2*(end of lpch data - 1024)
endif ; <56>
;————————————————————————————————————————————————————————————————————————————————————————————————————
AllocateResidentBlock
; allocate resident pointer the same size as all of patch resources *** better initial size?
move.l d7,d0
if INITVERSION then ; <40>
_NewHandle sys ; <40> alloc a locked handle instead of of pointer
bnz.s @allocErr ; <40> this is to get MacsBug to show labels
_HLock ; <40>
move.l (a0),a0 ; <41> Get pointer to block
bra.s @ok ; <40>
else
_NewPtr sys
bz.s @ok ; must succeed
endif
@allocErr
moveq #dsMemFullErr,d0
_SysError
@ok
move.l a0,d6 ; keep pointer to resident code in d6
;————————————————————————————————————————————————————————————————————————————————————————————————————
LockDownNonResidentPatches
; now we can lock down the non-resident part of the patches
move.l sp,a4 ; get first PatchResourceEntry
@next
tst.w (a4)+ ; end of table?
bz.s @done
move.l (a4)+,a0 ; now we can lock down each resource
if INITVERSION then
_HLock
move.l (a0),d0 ; dereference it (must be locked)
_StripAddress
else
move.l (a0),d0 ; <55> Dereference the fake handle to the 'lpch' data above the boot globals.
endif
move.l d0,(a4)+ ; store the dereferenced pointer in ptr
add.l #4,a4 ; skip over space for the codePtr
bra.s @next
@done
CheckForMissingResources
; now we check the number of 'lpch' resources stored in the first resource
; (which is the one used for just this ROM) to see if it is too low
lea -2-PatchResourceEntry.size+PatchResourceEntry.ptr(a4),a4 ; position at the ptr
move.l (a4),a0 ; get the pointer
cmp.w (a0)+,d4 ; is it the right number of resources?
beq.s @ok
moveq #dsBadPatch,d0 ; no, a resource was missing for this ROM
_SysError
@ok
move.l a0,(a4) ; advance past the count
;————————————————————————————————————————————————————————————————————————————————————————————————————
AllocateA5World
; get size of A5 world by looking at sizes at start of universal resource for all ROMs
; First we calculate how much space will be needed for the ROM table.
; Then we calculate how much will be needed for the patch proc and install
; proc jump table information. Since we use the same allocated space for both
; the larger of the two will determine the size of our non resident block
; we also set up the global offset into the jump tables extra information
move.l PatchResourceEntry.ptr(sp),a0 ; point at start of universal resource
moveq #0,d0
move.w (a0)+,d0 ; calculate the size of the ROM table
lsl.l #2,d0 ; x4 (ROM table entries are 4 bytes)
moveq #0,d5
move.w (a0)+,d5 ; calculate the size of the jump table
move.l d5,d1
addq.l #1,d1 ; one extra entry for coding convenience
move.l d1,d2
lsl.l #3,d1 ; x8 (jump table entries are 6 bytes)
lsl.l #2,d2 ; x4 (jump table entries are 6 bytes)
add.l d2,d1 ; 8+4 = 12 ( 6 bytes per jump table entry
; 6 bytes parallel info per entry )
move.l a0,PatchResourceEntry.ptr(sp) ; now all resources point to their code size
cmp.l d0,d1 ; get the larger of the two
bls.s @gotLarger
move.l d1,d0
@gotLarger
moveq #g.size,d2
add.l d2,d0 ; larger table + globals = total size
_NewPtr ,Clear ; create the A5 block
bz.s @ok ; must succeed
moveq #dsMemFullErr,d0
_SysError
@ok
move.l (a5),g.qdGlobalsPtr+g.offset(a0) ; copy the QuickDraw globals pointer
lea g.offset(a0),a5 ; switch to the new A5
move.w d3,g.romNumber(a5)
move.l d6,g.residentTop(a5) ; none of the resident pointer is used yet
if INITVERSION then ; <43>
Import StaticRangeTable ; <49>
lea StaticRangeTable,a3 ; <49> Use a built in patch range table for linked patch INITs.
endif ; <43>
move.l a3,g.patchRangeTable(a5) ; <39> Save the pointer to the patch range table.
lsr.l #1,d1 ; d1 is the size of the Jump Table Entries plus
; the parallel info entries. halfway thru jump table
lea g.jumpTable(a5),a0 ; space is start of info table so set up the offset to
move.l d1,g.ParallelJumpTableEntryInfoTableOffset(a5) ; info table for later
;————————————————————————————————————————————————————————————————————————————————————————————————————
GetNonROMConditions
; figure out non-ROM conditions that apply
import MrGestalt,TrapImplemented
; condition evaluation can trash standard registers (d0-d2/a0-a1)
; condition bit should be set in the flags long-word, d3
moveq #0,d3 ; no flags
CheckC96 ; <3>
; hasC96
MOVE.L ROMBase,A0 ; Get base of ROM
CMPI.W #$067C,8(A0) ; Do we have universal tables?
BNE.S @no ; -> Nope, no C96
TestFor SCSI96_1Exists ; C96 SCSI chip?
BEQ.S @no ; -> Nope, no C96
@yes BSET.L #Condition$hasC96,D3 ; We have a C96 SCSI chip!
@no
CheckPowerMgr ; <3>
; hasPwrMgr
MOVE.W HwCfgFlags,D0 ; Get them Hardware Config Flags
BTST.L #hwCbPwrMgr,D0 ; Do we have a PowerMgr?
BEQ.S @no ; -> No.
@yes BSET.L #Condition$hasPwrMgr,D3 ; We have a PowerMgr chip!
@no
CheckTERROR ; <SAM>
; notTERROR
MOVE.L ROMBase,A0 ; Get base of ROM
CMPI.W #$067C,8(A0) ; Is the ROM major version 67C?
BNE.S @no ; -> Nope, say notTERROR
CMPI.B #$15,18(A0) ; Is the ROM minor version #$15?
BEQ.S @yes ; -> Yes, we have TERROR
CMPI.B #$17,18(A0) ; Is the ROM minor version #$17? <3>
BEQ.S @yes ; -> Yes, we have Zydeco ( == TERROR) <3>
btst #0,20(a0) ; <51> <59> Horror ROM?
bnz.s @yes ; <51> Yes. Load Terror patches for this ROM too.
@no
BSET.L #Condition$notTERROR,D3 ; We do NOT have TERROR
BRA.S @done
@yes BSET.L #Condition$hasTERROR,D3 ; We have a TERROR ROM
@done
CheckAUX
; notAUX
btst.b #hwCbAUX,HWCfgFlags ; are we under A/UX?
bnz.s @no ; yes, no flag to set
bset.l #Condition$notAUX,d3
@no
CheckVM
; notVM
move.l #gestaltVMAttr,d0 ; check for VM
jsr MrGestalt
btst #gestaltVMPresent,d0
bnz.s @done ; bit is set, we have VM, so dont set flag
@no
bset.l #Condition$notVM,d3
@done
CheckMMU
; hasHMMU
; hasPMMU
cmp.b #2,CPUFlag ; are we on a 68020 or better?
blo.s @done ; no, there is no MMU
move.b MMUType,d0 ; get the MMU type
cmp.b #HMMU,d0 ; is it an HMMU?
beq.s @HMMU ; yes, there is an HMMU
cmp.b #PMMU851,d0 ; is there some kind of PMMU?
blo.s @done ; no, there is no PMMU
bset.l #Condition$hasPMMU,d3
bra.s @done
@HMMU
bset.l #Condition$hasHMMU,d3
@done
CheckMemoryDispatch
; hasMemoryDispatch
move.w #kMemoryDispatchTrap,d0
_GetTrapAddress newOS ; get address of _MemoryDispatch
jsr TrapImplemented
beq.s @no ; no VM
bset.l #Condition$hasMemoryDispatch,d3
@no
CheckSonyDriverVersion
; has800KDriver/hasFDHDDriver
move.l UTableBase,a0 ; look at the Sony drivers DCE
move.l 16(a0),a0 ; get the DCE handle
move.l (a0),a0 ; dereference it
cmp.b #1,dCtlQueue+1(a0) ; is it the old 800K driver?
ble.s @800K
bset.l #Condition$hasFDHDDriver,d3
bra.s @done
@800K
bset.l #Condition$has800KDriver,d3
@done
CheckIWM
; hasIWM
move.l ROMBase,a0 ; get ROM version
cmp.w #$067C,8(a0) ; is this the IIci ROM family?
bne.s @yes
TestFor IWMExists ; are we running with an IOP?
bz.s @no ; yes, so we dont have an IWM
@yes
bset.l #Condition$hasIWM,d3
@no
CheckEricksonMistake
; hasEricksonOverpatchMistake
move.l ROMBase,a0 ; get ROM version
cmp.w #$067C,8(a0) ; is this the IIci ROM family?
bne.s @no
cmp.w #$12F1,18(a0) ; check sub-version for Erickson ROM
bne.s @no
bset.l #Condition$hasEricksonOverpatchMistake,d3 ; the Erickson ROM has 1-ROM-only bugs
@no
CheckEricksonSoundMgr
; hasEricksonSoundMgr
; notEricksonSoundMgr
move.l ROMBase,a0 ; get ROM version
cmp.w #$067C,8(a0) ; is this the IIci ROM family?
bne.s @no
cmp.w #$12F1,18(a0) ; check sub-version for Erickson ROM or newer
blo.s @no
bset.l #Condition$hasEricksonSoundMgr,d3 ; Erickson and newer ROMs have a new Sound Mgr.
bra.s @done
@no
bset.l #Condition$notEricksonSoundMgr,d3 ; older ROMs have the original IIci Sound Mgr.
@done
CheckHeapsMode ; <30><31>
; using24BitHeaps
; using32BitHeaps
move.l #gestaltAddressingModeAttr,d0 ; check attributes
jsr MrGestalt
btst #gestalt32BitAddressing,d0
bnz.s @32
bset.l #Condition$using24BitHeaps,d3 ; set the flag for the 24 bit heap
bra.s @done ; time for the next conditional check
@32
bset.l #Condition$using32BitHeaps,d3 ; set the flag for the 32 bit heap
@done ; <30><31>
CheckNoPatchProtector
; noPatchProtector ; <31>
move.w #$A800,d0 ; get address of first Toolbox trap
_GetTrapWordAddress ; using new patched version of GetTrapAddress
move.l a0,a1
moveq #0,d0 ; get address of first OS trap
_GetTrapAddress ; using standard GetTrapAddress
cmp.l a0,a1 ; did we get the same answer both times?
bne.s @no ; no, the patch protector is working
bset.l #Condition$noPatchProtector,d3 ; yes, there is no patch protector yet
@no ; <31>
; *** new conditions should be added here ***
StoreConditions
if NumConditions$ <= 8 then
move.b d3,g.nonROMConditions(a5) ; store all the flags
elseif NumConditions$ <= 16 then
move.w d3,g.nonROMConditions(a5) ; store all the flags
else
move.l d3,g.nonROMConditions(a5) ; store all the flags
endif
;————————————————————————————————————————————————————————————————————————————————————————————————————
UnpackROMTable
; advance through all the resources, unpacking the ROM address tables
; also initialize the codePtr field of the PatchResourceEntries
move.l sp,a4 ; get first PatchResourceEntry
@nextResource
move.w PatchResourceEntry.romSet(a4),d0 ; get the ROM set (same as endOfList)
bz.s @done ; no ROMs means we are done
move.l PatchResourceEntry.ptr(a4),a2 ; get pointer into resource (advance as we go)
move.l (a2)+,d1 ; get size of the code
move.l a2,PatchResourceEntry.codePtr(a4) ; store pointer to code
add.l d1,a2 ; point at ROM table
; unpack all of the items in the ROM table
moveq #0,d2
move.w (a2)+,d2 ; get the first ROM table entry number
bmi.s @doneResource ; -1 means no ROM references in this 'lpch'
lsl.l #2,d2 ; turn into offset into ROM table (ROM table enntries are 4 bytes)
lea g.romTable(a5,d2.l),a0 ; point at this entry in the ROM table
; each entry in the ROM table has a ROM bind for every ROM this 'lpch' applies to
; *** isnt the offset into each entry in the packed ROM table constant for the ROM were loading on?
; *** if so, why not calculate it once instead of looping through NumROMs$ and bit testing each time?
; *** also, the size of each entry in the packed ROM table is constant, too (3 bytes * number of ROMs
; *** supported by this 'lpch')
@nextEntry
moveq #NumROMs$-1,d1 ; start with the last ROM
@loopROM
btst.l d1,d0 ; does this ROM have an entry for this 'lpch'?
bz.s @nextROM ; no, go on to the next ROM
cmp.w g.romNumber(a5),d1 ; is this the ROM we are loading for?
bne.s @skipEntry ; no, skip this entry in the packed ROM table
moveq #$7F,d2 ; yes, clear high bit (used as flag for end of table)
and.b (a2)+,d2 ; get the first byte
swap d2 ; move it up
move.b (a2)+,d2 ; get the second byte
lsl.w #8,d2 ; move it up
move.b (a2)+,d2 ; get the third byte
add.l ROMBase,d2 ; add offset to the ROMBase
bra.s @nextROM ; we have the ROM bind for this ROM,
; but we have to continue on to the end of this entry
@skipEntry
addq #3,a2 ; skip the offset *** define a constant for packed ROM table offset size
@nextROM
dbra d1,@loopROM ; move on to the next ROM
move.l d2,(a0)+ ; store the ROM address (or 0) *** why 0?
btst #7,-3(a2) ; test high bit of last entry
bz.s @nextEntry
@doneResource
move.l a2,PatchResourceEntry.ptr(a4) ; PatchResourceEntry.ptr now points to ROM reference exception table
lea PatchResourceEntry.size(a4),a4 ; advance to the PatchResourceEntry for the next resource
bra.s @nextResource
@done
;————————————————————————————————————————————————————————————————————————————————————————————————————
FixupROMReferences
; advance through all the resources, fixing up ROM references inside the code
move.l sp,a4 ; get first PatchResourceEntry
@nextResource
move.w PatchResourceEntry.romSet(a4),d0 ; get the ROM set (same as endOfList)
bz.s @done ; no ROMs means we are done
move.l PatchResourceEntry.ptr(a4),a2 ; get pointer to ROM reference exception table
; find the ROM references and fix them up
move.l PatchResourceEntry.codePtr(a4),a0 ; point at the start of the code
@nextTableOffset
moveq #0,d0
move.b (a2)+,d0 ; get high byte
swap d0
move.b (a2)+,d0 ; get middle byte
lsl.w #8,d0
move.b (a2)+,d0 ; get low byte
tst.l d0 ; check for end of table
bz.s @doneResource
@useOffset
add.l d0,a0 ; point at the ROM reference in code
moveq #0,d0
move.w (a0)+,d0 ; get the offset to the next ROM reference
moveq #0,d1
move.w (a0)+,d1 ; get the ROM table entry number for this ROM reference
lsl.l #2,d1 ; convert to ROM table offset
move.l g.romTable(a5,d1.l),-4(a0) ; jam the value into the code
add.l d0,d0 ; convert offset in words to offset in bytes
bnz.s @useOffset ; if valid, use the offset we just got from the code
bra.s @nextTableOffset ; otherwise, use a long offset from the ROM reference table
@doneResource
move.l a2,PatchResourceEntry.ptr(a4) ; PatchResourceEntry.ptr now points to packed jump table
lea PatchResourceEntry.size(a4),a4 ; advance to the resource
bra.s @nextResource
@done
;————————————————————————————————————————————————————————————————————————————————————————————————————
InitializeJumpTable
; Turns out things will work best if routineSizes are initialized to -1.
; This ensures that any unused entries will look like the beginning of a module, so
; we wont loop past the end of a module that does not have another module after it.
; This includes one extra entry at the end of the jump table for that end case.
; Note that d5 contains the number of entries in the table, not counting the extra one.
move.l g.ParallelJumpTableEntryInfoTableOffset(a5),d3 ; Make d3 offset to parallel jump table entries
lea g.jumpTable(a5),a0 ; Make a0 the first jump table entry
moveq #kInitialRoutineSize,d0 ; a good routine size to initialize to
; loop through jump table and parallel jump table initializing values
move.w d5,d1 ; initialize loop counter with number of entries in jump table
@next
move.w d0,ParallelJumpTableEntryInfo.routineSize(a0,d3.l) ; jam in the routine size
clr.w ParallelJumpTableEntryInfo.flags(a0,d3.l) ; start off with no flags set
clr.w JumpTableEntry.opcode(a0) ; start off not nailed down (0 opcode)
addq.l #JumpTableEntry.size,a0 ; advance to the next entry
dbra d1,@next ; loop through the whole jump table
;————————————————————————————————————————————————————————————————————————————————————————————————————
UnpackJumpTable
; now, go through all the 'lpch' resources on the stack unpacking the jump table
;
; Some registers we use consistently:
;
; a0 = pointer to jump table entry in g.jumpTable for current entry point or module
; a1 = pointer to jump table entry in g.jumpTable for previous module
; a2 = pointer to current entry in the packed jump table
; a4 = pointer to current PatchResourceEntry on the stack
;
; d0 = pointer to current entry point or module in the 'lpch' code
; d1 = pointer to previous module in the 'lpch' code so we can calculate the size of modules
; (or -1 if there is no previous module)
; d2 = current size of special opcode from packed jump table
; d3 = g.ParallelJumpTableEntryInfoTableOffset, the offset into g.jumpTable of the parallel jump table
; d4 = boolean indicating whether we have a jump table entry for a pending module to fix up
move.l sp,a4 ; get first PatchResourceEntry
@nextResource
tst.w PatchResourceEntry.endOfList(a4) ; are we at the end ot the list?
bz @done ; zero means yes
move.l PatchResourceEntry.ptr(a4),a2 ; get pointer to packed jump table
; unpack the real jump table, trashing the ROM table
lea g.jumpTable(a5),a0 ; build the jump table here
move.l PatchResourceEntry.codePtr(a4),d0 ; keep the running address in d0
moveq #-1,d1 ; there is no previous module
; the main loop itself
;
; A reminder about the format of the packed jump table:
;
; 0-251 distance from current position in the code to next entry or
; module specified in the packed jump table, divided by 2 (i.e. 0-502 bytes)
; 252 means skip entries in the jump table
; 0 means end of packed jump table
; 1-254 means number of jump table entries to skip
; 255 means word follows with number of jump table entries to skip
; 253 means previous was reference list head for this module
; 254 means previous was an entry, not a new module
; 255 means word distance from current position in the code to next
; entry or module specified in the packed jump table follows
@pendingModule
moveq #1,d4 ; when we start, we have a pending module
@loop
moveq #0,d2
move.b (a2)+,d2 ; get the size of the routine
cmp.b #254,d2 ; check if it is a special opcode
bhi.s @word ; word length size follows, so get it
beq.s @doEntry ; we just got an entry (as opposed to a new module)
cmp.b #252,d2 ; check for other special opcodes
bhi.s @doReferenceList ; we just got a reference list head for the current module
beq.s @skipJumpTableEntries ; we should skip ahead some entries before the next module
; otherwise, not a special opcode
add.w d2,d2 ; quick sizes are stored as half, double it
@gotSize
tst.b d4 ; is there a pending module?
bz.s @noPending
; yes, so the size must be for the current module
bsr.s @finishModule ; put size in previous module jump table entry
bsr.s @emitEntry ; emit the entry
@noPending
add.l d2,d0 ; no pending module, advance code pointer by the size
bra.s @pendingModule
; emitEntry initializes the referenceListHead and routineSize in the parallel jump table entry,
; stores a pointer to the routine in the code in the address field of the jump table entry,
; and advances the pointer into g.jumpTable to the next entry
;
; a0 = pointer to jump table entry in g.jumpTable for current entry point or module
; d0 = pointer in the code to current entry point or module
@emitEntry
move.w #kInitialReferenceListHead,ParallelJumpTableEntryInfo.referenceListHead(a0,d3.l) ; real reference list head comes later I promise
move.l d0,JumpTableEntry.address(a0) ; put code pointer here (actual address calculated later)
clr.w ParallelJumpTableEntryInfo.routineSize(a0,d3.l) ; routine size goes here later for a module
addq.l #JumpTableEntry.size,a0 ; move to next entry!
rts
; finishModule stores the size of any previous module in the routineSize field of its parallel jump table entry,
; then updates the pointer to the last modules jump table entry and the pointer to the previous module
;
; a1 = pointer to jump table entry in g.jumpTable for last module
; d1 = pointer to previous module in the code
@finishModule
tst.l d1 ; first one?
bmi.s @noOld ; no, this is the first module
sub.l d0,d1 ; calculate length of previous module
neg.w d1 ; by subtracting from where we are now
move.w d1,ParallelJumpTableEntryInfo.routineSize(a1,d3.l) ; so we can store the size in the parallel info for
; this jump table entry
@noOld
move.l a0,a1 ; always keep pointer to jump table entry of the last module here
move.l d0,d1 ; keep pointer to the last module here
rts
; we got a special opcode saying that current entry in packed jump table is word length
@word
move.b (a2)+,d2 ; get high byte
lsl.w #8,d2 ; push it up
move.b (a2)+,d2 ; get low byte
bra.s @gotSize
@doEntry
bsr.s @emitEntry
; fall through
@noPendingModule
moveq #0,d4 ; no pending module
bra.s @loop
; we got a special opcode saying that current entry is the first reference inside this module
@doReferenceList
move.l d0,d2 ; calculate offset of reference list head by subtracting pointer to
sub.l d1,d2 ; first reference from the beginning of the module
move.w d2,ParallelJumpTableEntryInfo.referenceListHead(a1,d3.l); now the reference list head is fixed as promised earlier
bra.s @noPendingModule ; go back and loop
; we got a special opcode saying to skip some entries in the jump table
@skipJumpTableEntries
move.b (a2)+,d2 ; get number of entries to skip
bz.s @doneResource ; 0, that means end of table
cmp.b #255,d2 ; 255 means number is two bytes
bne.s @gotSkip ; otherwise, got number of entries to skip
move.b (a2)+,d2 ; number of entries to skip is a word
lsl.w #8,d2 ; push it up
move.b (a2)+,d2 ; get low byte
@gotSkip
lsl.l #2,d2 ; each jump table entry is 6 bytes, so multiply size * 4
add.l d2,a0 ; advance to a new place in the jump table
lsr.l #1,d2 ; and add size * 2
add.l d2,a0 ; advance to a new place in the jump table
bra.s @loop ; continue in the loop
@doneResource
bsr.s @finishModule ; finish the last module
move.l a2,PatchResourceEntry.ptr(a4) ; PatchResourceEntry.ptr now points to packed patch table
lea PatchResourceEntry.size(a4),a4 ; advance to the next resource
bra @nextResource
@done
;————————————————————————————————————————————————————————————————————————————————————————————————————
FindAllResidentCode
; find resident entries in install routines, make patches resident
; note that the packed patch table is only in the universal 'lpch' resource
move.l PatchResourceEntry.ptr(sp),a2 ; point at the packed patch table
lea FindOrMakeResident,a1
bsr ForPatchTable ; do that for each triggered entry in the table
;————————————————————————————————————————————————————————————————————————————————————————————————————
CutBackResidentCode
; cut back the block containing the resident code
if INITVERSION then
; in INIT version of loader, code block is a locked handle ; <40>
move.l d6,a0 ; <40> get the start of the resident code block
_RecoverHandle ; <40>
move.l g.residentTop(a5),d0 ; <40> get the end of the resident code block
sub.l d6,d0 ; <40> calculate the size of the resident code block
_SetHandleSize ; <40>
else
move.l g.residentTop(a5),d0 ; get the end of the resident code block
move.l d6,a0 ; get the start of the resident code block
sub.l a0,d0 ; calculate the size of the resident code block
_SetPtrSize
endif
;————————————————————————————————————————————————————————————————————————————————————————————————————
GetIslandsOutOfJumpTable
; get references to islands out of the jump table
; MakeResident creates the islands, and points the jump table at them.
lea g.jumpTable(a5),a0 ; point at jump table
move.l g.ParallelJumpTableEntryInfoTableOffset(a5),d3 ; offset to parallel jump table
move.w d5,d1 ; initialize loop counter with number of entries in jump table
@loop
bclr #islandFlagBit,ParallelJumpTableEntryInfo.flags(a0,d3.l); was this an island?
bz.s @next ; no, go on to the next
move.l JumpTableEntry.address(a0),a1 ; yes, we cleared the island bit, point at island
move.l JumpTableEntry.address(a1),JumpTableEntry.address(a0) ; jam in the real address found in the island
@next
addq.l #JumpTableEntry.size,a0 ; advance to the next jump table entry
dbra d1,@loop ; loop through the whole jump table
;————————————————————————————————————————————————————————————————————————————————————————————————————
TellDebuggerToLookAtJumpTable
; call the debugger if he is here, and notify him that we just nailed everything down
; debugger interface is:
; d5.w -> number of jump table entries
; d6.l -> pointer to patch block
; 4(a5) -> first jump table entry (jumpTable global)
; -4(a5) -> end of patch block (residentTop global)
clr.w NewLoaderActive ; tells whip new loader is active
move.l LinkedPatchDebuggerPtr,d0 ; is the debugger here?
bmi.s @done
move.l d0,a0
jsr (a0) ; call the debugger
@done
;————————————————————————————————————————————————————————————————————————————————————————————————————
InstallAndExecute
; run through installing all the patches and executing all of the install routines
move.l PatchResourceEntry.ptr(sp),a2 ; point at the packed patch table
lea InstallOrExecute,a1
bsr ForPatchTable ; install each triggered patch in the table
;————————————————————————————————————————————————————————————————————————————————————————————————————
Done
; dispose all the handles left on the stack
@next
tst.w PatchResourceEntry.endOfList(sp) ; is this the end?
bz.s @done
; The System version of the linked patch loader disposes of the 'lpch' handles
; after copying the data above the boot globals so that there would not be
; two copies of the linked patches floating around in the system heap.
if INITVERSION then ; <55> we only use handles for the Init Version
move.l PatchResourceEntry.resourceHandle(sp),a0 ; <55> get rid of the resource
_DisposeHandle
endif
lea PatchResourceEntry.size(sp),sp ; pop the rest of the entry
bra.s @next
@done
addq.l #2,sp ; pop off the word that flags end of table
lea -g.offset(a5),a0 ; get pointer to the jump table block
_DisposePtr ; dispose the jump table block
if NOT INITVERSION then
move.l (sp)+,d0 ; <55> Restore the original value of <55> FM ;restore MemTop
move.l d0,MemTop ; <55> MemTop that we saved earlier <55> FM
endif
movem.l (sp)+,LoadLinkedPatchesRegisters ; restore all of the registers
rts
endproc
;————————————————————————————————————————————————————————————————————————————————————————————————————
ROMVersions proc
macro
EmitROMVersions
lcla &index
; for each ROM, emit the version
&index: seta NumROMs$
while &index > 0 do
&index: seta &index - 1
dc.w ROMVersion$&index
endwhile
endm
EmitROMVersions
dc.w 0
endproc
;————————————————————————————————————————————————————————————————————————————————————————————————————
ForPatchTable proc
; Traverse the patch table, calling the passed function for each patch that is triggered.
;
; In:
; g.romNumber(a5) number of the ROM we are running on
; g.nonROMConditions(a5) mask of other conditions
;
; a2 start of the patch table
; a1 function to be called for each patch routine
; In:
; a0 pointer to jump table entry
; d0.w trap number
; ccr result of tst.w d0
; a5 globals
; Out:
; can trash d0-d7/a0-a6
; Out:
; a2 points right *after* the condition table
;
; trashes d0-d2/a0-a1
;
; A reminder about the format of the packed patch table:
;
; Patches are grouped in sets of patches which have the same loading conditions. Each
; set is preceded by the load condition (1 to 4 bytes), followed by offsets for each
; entry, with the following encoding:
;
; 0-253 number of entries until the next patch
; 254 end of this set of patches, new load condition and another set follows
; 255 two bytes follow with the number of entries until the next patch (0 for end of table)
;
; After each offset is the trap number, which is two bytes. A trap number of 0 indicates an install proc.
; The table ends with a 255 followed by two bytes of 0.
ForPatchTableRegisters reg d3-d7/a3-a4/a6
ForPatchTableFunctionRegisters reg d2/a0-a2/a5
movem.l ForPatchTableRegisters,-(sp)
lea g.jumpTable(a5),a0 ; start out pointer at first jump table entry
NextCondition
; for each condition, check if it is active and skip it if not
if NumConditions$ <= 8 then
move.b (a2)+,d0 ; get the conditions (1 byte)
elseif NumConditions$ <= 16 then
move.b (a2)+,d0 ; get the conditions (2 bytes)
lsl.w #8,d0 ; push it up
move.b (a2)+,d0 ; get the next byte
elseif NumConditions$ <= 24 then
move.b (a2)+,d0 ; get the conditions (3 bytes)
swap d0 ; push it up
move.b (a2)+,d0 ; get the next byte
lsl.w #8,d0 ; push it up
move.b (a2)+,d0 ; get the next byte
else
move.b (a2)+,d0 ; get the conditions (4 bytes)
lsl.w #8,d0 ; push it up
move.b (a2)+,d0 ; get the next byte
swap d0 ; push it up
move.b (a2)+,d0 ; get the next byte
lsl.w #8,d0 ; push it up
move.b (a2)+,d0 ; get the next byte
endif
; the ROM must be one of the ROMs specified in the mask
move.w g.romNumber(a5),d1 ; get the ROM number
btst.l d1,d0 ; is the condition true for this ROM?
bz.s ConditionFalse ; no, condition not met
; all conditions specified must be true
if NumConditions$ <= 8 then
and.b #NonROMConditionsMask$,d0 ; conditions that must be true (1 byte)
move.b g.nonROMConditions(a5),d1 ; conditions that are true
and.b d0,d1 ; dont care about conditions not in the check word
cmp.b d0,d1 ; if all are true, then we should load
elseif NumConditions$ <= 16 then
and.w #NonROMConditionsMask$,d0 ; conditions that must be true (2 bytes)
move.w g.nonROMConditions(a5),d1 ; conditions that are true
and.w d0,d1 ; dont care about conditions not in the check word
cmp.w d0,d1 ; if all are true, then we should load
else
and.l #NonROMConditionsMask$,d0 ; conditions that must be true (3 or 4 bytes)
move.l g.nonROMConditions(a5),d1 ; conditions that are true
and.l d0,d1 ; dont care about conditions not in the check word
cmp.l d0,d1 ; if all are true, then we should load
endif
beq.s ConditionTrue ; required conditions are all met, go load
ConditionFalse
moveq #0,d2 ; flag condition false
bra.s NextPatch
ConditionTrue
moveq #1,d2 ; flag condition true
NextPatch
moveq #0,d1 ; get number of jump table entries until next patch
move.b (a2)+,d1 ; from patch table
cmp.b #254,d1 ; check for special opcodes
bhi.s @word ; 255 means number of entries is word sized, get it
beq.s NextCondition ; 254 means end of this set of patches, get next condition
@gotDistance
add.w d1,d1 ; number of entries * 2
add.l d1,a0 ; advance in the jump table
add.w d1,d1 ; number of entries * 4
add.l d1,a0 ; advance in the jump table (entries are 6 bytes)
move.b (a2)+,d0 ; get high byte of trap number
lsl.w #8,d0 ; push it up
move.b (a2)+,d0 ; get low byte of trap number
tst.b d2 ; was condition true?
beq.s NextPatch ; no, continue with the next patch
tst.w d0 ; set up condition codes, testing for install proc
movem.l ForPatchTableFunctionRegisters,-(sp)
jsr (a1) ; call the function (can trash anything)
movem.l (sp)+,ForPatchTableFunctionRegisters
bra.s NextPatch ; continue with the next patch
; we got a special opcode saying that current entry in packed patch table is word length
@word
move.b (a2)+,d1 ; get high byte of word
lsl.w #8,d1 ; move into the high byte
move.b (a2)+,d1 ; get low byte of word
tst.w d1 ; test whole word
bz.s Done ; 0 means end of table
bra.s @gotDistance
Done
movem.l (sp)+,ForPatchTableRegisters
rts
endproc
;————————————————————————————————————————————————————————————————————————————————————————————————————
proc
entry FindOrMakeResident
entry MakeResident
entry MakeNonResident
entry FindResident
import CheckPatchTableRange
;————————————————————————————————————————————————————————————————————————————————————————————————————
MakeResidentConservative
; Fixes the routine down in resident space, only if all modules referenced
; in the routine can be nailed down first. Note that this will always happen,
; except when there are cycles in the reference graph.
moveq #0,d7 ; flag resident
moveq #0,d6 ; flag Make, not Find
moveq #1,d5 ; flag conservative
bra.s MakeFindResidentAnyPoliticsCommon
;————————————————————————————————————————————————————————————————————————————————————————————————————
FindOrMakeResident
; Do a FindResident if the CCs say zero, or a MakeResident otherwise.
;
; This comes in handy when iterating through jump table entries in the patch table, because the
; trap number is tested just before calling here, and a trap number of 0 means install code, which
; should be FoundResident, while any other trap number means a patch, which should be MadeResident.
bz.s FindResident
; fall into MakeResident
;————————————————————————————————————————————————————————————————————————————————————————————————————
MakeResident
; Fixes the routine down in resident space. No matter what (i.e. non-conservative)
; even if some of its references arent nailed down yet. This takes care of cycles
moveq #0,d7 ; flag resident
bra.s MakeResidentCommon
;————————————————————————————————————————————————————————————————————————————————————————————————————
MakeNonResident
; Fixes the routine down in non-resident space. No matter what (i.e. non-conservative)
; This is used by install routines for their non resident entries
moveq #1,d7 ; flag non-resident
; fall into MakeResidentCommon
;————————————————————————————————————————————————————————————————————————————————————————————————————
MakeResidentCommon
moveq #0,d6 ; flag Make, not Find
bra.s MakeFindResidentCommon
;————————————————————————————————————————————————————————————————————————————————————————————————————
FindResident
; Makes all “resident” references in a routine resident.
moveq #0,d7 ; flag resident (when we get down a level)
moveq #1,d6 ; flag Find, not Make
; fall into MakeFindResidentCommon
;————————————————————————————————————————————————————————————————————————————————————————————————————
MakeFindResidentCommon
moveq #0,d5 ; not conservative
; fall into MakeFindResidentAnyPoliticsCommon
;————————————————————————————————————————————————————————————————————————————————————————————————————
MakeFindResidentAnyPoliticsCommon
; Now begins the one routine that does it all:
;
; Make/Find[Non]Resident[Conservative]
;
; or, to be more explicit:
;
; FindResident
; MakeResident
; MakeResidentConservative
; MakeNonResident
;
; In:
; g.jumpTable(a5) pointer to start of jump table
; g.residentTop(a5) pointer to next available resident code
;
; a0 pointer to jump table entry
; a5 globals
; d7 0 means resident, 1 means non-resident
; d6 0 means make, 1 means find
; d5 0 means non-conservative, 1 means conservative
;
; Out:
; g.residentTop(a5) pointer to next available resident code
;
; trashes d0-d7/a0-a3
;
; The algorithm for all these cases is similar. Here is a sketch:
;
; do nothing if this module is already nailed down (i.e. a jump instruction in its opcode field)
; do nothing if this module has its FindSeenFlagBit set AND we are doing a FindResident (to prevent cycles)
; do nothing if this module has its MakeSeenFlagBit set AND we are doing a Make[Non]Resident (to prevent cycles)
; walk back the jump table until you reach the beginning of the module
; if find
; set FindSeenFlagBit for this module and all the references in it to prevent cycles
; else (doing a make)
; if resident
; set MakeSeenFlagBit for this module and all the references in it to prevent cycles
; for each thing this module refers to, do a MakeResidentConservative
; unmark MakeSeenFlagBit for this module and all the references in it (it is no longer “half-nailed”)
; if conservative
; if not all modules made themselves resident, return
; make space in resident area for routine
; FoundFinalRestingPlace:
; fix up all jump table entries, now that we have found a final resting place
; if resident
; for each thing this module refers to that is too far away, make an island
;
; (then for both make and find)
; FollowReferences:
; for each thing this module refers to,
; if make
; do a Make[Non]Resident
; fix up the reference
; if find
; if the reference is marked resident, do a MakeResident
; <42> Before an attempt is made to nail down this module, see if the entry number for
; the module is in a range specified by the Gibblys patch table range list. If
; its not in any of the ranges specified by this list, dont nail it down.
;
CheckPatchNumber
move.l a0,d1 ; calculate jump table offset
sub.l a5,d1
subq.w #g.jumpTable,d1
divu.w #JumpTableEntry.size,d1 ; ÷6 jump table entries are 6 bytes
bsr CheckPatchTableRange ; See if patch is in range
tst.b d2 ; CheckPatchTableRange returns a condition in d2
bnz.s ShortCircuitActive ; Its in a range. Try to nail it down.
move.l JumpTableEntry.address(a0),d2 ; get address
bra Done
ShortCircuitActive
; do nothing if this module is already nailed down or if we hit a cycle (findSeen and makeSeen flag bits)
move.w JumpTableEntry.opcode(a0),d1 ; get the opcode
move.l JumpTableEntry.address(a0),d2 ; get address
cmp.w #kNailedDown,d1 ; already nailed down (JMP instruction)?
beq Done ; yes, we are done
move.l g.ParallelJumpTableEntryInfoTableOffset(a5),d3 ; get offset to entry table
tst.b d6 ; doing make or find?
bz @doingMake
btst #FindSeenFlagBit,ParallelJumpTableEntryInfo.flags(a0,d3.l) ; prevent cycles in find
bnz Done
bra.s WalkBackToProc
@doingMake
btst #MakeSeenFlagBit,ParallelJumpTableEntryInfo.flags(a0,d3.l) ; prevent cycles in make
bnz Done
WalkBackToProc
; walk back the jump table until you reach the beginning of the module
moveq #0,d0 ; make module size a long word
@next
move.w ParallelJumpTableEntryInfo.routineSize(a0,d3.l),d0 ; the beginning of the module (non-0 size)?
bnz.s @gotBeginning ; yes, found beginning
subq #ParallelJumpTableEntryInfo.size,a0 ; go back one jump table entry
bra.s @next
@gotBeginning
move.l JumpTableEntry.address(a0),a1 ; get pointer to start of this module
move.l a0,a2 ; save jump table entry for start of this module
moveq #0,d1
move.w ParallelJumpTableEntryInfo.referenceListHead(a0,d3.l),d1 ; get pointer to first reference inside module
; Check whether we are doing a find or a make and set the appropriate flag to prevent cycles
; Setting these flags could be considered “half-nailing” because we are trying to nail down
; the flagged entries but we cant finish until we have nailed down all the entrys references
MarkOurselvesHalfNailed
move.l a0,a3 ; go through our references
tst.b d6 ; find references?
bz @doingMake ; no, doing a make
; Set the FindSeenFlagBit for this module and all its entries so we wont get into an infinite loop later
@nextFindReference
bset #FindSeenFlagBit, ParallelJumpTableEntryInfo.flags(a3,d3.l) ; prevent cycles in find
addq.l #ParallelJumpTableEntryInfo.size,a3 ; point to the next jump table entry
tst.w ParallelJumpTableEntryInfo.routineSize(a3,d3.l) ; is this a new module (non-0 size)?
bz.s @nextFindReference ; no, still an entry within the current module
bra FollowReferences ; yes, check to see which of these references
; are marked resident
@doingMake
tst.b d7 ; resident or non-resident?
bnz.s FoundFinalRestingPlace ; non-resident, no need to move it out of the 'lpch'
; Set the MakeSeenFlagBit for this module and all its entries so we wont get into an infinite loop later
@next
bset #MakeSeenFlagBit, ParallelJumpTableEntryInfo.flags(a3,d3.l) ; prevent cycles in make
addq.l #ParallelJumpTableEntryInfo.size,a3 ; point to the next jump table entry
tst.w ParallelJumpTableEntryInfo.routineSize(a3,d3.l) ; is this a new module (non-0 size)?
bz.s @next ; no, still an entry within the current module
LureInTheConservatives
; for each thing this module refers to, do a MakeResidentConservative
; also keep track to see that all is nailed down. If a referenced entry
; is not nailed down we return an error by clearing d4
moveq #1,d4 ; assume for now that all will be nailed down
lea @doOne,a3 ; lets go makin
bsr ForAllReferences ; get those evil right wingers in memory
bra.s @continue
; LureInTheConservatives iterator
; this routine is iterated over each entry referenced by the current module
; it attempts to nail down as many of the references as possible by calling MakeResidentConservative
@doOne
movem.l d4/a0,-(sp) ; remember him
bsr.s MakeResidentConservative ; go lock him in
movem.l (sp)+,d4/a0
cmp.w #kNailedDown,JumpTableEntry.opcode(a0) ; did he get nailed?
beq.s @ok
clr.b d4 ; nope, we have a cycle in the graph, gentlemen
@ok
rts
@continue
MarkOurselvesNotHalfNailed
; we can clear our MakeSeenFlagBit now that we are done luring in the conservatives
; we MIGHT end up recursing later in “MakeIslands” but by that time we will be nailed
; down and the protection of the MakeSeenFlagBit will be unecessary
move.l a0,a3 ; go through our references
move.l g.ParallelJumpTableEntryInfoTableOffset(a5),d3 ; offset to parallel info list
@next
bclr #MakeSeenFlagBit, ParallelJumpTableEntryInfo.flags(a3,d3.l) ; clear the flag bit
addq.l #ParallelJumpTableEntryInfo.size,a3 ; point to the next entry
tst.w ParallelJumpTableEntryInfo.routineSize(a3,d3.l) ; is this a new module (non-0 size)?
bz.s @next ; no, still an entry within the current module
ExitIfConservativeFailure
; if were conservative, and we couldn't make all our references resident, then fail
tst.b d5 ; are we conservative?
bz.s @survive ; no, we survive no matter what
; if not all modules made themselves resident, return
tst.b d4 ; anyone blow it?
bnz @survive ; no, continue
bra Done
@survive
MoveResident
; make space in resident area for routine, and move it there
move.l a1,a0 ; point to the module
move.l g.residentTop(a5),a1 ; get place to put it in resident area
add.l d0,g.residentTop(a5) ; advance past it
_BlockMove
FoundFinalRestingPlace
; fix up all jump table entries, now that we have found a final resting place
; this is either in the resident block or in the code of the 'lpch' if non-resident
move.l g.ParallelJumpTableEntryInfoTableOffset(a5),d3 ; offset to parallel jump table
move.l a1,d0 ; calculate fixup
sub.l JumpTableEntry.address(a2),d0 ; by subtracting offset of start of module
add.l d0,d2 ; save away the resident address for the entry
; were looking at (we return this later)
@next
add.l d0,JumpTableEntry.address(a2) ; convert address in non-resident space to a real address
move.w #kNailedDown,JumpTableEntry.opcode(a2) ; mark as nailed down (this is a JMP opcode)
addq.l #JumpTableEntry.size,a2 ; point to the next entry
tst.w ParallelJumpTableEntryInfo.routineSize(a2,d3.l) ; is this a new module (non-0 size)?
bz.s @next ; no, keep looping
MakeIslands
; if module is resident, for each thing the module refers to that is too far away, make an island
tst.b d7 ; resident or non-resident?
bnz.s FollowReferences ; non-resident, no need for islands
lea @doOne,a3 ; lets go to the islands
bsr ForAllReferences ; make em
bra.s @continue
; MakeIslands iterator
; This routine is iterated over each entry referenced by the current module.
; It attempts to make an island in the resident block for references more than 32k away.
; Note that an island is the same as a nailed down jump table entry, i.e. JMP address.
@doOne
cmp.b #kAbsoluteOpcode*2,d0 ; never make islands for absolute references
beq.s @done
cmp.w #kNailedDown,JumpTableEntry.opcode(a0) ; a backward reference (already nailed)?
beq.s @backRef ; yes, can only make an island for backward refs.
bra @done
@backRef
move.l g.ParallelJumpTableEntryInfoTableOffset(a5),d3 ; offset to parallel info list
move.l JumpTableEntry.address(a0),a4 ; get address of routine
move.l a4,a2
sub.l a1,a2 ; get offset from here to routine
cmpa.w a2,a2 ; is it in range? (warning! 68000 hack!)
beq.s @done ; yes, no need for an island
; mark as island, and if we already had an island, get real address from the island itself
bset #islandFlagBit,ParallelJumpTableEntryInfo.flags(a0,d3.l)
bz.s @gotAddress ; island flag wasnt set, we have the real address
move.l 2(a4),a4 ; extract the address from the island itself
; well be making an island to an island
@gotAddress
; lay down the island, and point jump table at it
move.l g.residentTop(a5),a2 ; point to new place for island
move.l a2,JumpTableEntry.address(a0) ; store island address in the jump table
move.w #kNailedDown,JumpTableEntry.opcode(a2) ; lay down a jump
move.l a4,JumpTableEntry.address(a2) ; to the real routine address
addq.l #JumpTableEntry.size,a2
move.l a2,g.residentTop(a5) ; island is allocated now
@done
rts
@continue
; executed for both finds and makes
FollowReferences
; Recurse calling FindResident or MakeResident for each entry referenced by the current module.
lea @doOne,a3 ; recurse over the references
bsr ForAllReferences
tst.b d6 ; doing find?
bz @continue ; no, its a make
; if we were doing a find we need to clear our FindSeenFlagBit now
move.l a0,a3 ; yes, go through our references
move.l g.ParallelJumpTableEntryInfoTableOffset(a5),d3 ; offset to parallel info list
@next
bclr #FindSeenFlagBit, ParallelJumpTableEntryInfo.flags(a3,d3.l) ; clear the flag bit
addq.l #ParallelJumpTableEntryInfo.size,a3 ; point to the next entry
tst.w ParallelJumpTableEntryInfo.routineSize(a3,d3.l) ; is this a new module (non-0 size)?
bz.s @next ; no, still an entry within the current module
bra.w @continue
; FollowReferences iterator
; This routine is iterated over each entry referenced by the current module.
; Recurses doing a FindResident or MakeResident on each entry.
; Finally, we fix up the reference inside the code itself.
@doOne
; recurse with either FindResident or MakeResident
movea.l a0,a3 ; save address of jump table entry (keep carry flag)
@registers reg d0/d2/d3/d6/a1/a3
; if we are doing a FindResident, and this is a force-resident reference
; we have found something that we want to MakeResident, so recurse with a MakeResident
; on it instead of recursing with a FindResident
movem.l @registers,-(sp) ; keep carry flag
bcc.s @noForce ; reference not marked force-resident, skip over this
moveq #0,d6 ; force use to make resident, instead of just finding
@noForce
bsr MakeFindResidentCommon ; make this resident, or find more resident references
movem.l (sp)+,@registers
; in find case, do nothing else
tst.b d6 ; is this a find (not make)?
bnz.s @done ; yes, our job is done
; now, let us fix up the reference (at long last)
move.l g.ParallelJumpTableEntryInfoTableOffset(a5),d3 ; offset to parallel info list
cmp.b #kAbsoluteOpcode*2,d0 ; is it a 32-bit reference?
beq.s @32Bit
tst.b d7 ; non-resident?
bnz.s @nonResident ; yes, go handle it that way
; take special case of LEA or PEA to island into account (need address, not JMP opcode)
btst #islandFlagBit,ParallelJumpTableEntryInfo.flags(a3,d3.l)
bz.s @noOffset ; some island opcodes need offsets
add.w IslandOffsets(d0.w),a0 ; point to address instead of to JMP in island
@noOffset
; now check the distance
move.l a0,a2 ; get address of routine
sub.l a1,a2 ; get offset from here to routine
cmpa.w a2,a2 ; is it in range? (warning! 68000 hack!)
beq.s @ok ; yes, everything is OK
; Sometimes we can use the real thing instead of the island and it wont be out of range.
btst #islandFlagBit,ParallelJumpTableEntryInfo.flags(a3,d3.l)
bz.s @notOK ; if not island then we cant try indirection
move.l 2(a0),a0 ; get what the island points to
move.l a0,a2 ; get address of routine
sub.l a1,a2 ; get offset from here to routine
cmpa.w a2,a2 ; is it in range? (warning! 68000 hack!)
bne.s @notOk ; no, everything is notOK
move.w a2,(a1) ; store the offset
bra.s @notIsland ; continue with a non-island address
@notOk
moveq #dsLinkedPatchReferenceTooFar,d0 ; yes, big problem
_SysError
@ok
; now store the offset, and then the opcode
move.w a2,(a1) ; store the offset
btst #islandFlagBit,ParallelJumpTableEntryInfo.flags(a3,d3.l)
bnz.s @island ; yes, go handle it
@notIsland
move.w PCRelativeOpcodes(d0.w),-(a1) ; store the opcode
@done
rts
@island
move.w IslandOpcodes(d0.w),-(a1) ; store the opcode (use MOVE instead of LEA)
rts
@nonResident
add.w A5RelativeOffsets(d0.w),d2 ; turn jump table offset into A5 offset
move.w d2,(a1) ; store the offset
move.w A5RelativeOpcodes(d0.w),-(a1) ; store the opcode
rts
@32Bit
btst #islandFlagBit,ParallelJumpTableEntryInfo.flags(a3,d3.l)
bz.s @noIsland ; dont have to follow the island
move.l 2(a0),a0 ; get address of real thing from the island
@noIsland
move.l a0,-2(a1) ; store the address as a long absolute
rts
@continue
Done
; we are done, return the resident address
move.l d2,a0 ; result
rts
IslandOffsets
dcb.w 9,2 ; address for MOVE.L
dcb.w 2,0 ; opcode for JSR, JMP
PCRelativeOpcodes
dc.w $41FA,$43FA,$45FA,$47FA,$49FA,$4BFA,$4DFA,$4FFA,$487A ; LEA A0-A7, PEA
dc.w $4EBA,$4EFA ; JSR, JMP
IslandOpcodes
dc.w $207A,$227A,$247A,$267A,$287A,$2A7A,$2C7A,$2E7A,$2F3A ; MOVEA.L A0-A7, -(SP)
dc.w $4EBA,$4EFA ; JSR, JMP
A5RelativeOpcodes
dc.w $206D,$226D,$246D,$266D,$286D,$2A6D,$2C6D,$2E6D,$2F2D ; MOVEA.L A0-A7, -(SP)
dc.w $4EAD,$4EED ; JSR, JMP
A5RelativeOffsets
dcb.w 9,g.jumpTable+JumpTableEntry.address ; address for MOVE.L
dcb.w 2,g.jumpTable+JumpTableEntry.opcode ; opcode for JSR, JMP
;————————————————————————————————————————————————————————————————————————————————————————————————————
ForAllReferences
; Traverse the reference list, calling the passed function for each reference.
;
; In:
; a5 pointer to the globals (including the jump table)
; d1.l reference list head (offset into routine); 1 means do nothing
; a1 pointer to beginning of module
; d4/d6 values to be passed through
; a3 function to be called repeatedly
; In:
; a0 jump table entry of this reference
; d0.w opcode, expressed as an offset into a table of words
; a1 pointer to code (after opcode)
; d2.w jump table offset
; ccr carry set if this is a “force-resident” reference
; d4 passed through (in and out)
; d5-d7 passed through
; trashes d0-d7/a0-a4
;
; trashes d4
;
; A reminder about the format of references within modules:
;
; Before they are fixed up, references are 4 bytes as follows.
;
; +---------------------------------
; 2 bytes | F | offset to next reference |
; +---------------------------------
; +---------------------------------
; 2 bytes | OPCD | jump table entry number |
; +---------------------------------
;
; The first two bytes consist of a flag (F) in the first bit (1 for force-resident, 0 otherwise),
; followed by the offset to the next reference in the module, in words.
;
; The second two bytes consist of 4 bits (OPCD) corresponding to the opcode number, followed by
; 12 bits corresponding to the jump table entry number for this reference (limiting the number
; of jump table entries to 4096 max).
;
ForAllReferencesRegisters reg d0-d3/a0-a2/a4
ForAllReferencesFunctionRegisters reg d1/d5-d7/a1/a3
movem.l ForAllReferencesRegisters,-(sp)
bclr #0,d1 ; head of list can be 0 (1 means no references)
bz.s @skipTest ; if low bit is clear, skip the check
@next
tst.w d1 ; 0 means the end of the reference list
bz.s @done
@skipTest
add.l d1,a1 ; advance to item in reference list
move.w (a1),d1 ; get offset to next reference in module
move.w 2(a1),d0 ; get opcode number and jump table entry number
lea g.jumpTable(a5),a0 ; point to jump table
move.w d0,d2 ; calculate jump table entry number
lsl.w #4,d2 ; clear high 4 bits (opcode number)
lsr.w #3,d2 ; x2 (jump table entries are 6 bytes)
add.w d2,a0 ; advance in the jump table
move.w d2,d3 ; d3 = entry number * 2
add.w d2,d2 ; x4 (jump table entries are 6 bytes)
add.w d2,a0 ; advance in the jump table
add.w d3,d2 ; d3 = entry number * 6 for function call
lsr.w #8,d0 ; calculate offset into opcode table
lsr.w #4,d0 ; shift by 12 (roll high 4 bits down to the bottom)
add.w d0,d0 ; make into opcode table offset
add.w d1,d1 ; calculate offset to next reference (which was in words)
; and test the high bit (force-resident flag)
movem.l ForAllReferencesFunctionRegisters,-(sp)
addq #2,a1 ; point after opcode, before operand
jsr (a3) ; call the iterator
movem.l (sp)+,ForAllReferencesFunctionRegisters
bra.s @next
@done
movem.l (sp)+,ForAllReferencesRegisters
rts
endproc
;————————————————————————————————————————————————————————————————————————————————————————————————————
proc
export InstallOrExecute
import MakeNonResident
import GetPatchAddress
import SetPatchAddress
import FlushInstructionCache
import CheckPatchTableRange
InstallOrExecute
; Installs a patch or executes an install routine.
;
; In:
; a0 pointer to jump table entry
; d0.w trap number
; ccr z for install routine, nz for patch
;
; trashes d0-d7/a0-a6
; debugging code to break before installing specified jump table entries
;
; <62> This code used to be turned off for linked patch INITs, and we couldnt figure out
; why so we turned it on. If someone figures out why this was turned off for INITs,
; change it back, then come yell at us for not knowing any better.
move.l a0,d1 ; calculate jump table offset
sub.l a5,d1
subq.w #g.jumpTable,d1
divu.w #JumpTableEntry.size,d1 ; ÷6 jump table entries are 6 bytes
move.w d1,StashJumpTableEntryNum ; stash the jump table entry number
; <39> The conditions for this patch have been met. Check the patch number against the ranges
; in the patch table to determine if it should be installed or not.
bsr CheckPatchTableRange ; <39> See if patch is in range
tst.b d2 ; <39> CheckPatchTableRange returns a condition in d2
bz.s Done ; <39> Zero means dont load the patch.
; Chris, Greg and I always have trouble how to use these globals, so Im adding comments
; about it so we dont have to do this by trial and error any more.
;
; To stop on a particular linked patch entry:
; StopOnJumpTableEntryNum ($922) = linked patch entry to stop on
; StopOnJumpTableEntryNumHighest ($924) = -1
;
; To stop on a range of linked patch entries
; StopOnJumpTableEntryNum ($922) = lower end of range of entries to break on
; StopOnJumpTableEntryNumHighest ($924) = upper end of range of entries to break on
tst.w StopOnJumpTableEntryNumHighest ; should we test a range?
bmi.s @notRange
cmp.w StopOnJumpTableEntryNum,d1
bhs.s @stop ; stop if we are <= target
cmp.w StopOnJumpTableEntryNumHighest,d1
bls.s @stop ; stop if we are >= target
bra.s @dontStop
@notRange
cmp.w StopOnJumpTableEntryNum,d1
bne.s @dontStop ; stop if we hit the target entry <31>
@stop
_Debugger
@dontStop
tst.w d0
; end of debugging code (have to do the tst.w since we screwed up the condition codes)
bnz.s InstallPatch ; its a patch, go install it
ExecuteInstallRoutine
; execute an install routine
bsr MakeNonResident ; nail down install routine and references
bsr FlushInstructionCache ; this better not trash a0
if INITVERSION then
move.l TheZone,-(sp) ; save the current zone
move.l SysZone,TheZone ; set to the System heap
jsr (a0) ; call it
move.l (sp)+,TheZone ; restore the zone
rts
else
jmp (a0) ; call it
endif
InstallPatch
; install a patch
move.l g.ParallelJumpTableEntryInfoTableOffset(a5),d3 ; parallel entry table offset in d3
move.l a0,a1 ; keep jump table pointer here
move.w d0,d6 ; save trap number
FixOld
; find magic cookies and replace them with the old patch address
bsr.s GetPatchAddress ; get the old address of this patch
move.l a0,d4 ; keep it here to fix up
move.l JumpTableEntry.address(a1),a0 ; get the address of the patch routine
move.l a0,a2 ; and get ready to traverse it
move.w ParallelJumpTableEntryInfo.routineSize(a1,d3.l),d1 ; get the size of the patch routine
lsr.w #1,d1 ; turn the size into a DBRA count (words)
subq.w #2,d1 ; 1 for DBRA, 1 for last word (cookie is long)
blo.s @done ; routine very small
move.w #(OldTrapAddressMagicCookie >> 16),d2 ; look for high word of magic cookie
move.w #(OldTrapAddressMagicCookie and $FFFF),d3 ; look for low word of magic cookie
@next
cmp.w (a2)+,d2 ; did we find it?
@compared
dbeq d1,@next ; go until we do
bne.s @done
cmp.w (a2),d3 ; look for the low word
bne.s @compared ; nope, keep going
move.l d4,-2(a2) ; do a fixup right here
bra.s @compared ; keep going
@done
SetNew
; point at the new patch address
move.w d6,d0
bsr.w SetPatchAddress ; point at the new address of this patch
Done
rts
endproc
;————————————————————————————————————————————————————————————————————————————————————————————————————
GetPatchAddress proc
; Get the address of a trap or vector. (Similar interface to GetTrapAddress.)
;
; In:
; d0.w trap number (or vector number)
; Out:
; a0 address of trap or vector
;
; trashes d0/a0
cmp.w #$A000,d0 ; is it a trap?
blo.s @notTrap
_GetTrapWordAddress
rts
@notTrap
move.w d0,a0 ; get vector address in address register
cmp.w #$4000,a0 ; are we getting a vector, or ExpandMem offset?
blo.s @vector ; getting vector
@expandMem
sub.w #$4000,a0 ; change into an offset
add.l ExpandMem,a0 ; point to place within the ExpaneMem block
@vector
move.l (a0),a0 ; get old value from vector
rts
endproc
;————————————————————————————————————————————————————————————————————————————————————————————————————
SetPatchAddress proc
import FlushInstructionCache
; Set the address of a trap or vector. (Similar interface to SetTrapAddress.)
;
; In:
; d0.w trap number (or vector number)
; a0 address to set to
;
; trashes d0/a0
cmp.w #$A000,d0 ; is it a trap?
blo.s @notTrap
_SetTrapWordAddress
rts
@notTrap
ext.l d0 ; make vector address long
exg d0,a0 ; put vector address in address register
cmp.w #$4000,a0 ; are we setting a vector, or ExpandMem offset?
blo.s @vector ; setting vector
@expandMem
sub.w #$4000,a0 ; change into an offset
add.l ExpandMem,a0 ; point to place within the ExpaneMem block
@vector
move.l d0,(a0) ; put new address into vector
exg d0,a0 ; put everything back
bsr.s FlushInstructionCache ; this better not trash a0
rts
endproc
;————————————————————————————————————————————————————————————————————————————————————————————————————
FlushInstructionCache proc
; Flush the instruction cache, if there is one.
; This must be called after code that is modified in RAM.
;
; trashes no registers
movem.l d0-d2/a0-a1,-(sp)
if INITVERSION then
; *** we really only need to check if CacheFlush is implemented for pre-7.0 systems
; *** someday, we wont have to support these systems even with linked patch INITs
move.w #$9f,d0
_GetTrapAddress ; Get _Unimplemented
cmp.l jCacheFlush,a0 ; is CacheFlush implemented?
beq.s @exitFlushInstructionCache ; No, return.
endif
move.l jCacheFlush,a0 ; call through this vector
jsr (a0)
@exitFlushInstructionCache ; <20> mrr
movem.l (sp)+,d0-d2/a0-a1
rts
endproc
;————————————————————————————————————————————————————————————————————————————————————————————————————
CheckPatchTableRange Proc Export
;
; <39> Check the patch count index against the ranges stored in the patch range table. Dont load the
; patch if it isnt in one of the ranges.
;
; In: D1: Jump table entry number
;
; Out: D2: Zero if entry is in a range, non zero if not.
;
; Format of the patch range table (pointed to by g.patchRangeTable) is:
;
; 2 bytes version
; 2 bytes number of ranges that follow - 1
;
; for each range:
;
; 2 bytes entry number of first patch count index to load
; 2 bytes entry number of last patch count index to load
;
; note: ranges must be sorted in ascending order
;
movem.l a0/d0-d1,-(sp) ; Save off registers that are used
move.l g.patchRangeTable(a5),a0 ; Get pointer to patch table ranges
addq #2,a0 ; Skip past version
move.w (a0)+,d0 ; Get range count
bmi.s @dontLoadPatch ; <44> If negative, there are no ranges in this list.
moveq #1,d2 ; Assume patch should be loaded
@checkRangeLoop
cmp.w (a0)+,d1 ; Check against lower bound
blt.s @dontLoadPatch ; If lower, dont load
cmp.w (a0)+,d1 ; Check against upper bound
bls.s @loadPatch ; Patch should be loaded
dbra d0,@checkRangeLoop ; Loop through all the ranges
@dontLoadPatch
moveq #0,d2 ; If patch was not in any range, dont load it
@loadPatch
movem.l (sp)+,a0/d0-d1 ; Restore everything that was used except for D2
rts
EndProc
;————————————————————————————————————————————————————————————————————————————————————————————————————
TrapImplemented proc ; <31>
; Pass in a trap address in a0 and figure out whether it is implemented.
; If eq, the trap is not implemented.
; Trashes no registers.
movem.l d0/a0-a1,-(sp)
move.l a0,a1
move.w #kUnimplementedTrap,d0 ; get address of unimplemented trap
_GetTrapAddress
cmp.l a0,a1 ; see if trap is implemented
movem.l (sp)+,d0/a0-a1
rts
endproc
;————————————————————————————————————————————————————————————————————————————————————————————————————
MrGestalt proc ; <31>
; Call Gestalt, and get the result into d0.
; Return 0 for result if Gestalt or selector is unknown.
; This wont work for all Gestalt selectors, but it will for most.
; Trashes d0 only.
move.l a0,-(sp)
move.l d0,-(sp)
move.w #kGestaltTrap,d0
_GetTrapAddress newOS ; get address of Gestalt
jsr TrapImplemented
beq.s @dropStackReturn0 ; no Gestalt, return 0
move.l (sp)+,d0 ; avoid trashing the condition codes
_Gestalt
bne.s @return0 ; on error, return 0
move.l a0,d0 ; put result into d0
bra.s @done
@dropStackReturn0
addq #4,sp ; drop value of selector
@return0
move.l #0,d0 ; always use 0 for result
@done
movea.l (sp)+,a0 ; avoid trashing the condition codes
rts
endproc
;————————————————————————————————————————————————————————————————————————————————————————————————————
;
; <49> For linked patch INITs trying to run on Systems before Cube-E, keep this patch range
; table around in case they dont have a 'ptbl' resource handy.
;
if INITVERSION then ; <49>
StaticRangeTable Proc Export ; <49>
dc.w $0001 ; <49> Version
dc.w $0000 ; <49> Range count - 1
dc.w $0000 ; <49> First linked patch entry in range to load
dc.w $FFFF ; <49> Last entry to load
EndProc ; <49>
endif ; <49>
end