A Simple Multi-Tasking Operating System in ARM Assembly


Improve your writing skills in 5 minutes a day with the Daily Writing Tips email newsletter.

This project was done for the ‘Computer Organization and Assembly Language’ class, with Prof. Borin from Unicamp.

Below you’ll find the code that runs a very simple operating system in ARM Assembly. On system start the OS will configure some pieces of hardware (e.g., GPT, UART and TZIC) and it will setup the environment (supervisor mode stacks, user stacks, etc).

The code obviously needs root privileges to run (so you can’t run it inside another OS), and the user code is supposed to be assembled on position 0x8000 to be executed correctly.

The OS enables multi-tasking by using the preemptive scheduling technique. Basically the GPT interrupts the OS every 100 cycles and the scheduler switches between processes. The OS also handles some syscalls (e.g., write, fork, getpid and exit).

@--Configurable STACK values for each ARM core operation mode
.set USR_STACK, 0x11000
.set SVC_STACK, 0x10800
.set UND_STACK, 0x07c00
.set ABT_STACK, 0x07800
.set FIQ_STACK, 0x07400
.set IRQ_STACK, 0x07000

@--Interruption Vector
.org 0x0
b systemStart
.org 0x8
b swInterruption
.org 0x18
b gptInterruption

@-----------------System start/reset------------------@
.org 0x20
systemStart:

@--Set CPSR to Supervisor mode, IRQ/FIQ disabled
msr CPSR_c, #0xD3 

@---------------GPT CONFIGURATION---------------------@
@--Enable GPT and set Clock Source
mov r0, #0x41
ldr r1, =0x53FA0000
str r0, [r1]

@--Set GPT_OCR1 to 100
mov r0, #0x64
ldr r1, =0x53FA0010
str r0, [r1]

@--Enable Channel 1 Interruptions
mov r0, #1
ldr r1, =0x53FA000C
str r0, [r1]

