From: Alan Cox Date: Sun, 13 Nov 2016 00:04:33 +0000 (+0000) Subject: v65: initial platform code X-Git-Url: https://git.ndcode.org/public/gitweb.cgi?a=commitdiff_plain;h=058f0ad62e46290ea9a23fa1c4b0d74aae13848b;p=FUZIX.git v65: initial platform code This is sufficient to enter user space on the emulator. We don't yet implement pre-emption on the timer interrupt exit path but the rest is basically there. --- diff --git a/Kernel/platform-v65/Makefile b/Kernel/platform-v65/Makefile new file mode 100644 index 00000000..54b9fabb --- /dev/null +++ b/Kernel/platform-v65/Makefile @@ -0,0 +1,33 @@ + +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 diff --git a/Kernel/platform-v65/README b/Kernel/platform-v65/README new file mode 100644 index 00000000..fd901f8a --- /dev/null +++ b/Kernel/platform-v65/README @@ -0,0 +1,60 @@ +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 diff --git a/Kernel/platform-v65/commonmem.s b/Kernel/platform-v65/commonmem.s new file mode 100644 index 00000000..2a64624f --- /dev/null +++ b/Kernel/platform-v65/commonmem.s @@ -0,0 +1,41 @@ +; +; 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 diff --git a/Kernel/platform-v65/config.h b/Kernel/platform-v65/config.h new file mode 100644 index 00000000..13319b4f --- /dev/null +++ b/Kernel/platform-v65/config.h @@ -0,0 +1,45 @@ +/* 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() diff --git a/Kernel/platform-v65/crt0.s b/Kernel/platform-v65/crt0.s new file mode 100644 index 00000000..314eeca1 --- /dev/null +++ b/Kernel/platform-v65/crt0.s @@ -0,0 +1,90 @@ + ; 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 + 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 diff --git a/Kernel/platform-v65/devhd.c b/Kernel/platform-v65/devhd.c new file mode 100644 index 00000000..88979420 --- /dev/null +++ b/Kernel/platform-v65/devhd.c @@ -0,0 +1,79 @@ +/* + * ROMdisc hack for testing + */ + +#include +#include +#include +#include + +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); +} + diff --git a/Kernel/platform-v65/devhd.h b/Kernel/platform-v65/devhd.h new file mode 100644 index 00000000..5b99f00f --- /dev/null +++ b/Kernel/platform-v65/devhd.h @@ -0,0 +1,9 @@ +#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__ */ diff --git a/Kernel/platform-v65/device.h b/Kernel/platform-v65/device.h new file mode 100644 index 00000000..6f4c1e26 --- /dev/null +++ b/Kernel/platform-v65/device.h @@ -0,0 +1,6 @@ +#ifndef __DEVICE_DOT_H__ +#define __DEVICE_DOT_H__ + +extern void mod_control(uint8_t set, uint8_t clr); + +#endif /* __DEVICE_DOT_H__ */ diff --git a/Kernel/platform-v65/devices.c b/Kernel/platform-v65/devices.c new file mode 100644 index 00000000..fe94a0b5 --- /dev/null +++ b/Kernel/platform-v65/devices.c @@ -0,0 +1,39 @@ +#include +#include +#include +#include +#include +#include +#include + +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) +{ +} + diff --git a/Kernel/platform-v65/devtty.c b/Kernel/platform-v65/devtty.c new file mode 100644 index 00000000..9895ea32 --- /dev/null +++ b/Kernel/platform-v65/devtty.c @@ -0,0 +1,77 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +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(); +} diff --git a/Kernel/platform-v65/devtty.h b/Kernel/platform-v65/devtty.h new file mode 100644 index 00000000..da0afba5 --- /dev/null +++ b/Kernel/platform-v65/devtty.h @@ -0,0 +1,6 @@ +#ifndef __DEVTTY_DOT_H__ +#define __DEVTTY_DOT_H__ + +extern void tty_poll(void); + +#endif diff --git a/Kernel/platform-v65/kernel.def b/Kernel/platform-v65/kernel.def new file mode 100644 index 00000000..61bfff4c --- /dev/null +++ b/Kernel/platform-v65/kernel.def @@ -0,0 +1,9 @@ +; 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 diff --git a/Kernel/platform-v65/ld65.cfg b/Kernel/platform-v65/ld65.cfg new file mode 100644 index 00000000..eb221067 --- /dev/null +++ b/Kernel/platform-v65/ld65.cfg @@ -0,0 +1,38 @@ +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; +} diff --git a/Kernel/platform-v65/main.c b/Kernel/platform-v65/main.c new file mode 100644 index 00000000..eff6a4eb --- /dev/null +++ b/Kernel/platform-v65/main.c @@ -0,0 +1,40 @@ +#include +#include +#include +#include +#include + +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; +} diff --git a/Kernel/platform-v65/target.mk b/Kernel/platform-v65/target.mk new file mode 100644 index 00000000..eb53ebcf --- /dev/null +++ b/Kernel/platform-v65/target.mk @@ -0,0 +1 @@ +export CPU = 6502 diff --git a/Kernel/platform-v65/tricks.s b/Kernel/platform-v65/tricks.s new file mode 100644 index 00000000..d55361c2 --- /dev/null +++ b/Kernel/platform-v65/tricks.s @@ -0,0 +1,312 @@ +; +; 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 + 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 diff --git a/Kernel/platform-v65/v65.s b/Kernel/platform-v65/v65.s new file mode 100644 index 00000000..13853a40 --- /dev/null +++ b/Kernel/platform-v65/v65.s @@ -0,0 +1,650 @@ +; +; 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 $FFFF + 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+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+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 #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+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 #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 diff --git a/Kernel/platform-v65/zeropage.inc b/Kernel/platform-v65/zeropage.inc new file mode 100644 index 00000000..1ba03586 --- /dev/null +++ b/Kernel/platform-v65/zeropage.inc @@ -0,0 +1,26 @@ +; +; 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 +