10 The Assembler “Trap” Routine

The principal purpose of this chapter is to examine the assembly language code in “m40.s” which is involved in the handling of interrupts and traps.

This code is found between lines 0750 and 0805, and has two entry points, “trap” (0755) and “call” (0766). There are several different and relevant paths through this code and we shall trace some examples of these.

10.1 Sources of Traps and Interrupts

The discussion in Section One introduced three places where the occurrence of a trap or interrupt was expected:

(a)

“main” (1564) calls “fuibyte” repeatedly until a negative value is returned. This will occur after a “bus timeout error” has been encountered with a subsequent trap to vector location 4 (line 0512);

(b)

The clock has been set running and will generate an interrupt every clock tick i.e. 16.7 or 20 milliseconds;

(c)

Process #1 is about to execute a “trap” instruction as part of the system call on “exec”.

10.2 fuibyte (0814), fuiword (0844)

“main” uses both “fuibyte” and “fuiword”. Since the former is more complicated in a non-essential way, we leave it to the reader, and concentrate on the latter.

“fuiword” is called (1602) when the system is running in kernel mode with one argument which is an address in user address space. The function of the routine is to fetch the value of the corresponding word and to return it as a result (left in r0). However if an error occurs, the value –1 is to be returned.

Note that with “fuiword”, there is an ambiguity which does not occur with “fuibyte”, namely a returned value of –1 may not necessarily be an error indication but the actual value in the user space. Convince yourself that for the way it is used in “main”, this does not matter.

Also the code does not distinguish between a “bus timeout error” and a “segmentation error”.

The routine proceeds as follows:

0846:

The argument is moved to r1;

0848:

“gword” is called;

0852:

The current PS is stored on the stack;

0853:

The priority level is raised to 7 (to disable interrupts);

0854:

The contents of the location nofault (1466) are saved in the stack;

0855:

“nofault” is loaded with address of the routine “err”;

0856:

An “mfpi” instruction is used to fetch the word from user space.

If nothing goes wrong this value will left on the kernel stack.

0857:

The value is transferred from the stack to r0;

0876:

The previous values of “nofault” and PS are restored;

Now suppose something does go wrong with the “mfpi” instruction, and a bus time-out does occur.

0856:

The “mfpi” instruction will be aborted. PC will point to the next instruction (0857) and a trap via vector location 4 will occur;

0512:

The new PC will have the value of “trap”. The new PS will indicate:

present mode = kernel mode

previous mode = kernel mode

priority = 7;

0756:

The next instruction executed is the first instruction of “trap”. This saves the processor status word two words beyond the current “top of stack”. (This is not relevant here.);

0757:

“nofault” contains the address of “err” and is non-zero;

0765:

Moving 1 to SR0 reinitialises the memory management unit;

0766:

The contents of “nofault” are moved on top of the stack, overwriting the previous contents, which was the return address in “gword”;

0767:

The “rtt” returns, not to “gword” but to the first word of “err”;

0880:

“err” restores “nofault” and PS, skips the return to “fuiword”, places –1 in r0, and returns directly to the calling routine.

10.3 Interrupts

Suppose the clock has interrupted the processor.

Both clock vector locations, 100 and 104, have the same information. PC is set to the address of the location labelled “kwlp” (0568) and PS is set to show:

present mode = kernel mode

previous mode = kernel or user mode

priority = 6

Note. The PS will contain the true previous mode, regardless of the value picked up from the vector location.

0570:

The vector location contains a new PC value which is the address of the statement labelled “kwlp”. This instruction is a subroutine call on “call” via r0.

After the execution of this instruction, r0 is left with the address of the code word after the instruction which contains “_clock”, i.e. r0 contains the address of the address of the “clock” routine in the file “clock.c” (3725).

10.4 call (0776)

0777:

Copy PS onto the stack;

0779:

Copy r1 onto the stack;

0780:

Copy the stack pointer for the previous address space onto the stack. (This is only significant if the previous mode was user mode).

This represents a special case of the “mfpi” instruction. See the “PDP11 Processor Handbook”, page 6-20;

781:

Copy the copy of PS onto the stack and mask out all but the lower five bits. The resulting value designates the cause of the interrupt (or trap). The original value of the PS had to be captured quickly;

0783:

Test if the previous mode is kernel or user.

If the previous mode is kernel mode the branch is taken (0784). PS is changed to show the previous mode as user mode (0798);

0799:

The specialised interrupt handling routine pointed to by r0 is entered. (In this case it is the routine “clock”, which is discussed in detail in the next chapter.)

0800:

When the “clock” routine (or some other interrupt handler) returns, the top two words of the stack are deleted. These are the masked copy of the PS and the copy of the stack pointer;

0802:

