--- /dev/null
+
+CSRCS = devtty.c devhd.c
+CSRCS += devices.c main.c
+
+ASRCS = v65.s crt0.s
+ASRCS += tricks.s commonmem.s
+
+COBJS = $(CSRCS:.c=$(BINEXT))
+AOBJS = $(ASRCS:.s=$(BINEXT))
+OBJS = $(COBJS) $(AOBJS)
+
+JUNK = $(CSRCS:.c=.o) $(CSRCS:.c=.s) $(ASRCS:.s=.o)
+
+all: $(OBJS)
+
+$(COBJS): %$(BINEXT): %.c
+ $(CROSS_CC) $(CROSS_CCOPTS) $(CROSS_CC_SEG1) $<
+
+$(AOBJS): %$(BINEXT): %.s
+ $(CROSS_AS) $(ASOPTS) $< -o $*$(BINEXT)
+
+clean:
+ rm -f $(OBJS) $(JUNK) core *~
+
+image:
+ $(CROSS_LD) -o ../fuzix.bin --mapfile ../fuzix.map -C ld65.cfg crt0.o commonmem.o \
+ v65.o ../start.o ../version.o ../lowlevel-6502.o \
+ tricks.o main.o ../timer.o ../kdata.o devhd.o devices.o \
+ ../devio.o ../filesys.o ../process.o ../inode.o ../syscall_fs.o \
+ ../syscall_proc.o ../syscall_other.o ../mm.o ../bankfixed.o \
+ ../tty.o ../devsys.o ../syscall_fs2.o ../syscall_fs3.o ../syscall_exec16.o \
+ ../usermem.o ../usermem_std-6502.o devtty.o
+ dd if=../fuzix.bin of=fuzix.img bs=512 skip=1
--- /dev/null
+You need my cc65 git tree, released cc65 can't compile Fuzix as it lacks a
+compiler bug fix I contributed.
+
+Our memory mapping looks like this
+
+ 0x0000 ZP
+ 0x0100 6502 Stack (per proc)
+ 0x0200 C stack (per proc)
+ 0x0400 I stack (per proc)
+ 0x0500 Udata actual data per proc
+ 0x0600+ Common copy and compiler runtime
+
+ 0x2000 Kernel data (8K)
+ 0x4000 Kernel code (48K)
+ (currently the top the image holds discard, data
+ and common copies for initialisation. That needs
+ tidying up)
+
+And in user space
+
+ 0x2000+ User process (with vectors at top)
+
+This ensures we can do all our stack flips in one operation when we switch
+process in switchin.
+
+Things To Do
+
+Debug signals
+
+Checking on the 6502 stack. Probably we should just check for overflows and
+kill, or perhaps copy stacks in/out IFF it would otherwise run out (as
+Apple ProDOS seems to)
+
+Lots of memory to save in kernel space by making the common and data copies
+come from a bank we then switch out, along perhaps with the const data from
+what would be discard areas on the Z80.
+
+
+To build:
+Set the platform/target
+make clean
+make
+
+and you'll get an image file to dd onto the last 64K of your disk image for
+the emulator.
+
+TODO
+----
+- Signal handling paths
+- Fix brk() checking
+- Interrupts
+
+Optimisations We Need To Do
+--------------------------------------------------------------
+- Only use low bank numbers for low 8K until we have a cache
+- Only copy the needed memory when forking, not 48K (in theory we are copying
+ low->brk, sp->top, S->top of page, and Z)
+- usermem functions that use banking tricks
+- map_save/restore copy/restore entries for kernel mode so we can take an
+ interrupt when we are pulling banking tricks
--- /dev/null
+;
+; We keep our common area right down low, with the ZP and stack
+;
+;
+ ; exported symbols
+ .export _ub
+ .export _udata
+ .export kstack_top
+ .export istack_top
+ .export istack_switched_sp
+ .export CTemp
+
+ .segment "COMMONDATA"
+ .include "zeropage.inc"
+
+;
+; In 6502 land these are the C stacks, we will need to handle the
+; hardware stack separately, and also to save sp,sp+1 etc on irqs
+;
+; Declared as BSS so no non zero bytes here please
+;
+_ub: ; first 512 bytes: starts with struct u_block, with the kernel stack working down from above
+_udata:
+kstack_base:
+ .res 512,0
+kstack_top:
+
+ ; next 256 bytes: 253 byte interrupt stack, then 3 byte saved stack pointer
+istack_base:
+ .res 254,0
+istack_top:
+istack_switched_sp: .word 0
+;
+; Finally we tack the ZP save area for interrupts on the end
+;
+; Swap space for the the C temporaries (FIXME - we stash sp twice right now)
+;
+CTemp:
+ .res 2 ; sp
+ .res 2 ; sreg
+ .res (zpsavespace-4) ; Other stuff
--- /dev/null
+/* Enable to make ^Z dump the inode table for debug */
+#undef CONFIG_IDUMP
+/* Enable to make ^A drop back into the monitor */
+#undef CONFIG_MONITOR
+/* Profil syscall support (not yet complete) */
+#undef CONFIG_PROFIL
+/* Acct syscall support */
+#undef CONFIG_ACCT
+/* Multiple processes in memory at once */
+#define CONFIG_MULTI
+/* Use fixed banks for now. It's simplest and we've got so much memory ! */
+#define CONFIG_BANKS 1
+
+#define CONFIG_CALL_R2L /* Runtime stacks arguments backwards */
+
+/*
+ * As we have 1MB of RAM we simply allocate it into 64K per process
+ * for 15 processes plus kernel. A big server would no doubt want to
+ * use 16K banks and swapping 8)
+ */
+#define CONFIG_BANK_FIXED
+#define MAX_MAPS 15
+#define MAP_SIZE 0xE000 /* The low 8K is taken up with common space ZP and
+ S, while we don't currently use the top 8K
+ (see tricks.s and fix up the fork copy code) */
+
+#define TICKSPERSEC 10 /* Ticks per second */
+#define MAPBASE 0x0000 /* We map from 0 */
+#define PROGBASE 0x2000 /* also data base */
+#define PROGLOAD 0x2000
+#define PROGTOP 0xE000 /* Top of program. If we fixed a few things we
+ could go to FE00 */
+
+#define BOOT_TTY 513 /* Set this to default device for stdio, stderr */
+
+/* We need a tidier way to do this from the loader */
+#define CMDLINE NULL /* Location of root dev name */
+
+/* Device parameters */
+#define NUM_DEV_TTY 1
+#define TTYDEV BOOT_TTY /* Device used by kernel for messages, panics */
+#define NBUFS 8 /* Number of block buffers */
+#define NMOUNTS 2 /* Number of mounts at a time */
+
+#define platform_discard()
--- /dev/null
+ ; imported symbols
+ .import init_early
+ .import init_hardware
+ .import _fuzix_main
+ .import kstack_top
+ .import vector
+ .import nmi_handler
+
+ .import __BSS_RUN__, __BSS_SIZE__
+ .importzp ptr1, ptr2, tmp1
+
+ ; startup code @0
+ .include "zeropage.inc"
+
+;
+; So we end up first in the image
+;
+ .segment "START"
+ .byte 65
+ .byte 02
+
+entry:
+;
+; We are entered at $2002 just after the required magic number
+;
+ lda #'F'
+ sta $FE20 ; signal our arrival
+
+ sei ; interrupts off
+ cld ; decimal off
+ ldx #$FF
+ txs ; Stack (6502 not C)
+
+ lda #'u'
+ sta $FE20
+
+ lda #<kstack_top ; C stack
+ sta sp
+ lda #>kstack_top
+ sta sp+1
+
+ lda #<__BSS_RUN__
+ sta ptr1
+ lda #>__BSS_RUN__
+ sta ptr1+1
+
+ lda #'z'
+ sta $FE20
+
+ lda #0
+ tay
+ ldx #>__BSS_SIZE__
+ beq bss_wipe_tail
+bss_wiper_1: sta (ptr1),y
+ iny
+ bne bss_wiper_1
+ inc ptr1+1
+ dex
+ bne bss_wiper_1
+
+bss_wipe_tail:
+ cpy #<__BSS_SIZE__
+ beq gogogo
+ sta (ptr1),y
+ iny
+ bne bss_wipe_tail
+
+gogogo:
+ lda #'i'
+ sta $FE20
+
+ lda #'x'
+ sta $FE20
+
+ jsr init_early
+ lda #'.'
+ sta $FE20
+ jsr init_hardware
+ lda #13
+ sta $FE20
+ lda #10
+ sta $FE20
+ jsr _fuzix_main ; Should never return
+ sei ; Spin
+stop: jmp stop
+
+ .segment "VECTORS"
+ .addr vector
+ .addr $2002 ; does it matter ???
+ .addr nmi_handler
--- /dev/null
+/*
+ * ROMdisc hack for testing
+ */
+
+#include <kernel.h>
+#include <kdata.h>
+#include <printf.h>
+#include <devhd.h>
+
+extern uint8_t hd_map;
+
+extern void hd_read_data(uint16_t addr);
+extern void hd_write_data(uint16_t addr);
+
+volatile uint8_t *disknum = (volatile uint8_t *)0xFE30;
+volatile uint8_t *diskcylh = (volatile uint8_t *)0xFE31;
+volatile uint8_t *diskcyll = (volatile uint8_t *)0xFE32;
+volatile uint8_t *diskcmd = (volatile uint8_t *)0xFE33;
+volatile uint8_t *diskstat = (volatile uint8_t *)0xFE35;
+
+static int hd_transfer(uint8_t minor, bool is_read, uint8_t rawflag)
+{
+ uint16_t dptr, nb;
+ irqflags_t irq;
+ uint8_t err;
+
+ if(rawflag == 1 && d_blkoff(9))
+ return -1;
+
+ hd_map = rawflag;
+
+ dptr = (uint16_t)udata.u_dptr;
+ nb = udata.u_nblock;
+
+ while (udata.u_nblock--) {
+ *disknum = minor;
+ *diskcylh = udata.u_block >> 8;
+ *diskcyll = udata.u_block;
+ *diskcmd = 1;
+ if ((err = *diskstat) != 0) {
+ kprintf("hd%d: disk error %x\n", err);
+ udata.u_error = EIO;
+ return -1;
+ }
+
+ irq = di();
+ if (is_read)
+ hd_read_data(dptr);
+ else
+ hd_write_data(dptr);
+ irqrestore(irq);
+ udata.u_block++;
+ dptr += 512;
+ }
+ return nb;
+}
+
+int hd_open(uint8_t minor, uint16_t flag)
+{
+ used(flag);
+ if(minor != 0) {
+ udata.u_error = ENODEV;
+ return -1;
+ }
+ return 0;
+}
+
+int hd_read(uint8_t minor, uint8_t rawflag, uint8_t flag)
+{
+ used(flag);
+ return hd_transfer(minor, true, rawflag);
+}
+
+int hd_write(uint8_t minor, uint8_t rawflag, uint8_t flag)
+{
+ used(flag);
+ return hd_transfer(minor, false, rawflag);
+}
+
--- /dev/null
+#ifndef __DEVHD_DOT_H__
+#define __DEVHD_DOT_H__
+
+/* public interface */
+int hd_read(uint8_t minor, uint8_t rawflag, uint8_t flag);
+int hd_write(uint8_t minor, uint8_t rawflag, uint8_t flag);
+int hd_open(uint8_t minor, uint16_t flag);
+
+#endif /* __DEVHD_DOT_H__ */
--- /dev/null
+#ifndef __DEVICE_DOT_H__
+#define __DEVICE_DOT_H__
+
+extern void mod_control(uint8_t set, uint8_t clr);
+
+#endif /* __DEVICE_DOT_H__ */
--- /dev/null
+#include <kernel.h>
+#include <version.h>
+#include <kdata.h>
+#include <devhd.h>
+#include <devsys.h>
+#include <tty.h>
+#include <devtty.h>
+
+struct devsw dev_tab[] = /* The device driver switch table */
+{
+// minor open close read write ioctl
+// -----------------------------------------------------------------
+ /* 0: /dev/fd Floppy disc block devices */
+ { hd_open, no_close, hd_read, hd_write, no_ioctl },
+ /* 1: /dev/hd Hard disc block devices (absent) */
+ { nxio_open, no_close, no_rdwr, no_rdwr, no_ioctl },
+ /* 2: /dev/tty TTY devices */
+ { tty_open, tty_close, tty_read, tty_write, tty_ioctl },
+ /* 3: /dev/lpr Printer devices */
+ { no_open, no_close, no_rdwr, no_rdwr, no_ioctl },
+ /* 4: /dev/mem etc System devices (one offs) */
+ { no_open, no_close, sys_read, sys_write, sys_ioctl },
+ /* Pack to 7 with nxio if adding private devices and start at 8 */
+};
+
+bool validdev(uint16_t dev)
+{
+ /* This is a bit uglier than needed but the right hand side is
+ a constant this way */
+ if(dev > ((sizeof(dev_tab)/sizeof(struct devsw)) << 8) + 255)
+ return false;
+ else
+ return true;
+}
+
+void device_init(void)
+{
+}
+
--- /dev/null
+#include <kernel.h>
+#include <kdata.h>
+#include <printf.h>
+#include <stdbool.h>
+#include <devtty.h>
+#include <device.h>
+#include <vt.h>
+#include <tty.h>
+
+static volatile uint8_t *uart = (volatile uint8_t *)0xFE20;
+static volatile uint8_t *timer = (volatile uint8_t *)0xFE10;
+
+static char tbuf1[TTYSIZ];
+PTY_BUFFERS;
+
+struct s_queue ttyinq[NUM_DEV_TTY + 1] = { /* ttyinq[0] is never used */
+ {NULL, NULL, NULL, 0, 0, 0},
+ {tbuf1, tbuf1, tbuf1, TTYSIZ, 0, TTYSIZ / 2},
+ PTY_QUEUES
+};
+
+/* tty1 is the screen tty2 is the serial port */
+
+/* Output for the system console (kprintf etc) */
+void kputchar(uint8_t c)
+{
+ if (c == '\n')
+ tty_putc(1, '\r');
+ tty_putc(1, c);
+}
+
+ttyready_t tty_writeready(uint8_t minor)
+{
+ return TTY_READY_NOW;
+}
+
+void tty_putc(uint8_t minor, unsigned char c)
+{
+ minor;
+ uart[0] = c;
+}
+
+void tty_setup(uint8_t minor)
+{
+ minor;
+}
+
+void tty_sleeping(uint8_t minor)
+{
+ minor;
+}
+
+/* For the moment */
+int tty_carrier(uint8_t minor)
+{
+ minor;
+ return 1;
+}
+
+void tty_poll(void)
+{
+ uint8_t x;
+
+ x = uart[1] & 1;
+ if (x) {
+ x = uart[0];
+ tty_inproc(1, x);
+ }
+}
+
+void platform_interrupt(void)
+{
+ uint8_t t = *timer;
+ tty_poll();
+ while(t--)
+ timer_interrupt();
+}
--- /dev/null
+#ifndef __DEVTTY_DOT_H__
+#define __DEVTTY_DOT_H__
+
+extern void tty_poll(void);
+
+#endif
--- /dev/null
+; UZI mnemonics for memory addresses etc
+
+; (this is struct u_data from kernel.h)
+U_DATA .set $0200
+; 256+256+256 bytes.
+U_DATA__TOTALSIZE .set $300
+
+PROGLOAD .set $2000
+ZPBASE .set $0
--- /dev/null
+MEMORY {
+ RAMZ: start = $0000, size = $0100, type = rw, fill = yes;
+ STACK: start = $0100, size = $0100, type = rw, fill = yes;
+ RAM0: start = $0200, size = $1E00, type = rw, fill = yes;
+ RAM1: start = $2000, size = $DE00, type = rw, fill = yes;
+ RAM2: start = $FFFA, size = $0006, type = rw, fill = yes;
+}
+
+SEGMENTS {
+ ZEROPAGE: load = RAMZ, type = zp, define = yes;
+ COMMONDATA: load = RAM0, type = bss;
+ COMMONMEM: load = RAM0, type = rw;
+ CODE: load = RAM0, type = ro, define = yes;
+ RODATA: load = RAM0, type = ro;
+ STUBS: load = RAM0, type = ro, define = yes;
+
+ START: load = RAM1, type = ro;
+
+ DATA: load = RAM1, type = rw, define = yes;
+ BSS: load = RAM1, type = bss, define = yes;
+
+ SEG1: load = RAM1, type = ro;
+ SEG2: load = RAM1, type = ro;
+ SEG3: load = RAM1, type = ro;
+ SYS1: load = RAM1, type = ro;
+ SYS2: load = RAM1, type = ro;
+ SYS3: load = RAM1, type = ro;
+ SYS4: load = RAM1, type = ro;
+ SYS5: load = RAM1, type = ro;
+ DISCARD: load = RAM1, type = ro;
+ DISCARDDATA: load = RAM1, type = ro;
+
+ VECTORS: load = RAM2, type = ro;
+}
+
+FILES {
+ %O: format = bin;
+}
--- /dev/null
+#include <kernel.h>
+#include <timer.h>
+#include <kdata.h>
+#include <printf.h>
+#include <devtty.h>
+
+uint8_t kernel_flag = 1;
+
+void platform_idle(void)
+{
+ irqflags_t flags = di();
+ tty_poll();
+ irqrestore(flags);
+}
+
+void do_beep(void)
+{
+}
+
+/*
+ * Map handling: We have flexible paging. Each map table consists of a set of pages
+ * with the last page repeated to fill any holes.
+ */
+
+void pagemap_init(void)
+{
+ int i;
+ /* Bank 0 is the kernel */
+ for (i = 15 ; i > 0; i--)
+ pagemap_add(i * 8);
+}
+
+void map_init(void)
+{
+}
+
+uint8_t platform_param(unsigned char *p)
+{
+ return 0;
+}
--- /dev/null
+export CPU = 6502
--- /dev/null
+;
+; 6502 version
+;
+ .export _switchout
+ .export _switchin
+ .export _dofork
+ .export _ramtop
+ .export _create_init_common
+
+ .import _chksigs
+ .import _trap_monitor
+
+ .import map_kernel
+
+ .import _newproc
+ .import _getproc
+ .import _runticks
+ .import _inint
+ .import outstring
+ .import outxa
+ .import outcharhex
+
+ .include "kernel.def"
+ .include "../kernel02.def"
+ .include "zeropage.inc"
+
+ .segment "COMMONMEM"
+
+; ramtop must be in common for single process swapping cases
+; and its a constant for the others from before init forks so it'll be fine
+; here
+_ramtop:
+ .word $E000
+
+; 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().
+;
+; FIXME: make sure we optimise the switch to self case higher up the stack!
+;
+; This function can have no arguments or auto variables.
+_switchout:
+ sei
+
+ jsr _chksigs
+;
+; Put the C stack on the CPU stack, and store that in U_SP
+;
+ lda #0 ; return code
+ pha
+ pha
+ lda sp ; C stack
+ pha
+ lda sp+1
+ pha
+ tsx
+ stx U_DATA__U_SP ; Save it
+
+ ; set inint to false
+ lda #0
+ sta _inint
+
+ ; find another process to run (may select this one again) returns it
+ ; in x,a
+ jsr _getproc
+ jsr _switchin
+ ; we should never get here
+ jsr _trap_monitor
+
+badswitchmsg: .byte "_switchin: FAIL"
+ .byte 13, 10, 0
+
+;
+; On entry x,a holds the process to switch in
+;
+_switchin:
+ sei
+ sta ptr1
+ stx ptr1+1
+ ; Take a second saved set as we are going to swap stacks and ZP
+ ; with a CPU that hasn't got sufficient registers to keep it on
+ ; CPU
+ sta switch_proc_ptr
+ stx switch_proc_ptr+1
+; jsr outxa
+ ldy #P_TAB__P_PAGE_OFFSET
+ lda (ptr1),y
+; pha
+; jsr outcharhex
+; pla
+ sta $FE00 ; switches zero page, stack memory area
+ ; ------- New stack and ZP -------
+
+ ; Set ptr1 back up (the old ptr1 was on the other ZP)
+ lda switch_proc_ptr
+ sta ptr1
+ lda switch_proc_ptr+1
+ sta ptr1+1
+
+ ; check u_data->u_ptab matches what we wanted
+ lda U_DATA__U_PTAB
+ cmp ptr1
+ bne switchinfail
+ lda U_DATA__U_PTAB+1
+ cmp ptr1+1
+ bne switchinfail
+
+ lda #P_RUNNING
+ ldy #P_TAB__P_STATUS_OFFSET
+ sta (ptr1),y
+
+ lda #0
+ sta _runticks
+ sta _runticks+1
+
+ ; restore machine state -- note we may be returning from either
+ ; _switchout or _dofork
+ ldx U_DATA__U_SP
+ txs
+ pla
+ sta sp+1
+ pla
+ sta sp
+ lda #'*'
+ sta $FE20
+ lda _inint
+ beq swtchdone ; in ISR, leave interrupts off
+ cli
+swtchdone:
+ pla ; Return code
+ tax
+ pla
+ rts
+
+switchinfail:
+ lda #'!'
+ sta $FE20
+ lda ptr1+1
+ jsr outcharhex
+ lda ptr1
+ jsr outcharhex
+ lda #<badswitchmsg
+ ldx #>badswitchmsg
+ jsr outstring
+ ; something went wrong and we didn't switch in what we asked for
+ jmp _trap_monitor
+
+; Must not put this in ZP ?
+;
+; Move to commondata ??
+;
+fork_proc_ptr: .word 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
+ sei ; should already be the case ... belt and braces.
+
+ ; new process in X, get parent pid into y
+
+ sta fork_proc_ptr
+ stx fork_proc_ptr+1
+
+ ldy #P_TAB__P_PID_OFFSET
+ sta ptr1
+ stx ptr1+1
+
+ ; Save the stack pointer and critical registers.
+ ; 6502 at least doesn't have too many of those 8)
+
+ ; When this process (the parent) is switched back in, it will be as if
+ ; it returns with the value of the child's pid.
+ lda (ptr1),y
+ pha
+ iny
+ lda (ptr1),y
+ pha
+
+ ; 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) the child PID.
+ lda sp
+ pha
+ lda sp+1
+ pha
+ tsx
+ stx U_DATA__U_SP
+
+ ; now we're in a safe state for _switchin to return in the parent
+ ; process.
+
+ ;
+ ; Assumes ptr1 still holds the new process ptr
+ ;
+
+ jsr fork_copy
+
+ ; --------- we switch stack copies here -----------
+ lda fork_proc_ptr
+ ldx fork_proc_ptr+1
+ sta ptr1
+ stx ptr1+1
+ ldy #P_TAB__P_PAGE_OFFSET
+ lda (ptr1),y
+ sta $FE00 ; switch to child and child stack
+ ; and zero page etc
+ ; We are now in the kernel child context
+
+ ; now the copy operation is complete we can get rid of the stuff
+ ; _switchin will be expecting from our copy of the stack.
+ pla
+ pla
+ pla
+ pla
+
+ lda fork_proc_ptr
+ ldx fork_proc_ptr+1
+
+ jsr _newproc
+
+ ; any calls to map process will now map the childs memory
+
+ ; runticks = 0;
+ lda #0
+ sta _runticks
+ sta _runticks+1
+
+ ; in the child process, fork() returns zero.
+ tax
+
+ ; 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().
+ rts
+
+;
+; On entry ptr1 points to the process table of the child, and
+; the U_DATA is still not fully modified so holds the parents bank
+; number. This wants optimising to avoid copying all the unused
+; space!
+;
+fork_copy:
+ ldy #P_TAB__P_PAGE_OFFSET
+ lda (ptr1),y ; child->p_pag[0]
+ sta $FE03 ; 6000-7FFF
+ lda U_DATA__U_PAGE
+ sta $FE02 ; 4000-5FFF
+
+ ; Now use that window to copy 56K from 0000-DFFF
+
+ jsr bank2bank ; copies 8K
+ ; This assumes our MMU is r/w. We'd need shadows otherwise
+ inc $FE02 ; next 8K
+ inc $FE03
+ jsr bank2bank ; copies 8K
+ inc $FE02 ; next 8K
+ inc $FE03
+ jsr bank2bank ; copies 8K
+ inc $FE02 ; next 8K
+ inc $FE03
+ jsr bank2bank ; copies 8K
+ inc $FE02 ; next 8K
+ inc $FE03
+ jsr bank2bank ; copies 8K
+ inc $FE02 ; next 8K
+ inc $FE03
+ jsr bank2bank ; copies 8K
+ inc $FE02 ; next 8K
+ inc $FE03
+ jsr bank2bank ; copies 8K
+ jmp map_kernel ; put the kernel mapping back as it should be
+
+bank2bank: ; copy 4K between the blocks mapped
+ ; at 0x4000 and 0x6000
+ lda #$40
+ sta ptr3+1
+ lda #$60
+ sta ptr4+1
+ lda #0
+ sta ptr3
+ sta ptr4
+ tay
+ ldx #$20 ; 32 x 256 bytes = 8K
+copy1:
+ lda (ptr3),y
+ sta (ptr4),y
+ iny
+ bne copy1
+ inc ptr3+1
+ inc ptr4+1
+ dex
+ bne copy1
+ rts
+
+_create_init_common:
+ lda #$00
+ sta $FE02
+ lda #$08
+ sta $FE03
+ jsr bank2bank
+ jmp map_kernel
+;
+; The switch proc pointer cannot live anywhere in common as we switch
+; common on process switch
+;
+ .data
+
+switch_proc_ptr: .word 0
--- /dev/null
+;
+; v65 platform functions
+;
+
+ .export init_early
+ .export init_hardware
+ .export _program_vectors
+ .export map_kernel
+ .export map_process
+ .export map_process_always
+ .export map_save
+ .export map_restore
+
+ .export _unix_syscall_i
+ .export _platform_interrupt_i
+ .export platform_doexec
+
+ ; exported debugging tools
+ .export _trap_monitor
+ .export _trap_reboot
+ .export outchar
+ .export ___hard_di
+ .export ___hard_ei
+ .export ___hard_irqrestore
+ .export vector
+
+ .import interrupt_handler
+ .import _ramsize
+ .import _procmem
+ .import nmi_handler
+ .import unix_syscall_entry
+ .import kstack_top
+ .import istack_switched_sp
+ .import istack_top
+ .import _unix_syscall
+ .import _platform_interrupt
+ .import _kernel_flag
+ .import stash_zp
+ .import pushax
+
+ .import outcharhex
+ .import outxa
+ .import incaxy
+
+ .import _create_init_common
+
+ .include "kernel.def"
+ .include "../kernel02.def"
+ .include "zeropage.inc"
+
+;
+; syscall is jsr [$00fe]
+;
+syscall = $FE
+; -----------------------------------------------------------------------------
+; COMMON MEMORY BANK (0x0200 upwards after the common data blocks)
+; -----------------------------------------------------------------------------
+ .segment "COMMONMEM"
+
+_trap_monitor:
+ jmp _trap_monitor
+
+_trap_reboot:
+ lda #$A5
+ sta $FE40 ; Off
+ jmp _trap_reboot ; FIXME: original ROM map and jmp
+
+___hard_di:
+ php
+ sei ; Save old state in return to C
+ pla ; Old status
+ rts
+___hard_ei:
+ cli ; on 6502 cli enables IRQs!!!
+ rts
+
+___hard_irqrestore:
+ and #4 ; IRQ flag
+ beq irq_on
+ cli
+ rts
+irq_on:
+ sei
+ rts
+
+; -----------------------------------------------------------------------------
+; KERNEL MEMORY BANK (only accessible when the kernel is mapped)
+; -----------------------------------------------------------------------------
+ .code
+
+init_early:
+ ; copy the low 8K from bank 0 to bank 8 so we can keep the
+ ; simple model of 8-15 process 16-24 process ....
+ ; bank 0 is effectively spare at this point and can be
+ ; reused.
+ jsr _create_init_common
+ lda #$08
+ sta $FE00
+ rts
+
+init_hardware:
+ ; set system RAM size for test purposes
+ lda #0
+ sta _ramsize
+ lda #4
+ sta _ramsize+1
+ lda #192
+ sta _procmem
+ lda #3
+ sta _procmem+1
+ jmp program_vectors_k
+
+;------------------------------------------------------------------------------
+; COMMON MEMORY PROCEDURES FOLLOW
+
+ .segment "COMMONMEM"
+
+_program_vectors:
+ ; we are called, with interrupts disabled, by both newproc() and crt0
+ ; will exit with interrupts off
+ sei
+ ;
+ ; 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 $FFFE
+ lda #>vector
+ 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 bits that could simply "lose" the syscall!
+ lda #<syscall_entry
+ sta syscall
+ lda #>syscall_entry
+ sta syscall+1
+ jmp map_kernel
+
+;
+; On a fully switching system with far copies like the 6509 this is
+; all basically a no-op
+;
+; On a banked setup the semantics are:
+;
+; map_process_always()
+; Map the current process (ie the one with the live uarea)
+;
+; map_kernel()
+; Map the kernel
+;
+; map_process(pageptr [in X,A])
+; if pageptr = 0 then map_kernel
+; else map_process using the pageptr
+;
+; map_save
+; save the current mapping
+;
+; map_restore
+; restore the saved mapping
+;
+; save/restore are used so that the kernel can play with its internal
+; banking/mappings without having to leave interrupts off all the time
+;
+; ptr1 and tmp1 may be destroyed by these methods, but no other
+; temporaries.
+;
+map_process_always:
+ pha
+ txa
+ pha
+ ldx U_DATA__U_PAGE
+ jsr map_bank_i
+ pla
+ tax
+ pla
+ rts
+;
+; X,A points to the map table of this process
+;
+map_process:
+ cmp #0
+ bne map_process_2
+ cpx #0
+ bne map_process_2
+;
+; Map in the kernel below the current common, all registers preserved
+; the kernel lives in 40/42/43/.... (41 has the reset vectors in so
+; we avoid it. Later we'll be clever and stuff _DISCARD and the copy
+; blocks there or something (that would also let us put RODATA in
+; common area just to balance out memory usages).
+;
+map_kernel:
+ pha
+ txa
+ pha
+ ; Common is left untouched as is ZP and S
+ ldx #$01 ; Kernel RAM at 0x2000-0x3FFF
+ jsr map_bank
+ pla
+ tax
+ pla
+ rts
+
+;
+; Entry point to map a linear bank range
+;
+map_bank_i: ; We are not mapping the first user page yet
+ inx
+map_bank:
+ stx $FE01
+ inx
+ stx $FE02
+ inx
+ stx $FE03
+ inx
+ stx $FE04
+ inx
+ stx $FE05
+ inx
+ stx $FE06
+; inx ; We don't use bank 7 copies yet
+; stx $FE07
+ rts
+
+; X,A holds the map table of this process
+map_process_2:
+ sta ptr1
+ stx ptr1+1
+ tya
+ pha
+ ldy #0
+ lda (ptr1),y ; 4 bytes if needed
+ tax
+ pla
+ tay
+ jmp map_bank_i
+
+
+;
+; Restore mapping. This may not be sufficient. We may need to do a
+; careful 6 byte save/restore if we do clever stuff in future
+;
+map_restore:
+ pha
+ txa
+ pha
+ ldx saved_map ; First bank we skip half of
+ jsr map_bank
+ pla
+ tax
+ pla
+ rts
+
+;
+; Save the current mapping.
+; May not be sufficient if we want IRQs on while doing page tricks
+;
+map_save:
+ pha
+ lda $FE01
+ sta saved_map
+ pla
+ rts
+
+saved_map: .byte 0, 0, 0, 0
+
+; outchar: Wait for UART TX idle, then print the char in a without
+; corrupting other registers
+
+outchar:
+ sta $FE20
+ 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
+;
+vector:
+ pha
+ txa
+ pha
+ tya
+ pha
+ cld
+;
+; Q: do we want to spot brk() instructions and signal them ?
+;
+;
+; Save the old stack ptr
+;
+ jsr stash_zp ; Save zero page bits
+ tsx ; and save the 6502 stack ptr
+ stx istack_switched_sp ; in uarea/stacks
+;
+; Hope the user hasn't used all the CPU stack
+;
+; FIXME: we should check here if S is too low and if so set it high
+; and deliver SIGKILL
+;
+; Configure the C stack to the i stack
+;
+ lda #<istack_top
+ sta sp
+ lda #>istack_top
+ sta sp+1
+ jsr interrupt_handler
+;
+; Reload the previous value into the stack ptr
+;
+ ldx istack_switched_sp
+ txs ; recover 6502 stack
+ jsr stash_zp ; restore zero page bits
+
+;
+; TODO: pre-emption
+;
+
+
+
+;
+; Signal handling on 6502 is foul as the C stack may be inconsistent
+; during an IRQ. We push a new complete rti frame below the official
+; one, along with a vector and the signal number. The glue in the
+; app is expected to switch to a signal stack or similar, pop the
+; values, invoke the signal handler and then return.
+;
+; FIXME: at the moment the irqout path will not check for multiple
+; signals so the next one gets delivered next irq.
+;
+;
+ lda U_DATA__U_CURSIG
+ beq irqout
+ tay
+ tsx
+ txa
+ sec
+ sbc #6 ; move down past the existing rti
+ tax
+ txs
+ lda #>irqout
+ pha
+ lda #<irqout
+ pha ; stack a return vector
+ tya
+ pha ; stack signal number
+ ldx #0
+ stx U_DATA__U_CURSIG
+ asl a
+ tay
+ lda U_DATA__U_SIGVEC,y ; Our vector (low)
+ pha ; stack half of vector
+ lda U_DATA__U_SIGVEC+1,y ; High half
+ pha ; stack rest of vector
+ txa
+ sta U_DATA__U_SIGVEC,y ; Wipe the vector
+ sta U_DATA__U_SIGVEC+1,y
+ lda #<PROGLOAD + 20
+ pha
+ lda #>PROGLOAD + 20
+ lda #0
+ pha ; dummy flags, with irq enable
+ rti ; return on the fake frame
+ ; if the handler returns
+ ; rather than doing a longjmp
+ ; we'll end up at irqout and pop the
+ ; real frame
+irqout:
+ pla
+ tay
+ pla
+ tax
+ pla
+ rti
+;
+; sp/sp+1 are the C stack of the userspace
+; with the syscall number in X
+; Y indicates the number of bytes of argument
+;
+syscall_entry:
+ php
+ sei
+ cld
+
+ stx U_DATA__U_CALLNO
+
+ ; No arguments - skip all the copying and stack bits
+ cpy #0
+ beq noargs
+
+ ; Remove the arguments. This is fine as by the time we go back
+ ; to the user stack we'll have finished with them
+ lda sp
+ sta ptr1
+ ldx sp+1
+ stx ptr1+1
+ jsr incaxy
+ sta sp
+ stx sp+1
+
+ ;
+ ; We copy the arguments but need to deal with the compiler
+ ; stacking in the reverse order. At this point ptr1 points
+ ; to the last byte of the arguments (first argument). We go
+ ; down the stack copying words up the argument list.
+ ;
+ ldx #0
+copy_args:
+ dey
+ lda (ptr1),y ; copy the arguments over
+ sta U_DATA__U_ARGN+1,x
+ dey
+ lda (ptr1),y
+ sta U_DATA__U_ARGN,x
+ inx
+ inx
+ cpy #0
+ bne copy_args
+noargs:
+ ;
+ ; Now we need to stack switch. Save the adjusted stack we want
+ ; for return
+ ;
+ lda sp
+ pha
+ lda sp+1
+ pha
+ tsx
+ stx U_DATA__U_SYSCALL_SP
+;
+; We save a copy of the high byte of sp here as we may need it to get
+; the brk() syscall right.
+;
+ sta U_DATA__U_SYSCALL_SP + 1
+;
+;
+; FIXME: we should check here if there is enough 6502 stack left
+; and if so either copy and switch stacks or kill the process
+;
+; 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_SYSCALL_SP
+ txs
+;
+; From that recover the C stack and the syscall buf ptr
+;
+ pla
+ sta sp+1
+ pla
+ sta sp
+ lda U_DATA__U_CURSIG
+ beq syscout
+ tay
+
+ tsx ; Move past existing return stack
+ dex
+ dex
+ dex
+ txs
+
+ ;
+ ; The signal handler might make syscalls so we need to get
+ ; our return saved and return the right value!
+ ;
+ lda U_DATA__U_ERROR
+ pha
+ lda U_DATA__U_RETVAL
+ pha
+ lda U_DATA__U_RETVAL+1
+ pha
+ lda #>sigret ; Return address
+ pha
+ lda #<sigret
+ pha
+
+ tya
+ pha ; signal
+ ldx #0
+ stx U_DATA__U_CURSIG
+ asl a
+ tay
+ lda U_DATA__U_SIGVEC,y ; Our vector
+ pha
+ lda U_DATA__U_SIGVEC+1,y
+ pha
+ txa
+ sta U_DATA__U_SIGVEC,y ; Wipe the vector
+ sta U_DATA__U_SIGVEC+1,y
+
+ ; Invoke the helper with signal and vector stacked
+ ; it will then return to syscout and recover the original
+ ; frame. If the handler made syscalls then
+ jmp (PROGLOAD + 20)
+
+ ;
+ ; FIXME: should loop for more signals if appropriate
+ ;
+sigret:
+ pla ; Unstack the syscall return pieces
+ tax
+ pla
+ tay
+ pla
+ plp ; from original stack frame
+ rts
+
+syscout:
+; We may be in decimal mode beyond this line.. take care
+;
+ plp
+
+; Copy the return data over
+;
+ ldy U_DATA__U_RETVAL
+ ldx U_DATA__U_RETVAL+1
+; Also sets Z for us
+ lda U_DATA__U_ERROR
+
+ rts
+
+platform_doexec:
+;
+; Start address of executable
+;
+ stx ptr1+1
+ sta ptr1
+
+;
+; Set up the C stack
+;
+ lda U_DATA__U_ISP
+ sta sp
+ ldx U_DATA__U_ISP+1
+ stx sp+1
+
+;
+; Set up the 6502 stack
+;
+ ldx #$ff
+ txs
+ ldx #>PROGLOAD ; For the relocation engine
+ lda #ZPBASE
+ jmp (ptr1) ; Enter user application
+
+;
+; Straight jumps no funny banking issues
+;
+_unix_syscall_i:
+ jmp _unix_syscall
+_platform_interrupt_i:
+ jmp _platform_interrupt
+
+
+;
+; Disk copier (needs to be in common), call with ints off
+; for now
+;
+; AX = ptr, length always 512, src and page in globals
+;
+; Uses ptr3/4 as 1/2 are reserved for the mappers
+;
+
+ .export _hd_read_data,_hd_write_data,_hd_map
+
+_hd_read_data:
+ sta ptr3
+ stx ptr3+1 ; Save the target
+
+ ;
+ ; We must flip banks before we play mmu pokery, or it will
+ ; undo all our work. This means our variables must be commondata
+ ;
+ lda _hd_map
+ beq hd_kmap
+ jsr map_process_always
+hd_kmap:
+ ldy #0
+ jsr hd_read256
+ inc ptr3+1
+ jsr hd_read256
+ jsr map_kernel
+ rts
+
+hd_read256:
+ lda $FE34
+ sta (ptr3),y
+ iny
+ bne hd_read256
+ rts
+
+_hd_write_data:
+ sta ptr3
+ stx ptr3+1 ; Save the target
+
+ ;
+ ; We must flip banks before we play mmu pokery, or it will
+ ; undo all our work. This means our variables must be commondata
+ ;
+ lda _hd_map
+ beq hd_kmapw
+ jsr map_process_always
+hd_kmapw:
+ ldy #0
+ jsr hd_write256
+ inc ptr3+1
+ jsr hd_write256
+ jsr map_kernel
+ rts
+
+hd_write256:
+ lda (ptr3),y
+ sta $FE34
+ iny
+ bne hd_write256
+ rts
+
+ .segment "COMMONDATA"
+
+_hd_map:
+ .res 1
--- /dev/null
+;
+; zeropage.inc
+;
+; (C) Copyright 2002-2012, Ullrich von Bassewitz (uz@cc65.org)
+;
+
+; Assembler include file that imports the runtime zero page locations used
+; by the compiler, ready for usage in asm code.
+
+
+ .globalzp sp, sreg, regsave
+ .globalzp ptr1, ptr2, ptr3, ptr4
+ .globalzp tmp1, tmp2, tmp3, tmp4
+ .globalzp regbank
+
+; The size of the register bank
+regbanksize = 6
+
+; The total amount of zero page space used
+zpspace = 26
+
+; The amount of space that needs to be saved by an interrupt handler that
+; calls C code (does not include the register bank, which is saved by the
+; generated C code if required).
+zpsavespace = zpspace - regbanksize
+