From: Alan Cox Date: Sun, 8 Feb 2015 17:01:29 +0000 (+0000) Subject: z80: commit banked helpers X-Git-Url: https://git.ndcode.org/public/gitweb.cgi?a=commitdiff_plain;h=83f164a57c8cbb8621a100ebb44a1105c5f2c7c4;p=FUZIX.git z80: commit banked helpers These are annoyingly similar and eventually want folding together --- diff --git a/Kernel/lowlevel-z80-banked.s b/Kernel/lowlevel-z80-banked.s new file mode 100644 index 00000000..8789c993 --- /dev/null +++ b/Kernel/lowlevel-z80-banked.s @@ -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 index 00000000..0c692f19 --- /dev/null +++ b/Kernel/usermem_std-z80-banked.s @@ -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