The Alpha Micro system monitor (SYSTEM.MON[1,4]) is logically
divided into seven modules. These modules (in the order in which they
fall in memory) are:
SYSMON - base monitor
TRMSER - terminal service routines
FILSER - file service routines
FILERR - file error message routines
EXEC - executive program module
DSKSER - general disk driver routines
INITIA - the system initialization routine
SYSMON and EXEC are my own names for those modules. The
following discussion of SYSMON reflects the monitor as of the 4.2
release.
The thirty-one sixteen bit word addresses from 0 to 76 octal are
the reserved core locations as defined in the WD16 Programmer's
Reference Manual that comes with every AM-100 (TM) board. A brief
summary of these word locations and their functions follow (the reader
is advised to refer to the manual for a full description of the
definitions):
HEX OCTAL FUNCTION
--- ----- --------
00-10 00-20 R0-R5, SP, PC, and PS are saved
or fetched for halt or power up
(not implemented on the AM-100)
The following locations contain the value that
is placed in PC if the described event occurs:
12 22 buss error
14 24 nonvectored interrupt powerfail
16 26 power up/halt option power restore
18 30 parity error
1A 32 reserved op code
1C 34 illegal op code format
1E 36 XCT (execute single instruction) error
20 40 XCT trap
22 42 SVCA table (see note 1 below)
24 44 SVCB
26 46 SVCC
28 50 vectored interrupt table (see note 2 below)
2A 52 nonvectored interrupt
2B 54 BPT (breakpoint) trap
2E 56 I/O priority mask
30-38 60-70 floating point operation storage
3A-3C 72-74 not used
3E 76 floating point error PC
Note 1: content of octal 42 plus twice the value of the argument is
placed in PC. The content of the word thus addressed by PC is added
to PC to form the final destination address.
Note 2: content of octal 50 plus the device code is placed in PC.
The content of the word addressed by PC is added to PC to get the
final destination address.
Once the monitor is loaded, either by the disk controller board's
PROM routine, MONTST.PRG, or one a bootstrap loader (HWKLOD, WNGLOD,
etc.), a "CLR PC" is executed which sets the program counter to memory
location 0. Initially that location contains the instruction "JMP
@#31572", which in the 4.2 release of the monitor, is the address of
the initialization routine, INITIA, which will be discussed later.
In that there are no SVCC's currently implemented on the system,
location 46 octal (the SVCC PC) contains zero. Therefore, if an SVCC
ever gets called, the program counter will be set to location 0.
After the first clock interrupt, location 0 no longer contains the JMP
instruction. Part of the job scheduler's task is to update the arrow
display for the DYSTAT program. This is done by moving octal 15 to
the memory location contained in the JOBDYS word of each job's JCB.
If DYSTAT is not running on the system, the JOBDYS address is 0 and
the original JMP instruction gets modified into a LEA R4,@R5. When an
SVCC is executed the AM-100 starts executing garbage and the system
crashes.
The next section of the monitor, starting at octal 100 is the
system communication area as desrcibed in appendix B of the "AMOS
MONITOR CALLS MANUAL". In addition to that description, the following
information is known about selected words in that area:
SYSTEM - if bit 8 is set the system is running
from a cartidge disk.
DEVTBL - points to a contiguous area of memory.
Each entry consists of four words:
WORD 0 - device status (odd byte) and
drive number (even byte).
status byte:
bit 0 is always set so that word 0 is
never 0
bit 1 set = sharable
bit 2 set = non-sharable device is
assigned (set by ASSIGN)
bit 3 set = same as bit 2, but it is
not known how it is set
bit 4 set = mounted
WORD 1 - device name (packed RAD50)
WORD 2 - JCB address if device is assigned
if address is odd, device is
assigned to "COMPUTER B"
WORD 3 - device bitmap address (if 0, device is not
file structured)
Last word of the table is zero
CLKQUE, SCNQUE, and RUNQUE are discussed
later with the job scheduler.
DRVTRK - the table initially contains -1's
Following the system communication area is the vectored interrupt
(I0) table and an illegal interrupt trap routine. There are eight
entries in the table, which initially are offsets to the trap routine.
The internal stack is 100 octal words is size and is used as a
work stack by the job scheduler.
The SVCA and SVCB offset tables are the next area in the monitor.
The execution of an SVCA is described in the WD16 manual. There are
no undocumented SVCA's, however there appears to be an error in those
copies of SYS.MAC that I have seen. The BNKSWP call is defined as an
SVCA 46, but its table entry in the monitor makes it an SVCA 45.
SVCB's are processed with a routine which follows their offset
table. This routine decodes the PSI following the SVCB opcode and
places the address of the first argument in R4 and the address of the
second argument in R3. The function code is placed in R0 and the PC
address is adjusted up to three words past the SVCB. The offset table
is entered with a TCALL R5, which contains twice the value of the SVCB
argument. If the SVCB uses an extra argument, as SVCB 0 function code
octal 14 (DSKALC through DSKCTG) does, it is the responsibility of the
final destination routine to adjust PC past this word. There are no
undocumented SVCB's.
The monitor error trap routines follow the SVCB processing
module. The first of these is the ubiquitous "BUSS ERROR - PC nnnn".
The error control intercept words of the JCB are first tested for user
error recovery. If these words are null (they are always reset to
null by EXIT - SVCA 11), a system error message is generated. A "BUSS
ERROR" is reported for buss, breakpoint, and floating point errors.
There are no SVCA 0 or SVCA 1 calls defined and they are trapped out,
however they will be erroneously reported as "??SVCA 1 CALLED" or
"??SVCA 2 CALLED".
The queue system routines and the initial twenty queue blocks
form the next section of the monitor. The queue calls are fully
described in chapter five of the "AMOS MONITOR CALLS MANUAL". No test
is made by any queue call to determine if there are any queue blocks
available. If a particular system installation makes heavy use of the
queue system, it is recommended that QFREE be tested before a queue
call is executed.
The job scheduler follows the queue system. It is entered
whenever a nonvectored interrupt (I1) is generated. This interrupt is
the line clock. The job scheduler first executes a SAVE (PS and PC
were pushed on the stack when the interrupt occured), switches over to
the internal stack, and then increments TIME. CLKQUE entries are
processed every clock tick by first picking up the address of CLKQUE
which links to the first queue entry to be processed and the first
word of the queue entry links to next, etc. The second word of the
queue entry contains the address of the routine to be executed and the
remaining six words can be used for the routine's data (after the
routine is called R3 will contain the address of the third word of the
queue entry). The routine must exit with a RTN. If the "V" bit
(overflow bit in the status information) is set (via an LCC 2 or other
operation which sets this bit) on return from the routine, the entry
is deleted from the CLKQUE by returning the queue block to the QFREE
list and relinking the rest of the queue. The next entry is
processed by picking up its link from the previous entry. Examples of
CLKQUE entries are the SLEEP call (described below), HLDTIM, and
DYSTAT (which is never descheduled). CLKQUE entries are added with a
QINS call and placing the address of the routine in the second word of
the queue block.
When the end of the CLKQUE is reached, the initial entry will be
processed. This entry is two words long; the first containing the
link to SCNQUE and the second containing the address of a RTN
instruction (allowing simumlation of an actual routine).
SCNQUE entries are processed by the same routine that handles
CLKQUE entries and the format is identical. The job secheduler does
not make a distinction between them at this point. When the end of the
SCNQUE is reached its link points to the RUNQUE.
The RUNQUE is five words in length; its initial entries are as
follows:
RUNQUE: WORD RUNQUE+6 ; link to current job's run address
WORD 1004 ; address of a RTN instruction
WORD RUNQUE ; link to next job's run address
WORD 0 ; last link
WORD 2476 ; end of the job scheduler
The RTN instruction (RUNQUE+2) will always by executed when the
RUNQUE is entered (this provides for an initial simulation of a CLKQUE
or SCNQUE entry). If any jobs are scheduled (see JRUN for scheduling
information) the first RUNQUE word will contain the address of the
fourth word of the currently scheduled job's JOBRNQ. This address is
the run address for the job when it gets scheduled and will be either
the entry point of the job context switching routine or the job
priority timer routine. If the address is the first one, the job's SP
is restored from JOBRNQ+14, the job's priority is copied into the
timer word and incremented (to insure the job doesn't get 65,535 ticks
of CPU time), JOBCUR is updated, the address of the priority timer
routine is placed in the fourth word of JOBRNQ, the DYSTAT arrow is
sent, and memory bank switching (if active) is carried out. An RRTT
then sends the job off to continue whatever it was doing before being
interrupted.
When the priority timer routine is entered (either via an
interrupt or JWAIT - see below), the time counter word (JOBRNQ+10) is
decremented and, if non-zero, the job is allowed to continue. If the
job has used all its alloted time and there are other jobs in the
RUNQUE, a new job is scheduled (if no other jobs are scheduled, the
job is allowed to continue). Before scheduling the next job the
current job's SP is saved, the context switching routine address is
placed in the job's run address word, and the DYSTAT arrow is cleared.
The link to the next job is loaded from RUNQUE+4 and the link to the
next job up is placed in RUNQUE+4. This is somewhat oversimplified as
the RUNQUE linkage appears to be circular, but that is the general
idea.
The initial link defines the end of the RUNQUE. If all scheduled
jobs are completed (and descheduled) within the clock tick (or no jobs
were scheduled) the last link is reached. This link is to a section
of the job scheduler that insures interrupts are enabled (so another
clock interrupt will occur) and executes an SOB 400 times (presumably
waiting for an interrupt). If a clock interrupt does not occur after
the SOB argument reaches 0, the processor is locked and the SCNQUE
entries are processed again.
After the job scheduler is the BNKSWP routine which will swap
memory banks for the job in control of the CPU. The job scheduler
does not use this call, but rather, does it's own swapping. BNKSWP
will not update the JOBBNK word in the JCB and if the bank argument
passed in R1 does not exist the job will either be stuck here forever
or return the wrong or non-existant memory bank.
Although JWAIT occupies the next block of memory, discussion of
this call is postponed until after JRUN so that the scheduling of a
job can be done first.
The JRUN call first locks the processor and then picks up the
flag argument following the call (when the routine returns, the PC is
adjusted past this argument). If none of the flag argument bits are
set in the current JOBSTS word, the routine returns. If any bits are
set, they are cleared from the JOBSTS word (if the flag argument is
zero, it is a special case and the bit test is bypassed). The JOBRNQ
words are defined below to aid in the following discussion on job
scheduling.
JOBRNQ (seven word block):
WORD 0 - unknown / always 0
WORD 1 - link to last scheduled job
WORD 2 - link to next scheduled job
WORD 3 - job's run address
WORD 4 - priority counter
WORD 5 - job priority
WORD 6 - current SP
The link to the next scheduled job (JOBRNQ+4) is tested and, if
it is non-zero, the routine returns as the job is already scheduled.
If the link is zero, it is loaded with the link from the job currently
in control of the CPU to the next scheduled job. The link to the last
scheduled job (JOBRNQ+2) is loaded with the link to the job currently
in control. Thus, the JOB referenced by R0 when a JRUN is executed is
inserted between the job which executed the JRUN and the next
scheduled job. Because the interrupts were disabled by the call to
JRUN, the job will be the next one scheduled. Obviously, a JRUN with
R0 referencing its own job will accomplish nothing as the job must
have been scheduled to execute the JRUN.
In the JWAIT call the flag argument is picked up, its bits are
set in the JOBSTS word, and PC is adjusted past the argument. The
JWAIT functions in an opposite manner to JRUN. JWAIT links the job
scheduled before the one referenced by R0 to the job scheduled after
it and clears the forward pointing link in JOBRNQ+4(R0). If the link
was already cleared, JWAIT is exited as the job is already in a wait
state. The job's SP is saved in JOBRNQ+14, the internal stack address
is loaded, the RUNQUE is indexed, the DYSTAT arrow is cleared, and the
next job is scheduled. JWAIT may be called with R0 referencing its
own job; the caller should obviously provide a means for the job to be
rescheduled.
The SCAN call follows JRUN and executes entries in the SCNQUE
until the link to the RUNQUE is reached.
The SLEEP call inserts a queue block into the CLKQUE by loading
R3 with the address of the CLKQUE and calling QINS. Part of the SLEEP
code is a routine which decrements the tick argument of the SLEEP call
until it is 0; one decrement per clock tick. The address of this code
is placed in the second word of the queue block (the first word in the
block is the link to the next CLKQUE entry). The tick argument is
placed in the third word of the queue block (referenced by R3 after
the call to the routine) and the job's address is placed in the fourth
word. The job itself is placed in a wait state with "JWAIT J.SLP".
When the tick argument reaches zero, the job's address is picked up
from the queue block and a "JRUN J.SLP" is executed, clearing the
J.SLP flag from the job's JOBSTS word and rescheduling the job.
For those programmers interested in making use of the CLKQUE or
the SCNQUE the following remarks and example should prove helpful:
As described above, CLKQUE and SCNQUE entries are
identical in format and in the method by which they are
processed. The major difference being that SCNQUE entries
are processed not only at line clock interrupts, but also
when the processor is idle and when the SCAN call is
executed. The distinction can be important if the routine
is to be used for controlling or monitoring a real time or
external event. For the purposes of the following example,
an entry is inserted into the CLKQUE, but the method is the
same for the SCNQUE.
EXMPLE: LOCK ; no interrupts
MOV #CLKQUE,R3 ; load address of chain
QINS ; insert queue block at R3
LEA R1,CODE ; address of routine to be run
MOV R1,(R3)+ ; set address of routine in queue block
MOV DATA,(R3)+ ; remaining six words can be used for data
UNLOCK ; routine is queued
...
... ; balance of user program
...
EXIT
CODE: ... ; code which is executed when CLKQUE
... ; entry is called. R3 will index the
... ; second word of the queue block for
... ; use as data area. R4 must not be
... ; disturbed! Calls which index a job
... ; may not neccessarily index the caller's.
RTN ; the routine must return
Note: The block of code that is inserted into either SCNQUE
or CLKQUE may not reside in bank switched memory, it must be
in memory common to all users as a job does not run this
code, the monitor runs it. In addition, the queue entry
must be deleted from SCNQUE or CLKQUE before the program
terminates which inserted it if the code is part of that
program. If the code is another memory module which won't
move after the program terminates, then the queue entry may
remain. Otherwisw, garbage will probably be executed the
next time the entry is processed resulting in a system
crash.
To allow the inserted code to remove itself from CLKQUE
(or SCNQUE) when some condition is met, the following
instructions (or appropriate alternatives) are added to the
routine:
CODE: ... ; user routine
...
TST VALUE ; condition met yet?
BNE RETURN ; no
LCC 2 ; yes, set "V" bit to delete entry
RETURN: RTN
If it is desired that the routine only be executed at
fixed intervals a SLEEP call cannot be used as the SLEEP
call only deschedules jobs, not CLKQUE (or SCNQUE) entries.
A routine such as the one described by Lefford Lowden
(Method One) in issue Number 2.3 of his Newsletter should be
used. The CPU loading is not severe as the overhead of job
context switching is not incurred.
If the job which inserted the queue entry executed a
"JWAIT FLAGS", suspending itself until some event
transpired, the queue entry can revive it by executing a
"JRUN FLAGS" with R0 indexing the job's JCB before the final
return.
Good luck!
The three memory calls (GETMEM, DELMEM and CHGMEM) are all part
of the same module and include extensive error testing. The GETMEM
call will return a module cleared to nulls.
The most involved module in this section of the monitor is the
FETCH / SRCH module. It is composed of over 150 instructions
(compared to the barely 70 of the job scheduler). It first determines
what control flags have been set and then limits it's search on that
basis. Unless the F.ABS flag was set a search of either user memory
(F.USR) or system memory and user memory is made on a module by module
basis.
If a disk fetch was requested, FETCH clears all flags and error
codes from the user DDB, except flag bits 4 (transfer initiated) and 5
(read or write). The DDB is then pre-INITed (BIS #40000,DDB) and an
INIT DDB is called. This is done to get the address of the disk
driver in DDB+12 without allocating a buffer and also, FILSER will
supply the user's default Device if it was not specified in the file
specification. The FETCH call uses this address to determine the
record size of the device (always the first word of a disk driver).
It sets this record size in DDB+4 and then checks to make sure the
caller has enough memory to accomodate a disk record. If not, the
call will be aborted. If no PPN was supplied in the DDB, the user's
PPN is picked up before a search of the disk MFD is made to find the
address of the UFD. If the UFD was found, it's records are read until
the specified file and its link are located. If the file is found,
its size is calculated and if the user has enough free memory, a
memory module is built and the file is read in. If the user included
the F.FIL control flag, the file's name will be copied from the DDB to
the housekeeping words of the memory module and the module "FIL" flag
will be set.
The KBD call follows the FETCH / SRCH module. If the job has no
terminal attached a "JWAIT J.TIW" is executed, descheduling the job
until a terminal is attached. If the terminal is in image mode a TIN
is called and the input character is placed directly in R1. If the
terminal is in normal input mode, the job's command file size word
(JOBCMZ) is tested for command file processing. If the word is zero
(not processing a command file) TIN is called, adding characters to
the input buffer, until either a line-feed is reached or the buffer is
full.
The KBD routine also handles command file processing. If the
JOBCMZ word is non-zero, KBD will get its input from the command file
buffer, echoing the data if the Trace flag is set and placing it into
the input buffer, until a line-feed or a special command file symbol
(delimited by ":") is reached. If the command is ":<" the data is only
echoed until a ">" is reached.
The TTY call tests the JOBCMZ word to determine if a command file
is being processed. If the JOBCMZ word is zero, no command file is
loaded and TTY calls TOUT. If the JOBCMZ word is non-zero, bit 4 of
JOBCMS is tested to determine if the Trace flag is set; if not, TTY
returns. If Trace is set, bit 2 of JOBCMS (Revive) is tested and, if
it is set, TOUT is called else TTY returns.
The TTYI call executes a TTYL until a null character is reached.
TTYL executes a TTY. In the TTYL call, a carriage return (octal 15)
gets an automatic line-feed (octal 12) appended.
The TAB call executes a TTYI with octal 11 and 0 as immediate
data and a CRLF does the same, but with octal 15 and 0 as immediate
data.
The terminal service routines follow the preceding calls and they
will be discussed in the next article.