@---------------TZIC CONFIGURATION--------------------@
@--TZIC interruption 39 as non-secure
ldr r1, =0x0FFFC000
mov r0, #(1<<7)
str r0, [r1, #0x84]

@--TZIC enable interruption 39
str r0, [r1, #0x104]

@--TZIC configure interrupt 39 priority as 1
ldr r0, [r1, #0x424]
bic r0, r0, #0xFF000000
mov r2, #1
orr r0, r0, r2, lsl #24
str r0, [r1, #0x424]

@--TZIC configure PRIOMASK as 0
eor r0, r0, r0
str r0, [r1, #0xC]

@--TZIC enable interruptions controller
mov r0, #1
str r0, [r1, #0x0]

@----------------UART CONFIGURATION------------------@
@--Enable UART
ldr r1, =0x53FBC080
mov r0, #1
str r0, [r1]
@--Set hardware flow control, data format and enable trans/receiver
ldr r0, =0x2127
str r0, [r1, #4]!
@--Set UCR3[RXDMUXSEL] = 1
ldr r0, =0x0704
str r0, [r1, #4]!
@--Set CTS trigger level to 31
ldr r0, =0x7C00
str r0, [r1, #4]!
@--Set internal clock divider = 5
ldr r0, =0x089E
str r0, [r1, #4]!
@--Set baud rate to 921.6kbps
ldr r0, =0x08FF
str r0, [r1, #20]!
ldr r0, =0x0C34
str r0, [r1, #4]

@--Initializa Stack Points of All Modes
mov sp, #SVC_STACK 
msr CPSR_c, #0xDF  @ Enter system mode, FIQ/IRQ disabled
mov sp, #USR_STACK
msr CPSR_c, #0xD1  @ Enter FIQ mode, FIQ/IRQ disabled
mov sp, #FIQ_STACK
msr CPSR_c, #0xD2  @ Enter IRQ mode, FIQ/IRQ disabled
mov sp, #IRQ_STACK
msr CPSR_c, #0xD7  @ Enter abort mode, FIQ/IRQ disabled
mov sp, #ABT_STACK
msr CPSR_c, #0xDB  @ Enter undefined mode, FIQ/IRQ disabled
mov sp, #UND_STACK

@--Initialize array of active processes
ldr r1, =activeProcesses
@--Process 0 is active
mov r0, #1
strb r0, [r1], #1
@--All others are inactive
mov r0, #0
mov r2, #7
loop1:
  cmp r2, #0
  beq endLoop1
  strb r0, [r1], #1
  sub r2, r2, #1
  b loop1
endLoop1:

@--Set currentProcess=0
ldr r1, =currentProcess
mov r0, #0
str r0, [r1]

@--Change to User mode, enable interrupts  and Jump to user code
msr CPSR_c, #0x10
mov pc, #0x8000

@----------------GPT Interruption Handler------------@
gptInterruption:
  @-Set GPT_SR to 1 to acknowledge interruption
  ldr r1, =0x53FA0008
  mov r0, #1
  str r0, [r1]
  @-Jump to Main Scheduler
  b mainSchedulerSaveFirst

@---------------SW Interruption---------------------@
swInterruption:
  @-Set CPSR to Supervisor mode, IRQ/FIQ disabled
  msr CPSR_c, #0xD3
  @-Check syscall number
  cmp r7, #4
  beq writeSyscall
  cmp r7, #1
  beq exitSyscall
  cmp r7, #2
  beq forkSyscall
  cmp r7, #20
  beq getpidSyscall

  @------Write Syscall
  writeSyscall:
  push {r4-r6}
  ldr r3, =0x53FBC094
  ldr r5, =0x53FBC040
  mov r0, r2
  mov r0, #(1<<13)
  writeLoop:
    cmp r2, #0
    beq doneWriting
    @--Wait for the transmission queue be ready
    transmitterLoop:
      ldr r4, [r3]
      and r4, r4, r0
      cmp r4, #0
      beq transmitterLoop
    ldrb r6, [r1], #1
    strb r6, [r5]
    sub r2, r2, #1
    b writeLoop
  doneWriting:
  pop {r4-r6}
  b exitSwInterruption

  @-----GetPid Syscall
  getpidSyscall:
  ldr r0, =currentProcess
  ldr r0, [r0]
  add r0, r0, #1
  b exitSwInterruption

  @-----Fork Syscall
  forkSyscall:
  push {r1-r3}
  @-Find available id to fork
  mov r0, #0
  ldr r1, =activeProcesses
  findLoop:
    cmp r0, #8
    beq noProcessAvailable
    ldrb r3, [r1, r0]
    cmp r3, #0
    beq foundAvailable
    add r0, r0, #1
    b findLoop
  foundAvailable:
  @-Mark process id as active
  mov r2, #1
  strb r2, [r1, r0]
  @-Save return address
  ldr r1, =returnArray
  str r14, [r1, r0, lsl #2]
  @-Get address of contexts array
  ldr r1, =p1context
  @-Move to right process context
  add r1, r1, r0, lsl #6
  @-Save CPSR
  mrs r2, SPSR
  str r2, [r1], #4
  @-Save Registers r0-r3
  mov r2, #0
  str r2, [r1], #4
  pop {r2}
  str r2, [r1], #4
  pop {r2}
  str r2, [r1], #4
  pop {r2}
  str r2, [r1], #4
  @-Save Registers r4-r12
  mov r2, r4
  str r2, [r1], #4
  mov r2, r5
  str r2, [r1], #4
  mov r2, r6
  str r2, [r1], #4
  mov r2, r7
  str r2, [r1], #4
  mov r2, r8
  str r2, [r1], #4
  mov r2, r9
  str r2, [r1], #4
  mov r2, r10
  str r2, [r1], #4
  mov r2, r11
  str r2, [r1], #4
  mov r2, r12
  str r2, [r1], #4
  @----Set r13 appropriately
  push {r4-r8}
  ldr r2, =currentProcess
  ldr r2, [r2]
  @-Point r3 to stack of children process
  ldr r3, =0x11000
  sub r3, r3, r0, lsl #12
  @-Poiont r4 to stack of parent process
  ldr r4, =0x11000
  sub r4, r4, r2, lsl #12
  @-Go to System Mode to recover r13 and r14
  msr CPSR_c, #0xDF   
  mov r5, r13
  mov r6, r14
  @-Back to Supervidor Mode
  msr CPSR_c, #0xD3
  @-Loop to copy over stack
  CopyStack:
    cmp r4, r5
    blt doneCopyingStack
    ldr r7, [r4], #-4
    str r7, [r3], #-4
    b CopyStack
  doneCopyingStack:
  @-Adjust stack pointer
  add r3, r3, #4
  @-Save r13 and r14 on context array
  str r3, [r1], #4
  str r6, [r1]
  pop {r4-r8}

  @-Increment process id (1-indexed instead of 0-indexed) and return
  add r0, r0, #1
  b exitSwInterruption  

  noProcessAvailable:
  mov r0, #-1
  b exitSwInterruption    

  @-----Exit Syscall
  exitSyscall:
  @-Get currentProcess id
  ldr r0, =currentProcess
  ldr r0, [r0]
  @-Mark id as inactive on array
  ldr r1, =activeProcesses
  add r1, r1, r0
  mov r0, #0
  strb r0, [r1]
  @-Jump to scheduler
  b mainScheduler

  exitSwInterruption:
  movs pc, lr

@--------------Main Scheduler-------------------------@
@-Coming from GPT interruption, save context first
mainSchedulerSaveFirst:
  push {r0-r3}
  @-Save return address
  ldr r0, =currentProcess
  ldr r0, [r0]
  ldr r1, =returnArray
  sub r14, r14, #4
  str r14, [r1, r0, lsl #2]
  @-Get address of contexts array
        ldr r1, =p1context
        @-Move to right process context
  ldr r0, =currentProcess
  ldr r0, [r0]
        add r1, r1, r0, lsl #6
        @-Save CPSR
        mrs r2, SPSR
        str r2, [r1], #4
        @-Save Registers r0-r3
        pop {r2}
        str r2, [r1], #4
        pop {r2}
        str r2, [r1], #4
        pop {r2}
        str r2, [r1], #4
        pop {r2}
        str r2, [r1], #4
        @-Save Registers r4-r12
        mov r2, r4
        str r2, [r1], #4
        mov r2, r5
        str r2, [r1], #4
        mov r2, r6
        str r2, [r1], #4
        mov r2, r7
        str r2, [r1], #4
        mov r2, r8
        str r2, [r1], #4
        mov r2, r9
        str r2, [r1], #4
        mov r2, r10
        str r2, [r1], #4
        mov r2, r11
        str r2, [r1], #4
        mov r2, r12
        str r2, [r1], #4
  @-Go to System Mode to recover r13 and r14
        msr CPSR_c, #0xDF   
        mov r2, r13
        mov r3, r14
        @-Back to IRQ Mode
        msr CPSR_c, #0x92
  @-Save registers r13 and r14
  str r2, [r1], #4
  str r3, [r1]
  
  @-Back to Supervisor
        msr CPSR_c, #0xD3

mainScheduler:
  ldr r0, =currentProcess
  ldr r1, [r0]
  ldr r0, =activeProcesses
  mov r2, #8
  traverseArray:
    cmp r2, #0
    beq endTraversal
    cmp r1, #7
    moveq r1, #0
    addne r1, r1, #1
    ldrb r3, [r0, r1]
    cmp r3, #1
    @-if equal go to this process, changing currentProcess first
    beq changeProcess
    sub r2, r2, #1
    b traverseArray
  endTraversal:
  @--No more user processes to run, wait for interruption
  infiniteLoop:
    b infiniteLoop
  @--Change process and return execution to it
  changeProcess:
  ldr r0, =currentProcess
  str r1, [r0]
  @-Set return address on r14
  ldr r0, =returnArray
  ldr r2, [r0, r1, lsl #2]
  mov r14, r2
  @-Restore registers r14 and r13
  ldr r0, =p1context
  add r0, r0, r1, lsl #6
  add r0, r0, #60
  ldr r2, [r0], #-4
  ldr r3, [r0], #-4
  @-Change to System Mode
  msr CPSR_c, #0xDF
  mov r14, r2
  mov r13, r3
  @-Back to Supervisor
  msr CPSR_c, #0xD3
  @-Restore registers r12-r4
  ldr r2, [r0], #-4
  mov r12, r2
  ldr r2, [r0], #-4
        mov r11, r2
  ldr r2, [r0], #-4
        mov r10, r2
  ldr r2, [r0], #-4
        mov r9, r2
  ldr r2, [r0], #-4
        mov r8, r2
  ldr r2, [r0], #-4
        mov r7, r2
  ldr r2, [r0], #-4
        mov r6, r2
  ldr r2, [r0], #-4
        mov r5, r2
  ldr r2, [r0], #-4
        mov r4, r2
  @-Restore SPSR
  ldr r2, =p1context
  add r2, r2, r1, lsl #6
  ldr r3, [r2]
  msr SPSR, r3
  @-Restore registers r3-r0
  ldr r1, [r0], #-4
  mov r3, r1
  ldr r1, [r0], #-4
  mov r2, r1
  ldr r1, [r0], #-4
  ldr r0, [r0]
  @-Return execution to this process
  movs pc, lr

.ltorg

@--Interruption mode stacks
.org 0x6C00
irqStack: .space 1024
fiqStack: .space 1024
abtStack: .space 1024
undStack: .space 1024

@--User software goes in this memory range

@--User and supervisor mode stacks
.org 0x9000
p8supervisor: .space 2048
p8user: .space 2048
p7supervisor: .space 2048
p7user: .space 2048
p6supervisor: .space 2048
p6user: .space 2048
p5supervisor: .space 2048
p5user: .space 2048
p4supervisor: .space 2048
p4user: .space 2048
p3supervisor: .space 2048
p3user: .space 2048
p2supervisor: .space 2048
p2user: .space 2048
p1supervisor: .space 2048
p1user: .space 2048

@--Array to hold saved contexts
.org 0x12000
p1context: .space 512

@--Array to hold return addresses
.org 0x13000
returnArray: .space 32

@--CurrentProcess variable and array to store list of active ones
currentProcess: .space 4
activeProcesses: .space 8
 

One thought on “A Simple Multi-Tasking Operating System in ARM Assembly

Leave a Reply

Your email address will not be published. Required fields are marked *