/* This is the interrupt dispatcher that is called by all interrupts*/ /* The next problem occurs because the interrupt number is needed */ /* as a parameter to the interrupt handler. There are several ways that */ /* this can be accomplished: */ /* 1) Calling the dispatcher from the interrupt vector table */ /* This places the PC address of the vector table on the */ /* PC stack. The PC stack can be read to determine the */ /* interrupt that was invoked. The disadvantage is that */ /* an additional level of stack usage (CALL/RTS) is used */ /* up. */ /* 2) Pusing the interrupt number of the C runtime stack. */ /* The problem here is that the 020_hdr.asm file would */ /* need to be reassembled for each type of runtime stack */ /* that is supported. At the moment a single runtime */ /* header exists, which makes life much easier. */ /* 3) Test the right most (highest priority) bit in the */ /* interrupt latch. This can be a problem because we can */ /* not easily test which bit is the right most. To get */ /* around this problem, we bit reverse the IMASKP register*/ /* and then test for the left most bit. This seems to */ /* work well, and it is the technique implemented in this */ /* code. */ /* */ /* Another problem occurs with the compiler ability to pass */ /* parameters in registers. It is difficult to change this dispatcher at */ /* compile time, so we need a solution that works for both register */ /* passing on and off. To accomplish this we push the parameter (the */ /* interrupt number) on the stack, as well as in R4 (the first register */ /* used for parameter passing). */ /* */ /* Another issue the the default handling of interrupts after a */ /* single interrupt has been serviced by use of the signal() function. */ /* The interrupt slots hold a new IMASK mask word. This word is ANDed */ /* with the IMASK during the servicing. This will cause interrupts */ /* enabled with signal() to be serviced only once, while interrupts set */ /* with the interrupt() routine will be serviced until disabled. */ /* */ /* There is at least one more open issue with interrupts. If a */ /* library routine or user assembly code changes the value of a L-register*/ /* (not the scratch DAGS) it may be a problem for the interrupt handler. */ /* The compiler should be changed to save L-registers for associated */ /* I-registers that are used. The L-registers should be saved and */ /* restored at function begin and end. */ /* */ /* Gordon A. Sterling (617) 461 - 3076 February 28, 1992*/ /* Another infamous interrupt problem. The dispatcher needs to */ /* save the ASTAT register during an interrupt, and restore ASTAT before*/ /* returning to the interrupted code. The problem is the state of the */ /* flag outputs is stored in ASTAT. If the application changes the */ /* state of a flag output in the interrupt routine, (which seems to */ /* happen alot), the flag output will be restored to the same state it */ /* was before the interrupt. It is possible to AND out the flag bits, */ /* and OR in the current flag bits before returning. This takes some */ /* time, but there seems to be no alternative. */ /* While fixing this bug, I have moved a few memory accesses around*/ /* to minizime the timing impact. There general idea is to mask out the*/ /* flag bits in the saved ASTAT, and OR in the current state of the */ /* flags before restoring ASTAT. */ /* */ /* There is another issue (not a bug). The dispatcher assumes that*/ /* the default set of scratch registers are used, no user optional */ /* scratch. If the interrupt handler (or any of the routines that it */ /* calls use optional scratch registers, some values may (will?) get */ /* trashed. The interrupt handler MUST be complied with only the fixed */ /* scratch registers, and all routines call by the handler MUST use the */ /* fixed scratch register set only. */ /* */ /* Gordon A. Sterling (617) 461 - 3076 August 20, 1992 */ /* */ /* Another round of changes. The dispatcher will now be modified */ /* so that the DO-loop stack will be saved/restored around interrupt */ /* service routines. This will allow the compiler to use DO-loops and */ /* not worry if it is in and ISR or not. In addition, some of the */ /* code will be moved around to try and optimize things a bit more */ /* */ /* Gordon A. Sterling (617) 461 - 3076 January 31, 1993 */ /* */ /* The data register R1 has been added to the list of call */ /* clobbered registers. The interrupt dispatcher is responsible for */ /* saving all call clobbered registers before calling the handler. The */ /* R1 register must be saved in the function prologue. */ /* */ /* Gordon A. Sterling (617) 461 - 3076 June 10, 1993 */ /* */ /* OK, the emulator cannot step over a PUSH LOOP instruction. If */ /* the user attempts to step through this code in the emulator, the */ /* code will NOT function correctly. To remind users of this problem */ /* the label `___lib_DO_NOT_EMU_STEPn' where `n' is a unique number so */ /* that the labels do not clash. */ /* */ /* Gordon A. Sterling (617) 461 - 3076 June 29, 1993 */ /* */ /* Updated on 4/94 to accomodate the new improved run time header. The */ /* regular interrupt dispatcher should be slightly faster. Done by AS */ /* CBUG interrupt traceability has also been removed in order to speed */ /* the dispatchers up. */ #include "lib_glob.h" #include "sig_glob.h" .SEGMENT/CODE Code_Space_Name; .FILE RTL_FILENAME; .GLOBAL ___lib_int_cntrl; ___lib_int_cntrl: BIT SET MODE1 0x1000; /*Re-enable interrupts */ put(R0); /*Save R0 for our use */ put(R1); /* New scratch register */ put(R4); /*Save scratch registers*/ put(R8); /* since the ISR will */ put(R12); /* trash them */ put(ASTAT); /*Save status register */ put(R2); /*Save oldframe register*/ /* This section of code can be a bit confusing. We need to save the */ /* scratch DAG registers on the stack. The DAG2 registers can be pushed*/ /* directly, while the DAG1 registers need to be copied to DREGs. While*/ /* this is being done, I am also setting the R4 register equal to 6 and */ /* the R2 register equal to 3. The 6 is used to determine which slot */ /* in the interrupt table is to be used, while the 3 is used to */ /* decrement a pointer for loop stack reloading. */ /* */ /* Gordon A. Sterling (617) 461 - 3076 February 4, 1993 */ /* */ put(pm_ptr); /*If the stack is in DM */ put(pm_bse); /* the PM DAGs can be */ put(pm_mdf); /* pushed directly on */ R4=R4-R4, put(pm_lnt); /* the stack. */ R4=R4+1, R0=dm_ptr; /*The DM DAGs must be */ R4=R4+1, put(R0); /* pushed indirectly. */ R4=R4+R4, R0=dm_bse; R4=R4+1, put(R0); /* R4 gets 5 by the end */ R2=R2-R2, R0=dm_mdf; /* R2 gets 0 */ R2=R2+1, put(R0); /* R2 gets 1 */ R2=R2+1, R0=dm_lnt; /* R2 gets 2 */ R2=R2+1, put(R0); /* R2 gets 3 */ /* Here is the section of code that saves the loop stacks to memory */ /* before calling the handler. The technique is as follows: R12 points*/ /* to the address of the place to go if there were NO loop stack slots */ /* used. The various registers are saved, and if the Loop Stack Empty */ /* bit is NOT set int STKY, the address in R12 is decremented by three. */ /* This means that instead of just setting LCNTR, the first loop stack */ /* spot will be restored. */ /* The maximum number of loop stack spots will be saved to the C runtime*/ /* stack, but the R12 pointer will only be decremented if the stack is */ /* not empty. The critical step is the stack restore. See below for */ /* more details. */ /* */ /* Gordon A. Sterling (617) 461 - 3076 February 4, 1993 */ /* */ R12=empty_stack; /* Go here for no stack */ put(LCNTR); /*Save loop counter */ put(LADDR); /*Save loop address */ put(CURLCNTR); /*Save current counter */ BIT TST STKY 0x04000000;/*Test for empty stack */ IF TF JUMP (PC, 3); /*Loop stack is empty */ POP LOOP; /*Pop down loop stack */ R12=R12-R2; put(LADDR); /*Save loop address */ put(CURLCNTR); /*Save current counter */ BIT TST STKY 0x04000000;/*Test for empty stack */ IF TF JUMP (PC, 3); /*Loop stack is empty */ POP LOOP; /*Pop down loop stack */ R12=R12-R2; put(LADDR); /*Save loop address */ put(CURLCNTR); /*Save current counter */ BIT TST STKY 0x04000000;/*Test for empty stack */ IF TF JUMP (PC, 3); /*Set address */ POP LOOP; /*Pop down loop stack */ R12=R12-R2; put(LADDR); /*Save loop address */ put(CURLCNTR); /*Save current counter */ BIT TST STKY 0x04000000;/*Test for empty stack */ IF TF JUMP (PC, 3); /*Set address */ POP LOOP; /*Pop down loop stack */ R12=R12-R2; put(LADDR); /*Save loop address */ put(CURLCNTR); /*Save current counter */ BIT TST STKY 0x04000000;/*Test for empty stack */ IF TF JUMP (PC, 3); /*Set address */ POP LOOP; /*Pop down loop stack */ R12=R12-R2; put(LADDR); /*Save loop address */ put(CURLCNTR); /*Save current counter */ BIT TST STKY 0x04000000;/*Test for empty stack */ IF TF JUMP (PC, 3); /*Set address */ POP LOOP; /*Pop down loop stack */ R12=R12-R2; dm_lnt=dm_0; /*Clear length register */ dm_ptr=IMASKP; /*Get IMASKP into DAG */ BITREV(dm_ptr, 0); /*Reverse IMASKP */ R0=dm_ptr; /*Read into DREG */ R4=LEFTZ R0,pm_lnt=pm_0;/*Determine interrupt # */ put(R12); ram_ireg=I15; /*Point to slot*/ R8=rammem(next_func,ram_ireg); /*Read next function address*/ rammem(curr_func,ram_ireg)=R8; /*Write out next address*/ R8=rammem(next_mask,ram_ireg); /*Read next IMASK*/ put(R8); /*Save next IMASK on stack*/ pm_ptr=I13; /*handler saved from RTH*/ I13=int_cont; /*People may need this still*/ CALLER_HOLD(R2) CALLER_SWAP RTLCALL (pm_0, pm_ptr) (DB); /*Call function*/ SAVE_OLD_FRAME(R2) SAVE_RET_ADDR /* Here is the confusing part with the flags. The flag bits need */ /* to be removed from the saved ASTAT, everything else needs to be */ /* removed from the current ASTAT, and those two things need to be ORed */ /* together, and restored in ASTAT. All of this must be done with */ /* interrupts disabled. Oh boy */ int_cont: R8 = 0xff87ffff; /*Mask to remove flags */ R12=NOT R8, get(R2,1); /*Get new IMASK mask */ get(R4,25); /*Get old ASTAT */ I15=MODE1; /*Save state after handler*/ JUMP (PC, stop_ints) (DB); /*Discourage interrupts*/ BIT CLR MODE1 0x1000; /*Disable all interrupts*/ R4=R4 AND R8, R0=IMASK; /*Get current IMASK */ stop_ints: R0=R0 AND R2, R2=ASTAT; /*AND with new mask bits*/ R0=R2 AND R12, IMASK=R0;/*Remove flag bits */ R0=R0 OR R4; /*New ASTAT */ get(pm_ptr,2); /*get loop stk restore */ /* At this point, we are going to restore the loop stack. Remember*/ /* that we created a pointer that would point to the address to jump to.*/ /* Well, the JUMP is next, and it will jump to one of the restore stack */ /* locations below. When the JUMP occurs, the values will be read from */ /* the C runtime stack, and restored to the loop stack. If some dummy */ /* saves were made above (the loop stack was not full), then they are */ /* ignored. Only valid loop stack information is restored. */ /* */ /* Gordon A. Sterling (617) 461 - 3076 February 4, 1993 */ /* */ JUMP (pm_0, pm_ptr) (DB); ASTAT=R0; /*Write out new ASTAT */ MODE1=I15; /*Restore state after handler*/ ___lib_DO_NOT_EMU_STEP1:PUSH LOOP; get(CURLCNTR, 3); get(LADDR, 4); ___lib_DO_NOT_EMU_STEP2:PUSH LOOP; get(CURLCNTR, 5); get(LADDR, 6); ___lib_DO_NOT_EMU_STEP3:PUSH LOOP; get(CURLCNTR, 7); get(LADDR, 8); ___lib_DO_NOT_EMU_STEP4:PUSH LOOP; get(CURLCNTR, 9); get(LADDR, 10); ___lib_DO_NOT_EMU_STEP5:PUSH LOOP; get(CURLCNTR, 11); get(LADDR, 12); ___lib_DO_NOT_EMU_STEP6:PUSH LOOP; get(CURLCNTR, 13); get(LADDR, 14); empty_stack: get(LCNTR,15); get(dm_lnt,16); /*Restore DM DAG scratch*/ get(dm_mdf,17); get(dm_bse,18); /*Be sure to write base */ get(dm_ptr,19); /* before writting ptr */ get(pm_lnt,20); /*Restore PM DAG scratch*/ get(pm_mdf,21); get(pm_bse,22); /*Be sure to write base */ get(pm_ptr,23); /* before writting ptr */ get(R2,24); /*ASTAT is recovered before*/ get(R12,26); /*Restore r12 */ get(R8,27); /*Restore r8 */ get(R4,28); /*Restore r4 */ get(R1,29); /* Restore R1 New */ get(R0,30); get(I15,31); /*restore from RTH */ get(I13,32); /*Get ret addr, rest from RTH*/ RTI(DB); /*Return from interrupt */ alter(32); /*Clean the stack */ BIT CLR MODE2 0x00080000;/* Re-enable cache */ .ENDSEG;