plus3: Initial files for spectrum +3
authorAlan Cox <alan@linux.intel.com>
Sat, 28 Feb 2015 14:04:06 +0000 (14:04 +0000)
committerAlan Cox <alan@linux.intel.com>
Sat, 28 Feb 2015 14:04:06 +0000 (14:04 +0000)
This is just development in progress to make it easier to see how to split
the 128 and the plus 3 features.

20 files changed:
Kernel/Makefile
Kernel/platform-plus3/README [new file with mode: 0644]
Kernel/platform-plus3/commonmem.s [new file with mode: 0644]
Kernel/platform-plus3/config.h [new file with mode: 0644]
Kernel/platform-plus3/crt0.s [new file with mode: 0644]
Kernel/platform-plus3/devfd.c [new file with mode: 0644]
Kernel/platform-plus3/devfd.h [new file with mode: 0644]
Kernel/platform-plus3/devices.c [new file with mode: 0644]
Kernel/platform-plus3/devtty.c [new file with mode: 0644]
Kernel/platform-plus3/devtty.h [new file with mode: 0644]
Kernel/platform-plus3/floppy.s [new file with mode: 0644]
Kernel/platform-plus3/fuzix.lnk [new file with mode: 0644]
Kernel/platform-plus3/kernel.def [new file with mode: 0644]
Kernel/platform-plus3/main.c [new file with mode: 0644]
Kernel/platform-plus3/plus3.s [new file with mode: 0644]
Kernel/platform-plus3/rules.mk [new file with mode: 0644]
Kernel/platform-plus3/sysconfig.h [new file with mode: 0644]
Kernel/platform-plus3/target.mk [new file with mode: 0644]
Kernel/platform-plus3/tricks.s [new file with mode: 0644]
Kernel/platform-plus3/zxvideo.s [new file with mode: 0644]

index a59aaee..77ecfc3 100644 (file)
@@ -1,4 +1,4 @@
-TARGET_LIST = platform-nc100 platform-micropack platform-pcw8256 platform-socz80 platform-zx128 platform-trs80 platform-z80pack platform-z80pack-lite platform-z80pack32 platform-dragon platform-tgl6502
+TARGET_LIST = platform-nc100 platform-micropack platform-pcw8256 platform-socz80 platform-zx128 platform-trs80 platform-z80pack platform-z80pack-lite platform-z80pack32 platform-dragon platform-tgl6502 platform-plus3
 
 #export TARGET = 8086test
 #export TARGET = atarist
@@ -11,13 +11,14 @@ TARGET_LIST = platform-nc100 platform-micropack platform-pcw8256 platform-socz80
 #export TARGET = nc100
 #export TARGET = p112
 #export TARGET = pcw8256
+export TARGET = plus3
 #export TARGET = px4plus
 #export TARGET = tgl6502
 #export TARGET = trs80
 #export TARGET = ubee
 #export TARGET = z80pack
 #export TARGET = z80pack-lite
-export TARGET= zx128
+#export TARGET= zx128
 
 export VERSION = "0.1"
 export SUBVERSION = "ac1"
