chaOS/scheduler.asm
2014-04-18 00:17:43 +02:00

432 lines
No EOL
9.5 KiB
NASM

;; scheduler
;
; Because we want want to be able use multible thread, we need a scheduler.
; This scheduler is supposed to support up to 4 threads.
;
;;
;; global memory map
;
; $0000
; G P R
; $001F
; $0020
; I O R
; $005F
; $0060
; external I O R
; $00FF
; $0100
; SRAM
; $0100
; 164 bytes of scheduler memory
; $01A3
; $01A4
; 15,710 bytes of heap
; $3EFF
; $3F00
; 512 bytes of stack
; $40FF
; $40FF
;
;;
; this a ATmega1284P specific
.equ heap_start = 0x01A3 ; starting address of heap
.equ heap_end = 0x3EFF ; ending address of heap
.equ scheduler_global_active = 0x0100 ; address of active-thread-cell
.equ scheduler_global_number = 0x0101 ; address of thread-number-cell
.equ scheduler_global_maxnum = 0x0004 ; maximal number of thread
.equ scheduler_global_statu1 = 0x0102 ; address of global-status-cell 1
.equ scheduler_global_statu2 = 0x0103 ; address of global-status-cell 2
.equ scheduler_global_unused = 0x0104 ; start address of unused block
.equ scheduler_global_thread = 0x010B ; starting address of scheduler memory
.equ scheduler_global_length = 0x0026 ; length of thread in scheduler memory
.equ scheduler_global_lastad = 0x0142 ; last address of scheduler memory
.equ scheduler_global_stacks = 0x3FFF ; start address of stacks
.equ scheduler_offset_stacks = 0x0080 ; length of 1 stack
.equ scheduler_offset_instat = 0x0000 ; offset of internal sr in scheduler memory
.equ scheduler_offset_instrh = 0x0001 ; offset of iph in scheduler memory
.equ scheduler_offset_instrl = 0x0002 ; offset of ipl in scheduler memory
.equ scheduler_offset_status = 0x0003 ; offset of sr in scheduler memory
.equ scheduler_offset_stackh = 0x0004 ; offset of sph in scheduler memory
.equ scheduler_offset_stackl = 0x0005 ; offset of spl in scheduler memory
.equ scheduler_offset_gprsta = 0x0006 ; start offset of gpr in scheduler momory
.equ scheduler_offset_gprend = 0x0025 ; end offset of gpr in scheduler memory
;; scheduler memory
;
; We need to save the number of threads, the number of the active thread
; and some other things.
; For every thread we need to save:
; - the instruction pointer (2 byte)
; - the status register (1 byte)
; - the stack pointer (2 byte)
; - all 32 pgr
; - plus 1 byte of status-values for the scheduler
; => 38 bytes -> $26; starting at $06B
; so: the sp of our thread x is calculated this way:
; address = scheduler_global_thread + scheduler_global_length * x + scheduler_offset_stackp
;
;; scheduler memory map
;
; $0100 active thread
; $0101 number of threads
; $0102 sr1 of scheduler
; $0103 sr2 of scheduler
; // we still have 8 bytes of memory here
; $010B
; thread 0
; $010B internal sr
; $010C iph
; $010D ipl
; $010E sr
; $010F sph
; $0110 spl
; $0111
; gpr
; $0130
; $0130
; $0131
; thread 1
; $0131 internal sr
; $0132 iph
; $0133 ipl
; $0134 sr
; $0135 sph
; $0136 spl
; $0137
; gpr
; $0156
; $0156
; $0157
; thread 2
; $0157 internal sr
; $0158 iph
; $0159 ipl
; $015A sr
; $015B sph
; $015C spl
; $015D
; gpr
; $017C
; $017C
; $017D
; thread 3
; $017D internal sr
; $017E iph
; $017F ipl
; $0180 sr
; $0181 sph
; $0182 spl
; $0183
; gpr
; $01A2
; $01A2
;
;
;; stacks
;
; As we should support 4 threads, we need 4 stacks.
; For the calculation of the stack space (see global memory map) I assumed that
; no stack will grow over 127 entries.
; Our initial stackpointer for thread x will be calculated this way:
; address = schedulder_global_stacks + scheduler_offset_stacks * x
;
;;
SchedulerInit:
push r16
in r16, sreg
push r16
push r17
push xl
push xh
; first we'll clear all scheduler memory cells
ldi r16, 0
ldi r17, low (scheduler_global_lastad)
ldi xl, low (scheduler_global_active)
ldi xh, high(scheduler_global_active)
SchedulerInitLoop0: ; while(xl++ != r11)
st x+, r16
cp xl, r17 ; we can do this, because we'll never go over the low-byte
brne SchedulerInitLoop0
; add main-thread
call SchedulerAddThread
; start scheduler timer
; we will use timer0 because we don't use all posibilities of timer2
; some config
ldi r16, 0b000000010 ; CTC
out tccr0a, r16
ldi r16, 0b000000100 ; Prescaler 256
out tccr0b, r16
ldi r16, 0b000000011 ; compare a: 3 x 256 ticks between interrupts
out ocr0a r16
ldi r16, 0b000000010 ; compare a interrupt
pop xh
pop xl
pop r11
pop r10
out sreg, r10
pop r10
ret
SchedulerInterrupt:
call SchedulerChangeThread
reti
SchedulerAddThread:
; if the number of running threads is 0:
; the ip of the new thread should be here:
; r9 -> low
; r10 -> high
; else: this routine will overwrite r9 & r10
push r16
push r17
ldd r16, scheduler_global_number
ldi r17, 0
cp r16, r17
brne SchedulerAddThreadAddThread
; we have to get the ip
pop r17
std (scheduler_global_unused + 0), r17
pop r17 ; r17 is former r16
pop r10 ; high-byte
pop r9 ; low-byte
push r9
push r10
push r17
ldd r17, (scheduler_global_unused + 0)
push r17
SchedulerAddThreadAddThread:
; TODO
SchedulerAddThreadEnd:
pop r17
pop r16
ret
SchedulerChangeThread:
; puh, that will get hard
; first we have to get the current instruction pointer
push xl
push xh
push r16
in r16, sreg
ldi xl, low (scheduler_global_unused)
ldi xh, high(scheduler_global_unused)
st x+, r16 ; sreg is on unused[0]
pop r16
st x+, r16 ; r16 is on unused[1]
pop r16
st x+, r16 ; xh is on unused[2]
pop r16
st x+, r16 ; xl is on unused[3]
st x+, r17 ; r17 is on unused[4]
st x+, r18 ; r18 is on unused[5]
st x+, r19 ; r19 is on unused[6]
st x+, r20 ; r19 is on unused[7]
pop r16 ; the later item in the stack is high
pop r17 ; the earlier one is low
; epic win !!!!!111
; instruction pointer high is now in r16; low in r17
; next we have to determine where we have to store our data
ldd r18 scheduler_global_active
; number of current thread is now in r18
; now we have to determine the start address of our thread memory
ldi xl, low (scheduler_global_thread)
ldi xh, high(scheduler_global_thread)
ldi r19, scheduler_global_length
ldi r20, 0
SchedulerChangeThreadLoop0: ; while (r20++ != r18)
cp r20, r18
breq SchedulerChangeThreadLoop0End
; we can do the following, because the address can not be greater
; than 0x00FF (as you can see in the scheduler memory map)
add xl, r19
inc r20
jmp SchedulerChangeThreadLoop0
SchedulerChangeThreadLoop0End:
; x now contains the start address of our thread memory
; let's copy some data
; this is, because we don't want to modify the status register
ld r11, x+
; instruction pointer
st x+, r16
st x+, r17
; we need the status register, it's saved in unused[0]
ldd r16, scheduler_global_unused
st x+, r16
; so: the next step is the stack pointer
in r16, sph
st x+, r16
in r16, spl
st x+, r16
; whew. next we have to save all the pgrs
st x+, r0
st x+, r1
st x+, r2
st x+, r3
st x+, r4
st x+, r5
st x+, r6
st x+, r7
st x+, r8
st x+, r9
st x+, r10
st x+, r11
st x+, r12
st x+, r13
st x+, r14
st x+, r15
ldd r16, (scheduler_global_unused + 1)
st x+, r16
ldd r16, (scheduler_global_unused + 4) ; this is r17
st x+, r16
ldd r16, (scheduler_global_unused + 5) ; this is r18
st x+, r16
ldd r16, (scheduler_global_unused + 6) ; this is r19
st x+, r16
ldd r16, (scheduler_global_unused + 7) ; this is r20
st x+, r16
st x+, r21
st x+, r22
st x+, r23
st x+, r24
st x+, r25
ldd r16, (scheduler_global_unused + 3) ; this is xl
st x+, r16
ldd r16, (scheduler_global_unused + 2) ; this is xh
st x+, r16
st x+, yl
st x+, yh
st x+, zl
st x+, zh
; soooo. we now have to get to know which is the next thread.
; r18 contains the nr of the active thread
; we now increment r18 (because we want the next thread)
inc r18
ldd r17, (scheduler_global_number)
; r17 now containes the number of threads
; if r17 equals r18 our calculated next thread number is too high
cpi r17, r18
brne SchedulerChangeThreadHop0 ; if (r17 == r18)
ldi r18, 0
SchedulerChangeThreadHop0:
; so now we'll try to calculate the position of the scheduler memory
; for thread [insert number in r18]
ldi xl, low (scheduler_global_thread)
ldi xh, high(scheduler_global_thread)
ldi r19, scheduler_global_length
ldi r20, 0
SchedulerChangeThreadLoop1: ; while (r20++ != r18)
cp r20, r18
breq SchedulerChangeThreadLoop1End
; we can do the following, because the address can not be greater
; than 0x00FF (as you can see in the scheduler memory map)
add xl, r19
inc r20
jmp SchedulerChangeThreadLoop1
SchedulerChangeThreadLoop1End:
ld x+, r1 ; internal sr: we don't need it
ld x+, r2 ; iph is in r2
ld x+, r3 ; ipl is in r3
ld x+, r16 ; sr is in r16
ld x+, r5 ; sph is in r5
ld x+, r6 ; spl is in r6
std (scheduler_global_unused + 0), r16 ; sr is in unused[0]
; let's set the stack pointer back
out sph r5
out spl r6
; let's push the instruction pointer back to the stack
push r3 ; the earlier one is low
push r2 ; the later one is high
; and now all registers again
ld r1, x+
ld r2, x+
ld r3, x+
ld r4, x+
ld r5, x+
ld r6, x+
ld r7, x+
ld r8, x+
ld r9, x+
ld r10, x+
ld r11, x+
ld r12, x+
ld r13, x+
ld r14, x+
ld r15, x+
ld r16, x+
push r16 ; r16 is now on stack
ld r17, x+
ld r18, x+
ld r19, x+
ld r20, x+
ld r21, x+
ld r22, x+
ld r23, x+
ld r24, x+
ld r25, x+
ld r16, x+
std (scheduler_global_unused + 2), r16 ; xl is in unused[2]
ld r16, x+
std (scheduler_global_unused + 3), r16 ; xh is in unused[3]
ld yl, x+
ld yh, x+
ld zl, x+
ld zh, x+
ldd xl, (scheduler_global_unused + 2)
ldd xh, (scheduler_global_unused + 3)
; finaly we just have to replace the status register
; and we are done
ldd r16, (scheduler_global_unused + 0)
out sreg r16
pop r16
; done
ret