.export unix_syscall_entry
.export _doexec
.export interrupt_handler
- .export null_handler
.export nmi_handler
- .export trap_illegal
.export outstring
.export outstringhex
.export outnewline
- .export outx
- .export outy
.export outcharhex
+ .import outchar
+ .import _kernel_flag
+ .import _unix_syscall_i
+ .include "platform/zeropage.inc"
+
+ .segment "COMMONMEM"
+;
+; Unlike Z80 we need to deal with systems that have no overlapping
+; memory banks only a 'far write/far read' model (eg 6509). We pass
+; the arguments is a single pointer therefore we expect the platform
+; code to have copied the syscall arguments into udata then called us
+; it also saves any registers etc for us (as it will need them too)
+;
+; Called with interrupts off, on the kernel stack
+; On completion U_DATA__U_ERROR an U_DATA__U_RETVAL hold the returns
+;
unix_syscall_entry:
-_doexec:
+ sty U_DATA__U_CALLNO ; Save the syscall code
+ lda #1
+ sta _kernel_flag ; In kernel mode
+ jsr map_kernel ; Ensure kernel is mapped (no-op in some cases)
+ cli ; Interrupts now ok
+ jsr _unix_syscall_i ; Enter C space via the __interrupt wrapper
+ sei ; Interrupts back off
+ pha
+ txa
+ pha ; Save return code
+ lda #0
+ sta _kernel_flag
+unix_sig_exit:
+ jsr map_process_always ; Map back process if we have common
+ rts
+
+;
+; On 6502 the platform code is responsible for invoking the
+; signal dispatch (as it may have to be in the stub in the
+; process space if we have no common)
+;
+
+
+;
+; doexec is a special case syscal exit path. As we may have no
+; common we have to hand the last bits off to the platform code
+;
+_doexec
+ sei
+ lda #0
+ sta _kernel_flag
+ call map_process_always
+ jmp platform_doexec
+
+;
+; Platform code has saved the registers and ensured we are in the
+; right banks if running with no common (6509 etc). It has also
+; done stack switches and checked for re-entrancy
+;
+; Caller on the exit side is responsible for stack switches and
+; checking for signals
+;
interrupt_handler:
-null_handler:
+ jsr map_save
+ jsr map_kernel
+ lda #1
+ sta _inint
+ jsr _platform_interrupt_i ; call via C int wrapper
+ lda #0
+ sta _inint
+ lda _kernel_flag
+ bne interrupt_k
+ jsr map_process_always ; may have switched task
+ jr int_switch
+interrupt_k:
+ jsr map_restore
+int_switch:
+ lda #0
+ sta _inint
+ rts
+
nmi_handler:
-trap_illegal:
+ ldx #>nmi_trap
+ lda #<nmi_trap
+ jsr outstring
+nmi_stop:
+ jmp nmi_stop
+
outstring:
-outstringhex:
+ sta ptr1
+ stx ptr1+1
+ ldy #0
+outstringhexl:
+ lda (ptr1),y
+ cmp #0
+ beq outdone1
+ call outcharhex
+ iny
+ jmp outstringhexl
+
+outstringhex: ; string in X,A
+ sta ptr1
+ stx ptr1+1
+ ldy #0
+outstringhexl:
+ lda (ptr1),y
+ cmp #0
+ beq outdone1
+ call outcharhex
+ iny
+ jmp outstringhexl
+
outnewline:
-outx:
-outy:
-outcharhex:
+ pha
+ lda #10
+ jsr outchar
+ lda #10
+ jsr outchar
+ pla
rts
+
+outcharhex:
+ pha
+ and #$f0
+ lsr a
+ lsr a
+ lsr a
+ lsr a
+ add #'0'
+ jsr outchar
+ pla
+ and #$0f
+ add #'0'
+ jmp outchar
--- /dev/null
+6502 test sketching out
+
+This is not a complete port, but a sketch in progress to test that it should
+be possible to run FUZIX on a banked 6502 such as the Commodore 6509 based
+systems.
+
+The 6509 also provides an opportunity to test a very different banking
+model. There is no common area on a 6509 based system. Address 0 sets the
+64K bank to be used and swaps wholesale between banks. Address 1 set the
+bank used on two specific instructions LDA (foo),y and STA (foo),y. In other
+words the only interbank feature you have is far data pointers.
+
+
+For Fuzix this should mean we can
+
+- select fixed banks so we hand each task a bank
+- make map_kernel/map_process/map_* in general mostly no-ops. We do
+ need to save address 1 on map_save/restore in case the kernel is mid
+ far access
+- add a new section "STUBS" which is copied to the same spot in each bank
+ and contains mini wrappers for syscall, interrupts, doexec and signal
+ dispatch
+
+Interrupts simply flip bank and go via a C __interrupt wrapper which saves
+the C stack state for the kernel, then runs the IRQ handler, then returns
+back.
+
+ISSUE: need to figure where to save the __interrupt bits as we task switch
+in irq cases
+
+
+System calls far copy the syscall code and the arguments into the udata
+rather than fishing them off the stack as Z80 does. They then switch to the
+kernel stack, set up the C stack and run the system call. When it returns
+for that process context we copy back the error and return values into the
+user bank, and bankflip back.
+
+In both cases we need to also pull over the pending signal and the vector
+for it as we need to dispatch the signal from the user bank. (*TODO*)
+
+
+The other odd case is doexec. When we exec a process we don't return via
+the system call path but instead directly exit kernel mode and jump to the
+new stack/pc. This is arguably a wart and we should instead patch the return
+stack (and fix start up). For now however we need a stub of code to do the
+dispatch. Basically we set the sp in the user bank, switch to the user bank
+and jump to the target
.export _ei
.export _irqrestore
+ .import interrupt_handler
+ .import _ramsize
+ .import _procmem
+ .import nmi_handler
+ .import unix_syscall_entry
+
.include "kernel.def"
.include "../kernel02.def"
+ .include "zeropage.inc"
; -----------------------------------------------------------------------------
; COMMON MEMORY BANK (0xF000 upwards)
_trap_monitor:
sei
- bra _trap_monitor
+ jmp _trap_monitor
_trap_reboot:
; lda 0xff90
; jmp 0
_di:
+ php
sei ; FIXME: save old state in return to C
+ pla ; Old status
rts
_ei:
cli ; on 6502 cli enables IRQs!!!
rts
_irqrestore:
- ; FIXME - pull off C stack
+ and #4 ; IRQ flag
+ beq irq_on
+ cli
+ rts
+irq_on:
+ sei
rts
; -----------------------------------------------------------------------------
; set system RAM size for test purposes
lda #1
sta _ramsize+1
- dea
+ lda #0
sta _ramsize
sta _procmem+1
lda #192
sta _procmem
-; ; Our vectors are in high memory unlike Z80 but we still
-; ; need vectors
-; FIXME: need to make a C call here
-; ldx #0
-; jsr _program_vectors
+ jsr program_vectors_k
rts
_program_vectors:
; we are called, with interrupts disabled, by both newproc() and crt0
; will exit with interrupts off
-; orcc #0x10 ; di just to be sure
-
-; jsr map_process
+ sei
+ ;
+ ; Fixme: block copy stubs segment as well if 6509.
+ ;
+ ; our C caller will invoke us with the pointer in x,a
+ ; just pass it on
+ jsr map_process
+program_vectors_k:
lda #<vector
- sta 0xFFFE
+ sta $FFFE
lda #>vector
- sta 0xFFFF
+ sta $FFFF
+ lda #<nmi_handler
+ sta $FFFA
+ lda #>nmi_handler
+ sta $FFFB
+ ; However tempting it may be to use BRK for system calls we
+ ; can't do this on an NMOS 6502 because the chip has brain
+ ; dead IRQ handling buts that could simply "lose" the syscall!
+ lda #JSR
+ sta syscall
+ lda #<syscall_entry
+ sta syscall+1
+ lda #>syscall_entry
+ sta syscall+2
jsr map_kernel
rts
-
-vector:
- ; FIXME: decide whether its an IRQ or syscall and branch
-
-; ldd #interrupt_handler
-; ldd #unix_syscall_entry
- rts
-
;
-; Userspace mapping pages 7+ kernel mapping pages 3-5, first common 6
+; On a fully switching system with far copies like the 6509 this is
+; all basically a no-op
;
-; Pass the table pointer in zero page ?
+; On a banked setup the semantics are:
;
-map_process_always:
-; pshs y,u
-; ldx #U_DATA__U_PAGE
-; jsr map_process_2
-; puls y,u,pc
+; map_process_always()
+; Map the current process (ie the one with the live uarea)
;
-; HL is the page table to use, A is eaten, HL is eaten
+; map_kernel()
+; Map the kernel
;
-map_process:
-; cmpx #0
-; bne map_process_2
+; map_process(pageptr [in X,A])
+; if pageptr = 0 then map_kernel
+; else map_process using the pageptr
;
-; Map in the kernel below the current common, all registers preserved
+; map_save
+; save the current mapping
;
-map_kernel:
+; map_restore
+; restore the saved mapping
;
-; Two MMU mappings is pure luxury
+; save/restore are used so that the kernel can play with its internal
+; banking/mappings without having to leave interrupts off all the time
;
-; Kernel map was set up by boot loader, just flip to it
+map_process_always:
+ pha
+ lda #<U_DATA__U_PAGE
+ ldx #>U_DATA__U_PAGE
+ jsr map_process_2
+ pla
+ ret
;
-; lda 0xff91 ; INIT1, use 0xFFA8 maps
-; ora #0x01
-; sta 0xff91
- rts
+; X,A points to the map table of this process
;
-; User is in the FFA0 map with the top 8K as common
+map_process:
+ cmp #0
+ bne map_process_2
+ cpx
+ bne map_process_2
;
-; As the core code currently does 16K happily but not 8 we just pair
-; up pages
+; Map in the kernel below the current common, all registers preserved
;
+map_kernel:
+ lda #1 ; for 6509 clean up any far copy ptr
+ sta 1
+ rts
+
+; X,A holds the map table of this process
map_process_2:
-; pshs x,y,a
-; ldy #0xffa0 ; MMU user map. We can fiddle with
-; lda ,x+ ; this to our hearts content
-; sta ,y+ ; as it's not live yet
-; inca
-; sta ,y+
-; lda ,x+
-; sta ,y+
-; inca
-; sta ,y+
-; lda ,x+
-; sta ,y+
-; inca
-; sta ,y+
-; lda ,x+
-; sta ,y+
-; inca
-; sta ,y
-; lda 0xff91
-; anda #0xfe
-; sta 0xff91 ; new mapping goes live here
-; puls x,y,a,pc ; so had better include common!
-;
-; Restore a saved mapping. We are guaranteed that we won't switch
-; common copy between save and restore. Preserve all registers
-;
-; We cheat somewhat. We have two mapping sets, so just remember
-; which space we were in
+ tya
+ pha
+ sta ptr1
+ sty ptr1+1
+ ldy #0
+ lda (ptr1),y ; 4 bytes if needed
+ sta map_current
+ pla
+ tay
+ rts
+
+
+;
+; Restore the indirection register on the 6509, just in case we
+; interrupted mid far copy
;
map_restore:
-; pshs a
-; lda 0xff91
-; ora saved_map
-; sta 0xff91
-; puls a,pc
-;
+ pha
+ lda saved_map
+ sta 1
+ pla
+ rts
-; Save the current mapping.
+; Save the current mapping. For a 6509 just save the indirection
;
map_save:
-; pshs a
-; lda 0xff91
-; anda #1
-; sta saved_map
-; puls a,pc
+ pha
+ lda 1
+ sta saved_map
+ pla
+ rts
saved_map: .dbyt 0
-
+map_current: .dbyt 0
-; outchar: Wait for UART TX idle, then print the char in a
+; outchar: Wait for UART TX idle, then print the char in a without
+; corrupting other registers
outchar:
-; pshs b
outcharw:
; ldb 0xffa0
; bitb #0x02
; beq outcharw
; sta 0xffa1
; puls b,pc
+ rts
+
+;
+; Code that will live in each bank at the same address and is copied
+; there on 6509 type setups. On 6502 it may well be linked once in
+; common space
+;
+ .segment "STUBS"
+;
+; Interrupt vector logic. Keep this in platform for the 6502 so that
+; we can use the shorter one for the CMOS chip
+;
+; FIXME: spot BRK instructions and turn them into a synchronous
+; SIGTRAP
+;
+vector:
+ pha
+ txa
+ pha
+ tya
+ pha
+ cld
+ tsx
+ inx
+ inx
+ inx
+ inx
+ lda $0100,X
+ and a, $10
+;
+; FIXME: either don't care about brk or ship it somewhere like
+; kill -SIGTRAP
+;
+ bne bogon
+;
+; Switch to the kernel memory mapping (stack gone away)
+;
+ lda 1 ; Save the indirection vector
+ pha
+ lda #1 ; Kernel in bank 1
+ sta 1
+ sta 0
+; The next fetch will occur from the new mapping (kernel) copy
+; Stack has gone for a walk if we were not coming from kernel
+ tsx
+ stx istack_switched_sp ; in uarea/stacks
+ ldx #0xC0
+ txs ; our istack
+ jsr interrupt_handler
+ ldx istack_switched_sp
+ txs ; entry stack
+ lda _kernel_flag
+ beq vector_um
+;
+; Already in kernel (bank 1)
+;
+ pla
+ sta 1 ; restore the saved indirect
+ jmp irqout
+vector_um:
+ lda U_DATA__U_PAGE ; may be a new task
+ sta 1 ; flip to it
+ sta 0
+; leap back into user context (may be a different user context to the one
+; we left. Stack now valid again
+ pla ; discard saved idirect
+
+irqout:
+ pla
+ tya
+ pla
+ txa
+ pla
+ rti
+
+
+; X, A holds the syscall block
+; Y holds the call code
+; We assume the kernel is in bank 0
+;
+syscall_entry:
+ sei
+ sty tmp1 ; for a moment
+ sta ptr1
+ stx ptr1+1
+ lda #<U_DATA__U_ARGN
+ sta ptr2
+ lda #>U_DATA__U_ARGN
+ sta ptr2+1
+ lda #1
+ sta 1 ; magic far copy hackery
+
+ ldx #0
+ txy
+copy_args: lda (ptr1), x ; copy the arguments from current bank
+ sta (ptr2), y ; will write into bank 1
+ inx
+ iny
+ cpx #8
+ bne copy_args
+ ldy tmp1 ; syscall code
+ ;
+ ; Now we need to bank and stack switch
+ ;
+ tsx
+ lda #1
+ sta 0 ; kernel banks
+ sta 1
+;
+; We are now suddenely in the kernel copy of this, and our stack is
+; missing in action. Access to userspace is not available
+;
+; On a 6509 this also means our C stack is missing in action, which
+; is quite convenient as it saves us a save and restore it. On other
+; CPUs it might be a little less convenient
+;
+ stx U_DATA__U_SP
+ ldx #0
+;
+; FIXME: how to handle IRQ division of stack ???
+;
+ txs ; Switch to the working stack
+;
+; Set up the C stack
+;
+ lda #<kstack_top
+ sta sp
+ lda #>kstack_top
+ sta sp+1
+
+ cli
+;
+; Caution: We may enter here and context switch and another task
+; exit via its own syscall returning in its own memory context.
+;
+; Don't assume anything we stored statically *except* the uarea
+; will be different. The uarea is banked in and out (or copied in
+; more awkward systems).
+;
+ jsr unix_syscall_entry
+
+ sei
+;
+; Correct the system stack
+;
+ ldx U_DATA__U_SP
+ txs
+ lda U_DATA__U_PAGE ; our bank
+ sta 1 ; use our bank for sta (ptr), y
+;
+; Copy the return data over
+;
+ ldy #8 ; write them after the argument block
+;
+; As we are a user process and making a syscall our ptr1 was saved
+; and banked out. If you had common memory this would be uglier but
+; then you've got somewhere else to put the pointer right !
+;
+ lda U_DATA__U_ERROR
+ sta (ptr1), y
+ iny
+ lda U_DATA__U_ERROR+1
+ sta (ptr1),y
+ iny
+ lda U_DATA__U_RETVAL
+ sta (ptr1),y
+ iny
+ lda U_DATA__U_RETVAL+1
+ sta (ptr1), y
+; FIXME Also copy over needed signal information (vector, pending stuff)
+;
+ lda 1 ; The sta,y redirection bank
+ sta 0 ; becomes our execution bank
+;
+; We just teleported back to the copy of this code in the
+; user process bank
+;
+; Our C stack is valid as we simply bounced out of it for the 6509.
+;
+;
+; FIXME: do signal dispatch - this will need C stack fixing, and
+; basically signal dispatch is __interrupt.
+;
+
+ rts