From 4daa47984b9fbe4c8f0b74f21fc9323aef6bf868 Mon Sep 17 00:00:00 2001 From: Alan Cox Date: Wed, 8 Aug 2018 00:07:46 +0100 Subject: [PATCH] z80: first sketches of the 32K/32K support Some of this will also hopefully apply to the 64K/ROM switch and the Cromemco / NASCOM style models where you have write through to multiple banks but 64K switching --- Kernel/lowlevel-z80-thunked.s | 435 +++++++++++++++++++++++++++++++ Kernel/usermem_std-z80-thunked.s | 269 +++++++++++++++++++ 2 files changed, 704 insertions(+) create mode 100644 Kernel/lowlevel-z80-thunked.s create mode 100644 Kernel/usermem_std-z80-thunked.s diff --git a/Kernel/lowlevel-z80-thunked.s b/Kernel/lowlevel-z80-thunked.s new file mode 100644 index 00000000..1e5a986b --- /dev/null +++ b/Kernel/lowlevel-z80-thunked.s @@ -0,0 +1,435 @@ +; +; 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 +; +; This version is intended for those awkward platforms with 32K/32K banking +; or 64K whole memory bank flipping. +; +; 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 +; +; Based upon code (C) 2013 William R Sowerbutts +; + + .module lowlevel-thunked + + ; debugging aids + .globl outcharhex + .globl outbc, outde, outhl + .globl outnewline + .globl outstring + .globl outstringhex + .globl outnibble + + ; platform provided functions + .globl map_kernel_low + .globl map_user_low + .globl map_page_low + .globl map_save_low + .globl map_restore_low + .globl outchar + .globl _inint + .globl _platform_interrupt + .globl _need_resched + .globl _platform_switchout + .globl _platform_doexec + + ; exported symbols + .globl unix_syscall_entry + .globl _doexec + .globl nmi_handler + .globl interrupt_handler + .globl ___hard_ei + .globl ___hard_di + .globl ___hard_irqrestore + .globl _out + .globl _in + + ; imported symbols + .globl _chksigs + .globl _platform_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 + + +; +; This is the entry point from the platform wrapper. When we hit this +; our stack is above 32K and the upper 32K of kernel space is mapped +; and the low 32K of user space. +; +; The arguments are in BC DE HL IX and the syscall number in A +; The caller has saved IX for us and A BC DE HL don't matter +; The caller has placed us on kstack top and already saved the user sp +; +; Our return is +; A - page to map high +; DE - retval +; H - signal or 0 +; L - errno +; BC - signal vector +; +; We enter and exit with interrupts off, we will enable interurpts in +; the mean time. Due to fork() we may exit twice from one call, and +; due to exiting we may never leave! +; +unix_syscall_entry: + ; Start by saving the arguments. That frees up all our registers + ld (U_DATA__U_CALLNO),a + ld (U_DATA__U_ARGN),bc + ld (U_DATA__U_ARGN1),de + ld (U_DATA__U_ARGN2),hl + ld (U_DATA__U_ARGN3),ix + ; Stack the alternate registers + exx + push bc + push de + push hl + ex af,af' + push af + ; Tell the pre-emption code we are in kernel right now + ld a,#1 + ld (U_DATA__U_INSYS),a + ; Make the kernel appear + call map_kernel_low + ; Call into the kernel with interrupts on + ei + call _unix_syscall + ; Unmap the kernel low 32K + di + call map_user_low + ; We are now not in kernel + xor a + ld (U_DATA__U_INSYS),a + ; Recover the return data + ld hl, (U_DATA__U_ERROR) + ld de, (U_DATA__U_RETVAL) + ld a, (U_DATA__U_CURSIG) + or a + ; Keep the hot path inline + jr nz, signal_path +no_signal: + ; No signal pending + ld h,#0 +syscall_return: + ; restore the alternate registers + pop af + ex af,af' + exx + pop hl + pop de + pop bc + exx + ; Return page for caller (may not be the page we can in on if we + ; swapped + ld a,(U_DATA__U_PAGE2) + ret +signal_path: + ld h,a ; save signal number + push hl + ; Get the signal vector and zero it + add a,a + ld hl,#U_DATA__U_SIGVEC + ld c,a + xor a + ld (U_DATA__U_CURSIG),a + ld b,a + add hl,bc + ld c,(hl) + ld (hl),a ; clear the vector (b = 0) + inc hl + ld b,(hl) + ld (hl),a + pop hl + ld a,b + or c + jr z, no_signal ; cleared under us (can this occur ??) + jr syscall_return + +; +; Execve also needs a platform helper for 32/32K +; +; Runs a low memory stub helper in the user bank with +; HL = start address, IY = relocation base +; Helper must re-enable interrupts +; +_doexec: + di + call map_user_low + xor a + ld (U_DATA__U_INSYS),a + pop bc + pop de ; start address + ld hl,(U_DATA__U_ISP) + ld sp,hl + ex de,hl + ld iy,#PROGLOAD + jp _platform_doexec ; jump into the low memory stub + + +nmimsg: .ascii "[NMI]" + .db 13,10,0 + +nmi_handler: + call map_kernel_low + ld hl, #nmimsg +traphl: + call outstring + call _platform_monitor + +; +; The stub caller has already saved AF DE HL, mapped the kernel high +; and switched to the istack as well as saving the old sp in +; istack_switched_sp +; +; FIXME: pure syscall path +; +interrupt_handler: + call map_save_low ; save old and map kernel + ex af,af' + push af + push bc + exx + push bc + push de + push hl + push ix + push iy + ld a,#1 + ld (_inint),a + ld (U_DATA__U_ININTERRUPT),a + call _platform_interrupt + xor a + ld (_inint),a + ld a,(_need_resched) + or a + jr nz, preemption + ld hl,#intreti + push hl + reti ; Generate M1 RETI cycles to clear + ; any interrupt controller +intreti:di + xor a + ld (U_DATA__U_ININTERRUPT),a + + ; Are we returning to kernel ? + ld a, (U_DATA__U_INSYS) + or a + jr nz, interrupt_kernel +intsig: + ld a,(U_DATA__U_CURSIG) + or a + jr nz, interrupt_sig + ; Nothing special to do +no_sig: + xor a + ld e,a + ld a,(U_DATA__U_PAGE2) +intret: + exx + pop iy + pop ix + pop hl + pop de + pop bc + exx + pop bc + pop af + ex af,af' + ret + +interrupt_kernel: + xor a + ld e,a + jr intret +; +; Signal return. Get the vector, clear the vector and queue, then pass +; the right info back +; +interrupt_sig: + ld e,a + xor a + ld d,a + ld (U_DATA__U_CURSIG),a + ld hl,#U_DATA__U_SIGVEC + add hl,de + add hl,de + ld e,(hl) + ld (hl),a + inc hl + ld d,(hl) + ld (hl),a + ld a,d + or e + jr z, no_sig + jr intret + +; +; The horrible case - task switching time +; +preemption: + xor a + ld (_need_resched),a ; we are doing it thank you + ld hl,(istack_switched_sp) + ld (U_DATA__U_SYSCALL_SP), hl ; save the stack save + ld sp, #kstack_top ; free because we are not pre-empted + ; during a system call + ld hl,#intret2 + push hl + reti ; reti M1 cycle for IM2 +intret2: + di + ; Fake being in a syscall + ld a,#1 + ld (U_DATA__U_INSYS),a + call _chksigs + ld hl, (U_DATA__U_PTAB) + ld (hl), #P_READY + ; Give up the CPU + call _platform_switchout + ; Undo the fakery + xor a + ld (U_DATA__U_ININTERRUPT),a + ld (U_DATA__U_INSYS),a + ld hl,(U_DATA__U_SYSCALL_SP) + ld (istack_switched_sp),hl + ; Now continue on the interrupt return path + ; looking for signals + jr intsig + +; +; 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 + +; +; I/O helpers for cases we don't use __sfr +; +_out: + pop hl + pop bc + out (c), b + push bc + jp (hl) + +_in: + pop hl + pop bc + push bc + push hl + in l, (c) + ret + +; +; Enable interrupts +; +___hard_ei: + ei + ret + +; +; Pull in the CPU specific workarounds +; + +.ifeq CPU_NMOS_Z80 + .include "lowlevel-z80-nmos.s" +.else + .include "lowlevel-z80-cmos.s" +.endif diff --git a/Kernel/usermem_std-z80-thunked.s b/Kernel/usermem_std-z80-thunked.s new file mode 100644 index 00000000..9d10cc07 --- /dev/null +++ b/Kernel/usermem_std-z80-thunked.s @@ -0,0 +1,269 @@ + .module usermem32 + + .include "platform/kernel.def" + .include "kernel.def" + + ; exported symbols + .globl __uget + .globl __ugetc + .globl __ugetw + + .globl __uput + .globl __uputc + .globl __uputw + .globl __uzero + .globl user_mapping + + ; imported memory helpers + + .globl map_page_low + .globl map_kernel_low +; +; We need these in the high bank as they switch +; + .area _COMMONMEM + + +; HL = user address, BC = length, DE = kaddr +; +; On return +; Z HL = address to write to, BC = length, DE = kaddr +; +; NZ HL = address to write to, BC = length before split, DE = kaddr +; HL' = user address of rest BC' = length of rest, DE' = kaddr +; of rest (for a second call) +; +user_mapping: + bit 7,h + jr nz, one_high_map ; all over 32K + push hl + add hl,bc + bit 7,h + jr nz, split_map + ; + ; Copying an area below the 32K boundary + ; + pop hl + ld a,(ix) + call map_page_low + xor a + ret + ; + ; Base address above 32K so copying a single block of + ; high memory. Map page2 low, clear top bit of user address + ; to get target + ; +one_high_map: + res 7,h + ld a,2(ix) + call map_page_low + xor a + ret + ; + ; The hard case - a cross boundary mapping + ; +split_map: + push de ; target to stack for alt regs + exx ; do the initial calculations + pop de ; get the target address into the alt regs + pop bc ; get the start address again + ld hl,#0x8000 + or a + sbc hl,bc ; HL is now bytes that we can fit, BC = start + ld a,c ; swap HL and BC + ld c,l + ld l,a + ld a,b + ld b,h + ld h,a ; HL is start, BC = length to do, DE = target + push bc + exx ; back to the main registers + ; We need to adjust DE and BC here. The start doesn't matter we know + ; the split start with always be 0x8000 + ex de,hl + pop de + add hl,de + ex de,hl ; DE is now the second block target, HL is the bytes + ; done now fix up BC + xor a ; We need to do BC -= HL without disturbing DE + sub l ; so negate BC + ld l,a + sbc a,a + sub h + ld h,a + add hl,bc ; add -HL to BC + ld c,l ; and move it back + ld b,h + ld hl,#0x8000 + exx ; flip back to the right register set + ld a,(ix) + call map_page_low + or h ; set NZ + ret + +uputget: + ; load DE with the byte count + ld c, 8(ix) ; byte count + ld b, 9(ix) + ld a, b + or c + ret z ; no work + ; load HL with the source address + ld l, 4(ix) ; src address + ld h, 5(ix) + ; load DE with destination address (in userspace) + ld e, 6(ix) + ld d, 7(ix) + ret + +__uget: + push ix + ld ix,#0 + add ix, sp + call uputget + jr z, uget_out + call user_mapping + jr z, uget1 + ldir + exx + call user_mapping +uget1: ; not split + ldir +uget_out: + pop ix + jp map_kernel_low + +__uput: + push ix + ld ix,#0 + add ix,sp + call uputget + jr z, uget_out + call user_mapping + jr z, uput1 + ex de,hl + ldir + exx + call user_mapping +uput1: + ex de,hl + ldir + pop ix + jp map_kernel_low + +__uzero: + pop de + pop hl + pop bc + push bc + push hl + push de + ld a,b + or c + ret z + call user_mapping + jr z, zeroit + call zeroit + exx + call user_mapping +zeroit: + ld (hl),#0 + dec bc + ld a,b + or c + ret z + ld e,l + ld d,h + inc de + ldir + jp map_kernel_low + +__ugetc: + pop bc + pop hl + push hl + push bc + bit 7,(hl) + ld a,(U_DATA__U_PAGE) + jr z, ugetcl + ld a,(U_DATA__U_PAGE + 2) +ugetcl: + call map_page_low + ld l,(hl) + ld h,#0 + jp map_kernel_low + + +__ugetw: + pop bc + pop hl + push hl + push bc + bit 7,(hl) + jr z, ugetwl + ld a,(U_DATA__U_PAGE + 2) + call map_page_low + res 7,h + ld a,(hl) + inc hl +normal_wl: + ld h,(hl) + ld l,a + jp map_kernel_low +ugetwl: + ld a,(U_DATA__U_PAGE) + call map_page_low + ld a,(hl) + inc hl + bit 7,h + jr z, normal_wl + ld l,a + ld a,(0) ; Split can only mean one address + ld h,a + ld a,(U_DATA__U_PAGE + 2) + jp map_kernel_low + +__uputc: + pop bc + pop de + pop hl + push hl + push de + push bc + bit 7,(hl) + ld a,(U_DATA__U_PAGE) + jr z, uputcl + ld a,(U_DATA__U_PAGE + 2) +uputcl: + call map_page_low + ld (hl),e + jp map_kernel_low + +__uputw: + pop bc + pop de + pop hl + push hl + push de + push bc + bit 7,(hl) + jr z, uputwl + ld a,(U_DATA__U_PAGE + 2) + call map_page_low + res 7,h + ld (hl),e + inc hl +normal_pwl: + ld (hl),d + jp map_kernel_low +uputwl: + ld a,(U_DATA__U_PAGE) + call map_page_low + ld (hl),e + inc hl + bit 7,h + jr z, normal_pwl + ld a,(U_DATA__U_PAGE + 2) + ld a,d + ld (0),a ; Split can only mean one address + jp map_kernel_low -- 2.34.1