summaryrefslogtreecommitdiff
path: root/assembly
diff options
context:
space:
mode:
Diffstat (limited to 'assembly')
-rw-r--r--assembly/rrxing2/Makefile24
-rw-r--r--assembly/rrxing2/rrxing2.S373
l---------assembly/rrxing2/rvcontroller.ld1
-rw-r--r--assembly/rrxing2/ui.S452
-rw-r--r--assembly/rrxing2/util.S43
5 files changed, 893 insertions, 0 deletions
diff --git a/assembly/rrxing2/Makefile b/assembly/rrxing2/Makefile
new file mode 100644
index 0000000..0d3b39b
--- /dev/null
+++ b/assembly/rrxing2/Makefile
@@ -0,0 +1,24 @@
+MARCH ?= rv32imacbzicntr_zicond_zicsr_zifencei_zihintpause_zilsd_zclsd_zabha_zacas_zbkb_zbkx_zcb_zcmp_zcmt_zicfilp
+
+.PHONY: all dump load clean
+
+all: rrxing2.hex
+
+rrxing2.o: rrxing2.S ui.S util.S
+ riscv32-none-elf-as -I../rvcontroller-libraries -march=${MARCH} -o rrxing2.o rrxing2.S
+
+rrxing2.elf: rrxing2.o
+ riscv32-none-elf-ld -T rvcontroller.ld --no-warn-rwx-segments -o rrxing2.elf rrxing2.o
+
+dump: rrxing2.elf
+ riscv32-none-elf-objdump -d rrxing2.elf
+
+rrxing2.hex: rrxing2.elf
+ riscv32-none-elf-objcopy -O ihex rrxing2.elf rrxing2.hex
+
+load: rrxing2.hex
+ bash -c "wl-copy < rrxing2.hex"
+
+clean:
+ rm -f rrxing2.hex rrxing2.elf rrxing2.o
+
diff --git a/assembly/rrxing2/rrxing2.S b/assembly/rrxing2/rrxing2.S
new file mode 100644
index 0000000..2118ca1
--- /dev/null
+++ b/assembly/rrxing2/rrxing2.S
@@ -0,0 +1,373 @@
+j _start
+
+.include "util.S"
+
+_start:
+li sp,0x10000 # Set up stack pointer
+la t0,handler
+csrw mtvec,t0 # Set up trap handler
+li t0,0x400
+csrc mstatush,t0 # Turn off MDT
+li a7,131
+ecall # Clear console input buffer
+li t0,0x60000
+csrw mie,t0 # Mask all interrupts except digilines and console
+li gp,0xffff0000
+csrw 0x801,gp # Map MMIO region at 0xffff0000
+li t1,0xffffffff
+mv t2,t1
+sd t1,10(gp) # Set mtimecmp to 0xffffffffffffffff
+csrw mip,zero # Clear any pending interrupts
+csrsi mstatus,0x8 # Turn on MIE to enable interrupts
+
+# Hardware initialization
+digiline_send lightchannel,lightoffmsg
+digiline_send gatechannel,gateupmsg
+digiline_send bellchannel,belloffmsg
+digiline_send signalchannel,lightredmsg
+
+.include "ui.S"
+
+.align 2
+handler:
+csrrw x31,mscratch,x31 # Free up x31 to hold the context address
+la x31,hcontext # Using x register numbers here since trap handlers don't use the usual calling convention
+sw x1,0(x31) # Save all the registers to the context
+sd x2,4(x31) # Two at a time since we have Zilsd/Zclsd
+sd x4,12(x31)
+sd x6,20(x31)
+sd x8,28(x31)
+sd x10,36(x31)
+sd x12,44(x31)
+sd x14,52(x31)
+sd x16,60(x31)
+sd x18,68(x31)
+sd x20,76(x31)
+sd x22,84(x31)
+sd x24,92(x31)
+sd x26,100(x31)
+sd x28,108(x31)
+sw x30,116(x31)
+csrr x30,mscratch # Get the old x31 value back into a register
+sw x30,120(x31) # and write that back out too
+
+li t0,0x400
+csrw mseccfg,t0 # Turn on MLPE (enable Zicfilp)
+
+csrr t0,mcause # Read why the trap occurred
+
+li t1,0x80000011 # Interrupt 17 = digilines
+beq t0,t1,handler_digilines
+
+addi t1,t1,1 # Interrupt 18 = console
+beq t0,t1,handler_console
+
+li t1,0x80000007 # Interrupt 7 = timer
+beq t0,t1,handler_timer
+
+j handler_other # Something else
+
+handler_digilines:
+li t0,131072 # 2^17, value of MDIP bit
+csrc mip,t0 # Acknowledge digilines interrupt
+la a0,channelbuf
+li a1,16
+la a2,msgbuf
+li a3,16
+li a7,135
+ecall # Read the next digilines message
+
+la t0,channelbuf
+ld t1,0(t0)
+li t3,0x65746564 # 'dete'
+bne t1,t3,1f # not the detector
+li t3,0x00ffffff
+and t2,t2,t3
+li t3,0x00007463 # 'ct\0\0'
+bne t2,t3,1f # not the detector
+
+la t0,currstate
+lb t0,0(t0) # Read current state from RAM
+andi t0,t0,0xf # Mask out the country
+bnez t0,2f
+# State 0: start sequence
+la t1,country
+lb t1,0(t1) # Read current country setting
+slli t1,t1,4 # Multiply by 16 to get the offset into the state table
+addi t1,t1,1 # And add 1 to get to the first non-idle state
+la t0,currstate
+sb t1,0(t0) # Update state in RAM to the first state in the current country
+la t0,states
+sh2add t0,t1,t0 # Calculate the proper offset in the list of pointers (state number * 4 + base address)
+lw t0,0(t0) # Load the pointer
+jalr ra,t0,0 # And call that function
+
+2: la t0,currstate
+lb s0,0(t0) # Re-read current state from RAM
+li t1,5
+beq s0,t1,1f # State 5: reset timer
+li t1,21
+beq s0,t1,1f # State 21 (UK state 5): reset timer
+li t1,35
+beq s0,t1,1f # State 35 (US state 3): reset timer
+j 2f # None of the above, skip over the reset
+1: set_timer_to extensiontime
+2: li a7,133
+ecall # Check if there are more messages to process
+bnez a0,handler_digilines
+j handler_epilogue
+
+handler_timer:
+stop_timer # Stopping timer acknowledges the interrupt too
+la t2,currstate
+lb s0,0(t2) # Read current state from RAM
+beqz s0,handler_epilogue # State 0: timer shouldn't have fired in the first place
+li t3,16
+beq s0,t3,handler_epilogue # State 16 (UK state 0): timer shouldn't have fired in the first place
+li t3,32
+beq s0,t3,handler_epilogue # State 32 (US state 0): timer shouldn't have fired in the first place
+addi s0,s0,1 # Increment to get to next state
+sb s0,0(t2) # Write new state to RAM
+la t1,4
+mul t0,s0,t1
+la t1,states
+add t0,t0,t1 # Calculate address of function address for new state
+lw t1,0(t0) # Read what the function address should be
+addi t2,s0,1 # Landing pad label is state number + 1 (to avoid having any of them be 0)
+slli t2,t2,12
+jalr ra,t1,0 # And call the proper function
+# Fall through to epilogue
+
+
+handler_epilogue:
+csrr t0,mip # Check if there are any interrupts still pending
+bexti t1,t0,7 # Bit 7 = MTIP (timer)
+bnez t1,handler_timer
+bexti t1,t0,17 # Bit 17 = MDIP (digilines)
+bnez t1,handler_digilines
+bexti t1,t0,18 # Bit 18 = MCIP (console)
+bnez t1,handler_console
+la x31,hcontext # x numbers here for the same reason as during the save
+lw x1,0(x31) # Load all of the registers from the context
+ld x2,4(x31) # Two at a time since we have Zilsd/Zclsd
+ld x4,12(x31)
+ld x6,20(x31)
+ld x8,28(x31)
+ld x10,36(x31)
+ld x12,44(x31)
+ld x14,52(x31)
+ld x16,60(x31)
+ld x18,68(x31)
+ld x20,76(x31)
+ld x22,84(x31)
+ld x24,92(x31)
+ld x26,100(x31)
+ld x28,108(x31)
+ld x30,116(x31)
+mret # Return to the program
+
+handler_other:
+li a7,4
+la a0,uiunimplementedtrap
+ecall # Display unimplemented trap message
+
+csrr a0,mcause # Read what the cause was
+li a7,1
+ecall # Tell the user
+
+li a7,10
+ecall # Then halt
+
+states: .word destate0,destate1,destate2,destate3,destate4,destate5,destate6,0,0,0,0,0,0,0,0,0
+.word ukstate0,ukstate1,ukstate2,ukstate3,ukstate4,ukstate5,ukstate6,ukstate7,0,0,0,0,0,0,0,0
+.word usstate0,usstate1,usstate2,usstate3,usstate4,0,0,0,0,0,0,0,0,0,0,0
+
+.align 2
+destate0:
+lpad 1
+stop_timer
+digiline_send lightchannel,lightoffmsg
+digiline_send gatechannel,gateupmsg
+digiline_send bellchannel,belloffmsg
+digiline_send signalchannel,lightredmsg
+ret
+
+.align 2
+destate1:
+lpad 2
+digiline_send lightchannel,lightyellowmsg
+set_timeri 2
+ret
+
+.align 2
+destate2:
+lpad 3
+digiline_send bellchannel,bellonmsg
+set_timeri 3
+ret
+
+.align 2
+destate3:
+lpad 4
+digiline_send lightchannel,lightredmsg
+set_timer_to gatedroptime
+ret
+
+.align 2
+destate4:
+lpad 5
+digiline_send gatechannel,gatedownmsg
+set_timeri 3
+ret
+
+.align 2
+destate5:
+lpad 6
+digiline_send signalchannel,lightgreenmsg
+digiline_send bellchannel,belloffmsg
+set_timer_to activetime
+ret
+
+.align 2
+destate6:
+lpad 7
+digiline_send signalchannel,lightredmsg
+digiline_send gatechannel,gateupmsg
+set_timeri 2
+la t0,currstate
+li t1,-1
+sb t1,0(t0) # Next state should be 0
+ret
+
+.align 2
+ukstate0:
+lpad 17
+stop_timer
+digiline_send lightchannel,lightoffmsg
+digiline_send gatechannel,gateupmsg
+digiline_send bellchannel,belloffmsg
+digiline_send signalchannel,lightredmsg
+ret
+
+.align 2
+ukstate1:
+lpad 18
+digiline_send lightchannel,lightyellowmsg
+digiline_send bellchannel,bellonmsg
+set_timeri 3
+ret
+
+.align 2
+ukstate2:
+lpad 19
+digiline_send lightchannel,lightredmsg
+set_timeri 1
+ret
+
+.align 2
+ukstate3:
+lpad 20
+digiline_send lightchannel,lightflashredmsg
+set_timer_to gatedroptime
+ret
+
+.align 2
+ukstate4:
+lpad 21
+digiline_send gatechannel,gatedownmsg
+set_timeri 3
+ret
+
+.align 2
+ukstate5:
+lpad 22
+digiline_send signalchannel,lightgreenmsg
+set_timer_to activetime
+ret
+
+.align 2
+ukstate6:
+lpad 23
+digiline_send signalchannel,lightredmsg
+digiline_send bellchannel,belloffmsg
+set_timeri 1
+ret
+
+.align 2
+ukstate7:
+lpad 24
+digiline_send gatechannel,gateupmsg
+set_timeri 3
+la t0,currstate
+li t1,15
+sb t1,0(t0) # Next state should be 16 (UK state 0)
+ret
+
+.align 2
+usstate0:
+lpad 33
+stop_timer
+digiline_send lightchannel,lightoffmsg
+digiline_send gatechannel,gateupmsg
+digiline_send bellchannel,belloffmsg
+digiline_send signalchannel,lightredmsg
+ret
+
+.align 2
+usstate1:
+lpad 34
+digiline_send lightchannel,lightflashredmsg
+digiline_send bellchannel,bellonmsg
+set_timer_to gatedroptime
+ret
+
+.align 2
+usstate2:
+lpad 35
+digiline_send gatechannel,gatedownmsg
+set_timeri 3
+ret
+
+.align 2
+usstate3:
+lpad 36
+digiline_send bellchannel,belloffmsg
+digiline_send signalchannel,lightgreenmsg
+set_timer_to activetime
+ret
+
+.align 2
+usstate4:
+lpad 37
+digiline_send signalchannel,lightredmsg
+digiline_send gatechannel,gateupmsg
+set_timeri 3
+la t0,currstate
+li t1,31
+sb t1,0(t0) # Next state should be 32 (US state 0)
+ret
+
+hcontext: .skip 32*4
+currstate: .byte 0
+country: .byte 0 # 0 = DE, 1 = UK, 2 = US
+gatedroptime: .byte 3
+activetime: .byte 15
+extensiontime: .byte 25
+
+uiunimplementedtrap: .asciz "Unimplemented trap!\nmcause: "
+channelbuf: .skip 16
+msgbuf: .skip 16
+lightchannel: .asciz "light"
+bellchannel: .asciz "bell"
+gatechannel: .asciz "gate"
+signalchannel: .asciz "signal"
+lightoffmsg: .asciz "OFF"
+lightredmsg: .asciz "RED"
+lightyellowmsg: .asciz "YELLOW"
+lightgreenmsg: .asciz "GREEN"
+lightflashredmsg: .asciz "FLASHRED"
+belloffmsg: .asciz "off"
+bellonmsg: .asciz "on"
+gateupmsg: .asciz "up"
+gatedownmsg: .asciz "down"
+
diff --git a/assembly/rrxing2/rvcontroller.ld b/assembly/rrxing2/rvcontroller.ld
new file mode 120000
index 0000000..bc01402
--- /dev/null
+++ b/assembly/rrxing2/rvcontroller.ld
@@ -0,0 +1 @@
+../../rvcontroller.ld \ No newline at end of file
diff --git a/assembly/rrxing2/ui.S b/assembly/rrxing2/ui.S
new file mode 100644
index 0000000..ef3734d
--- /dev/null
+++ b/assembly/rrxing2/ui.S
@@ -0,0 +1,452 @@
+mainloop:
+la ra,uidrawend # Will call a function to draw the current page, but it will return to the end instead of here
+la t0,uipage
+lbu t0,0(t0)
+
+beqz t0,uidrawidle # Page 0: Idle screen
+
+li t1,1
+beq t0,t1,uidrawmainmenu # Page 1: Main menu
+
+li t1,2
+beq t0,t1,uidrawcountryselect # Page 2: Country selection
+
+li t1,3
+beq t0,t1,uidrawadjusttiming # Page 3: Timing adjustments
+
+li t1,4
+beq t0,t1,uidrawgatedropdelay # Page 4: Gate drop delay adjustment
+
+li t1,5
+beq t0,t1,uidrawinitialtime # Page 5: Initial active time adjustment
+
+li t1,6
+beq t0,t1,uidrawextensiontime # Page 6: Extension time adjustment
+
+li t1,7
+beq t0,t1,uidrawinvalidinput # Page 7: Invalid (time) input
+
+j uidrawunknown
+
+uidrawend:
+wfi # Program is entirely interrupt-driven, so wait for something to happen
+j mainloop
+
+handler_console:
+li t0,262144 # 2^18, value of MCIP bit
+csrc mip,t0
+la ra,uihandlingend # Will call a function to handle input for the current page, but it will return to the end instead of here
+la t0,uipage
+lbu t0,0(t0)
+
+beqz t0,uihandleidle # Page 0: Idle screen
+
+li t1,1
+beq t0,t1,uihandlemainmenu # Page 1: Main menu
+
+li t1,2
+beq t0,t1,uihandlecountryselect # Page 2: Country selection
+
+li t1,3
+beq t0,t1,uihandleadjusttiming # Page 3: Timing adjustments
+
+li t1,4
+beq t0,t1,uihandlegatedropdelay # Page 4: Gate drop delay adjustment
+
+li t1,5
+beq t0,t1,uihandleinitialtime # Page 5: Initial active time adjustment
+
+li t1,6
+beq t0,t1,uihandleextensiontime # Page 6: Extension time adjustment
+
+li t1,7
+beq t0,t1,uihandleinvalidinput # Page 7: Invalid (time) input
+
+uihandlingend:
+li a7,131
+ecall # Drop anything else the user may have typed
+j handler_epilogue
+
+copystring:
+# Destination pointer in a0, source pointer in a1, copies until null is reached (but doesn't copy the null), not very optimized
+lb t0,0(a1)
+beqz t0,1f
+sb t0,0(a0)
+addi a0,a0,1
+addi a1,a1,1
+j copystring
+1: ret
+
+uidrawidle:
+cm.push {ra},-16
+la a0,uiidle+44
+la a3,uiidle
+la t0,currstate # Check if currently running or not
+lbu t0,0(t0)
+andi t0,t0,0xf
+beqz t0,1f
+la a0,uiactive+44 # If so, then show the active message instead
+la a3,uiactive
+1: la a1,uide
+la t0,country
+lb t0,0(t0)
+li t1,1
+bne t0,t1,1f
+la a1,uiuk
+1: li t1,2
+bne t0,t1,1f
+la a1,uius
+1: call copystring
+mv a0,a3
+li a7,4
+ecall
+cm.popret {ra},16
+
+uihandleidle:
+cm.push {ra},-16
+li a7,132
+ecall # Read one character of input
+li t0,'m'
+bne a0,t0,1f # If it was 'm'...
+la t0,uipage
+li t1,1
+sb t1,0(t0) # ...go to the main menu (page 1)
+1: li t0,'a'
+bne a0,t0,1f # If it was 'a', then start the sequence now
+la t0,currstate
+lbu t0,0(t0)
+andi t0,t0,0xf
+bnez t0,1f # Unless it's already running, that is
+la t1,country
+lb t1,0(t1) # Read current country setting
+slli t1,t1,4 # Multiply by 16 to get the offset into the state table
+addi t1,t1,1 # And add 1 to get to the first non-idle state
+la t0,currstate
+sb t1,0(t0) # Update state in RAM to the first state in the current country
+la t0,states
+sh2add t0,t1,t0 # Calculate the proper offset in the list of pointers (state number * 4 + base address)
+lw t0,0(t0) # Load the pointer
+jalr ra,t0,0 # And call that function
+1: cm.popret {ra},16
+ret
+
+uidrawmainmenu:
+la a0,uimainmenu
+li a7,4
+ecall
+ret
+
+uihandlemainmenu:
+li a7,132
+ecall # Read one character of input
+li t0,'1'
+bne a0,t0,1f
+la t1,uipage
+li t2,2
+sb t2,0(t1) # User entered '1', go to country selection (page 2)
+1: li t0,'2'
+bne a0,t0,1f
+la t1,uipage
+li t2,3
+sb t2,0(t1) # User entered '2', go to timing adjustment (page 3)
+1: li t0,'0'
+bne a0,t0,1f
+la t1,uipage
+sb x0,0(t1) # User entered '0', go to idle screen (page 0)
+1: ret
+
+uidrawcountryselect:
+la a0,uicountryselect
+li a7,4
+ecall
+ret
+
+uihandlecountryselect:
+li a7,132
+ecall # Read one character of input
+li t0,'1'
+bne a0,t0,1f
+la t1,country
+sb x0,0(t1) # User entered '1', change country to DE (0)
+la t1,uipage
+li t2,1
+sb t2,0(t1) # Return to main menu (page 1)
+1: li t0,'2'
+bne a0,t0,1f
+la t1,country
+li t2,1
+sb t2,0(t1) # User entered '2', change country to UK (1)
+la t1,uipage
+li t2,1
+sb t2,0(t1) # Return to main menu (page 1)
+1: li t0,'3'
+bne a0,t0,1f
+la t1,country
+li t2,2
+sb t2,0(t1) # User entered '3', change country to US (2)
+la t1,uipage
+li t2,1
+sb t2,0(t1) # Return to main menu (page 1)
+1: li t0,'0'
+bne a0,t0,1f
+la t1,uipage
+li t2,1
+sb t2,0(t1) # User entered '0', return to main menu (page 1) without changing anything
+1: ret
+
+uidrawadjusttiming:
+la a0,uiadjusttiming
+li a7,4
+ecall
+ret
+
+uihandleadjusttiming:
+li a7,132
+ecall # Read one character of input
+li t0,'1'
+bne a0,t0,1f
+la t1,uipage
+li t2,4
+sb t2,0(t1) # User entered '1', go to gate drop time setting (page 4)
+1: li t0,'2'
+bne a0,t0,1f
+la t1,uipage
+li t2,5
+sb t2,0(t1) # User entered '2', go to initial active time setting (page 5)
+1: li t0,'3'
+bne a0,t0,1f
+la t1,uipage
+li t2,6
+sb t2,0(t1) # User entered '3', go to extension time setting (page 6)
+1: li t0,'0'
+bne a0,t0,1f
+la t1,uipage
+li t2,1
+sb t2,0(t1) # User entered '0', go to main menu (page 1)
+1: ret
+ret
+
+uidrawgatedropdelay:
+la a0,uigatedropdelay
+la t0,gatedroptime
+lbu t0,0(t0) # Load the current setting
+li t1,10
+div t2,t0,t1 # Split it into tens and ones places
+rem t3,t0,t1
+addi t2,t2,0x30 # Convert to ASCII
+addi t3,t3,0x30
+sb t2,27(a0) # Insert the digits into the string
+sb t3,28(a0)
+li a7,4
+ecall # And print it
+ret
+
+uihandlegatedropdelay:
+# Read first digit
+li a7,132
+ecall
+
+# Check if the user wanted to cancel
+li t0,'c'
+bne a0,t0,1f
+la t0,uipage
+li t1,3 # User chose to cancel, go back to timing adjustment (page 3)
+sb t1,0(t0)
+ret
+1:
+
+# Validate and process first digit
+addi s0,a0,-0x30 # Convert the first digit to ASCII
+li t0,10
+bltu s0,t0,1f # Inputs less than 0x30 become negative in the addi above, this branch is unsigned so those become large positive numbers
+la t0,uipage # ^ Thank the RISC-V manual for that trick
+li t1,7
+sb t1,0(t0) # First digit was not valid, go to invalid input error (page 7)
+ret
+1: mul s0,s0,t0 # Multiply the first digit by 10
+
+# Read second digit
+li a7,132
+ecall
+bnez a0,1f
+div s0,s0,t0 # Second digit was null, so the first digit was really the ones place
+li a0,0x30 # Pretend this digit was 0 so validation still passes but the addition doesn't change anything
+1: addi a0,a0,-0x30
+bltu a0,t0,1f # Inputs less than 0x30 become negative in the addi above
+la t0,uipage
+li t1,7
+sb t1,0(t0) # Second digit was not valid, go to invalid input error (page 7)
+ret
+1: add s0,s0,a0 # Sum the two digits together
+bnez s0,1f
+la t0,uipage
+li t1,7
+sb t1,0(t0) # Input was 0, go to invalid input error (page 7)
+ret
+1: la t0,gatedroptime
+sb s0,0(t0) # Write out the new setting
+la t0,uipage
+li t1,3 # Go back to timing adjustment (page 3)
+sb t1,0(t0)
+ret
+
+uidrawinitialtime:
+la a0,uiinitialtime
+la t0,activetime
+lbu t0,0(t0) # Load the current setting
+li t1,10
+div t2,t0,t1 # Split it into tens and ones places
+rem t3,t0,t1
+addi t2,t2,0x30 # Convert to ASCII
+addi t3,t3,0x30
+sb t2,29(a0) # Insert the digits into the string
+sb t3,30(a0)
+li a7,4
+ecall # And print it
+ret
+
+uihandleinitialtime:
+# Read first digit
+li a7,132
+ecall
+
+# Check if the user wanted to cancel
+li t0,'c'
+bne a0,t0,1f
+la t0,uipage
+li t1,3 # User chose to cancel, go back to timing adjustment (page 3)
+sb t1,0(t0)
+ret
+1:
+
+# Validate and process first digit
+addi s0,a0,-0x30 # Convert the first digit to ASCII
+li t0,10
+bltu s0,t0,1f # Inputs less than 0x30 become negative in the addi above, this branch is unsigned so those become large positive numbers
+la t0,uipage # ^ Thank the RISC-V manual for that trick
+li t1,7
+sb t1,0(t0) # First digit was not valid, go to invalid input error (page 7)
+ret
+1: mul s0,s0,t0 # Multiply the first digit by 10
+
+# Read second digit
+li a7,132
+ecall
+bnez a0,1f
+div s0,s0,t0 # Second digit was null, so the first digit was really the ones place
+li a0,0x30 # Pretend this digit was 0 so validation still passes but the addition doesn't change anything
+1: addi a0,a0,-0x30
+bltu a0,t0,1f # Inputs less than 0x30 become negative in the addi above
+la t0,uipage
+li t1,7
+sb t1,0(t0) # Second digit was not valid, go to invalid input error (page 7)
+ret
+1: add s0,s0,a0 # Sum the two digits together
+bnez s0,1f
+la t0,uipage
+li t1,7
+sb t1,0(t0) # Input was 0, go to invalid input error (page 7)
+ret
+1: la t0,activetime
+sb s0,0(t0) # Write out the new setting
+la t0,uipage
+li t1,3 # Go back to timing adjustment (page 3)
+sb t1,0(t0)
+ret
+
+uidrawextensiontime:
+la a0,uiextensiontime
+la t0,extensiontime
+lbu t0,0(t0) # Load the current setting
+li t1,10
+div t2,t0,t1 # Split it into tens and ones places
+rem t3,t0,t1
+addi t2,t2,0x30 # Convert to ASCII
+addi t3,t3,0x30
+sb t2,26(a0) # Insert the digits into the string
+sb t3,27(a0)
+li a7,4
+ecall # And print it
+ret
+
+uihandleextensiontime:
+# Read first digit
+li a7,132
+ecall
+
+# Check if the user wanted to cancel
+li t0,'c'
+bne a0,t0,1f
+la t0,uipage
+li t1,3 # User chose to cancel, go back to timing adjustment (page 3)
+sb t1,0(t0)
+ret
+1:
+
+# Validate and process first digit
+addi s0,a0,-0x30 # Convert the first digit to ASCII
+li t0,10
+bltu s0,t0,1f # Inputs less than 0x30 become negative in the addi above, this branch is unsigned so those become large positive numbers
+la t0,uipage # ^ Thank the RISC-V manual for that trick
+li t1,7
+sb t1,0(t0) # First digit was not valid, go to invalid input error (page 7)
+ret
+1: mul s0,s0,t0 # Multiply the first digit by 10
+
+# Read second digit
+li a7,132
+ecall
+bnez a0,1f
+div s0,s0,t0 # Second digit was null, so the first digit was really the ones place
+li a0,0x30 # Pretend this digit was 0 so validation still passes but the addition doesn't change anything
+1: addi a0,a0,-0x30
+bltu a0,t0,1f # Inputs less than 0x30 become negative in the addi above
+la t0,uipage
+li t1,7
+sb t1,0(t0) # Second digit was not valid, go to invalid input error (page 7)
+ret
+1: add s0,s0,a0 # Sum the two digits together
+bnez s0,1f
+la t0,uipage
+li t1,7
+sb t1,0(t0) # Input was 0, go to invalid input error (page 7)
+ret
+1: la t0,extensiontime
+sb s0,0(t0) # Write out the new setting
+la t0,uipage
+li t1,3 # Go back to timing adjustment (page 3)
+sb t1,0(t0)
+ret
+
+uidrawinvalidinput:
+la a0,uiinvalidinput
+li a7,4
+ecall
+ret
+
+uihandleinvalidinput:
+la t0,uipage
+li t1,3 # Go back to timing adjustment (page 3)
+sb t1,0(t0)
+ret
+
+uidrawunknown:
+la a0,uiunknown
+li a7,4
+ecall
+ret
+
+uipage: .byte 0
+uiidle: .asciz "\nRailroad Crossing 2\nfor RVController\nMode: ??\n\nPress 'm' for menu\n'a' to activate now"
+uiactive: .asciz "\nRailroad Crossing 2\nfor RVController\nMode: ??\nCrossing is active\n\nPress 'm' for menu"
+uimainmenu: .asciz "\nMAIN MENU\n1: Change Country\n2: Adjust Timing\n\n\n0: Exit"
+uiunknown: .asciz "\nInternal error\n\nUnknown UI page\n\n\n"
+uicountryselect: .asciz "\nCHANGE COUNTRY\n1: Germany\n2: United Kingdom\n3: United States\n\n0: Cancel"
+uiadjusttiming: .asciz "\nADJUST TIMING\n1: Gate Drop Delay\n2: Init. Active Time\n3: Extension Time\n\n0: Cancel"
+uigatedropdelay: .asciz "\nGATE DROP DELAY\n\nCurrent: XX sec\n\nEnter new time or\n'c' to cancel:"
+uiinitialtime: .asciz "\nINIT. ACTIVE TIME\n\nCurrent: XX sec\n\nEnter new time or\n'c' to cancel:"
+uiextensiontime: .asciz "\nEXTENSION TIME\n\nCurrent: XX sec\n\nEnter new time or\n'c' to cancel:"
+uiinvalidinput: .asciz "\nInvalid entry\n\nPress any key\n\n\n"
+uide: .asciz "DE"
+uiuk: .asciz "UK"
+uius: .asciz "US"
diff --git a/assembly/rrxing2/util.S b/assembly/rrxing2/util.S
new file mode 100644
index 0000000..9dda1ab
--- /dev/null
+++ b/assembly/rrxing2/util.S
@@ -0,0 +1,43 @@
+.macro digiline_send channel msg
+ la a0,(\channel)
+ la a1,(\msg)
+ li a7,129
+ ecall
+.endm
+
+.macro set_timeri time
+ li t0,0x80
+ csrs mie,t0 # unmask timer interrupt
+ ld t1,2(gp) # read mtime
+ li t0,(\time)
+ add t1,t1,t0 # calculate target value (lower half)
+ sltu t0,t1,t0 # check if it overflowed
+ add t2,t2,t0 # add the overflow into the high half if there was any
+ sd t1,10(gp) # set mtimecmp to the target value
+.endm
+
+.macro stop_timer
+ li t1,0xffffffff
+ li t2,0xffffffff
+ sd t1,10(gp) # set mtime to far in the future
+ li t1,0x80
+ csrc mie,t1 # mask timer interrupt
+.endm
+
+.macro set_timer_to label
+ cm.push {ra},-16
+ la a0,(\label)
+ lbu a0,0(a0)
+ call set_timer
+ cm.pop {ra},16
+.endm
+
+set_timer:
+li t0,0x80
+csrs mie,t0 # unmask timer interrupt
+ld t1,2(gp) # read mtime
+add t1,t1,a0 # calculate target value (lower half)
+sltu t0,t1,a0 # check if it overflowed
+add t2,t2,t0 # add the overflow into the high half if there was any
+sd t1,10(gp) # set mtimecmp to the target value
+ret