From: Alan Cox Date: Thu, 7 Feb 2019 23:36:58 +0000 (+0000) Subject: rabbit: Low level code for the r2k X-Git-Url: https://git.ndcode.org/public/gitweb.cgi?a=commitdiff_plain;h=1f67d35d7fef09fde7ce811710f11ecd3e134a47;p=FUZIX.git rabbit: Low level code for the r2k --- diff --git a/Kernel/lowlevel-r2k.s b/Kernel/lowlevel-r2k.s new file mode 100644 index 00000000..5ac31513 --- /dev/null +++ b/Kernel/lowlevel-r2k.s @@ -0,0 +1,641 @@ +; +; Rabbit 2/3000 low level interface code +; +; Heavily based on the Z80/Z180 code +; +; Try and keep any CPU specifics elsewhere. +; + + .module lowlevel + + ; debugging aids + .globl outcharhex + .globl outbc, outde, outhl + .globl outnewline + .globl outstring + .globl outstringhex + .globl outnibble + + ; platform provided functions + .globl map_kernel + .globl map_process_always + .globl map_save + .globl map_restore + .globl outchar + .globl _inint + .globl _platform_interrupt + .globl platform_interrupt_all + .globl _platform_monitor + .globl _platform_switchout + + ; exported symbols + .globl null_handler + .globl unix_syscall_entry + .globl _doexec + .globl trap_illegal + .globl interrupt_handler + .globl ___hard_ei + .globl ___hard_di + .globl ___hard_irqrestore + + .globl mmu_irq_ret + + ; imported symbols + .globl _unix_syscall + .globl outstring + .globl kstack_top + .globl istack_switched_sp + .globl istack_top + .globl _ssig + .globl _doexit + .globl _need_resched + .globl _chksigs + + .include "platform/kernel.def" + .include "kernel-rabbit.def" + + .area _COMMONMEM + +; +; Called on the user stack in order to process signals that +; are pending. A user process can longjmp out of this loop so +; care is needed. Call with interrupts disabled and user mapped. +; +; Returns with interrupts disabled and user mapped, but may +; enable interrupts and change mappings. +; +deliver_signals: + ; Pending signal + ld a, (U_DATA__U_CURSIG) + or a,a + ret z + +deliver_signals_2: + ld l, a + ld h, #0 + push hl ; signal number as C argument to the handler + + ; Handler to use + add hl, hl + ld de, #U_DATA__U_SIGVEC + add hl, de + ld e, (hl) + inc hl + ld d,(hl) + + ; Indicate processed + xor a,a + ld (U_DATA__U_CURSIG), a + + ; Semantics for now: signal delivery clears handler + ld (hl), a + dec hl + ld (hl), a + + ld bc, #signal_return + push bc ; bc is passed in as the return vector + + ex de, hl + ipset0 +;;FIX call mmu_user ; must preserve HL + jp (hl) ; return to user space. This will then return via + ; the return path handler passed in BC + +; +; Syscall signal return path +; +signal_return: + pop hl ; argument + ipset1 +;;FIX call mmu_kernel + ; + ; We must keep IRQ disabled in the kernel mapped + ; element of this processing, as we don't want to + ; set INSYS flags here. + ; + ld (U_DATA__U_SYSCALL_SP), sp + ld sp, #kstack_top + call map_kernel + call _chksigs + call map_process_always + ld sp, (U_DATA__U_SYSCALL_SP) + jr deliver_signals + +; +; Syscall processing path +; +; FIXME: rework to undo the Z80 mistakes. Pass arguments in registers +; don't screw up stack. Check how our trap arrives and push pop ip etc +; as needed for different stack layout. +; +unix_syscall_entry: + ipset1 + ; store processor state + ex af, af' + push af + ex af, af' + exx + push bc + push de + push hl + exx + push ix + push iy + ; We don't save AF BC DE HL + ; locate function call arguments on the userspace stack + ld hl, #20 ; 16 bytes machine state, plus 2 bytes return address + ; plus 2 bytes return frame for the syscall stub + add hl, sp + +;; call mmu_kernel ; must preserve HL + ; save system call number + ld a, (hl) + ld (U_DATA__U_CALLNO), a + ; advance to syscall arguments + inc hl + inc hl + ; copy arguments to common memory + ld de, #U_DATA__U_ARGN + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + + ld a, #1 + ld (U_DATA__U_INSYS), a + + ; save process stack pointer + ld (U_DATA__U_SYSCALL_SP), sp + ; switch to kernel stack + ld sp, #kstack_top + + ; map in kernel keeping common + call map_kernel + + ; re-enable interrupts + ipres + + ; now pass control to C + call _unix_syscall + + ; + ; WARNING: There are two special cases to beware of here + ; 1. fork() will return twice from _unix_syscall + ; 2. execve() will not return here but will hit _doexec() + ; + ; The fork case returns with a different U_DATA mapped so the + ; U_DATA referencing code is fine, but globals are usually not + + ipset1 + + + call map_process_always + + xor a,a + ld (U_DATA__U_INSYS), a + + ; Back to the user stack + ld sp, (U_DATA__U_SYSCALL_SP) + + ld hl, (U_DATA__U_ERROR) + ld de, (U_DATA__U_RETVAL) + + ld a, (U_DATA__U_CURSIG) + or a,a + + ; Fast path the normal case + jr nz, via_signal + + ; Restore stacks and go + ; + ; Should we change the ABI and just return in DE/HL ? + ; +unix_return: + ld a, h + or a,l + jr z, not_error + scf ; carry flag on return state for errors + jr unix_pop + +not_error: + ex de, hl ; return the retval instead + ; + ; Undo the stacking and go back to user space + ; +unix_pop: +;; call mmu_user ; must preserve HL + ; restore machine state + pop iy + pop ix + exx + pop hl + pop de + pop bc + exx + ex af, af' + pop af + ex af, af' + reti + +via_signal: + ; Get off the kernel syscall stack before we start signal + ; handling. Our signal handlers may themselves elect to make system + ; calls. This means we must also save the error/return code + ld hl, (U_DATA__U_ERROR) + push hl + ld hl, (U_DATA__U_RETVAL) + push hl + + ; Signal processing. This may longjmp back into userland + call deliver_signals_2 + + ; If not then we recover the syscall return values and + ; exit via the syscall return path + pop de ; retval + pop hl ; errno + jr unix_return + + +; +; Final component of execve() +; +_doexec: + ipset1 + call map_process_always + + pop bc ; return address + pop de ; start address + + ld hl, (U_DATA__U_ISP) + ld sp, hl ; Initialize user stack, below main() parameters and the environment + + ; u_data.u_insys = false + xor a,a + ld (U_DATA__U_INSYS), a + + ex de, hl + + ; for the relocation engine - tell it where it is + ld iy, #PROGLOAD +;; call mmu_user ; must preserve HL + ipres + jp (hl) + +; +; Called from process context (hopefully) +; +null_handler: + ; kernel jump to NULL is bad + ld a, (U_DATA__U_INSYS) + or a,a + jp nz, trap_illegal + ld a, (_inint) + jp nz, trap_illegal + ; user is merely not good + ld hl, #7 + push hl + ld ix, (U_DATA__U_PTAB) + ld l,P_TAB__P_PID_OFFSET(ix) + ld h,P_TAB__P_PID_OFFSET+1(ix) + push hl + ld hl, #39 ; signal (getpid(), SIGBUS) + push hl + call unix_syscall_entry ; syscall + ld hl, #0xFFFF + call _doexit ; never returns + + +illegalmsg: .ascii "[illegal]" + .db 13, 10, 0 + +trap_illegal: + ld hl, #illegalmsg +traphl: + call outstring + call _platform_monitor + +; +; Interrupt handler. Not quite the same as syscalls, we need to +; stack everything and we must get off the IRQ stack and then +; process need_resched and signals +; +interrupt_handler: + ; store machine state + ; FIXME: who saves XPC - map_save ?? + ex af,af' + push af + ex af,af' + exx + push bc + push de + push hl + exx + push af + push bc + push de + push hl + push ix + push iy + ; + ; This is a bit exciting - if our MMU enforces r/o then the entire + ; stack state might be bogus! + ; +;; ld hl, #mmu_irq_ret +;; jp mmu_kernel_irq +;;mmu_irq_ret: + ; FIXME: add profil support here (need to keep profil ptrs + ; unbanked if so ?) + + ; Get onto the IRQ stack + ld (istack_switched_sp), sp + ld sp, #istack_top + + ld a, (0) + + call map_save + ; + ; FIXME: re-implement sanity checks and add a stack one + ; + + ; We need the kernel mapped for the IRQ handling + call map_kernel + + cp a,#0xC3 + jr z, no_null_ptr + call null_pointer_trap +no_null_ptr: + ; So the kernel can check rapidly for interrupt status + ; FIXME: move to the C code + ld a, #1 + ld (_inint), a + ; So we know that this task should resume with IRQs off + ld (U_DATA__U_ININTERRUPT), a + + call _platform_interrupt + + xor a,a + ld (_inint), a + + ld a, (_need_resched) + or a,a + jr nz, preemption + + ; Back to the old memory map + call map_restore + + ; + ; Back on user stack + ; + ld sp, (istack_switched_sp) + +intout: + xor a,a + ld (U_DATA__U_ININTERRUPT), a + +intret: + ipset1 + ld a, (U_DATA__U_INSYS) + or a,a + jr nz, interrupt_pop + + ; Loop through any pending signals. These could longjmp out + ; of the handler so ensure everything is fixed before this ! + + call deliver_signals +;; call mmu_restore_irq + + ; Then unstack and go. +interrupt_pop: + pop iy + pop ix + pop hl + pop de + pop bc + pop af + exx + pop hl + pop de + pop bc + exx + ex af, af' + pop af + ex af, af' + reti ; runs in the ei interrupt shadow + +; +; Called with the kernel mapped, mid interrupt and on the IRQ stack +; +null_pointer_trap: + ld a, #0xC3 + ld (0), a + ld hl, #11 ; SIGSEGV +trap_signal: + push hl + ld hl, (U_DATA__U_PTAB); + push hl + call _ssig + pop hl + pop hl + ret + + +; +; Pre-emption. We need to get off the interrupt stack, switch task +; and clean up the IRQ state carefully +; + + .globl preemption + +preemption: + xor a,a + ld (_need_resched), a ; Task done + + ; Back to the old memory map + call map_restore + + ld hl, (istack_switched_sp) + ld (U_DATA__U_SYSCALL_SP), hl + + ld sp, #kstack_top ; We don't pre-empt in a syscall + ; so this is fine + + ipset1 + ; + ; We are now on the syscall stack (which is fine, we don't + ; pre-empt mid syscall so therefore it is free. We will now + ; task switch. The process being pre-empted will disappear into + ; switchout() and whoever is next will come out of the same - + ; hence the need to reti + ; + call map_kernel + ; + ; Semantically we are doing a null syscall for pre-empt. We need + ; to record ourselves as in a syscall so we can't be recursively + ; pre-empted when switchout re-enables interrupts. + ; + ld a, #1 + ld (U_DATA__U_INSYS), a + ; + ; Check for signals + ; + call _chksigs + ; + ; Process status is offset 0 + ; + ld hl, (U_DATA__U_PTAB) + ld a,(hl) + cp a,#P_RUNNING + jr nz, not_running + ld (hl), #P_READY +not_running: + call _platform_switchout + ; + ; We are no longer in an interrupt or a syscall + ; + xor a,a + ld (U_DATA__U_ININTERRUPT), a + ld (U_DATA__U_INSYS), a + ; + ; We have been rescheduled, remap ourself and go back to user + ; space via signal handling + ; + call map_process_always ; Get our user mapping back + ; We were pre-empted but have now been rescheduled + ; User stack + ld sp, (U_DATA__U_SYSCALL_SP) + ld a, (U_DATA__U_CURSIG) + or a,a + jr z, nosigs + call deliver_signals_2 +nosigs: + ; + ; pop the stack and go + ; +;; call mmu_user + jr interrupt_pop + +; +; Debugging helpers +; + + .area _COMMONMEM + +; outstring: Print the string at (HL) until 0 byte is found +; destroys: AF HL +outstring: + ld a, (hl) ; load next character + and a,a ; test if zero + ret z ; return when we find a 0 byte + call outchar + inc hl ; next char please + jr outstring + +; print the string at (HL) in hex (continues until 0 byte seen) +outstringhex: + ld a, (hl) ; load next character + and a,a ; test if zero + ret z ; return when we find a 0 byte + call outcharhex + ld a, #0x20 ; space + call outchar + inc hl ; next char please + jr outstringhex + +; output a newline +outnewline: + ld a, #0x0d ; output newline + call outchar + ld a, #0x0a + jp outchar + +outhl: ; prints HL in hex. + push af + ld a, h + call outcharhex + ld a, l + call outcharhex + pop af + ret + +outbc: ; prints BC in hex. + push af + ld a, b + call outcharhex + ld a, c + call outcharhex + pop af + ret + +outde: ; prints DE in hex. + push af + ld a, d + call outcharhex + ld a, e + call outcharhex + pop af + ret + +; print the byte in A as a two-character hex value +outcharhex: + push bc + push af + ld c, a ; copy value + ; print the top nibble + rra + rra + rra + rra + call outnibble + ; print the bottom nibble + ld a, c + call outnibble + pop af + pop bc + ret + +; print the nibble in the low four bits of A +outnibble: + and a,#0x0f ; mask off low four bits + cp a,#10 + jr c, numeral ; less than 10? + add a, #0x07 ; start at 'A' (10+7+0x30=0x41='A') +numeral: add a, #0x30 ; start at '0' (0x30='0') + jp outchar + +; +; IRQ helpers, in common as they may get used by common C +; code (and are tiny) +; +; We use the multiple IRQ levels as our low level code needs to do things +; like soft buffer uarts +; +_ei: +___hard_ei: + ipset0 + ret +; +; This needs review. If we are careful about our root bank of 4K we may be +; able to stuff all our vectors and handling for critical IRQ code there and +; *never* disable interrupts except for the tiny bits of code working the +; serial queues ! +; +___hard_di: push ip + pop hl + ipset3 ; we block everything for hard_di + ret + +_irqrestore: +___hard_irqrestore: + pop hl + pop ip + jp (hl) + +_di: push ip + pop hl + ipset1 ; check timer priority we are using and what + ; we can fit into the low space + ret