diff --git a/Kernel/platform-plus3/README b/Kernel/platform-plus3/README
new file mode 100644 (file)
index 0000000..c96f0a1
--- /dev/null
@@ -0,0 +1,29 @@
+An FUZIX target for ZX Spectrum +2A/+3
+
+The +2A and +3 have the following choice of memory configurations on top of
+the standard 128K spectrum
+
+       00  40  80  C0
+conf0  [0] [1] [2] [3]
+conf1  [4] [5] [6] [7]
+conf2  [4] [5] [6] [3]
+conf3  [4] [7] [6] [3]
+
+
+That gives us a conventional low 0/1 and 4/5 for user space with the kernel
+using 2/3/6/7.
+
+Kernel maps are then
+
+3 = common (always mapped high)
+2/6 = banked (at 0x8000)
+7 = banked with screen (at 0x4000)
+
+although 7 does not appear to be part of the banks we cannot map 2 and 7
+together so it's effectively banked.
+
+Alternatively we could go with a single 64K swapping user space with
+kernel mapped normally at 4-7  (with screen hole at C000) and user at 0-3
+and a small copied common in bank 3 and 7
+
+
diff --git a/Kernel/platform-plus3/commonmem.s b/Kernel/platform-plus3/commonmem.s
new file mode 100644 (file)
index 0000000..dc31c24
--- /dev/null
@@ -0,0 +1,8 @@
+;
+; Multiple app sizes and the fact the kernel and apps share the same banks
+; means we need to put this somewhere low
+;
+        .module commonmem
+        .area _COMMONDATA
+
+       .include "../cpu-z80/std-commonmem.s"
diff --git a/Kernel/platform-plus3/config.h b/Kernel/platform-plus3/config.h
new file mode 100644 (file)
index 0000000..51b7253
--- /dev/null
@@ -0,0 +1,69 @@
+/* Simple IDE interface */
+#define CONFIG_IDE
+#define IDE_REG_DATA           0xA3
+#define IDE_REG_ERROR          0xA7
+#define IDE_REG_FEATURES       0xA7
+#define IDE_REG_SEC_COUNT      0xAB
+#define IDE_REG_LBA_0          0xAF
+#define IDE_REG_LBA_1          0xB3
+#define IDE_REG_LBA_2          0xB7
+#define IDE_REG_LBA_3          0xBB
+#define IDE_REG_DEVHEAD                0xBB
+#define IDE_REG_STATUS         0xBF
+#define IDE_REG_COMMAND                0xBF
+
+/* 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
+/* Multiple processes in memory at once */
+#define CONFIG_MULTI
+/* Single tasking */
+#undef CONFIG_SINGLETASK
+/* Swap only : swap via IDE or banked low RAM or similar */
+#define CONFIG_SWAP_ONLY
+/* Banks as reported to user space */
+#define CONFIG_BANKS 1
+
+/* Keyboard contains not ascii symbols */
+#define CONFIG_UNIKEY
+
+/* Video terminal, not a serial tty */
+#define CONFIG_VT
+/* 8x8 font for the moment */
+#define CONFIG_FONT8X8
+/* 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    0x0000  /* also data base */
+#define PROGLOAD    0x0100  /* also data base */
+#define PROGTOP     0xF800  /* Top of program */
+#define PROC_SIZE   64   /* Memory needed per process */
+
+#define BOOT_TTY (513)  /* 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 */
+
+#define SWAPDEV     (swap_dev)         /* Swap device (dynamic) */
+#define SWAP_SIZE   0x7C       /* 64K minus the common in blocks */
+#define SWAPBASE    0x0000     /* We swap the lot in one, include the */
+#define SWAPTOP            0xF800      /* vectors so its a round number of sectors */
+#define MAX_SWAPS      64
+
+
+#define NBUFS    9        /* Number of block buffers */
+#define NMOUNTS         4        /* Number of mounts at a time */
+#define MAX_BLKDEV 2      /* 2 IDE drives, 1 SD drive */
diff --git a/Kernel/platform-plus3/crt0.s b/Kernel/platform-plus3/crt0.s
new file mode 100644 (file)
index 0000000..f1025b9
--- /dev/null
@@ -0,0 +1,103 @@
+        .module crt0
+
+        .module crt0
+
+       ;
+       ; Bank 4-7
+       ;
+        .area _CODE
+        .area _CODE2
+       .area _CODE3
+        .area _CONST
+       .area _VIDEO
+        .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 _DISCARD
+       .area _FONT
+       ;
+       ;       Above 0xC000 in the top of bank 3
+       ;
+        .area _COMMONMEM
+       .area _COMMONDATA
+
+        ; imported symbols
+        .globl _fuzix_main
+        .globl init_early
+        .globl init_hardware
+       .globl _sysconfig
+        .globl s__INITIALIZER
+        .globl s__DATA
+        .globl l__DATA
+        .globl s__FONT
+        .globl l__FONT
+        .globl s__DISCARD
+        .globl l__DISCARD
+        .globl s__COMMONMEM
+        .globl l__COMMONMEM
+
+        .globl kstack_top
+
+        ; startup code
+        .area _CODE
+
+       .include "kernel.def"
+
+;
+;      The bootloader has executed and now enters our code. HL points
+;      to a table of properties extracted from the 3DOS and BASIC
+;      environment. Do the memory shuffle and get going
+;
+init1:
+        di
+        ld sp, #kstack_top
+       push hl
+
+        ; 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
+       ; font
+       ld de, #s__FONT
+       ld bc, #l__FONT
+       ldir
+       ; and the discard
+       ld de, #s__DISCARD
+       ld bc, #l__DISCARD
+       ldir
+       ; then zero the data area
+       ld hl, #s__DATA
+       ld de, #s__DATA + 1
+       ld bc, #l__DATA - 1
+       ld (hl), #0
+       ldir
+
+       pop hl
+       ld (_sysconfig), hl     ; We keep this safe until we wiped data
+
+        ; 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
+
+
+       .area _DATA
+_sysconfig:
+       .word   0
diff --git a/Kernel/platform-plus3/devfd.c b/Kernel/platform-plus3/devfd.c
new file mode 100644 (file)
index 0000000..3fe7908
--- /dev/null
@@ -0,0 +1,116 @@
+#include <kernel.h>
+#include <kdata.h>
+#include <printf.h>
+#include <devfd.h>
+#include <sysconfig.h>
+
+#define MAX_FD 2
+
+extern uint8_t fdc_user;
+extern uint16_t fdc_addr;
+extern uint8_t rwcmd[9];
+extern uint8_t seekcmd[3];
+
+extern void fdc_motoron(void);
+extern void fdc_motoroff(void);
+extern uint8_t fdc_read(void);
+extern uint8_t fdc_write(void);
+extern uint8_t fdc_recal(void);
+extern uint8_t fdc_seek(void);
+
+uint8_t track[MAX_FD] = { 0xFF, 0xFF };
+
+static int fd_transfer(uint8_t minor, bool is_read, uint8_t rawflag)
+{
+    blkno_t block;
+    uint16_t dptr;
+    int ct = 0;
+    int tries;
+    uint8_t err = 0;
+    uint8_t nblock;
+
+    if(rawflag == 2)
+        goto bad2;
+
+    if (rawflag == 0) {
+        dptr = (uint16_t)udata.u_buf->bf_data;
+        block = udata.u_buf->bf_blk;
+        nblock = 1;
+    } else {
+        if (((uint16_t)udata.u_offset|udata.u_count) & BLKMASK)
+            goto bad2;
+        dptr = (uint16_t)udata.u_base;
+        block = udata.u_offset >> 9;
+        nblock = udata.u_count >> 9;
+    }
+    
+    rwcmd[1] = 1 << minor;
+    rwcmd[2] = block / 9;
+    rwcmd[3] = 0;              /* Single sided only for now */
+    rwcmd[4] = 1 + block % 9;  /* Sector */
+    rwcmd[6] = rwcmd[4];
+
+    fdc_user = rawflag;
+
+    while (ct < nblock) {
+        fdc_addr = dptr;
+        if (track[minor] != rwcmd[2]) {
+            seekcmd[1] = 1 << minor;
+            seekcmd[2] = rwcmd[2];
+            fdc_seek();
+        }
+        for (tries = 0; tries < 4 ; tries++) {
+            /* FIXME: need to return status properly and mask it */
+            if (is_read)
+                err = fdc_read();
+            else
+                err = fdc_write();
+            if (err == 0)
+                break;
+        }
+        if (tries > 1)         /* FIXME: set drive */
+            fdc_recal();
+        if (tries == 4)
+            goto bad;
+
+        dptr += 0x200; /* Move on 512 bytes in the buffer */
+
+        rwcmd[4]++;
+        /* Step a track */
+        if (rwcmd[4] > 10) {
+            rwcmd[4] = 1;
+            rwcmd[2]++;
+        }
+        rwcmd[6] = rwcmd[4];
+        ct++;
+    }
+    return 1;
+bad:
+    kprintf("fd%d: error %x\n", minor, err);
+bad2:
+    udata.u_error = EIO;
+    return -1;
+}
+
+int fd_open(uint8_t minor, uint16_t flag)
+{
+    flag;
+    /* Check we have a floppy */
+    if(!(sysconfig & CONF_PLUS3) || minor >= MAX_FD) {
+        udata.u_error = ENODEV;
+        return -1;
+    }
+    return 0;
+}
+
+int fd_read(uint8_t minor, uint8_t rawflag, uint8_t flag)
+{
+    flag;
+    return fd_transfer(minor, true, rawflag);
+}
+
+int fd_write(uint8_t minor, uint8_t rawflag, uint8_t flag)
+{
+    flag;
+    return fd_transfer(minor, false, rawflag);
+}
diff --git a/Kernel/platform-plus3/devfd.h b/Kernel/platform-plus3/devfd.h
new file mode 100644 (file)
index 0000000..50f9283
--- /dev/null
@@ -0,0 +1,13 @@
+#ifndef __DEVFD_DOT_H__
+#define __DEVFD_DOT_H__
+
+/* public interface */
+int fd_read(uint8_t minor, uint8_t rawflag, uint8_t flag);
+int fd_write(uint8_t minor, uint8_t rawflag, uint8_t flag);
+int fd_open(uint8_t minor, uint16_t flag);
+
+/* low level interface */
+uint16_t fd_reset(uint8_t *driveptr);
+uint16_t fd_operation(uint8_t *driveptr);
+
+#endif /* __DEVFD_DOT_H__ */
diff --git a/Kernel/platform-plus3/devices.c b/Kernel/platform-plus3/devices.c
new file mode 100644 (file)
index 0000000..51e43f3
--- /dev/null
@@ -0,0 +1,44 @@
+#include <kernel.h>
+#include <version.h>
+#include <kdata.h>
+#include <tty.h>
+#include <devsys.h>
+#include <vt.h>
+#include <devfd.h>
+#include <devide.h>
+#include <blkdev.h>
+
+struct devsw dev_tab[] =  /* The device driver switch table */
+{
+  /* 0: /dev/fd        Floppy disc block devices: disciple */
+  {  fd_open,      no_close,     fd_read,  fd_write,   no_ioctl },
+#ifdef CONFIG_IDE
+  /* 1: /dev/hd                Hard disc block devices */
+  {  blkdev_open,  no_close,     blkdev_read,   blkdev_write,  blkdev_ioctl },
+#else
+  {  no_open,      no_close,     no_rdwr,       no_rdwr,       no_ioctl },
+#endif
+  /* 2: /dev/tty       TTY devices */
+  {  tty_open,    tty_close,    tty_read,      tty_write,     vt_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  }
+};
+
+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)
+{
+#ifdef CONFIG_IDE
+  devide_init();
+#endif
+}
diff --git a/Kernel/platform-plus3/devtty.c b/Kernel/platform-plus3/devtty.c
new file mode 100644 (file)
index 0000000..d50893d
--- /dev/null
@@ -0,0 +1,179 @@
+#include <kernel.h>
+#include <kdata.h>
+#include <printf.h>
+#include <stdbool.h>
+#include <devtty.h>
+#include <keycode.h>
+#include <vt.h>
+#include <tty.h>
+
+char tbuf1[TTYSIZ];
+
+/* buffer for port scan procedure */
+uint8_t keybuf[8];
+/* Previous state */
+uint8_t keymap[8];
+
+static uint8_t keybyte, keybit;
+static uint8_t newkey;
+static int keysdown = 0;
+
+uint8_t keyboard[8][5] = {
+       {' ', 0  , 'm', 'n', 'b'},
+       {13 , 'l', 'k', 'j', 'h'},
+       {'p', 'o', 'i', 'u', 'y'},
+       {'0', '9', '8', '7', '6'},
+       {'1', '2', '3', '4', '5'},
+       {'q', 'w', 'e', 'r', 't'},
+       {'a', 's', 'd', 'f', 'g'},
+       {0  , 'z', 'x', 'c', 'v'}
+};
+
+/* SYMBOL SHIFT MODE */
+uint8_t shiftkeyboard[8][5] = {
+       {' ', 0 , '.',  ',', '*'},
+       {13 , '=', '+', '-', '^'},
+       {'"', ';', '@', ']', '['},
+       {'_', ')', '(', '\'','&'},
+       {'!', '@', '#', '$', '%'},
+       {'`',  0 ,  0 , '<', '>'},
+       {'~' ,'|', '\\','{', '}'},
+       {0  , ':', KEY_POUND  , '?', '/'}
+};
+
+
+static uint8_t shiftmask[8] = { 0x02, 0, 0, 0, 0, 0, 0, 0x01 };
+
+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 */
+ttyready_t tty_writeready(uint8_t minor)
+{
+       minor;
+       return TTY_READY_NOW;
+}
+
+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_sleeping(uint8_t minor)
+{
+       minor;
+}
+
+void update_keyboard(void)
+{
+       /* We need this assembler code because SDCC __sfr cannot handle 16-bit addresses. And because it is much faster, of course  */
+       /* TODO: make it naked? */
+       __asm
+               ld hl,#_keybuf
+               ld c, #0xFE
+               ld b, #0x7f
+               ld e, #8        ; 8 keyboard ports, 7FFE, BFFE, DFFE and so on
+       read_halfrow:
+               in a, (c)
+;              and #0
+               cpl
+               ld(hl), a
+               rrc b
+               inc hl
+               dec e
+               jr nz, read_halfrow
+       __endasm;
+}
+
+void tty_pollirq(void)
+{
+       int i;
+
+       update_keyboard();
+
+       newkey = 0;
+
+       for (i = 0; i < 8; i++) {
+               int n;
+               uint8_t key = keybuf[i] ^ keymap[i];
+               if (key) {
+                       uint8_t m = 0x10;
+                       for (n = 4; n >= 0; n--) {
+                               if ((key & m) && (keymap[i] & m))
+                                       if (!(shiftmask[i] & m))
+                                               keysdown--;
+
+                               if ((key & m) && !(keymap[i] & m)) {
+                                       if (!(shiftmask[i] & m))
+                                               keysdown++;
+                                       keybyte = i;
+                                       keybit = n;
+                                       newkey = 1;
+                               }
+                               m >>= 1;
+                       }
+               }
+               keymap[i] = keybuf[i];
+       }
+
+       if (keysdown < 3 && newkey)
+               keydecode();
+}
+
+static uint8_t cursor[4] = { KEY_LEFT, KEY_DOWN, KEY_UP, KEY_RIGHT };
+
+static void keydecode(void)
+{
+       uint8_t c;
+
+       uint8_t ss = keymap[0] & 0x02;  /* SYMBOL SHIFT */
+       uint8_t cs = keymap[7] & 0x01;  /* CAPS SHIFT */
+
+       if (ss) {
+               c = shiftkeyboard[keybyte][keybit];
+       } else {
+               c = keyboard[keybyte][keybit];
+               if (cs) {
+                       if (c >= 'a' && c <= 'z')
+                               c -= 'a' - 'A';
+                       else if (c == '0')      /* CS + 0 is backspace) */
+                               c = 0x08;
+                       else if (c == ' ')
+                               c = KEY_STOP;   /* ^C map for BREAK */
+                       else if (c >= '5' && c <= '8')
+                               c = cursor[c - '5'];
+               }
+       }
+
+
+       if (c != 0)
+               tty_inproc(1, c);
+}
+
+
+/* This is used by the vt asm code, but needs to live in the kernel */
+uint16_t cursorpos;
diff --git a/Kernel/platform-plus3/devtty.h b/Kernel/platform-plus3/devtty.h
new file mode 100644 (file)
index 0000000..039589f
--- /dev/null
@@ -0,0 +1,13 @@
+#ifndef __DEVTTY_DOT_H__
+#define __DEVTTY_DOT_H__
+
+void tty_pollirq(void);
+static void keydecode(void);
+
+#define KEY_ROWS       8
+#define KEY_COLS       5
+extern uint8_t keymap[8];
+extern uint8_t keyboard[8][5];
+extern uint8_t shiftkeyboard[8][5];
+
+#endif
diff --git a/Kernel/platform-plus3/floppy.s b/Kernel/platform-plus3/floppy.s
new file mode 100644 (file)
index 0000000..d367d8b
--- /dev/null
@@ -0,0 +1,302 @@
+;
+;      ROM timings are used for the drives.
+;
+;      0x0A, motor on
+;      0x32, motor off
+;      0xAF, write off
+;      0x1E, head settle       ms
+;      0x0C, step rate         ms
+;      0x0F, head unload
+;      0x03, head load x 2 + 1
+;
+;
+       .area _COMMONMEM
+
+       .globl _fdc_motoron, _fdc_motoroff
+       .globl _fdc_read, _fdc_write, _fdc_recal, _fdc_seek
+       .globl _fdc_user, _fdc_addr
+       .globl _fdc_statbuf
+       .globl _seekcmd
+       .globl _rwcmd
+       .globl _recalcmd
+       .globl port_map
+       .globl map_kernel, map_process_always
+
+fdc_cmd:
+       dec b
+       jr z, fdclast
+fdc_cmdl:
+       call fdc_outcmd
+       inc hl
+       ret c                   ; Error
+       djnz fdc_cmdl
+fdclast:
+       di
+fdc_outcmd:
+       push bc
+       ld bc, #0x2ffd
+       ld e, (hl)
+fdc_outcmdw:                   ; Wait non busy
+       in a, (c)
+       add a, a
+       jr c, fdc_outcmdw
+       add a, a                ; FIXME: can we use ret m or similar here ?
+       ret c                   ; Failed
+       ld b, #0x3F
+       out (c), e              ; Output the byte
+       ex (sp), hl
+       ex (sp), hl             ; Nap
+       pop bc
+       ret                     ; NC
+
+fdc_cmdq:                      ; Quick (non data) command
+       call fdc_outcmd
+       djnz fdc_cmdq
+       ret
+
+fdc_status:
+       ld bc, #0x2ffd
+       ld hl, #_fdc_statbuf
+fdc_statw:
+       in a, (c)
+       add a, a
+       jr c, fdc_statw
+       add a, a
+       ret nc                  ; Finished copying block
+       ld b, #0x3F
+       ini
+       ld b, #0x2F
+       ex (sp), hl
+       ex (sp), hl
+       jr fdc_statw
+
+fdc_statusa:
+       call fdc_status
+       ld a, (_fdc_statbuf)
+       ret
+
+fdc_read:
+       ld hl, #_rwcmd
+       ld (hl), #66
+       ld b, #9
+       call fdc_cmd            ; Will return with DI
+fdc_readgo:
+       ld bc, #0x2ffd
+       ld d, #0x20
+       jr fdc_rwait
+;
+;      This would be faster using EXX. May be worth looking at depending
+;      upon clock counts.
+;
+fdc_rbyte:
+       ld b, #0x3F             ; 3FFD
+       ini
+       ld b, #0x2f
+fdc_rwait:                     ; wait for ready
+       in a, (c)
+       jp p, fdc_rwait
+       and d
+       jp nz, fdc_rbyte
+       ; now get status
+       jr fdc_statusa
+
+fdc_write:
+       ld hl, #_rwcmd
+       ld (hl), #65
+       ld b, #9
+       call fdc_cmd            ; Will return with DI
+fdc_writego:
+       ld bc, #0x2ffd
+       ld d, #0x20
+       jr fdc_wwait
+fdc_wbyte:
+       ld b, #0x40
+       outi
+       ld b, #0x2f
+fdc_wwait:
+       in a, (c)
+       jp p, fdc_wwait
+       ; now read status
+       jr fdc_statusa
+
+;
+;      Seek is interesting as we have to second guess the delays
+;
+fdc_seek:
+       ld (_seekcmd + 2), a            ; Cylinder we want
+fdc_seek2:
+       ld b, a
+       ld b, #3
+       call fdc_cmdq
+       ld bc, (_seekcmd + 2)
+       ld a, (curtrack)                ; Current track FIXME
+       sub c
+       bit 7, a
+       jr z, seekin
+       neg
+seekin:                                ; milliseconds of time for the seek
+       add a, a
+       ld b, a
+       add a, a
+       add a, b
+       add a, a                ; 12ms step rate assumed
+       add a, #0x1e            ; head settle
+       call msdelay
+       ; We should now get a sense interrupt
+       call fdc_sense
+       ; Check if it worked
+       bit 6, a
+       jr nz, fdc_seekfail
+       ld a, (_seekcmd + 2)
+       ld (curtrack), a        ; FIXME per drive
+       ret                     ; Z
+fdc_seekfail:
+       ld a, #0xFF
+       ld (curtrack), a        ; Mark as busted
+       ret                     ; NZ
+
+fdc_recalibrate:
+       ld hl, #_recalcmd
+       ld b, #2                ; recalibrate, unit
+       call fdc_cmdq
+       ld a, #80               ; or 40 if 40 track !
+       ld b, #12               ; 12ms
+fdc_recald:
+       ld a, #80               ; or 40 if 40 track !
+       call msdelay
+       djnz fdc_recald
+       ld a, #0x1e             ; settle
+       call msdelay
+       call fdc_status
+       bit 6, a
+       jr nz, fdc_seekfail
+       xor a
+       ld (curtrack), a
+       ret                     ; Z
+
+fdc_sense:
+       ld hl, #sensecmd
+       call fdc_outcmd
+       call fdc_statusa
+       bit 7, a
+       jr nz, fdc_sense
+       ld a, (_fdc_statbuf)
+       ret
+
+fdc_cmdwait:
+       call fdc_sense
+       and #0xC0
+       cp #0x80
+       jr nz, fdc_cmdwait
+       ret
+
+;fdc_getunit:
+;      ld hl, #fdc_guscmd
+;      ld b, #2
+;      call fdc_cmdq
+;      jr fdc_statusa
+
+;
+;      Delay 'a' milliseconds. Trashes A, C.
+;
+msdelay:                       ; For uncontended RAM
+       ld c, #0xDC
+       dec c
+       jr nz, msdelay
+       dec a
+       jr nz, msdelay
+       ret
+       
+
+;
+;      Must be called with the motor timeout stopped
+;
+_fdc_motoron:
+       ld a, (port_map)
+       bit 3, a
+       ret nz
+       or #9
+       ld (port_map), a
+       ld bc, #0x1ffd
+       out (c), a
+_fdc_mwait:
+       ld bc, #0x3548
+       dec bc
+       ld a, b
+       or c
+       jr nz, _fdc_mwait
+       ret
+
+_fdc_motoroff:
+       ld a, (port_map)
+       and #0xF7
+       ld (port_map), a
+       ld bc, #0x1ffd
+       out (c), a
+       ret
+
+_fdc_read:
+       ld hl, (_fdc_addr)
+       ld a, (_fdc_user)
+       or a
+       push af
+       call nz, map_process_always
+       call fdc_read
+       pop af
+       call nz, map_kernel
+       ret
+       
+_fdc_write:
+       ld hl, (_fdc_addr)
+       ld a, (_fdc_user)
+       or a
+       push af
+       call nz, map_process_always
+       call fdc_write
+       pop af
+       call nz, map_kernel
+       ret
+
+_fdc_recal:
+       call    fdc_recalibrate
+       ld      l, a
+       ret
+
+_fdc_seek:
+       call    fdc_seek2
+       ret
+
+       .area _COMMONDATA
+
+curtrack:
+       .byte   0               ; FIXME
+
+_rwcmd:
+       .byte   0x66            ; MFM read / 0x65 for write
+       .byte   0               ; Drive 0, head 0 (for now)
+       .byte   0               ; Track
+       .byte   0               ; Head
+       .byte   0               ; Sector
+       .byte   2               ; 512 byte blocks
+       .byte   0               ; Last sector (== sector)
+       .byte   0x2A            ; Gap length
+       .byte   0xFF
+
+sensecmd:
+       .byte   0x08
+
+_seekcmd:
+       .byte   0x0F
+       .byte   0x00
+       .byte   0x00            ; Cylinder
+
+_recalcmd:
+       .byte   0x07
+       .byte   0
+
+_fdc_user:
+       .byte   0x00
+_fdc_addr:
+       .word   0x0000
+_fdc_statbuf:
+       .word   0,0,0,0
diff --git a/Kernel/platform-plus3/fuzix.lnk b/Kernel/platform-plus3/fuzix.lnk
new file mode 100644 (file)
index 0000000..0430d96
--- /dev/null
@@ -0,0 +1,45 @@
+-mwxuy
+-r
+-i fuzix.ihx
+-b _COMMONDATA=0xF800
+-b _CODE=0x0000
+-b _DISCARD=0xC000
+-l z80
+platform-plus3/crt0.rel
+platform-plus3/commonmem.rel
+platform-plus3/plus3.rel
+platform-plus3/zxvideo.rel
+platform-plus3/main.rel
+start.rel
+version.rel
+lowlevel-z80.rel
+usermem_std-z80.rel
+platform-plus3/tricks.rel
+timer.rel
+kdata.rel
+usermem.rel
+platform-plus3/devices.rel
+platform-plus3/devfd.rel
+platform-plus3/floppy.rel
+devio.rel
+filesys.rel
+process.rel
+inode.rel
+syscall_exec16.rel
+syscall_fs.rel
+syscall_fs2.rel
+syscall_proc.rel
+syscall_other.rel
+tty.rel
+vt.rel
+font8x8.rel
+mm.rel
+simple.rel
+swap.rel
+devsys.rel
+platform-plus3/devtty.rel
+platform-plus3/devide.rel
+platform-plus3/devide_discard.rel
+platform-plus3/mbr.rel
+platform-plus3/blkdev.rel
+-e
diff --git a/Kernel/platform-plus3/kernel.def b/Kernel/platform-plus3/kernel.def
new file mode 100644 (file)
index 0000000..88bc5c4
--- /dev/null
@@ -0,0 +1,10 @@
+; UZI mnemonics for memory addresses etc
+
+U_DATA                      .equ 0xF800       ; (this is struct u_data from kernel.h)
+U_DATA__TOTALSIZE           .equ 0x300        ; 256+256+256 bytes.
+
+Z80_TYPE                   .equ 1
+
+PROGBASE                   .equ 0x0000
+PROGLOAD                   .equ 0x0100
+
diff --git a/Kernel/platform-plus3/main.c b/Kernel/platform-plus3/main.c
new file mode 100644 (file)
index 0000000..37987a7
--- /dev/null
@@ -0,0 +1,27 @@
+#include <kernel.h>
+#include <timer.h>
+#include <kdata.h>
+#include <printf.h>
+#include <devtty.h>
+
+uint16_t ramtop = PROGTOP;
+uint16_t swap_dev;
+
+void platform_idle(void)
+{
+ __asm
+  halt
+ __endasm;
+}
+
+void platform_interrupt(void)
+{
+ tty_pollirq();
+//FIXME floppy_timer();
+ timer_interrupt();
+}
+
+/* Nothing to do for the map of init */
+void map_init(void)
+{
+}
diff --git a/Kernel/platform-plus3/plus3.s b/Kernel/platform-plus3/plus3.s
new file mode 100644 (file)
index 0000000..61f75b9
--- /dev/null
@@ -0,0 +1,235 @@
+;
+;    ZX Spectrum Plus 2A and Plus 3 hardware support
+;
+
+        .module plus3
+
+        ; exported symbols
+        .globl init_early
+        .globl init_hardware
+        .globl _program_vectors
+        .globl platform_interrupt_all
+       .globl interrupt_handler
+       .globl unix_syscall_entry
+       .globl null_handler
+       .globl nmi_handler
+
+        .globl map_kernel
+        .globl map_process
+        .globl map_process_always
+        .globl map_save
+        .globl map_restore
+       .globl map_process_save
+       .globl map_kernel_restore
+       .globl map_video
+       .globl unmap_video
+
+        .globl _kernel_flag
+       .globl port_map
+
+        ; 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 (above 0xC000 in page 3)
+; -----------------------------------------------------------------------------
+            .area _COMMONMEM
+
+_trap_monitor:
+       ;
+       ;       Not so much a monitor as wait for space
+       ;
+       ld a, #0x7F
+       in a, (0xFE)
+       rra
+       jr c, _trap_monitor
+
+_trap_reboot:
+       di
+       im 1
+       ld bc, #0x7ffd
+       xor a           ; 128K ROM, initial banks, low screen
+       out (c), a
+        rst 0          ; Into the ROM
+
+platform_interrupt_all:
+        ret
+
+; -----------------------------------------------------------------------------
+; KERNEL MEMORY BANK (below 0xC000, only accessible when the kernel is mapped)
+; -----------------------------------------------------------------------------
+        .area _CODE
+
+init_early:
+       ld bc, #0x7ffd
+       ld a, #3 + 8              ; Screen into page 7
+        ret
+
+       .area _VIDEO
+
+init_hardware:
+        ; set system RAM size
+        ld hl, #128
+        ld (_ramsize), hl
+        ld hl, #(128 - 64)        ; 64K for kernel/screen/etc
+        ld (_procmem), hl
+
+       call map_video
+        ; screen initialization
+        ; clear
+        ld hl, #0xC000
+        ld de, #0xC001
+        ld bc, #0x1800            ; There should be 0x17FF, but we are going
+        xor a                     ; to copy additional byte to avoid need of
+        ld (hl), a                ; DE and HL increment before attribute
+        ldir                      ; initialization (2 bytes of RAM economy)
+
+        ; set color attributes
+        ld a, #7                ; black paper, white ink
+        ld bc, #0x300 - #1
+        ld (hl), a
+        ldir
+
+       call unmap_video
+        ret
+
+;------------------------------------------------------------------------------
+; COMMON MEMORY PROCEDURES FOLLOW
+
+        .area _COMMONMEM
+
+_program_vectors:
+       di
+       pop de
+       pop hl
+       push hl
+       push de
+
+       call map_process
+
+        ; write zeroes across all vectors
+        ld hl, #0
+        ld de, #1
+        ld bc, #0x007f ; program first 0x80 bytes only
+        ld (hl), #0x00
+        ldir
+
+        ; now install the interrupt vector at 0x0038
+        ld a, #0xC3 ; JP instruction
+        ld (0x0038), a
+        ld hl, #interrupt_handler
+        ld (0x0039), hl
+
+        ; set restart vector for UZI system calls
+        ld (0x0030), a   ;  (rst 30h is unix function call vector)
+        ld hl, #unix_syscall_entry
+        ld (0x0031), hl
+
+        ; Set vector for jump to NULL
+        ld (0x0000), a   
+        ld hl, #null_handler  ;   to Our Trap Handler
+        ld (0x0001), hl
+
+        ld (0x0066), a  ; Set vector for NMI
+        ld hl, #nmi_handler
+        ld (0x0067), hl
+       jr map_kernel
+
+switch_kernel:
+       ld a, (port_map)
+       and #0xF8               ; Preserve the other bits
+       or #0x05                ; Map 4,5,6,3
+switchit:
+       push bc
+       ld (port_map), a
+       ld bc, #0x1FFD
+       out (c), a
+       pop bc
+       ret
+
+switch_user:
+       ld a, (port_map)
+       and #0xF8               ; Preserve the other bits
+       and #0x01               ; Map 0,1,2,3
+       jr switch_kernel
+
+switch_video:
+       ld a, (port_map)
+       or #0x7                 ; Map 4,7,6,3
+       jr switchit
+
+map_process:
+        ld a, h
+        or l
+        jr z, map_kernel
+map_process_save:
+map_process_always:
+       push af
+       call switch_user
+       pop af
+       ret
+
+unmap_video:
+map_kernel:
+map_kernel_restore:
+       push af
+       call switch_kernel
+       pop af
+       ret
+
+map_video:
+       push af
+       call switch_video
+       pop af
+       ret
+
+map_save:
+       push af
+        ld a, (port_map)
+       and #7
+        ld (map_store), a
+       pop af
+        ret
+
+map_restore:
+       push af
+       push hl
+       ld a, (port_map)
+       and #0xF8
+       ld hl, #map_store
+       or (hl)
+       call switchit
+       pop hl
+       pop af
+       ret
+;
+;      We have no easy serial debug output instead just breakpoint this
+;      address when debugging.
+;
+outchar:
+        ret
+
+       .area _COMMONDATA
+_kernel_flag:
+        .db 1
+port_map:                   ; place to store current map register values
+        .db 0               ; because we have no ability to read 1ffd port
+                            ; to detect what page is mapped currently 
+map_store:
+        .db 0
+ksave_map:
+        .db 0
diff --git a/Kernel/platform-plus3/rules.mk b/Kernel/platform-plus3/rules.mk
new file mode 100644 (file)
index 0000000..41bd765
--- /dev/null
@@ -0,0 +1,3 @@
+#
+#      ZXSpectrum +3 uses unbanked kernel images (for now anyway)
+#
diff --git a/Kernel/platform-plus3/sysconfig.h b/Kernel/platform-plus3/sysconfig.h
new file mode 100644 (file)
index 0000000..bdfdc9f
--- /dev/null
@@ -0,0 +1,8 @@
+#ifndef _SYSCONFIG_H
+#define _SYSCONFIG_H
+
+extern uint16_t sysconfig;
+
+#define CONF_PLUS3             0x0001          /* Plus3 (has 3DOS) */
+
+#endif
\ No newline at end of file
diff --git a/Kernel/platform-plus3/target.mk b/Kernel/platform-plus3/target.mk
new file mode 100644 (file)
index 0000000..3bffcde
--- /dev/null
@@ -0,0 +1 @@
+export CPU = z80
diff --git a/Kernel/platform-plus3/tricks.s b/Kernel/platform-plus3/tricks.s
new file mode 100644 (file)
index 0000000..4744ac1
--- /dev/null
@@ -0,0 +1,226 @@
+        .module tricks
+
+;
+;      Standardised implementation of tricks.s for pure swapping Z80
+;      systems. Wants moving from here to lib/
+;
+;      FIXME: All of this can be moved into CODE ?
+;
+        .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 _swapper
+       .globl _swapout
+
+        ; 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
+
+        ; 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
+
+       push de
+        ld hl, #P_TAB__P_PAGE_OFFSET
+       add hl, de      ; process ptr
+       pop de
+
+        ld a, (hl)
+
+       or a
+       jr nz, not_swapped
+
+       ;
+       ;       We are still on the departing processes stack, which is
+       ;       fine for now.
+       ;
+       ld sp, #_swapstack
+       push hl
+       ; We will always swap out the current process
+       ld hl, (U_DATA__U_PTAB)
+       push hl
+       call _swapout
+       pop hl
+       pop hl
+       push de
+       call _swapper
+       pop de
+       pop hl
+       ld a, (hl)
+
+not_swapped:        
+        ; 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.
+
+       ld hl, (U_DATA__U_PTAB)
+       push hl
+       call _swapout
+       pop hl
+
+        ; 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:
diff --git a/Kernel/platform-plus3/zxvideo.s b/Kernel/platform-plus3/zxvideo.s
new file mode 100644 (file)
index 0000000..8e25a03
--- /dev/null
@@ -0,0 +1,289 @@
+;
+;       zx128 vt primitives
+;
+;      We load this high on the Plus3 so we can page in page 7 for the
+;      video overlay and frame buffer space. Any font will also go in page
+;      7. These routines must not call any code outside of common and their
+;      own bank.
+;
+
+        .module zxvideo
+
+        ; 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
+
+        .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 #0xC0           ; not 0x40 as in screen 7
+        ld d,a
+        ret
+
+_plot_char:
+       pop iy
+        pop hl
+        pop de              ; D = x E = y
+        pop bc
+        push bc
+        push de
+        push hl
+       push iy
+
+        call videopos
+
+       ;
+       ;       TODO: Map char 0x60 to a grave accent bitmap rather
+       ;       than fudging with a quote
+       ;
+
+        ld b, #0            ; calculating offset in font table
+        ld a, c
+       cp #0x60
+       jr nz, nofiddle
+       ld a, #0x27
+nofiddle:
+       or a                ; clear carry
+        rla
+        rl b
+        rla
+        rl b
+        rla
+        rl b
+        ld c, a
+
+        ld hl, #0x3C00     ; ROM font
+        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 bc
+        pop hl
+        pop de              ; E = line, D = count
+        push de
+        push hl
+       push bc
+
+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 iy
+        pop hl
+        pop de              ; DE = coords 
+        pop bc              ; C = count
+        push bc
+        push de
+        push hl
+       push iy
+        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,22), DE = (0, 23)
+        xor a
+        ld d, a
+        ld h, a
+        ld l, #22
+        ld e, #23
+        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
+
+        dec l
+        dec e
+        dec c
+        jr nz, loop_scroll_down
+
+        ret
+
+
+_scroll_up:
+        ; set HL = (0,1), DE = (0, 0)
+        xor a
+        ld d, a
+        ld e, a
+        ld h, a
+        ld l, #1
+        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
+
+        inc l
+        inc e
+        dec c
+        jr nz, loop_scroll_up
+
+        ret
+
+_cursor_on:
+       pop bc
+        pop hl
+        pop de
+        push de
+        push hl
+       push bc
+        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
+
+;
+;      Must live in video or common so we are sure we can get at it with
+;      page 7 mapped
+;
+cursorpos:
+        .dw 0