CSRCS = devtty.c devfd.c
CSRCS += devices.c main.c
-ASRCS = crt0.s cromemco.s
+ASRCS = crt0.s cromemco.s usermem.s
ASRCS += tricks.s commonmem.s
AOBJS = $(ASRCS:.s=.rel)
OBJS = $(AOBJS) $(COBJS)
-JUNK = *.rel *.lst *.asm *.sym *.rst
+
all: $(OBJS)
$(CROSS_CC) $(CROSS_CCOPTS) -c $<
clean:
- rm -f $(OBJS) $(JUNK) core *~
+ rm -f *.rel *.lst *.asm *.sym *.rst core *~
+ rm -f cloader-16fdc.ihx cloader-16fdc.tmp
+ rm -f cloader-16fdc.lk cloader-16fdc.bin
+ rm -f cloader-16fdc.map
image:
sdasz80 -o cloader-16fdc.s
FUZIX on a Cromenco (no hard disk support right now - need docs for that and
suitable emulation!)
+The mapping model is intended to be
+
+Bank 0 Kernel
+ 0000-00FF, F200-FFFF are propogated into the other banks
+
+Bank 1-6 User
+ 0000-00FF start with shared vectors
+ FIXME: we need to clean this up in program_vectors
+ from the kernel copy ????
+ 0100-EFFF Application
+ F000-F111 Udata copy for this application
+ F200-FFFF common copy
+
+The kstack and istack exist in each bank. We'll normally only use the kernel
+one but there are cases we switch and borrow a bit of the other bank copy.
+It's easier to arrange this way anyhow.
+
+
+TODO
+- Debug a loader
+- An awful lot of early kernel boot debugging
+- Interrupts properly (and IM2)
+
+
+
We expect
0 tuart / 4 FDC
- .area ASEG(ABS)
- .org 0x80
+ .area ASEG(ABS)
+ .org 0x80
;
-; Loader for Fuzix on a Cromenco 16FDC
+; We have 128 bytes and are loaded by the ROM at 0x0080. The rest is
+; bascially our problem
;
-; The kernel is written in blocks 1+ SS/SD with no magic
-; involved. We have 128 bytes, but fortunately a ROM helper
+; Load sectors 2-26 ( a whopping 3.2K )
;
+; Assumes an 8" disk in SD mode
;
-; Memory map
-; 0x0000-0x002E Stack
-; 0x0060-0x007F Used by the boot ROM
-; 0x0080-0x00FF This boot block
-; 0x0100-0xBFFF Free RAM
-; 0xC000-0xCFFF Boot ROM (until we out to 0x40)
-; 0xD000-0xFFFF May be RAM but may also be ROM shadows
-;
+ ld a,#1
+ out (0x40),a ; ROM off
+
+ ld hl,#go
+ call strout
+
+restart:
+ ld bc,#0x020F ; sector 2 , restore
+ ld de,#0x3100 ; D 8", A drive motor on E track
+
+ ld hl,#0x0100
+ ld a,#0x7f
+ out (0x04),a ; Side 0
+
+ ld a,d ; set up drive
+ out (0x34),a
+
+ ld a,c
+
+movecmd:
+ out (0x30),a ; now doing a restore at lowest speed
-set_side .equ 0xC003
-set_track .equ 0xC006
-set_sector .equ 0xC009
-set_buffer .equ 0xC00C
-disk_restore .equ 0xC00F
-disk_seek .equ 0xC012
-disk_read .equ 0xC015
-disk_write .equ 0xC018
-setup_uart .equ 0xC01B
-char_input .equ 0xC01E
-char_ready .equ 0xC021
-line_input .equ 0xC024
-char_output .equ 0xC027
-newline_output .equ 0xC02A
-
- .globl start
- .globl endc
- .globl end
- .globl msg
-; Carry set, drive in A
-start:
- push af
- ld hl,#boot
- call tout
- ld hl,#0xFC
- pop af
- ld (hl),a ; boot drive
- inc hl
- ld a,#16
- ld (hl),a ; controller type (16FDC)
- inc hl
- ld bc,(0x7E) ; features and feature save for boot disk
- ld (hl),c
- inc hl
- ld (hl),b
- inc hl ; now 0x100
xor a
- ld b,a
- call set_side
- ld a,(0x6E) ; sectors per track
- inc a
- ld c,a
- ld a,#1 ; sector 2
- ld de,#128
-trackloop:
- push af
+wait: dec a
+ jr nz,wait
+
+wait2: in a,(0x34) ; when restore is done we are good
+ rra
+ jr nc, wait2
+
+ in a,(0x30)
+ and #0x98
+ jr nz, restart
+
+ ; Ok drive is up and running
+
ld a,b
- call set_track
- pop af
-secloop:
- inc a
- call set_sector
- call set_buffer
- ex af,af' ; shorter than pushing them
- exx
- call disk_seek
- call disk_read
- jr c, crap
- exx
- add hl,de
- ld a,h
- cp #0xC0
- jr z,go
- ex af,af'
- cp c
- jr nz, secloop
- inc b
- xor a
- jr trackloop
-go:
- ld a,(0x100)
- cp #0xC3
- jr z, 0x100
-crap:
- ld hl, #fail
- call tout
- jp 0xC000
-
-tout:
+ out (0x32),a ; sector we want
+ ld a,d
+ or #0x80 ; autowait
+ out (0x34),a
+
+ ld c,#0x33
+ ld a,#0x9c ; read multiple 128byte, head load delay
+ out (0x30),a ; load heads and read
+
+ ; autowait means we don't poll the ready bit
+eoj:
+ in a,(0x34)
+ rra
+ jr c, trackend
+ ini
+ jp eoj
+trackend:
+ ;
+ ; FIXME: now step in until we hit track 19 (63000 bytes)
+ ;
+ in a,(0x30)
+ bit 4,a
+ jr z,restart
+
+ ld a,d
+ out (0x34),a ; turn off autowait
+
+ inc e
+ ld a,#19
+ cp e
+ jr z, run
+
+ ld a,#'.' ; We know that one byte per track will not
+ out (0),a ; overrun!
+
+ ld b,#0x01 ; run from sector 1
+
+ ;
+ ; Step in a track
+ ;
+ ld a,#0x5F ; step in with verify and update, slowest
+
+
+ jr movecmd
+
+strout:
+ in a,(0x0)
+ rla
+ jr nc,strout
ld a,(hl)
- inc hl
or a
ret z
- push hl
- call char_output
- pop hl
- jr tout
+ out (0),a
+ inc hl
+ jr strout
-msg:
-boot: .asciz 'Boot'
-fail: .ascii ' failed'
- .byte 13,10,0
+go: .ascii 'FUZIX LOADER'
+nl: .db 13,10,0
-endc:
- .org 0xF8
+ .ds 1
- .ascii 'FZSSSD'
+run:
+ ld hl,#nl
+ call strout
end:
+ ; and runs on into 0x100
#define TICKSPERSEC 10 /* Ticks per second */
#define PROGBASE 0x0000 /* also data base */
#define PROGLOAD 0x0100 /* also data base */
-#define PROGTOP 0xED00 /* Top of program, base of U_DATA copy */
-#define PROC_SIZE 60 /* Memory needed per process */
+#define PROGTOP 0xF000 /* Top of program, base of U_DATA copy */
+#define PROC_SIZE 60 /* Memory needed per process */
#define BOOT_TTY (512 + 1)/* Set this to default device for stdio, stderr */
/* In this case, the default is the first TTY device */
; Cromemco hardware support
;
- .module cromemco
-
- ; exported symbols
- .globl init_early
- .globl init_hardware
- .globl _program_vectors
- .globl platform_interrupt_all
-
- .globl map_kernel
- .globl map_process
- .globl map_process_always
- .globl map_kernel_di
- .globl map_process_di
- .globl map_process_always_di
- .globl map_process_a
- .globl map_save_kernel
- .globl map_restore
-
- .globl _platform_reboot
-
- .globl _int_disabled
-
- ; exported debugging tools
- .globl _platform_monitor
- .globl outchar
-
- ; imported symbols
- .globl _ramsize
- .globl _procmem
-
- .globl unix_syscall_entry
- .globl null_handler
- .globl nmi_handler
- .globl interrupt_handler
- .globl _doexit
- .globl _inint
- .globl kstack_top
- .globl _panic
- .globl _need_resched
- .globl _ssig
-
- .globl outcharhex
- .globl outhl, outde, outbc
- .globl outnewline
- .globl outstring
- .globl outstringhex
-
- .include "kernel.def"
- .include "../kernel.def"
+ .module cromemco
+
+ ; exported symbols
+ .globl init_early
+ .globl init_hardware
+ .globl _program_vectors
+ .globl platform_interrupt_all
+
+ .globl map_kernel_low
+ .globl map_save_low
+ .globl map_restore_low
+ .globl map_user_low
+ .globl map_page_low
+
+ .globl _platform_reboot
+
+ .globl _platform_doexec
+
+ .globl _int_disabled
+
+ ; exported debugging tools
+ .globl _platform_monitor
+ .globl outchar
+
+ ; imported symbols
+ .globl _ramsize
+ .globl _procmem
+
+ .globl s__COMMONMEM
+ .globl l__COMMONMEM
+
+ .globl unix_syscall_entry
+ .globl my_nmi_handler
+ .globl interrupt_handler
+ .globl _doexit
+ .globl _inint
+ .globl kstack_top
+ .globl istack_top
+ .globl istack_switched_sp
+ .globl _panic
+ .globl _need_resched
+ .globl _ssig
+
+ .globl outcharhex
+ .globl outhl, outde, outbc
+ .globl outnewline
+ .globl outstring
+ .globl outstringhex
+
+ .include "kernel.def"
+ .include "../kernel.def"
; -----------------------------------------------------------------------------
-; COMMON MEMORY BANK (0xF000 upwards)
+; COMMON MEMORY BANK (0xF200 upwards)
; -----------------------------------------------------------------------------
- .area _COMMONMEM
+ .area _COMMONMEM
_platform_reboot:
_platform_monitor:
- jr _platform_monitor
+ jr _platform_monitor
platform_interrupt_all:
- ret
+ ret
_int_disabled:
- .db 1
+ .db 1
; -----------------------------------------------------------------------------
-; KERNEL MEMORY BANK (below 0xF000, only accessible when the kernel is mapped)
+; KERNEL MEMORY BANK (only accessible when the kernel is mapped)
; -----------------------------------------------------------------------------
- .area _CODE
+ .area _CODE
init_early:
- ld a,#0x81 ; Every memory is in bank 7
- out (0x40),a ; MMU set
- ld hl,#0xF000 ; Copy 4K to itself loading
- ld d,h ; into into the other banks
- ld e,l
- ld b,#0x10
- ld c,l
- ldir
- ld a,#0x01 ; bank to the kernel bank
- out (0x40),a
- ret
+ ld a,#0x81 ; Every memory writeable, read kernel
+ out (0x40),a ; MMU set
+
+ ; Write the stubs everywhere
+ ld hl,#stubs_low
+ ld de,#0x0000
+ ld bc,#0x67
+ ldir
+
+ ; And the common across all banks
+ ld hl,#s__COMMONMEM
+ ld d,h
+ ld e,l
+ ld bc,#l__COMMONMEM
+ ldir
+
+ ld a,#0x01 ; bank to the kernel bank
+ out (0x40),a
+ ret
init_hardware:
- ; set system RAM size
- ld hl, #448
- ld (_ramsize), hl
- ld hl, #(448-64) ; 64K for kernel
- ld (_procmem), hl
-
- ld a, #156 ; ticks for 10Hz (9984uS per tick)
- out (8), a ; 10Hz timer on
+ ; set system RAM size
+ ld hl, #448 ; Assuming fully loaded for now
+ ld (_ramsize), hl
+ ld hl, #(448-64) ; 64K for kernel
+ ld (_procmem), hl
- ; set up interrupt vectors for the kernel (also sets up common memory in page 0x000F which is unused)
- ld hl, #0
- push hl
- call _program_vectors
- pop hl
+ ld a, #156 ; ticks for 10Hz (9984uS per tick)
+ out (8), a ; 10Hz timer on
-; ld a, #0xfe ; Use FEFF (currently free)
-; ld i, a
-; im 2 ; set CPU interrupt mode
- im 1 ; really should use a page and im2?
- ret
+ im 1 ; really should use a page and im2?
+ ret
;------------------------------------------------------------------------------
; COMMON MEMORY PROCEDURES FOLLOW
- .area _COMMONMEM
+ .area _COMMONMEM
+;
+; We switch in one go so we don't have these helpers. This means
+; we need custom I/O wrappers and custom usercopy functions.
+;
+map_save_low:
+map_kernel_low:
+map_restore_low:
+map_user_low:
+map_page_low:
+ ret
_program_vectors:
- ; we are called, with interrupts disabled, by both newproc() and crt0
- ; will exit with interrupts off
- di ; just to be sure
- pop de ; temporarily store return address
- pop hl ; function argument -- base page number
- push hl ; put stack back as it was
- push de
-
- call map_process
-
- ; write zeroes across all vectors
- ld hl, #0
- ld de, #1
- ld bc, #0x007f ; program first 0x80 bytes only
- ld (hl), #0x00
- ldir
-
- ; now install the interrupt vector at 0x38
- ld hl, #interrupt_handler
- ld (0x39), hl
-
- ld a,#0xC3 ; JP
- ; set restart vector for UZI system calls
- ld (0x0030), a ; (rst 30h is unix function call vector)
- ld (0x0038), a ; (rst 38h)
- ld hl, #unix_syscall_entry
- ld (0x0031), hl
-
- ; now install the interrupt vector at 0x38
- ld hl, #interrupt_handler
- ld (0x39), hl
-
- ; Set vector for jump to NULL
- ld (0x0000), a
- ld hl, #null_handler ; to Our Trap Handler
- ld (0x0001), hl
-
- ld (0x0066), a ; Set vector for NMI
- ld hl, #nmi_handler
- ld (0x0067), hl
-
- ; falls through
-
- ; put the paging back as it was -- we're in kernel mode so this is predictable
-map_kernel:
-map_kernel_di:
- push af
- ld a,#1
- out (0x40), a
- ld (map_page), a ; map_page lives in kernel so be careful
- pop af ; our common is r/o common so writes won't
- ret ; cross a bank
-map_process:
-map_process_di:
- ld a, h
- or l
- jr z, map_kernel
- ld a, (hl)
-map_process_a:
- ld (map_page),a ; save before we map out kernel
- out (0x40), a
- ret
-map_process_always:
-map_process_always_di:
- push af
- ld a, (U_DATA__U_PAGE)
- ld (map_page),a ; save before we map out kernel
- out (0x40), a
- pop af
- ret
-map_save_kernel:
- push af ; map_save will always do a map_kernel
- ld a, #1 ; map_kernel so we do the map_kernel
- out (0x40), a ; first so we can get the variables
- ld a, (map_page) ;
- ld (map_store), a
- pop af
- ret
-map_restore: ; called in kernel map
- push af
- ld a, (map_store)
- ld (map_page),a
- out (0x40), a
- pop af
- ret
-map_store:
- .db 0
-map_page:
- .db 0
+ ret
; outchar: Wait for UART TX idle, then print the char in A
; destroys: AF
outchar:
- push af
+ push af
outcharl:
- in a, (0x00)
- bit 7,a
- jr z, outcharl
- pop af
- out (0x01), a
- ret
+ in a, (0x00)
+ bit 7,a
+ jr z, outcharl
+ pop af
+ out (0x01), a
+ ret
;
; Low level pieces for floppy driver
;
- .globl _fd_reset
- .globl _fd_seek
- .globl _fd_operation
- .globl _fd_map
- .globl _fd_cmd
+ .globl _fd_reset
+ .globl _fd_seek
+ .globl _fd_operation
+ .globl _fd_map
+ .globl _fd_cmd
;
; On entry c holds the bits from 0x34, bde must be preserved
;
;
_fd_operation:
- ;
- ; We run in common. Map the kernel or user according to the
- ; destination of the transfer
- ;
- ld a, (_fd_map)
- or a
- call nz, map_process_always
;
; Set up the registers in order. The aux register is already
; done by our caller
issue_command:
ld hl,(_fd_cmd+3) ; buffer
ld c,#0x33 ; data port
- ld a,b
+ ld e,b ; save command code
+ di
+
+ ; If need be flip bank. Remember this also switches stack copy
+ ld a,(_fd_map)
+ or a
+ jr nz, cmdout
+
+ out (0x40),a ; switch bank and stack copy
+cmdout:
+ ld a,e
out (0x30),a ; issue the command
+ ld a,(_fd_map)
+
+ ; FIXME: mappings
;
; For now we only do double density (256 words per sector)
;
; The transfer is done - wait for EOJ
;
fd_waiteoj:
+ ;
+ ; Memory and stack back
+ ;
+ ld a,#1
+ out (0x40),a ; kernel map back
+fd_waiteoj_l:
ei
in a,(0x34)
rra
- jr nc, fd_waiteoj
+ jr nc, fd_waiteoj_l
;
; Job complete. Turn off autowait and recover
; the status byte
;
fd_done:
+ ld a,#1
+ out (0x40),a ; kernel map back
ei
- call map_kernel
ld a,d ; 0x34 bits
and #0x7F
out (0x34),a ; autowait off
pop bc ; 10
ret ; 10
+;
+; Don't be tempted to put the symbol in the code below ..it's relocated
+; to zero. Instead define where it ends up.
+;
+
+_platform_doexec .equ 0x18
+
+ .area _DISCARD
+
+ .globl rst38
+ .globl stubs_low
+;
+; This exists at the bottom of each bank. We move these into place
+; from discard.
+;
+stubs_low:
+ .byte 0xC3
+ .word 0 ; cp/m emu changes this
+ .byte 0 ; cp/m emu I/O byte
+ .byte 0 ; cp/m emu drive and user
+ jp 0 ; cp/m emu bdos entry point
+rst8:
+ ret
+ nop
+ nop
+ nop
+ nop
+ nop
+ nop
+ nop
+rst10:
+ ret
+ nop
+ nop
+ nop
+ nop
+ nop
+ nop
+ nop
+rst18:
+ ; Activate the user bank (which also holds these bytes)
+ ld a,(U_DATA__U_PAGE)
+ out (0x40),a
+ ei
+ ; and leap into user space
+ jp (hl)
+ nop
+ nop
+rst20: ret
+ nop
+ nop
+ nop
+ nop
+ nop
+ nop
+ nop
+rst28: ret
+ nop
+ nop
+ nop
+ nop
+ nop
+ nop
+ nop
+rst30: jp syscall_high
+ nop
+ nop
+ nop
+ nop
+ nop
+;
+; We only have 38-4F available for this in low space
+;
+rst38: jp interrupt_high ; Interrupt handling stub
+ nop
+ nop
+ nop
+ nop
+ nop
+ .ds 0x26
+my_nmi_handler: ; Should be at 0x66
+ retn
+
+;
+; This stuff needs to live somewhere, anywhere out of the way (so we
+; use common). We need to copy it to the same address on both banks
+; so place it in common as we will put common into both banks
+;
+
+ .area _COMMONMEM
+
+ .globl ldir_to_user
+ .globl ldir_from_user
+ .globl ldir_far
+;
+; This needs some properly optimized versions!
+;
+ldir_to_user:
+ ld de,(U_DATA__U_PAGE) ; will load with 0 e with page
+ inc d ; Kernel is in #1
+ldir_far:
+ push bc
+ ld c,#0x40
+ exx
+ pop bc ; get BC into alt bank
+far_ldir_1:
+ exx
+ out (c),d ; Select source
+ ld a,(hl)
+ inc hl
+ out (c),e ; Select target
+ ld (ix),a
+ inc ix
+ exx
+ dec bc
+ ld a,b
+ or c
+ jr nz, far_ldir_1
+ ld a,#1
+ out (0x40),a ; Select kernel
+ ret
+ldir_from_user:
+ ld a,(U_DATA__U_PAGE)
+ ld e,#1
+ ld d,a
+ jr ldir_far
+;
+; High stubs. Present in each bank in the top 256 bytes
+; of the available space (remembering F000-FFFF is not available
+; for general usage but is ok for reading)
+;
+interrupt_high:
+ push af
+ push de
+ push hl
+ ex af,af'
+ push af
+ push bc
+ exx
+ push bc
+ push de
+ push hl
+ push ix
+ push iy
+ in a,(0x40) ; bank register is thankfully R/W
+ ld c,a
+ ld a,#0x01
+ out (0x40),a ; Kernel map
+ ld (istack_switched_sp),sp ; istack is positioned to be valid
+ ld sp,#istack_top ; in both banks. We just have to
+ ;
+ ; interrupt_handler may come back on a different stack in
+ ; which case bc is junk. Fortuntely we never pre-empt in
+ ; kernel so the case we care about bc is always safe. This is
+ ; not a good way to write code and should be fixed! FIXME
+ ;
+ push bc
+ call interrupt_handler ; switch on the right SP
+ pop bc
+ ; Restore stack pointer to user. This leaves us with an invalid
+ ; stack pointer if called from user but interrupts are off anyway
+ ld sp,(istack_switched_sp)
+ ; On return HL = signal vector E= signal (if any) A = page for
+ ; high
+ or a
+ jr z, kernout
+ ; Returning to user space
+ out (0x40),a ; page passed back
+ ; User stack is now valid
+ ; back on user stack
+ xor a
+ cp e
+ call nz, sigpath
+pops:
+ ex af,af'
+ exx
+ pop iy
+ pop ix
+ pop hl
+ pop de
+ pop bc
+ exx
+ pop bc
+ pop af
+ ex af,af'
+ pop hl
+ pop de
+ pop af
+ ei
+ ret
+kernout:
+ ; restore bank - if we interrupt mid user copy or similar we
+ ; have to put the right bank back
+ ld a,c
+ out (0x40),a
+ jr pops
+
+sigpath:
+ push de ; signal number
+ ld de,#irqsigret
+ push de ; clean up
+ jp (hl)
+irqsigret:
+ inc sp ; drop signal number
+ inc sp
+ ret
+
+syscall_high:
+ push ix
+ ld ix,#0
+ add ix,sp
+ push de ; the syscall if must preserve de for now
+ ; needs fixing when we change the syscall
+ ; API for Z80 to something less sucky
+ ld a,4(ix)
+ ld c,6(ix)
+ ld b,7(ix)
+ ld e,8(ix)
+ ld d,9(ix)
+ ld l,10(ix)
+ ld h,11(ix)
+ push hl
+ ld l,12(ix)
+ ld h,13(ix)
+ pop ix
+ di
+ ; FIXME: corrupts af' - probably will define af' and some other '
+ ; registers as non-save in API change anyway
+ ex af, af' ; Ick - find a better way to do this bit !
+ ld a,#1 ; Kernel
+ out (0x40),a
+ ex af,af'
+ ; Stack now invalid
+ ld (U_DATA__U_SYSCALL_SP),sp
+ ld sp,#kstack_top
+ call unix_syscall_entry
+ ; FIXME check di rules
+ ; stack now invalid. Grab the new sp before we unbank the
+ ; memory holding it
+ ld sp,(U_DATA__U_SYSCALL_SP)
+ ld a, (U_DATA__U_PAGE) ; back to the user page
+ out (0x40),a
+ xor a
+ cp h
+ call nz, syscall_sigret
+ ; FIXME for now do the grungy C flag HL DE stuff from
+ ; lowlevel-z80 until we fix the ABI
+ ld bc,#0
+ ld a,h
+ or l
+ jr nz, error
+ ex de,hl
+ pop de
+ pop ix
+ ei
+ ret
+error: scf
+ pop de
+ pop ix
+ ei
+ ret
+syscall_sigret:
+ ld a,l ; DEBUG
+ push hl ; save errno
+ push de ; save retval
+ ld l,h
+ ld h,#0
+ push hl ; signal
+ ld hl,#syscall_sighelp
+ push hl ; vector
+ ret
+syscall_sighelp:
+ pop de ; discard signal
+ pop de ; recover error info
+ pop hl
+ ld h,#0 ; clear signal bit
+ ret
; Configure memory map
call init_early
- ; move the common memory where it belongs
+ ld a,#0x81 ; Every memory writeable, read kernel
+ out (0x40),a ; MMU set
+
+ ; move the common memory where it belongs in all banks
+
ld hl, #s__DATA
ld de, #s__COMMONMEM
ld bc, #l__COMMONMEM
ldir
- ; and the discard
+
+ ld a,#0x01 ; Kernel mapping only
+ out (0x40),a ; MMU set
+
+ ; and the discard to the kernel
ld de, #s__DISCARD
ld bc, #l__DISCARD
ldir
+
; then zero the data area
ld hl, #s__DATA
ld de, #s__DATA + 1
-mwxuy
-i fuzix.ihx
-b _CODE=0x0100
--b _COMMONMEM=0xF000
+-b _COMMONMEM=0xF200
-b _DISCARD=0xE000
-l z80
platform-cromemco/crt0.rel
platform-cromemco/main.rel
start.rel
version.rel
-lowlevel-z80.rel
-usermem_std-z80.rel
+lowlevel-z80-thunked.rel
platform-cromemco/tricks.rel
timer.rel
kdata.rel
usermem.rel
+platform-cromemco/usermem.rel
platform-cromemco/devfd.rel
platform-cromemco/devices.rel
devio.rel
; UZI mnemonics for memory addresses etc
-U_DATA .equ 0xF000 ; (this is struct u_data from kernel.h)
-U_DATA__TOTALSIZE .equ 0x300 ; 256+256+256 bytes.
+U_DATA .equ 0xF200 ; (this is struct u_data from kernel.h)
+U_DATA__TOTALSIZE .equ 0x200 ; 256+256 bytes
-U_DATA_STASH .equ 0xED00 ; ED00-EFFF
+U_DATA_STASH .equ 0xF000 ; F000-F200
PROGBASE .equ 0x0000
PROGLOAD .equ 0x0100
Z80_MMU_HOOKS .equ 0
-
CONFIG_SWAP .equ 0
+
+;
+; Select where to put the high code - in our case we need this
+; in _COMMONMEM
+;
+.macro HIGH
+ .area _COMMONMEM
+.endm
void pagemap_init(void)
{
int i;
+ /* 1 << 0 is kernel */
for (i = 1; i < 7; i++)
pagemap_add(1 << i);
}
.include "../kernel.def"
.include "kernel.def"
- .include "../lib/z80fixedbank.s"
+ ;
+ ; We can't quite use the standard helpers as we have rather weird
+ ; memory mapping logic
+ ;
+ .module cromemcotricks
+
+ .globl _ptab_alloc
+ .globl _makeproc
+ .globl _chksigs
+ .globl _getproc
+ .globl _platform_monitor
+ .globl _platform_switchout
+ .globl _switchin
+ .globl _doexec
+ .globl _dofork
+ .globl _runticks
+ .globl unix_syscall_entry
+ .globl interrupt_handler
+ .globl _need_resched
+ .globl _nready
+ .globl _platform_idle
+ .globl _int_disabled
+ .globl _udata
+
+ .globl ldir_to_user
+ .globl ldir_from_user
+ .globl ldir_far
+
+ ; imported debug symbols
+ .globl outstring, outde, outhl, outbc, outnewline, outchar, outcharhex
+
+ .area _COMMONMEM
+
+; __switchout switches out the current process, finds another that is READY,
+; possibly the same process, and switches it in. When a process is
+; restarted after calling switchout, it thinks it has just returned
+; from switchout().
+;
+; This function can have no arguments or auto variables.
+;
+_platform_switchout:
+ ld hl, #0 ; return code set here is ignored, but _switchin can
+ ; return from either _switchout OR _dofork, so they must both write
+ ; U_DATA__U_SP with the following on the stack:
+ push hl ; return code
+ push ix
+ push iy
+ ld (U_DATA__U_SP), sp ; this is where the SP is restored in _switchin
+
+ ; Stash the uarea back into process memory
+ ld hl, #U_DATA
+ ld ix, #U_DATA_STASH
+ ld bc, #U_DATA__TOTALSIZE
+ ; Slow - must be a more elegant way to tackle this ! FIXME
+ call ldir_to_user
+
+ ; find another process to run (may select this one again)
+ call _getproc
+
+ push hl
+ call _switchin
+
+ ; we should never get here
+ call _platform_monitor
+
+badswitchmsg: .ascii "_switchin: FAIL"
+ .db 13, 10, 0
+swapped: .ascii "_switchin: SWAPPED"
+ .db 13, 10, 0
+
+_switchin:
+ di
+ pop bc ; return address
+ pop de ; new process pointer
+
+ ld hl, #P_TAB__P_PAGE_OFFSET
+ add hl, de ; process ptr
+
+.ifne CONFIG_SWAP
+ .globl _swapper
+ ;
+ ; Always use the swapstack, otherwise when we call map_kernel
+ ; having copied the udata stash back to udata we will crap
+ ; somewhere up the stackframe and it's then down to luck
+ ; if those bytes are discarded or not.
+ ;
+ ; Yes - this was a bitch to debug, please don't break it !
+ ;
+ ld sp, #_swapstack
+
+ ld a, (hl)
+
+ or a
+ jr nz, not_swapped
+
+ ;
+ ; Re-enable interrupts while we swap. This is ok because
+ ; we are not on the IRQ stack when switchin is invoked.
+ ;
+ ; There are two basic cases
+ ; #1: pre-emption. Not in a system call, must avoid
+ ; re-entering pre-emption logic, Z80 lowlevel code sets U_INSYS
+ ; #2: kernel syscall. Also protected by U_DATA__U_INSYS
+ ;
+ ei
+ xor a
+ ld (_int_disabled),a
+ push hl
+ push de
+ call _swapper
+ pop de
+ pop hl
+ ld a,#1
+ ld (_int_disabled),a
+ di
+.endif
+ ld a, (hl)
+not_swapped:
+ ; We need the swap stack anyway or we run out of registers
+ ld sp, #_swapstack
+
+ ld hl, (U_DATA__U_PTAB)
+ or a
+ sbc hl, de
+ jr z, skip_copyback ; Tormod's optimisation: don't copy the
+ ; the stash back if we are the task who
+ ; last owned the real udata
+
+ ; bear in mind that the stack will be switched now, so we can't use it
+ ; to carry values over this point
+
+ push de
+
+ ld hl, #U_DATA_STASH
+ ld de, #U_DATA
+ ld bc, #U_DATA__TOTALSIZE
+ call ldir_to_user
+
+ pop de
+
+ ; In the non swap case we must set so before we use the stack
+ ; otherwise we risk corrupting the restored stack frame
+ ld sp, (U_DATA__U_SP)
+
+ ; check u_data->u_ptab matches what we wanted
+ ld hl, (U_DATA__U_PTAB) ; u_data->u_ptab
+ or a ; clear carry flag
+ sbc hl, de ; subtract, result will be zero if DE==HL
+ jr nz, switchinfail
+
+skip_copyback:
+ ; wants optimising up a bit
+ ld ix, (U_DATA__U_PTAB)
+ ; next_process->p_status = P_RUNNING
+ ld P_TAB__P_STATUS_OFFSET(ix), #P_RUNNING
+
+ ; Fix the moved page pointers
+ ; Just do one byte as that is all we use on this platform
+ ld a, P_TAB__P_PAGE_OFFSET(ix)
+ ld (U_DATA__U_PAGE), a
+ ; runticks = 0
+ ld hl, #0
+ ld (_runticks), hl
+
+ ; restore machine state -- note we may be returning from either
+ ; _switchout or _dofork
+ ld sp, (U_DATA__U_SP)
+
+ pop iy
+ pop ix
+ pop hl ; return code
+
+ ; enable interrupts, if we didn't pre-empt in an ISR
+ ld a, (U_DATA__U_ININTERRUPT)
+ ld (_int_disabled),a
+ or a
+ ret nz ; Not an ISR, leave interrupts off
+ ei
+ ret ; return with interrupts on
+
+switchinfail:
+ call outhl
+ ld hl, #badswitchmsg
+ call outstring
+ ; something went wrong and we didn't switch in what we asked for
+ jp _platform_monitor
+
+fork_proc_ptr: .dw 0 ; (C type is struct p_tab *) -- address of child process p_tab entry
+
+;
+; Called from _fork. We are in a syscall, the uarea is live as the
+; parent uarea. The kernel is the mapped object.
+;
+_dofork:
+ ; always disconnect the vehicle battery before performing maintenance
+ di ; should already be the case ... belt and braces.
+
+ ;
+ ; FIXME: we should no longer need interrupts off for most of a
+ ; fork() call.
+ ;
+ pop de ; return address
+ pop hl ; new process p_tab*
+ push hl
+ push de
+
+ ld (fork_proc_ptr), hl
+
+ ; prepare return value in parent process -- HL = p->p_pid;
+ ld de, #P_TAB__P_PID_OFFSET
+ add hl, de
+ ld a, (hl)
+ inc hl
+ ld h, (hl)
+ ld l, a
+
+ ; Save the stack pointer and critical registers.
+ ; When this process (the parent) is switched back in, it will be as if
+ ; it returns with the value of the child's pid.
+ push hl ; HL still has p->p_pid from above, the return value in the parent
+ push ix
+ push iy
+
+ ; save kernel stack pointer -- when it comes back in the parent we'll be in
+ ; _switchin which will immediately return (appearing to be _dofork()
+ ; returning) and with HL (ie return code) containing the child PID.
+ ; Hurray.
+
+ ld (U_DATA__U_SP), sp
+
+ ; now we're in a safe state for _switchin to return in the parent
+ ; process.
+
+ ; --------- copy process ---------
+
+ ld hl, (fork_proc_ptr)
+ ld de, #P_TAB__P_PAGE_OFFSET
+ add hl, de
+ ; load p_page
+ ld c, (hl)
+ ; load existing page ptr
+ ld a, (U_DATA__U_PAGE)
+
+ push bc
+ call bankfork ; do the bank to bank copy
+ pop bc
+
+ ; Copy done
+
+ ; We are going to copy the uarea into the parents uarea stash
+ ; we must not touch the parent uarea after this point, any
+ ; changes only affect the child
+ ld hl, #U_DATA ; copy the udata from common into the
+ ld de, #U_DATA_STASH ; target process
+ ld bc, #U_DATA__TOTALSIZE
+
+ call ldir_to_user
+
+ ; now the copy operation is complete we can get rid of the stuff
+ ; _switchin will be expecting from our copy of the stack.
+ pop bc
+ pop bc
+ pop bc
+
+ ; Make a new process table entry, etc.
+ ld hl,#_udata
+ push hl
+ ld hl, (fork_proc_ptr)
+ push hl
+ call _makeproc
+ pop bc
+ pop bc
+
+ ; runticks = 0;
+ ld hl, #0
+ ld (_runticks), hl
+ ; in the child process, fork() returns zero.
+ ;
+ ; And we exit, with the kernel mapped, the child now being deemed
+ ; to be the live uarea. The parent is frozen in time and space as
+ ; if it had done a switchout().
+ ret
+
+ .area _COMMONMEM
+;
+; We can keep a stack because we don't have enough registers
+; to do all the bank trickery we need
+;
+ .ds 256
+_swapstack:
+_need_resched: .db 0
+
+ ;
+ ; FIXME: optimize ldir_far. or maybe add an ldir_far_aligned
+ ; This one is really hard to do at a sensible speed
+ ;
+bankfork:
+ ld d,c ; source
+ ld e,a ; dest
+ ld hl,#0x0000
+ ld ix,#0x0000
+ ld bc,#0xF000
+ call ldir_far
+ ret