10. Interrupts
Interrupt:
Caused by external source
Truly asynchronous (e. g. may occur in the middle of pseudoinstruction)
Main use: perform a subroutine when time is come
Handled by 0x800000180 kernel handler
when returning from interrupt handler, interrupted instruction must be re-executed
⇒ unlike trap/exception, handler needs not to add 4 to EPC
Hardware requirements:
- Detection of an interrupt
- store device ID and event ID
- Deal with simultaneous interrupts
- ⇒ interrupt pending + interrupt masking
- some interrupts cannot be masked
- arbitrage
Interrupt handler
The Status C0 register (slightly MARS-specific):
bits |
31-16 |
15-8 |
7-5 |
4 |
3-2 |
1 |
0 |
target |
unused |
Int. mask |
unused |
K/U |
unused |
Exception level |
Int enable |
- Interrupt mask — bit scale of enabled/disabled (1/0) interrupts. Disabled interrupts are ignored
- Kernel Mode / User Mode — indicating whether CPU in kernel or user mode (MARS: always kernel)
- Exception level — is set to 1 while handling exception, disables recurrence
- Interrupt enable — global interrupt handling enable/disable (1/0)
The Cause C0 register
bits |
31 |
30-16 |
15-8 |
7 |
6-2 |
1-0 |
target |
Br |
unused |
Pending interrupts |
unused |
Exception code |
unused |
- Br: 1 if exception is breaking pipeline
Pending interrupts: bit scale of interrupts just occurred. Being asynchronous, interrupts can occur simultaneously
- Exception code is set to the exception type
Policy:
All registers is to be kept (including $at), except $k0 and $k1
- Interrupts shall be disabled ASAP (to prevent concurrent handling)
Do not rely on $sp (it can be corrupted)
It's common to provide separated kernel stack (e. g. down from 0xfffeeffc)
- Exceptions/traps are different to handle (on EPC modifying if nothing else)
When handling an interrupt «Exception code» field of Cause C0 register is 0
Interrupt must be handled as quick as possible, the less execution flow stay in kernel space, the better
To run a bit of user code
- To let other devices work normally
- You probably get pending interrupts, they need to handle too
- Before exit:
Clean Cause field
- Restore all registers
- Enable interrupts
Mars: you need еo enable interrupt manually first. On «Digital Lab Sim» setting bit 7 of 0xffff0012 MMIO register enables key down interrupt.
1 .data
2 msg: .asciiz "Tick\n"
3 .text
4 mfc0 $a0 $12 # read from the status register
5 ori $a0 0xff11 # enable all interrupts
6 mtc0 $a0 $12 # write back to the status register
7
8 # Byte value at address 0xFFFF0012 : command row number of hexadecimal keyboard (bit 0 to 3) and enable keyboard interrupt (bit 7)
9 li $t0 0x80
10 sb $t0 0xffff0012
11
12 loop: li $v0 4
13 la $a0 msg
14 syscall
15 li $v0 32
16 li $a0 1000
17 syscall
18 j loop
Note «sleep» syscall up there.
BTW, «DLS» has another useful interrupt: on every 30th instruction. it controlled by 0xFFFF0013 MMIO register:
1 .macro push %reg
2 addiu $sp $sp -4
3 sw %reg ($sp)
4 .end_macro
5
6 .macro pop %reg
7 lw %reg ($sp)
8 addiu $sp $sp 4
9 .end_macro
10
11 .data
12 msg: .asciiz " tick\n"
13 .text
14 mfc0 $a0 $12 # read from the status register
15 ori $a0 0xff11 # enable all interrupts
16 mtc0 $a0 $12 # write back to the status register
17
18 # Byte value at address 0xFFFF0012 : command row number of hexadecimal keyboard (bit 0 to 3) and enable keyboard interrupt (bit 7)
19 li $t0 1
20 sb $t0 0xffff0013
21
22 li $t0 0
23 loop: li $v0 1
24 move $a0 $t0
25 syscall
26 li $v0 4
27 la $a0 msg
28 syscall
29 li $v0 32
30 li $a0 1000
31 syscall
32 addiu $t0 $t0 1
33 j loop
34
35 .macro keep %reg %addr
36 move $k0 %reg
37 sw $k0 %addr
38 .end_macro
39
40 .macro undo %reg %addr
41 lw $k0 %addr
42 move %reg $k0
43 .end_macro
44
45 .kdata
46 _at: .word 0 # keep $at
47 _sp: .word 0 # keep $sp
48 imsg: .asciiz "-!-"
49
50 .ktext 0x80000180
51 mfc0 $k0 $12 # !! disable interrupts
52 andi $k0 $k0 0xfffe # !!
53 mtc0 $k0 $12 # !!
54
55 keep $at _at # why not use "sw $at _at" ? :)
56 keep $sp _sp # user stack
57 li $sp 0x90100000 # MARS restriction; we can use stack from now
58 push $a0
59 push $v0
60
61 mfc0 $k0 $13 # Cause register
62 srl $a0 $k0 2 # Extract ExcCode Field
63 andi $a0 $a0 0x1f
64 bne $a0 $zero kexc # Exception Code is 0 for interrupts
65
66 kint: # Just mark the interrupt
67 li $v0 4
68 la $a0 imsg
69 syscall
70 b intret
71
72 kexc: # No exceptions in the program, but just in case of one
73 b exret
74 exret: mfc0 $v0 $14
75 addi $v0 $v0 4 # Return to next instruction
76 mtc0 $v0 $14
77
78 intret:
79 pop $v0
80 pop $a0
81 undo $sp _sp
82 undo $at _at
83 mfc0 $k0 $12 # Set Status register
84 ori $k0 0x01 # Interrupts enabled
85 mtc0 $k0 $12 # write back to status
86 mtc0 $zero $13 # clean Cause
87 eret
This handler is (not completely) accurate:
- Disables interrupts
Keeps critical registers ($at and $sp)
- Keeps other useful registers on self-made kernel stack
- Calculates if it is exception or interrupt
- and jumps to corresponded code part
- (still not) checks whether many interrupts are pending (to handle'm all)
- Can handle exception with new EPC calculation (but does nothing for now)
- Restores all registers
- Enable interrupts
eret
Remember «as quick as possible» rule?
Kernel memory and MMIO/device interaction is performed by handler
Any interpretation of the interrupt results is better to be performed by user code
- It's common not to allow user code to access MMIO and kernel memory
⇒ we need a protocol for user/kernel data pass
Keyboard and Display MMIO Simulator
(serial) Console is the device than can input bytes from keyboard and output bytes to text screen:
0xffff0000 |
RcC |
Receive Control |
RW |
bit 0 — ready, bit 1 — interrupt enable |
0xffff0004 |
RcD |
Receive Data |
R |
byte just input |
0xffff0008 |
TxC |
Transmit Control |
RW |
bit 0 — ready, bit 1 — interrupt enable |
0xffff000c |
TxD |
Transmit Data |
W |
three high bytes — device control data, low byte — byte to output |
- When device is ready to trausmit/receive, the corresponded ready bit is set to 1
- When RcC ready is 0, no key was pressed (or keypress is not processed by console yet)
- When TxC ready is 0, console can not perform output (e. g. is busy transmitting previous byte)
- Device is (deliberately) slow, so frequent unready
Console polling example (note slowing execution speed under ~5 instructions per second)
Double poll problem (also remember, you need to reset the device before every run, like IRL)
1 li $t1 0
2 loop: beqz $t1 noout # check if we need to output
3 loopi: lb $t0 0xffff0008 # check output ready
4 andi $t0 $t0 1 # is it?
5 beqz $t0 loopi # u gotta! jump back
6 sb $t1 0xffff000c # output a byte
7 li $t1 0 # clear byte to be out
8 noout: lb $t0 0xffff0000 # check input ready
9 andi $t0 $t0 1 # is it?
10 beqz $t0 loop # repeat all the process
11 lb $t1 0xffff0004 # read a byte
12 b loop
We can make console transmit slower by moving «Delay length» slider ie. g. up to 20, so gain a situation when input is read, but output not, and the program is still polling for output.
Console interrupts
Bit 1 in RcC enables input interrupt, so we can handle keypress asynchronously.
1 li $a0 2
2 sw $a0 0xffff0000 # enable keyboard interrupt
3 li $a0 0
4 loop: beqz $a0 loop # infinite loop
5 beq $a0 0x1b done # finish when pressing ESC
6 li $v0 11 # print character stored by handler
7 syscall
8 li $a0 0 # make $a0 zer0 again
9 j loop
10 done: li $v0 10
11 syscall
12
13 .ktext 0x80000180 # THIS IS DIRTY!
14 lw $a0 0xffff0004 # store input to $a0
15 eret
Cleaner implementation (still do not check what interrupt is occurred):
- more or less fair handler
- character is stored in memory
- program does pointless things most of the time, but periodically check if something was pressed on keyboard
1 .text
2 .globl main
3 main:
4 mfc0 $a0 $12 # read from the status register
5 ori $a0 0xff11 # enable all interrupts
6 mtc0 $a0 $12 # write back to the status register
7
8 li $a0 2 # enable keyboard interrupt
9 sw $a0 0xffff0000
10
11 here:
12 jal sleep
13 lw $a0 ($gp) # print key stored in ($gp)
14 beqz $a0 here # no keypress
15 beq $a0 0x1b done # ESC terminates
16 li $v0 1
17 syscall
18 sw $zero ($gp)
19 j here
20 done: li $v0 10
21 syscall
22
23 .eqv ZZZ 100000
24 sleep: li $t0 ZZZ # Do nothing
25 tormo0: subi $t0 $t0 1
26 blez $t0 tormo1
27 j tormo0
28 tormo1: jr $ra
29
30 .ktext 0x80000180 # kernel code starts here
31
32 mfc0 $k0 $12 # !! disable interrupts
33 andi $k0 $k0 0xfffe # !!
34 mtc0 $k0 $12 # !!
35
36 move $k1 $at # save $at. User programs are not supposed to touch $k0 and $k1
37 sw $v0 s1 # We need to use these registers
38 sw $a0 s2 # not using the stack
39
40 mfc0 $k0 $13 # Cause register
41 srl $a0 $k0 2 # Extract ExcCode Field
42 andi $a0 $a0 0x1f
43 bne $a0 $zero kexc # Exception Code 0 is I/O. Only processing I/O here
44
45 lw $a0 0xffff0004 # get the input key
46 sw $a0 ($gp) # store key
47 li $a0 '.' # Show that we handled the interrupt
48 li $v0 11
49 syscall
50 j kdone
51
52 kexc: mfc0 $v0 $14 # No exceptions in the program, but just in case of one
53 addi $v0 $v0 4 # Return to next instruction
54 mtc0 $v0 $14
55 kdone:
56 lw $v0 s1 # Restore other registers
57 lw $a0 s2
58 move $at $k1 # Restore $at
59 mtc0 $zero $13
60
61 mfc0 $k0 $12 # Set Status register
62 ori $k0 0x01 # Interrupts enabled
63 mtc0 $k0 $12 # write back to status
64
65 eret
66
67 .kdata
68 s1: .word 10
69 s2: .word 11
Device control
Some devices, e. g. printers, consoles, ink graphers, modems etc. can not be fully controlled directly by special MMIO registers. They interpret the data they're receiving instead. This data is called «device control characters» (or control sequence).
Keyboard and Display MMIO Simulator:
- output byte 12, no control data — clear screen
- output byte 7 — cursor positioning, control data: column (bits 31-20), row (bits 19-8)
1 .macro text %reg
2 wait: lb $a0 0xffff0008 # wait until output is ready
3 andi $a0 $a0 1
4 beqz $a0 wait # if not, wait again
5 move $a0 %reg
6 sw $a0 0xffff000c
7 .end_macro
8
9 .macro pos %x %y %c
10 li $a0 %x
11 sll $a0 $a0 20 # X-coordinate
12 li $a1 %y
13 sll $a1 $a1 8 # Y-coordinate
14 or $a0 $a0 $a1
15 ori $a1 $a0 7
16 text $a1 # cursor poitioning
17 li $a1 %c
18 text $a1 # character output
19 .end_macro
20
21 .text
22 li $s1 12
23 text $s1
24 pos 1 1 '@'
25 pos 6 2 '@'
26 pos 12 4 '@'
27 pos 15 6 '@'
28 nop
29 nop
30 nop
31 nop
32 nop
This places some '@' at screen
Note five nops to wait until Console finally displays our character
Do not forget click Reset before every run
H/W
- None