rabbit: Low level code for the r2k
authorAlan Cox <alan@linux.intel.com>
Thu, 7 Feb 2019 23:36:58 +0000 (23:36 +0000)
committerAlan Cox <alan@linux.intel.com>
Thu, 7 Feb 2019 23:36:58 +0000 (23:36 +0000)
Kernel/lowlevel-r2k.s [new file with mode: 0644]

diff --git a/Kernel/lowlevel-r2k.s b/Kernel/lowlevel-r2k.s
new file mode 100644 (file)
index 0000000..5ac3151
--- /dev/null
@@ -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