! File NMOUSE:LOADER ! Mouse system bootstrap loader, ! "FE02" format linking loader, ! Heap package, ! Basic Imp I/O support. ! The .MOB file produced by this source file is concatenated onto the ! end of PREFIX.MOB using the BUILD program. The resulting image ! file (called X) is loaded into local memory by the ROM bootstrap. %option "-low-nons-nodiag-nocheck-nostack-nowarn" {-half??} %include "mouse.inc" ! Heap package ! 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 %systemintegerfn heapget(%integer need) ! Allocate a heap chunk at least NEED bytes long, initialise it ! to the unassigned pattern, return its address in both A0 and D0. ! 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. %constinteger unassign heap chunks = 1 %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 heap space" %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 {point at header word} %returnunless poa_heapbase<=pos0 {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 %routine DISPOSE (%name x) heapput(addr(x)) %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))) p == integer(addr(fcb)) %cycle p = 0; p == p[1]; n = n-4 %repeatuntil n=0 fcb_filename = f %result == fcb %end ! Loader %constinteger jmp=16_4EF9, lea a4=16_49F9, jsr=16_4eb9, pea=16_4879, rts=16_4e75 %constinteger dodgy = 1 %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 header contains the magic number. %record(fe02 header fm)%name h == m_header %falseif m_size<=sizeof(h) %or h_tyver#16_fe02 %falseif m_gla=dodgy %falseunless m_size>=sizeof(h)+h_export+h_import+h_codesize %true %end %record(fe02object fm)%map next loader record(%record(fe02object fm)%name r) ! Given the variable size loader record R in the export or import ! section of a code file, return a reference to the following one. r == record((addr(r[1])-255+length(r_name)+1)&\1) r == nil %if r_flags=0 %result == r %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) %predicate 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(mar fm)%name m %record(fe02header fm)%name h %record(fe02object fm)%name o %integer codestart,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 the reference is dynamic, we defer loading until the thing is ! actually used. At boot-time (as indicated by POA_HEAPBASE being 0) ! we speed things up slightly by pretending all external references ! are dynamic, thus not loading some modules which will not be needed. ! If REF_OP1 is PEA, then the object has actually been called. flags = object_flags&procmask flags = dynamic %if flags=external %and poa_heapbase=0 flags = 0 %unless flags=dynamic flags = 0 %if ref_op1=pea %if flags#0 %start ref_op1 = pea; ref_opd1 = addr(object) ref_op2 = jsr; ref_opd2 = addr(dynref) %true %finish m == poa_filelist %while m##nil %cycle {search all modules whether loaded or not} h == m_header o == record(addr(h[1])) o == nil %ifnot 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 codestart = addr(h)+sizeof(h)+h_export+h_import 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+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+o_offset %finish %true %finish flags = 'm' {mode or type mismatch} %finish o == next loader record(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(fe02indir fm)%name dref, %record(fe02object fm)%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 *move.l (sp)+,(sp) *rts %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,codestart %record(fe02 object fm)%name o %record(fe02 header fm)%name h == m_header %record(fe02 indir fm)%name d %result = 0 %unless executable(m) codestart = addr(h)+sizeof(h)+h_export+h_import ok = h_main<<1+codestart %result = ok %if m_gla>0 {already loaded {{rompstr("Loading ";m_name) %if m_gla<0 %start {GLA already allocated m_gla = -m_gla {{rompstr(" main program GLA: ") %elseif h_ownsize=0 {no GLA wanted: m_gla = memtop {use invalid address {{rompstr(" no GLA: ") %else m_gla = heapget(h_ownsize) {allocate GLA {{rompstr(" new GLA: ") %finish {{romphex(m_gla); rompsym('-') ! 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 {{romphex(a0); rompsym(nl) ! Call reset routine gla = m_gla pos = h_reset<<1+codestart *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 d == record(o_offset+gla); d_op1 = 0 {not PEA} %unless loadobject(o,d) %start printstring(event_message); newline ok = 0 %finish %finish o == next loader record(o) %repeat %if ok=0 %start m_gla = 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 object fm)object %record(fe02 indir fm)indir object = 0 object_flags = extbit+external object_name = s indir_op1 = pea %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 ! IO package %routine standardise (%string(*)%name name) ! Locate entry point for the external procedure STANDARDISE FILE NAME, ! not defined in this module, and then call it with NAME as parameter. %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) ! Perform operation specified by CODE (which is CBOP* where * is ! one of {CLOSE, ABORT, FLUSH, REFRESH, WRITE, READ}, on the file ! associated with FCB, using parameters P1, P2, and 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 input or output file normally fcb op(x,cbopclose,0,0,nil) %unless x==nil %end %systemroutine FCB abort (%record(fcb fm)%name x) ! Close input or output file abnormally fcb op(x,cbopabort,0,0,nil) %unless x==nil %end %systemroutine FCB flush (%record(fcb fm)%name x) ! Write contents of file buffer into file and maintain high water mark 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 from the 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) ! Independently of the buffer in the FCB, write AMOUNT bytes into ! the file at offset POSITION, from user buffer B. ! If POSITION<0, use and update the current position noted in FCB. %returnif x==nil %if position<0 %start fcb op(x,cbopwrite,x_p-x_fs,amount,b) x_p = x_fs+position+amount; x_l = x_p x_fl = x_p %if x_p>x_fl %else fcb op(x,cbopwrite,position,amount,b) %finish %end %systemroutine FCB read - (%record(fcb fm)%name x,%integer position,amount,%name b) ! Independently of the buffer in the FCB, read AMOUNT bytes from ! the file at offset POSITION, into user buffer B. ! Use and update position in FCB if POSITION<0. %signal 9,,,eofnull %if x==nil %if position<0 %start fcb op(x,cbopread,x_p-x_fs,amount,b) x_p = x_fs+position+amount; x_l = x_p %else fcb op(x,cbopread,position,amount,b) %finish %end %systemrecord(fcb fm)%map FCB open (%integer code,%string(*)%name file,%name x) ! Call the appropriate device driver to perform the operation specified ! by CODE on the specified FILE (with extra parameter X if appropriate). ! FILE (and X if relevant) are assumed to be already STANDARDISED. ! If CODE is FOP* with * one of {OPENI, OPENO, OPENM, OPENA} then return a ! file control block (FCB) as result, for use with the above IO procedures. ! If CODE is something else, simply perform the operation directly, in which ! case the result is undefined. %string(255)dev %bytename d,f %integer pc,gla,p %record(fcb fm)%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) ! Perform direct (non-FCB) operation CODE on file FILE (with parameter X). %record(fcb fm)%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_p0 byte = 0 %if byte<0 byte = poa_curout_fl-poa_curout_fs %if byte>poa_curout_fl-poa_curout_fs poa_curin_p = poa_curin_fs+byte poa_curin_l = poa_curin_p %unless poa_curin_bs<=poa_curin_p<=poa_curin_l %end %systemroutine SET OUTPUT (%integer byte) %on 9 %start poa_curout_p = poa_curout_fs+byte poa_curout_l = poa_curout_bl %return %finish %returnunless addr(poa_curout)>0 byte = 0 %if byte<0 byte = poa_curout_fl-poa_curout_fs %if byte>poa_curout_fl-poa_curout_fs %unless poa_curout_bs <= poa_curout_fs+byte <= poa_curout_bl %start poa_curout_p = poa_curout_bl poa_curout_p = poa_curout_fl %if poa_curout_fl9 ! f = f+1; f[f] = n+'0' ! %end ! add hex digit(machine>>4) ! add hex digit(machine&15) ! %result = filename !%end %routine add mar(%string(255)name,%integer start,size) ! Add a 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 loadfile(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 obeyfile(substring(line,2,l)) %else loadfile(line,nil,nil) %finish %repeat %end {obeyfile} %routine run(%record(mar fm)%name m) ! Invoke the program described by M. %record(process fm)%name newprocess %record(poa fm)%name newpoa %record(mar fm)%name om,nm %integer p,gla %on %event 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 %start freebot = ownbase %returnif event_event!event_sub=0 rompstr("Program ") %if currentprocess_name="" - %then romphex(a5-1024) %else rompstr(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 %unless a5=memtop-1024 %return %finish %returnunless executable(m) event = 0 ownbase = freebot stacklimit = freetop gla = (stacklimit-sizeof(poa_evtrap)-m_header_ownsize)&-4 m_gla = -gla split(m_name,m_name,cliparam) currentprocess_name = substring(m_name,1,length(m_name)-4) {.mob} p = loadmodule(m) m_gla = gla %andstopif p=0 a0 = p; a4 = gla; a7 = a4; *jsr (a0) %stop %end {run} ! Note where we have been loaded self == record(a0) ! Register the extracode entry points *lea heapget,a0; *move.l a0,xc20 *lea dispose,a0; *move.l a0,xc21 *lea nextsymbol,a0; *move.l a0,xc22 *lea readsymbol,a0; *move.l a0,xc23 *lea printsymbol,a0; *move.l a0,xc24 *lea printstring,a0; *move.l a0,xc25 *lea openinput,a0; *move.l a0,xc26 *lea openoutput,a0; *move.l a0,xc27 *lea selectinput,a0; *move.l a0,xc28 *lea selectoutput,a0; *move.l a0,xc29 *lea closeinput,a0; *move.l a0,xc32 *lea closeoutput,a0; *move.l a0,xc33 ! Carve up memory a5 = memtop-1024 currentprocess == record(a5-1024) freetop = memtop-4096 freebot = membot + (32*1024+256) freetop = freetop-sizeof(ko); ko == array(freetop) freetop = freetop-sizeof(bu); bu == array(freetop) currentprocess = 0 *lea rompsym,a0; *add.l #16_80000000,a0; *move.l a0,poa_curout ! Read in files needed to build the system printstring("Loading"); newline global code base = freebot add mar("nmouse:loader.mob",addr(self), sizeof(self)+self_export+self_import+self_codesize) loadfile("nmouse:super.mob",nil,nil) loadfile("nmouse:mapper.mob",nil,nil) obeyfile("nmouse:boot.com") {or custom file} freebot = (freebot+1023)&-1024 global code limit = freebot ! Now run loaded object files in sequence 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 ! Finally fade away printstring("Booting complete"); newline freebot = -freebot freetop = -freetop hang %end