!TITLE Interrupts, %EVENTs and fault trapping. ! !< READID Within the TRAP routine, the subsystem can identify the kind of interrupt from the values of CLASS and SUBCLASS, and can find more details by calling the %EXTERNAL %INTEGER %FN READID (%INTEGER AD) which is part of Director (see Director documentation for details), and a suitable call is found quite early in DIRTRAP. The parameter AD must be the address of the first element of an array of 18 words. !> !< Environment The data returned by READID will be the 'environment' at the time of the interrupt, laid out as follows: 1: SSN/LNB 2: PSR 3: PC 4: SSR 5: SSN/SF 6: IT 7: IC 8: CTB 9: XNB 10: B 11 and 12: DR 13 to 16: Accumulator 17: FPC (for PE only) 18: ? !> !< DRESUME When the subsystem has finished processing the interrupt, if it is required to resume the process at the point where it was suspended before Director forced the call of the TRAP routine, then it must call DRESUME (0, 0, AD), where DRESUME is supplied in Director with the specification %EXTERNAL %ROUTINE %SPEC DRESUME (%INTEGER LNB, PC, AD) and AD has the same value as the AD parameter passed to READID (or, at any rate, is the address of 18 words containing the same data as was returned from READID). There is such a call of DRESUME near the end of DIRTRAP, just before the lable REROUTE. !> !< Inhibiting interrupts !< SSINHIBIT and SSINTCOUNT From time to time, Subsystem may find it impractical to handle interrupts for a short period, and it can inhibit calls on DIRTRAP by setting a non-zero value into the first of two consecutive words which are declared as SSINHIBIT and SSINTCOUNT in the Subsystem. So long as SSINHIBIT is non-zero, Director will not force any calls on DIRTRAP, even if some interrupt occurs; but if one does occur, details will be recorded in Director's tables for future reference, and SSINTCOUNT will be incremented. !> !< ALLOW INTERRUPTS Later on (but, for safety's sake, after only a short delay), Subsystem will clear SSINHIBIT to zero, to allow calls on DIRTRAP again. Once SSINHIBIT is zero, all the interrupts which arrived while calls of DIRTRAP were inhibited, and whose details have been saved up by Director, must be processed: this is achieved by %WHILE SSINTCOUNT>0 %THEN I = DASYNCINH (1,0) which requests Director to initiate the appropriate calls of DIRTRAP (supported by the relevant information to be acquired via READID) for each of the 'pending' interrupts, one after another. It is left to Director to decrement SSINTCOUNT. The relevant code in Subsystem is found in the %SYSTEM %ROUTINE ALLOW INTERRUPTS (with no parameters) in the BASE module. !> !< DASYNCINH For this mechanism to work, Director must know where to find the two consecutive words SSINHIBIT and SSINTCOUNT. This information is supplied by Subsystem in a call on DASYNCINH (0,ADDR(SSINHIBIT)); the declaration of DASYNCINH is %EXTERNAL %INTEGER %FN %SPEC DASYNCINH (%INTEGER MODE, A) and the relevant call is found near the start of the routine CALL BCI in CONTROL in module CONT. Until this call has been made, Director will not initiate any calls on DIRTRAP, even though DIRTRAP has already been nominated by PRIME CONTINGENCY. Instead, all interrupts will be 'saved' or kept pending from the time of the call of PRIME CONTINGENCY until the call on DASYNCINH. Any interrupts which were already pending at the time of the original call on PRIME CONTINGENCY, on the other hand, will be discarded and utterly lost. Consequently the start-up sequence of the Subsystem includes a very early call on PRIME CONTINGENCY, but defers the first call of DASYNCINH until much later, when the environment is completely set up for handling interrupts. Only after that call will Director initiate calls on DIRTRAP for the pending interrupts (if any). !> !> !< Calls on Director Apart from declarations, Subsystem contains one call of READID, one use of DIRTRAP, two calls of DASYNCINH and two of DRESUME. We have so far accounted for all of these except one call on DRESUME. The story so far is a complete account of the interactions between Subsystem and Director for processing interrupts: what follows is concerned with Subsystem's internal mechanisms and with the effects on, and facilities for, user programs. !> !< REROUTE CONTINGENCY Director is not very selective about interrupts. If it has something to report to Subsystem, then the only decision which arises is whether to report it now or to save it for later. There are no facilities to inhibit some kinds of interrupts but not others, nor to report different kinds of interrupts through different paths. (It is possible for Subsystem to change the nominated TRAP routine by a second call on PRIME CONTINGENCY, but this is not a very versatile facility, and Subsystem soes not use it.) However, Subsystem has an internal mechanism to achieve some of these things. There is a %SYSTEM %ROUTINE REROUTE CONTINGENCY (%INTEGER EP, CLASS, %C %LONG %INTEGER MASK, %ROUTINE STRAP, %INTEGER %NAME FLAG) (declared in BASE) which allows the caller to nominate an alternative routine STRAP with specification %ROUTINE %SPEC STRAP (%INTEGER CLASS, SUBCLASS) exactly like the TRAP parameter to PRIME CONTINGENCY. The parameters EP, CLASS and MASK in a call of REROUTE CONTINGENCY indicate which kinds of interrupt are to be handled by STRAP. Up to eight different STRAP routines can be simultaneously defined, by different calls on REROUTE CONTINGENCY, each one for a different kind of interrupt. Director will still initiate a call of DIRTRAP for any kind of interrupt, but DIRTRAP contains code to select the appropriate STRAP routine and initiate a call on that. If the interrupt is of a kind for which no STRAP has been nominated, then DIRTRAP will handle it in the normal way. Within a STRAP routine, READID may be used to obtain complete information about the interrupt, and DRESUME can be used to resume the interrupted process, just as is done in DIRTRAP. !PAGE REROUTE CONTINGENCY can be used to define up to 8 different STRAP routines to handle different kinds of interrupt (actually, the limit is the declared value of the %CONST %INTEGER MAXRRC). The number of STRAP routines currently defined is given by the %INTEGER RRCTOP, and the whole set of STRAP routines can be discarded by calling REROUTE CONTINGENCY with the parameter EP=0 (which has the effect of setting RRCTOP to zero). Other calls on REROUTE CONTINGENCY must have 1<=EP<=5, and these are used to nominate STRAP routines. EP, CLASS and MASK are used to specify what kinds of interrupt should be handled by STRAP. MASK is a 64-bit mask in which the least significant (right-hand) bit corresponds to the value 0 or 64, the next bit corresponds to 1 or 65, and so on up to the most significant (left-hand) bit which corresponds to 63 or 127. The meaning of the values of EP, CLASS and MASK are as follows: N.B.: In the following table, 'class' and 'subclass' (written in lower case) refer to the properties of an interrupt. 'CLASS' and 'MASK' (written in upper case) refer to the parameters supplied to REROUTE CONTINGENCY. !PAGE EP=1 Handle interrupts whose class = CLASS (ignore MASK). EP=2 Handle interrupts whose class = CLASS, provided that the bit corresponding to subclass is set in MASK (for subclasses 0 to 63). EP=3 Handle interrupts whose class = CLASS, provided that the bit corresponding to subclass is set in MASK (for subclasses 64 to 127). EP=4 Check the MASK bit corresponding to the class of the interrupt, and handle the interrupt if the bit is non-zero (for classes 0 - 63). Ignore CLASS. EP=5 Check the MASK bit corresponding to the class of the interrupt, and handle the interrupt if the bit is non-zero (for classes 64-127). Ignore CLASS. !PAGE Incidentally, any use of EP=1 could be replaced by an equivalent use of EP=4 or 5 (unless one is dealing with interrupt classes greater than 127). !> !< DIRTRAP REROUTE CONTINGENCY is, in fact, used nowhere in the standard Subsystem. Consequently all interrupts are handled by DIRTRAP, so we must now consider what DIRTRAP does. For completeness, this description will include 'forward references' to some things which have not yet been defined - in particular, SIGLEVEL and SIGNAL - but the general outline should be sufficiently clear if you know that SIGNAL is a routine in the Subsystem which is used to notify the 'user program' of an event. 1. Check whether there is any STRAP routine nominated by a previous call of REROUTE CONTINGENCY to handle this interrupt. If there is, bodge up a call to that routine, passing on the parameters CLASS and SUBCLASS. There will be no further action in DIRTRAP. In the usual case, there is no STRAP routine, so DIRTRAP carries on at step 2. 2. Check 0 !< Time exceeded CLASS = 64 (time limit exceeded): use DSETIC to get some more time; if STOPPING then resume with no further action; copy the interrupt data (as returned by READID) into SIGDATA(SIGLEVEL); copy the same data, PLUS class, subclass and time, into one of the SAVEIDATA areas (for #REGS); SIGNAL (2,64,SUBCLASS,FLAG) - i.e., signal 'time exceeded' at the most recently defined level; resume. !> !< Single character INT CLASS = 65 (single character INT:) - SUBCLASS will be the interrupt character: convert lower to upper case; convert all characters to 'X' for batch jobs; recognise which character; if it's not a recognised character (Q,W,X,Y,T,A,C), then resume with no further action; !< W, X, Y for W, X, Y: if STOPPING then DSTOP (113); set INT IN PROGRESS (ignoring its previous value); SIGNAL (3,65,SUBCLASS,FLAG) - i.e., signal at outermost level; resume. !> !< A, C for A, C: if INT IN PROGRESS or STOPPING then resume (i.e., ignore it); set INT IN PROGRESS; SIGNAL (3,65,SUBCLASS,FLAG) - i.e., signal at outermost level; resume. !> !< Q for Q: if INT IN PROGRESS or STOPPING then resume (i.e., ignore it); set INTQ (but INT IN PROGRESS is not set - why not?); copy the interrupt data (as returned by READID) into SIGDATA(SIGLEVEL); copy the same data, PLUS class, subclass and time, into one of the SAVEIDATA areas (for #REGS); SIGNAL (2,65,'Q',FLAG) - i.e., signal 'INT:Q' at the most recently defined level; resume. !> !< T for T: if INT IN PROGRESS or STOPPING then resume (i.e., ignore it); CONSOLE (12,FLAG,FLAG) to print the time, etc.; resume. !> !> !< Operator message CLASS = 66 (operator message): if not STOPPING then call CONSOLE (6,...) to print the message; resume. !> !> !> ! !< Summary Summary of parameters: (0,PC,LNB,FLAG) define a new 'level': save PC and LNB, increment SIGLEVEL. (1,0,-,FLAG) abandon a level: decrement SIGLEVEL. (1,n{#0},-,FLAG) abandon all levels: set SIGLEVEL = 0. (2,CLASS,SUBCLASS,FLAG) signal event at most recently defined level and abandon that level: event defined by CLASS, SUBCLASS and SIGDATA(SIGLEVEL)_A; pass control to code with PC, LNB extracted from SIGDATA(SIGLEVEL); decrement SIGLEVEL. !PAGE (3,CLASS,SUBCLASS,FLAG) signal event at outermost level: event defined by CLASS, SUBCLASS and SIGDATA(1)_A; pass control to code with PC, LNB extracted from SIGDATA(1). (4,-,-,FLAG) signal last event again at most recent surviving level. (5,-,-,FLAG) %MONITOR; %STOP. (6,ADDR(L),-,FLAG) copy SIGLEVEL into L. !> !> ! !< Data Data used by this mechanism: !< Stack frame 1. Stack frame, located via LNB. Word 0: pointer to previous tack frame. Words 1 and 2: return link (code descriptor) Word 3: first word of GLA descriptor. The bound (bits 8-31 of this word) is actually an offset within the shareable symbol table, pointing to a diagnostic record. Word 4: address of GLA. . . Word n: line number. n can be found from the diagnostic record (defined below), and when there is no line number, n will be given as zero. !> !< GLA 2. GLA, located via word 4 of stack frame. Words 0 to 2: .... Word 3: address of shareable symbol tables. Word 4: most significant byte is the language code. . . Word j: address in code at which to resume processing when a 'trapped' event occurs: j is found from ONWORD in the diagnostic record. !> !< Diagnostic record 3. Diagnostic record, found via word 3 of the stack frame and word 3 of the GLA. Word 0: less significant half-word is the offset in the stack frame for the line number word (or zero if there is no line number word). Word 1: less significant half-word is the offset in the unshareable symbol table for another diagnostic record - i.e., this is the link to the next record. Word 2: not used in this code. Words 3 onwards: a name string, with the length byte in the most significant byte of word 3, and the remainder of the last word (if any) unused. If there is no name, then Word 3 is zero. Word i (the word immediately after the last word occupied by the name): ONWORD, in which bits 0-13 are a mask indicating which events can be accepted, and bits 14-31 are an offset within the GLA of a word containing the address of the code at which to resume processing when the event occurs. !> !< Event INFO 4. Double word INFO - describing an event. Word 0: bits 0-23, event number. bits 24-31, subevent number. Word 1: line number of source code (or zero). !> !> !> ! !< Action When Director is activated to handle some event, it initiates a call on DIRTRAP, on the same stack where the process was working when the event occurred. DIRTRAP uses READID to get 18 words of interrupt data, and, in most cases, stores it in the appropriate SIGDATA record and calls SIGNAL (again, on the same stack). SIGNAL prefixes the 18 words with two more - the class and subclass - and then uses DRESUME to resume execution at one or other of the 'SIGNAL traps'. In the more common (and the more interesting) case, control arrives at the label FAIL in BCI or in ENTER ON USER STACK, with: - LNB pointing to the appropriate stack frame, so that the code can find its variables, and - the address of twenty words of information in the accumulator (actually in the bottom half of the accumulator, since DRESUME takes its third parameter, expands it into a descriptor, and loads it into the accumulator before passing control to the nominated address). !PAGE The twenty words are: 1: Class 2: Subclass 3: SSN/LNB 4: PSR 5: PC 6: SSR 7: SSN/SF 8: IT 9: IC 10: CTB 11: XNB 12: B 13 and 14: DR 15 to 18: Accumulator 19: FPC (for PE only) 20: ? !PAGE When control arrives at the label FAIL, the first action is to store the accumulator, i.e., a descriptor pointing to this twenty-word area, and then, using the second word of the stored descriptor to locate the data, NDIAG is called with parameters which are - the PC at the time of the interrupt; the LNB at the time of the interrupt; the value 10; the class of the interrupt. NDIAG starts by laying down a new SIGNAL trap in case of further problems arising during the printing of diagnostics, and also increments ACTIVE. Then it starts to unravel the stack frames of the interrupted process. It has checks for stack frames which are totally implausible - that is, incompatible with the 2900 architecture; for frames which do not conform to EMAS standards (generated by 'odd' languages, perhaps); and for frames indicating GLAs in public segments, corresponding to local controller. For stack frames which pass all these tests, it extracts the language flag from the indicated GLA, and then goes through a fairly complicated bit of code to convert the 'class' (passed in to NDIAG as a parameter) into an 'event' and 'subevent'. For IMP code, it then calls ONCOND, passing the parameters EVENT, SUBEVENT and LNB. ONCOND then works its own way back down the stack frames, looking for an %ON %EVENT trap. If there is none, ONCOND simply returns to NDIAG which goes on to print the diagnostics. But if a trap is found, then ONCOND constructs a double-word of information about the event and passes control to the nominated place. !> !< Summary To summarise, when an event occurs, control passes through the following bits of code: Director; routine DIRTRAP; routine SIGNAL; one of the SIGNAL traps; routine NDIAG; routine ONCOND; some event trap - or, if there is none, control returns to NDIAG to print diagnostics. !> !> !< Example of interrupt trapping %systemroutinespec ndiag(%integer lnb,pc,fault,inf) %systemroutinespec reroute contingency(%integer ep,class, %longinteger mask,%routine on trap,%integername flag) %systemroutinespec signal(%integer ep,p1,p2,%integername flag) %systemroutinespec console(%integer ep,%integername st,len) %externalintegerfnspec readid(%integer adr18) %externalroutinespec dstop(%integer reason) %externalintegerfnspec dresume(%integer lnb,pc,adr18) %externalintegerfnspec ddelay(%integer secs) %externalstring(15)%fnspec interrupt %constlonginteger mask=X'FFFFFFFFFFFFFFFF'{the lot} %externalroutine test trap(%string(255)dummy) %integer flag,counter %string(15) multi int %routine trap (%integer class,subclass) %conststring(10)%array regs(1:18)="SSN/LNB","PSR ","PC ","SSR ", "SSN/SF ","IT ","IC ","CTB ","XNB ","B ","DR0 ", "DR1 ","ACC 0 ","ACC 1 ","ACC 2 ","ACC 3 ","FPC ","? " %integer flag,adr18 %integerarray aa(1:18) %if 'a'<=subclass<='z' %then subclass=subclass-'a'+'A' %if 'W'<=subclass<='Y' %thenstart %if subclass#'Y' %thenstart;!cannot write if comms failure printstring("*** Fatal Interrupt ".tostring(subclass)." ***") newline printstring("*** Process stops") newline %finish flag=ddelay(2);!to give message time to get out signal(3,class,subclass,flag);!pass interrupt to subsystem dstop(100);!we should NEVER get here %finishelsestart adr18=addr(aa(1)) flag=readid(adr18) %if subclass='T' %then console(12,flag,flag) %and ->resume %if subclass='K' %then console( 7,flag,flag) %and ->resume %if subclass='C' %then console( 8,flag,flag) %and ->resume %if subclass='Q' %thenstart newline printstring("Registers at event:");newline %for flag=1,1,18 %cycle printstring(regs(flag)." ");write(aa(flag),10);newline %repeat ->resume %finish printstring("*** Int:".tostring(subclass)." trapped and ignored") newline printstring("*** Use Int:stop to stop") newline resume: flag=dresume(0,0,adr18);!go back to where we were %finish %end reroute contingency(3,65,mask,trap,flag) !all interrupts of class 65 whose subclass is set in mask (0<=subclass<=63) reroute contingency(2,65,mask,trap,flag) !all interrupts of class 65 whose subclass is set in mask (64<=subclass<=127) !a mask of X'0382000A0382000A' will trap A,C,Q,W,X,Y %for counter=1,1,120 %cycle flag=ddelay(1) multi int=interrupt %if multi int="stop" %thenexit %if multi int#"" %start newline printstring("*** Multi Character Int:".multi int." inspected ") newline printstring("*** Use Int:stop to stop") newline printstring("*** Counter = ") write(counter,1) newline %finish %repeat reroute contingency(0,0,0,trap{dummy},flag) !to clear all nominated traps !without this the next event would be sent to the expected address and !find the object had been unloaded - disaster! %end %endoffile !>