! Mouse loader ! (c) RWT July 1988 ! This file contains the FE02 linking loader, which is used ! both during initial bootstrapping of the system, but also ! later on when the system is up and running. ! The heap package is here too because the loader needs it. ! Those Imp IO routines which need to be accessed GLA-lessly ! (i.e. as extracode/system routines) are also in this file. %option "-low-nons-half-nodiag-nocheck-nostack-nowarn" %include "nmouse.inc-nolist" ! A process's work space is a single contiguous region within which ! heap space is allocated from low addresses upwards while the stack ! grows downwards from high addresses. POA_HEAPBASE points at the ! (low) base address of this region, POA_STACKLIMIT points at the ! heap front, i.e. the lowest address not allocated to the heap (More ! precisely, because the Imp stack-check compares SP with POA_STACKLIMIT ! from time to time, this actually points 256 bytes further on, giving ! a small safety zone between the heap and stack fronts). ! ! Each heap cell is prefixed by a 4-byte header word, which is either ! negative (denoting a cell in use), positive (denoting a free cell), ! or zero (denoting the dummy cell at the end (front) of the heap). ! If the value is non-zero, its absolute value is the size of the cell ! including the 4 header bytes, this value is always a multiple of 4, ! even if the requested size was not. All heap chunks are adjacent, ! so POA_STACKLIM is equal to the sum of the sizes of all the cells, ! plus POA_HEAPBASE+256. %systemintegerfn freestore %result = freetop-freebot %if poa_heapbase=0 {still in boot phase} %result = a7-poa_stacklimit {normal mode} %end %constinteger unassign heap chunks = 1 %systemintegerfn heapget(%integer need) ! Allocation algorithm: ! Always search the entire heap from the beginning, amalgamating ! any adjacent free cells. Use the smallest cell big enough. If that ! cell is too big, split it in two, allocating only part of it. If no ! free cell is found, allocate fresh space by advancing the heap front. %integer bestsize=maxint,bestpos=0 %integer limit=poa_stacklimit-256 %integer pos=poa_heapbase %routine check space(%integer amount) %signal 2,1,amount,"not enough store" %if freestore0 {cell free - try to amalgamate} integer(pos) = integer(pos)+integer(pos+integer(pos)) - %while pos+integer(pos)0 %if bestsize>=integer(pos)>=need %start {better fit} bestsize = integer(pos); bestpos = pos %finish pos = pos+integer(pos) %finishelseexit {end of list, should not happen} %repeat pos = bestpos %if pos#0 %start {suitable hole found} %if bestsize-need>=8 %start {worth splitting} integer(pos+need) = bestsize-need %else need = bestsize {use it all} %finish %else {no hole found: extend front} check space(need+256) pos = limit poa_stacklimit = poa_stacklimit+need integer(pos+need) = 0 %finish integer(pos) = -need $if unassign heap chunks # 0 a0 = pos+need d0 = need>>2-2 *swap d0 o: *swap d0 i: *move.l d7,-(a0) *dbra d0,i *swap d0 *dbra d0,o $finish pos = pos+4 a0 = pos; %result = pos %end %systemroutine heapput(%integer where) ! Disposal algorithm: ! Try to amalgamate the cell being freed with any cells adjacent to ! it on the higher address side. Then, if the resulting cell is ! at the heap front, move back the front. Otherwise simply mark the ! cell as free by making its negative header word positive. %integer limit=poa_stacklimit-256,size %integer pos=where %returnif pos=0 {dispose NIL??} pos = pos-4 size = -integer(pos) %returnunless size>0 {already disposed??} size = size+integer(pos+size) %while - pos+size0 %if pos+size=limit %start size = 0 poa_stacklimit = pos+256 %finish integer(pos) = size %end %systemroutine DISPOSE (%name x) heapput(addr(x)) %unless x==nil %end %systemstring(*)%map NEW STRING (%string(*)%name s) ! Allocate a chunk just big enough to hold string S, then ! copy S into it and return a reference to the chunk. %string(*)%name h h == string(heapget(length(s)+1)) h = s; %result == h %end %systemrecord(fcb fm)%map NEW FCB (%string(*)%name f) ! Allocate a file control block of variable size, just big ! enough to hold the file name F (remember that the file ! name is the last field in the FCB), copy F into this ! field and clear the rest of the FCB. %integer n %record(fcb fm)%name fcb %integername p n = sizeof(fcb)-256 fcb == record(heapget(n+1+length(f))) ! ** must not say FCB = 0 ** p == integer(addr(fcb)) %cycle p = 0; p == p[1]; n = n-4 %repeatuntil n=0 fcb_filename = f %result == fcb %end ! FE02 Loader stuff %constinteger jmp=16_4EF9, lea a4=16_49F9, jsr = 16_4eb9, pea = 16_4879 %constinteger extbit=16_4000,procmask=16_3000, system=16_1000,external=16_2000,dynamic=16_3000 %predicate executable(%record(mar fm)%name m) ! True iff the file described by M is big enough to contain ! an FE02 header and that headder contains the magic number. %falseif m_size<=sizeof(m_header) %or m_header_tyver#16_fe02 %true %end %integerfn codestart(%record(fe02 header fm)%name h) ! Returns address of start of code section in a code file %result = addr(h)+sizeof(h)+h_export+h_import %end %record(fe02objectfm)%map next desc(%record(fe02objectfm)%name o) ! Given the variable size object descriptor O in the export or import ! section of a code file, return a reference to the following one. o == record((addr(o[1])-255+length(o_name)+1)&\1) o == nil %if o_flags=0 %result == o %end %systemintegerfn stringdiff (%string(*)%name a,b) ! Result is <=> zero iff a<=>b, ! but upper/lower case (etc) are treated as equivalent. %register(a0)%bytename p,q %register(d1)%integer m,n p == length(a); q == length(b) m = p-q %if m<=0 %then n = p %else n = q %cycle n = n-1; %result = m %if n<0 p == p[1]; q == q[1] %result = (p!32)-(q!32) %unless (p!32)-(q!32)=0 %repeat %end ! NB MAR_GLA values: ! zero: module not yet loaded ! odd: module cannot be loaded ! pos: module loaded, value = GLA ! neg: main program module not yet reset, value = -GLA, %integerfnspec load module(%record(mar fm)%name m) %systempredicate load object - (%record(fe02 object fm)%name object,%record(fe02 indir fm)%name ref) ! If the required OBJECT can be found in some module, ! then load that module if it is not already loaded, and ! fill in the particulars of the object in the REF record. ! This will involve accessing its first 4, 6, or 12 bytes. ! For a data object, REF_ADDRESS will point at it. ! For an %EXTERNAL procedure, REF will contain: LEA glabase,A4; ! JMP entrypoint. ! For a %SYSTEM procedure, REF will contain: JMP entrypoint. ! For a %DYNAMIC procedure, REF will contain: PEA object; ! JSR dynref. %record(marfm)%name m %record(fe02headerfm)%name h %record(fe02objectfm)%name o %integer flags %label dynref %predicate compatible(%record(fe02 object fm)%name want,have) ! If WANT is a data object, HAVE has to be too. ! If WANT or HAVE is %DYNAMIC, treat it as %EXTERNAL. ! If WANT is %EXTERNAL, HAVE may be %SYSTEM or %EXTERNAL. ! If WANT is %SYSTEM, HAVE must be %SYSTEM (it may be %EXTERNAL, ! but only if the module exporting it is GLA-less). ! In addition, the types of WANT and HAVE must agree, ! unless either of them is coded as zero (wildcard). %integer wt,ht wt = want_flags&procmask; wt = external %if wt=dynamic ht = have_flags&procmask; ht = external %if ht=dynamic %unless wt=ht %start %if wt=external %start %falseunless ht=system %elseif wt=system %falseunless ht=external %falseunless h_ownsize=0 %else %false %finish %finish wt = want_type; ht = have_type %unless wt=ht %start %falseunless wt=0 %or ht=0 %finish %true %end %if object_flags&procmask=dynamic %and ref_op1#pea %start ref_op1 = pea; ref_opd1 = addr(object) ref_op2 = jsr; ref_opd2 = addr(dynref) %true %finish flags = 0 m == poa_filelist %while m##nil %cycle {search all modules whether loaded or not} h == m_header o == record(addr(h[1])) o == nil %if m_gla&1#0 {module dodgy} - %ornot executable(m) - %or h_export=0 %while o##nil %cycle %if stringdiff(object_name,o_name)=0 %start {found it} %if compatible(object,o) %start {yes really} %if loadmodule(m)=0 %start {load the module} event_message = "*Cannot load " event_message = event_message.object_name %false %finish flags = object_flags&procmask flags = system %if o_flags&procmask=system - %or (o_flags&procmask=external %and h_ownsize=0) %if flags=system %start {6-byte entry sequence} ref_op1 = jmp; ref_opd1 = codestart(h)+o_offset %elseif flags=0 {data object} ref_address = m_gla+o_offset %else {12-byte entry sequence} ref_op1 = lea a4; ref_opd1 = m_gla ref_op2 = jmp; ref_opd2 = codestart(h)+o_offset %finish %true %finish flags = 'm' {mode or type mismatch} %finish o == nextdesc(o) {try next export in module} %repeat m == m_next {try next file} %repeat %if flags='m' %start event_message = "*Mismatch for " %else event_message = "*Cannot find " %finish event_message = event_message.object_name %false @0(a7)%integerarray r(0:14), %record(fe02indirfm)%name dref, %record(fe02objectfm)%name dobject dynref: *movem.l d0-d7/a0-a6,-(sp) dref == dref[-1] %if load object(dobject,dref) %and dref_op1#pea %start *movem.l (sp)+,d0-d7/a0-a6 *=16_4e74; *=4 {*rtd #4} %finish printstring(event_message); newline *movem.l (sp)+,d0-d7/a0-a6 *lea 8(sp),sp *clr.l d0 *rts %false %end %systemintegerfn load module(%record(mar fm)%name m) ! Load the specified module if it is not already loaded. ! This involves allocating space for its OWN area, calling ! its reset routine to initialise that area, and then satisfying ! any external references, filling in the gaps in the own area. ! Result is the main entry point PC, zero if unsuccessful. %integer gla,pos,ok %record(fe02 object fm)%name o %record(fe02 header fm)%name h == m_header %result = 0 %if m_gla&1#0 {dodgy} - %ornot executable(m) ok = h_main<<1+codestart(h) %result = ok %if m_gla>0 {already loaded %if m_gla<0 %start {GLA already allocated m_gla = -m_gla %elseif h_ownsize=0 {no GLA wanted: m_gla = memtop {use invalid address %else m_gla = heapget(h_ownsize) {allocate GLA %finish ! Unassign the GLA d0 = (h_ownsize+3)>>2-1 a0 = m_gla %while d0>=0 %cycle loop: *move.l d7,(a0)+; *dbra d0,loop d0 = d0-65536 %repeat ! Call reset routine gla = m_gla pos = h_reset<<1+codestart(h) *move.l pos,a0 *move.l gla,a1 *move.l a4,-(sp) *move.l a1,a4 *jsr (a0) *move.l (sp)+,a4 ! Satisfy import requirements o == record(addr(h[1])+h_export) o == nil %if h_import=0 %while o##nil %cycle %if o_flags&extbit#0 %start %unless loadobject(o,record(o_offset+gla)) %start printstring(event_message); newline ok = 0 %finish %finish o == nextdesc(o) %repeat %if ok=0 %start m_gla = 1 {mark as dodgy} printstring("*Unable to load ";m_name); newline %finish %result = ok %end %systempredicate dynamically loaded (%string(255)s,%integername pc,gla) ! Attempt to load external procedure specified in S. ! If found, return TRUE and set PC to its entry point, and GLA ! to the address of the relevant module's data area (0 if none). %constinteger extbit=16_4000,external=16_2000,jmp=16_4ef9 %record(fe02 indir fm)indir %record(fe02 object fm)object indir = 0 object = 0 object_flags = extbit+external object_name = s %if load object(object,indir) %start %if indir_op1=jmp %start gla = 0; pc = indir_opd1 %true %finish %if indir_op2=jmp %start gla = indir_opd1; pc = indir_opd2 %true %finish %finish %false %end ! End of loader ! Start of IO package %routine standardise (%string(*)%name name) %integer pc,gla %returnunless dynamically loaded("standardisefilename",pc,gla) *move.l name,a0 *move.l pc,a1 *move.l gla,a2 *move.l a4,-(sp) *move.l a2,a4 *jsr (a1) *move.l (sp)+,a4 %end %conststring eofnull="Attempt to read from null file" %routine FCB op (%registerrecord(fcb fm)%name fcb, %integer code,p1,p2,%name b) *move.l a4,-(sp) *move.l fcb_pc,a2 *move.l fcb_gla,a4 *jsr (a2) *move.l (sp)+,a4 %end %systemroutine FCB close (%record(fcb fm)%name x) ! Close file normally (input or output) fcb op(x,cbopclose,0,0,nil) %unless x==nil %end %systemroutine FCB abort (%record(fcb fm)%name x) ! Close file abnormally (input or output, ! but for input it does the same as CLOSE FCB). fcb op(x,cbopabort,0,0,nil) %unless x==nil %end %systemroutine FCB flush (%record(fcb fm)%name x) ! Write contents of file buffer (X_P-X_BS bytes at X_BS) to file ! at position X_BS-X_FS in file. ! Maintain high water mark (set X_FL to X_P if X_P>X_FL). ! If X_P=X_L, advance the buffer through the file (normally by ! leaving X_BS and X_BL alone and subtracting X_BL-X_BS from ! X_FS and X_FL. Return with X_L=X_BL. ! Normally return with X_P=X_BS, but if CH>=0, in the case of ! non-buffered devices (in which case X_BS=X_BL), write the one ! byte CH to the device, in the case of buffered devices, add CH ! to the buffer (and return with X_P=X_BS+1). fcb op(x,cbopflush,-1,0,nil) %unless x==nil %end %systemroutine FCB refresh (%record(fcb fm)%name x) ! Fill the file buffer by reading X_BL-X_BS bytes (less if near ! end of file) from such a position in the file that byte X_P-X_FS ! of the file will be in the buffer. This will usually involve ! updating X_FS and X_FL and X_P (but always return such that ! X_P-X_FS before is the same as X_P-X_FS after, i.e. X_P-X_FS ! denotes the current position in the file, we do not automatically ! return with X_P=X_BS, although this will normally be the case). ! Normally return with X_L=X_BL (unless near the end of file). %signal 9,,,eofnull %if x==nil fcb op(x,cboprefresh,0,0,nil) %end %systemroutine FCB write - (%record(fcb fm)%name x,%integer position,amount,%name b) ! Ignoring the buffer pointers in the FCB, write AMOUNT bytes to ! the file at POSITION in the file, from user buffer B. ! Use current position indicated in FCB if POSITION<0. %returnif x==nil position = x_p-x_fs %if position<0 fcb op(x,cbopwrite,position,amount,b) %end %systemroutine FCB read - (%record(fcb fm)%name x,%integer position,amount,%name b) ! Ignoring the buffer pointers in the FCB, read AMOUNT bytes from ! place POSITION in the file, to user buffer B. ! Use current position indicated in FCB if POSITION<0. %signal 9,,,eofnull %if x==nil position = x_p-x_fs %if position<0 fcb op(x,cbopread,position,amount,b) %end %systemrecord(fcb fm)%map FCB open (%integer code,%string(*)%name file,%name x) ! Either (if CODE is one of FOP OPEN I/O/M/A) open the specified FILE, ! returning an appropriate FCB, or perform a direct non-FCB file operation, ! which might involve a second string or buffer parameter in X. ! FILE (and X if relevant) are assumed to be already STANDARDISED. %string(255)dev %bytename d,f %integer pc,gla,p %record(fcbfm)%map call it(%integer a,%name b,c) *move.l a4,-(sp) *move.l gla,a4 *move.l pc,a2 *jsr (a2) *move.l (sp)+,a4 %end ! Make DEV equal to "FOP_". d == length(dev); f == length(file) dev = "FOP_" p = 2 {past the initial colon} %cycle %exitif p>f %or f[p]=':' d = d+1; d[d] = f[p]; p = p+1 %repeat %if dynamically loaded(dev,pc,gla) %start %result == call it(code,file,x) %finish dev = "No device driver for ".file %signal 3,3,,dev %end %systemroutine FILE OP (%integer code,%string(*)%name file,%name x) %record(fcbfm)%name dummy dummy == fcb open(code,file,x) %end %routine streamcheck(%integer n) %returnif 0<=n<=7 %signal 6,2,n,"Stream number out of range 0:7" %end %systemroutine selectinput(%integer stream) streamcheck(stream) poa_curin == poa_in(stream) poa_instream = stream %end %systemroutine selectoutput(%integer stream) streamcheck(stream) poa_curout == poa_out(stream) poa_outstream = stream %end %routine OPENINPUT (%integer s,%string(255)f) %record(fcb fm)%name fcb selectinput(s) standardise(f) fcb == fcb open(fopopeni,f,nil) fcb_next == poa_in(s) poa_in(s) == fcb poa_curin == fcb %end %routine OPENOUTPUT (%integer s,%string(255)f) %record(fcb fm)%name fcb selectoutput(s) standardise(f) fcb == fcb open(fopopeno,f,nil) fcb_next == poa_out(s) poa_out(s) == fcb poa_curout == fcb %end %systemintegerfn nextsymbol %register(a0)%record(fcb fm)%name cb == poa_curin %if addr(cb)>0 %start %if cb_p>=cb_l %start *move.l a4,-(sp) a4 = cb_gla a1 = cb_fastpc *jsr (a1) *move.l (sp)+,a4 %finish %result = byte(cb_p) %finish *temp %signal 9,,,eofnull %end %systemintegerfn readsymbol %register(a0)%record(fcb fm)%name cb == poa_curin %if addr(cb)<=0 %or cb_p>=cb_l %start *jsr nextsymbol %finish cb_p = cb_p+1 %result = byte(cb_p-1) %end %systemroutine printsymbol(%registerinteger k) %register(a0)%record(fcb fm)%name cb == poa_curout %if addr(cb)>0 %start %if cb_p9 ! f = f+1; f[f] = n+'0' ! %end ! add hex digit(machine>>4) ! add hex digit(machine&15) ! %result = filename %result = "mouse:boot.com" %end %routine note file(%string(255)name,%integer start,size) ! Add a file connection / module activation record for the specified file. %record(mar fm)%name mar,x mar == record(heapget(sizeof(mar)-255+length(name))) mar_start = start; mar_size = size; mar_gla = 0 mar_name = name; mar_next == nil x == poa_filelist %if x==nil %then poa_filelist == mar %elsestart x == x_next %while x_next##nil x_next == mar %finish %end %routine split(%string(255)line,%string(*)%name verb,param) ! Split line into verb and parameter (separated by one or more spaces) ! (string resolution not available at this low level) %bytename l,v,p %integer i l == length(line); v == length(verb); p == length(param) v = 0 %while v=pos+size %start pointer = 0 %result = -1 %finish pointer = pointer+1; %result = byte(pointer-1) %end %routine rline ! Read a line into global string LINE, assuming L points at its length. ! Comments (starting with '!' and preceded by any number of spaces, ! and continuing to the end of line, are skipped, as are leading spaces. ! Blank lines may be returned. %integer sym l = 0 %cycle sym = rsym; %exitif sym<=nl {end of line or file} %if sym='!' %start {start of comment} sym = rsym %until sym<=nl {skip rest of line} %exit %finish %unless sym=' ' %and l=0 %start {not leading space} l = l+1; l[l] = sym %finish %repeat l = l-1 %while l[l]=' ' {delete trailing spaces} %end pos = readfile(size) note file(file,pos,size) pointer = pos l == length(line) %cycle rline; %returnif pointer=0 {end of file} %continueif l=0 {blank line} %returnif l[1]='.' {end marker} %if l[1]='@' %start {chained file} process command file(substring(line,2,l)) %else split(line,file,cliparam) mpos = readfile(msize) note file(line,mpos,msize) %finish %repeat %end {process command file} %routine run(%record(mar fm)%name m) %record(processfm)%name newprocess %record(poafm)%name newpoa %record(marfm)%name om,nm %integer p,gla,fb %on %event 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 %start %returnif currentprocess==record(memtop-1024) {called from BECOMEPROCESS} %if currentprocess_stackbase=0 %start {before BECOMEPROCESS} %unless event_event!event_sub!event_extra=0 %start {Not normal stop} printstring("Program ";m_name;" crashed ") romphex1(event_event); space; romphex2(event_sub); space romphex(event_extra); space; printstring(event_message) newline %finish freebot = fb %return %finish rompstr("Process ";currentprocess_name) %if event_event!event_sub=0 %then rompstr(" stopped") %elsestart rompstr(" crashed "); romphex1(event_event) rompsym(' '); romphex2(event_sub) rompsym(' '); romphex(event_extra) rompsym(' '); rompstr(event_message) %finish rompsym(nl) hang %finish {%on} %returnunless executable(m) event = 0 fb = freebot; freebot = (freebot+1023)&-1024 newprocess == record(freetop-1024) newprocess = currentprocess currentprocess == newprocess newpoa == record(freetop-2048) newpoa = poa poa == newpoa currentprocess_ownbase = freebot currentprocess_stacklimit = freetop-2048 p = freetop-2048-12 integer(p) = p integer(p+4) = integer(poa_evlink+4) integer(p+8) = a4 gla = p-m_header_ownsize m_gla = -gla split(m_name,m_name,cliparam) currentprocess_name = m_name p = loadmodule(m) m_gla = gla %andstopif p=0 a0 = p; a4 = gla; a7 = a4; *jsr (a0) %stop %end {run} %record(fe02 header fm)%name self %record(mar fm)%name p,q self == record(a0) %constinteger - newpos=16_3f02-6*20,dispos=newpos-6, nsympos=dispos-6,rsympos=nsympos-6, psympos=rsympos-6,pstrpos=psympos-6, opipos=pstrpos-6,opopos=opipos-6, selipos=opopos-6,selopos=selipos-6, clipos=selopos-18,clopos=clipos-6 *lea heapget,a0; *move.l a0,newpos *lea dispose,a0; *move.l a0,dispos *lea nextsymbol,a0; *move.l a0,nsympos *lea readsymbol,a0; *move.l a0,rsympos *lea printsymbol,a0; *move.l a0,psympos *lea printstring,a0; *move.l a0,pstrpos *lea openinput,a0; *move.l a0,opipos *lea openoutput,a0; *move.l a0,opopos *lea selectinput,a0; *move.l a0,selipos *lea selectoutput,a0; *move.l a0,selopos *lea closeinput,a0; *move.l a0,clipos *lea closeoutput,a0; *move.l a0,clopos *lea rompsym,a0; *add.l #16_80000000,a0; *move.l a0,poa_curout freetop = memtop-4096 freebot = membot+32*1024 note file("mouse:loader.mob",addr(self), sizeof(self)+self_export+self_import+self_codesize) newline process command file(custom file) freebot = (freebot+1023)&-1024 message pool == new(message pool) object pool == new(object pool) currentprocess == record(memtop-1024) p == poa_filelist_next %while p##nil %cycle q == poa_filelist q_gla = 0 %and q == q_next %until q==nil run(p); p == p_next %repeat hang %end