+++ /dev/null
-;
-; 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.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
- 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
- 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
- ipset 0
-;;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
- ipset 1
-;;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:
- ipset 1
- ; store processor state
- ex af, af'
- push af
- ex af, af'
- exx
- push bc ; FIXME we don't I think need to save bc/de/hl
- push de ; as they are compiler caller save
- push hl
- exx
- push bc
- push de
- push ix
- push iy
- ; We don't save AF or HL
- ; locate function call arguments on the userspace stack
- ld hl, #18 ; 16 bytes machine state, plus 2 bytes return address
- 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
-
- ipset 1
-
-
- call map_process_always
-
- xor 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
-
- ; 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 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
- pop de
- pop bc
- 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:
- ipset 1
- 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
- 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
- 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 #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
- ld (_inint), a
-
- ld a, (_need_resched)
- or 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
- ld (U_DATA__U_ININTERRUPT), a
-
-intret:
- ipset 1
- ld a, (U_DATA__U_INSYS)
- or 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
- 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
-
- ipset 1
- ;
- ; 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 #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
- 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
- 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 ; 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 ; 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 #0x0f ; mask off low four bits
- cp #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:
- ipset 0
- 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
- ipset 3 ; we block everything for hard_di
- ret
-
-_irqrestore:
-___hard_irqrestore:
- pop hl
- pop ip
- jp (hl)
-
-_di: push ip
- pop hl
- ipset 1 ; check timer priority we are using and what
- ; we can fit into the low space
- ret