10. Interrupts

Interrupt:

Hardware requirements:

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

The Cause C0 register

bits

31

30-16

15-8

7

6-2

1-0

target

Br

unused

Pending interrupts

unused

Exception code

unused

Policy:

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:

Remember «as quick as possible» rule?

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

Console polling example (note slowing execution speed under ~5 instructions per second)

   1 loop:   lb      $t0 0xffff0000          # check input ready
   2         andi    $t0 $t0 1               # is it?
   3         beqz    $t0 loop                # to check again
   4         lb      $a0 0xffff0004          # read character
   5         li      $v0 11                  # print it
   6         syscall
   7         b       loop

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):

   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:

   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 :)

H/W

HSE/ArchitectureASM/10_Interrupts (последним исправлял пользователь FrBrGeorge 2019-12-20 18:53:40)