zx128: Initial ZX Spectrum 128 support
authorAlexander Tsidaev <a.tsidaev@gmail.com>
Sun, 9 Nov 2014 09:51:59 +0000 (14:51 +0500)
committerAlexander Tsidaev <a.tsidaev@gmail.com>
Sun, 9 Nov 2014 09:51:59 +0000 (14:51 +0500)
13 files changed:
Kernel/platform-zx128/Makefile [new file with mode: 0644]
Kernel/platform-zx128/README [new file with mode: 0644]
Kernel/platform-zx128/commonmem.s [new file with mode: 0644]
Kernel/platform-zx128/config.h [new file with mode: 0644]
Kernel/platform-zx128/crt0.s [new file with mode: 0644]
Kernel/platform-zx128/devices.c [new file with mode: 0644]
Kernel/platform-zx128/devtty.c [new file with mode: 0644]
Kernel/platform-zx128/devtty.h [new file with mode: 0644]
Kernel/platform-zx128/kernel.def [new file with mode: 0644]
Kernel/platform-zx128/main.c [new file with mode: 0644]
Kernel/platform-zx128/tricks.s [new file with mode: 0644]
Kernel/platform-zx128/zx128.s [new file with mode: 0644]
Kernel/platform-zx128/zxvideo.s [new file with mode: 0644]

diff --git a/Kernel/platform-zx128/Makefile b/Kernel/platform-zx128/Makefile
new file mode 100644 (file)
index 0000000..c6c9291
--- /dev/null
@@ -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 (file)
index 0000000..41f7651
--- /dev/null
@@ -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 (file)
index 0000000..98969ae
--- /dev/null
@@ -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 (file)
index 0000000..5efb322
--- /dev/null
@@ -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 (file)
index 0000000..daa1335
--- /dev/null
@@ -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 (file)
index 0000000..22c06fd
--- /dev/null
@@ -0,0 +1,32 @@
+#include <kernel.h>
+#include <version.h>
+#include <kdata.h>
+#include <tty.h>
+#include <devsys.h>
+#include <devtty.h>
+
+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 (file)
index 0000000..79f2300
--- /dev/null
@@ -0,0 +1,61 @@
+#include <kernel.h>
+#include <kdata.h>
+#include <printf.h>
+#include <stdbool.h>
+#include <devtty.h>
+#include <vt.h>
+#include <tty.h>
+
+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 (file)
index 0000000..de0eaf7
--- /dev/null
@@ -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 (file)
index 0000000..b28696f
--- /dev/null
@@ -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 (file)
index 0000000..1567072
--- /dev/null
@@ -0,0 +1,52 @@
+#include <kernel.h>
+#include <timer.h>
+#include <kdata.h>
+#include <printf.h>
+#include <devtty.h>
+
+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 (file)
index 0000000..6f250bd
--- /dev/null
@@ -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 (file)
index 0000000..6167657
--- /dev/null
@@ -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 (file)
index 0000000..2b496ee
--- /dev/null
@@ -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