From d1f2e8ebc3944754ecfda4c43aa215ef580fe40f Mon Sep 17 00:00:00 2001 From: Alexander Tsidaev Date: Sun, 9 Nov 2014 14:51:59 +0500 Subject: [PATCH] zx128: Initial ZX Spectrum 128 support --- Kernel/platform-zx128/Makefile | 25 +++ Kernel/platform-zx128/README | 29 +++ Kernel/platform-zx128/commonmem.s | 52 ++++++ Kernel/platform-zx128/config.h | 56 ++++++ Kernel/platform-zx128/crt0.s | 116 ++++++++++++ Kernel/platform-zx128/devices.c | 32 ++++ Kernel/platform-zx128/devtty.c | 61 ++++++ Kernel/platform-zx128/devtty.h | 6 + Kernel/platform-zx128/kernel.def | 6 + Kernel/platform-zx128/main.c | 52 ++++++ Kernel/platform-zx128/tricks.s | 299 ++++++++++++++++++++++++++++++ Kernel/platform-zx128/zx128.s | 172 +++++++++++++++++ Kernel/platform-zx128/zxvideo.s | 264 ++++++++++++++++++++++++++ 13 files changed, 1170 insertions(+) create mode 100644 Kernel/platform-zx128/Makefile create mode 100644 Kernel/platform-zx128/README create mode 100644 Kernel/platform-zx128/commonmem.s create mode 100644 Kernel/platform-zx128/config.h create mode 100644 Kernel/platform-zx128/crt0.s create mode 100644 Kernel/platform-zx128/devices.c create mode 100644 Kernel/platform-zx128/devtty.c create mode 100644 Kernel/platform-zx128/devtty.h create mode 100644 Kernel/platform-zx128/kernel.def create mode 100644 Kernel/platform-zx128/main.c create mode 100644 Kernel/platform-zx128/tricks.s create mode 100644 Kernel/platform-zx128/zx128.s create mode 100644 Kernel/platform-zx128/zxvideo.s diff --git a/Kernel/platform-zx128/Makefile b/Kernel/platform-zx128/Makefile new file mode 100644 index 00000000..c6c92919 --- /dev/null +++ b/Kernel/platform-zx128/Makefile @@ -0,0 +1,25 @@ + +CSRCS = devtty.c +CSRCS += devices.c main.c + +ASRCS = crt0.s zx128.s zxvideo.s +ASRCS += tricks.s commonmem.s + +COBJS = $(CSRCS:.c=.rel) +AOBJS = $(ASRCS:.s=.rel) +OBJS = $(COBJS) $(AOBJS) + +JUNK = $(CSRCS:.c=.lst) $(CSRCS:.c=.asm) $(CSRCS:.c=.sym) $(ASRCS:.s=.lst) $(ASRCS:.s=.sym) $(CSRCS:.c=.rst) $(ASRCS:.s=.rst) + +all: $(OBJS) + +$(COBJS): %.rel: %.c + $(CROSS_CC) $(CROSS_CCOPTS) -c $< + +$(AOBJS): %.rel: %.s + $(CROSS_AS) $(ASOPTS) $< + +clean: + rm -f $(OBJS) $(JUNK) core *~ + +image: diff --git a/Kernel/platform-zx128/README b/Kernel/platform-zx128/README new file mode 100644 index 00000000..41f7651d --- /dev/null +++ b/Kernel/platform-zx128/README @@ -0,0 +1,29 @@ +An FUZIX target for ZX Spectrum 128. + +Big part of the code was taken from z80pack and msx1 ports. + +ZX Spectrum has a memory layout like follows: + +0000-3FFF ROM +4000-57FF Screen pixel data +5800-5AFF Screen attributes data +5B00-FFFF RAM + +1 memory bank exists at 0xC000, one of 6 16384-byte pages can be mapped there. + +So the fuzix port is limited to those features: + +1) Code1 segment should be flashed into ROM (instead of BASIC-128 in a simplest case). +2) We have memory "hole" at screen area (4000-5AFF), which we can not use for code or data. + So we can not allow Code1 to be larger than 0x4000 bytes. +3) We need to store some bootloader procedure in Code1, which should read fuzix image + data from somewhere (for now this is done via emulator hack) and place it in RAM at + correct addresses. +4) Common area can not be at F000 as usual because F000 belongs to banking area. +5) Maximum user program size is 16384 bytes. +6) ZX Spectrum 128 had not any official floppy disk controller. Third-party hardware + (like popular in Eastern Europe Betadisk Interface) was designed to be compatible + with old software, so contains some terrible features like FDC port visibility + limited to 256-bytes long area of ROM. Outside this area any requests to FDC ports + are ignored. This makes disk driver implementation very tricky until we have more + smart linker. \ No newline at end of file diff --git a/Kernel/platform-zx128/commonmem.s b/Kernel/platform-zx128/commonmem.s new file mode 100644 index 00000000..98969ae0 --- /dev/null +++ b/Kernel/platform-zx128/commonmem.s @@ -0,0 +1,52 @@ +; +; We need to put commonmem below the 0xC000 for multi tasking. +; + .module commonmem + + ; exported symbols + .globl _ub + .globl _udata + .globl kstack_top + .globl istack_top + .globl istack_switched_sp + + .area _COMMONMEM + +_ub: ; first 512 bytes: starts with struct u_block, with the kernel stack working down from above +_udata: +kstack_base: + .db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + .db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + .db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + .db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + .db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + .db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + .db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + .db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + .db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + .db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + .db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + .db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + .db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + .db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + .db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + .db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +kstack_top: + + ; next 256 bytes: 254 byte interrupt stack, then 2 byte saved stack pointer +istack_base: + .db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + .db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + .db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + .db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + .db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + .db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + .db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + .db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +istack_top: +istack_switched_sp: .dw 0 +; +; Padding so we can read/write uarea easily. We have tons of common +; free so can be quite relaxed about it all +; + .ds 0x100 diff --git a/Kernel/platform-zx128/config.h b/Kernel/platform-zx128/config.h new file mode 100644 index 00000000..5efb322c --- /dev/null +++ b/Kernel/platform-zx128/config.h @@ -0,0 +1,56 @@ +/* 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) */ +#define CONFIG_PROFIL +/* Multiple processes in memory at once */ +#undef CONFIG_MULTI +/* Single tasking */ +#define CONFIG_SINGLETASK +/* CP/M emulation */ +#undef CONFIG_CPM_EMU + +/* Video terminal, not a serial tty */ +#define CONFIG_VT +/* We want the 8x8 font */ +#define CONFIG_FONT8X8 +#define CONFIG_FONT8X8SMALL + +/* We have 1 bank at C000 with 6 possible pages to map, but I'm not sure if CONFIG_BANK_FIXED is our choise. */ +/* Fixed banking */ +#define CONFIG_BANK_FIXED +/* 6 16K banks, 1 is for kernel needs */ +#define MAX_MAPS 5 +#define MAP_SIZE 0x4000U + +/* Banks as reported to user space */ +#define CONFIG_BANKS 1 + +/* Vt definitions */ +#define VT_WIDTH 32 +#define VT_HEIGHT 24 +#define VT_RIGHT 31 +#define VT_BOTTOM 23 + +#define TICKSPERSEC 50 /* Ticks per second */ +#define PROGBASE ((char *)(0xC000)) /* also data base */ +#define PROGTOP ((char *)(0xFFFF)) /* Top of program, base of U_DATA copy */ +#define PROC_SIZE 16 /* Memory needed per process */ + +#define UDATA_BLOCKS 0 /* We swap the stash not the uarea */ +#define UDATA_SWAPSIZE 0 + +#define BOOT_TTY (1) /* Set this to default device for stdio, stderr */ + /* In this case, the default is the first TTY device */ + +/* 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 */ +#undef SWAPDEV /* Do not use swap */ +#define NBUFS 10 /* Number of block buffers */ +#define NMOUNTS 4 /* Number of mounts at a time */ diff --git a/Kernel/platform-zx128/crt0.s b/Kernel/platform-zx128/crt0.s new file mode 100644 index 00000000..daa1335b --- /dev/null +++ b/Kernel/platform-zx128/crt0.s @@ -0,0 +1,116 @@ +; 2013-12-18 William R Sowerbutts + + .module crt0 + + ; Ordering of segments for the linker. + ; WRS: Note we list all our segments here, even though + ; we don't use them all, because their ordering is set + ; when they are first seen. + .area _CODE + .area _CODE2 + .area _VIDEO + .area _DISCARD ; not discarded yet + .area _CONST + .area _FONT + .area _DATA + .area _INITIALIZED + .area _BSEG + .area _BSS + .area _HEAP + ; note that areas below here may be overwritten by the heap at runtime, so + ; put initialisation stuff in here + .area _INITIALIZER + .area _GSINIT + .area _GSFINAL + .area _COMMONMEM + + ; imported symbols + .globl _fuzix_main + .globl init_early + .globl init_hardware + .globl s__INITIALIZER + .globl s__COMMONMEM + .globl l__COMMONMEM + .globl s__DATA + .globl l__DATA + .globl kstack_top + + + .globl unix_syscall_entry + .globl nmi_handler + .globl interrupt_handler + + ; startup code + .area _CODE +init: + di + + ; if any button is pressed during reset - boot BASIC48 + in a, (#0xFE) ; only low 5 bits of 0xFE port contains key info. Bit is 0 when corresponding key of any half row is pressed. + or #0xE0 ; so setting high 3 bits to 1 + add #1 ; and check if we got 0xFF + + jp z, init_continue + + ; otherwise perform ROM bank switch and goto 0x0000 + ld de, #0x4000 + ld hl, #jump_to_basic_start + ld bc, #jump_to_basic_start - #jump_to_basic_end + ldir + jp 0x4000 + +jump_to_basic_start: + ld bc, #0x7FFD + ld a, #0x10 + out (c), a + jp 0 +jump_to_basic_end: + + ; spacer + .ds 0x0E + + ; .org 0x0030 ; syscall entry + jp unix_syscall_entry + + .ds 0x05 ; spacer + + ; .org 0x0038 ; interrupt handler + jp interrupt_handler + .ds 0x2B + + ; .org 0x0066 ; nmi handler + jp nmi_handler + +init_continue: + ld sp, #kstack_top + + ; hack for emulator. Read remaining fuzix part to RAM from fuzix.bin + ld bc, #0x1ee7 + in a, (c) + + ; Configure memory map + call init_early + + ; move the common memory where it belongs + ld hl, #s__INITIALIZER + ld de, #s__COMMONMEM + ld bc, #l__COMMONMEM + ldir + ; then zero the data area + ld hl, #s__DATA + ld de, #s__DATA + 1 + ld bc, #l__DATA - 1 + ld (hl), #0 + ldir + + ; Hardware setup + call init_hardware + + ; Call the C main routine + call _fuzix_main + + ; main shouldn't return, but if it does... + di +stop: halt + jr stop + diff --git a/Kernel/platform-zx128/devices.c b/Kernel/platform-zx128/devices.c new file mode 100644 index 00000000..22c06fdb --- /dev/null +++ b/Kernel/platform-zx128/devices.c @@ -0,0 +1,32 @@ +#include +#include +#include +#include +#include +#include + +struct devsw dev_tab[] = /* The device driver switch table */ +{ +// minor open close read write ioctl +// ----------------------------------------------------------------- + /* 0: /dev/tty TTY devices */ + { tty_open, tty_close, tty_read, tty_write, tty_ioctl }, + /* 1: /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-zx128/devtty.c b/Kernel/platform-zx128/devtty.c new file mode 100644 index 00000000..79f23005 --- /dev/null +++ b/Kernel/platform-zx128/devtty.c @@ -0,0 +1,61 @@ +#include +#include +#include +#include +#include +#include +#include + +char tbuf1[TTYSIZ]; + +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}, +}; + +/* tty1 is the screen */ + +/* Output for the system console (kprintf etc) */ +void kputchar(char c) +{ + if (c == '\n') + tty_putc(0, '\r'); + tty_putc(0, c); +} + +/* Both console and debug port are always ready */ +bool tty_writeready(uint8_t minor) +{ + minor; + return 1; +} + +void tty_putc(uint8_t minor, unsigned char c) +{ + minor; + vtoutput(&c, 1); +} + +int tty_carrier(uint8_t minor) +{ + minor; + return 1; +} + +void tty_setup(uint8_t minor) +{ + minor; +} + +void tty_pollirq(void) +{ + +} + +void tty_interrupt(void) +{ +} + +/* This is used by the vt asm code, but needs to live in the kernel */ +uint16_t cursorpos; + diff --git a/Kernel/platform-zx128/devtty.h b/Kernel/platform-zx128/devtty.h new file mode 100644 index 00000000..de0eaf73 --- /dev/null +++ b/Kernel/platform-zx128/devtty.h @@ -0,0 +1,6 @@ +#ifndef __DEVTTY_DOT_H__ +#define __DEVTTY_DOT_H__ + +void tty_pollirq(void); + +#endif diff --git a/Kernel/platform-zx128/kernel.def b/Kernel/platform-zx128/kernel.def new file mode 100644 index 00000000..b28696fb --- /dev/null +++ b/Kernel/platform-zx128/kernel.def @@ -0,0 +1,6 @@ +; 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_STASH .equ 0xED00 ; BD00-BFFF \ No newline at end of file diff --git a/Kernel/platform-zx128/main.c b/Kernel/platform-zx128/main.c new file mode 100644 index 00000000..15670722 --- /dev/null +++ b/Kernel/platform-zx128/main.c @@ -0,0 +1,52 @@ +#include +#include +#include +#include +#include + +uint8_t *ramtop = PROGTOP; + + +void pagemap_init(void) +{ + int i; + for (i = 1; i < 8; i++) + pagemap_add(i); +} + +/* The uarea is already synched to the stash which is written with the + process */ +uint8_t *swapout_prepare_uarea(ptptr p) +{ + p; + return NULL; +} + +/* The switchin code will move the uarea into the process itself, we just + need to fix up the u_page pointer */ +uint8_t *swapin_prepare_uarea(ptptr p) +{ + p; + return NULL; +} + +/* On idle we spin checking for the terminals. Gives us more responsiveness + for the polled ports */ +void platform_idle(void) +{ + /* We don't want an idle poll and IRQ driven tty poll at the same moment */ + irqflags_t irq = di(); + tty_pollirq(); + irqrestore(irq); +} + +void platform_interrupt(void) +{ + tty_pollirq(); + timer_interrupt(); +} + +/* Nothing to do for the map of init */ +void map_init(void) +{ +} diff --git a/Kernel/platform-zx128/tricks.s b/Kernel/platform-zx128/tricks.s new file mode 100644 index 00000000..6f250bd3 --- /dev/null +++ b/Kernel/platform-zx128/tricks.s @@ -0,0 +1,299 @@ +; 2013-12-21 William R Sowerbutts +; TODO: this code is copied from z80pack. Need to rewrite for zx128. + + .module tricks + + .globl _ptab_alloc + .globl _newproc + .globl _chksigs + .globl _getproc + .globl _trap_monitor + .globl trap_illegal + .globl _inint + .globl _switchout + .globl _switchin + .globl _doexec + .globl _dofork + .globl _runticks + .globl unix_syscall_entry + .globl interrupt_handler + .globl dispatch_process_signal + + ; imported debug symbols + .globl outstring, outde, outhl, outbc, outnewline, outchar, outcharhex + + .include "kernel.def" + .include "../kernel.def" + + .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(). +; +; FIXME: make sure we optimise the switch to self case higher up the stack! +; +; This function can have no arguments or auto variables. +_switchout: + di + call _chksigs + ; save machine state + + 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 + + ; set inint to false + xor a + ld (_inint), a + + ; Stash the uarea back into process memory + ld hl, (U_DATA__U_PAGE) + ld a, l + out (21), a + ld hl, #U_DATA + ld de, #U_DATA_STASH + ld bc, #U_DATA__TOTALSIZE + ldir + xor a + out (21), a + + ; find another process to run (may select this one again) + call _getproc + + push hl + call _switchin + + ; we should never get here + call _trap_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 +; +; FIXME: do we actually *need* to restore the stack ! +; + push de ; restore stack + push bc ; restore stack + + xor a + out (21), a + + push de + ld hl, #P_TAB__P_PAGE_OFFSET + add hl, de ; process ptr + pop de + + ld a, (hl) + + ; Pages please ! + out (21), a + + ; bear in mind that the stack will be switched now, so we can't use it + ; to carry values over this point + + exx ; thank goodness for exx 8) + ld hl, #U_DATA_STASH + ld de, #U_DATA + ld bc, #U_DATA__TOTALSIZE + ldir + exx + + xor a + out (21), a + + ; 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==IX + jr nz, switchinfail + + ; 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 the ISR isn't already running + ld a, (_inint) + or a + ret z ; in 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 _trap_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. + + 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. + + ; Need to write a new 47.25K bank copy here, then copy the live uarea + ; into the stash of the new process + + ; --------- copy process --------- + + ld hl, (fork_proc_ptr) + ld de, #P_TAB__P_PAGE_OFFSET + add hl, de + ; load p_page + ld c, (hl) + ld hl, (U_DATA__U_PAGE) + ld a, l + + call bankfork ; do the bank to bank copy + + ; Copy done + + ld hl, (U_DATA__U_PAGE) ; parent memory + ld a, l + out (21), a ; Switch context to parent + + ; 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 + ldir + xor a + out (21), a + ; 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, (fork_proc_ptr) + push hl + call _newproc + 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 +; +; We can keep a stack in common because we will complete our +; use of it before we switch common block. In this case we have +; a true common so it's even easier. +; + .ds 128 +_swapstack: + +; +; This is related so we will keep it here. Copy the process memory +; for a fork. a is the page base of the parent, c of the child +; (this API will be insufficient once we have chmem and proper use of +; banks - as well as needing to support fork to disk) +; +; Assumption - fits into a fixed number of whole 256 byte blocks +; +bankfork: +; ld bc, #(0xC000 - 768) ; 48K minus the uarea stash + + ld b, #0xBD ; C0 x 256 minus 3 sets for the uarea stash + ld hl, #0 ; base of memory to fork (vectors included) +bankfork_1: + push bc ; Save our counter and also child offset + push hl + out (21), a ; switch to parent bank + ld de, #bouncebuffer + ld bc, #256 + ldir ; copy into the bounce buffer + pop de ; recover source of copy to bounce + ; as destination in new bank + pop bc ; recover child port number + push bc + ld b, a ; save the parent bank id + ld a, c ; switch to the child + out (21), a + push bc ; save the bank pointers + ld hl, #bouncebuffer + ld bc, #256 + ldir ; copy into the child + pop bc ; recover the bank pointers + ex de, hl ; destination is now source for next bank + ld a, b ; parent back is wanted in a + pop bc + djnz bankfork_1 ; rinse, repeat + ret + +; +; For the moment +; +bouncebuffer: + .ds 256 diff --git a/Kernel/platform-zx128/zx128.s b/Kernel/platform-zx128/zx128.s new file mode 100644 index 00000000..61676573 --- /dev/null +++ b/Kernel/platform-zx128/zx128.s @@ -0,0 +1,172 @@ +; +; ZX Spectrum 128 hardware support +; +; +; This goes straight after udata for common. Because of that the first +; 256 bytes get swapped to and from disk with the uarea (512 byte disk +; blocks). This isn't a problem but don't put any variables in here. +; +; If you make this module any shorter, check what follows next +; + + + .module zx128 + + ; exported symbols + .globl init_early + .globl init_hardware + .globl _program_vectors + .globl _system_tick_counter + .globl platform_interrupt_all + + .globl map_kernel + .globl map_process + .globl map_process_always + .globl map_save + .globl map_restore + + .globl _fd_bankcmd + + ; exported debugging tools + .globl _trap_monitor + .globl outchar + + ; imported symbols + .globl _ramsize + .globl _procmem + + .globl outcharhex + .globl outhl, outde, outbc + .globl outnewline + .globl outstring + .globl outstringhex + + .include "kernel.def" + .include "../kernel.def" + +; ----------------------------------------------------------------------------- +; COMMON MEMORY BANK (0xF000 upwards) +; ----------------------------------------------------------------------------- + .area _COMMONMEM + +_trap_monitor: + ld a, #128 + ; out (29), a ; TODO: go where? BASIC48? +platform_interrupt_all: + ret + +_trap_reboot: + rst 0 + +; +; We need the right bank present when we cause the transfer +; +_fd_bankcmd: + ret + pop de ; return + pop bc ; command + pop hl ; bank + push hl + push bc + push de ; fix stack + ld a, i + di + push af ; save DI state + call map_process ; HL alread holds our bank + ld a, c ; issue the command + ; out (13), a ; + call map_kernel ; return to kernel mapping + pop af + ret po + ei + ret + +; ----------------------------------------------------------------------------- +; KERNEL MEMORY BANK (below 0xC000, only accessible when the kernel is mapped) +; ----------------------------------------------------------------------------- + .area _CODE + +init_early: + ld bc, #0x7ffd + xor a + out (c), a ; set page 0 at 0xC000 + ret + +init_hardware: + ; set system RAM size + ld hl, #128 + ld (_ramsize), hl + ld hl, #(128 - 48) ; 48K for kernel + ld (_procmem), hl + + ; screen initialization + ; clear + ld hl, #0x4000 + ld de, #0x4001 + ld bc, #0x1800 + xor a + ld (hl), a + ldir + + ; set color attributes + ld a, #7 ; black paper, white ink + ld bc, #0x300 - #1 + ld (hl), a + ldir + + im 1 ; set CPU interrupt mode + ret + +;------------------------------------------------------------------------------ +; COMMON MEMORY PROCEDURES FOLLOW + + .area _COMMONMEM + + ; our vectors are in ROM, so nothing to do here +_program_vectors: + ret + + ; below is code which was copied from z80pack, so it's useless for zx128 +map_kernel: + push af + xor a + ; out (21), a + pop af + ret + +map_process: + ld a, h + or l + jr z, map_kernel + ld a, (hl) + ; out (21), a + ret + +map_process_always: + push af + ld a, (U_DATA__U_PAGE) + ; out (21), a + pop af + ret + +map_save: + push af + in a, (21) + ld (map_store), a + pop af + ret + +map_restore: + push af + ld a, (map_store) + ; out (21), a + pop af + ret + +map_store: + .db 0 + +; outchar: TODO: add something here (char in A). Current port #15 is emulator stub +outchar: + out (#0x15), A + ret diff --git a/Kernel/platform-zx128/zxvideo.s b/Kernel/platform-zx128/zxvideo.s new file mode 100644 index 00000000..2b496ee1 --- /dev/null +++ b/Kernel/platform-zx128/zxvideo.s @@ -0,0 +1,264 @@ +; +; zx128 vt primitives + + .module zx128 + + ; exported symbols + .globl _plot_char + .globl _scroll_down + .globl _scroll_up + .globl _cursor_on + .globl _cursor_off + .globl _clear_lines + .globl _clear_across + .globl _do_beep + + .globl _fontdata_8x8 + + .area _VIDEO + + ; colors are ignored everywhere for now + +videopos: + ld a,e + and #7 + rrca + rrca + rrca + add a,d + ld d,e + ld e,a + ld a,d + and #0x18 + or #0x40 + ld d,a + ret + +_plot_char: + pop hl + pop de ; D = x E = y + pop bc + push bc + push de + push hl + + call videopos + + ld b, #0 ; calculating offset in font table + ld a, c + rla + rl b + rla + rl b + rla + rl b + ld c, a + + ld hl, #_fontdata_8x8 + add hl, bc ; hl points to first byte of char data + + ; printing + ld c, #8 +plot_char_loop: + ld a, (hl) + ld (de), a + inc hl ; next byte of char data + inc d ; next screen line + dec c + jr nz, plot_char_loop + ret + + +_clear_lines: + pop hl + pop de ; E = line, D = count + push de + push hl + +clear_next_line: + push de + ld d, #0 ; from the column #0 + ld b, d ; b = 0 + ld c, #32 ; clear 32 cols + push bc + push de + call _clear_across + + pop hl ; clear stack + pop hl + + pop de + inc e + dec d + jr nz, clear_next_line + + ret + + +_clear_across: + pop hl + pop de ; DE = coords + pop bc ; C = count + push bc + push de + push hl + call videopos ; first pixel line of first character in DE + push de + pop hl ; copy to hl + xor a + + ; no boundary checks. Assuming that D + C < SCREEN_WIDTH + +clear_line: + ld b, #8 ; 8 pixel lines to clear for this char +clear_char: + ld (de), a + inc d + dec b + jr nz, clear_char + + ex de, hl + inc de + push de + pop hl + + dec c + jr nz, clear_line + ret + +copy_line: + ; HL - source, DE - destination + + ; convert line coordinates to screen coordinates both for DE and HL + push de + ex de, hl + call videopos + ex de, hl + pop de + call videopos + + ld c, #8 + +copy_line_nextchar: + push hl + push de + + ld b, #32 + +copy_pixel_line: + ld a, (hl) + ld (de), a + inc e + inc l + dec b + jr nz, copy_pixel_line + + pop de + pop hl + inc d + inc h + dec c + jr nz, copy_line_nextchar + ret + + ; TODO: the LDIR way should be much faster + +_scroll_down: + ; set HL = (0,0), DE = (0, 1) + xor a + ld d, a + ld h, a + ld l, a + ld e, #1 + ld c, #23 ; 23 lines to move + +loop_scroll_down: + push hl + push de + push bc + + call copy_line + + pop bc + pop de + pop hl + + inc l + inc e + dec c + jr nz, loop_scroll_down + + ret + + +_scroll_up: + ; set HL = (0,23), DE = (0, 22) + xor a + ld d, a + ld h, a + ld l, #23 + ld e, #22 + ld c, #23 ; 23 lines to move + +loop_scroll_up: + push hl + push de + push bc + + call copy_line + + pop bc + pop de + pop hl + + dec l + dec e + dec c + jr nz, loop_scroll_up + + ret + +_cursor_on: + pop hl + pop de + push de + push hl + ld (cursorpos), de + + call videopos + ld a, #7 + add a, d + ld d, a + ld a, #0xFF + ld (de), a + ret +_cursor_off: + ld de, (cursorpos) + call videopos + ld a, #7 + add a, d + ld d, a + xor a + ld (de), a + ret + + ; FIXME: now this is_do_silent_click actually +_do_beep: + ld e, #0xFF ; length + ld c, #0xFE ; beeper port + ld l, #0x10 ; beeper bit +loop_beep: + ld a, l + out (c), a + xor a + out (c), a + dec bc + ld a, b + or c + jr nz, loop_beep + ret + + .area _DATA + +cursorpos: + .dw 0 -- 2.34.1