z80: commit banked helpers
authorAlan Cox <alan@linux.intel.com>
Sun, 8 Feb 2015 17:01:29 +0000 (17:01 +0000)
committerAlan Cox <alan@linux.intel.com>
Sun, 8 Feb 2015 17:01:29 +0000 (17:01 +0000)
These are annoyingly similar and eventually want folding together

Kernel/lowlevel-z80-banked.s [new file with mode: 0644]
Kernel/usermem_std-z80-banked.s [new file with mode: 0644]

diff --git a/Kernel/lowlevel-z80-banked.s b/Kernel/lowlevel-z80-banked.s
new file mode 100644 (file)
index 0000000..8789c99
--- /dev/null
@@ -0,0 +1,583 @@
+;
+;      Common elements of low level interrupt and other handling. We
+; collect this here to minimise the amount of platform specific gloop
+; involved in a port
+;
+; Some features are controlled by Z80_TYPE which should be declared in
+; platform/kernel.def as one of the following values:
+;     0   CMOS Z80
+;     1   NMOS Z80
+;     2   Z180
+;
+;      Based upon code (C) 2013 William R Sowerbutts
+;
+
+       .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 _kernel_flag
+       .globl _inint
+       .globl _platform_interrupt
+       .globl platform_interrupt_all
+
+        ; exported symbols
+        .globl unix_syscall_entry
+       .globl _chksigs
+       .globl null_handler
+       .globl unix_syscall_entry
+        .globl _doexec
+        .globl trap_illegal
+       .globl nmi_handler
+       .globl interrupt_handler
+       .globl _di
+       .globl _irqrestore
+
+        ; imported symbols
+        .globl _trap_monitor
+        .globl _unix_syscall
+        .globl outstring
+        .globl kstack_top
+       .globl istack_switched_sp
+       .globl istack_top
+       .globl _ssig
+
+        .include "platform/kernel.def"
+        .include "kernel.def"
+
+; these make the code below more readable. sdas allows us only to 
+; test if an expression is zero or non-zero.
+CPU_CMOS_Z80       .equ    Z80_TYPE-0
+CPU_NMOS_Z80       .equ    Z80_TYPE-1
+CPU_Z180           .equ    Z80_TYPE-2
+
+        .area _COMMONMEM
+
+; entry point for UZI system calls
+unix_syscall_entry:
+        di
+        ; store processor state
+        ex af, af'
+        push af
+        ex af, af'
+        exx
+        push bc
+        push de
+        push hl
+        exx
+        ; push af ;; WRS: also skip this
+        push bc
+        push de
+        ; push hl ;; WRS: we could skip this since we always set HL on return
+        push ix
+        push iy
+
+        ; locate function call arguments on the userspace stack
+        ld hl, #18     ; 16 bytes machine state, plus 2 bytes return address
+        add hl, sp
+        ; 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 bc, #8      ; four 16-bit values
+        ld de, #U_DATA__U_ARGN
+        ldir           ; copy
+
+        ; 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
+
+       ; make sure the interrupt logic knows we are in kernel mode (flag lives in kernel bank)
+       ld a, #1
+       ld (_kernel_flag), a
+
+        ; re-enable interrupts
+        ei
+
+        ; now pass control to C
+       push af
+        call _unix_syscall
+       pop af
+
+        di
+       ; let the interrupt logic know we are not in kernel mode any more
+       ; kernel_flag is not in common so write it before we map it away
+       xor a
+       ld (_kernel_flag), a
+       ;
+       ; We restart from here on a unix syscall signal return path
+       ;
+unix_sig_exit:
+        ; map process memory back in based on common (common may have
+        ; changed on a task switch)
+        call map_process_always
+
+        ; switch back to user stack
+        ld sp, (U_DATA__U_SYSCALL_SP)
+
+        ; check for signals, call the handlers
+        call dispatch_process_signal
+
+        ; check if error condition to be signalled on return
+        ld hl, (U_DATA__U_ERROR)
+        ld a, h
+        or l    ; set NZ flag if we are to return error
+        jr z, not_error
+
+        scf    ; set carry flag
+        ; note error code remains in HL
+        jr unix_return
+
+not_error:
+        ; no error to signal! return syscall return value instead of error code
+        ld hl, (U_DATA__U_RETVAL)
+        ; fall through to return code
+
+unix_return:
+        ; restore machine state
+        pop iy
+        pop ix
+        ; pop hl ;; WRS: skip this!
+        pop de
+        pop bc
+        ; pop af ;; WRS: skip this!
+        exx
+        pop hl
+        pop de
+        pop bc
+        exx
+        ex af, af'
+        pop af
+        ex af, af'
+        ei
+        ret ; must immediately follow EI
+
+dispatch_process_signal:
+        ; check if any signal outstanding
+        ld a, (U_DATA__U_CURSIG)
+        or a
+        ret z
+
+        ; put system call number on the stack as the argument for the signal handler
+        ld l, a
+        ld h, #0
+        push hl
+
+        ; load the address of signal handler function
+        add hl, hl
+        ld de, #U_DATA__U_SIGVEC
+        add hl, de
+        ld e, (hl)
+        inc hl
+        ld d, (hl)      ; now DE = udata.u_sigvec[cursig]
+
+        ; udata.u_cursig = 0;
+        xor a
+        ld (U_DATA__U_CURSIG), a
+
+        ; restore signal handler to the default.
+        ; udata.u_sigvec[cursig] = SIG_DFL;
+        ; SIG_DFL = 0, A is still 0 from above, HL points at second byte of the signal vector.
+        ld (hl), a
+        dec hl
+        ld (hl), a
+
+        ld hl, #signal_return
+        push hl      ; push return address
+
+        ex de,hl
+        ei
+        jp (hl)        ; call signal handler in userspace, with interrupts enabled
+
+signal_return:
+        pop hl  ; remove arg from stack
+       pop hl  ; we won't be using the return address either
+        di     ; So we don't screw up in mapping and stack games
+        ; save process stack pointer
+        ld (U_DATA__U_SYSCALL_SP), sp
+        ; switch to kernel stack
+        ld sp, #kstack_top
+       call map_kernel
+       push af
+       call _chksigs
+       pop af
+       ; Loop back around, switch stacks, check if there is a signal
+       ; and if so process it.
+       ;
+       ; If we do restartable signals we can just check the restartable
+       ; info and jmp back further up the syscall path *providing* that
+       ; on signal exit paths we write back any needed parameters with
+       ; their new info
+       jr unix_sig_exit
+
+_doexec:
+        di
+       ; this is a funny extra path out of syscall so we must also cover
+       ; the exit from kernel here
+       xor a
+       ld (_kernel_flag), a
+        ; map task into address space (kernel_flag is no longer mapped, don't
+       ; re-order this)
+        call map_process_always
+
+        pop bc ; return address
+        pop de ; start address
+        ;; push de ; restore stack ... but we're about to discard SP anyway!
+        ;; push bc 
+
+        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
+        ei
+        jp (hl)
+
+;
+;      Very simple IRQ handler, we get interrupts at 50Hz and we have to
+;      poll ttys from it. The more logic we could move to common here the
+;      better.
+;
+interrupt_handler:
+            ; store machine state
+            ; we arrive here via the trampoline at 0038h with interrupts disabled
+            ; save CPU registers (restored in _IRET)
+            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
+
+           ; Some platforms (MSX for example) have devices we *must*
+           ; service irrespective of kernel state in order to shut them
+           ; up. This code must be in common and use small amounts of stack
+           call platform_interrupt_all
+           ; FIXME: add profil support here (need to keep profil ptrs
+           ; unbanked if so ?)
+
+            ; don't allow us to run re-entrant, we've only got one interrupt stack
+            ld a, (U_DATA__U_ININTERRUPT)
+            or a
+            jp nz, interrupt_return
+            inc a
+            ld (U_DATA__U_ININTERRUPT), a
+
+.ifeq CPU_Z180
+            ; On Z180 we have more than one IRQ, so we need to track of which one
+            ; we arrived through. The IRQ handler sets irqvector_hw when each
+            ; interrupt arrives. If we are not already handling an interrupt then
+            ; we copy this into _irqvector which is the value the kernel code
+            ; examines (and will not change even if reentrant interrupts arrive). 
+            ; Generally the only place that irqvector_hw should be used is in 
+            ; the platform_interrupt_all routine.
+            .globl hw_irqvector
+            .globl _irqvector
+            ld a, (hw_irqvector)
+            ld (_irqvector), a
+.endif
+
+            ; switch stacks
+            ld (istack_switched_sp), sp
+           ; the istack is not banked (very important!)
+            ld sp, #istack_top
+
+           call map_save
+
+           ld a, (0)           ; save address 0 contents for checking
+           ld b, a
+
+           call map_kernel
+           ;
+           ; kernel_flag is in the kernel map so we need to map early, we
+           ; need to map anyway for trap_signal
+           ;
+           ld a, (_kernel_flag)
+           or a
+           push af
+           jr nz, in_kernel
+
+            ; we're not in kernel mode, check for signals and fault
+           ld a, #0xC3
+           cp b                ; should be a jump
+           jr z, nofault
+           call map_process_always; map the process
+           ld a, #0xC3         ; put it back
+           ld (0), a           ; write
+           call map_kernel     ; restore the map
+           ld hl, #11          ; SIGSEGV
+           call trap_signal    ; signal the user with a fault
+
+nofault:
+in_kernel:
+            ; set inint to true
+            ld a, #1
+            ld (_inint), a
+
+           ; this may task switch if not within a system call
+           ; if we switch then istack_switched_sp is out of date.
+           ; When this occurs we will exit via the resume path 
+           push af
+            call _platform_interrupt
+           pop af
+
+            xor a
+            ld (_inint), a
+
+           pop af                      ; Z = in kernel
+           jr nz, in_kernel_2
+
+           ; On a return to user space always do a new map, we may have
+           ; changed process
+           call map_process_always
+            jr int_switch
+           ; On a return from an interrupt in kernel mode restore the old
+           ; mapping as it will vary during kernel activity and the kernel
+           ; wants it put back as it was before
+in_kernel_2:
+           call map_restore
+int_switch:
+            ld sp, (istack_switched_sp)        ; stack back
+
+            xor a
+            ld (U_DATA__U_ININTERRUPT), a
+
+            ld a, (U_DATA__U_INSYS)
+            or a
+            jr nz, interrupt_return
+
+           ; FIXME: check kernel mode flag ?
+            ; we're not in kernel mode, check for signals
+            call dispatch_process_signal
+           ; FIXME: we should loop for multiple signals probably
+
+interrupt_return:
+            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'
+            ei
+.ifeq CPU_Z180
+            ; WRS - we could examine hw_irqvector and return with ret/reti as appropriate?
+            ret
+.else
+            reti
+.endif
+
+;  Enter with HL being the signal to send ourself
+trap_signal:
+            push hl
+           ld hl, (U_DATA__U_PTAB);
+            push hl
+           push af
+            call _ssig
+           pop af
+            pop hl
+            pop hl
+           ret
+
+;  Called from process context (hopefully)
+null_handler:
+           ; kernel jump to NULL is bad
+           ld a, (U_DATA__U_INSYS)
+           or a
+           jp z, trap_illegal
+           ; user is merely not good
+            ld hl, #7
+            push hl
+           ld hl, (U_DATA__U_PTAB)
+            push hl
+           ld hl, #10          ; signal (getpid(), SIGBUS)
+            rst #0x30          ; syscall
+            pop hl
+            pop hl
+            ld hl, #0
+            rst #0x30          ; exit
+
+
+
+illegalmsg: .ascii "[trap_illegal]"
+            .db 13, 10, 0
+
+trap_illegal:
+        ld hl, #illegalmsg
+        call outstring
+        call _trap_monitor
+
+dpsmsg: .ascii "[dispsig]"
+        .db 13, 10, 0
+
+
+nmimsg: .ascii "[NMI]"
+        .db 13,10,0
+nmi_handler:
+       call map_kernel
+        ld hl, #nmimsg
+        call outstring
+        jp _trap_monitor
+
+
+       .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
+        call outchar
+        ret
+
+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
+
+;
+;      Pull in the CPU specific workarounds
+;
+
+.ifeq CPU_NMOS_Z80
+       .include "lowlevel-z80-nmos.s"
+.else
+       .include "lowlevel-z80-cmos.s"
+.endif
+
+;
+;      These are needed by the experimental SDCC size optimisations
+;
+
+       .globl __enter, __enter_s
+
+__enter:
+       pop hl          ; return address
+       push ix         ; save frame pointer
+       ld ix, #0
+       add ix, sp      ; set ix to the stack frame
+       jp (hl)         ; and return
+
+__enter_s:
+       pop hl          ; return address
+       push ix         ; save frame pointer
+       ld ix, #0
+       add ix, sp      ; frame pointer
+       ld e, (hl)      ; size byte
+       ld d, #0xFF     ; always minus something..
+       inc hl
+       ex de, hl
+       add hl, sp
+       ld sp, hl
+       push de
+       ret
diff --git a/Kernel/usermem_std-z80-banked.s b/Kernel/usermem_std-z80-banked.s
new file mode 100644 (file)
index 0000000..0c692f1
--- /dev/null
@@ -0,0 +1,204 @@
+;
+;      No fancy optimisations here. It's not clear they are worth it once
+;      you have to deal with misaligned transfers that might cross source
+;      or destination banks. One optimisation might be worth doing - that
+;      is 512 byte blocks that don't bank cross ?
+;
+        .module usermem
+
+       .include "platform/kernel.def"
+        .include "kernel.def"
+
+        ; exported symbols
+        .globl __uget
+        .globl __ugetc
+        .globl __ugets
+        .globl __ugetw
+
+       .globl outcharhex
+       .globl outhl
+
+        .globl __uput
+        .globl __uputc
+        .globl __uputw
+        .globl __uzero
+
+       .globl  map_process_always
+       .globl  map_kernel
+;
+;      We need these in common as they bank switch
+;
+        .area _COMMONMEM
+
+uputget:
+        ; load DE with the byte count
+        ld e, 10(ix) ; byte count
+        ld d, 11(ix)
+       ld a, d
+       or e
+       ret z           ; no work
+       dec de          ; we return BC as a count for two 8bit loops
+       ld b, e         ; not a 16bit value
+       inc b           ; See http://map.grauw.nl/articles/fast_loops.php
+       inc d
+       ld c, d
+        ; load HL with the source address
+        ld l, 6(ix) ; src address
+        ld h, 7(ix)
+        ; load DE with destination address (in userspace)
+        ld e, 8(ix)
+        ld d, 9(ix)
+       ld a, b
+       or c
+       ret
+
+__uputc:
+       pop iy  ;       bank
+       pop bc  ;       return
+       pop de  ;       char
+       pop hl  ;       dest
+       push hl
+       push de
+       push bc
+       push iy
+       call map_process_always
+       ld (hl), e
+uputc_out:
+       jp map_kernel                   ; map the kernel back below common
+
+__uputw:
+       pop iy
+       pop bc  ;       return
+       pop de  ;       word
+       pop hl  ;       dest
+       push hl
+       push de
+       push bc
+       push iy
+       call map_process_always
+       ld (hl), e
+       inc hl
+       ld (hl), d
+       jp map_kernel
+
+__ugetc:
+       pop de
+       pop bc  ; return
+       pop hl  ; address
+       push hl
+       push bc
+       push de
+       call map_process_always
+        ld l, (hl)
+       ld h, #0
+       jp map_kernel
+
+__ugetw:
+       pop de
+       pop bc  ; return
+       pop hl  ; address
+       push hl
+       push bc
+       push de
+       call map_process_always
+        ld a, (hl)
+       inc hl
+       ld h, (hl)
+       ld l, a
+       jp map_kernel
+
+__uput:
+       push ix
+       ld ix, #0
+       add ix, sp
+       call uputget                    ; source in HL dest in DE, count in BC
+       jr z, uput_out                  ; but count is at this point magic
+       
+uput_l:        ld a, (hl)
+       inc hl
+       call map_process_always
+       ld (de), a
+       call map_kernel
+       inc de
+       djnz uput_l
+       dec c
+       jr nz, uput_l
+
+uput_out:
+       pop ix
+       ld hl, #0
+       ret
+
+
+__uget:
+       push ix
+       ld ix, #0
+       add ix, sp
+       call uputget                    ; source in HL dest in DE, count in BC
+       jr z, uput_out                  ; but count is at this point magic
+       
+uget_l:
+       call map_process_always
+       ld a, (hl)
+       inc hl
+       call map_kernel
+       ld (de), a
+       inc de
+       djnz uget_l
+       dec c
+       jr nz, uget_l
+       jr uput_out
+
+__ugets:
+       push ix
+       ld ix, #0
+       add ix, sp
+       call uputget                    ; source in HL dest in DE, count in BC
+       jr z, ugets_bad                 ; but count is at this point magic
+       
+ugets_l:
+       call map_process_always
+       ld a, (hl)
+       inc hl
+       call map_kernel
+       ld (de), a
+       or a
+       jr z, ugets_good
+       inc de
+       djnz ugets_l
+       dec c
+       jr nz, ugets_l
+       dec de
+       xor a
+       ld (de), a
+ugets_bad:
+       ld hl,  #0xFFFF                 ; flag an error
+       jr uput_out
+ugets_good:
+       ld hl,#0
+       jr uput_out
+
+;
+__uzero:
+       pop iy
+       pop de  ; return
+       pop hl  ; address
+       pop bc  ; size
+       push bc
+       push hl 
+       push de
+       push iy
+       ld a, b ; check for 0 copy
+       or c
+       ret z
+       call map_process_always
+       ld (hl), #0
+       dec bc
+       ld a, b
+       or c
+       jp z, uputc_out
+       ld e, l
+       ld d, h
+       inc de
+       ldir
+       jp uputc_out