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. .