z80: first sketches of the 32K/32K support
authorAlan Cox <alan@linux.intel.com>
Tue, 7 Aug 2018 23:07:46 +0000 (00:07 +0100)
committerAlan Cox <alan@linux.intel.com>
Tue, 7 Aug 2018 23:07:46 +0000 (00:07 +0100)
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 [new file with mode: 0644]
Kernel/usermem_std-z80-thunked.s [new file with mode: 0644]

diff --git a/Kernel/lowlevel-z80-thunked.s b/Kernel/lowlevel-z80-thunked.s
new file mode 100644 (file)
index 0000000..1e5a986
--- /dev/null
@@ -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 (file)
index 0000000..9d10cc0
--- /dev/null
@@ -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