r1 is restored from the stack;

0803:

Delete the copy of PS from the stack;

0804:

Restore the value of r0 from the stack;

0805:

Finally the “rtt” instruction returns to the “kernel” mode routine that was interrupted;

If the previous mode was user mode it is not certain that the interrupted routine will be resumed immediately;

0788:

After the specialised interrupt routine (in this case “clock”) returns, a check (“runrun \({\gt}\) 0”) is made to see if any process of higher priority than the current process is ready to run. If the decision is to allow the current process to continue, then it is important that it be not interrupted as it restores its registers prior to the “return from interrupt” instruction. Hence before the test, the processor priority is raised to seven (line 0787), thus ensuring that no more interrupts occur until user mode is resumed. (Another interrupt may occur immediately thereafter, however.)

If “runrun \({\gt}\) 0”, then another, higher priority, process is waiting. The processor priority is reset to 0, allowing any pending interrupt to be taken. A call is then made to “swtch” (2178), to allow the higher priority process to proceed. When the process returns from “swtch”, the program loops back to repeat the test.

The above discussion obviously extends to all interrupts. The only part which relates specifically to the clock interrupt is the call on the specialised routine “clock”.

10.5 User Program Traps

The “system call” mechanism which enables user mode programs to call on the operating system for assistance, involves the execution by the user mode program of one of 256 versions of the “trap” instruction. (The “version” is the value of the low order byte of the instruction word.)

0518:

Execution of the trap instruction in a user mode program causes a trap to occur to vector location 34 which causes the PC to be loaded with the value of the label “trap” (lines 0512, 0755). A new PS is set which indicates

present mode = kernel mode

previous mode = user mode

priority = 7

0756:

The next instruction executed is the first instruction of “trap”. This saves the processor status word in the stack two words beyond the current “top of stack”.

It is important to save the PS as soon as possible, before it can be changed, since it contains information defining the type of trap that occurred. The somewhat unconventional destination of the “move” is to provide compatibility with the handling of interrupts, so that the same code can be used further on;

0757:

“nofault” will be zero so the branch is not taken;

0759:

The memory management status registers are stored just in case they will be needed, and the memory management unit is reinitialised;

0762:

A subroutine entry is made to “call” using r0. (This neatly stores the old value of r0 in the stack, but not a return address. The new value is the address of the address of the routine to be entered next (in this case the “trap” routine in the file “trap.c” (2693));

0772:

The stack pointer is adjusted to point to the location which already contains the copy of PS;

0773:

The CPU priority is set to zero;

From here the same path as for an interrupt is followed.

10.6 The Kernel Stack

The state of the kernel stack at the time that the “trap” procedure (“C” version) or one of the specialised interrupt handling routines is entered, is shown in Figure 10.1.

     

....

Previous top of stack

(rps

2)

7

ps

old PS

(r7

1)

6

pc

old PC (r7)

(r0

0)

5 –\({\gt}\)

r0

old r0

   

4

nps

new PS after trap

(r1

–2)

3

r1

old r1

(r6

–3)

2

sp

old SP for previous mode

   

1

dev

masked new PS

   

0 –\({\gt}\)

tpc

return address in “call”

(r5

–6

–1

(r5)

old r5

(r4

–7

–2

(r4)

old r4

(r3

–8

–3

(r3)

old r3

(r2

–9

–4

(r2)

old r2

Figure 10.1

Columns (2) and (3) give the positions of stack words relative to the positions in the stack of the words labelled “r0” and “tpc” respectively.

Columns (1) and (2) define (or explain) the contents of the file “reg.h” (Sheet 26).

“dev”, “sp”, “r1”, “nps” “r0”, “pc” and “ps” in that order are the names of the parameters used in the declaration of the procedures “trap” (2693) and “clock” (3725).

Note that just before entry to “trap” (“C” version) or the other interrupt handling routines, the values for the registers r2, r3, r4 and r5 have not yet been saved in the stack. This is performed by a call on “csv” (lg20) which is automatically included by the “C” compiler at the beginning of every compiled procedure. The form of the call on “csv” is equivalent to the assembler instruction

   jsr r5,csv

This saves the current value of r5 on the stack and replaces it by the address of the next instruction in the “C” procedure.

1421:

This value of r5 is copied into r0;

1422:

the current value of the stack pointer is copied into r5.

Note that at this point, r5 points to a stack location containing the previous value of r5 i.e. it points to the beginning of a chain of pointers, one per procedure, which “thread” the stack. When a “C” procedure exits, it actually returns to “cret” (1430) where the value of r5 is used to restore the stack and r2, r3 and r4 to their earlier condition (i.e. as they were immediately prior to entering the procedure). For this reason r5 is often called the environment pointer.