zxdev: second attempt at exploring a spectrum port
authorAlan Cox <alan@linux.intel.com>
Wed, 21 Nov 2018 00:34:10 +0000 (00:34 +0000)
committerAlan Cox <alan@linux.intel.com>
Wed, 21 Nov 2018 00:34:10 +0000 (00:34 +0000)
This time assume we are going to work with interfaces that can put us in the
low 16K. That is true for a lot of disk interfaces and also for many of the
Russian clones so they could run CP/M.

That gives us a much saner memory map to work from.

We get part way into boot - need to fix up the memory mappings and udata
to make further progress, then debug the DivIDE disk interface logic.

The loader works though

23 files changed:
Kernel/platform-zxdiv/Makefile [new file with mode: 0644]
Kernel/platform-zxdiv/README [new file with mode: 0644]
Kernel/platform-zxdiv/bank128.c [new file with mode: 0644]
Kernel/platform-zxdiv/commonmem.s [new file with mode: 0644]
Kernel/platform-zxdiv/config.h [new file with mode: 0644]
Kernel/platform-zxdiv/crt0.s [new file with mode: 0644]
Kernel/platform-zxdiv/devices.c [new file with mode: 0644]
Kernel/platform-zxdiv/devtty.c [new file with mode: 0644]
Kernel/platform-zxdiv/devtty.h [new file with mode: 0644]
Kernel/platform-zxdiv/discard.c [new file with mode: 0644]
Kernel/platform-zxdiv/divide.c [new file with mode: 0644]
Kernel/platform-zxdiv/divmmc.c [new file with mode: 0644]
Kernel/platform-zxdiv/fuzix.lnk [new file with mode: 0644]
Kernel/platform-zxdiv/kernel.def [new file with mode: 0644]
Kernel/platform-zxdiv/loader-divide.s [new file with mode: 0644]
Kernel/platform-zxdiv/loader.s [new file with mode: 0644]
Kernel/platform-zxdiv/main.c [new file with mode: 0644]
Kernel/platform-zxdiv/platform_ide.h [new file with mode: 0644]
Kernel/platform-zxdiv/rules.mk [new file with mode: 0644]
Kernel/platform-zxdiv/target.mk [new file with mode: 0644]
Kernel/platform-zxdiv/tricks.s [new file with mode: 0644]
Kernel/platform-zxdiv/zx128.s [new file with mode: 0644]
Kernel/platform-zxdiv/zxvideo.s [new file with mode: 0644]

diff --git a/Kernel/platform-zxdiv/Makefile b/Kernel/platform-zxdiv/Makefile
new file mode 100644 (file)
index 0000000..2d79c7a
--- /dev/null
@@ -0,0 +1,48 @@
+CSRCS = devtty.c devices.c main.c bank128.c divide.c divmmc.c
+CDSRCS = discard.c
+DSRCS = ../dev/devide.c ../dev/devsd.c ../dev/blkdev.c
+DDSRCS = ../dev/devide_discard.c ../dev/devsd_discard.c ../dev/mbr.c
+ASRCS = crt0.s zx128.s zxvideo.s
+ASRCS += tricks.s commonmem.s loader-divide.s
+
+COBJS = $(CSRCS:.c=.rel)
+CDOBJS = $(CDSRCS:.c=.rel)
+AOBJS = $(ASRCS:.s=.rel)
+DOBJS = $(patsubst ../dev/%.c,%.rel, $(DSRCS))
+DDOBJS = $(patsubst ../dev/%.c,%.rel, $(DDSRCS))
+OBJS  = $(COBJS) $(CDOBJS) $(AOBJS) $(DOBJS) $(DDOBJS)
+
+CROSS_CCOPTS += -I../dev/
+
+CROSS_CC_SEG3 = --codeseg CODE3
+
+all:   $(OBJS)
+
+$(COBJS): %.rel: %.c
+       $(CROSS_CC) $(CROSS_CCOPTS) $(CROSS_CC_SEG3) -c $<
+
+$(CDOBJS): %.rel: %.c
+       $(CROSS_CC) $(CROSS_CCOPTS) $(CROSS_CC_SEGDISC) -c $<
+
+$(DOBJS): %.rel: ../dev/%.c
+       $(CROSS_CC) $(CROSS_CCOPTS) $(CROSS_CC_SEG3) -c $<
+
+$(DDOBJS): %.rel: ../dev/%.c
+       $(CROSS_CC) $(CROSS_CCOPTS) $(CROSS_CC_SEGDISC) -c $<
+
+$(AOBJS): %.rel: %.s
+       $(CROSS_AS) $(ASOPTS) $<
+
+clean:
+       rm -f $(OBJS) *.lst *.asm *.sym *.rst *.rel core *~ 
+
+# Re-order the image and snapshop it
+image:
+       # The bootstrap to con Fatware
+       dd if=../common.bin of=BOOT.BIN bs=8192 count=1
+       # Rest of the base image, starts at 0x2200, pad it
+       dd if=../common.bin bs=65536 count=1 conv=sync | \
+               dd of=strap.bin bs=512 skip=17
+       # Banked segments 1 and 7
+       dd if=../bank2.bin bs=16384 skip=3 >>strap.bin conv=sync
+       dd if=../bank3.bin bs=16384 skip=3 >>strap.bin conv=sync
diff --git a/Kernel/platform-zxdiv/README b/Kernel/platform-zxdiv/README
new file mode 100644 (file)
index 0000000..578db85
--- /dev/null
@@ -0,0 +1,157 @@
+Experimental porting work for ZX Spectrum 128K systems that have the ability
+to somehow get the low 16K mapped with our code. That makes it a lot more
+practical and a lot of interfaces let us do this, plus a lot of the clones
+support putting RAM low for CP/M.
+
+----
+
+
+
+Fuzix for the 128K Spectrum and clones with DivIDE or DivMMC mapping
+functions. These machines all have the same basic problem, there is a 16K
+window at C000-FFFF which is pageable but no big paging.
+
+For the 128K spectrum we get 8 pages of 16K, all can be stuffed into the top
+16K but some are also wired to other things or hardwired elsewhere.
+
+DivIDE and DIVMMC we do
+0xE3:  7: conmem 6: mapram 0-5: bank
+       (may only be 4 banks)
+
+       7 set ROM at 0000-1FFF and RAM at 2000-3FFF (banked)
+       and pins it (overrides 6)
+       6 is a one shot and write protects bank 3 and places it at
+       0000-1FFF, 
+       5-0: are the bank at 2000-3FFF
+
+To make our life miserable however the boot firmware loads stuff into page 3
+switches to it and locks it. Fortunately it turns out we can trojan Fatware
+0.14 as it expects a \fatware\boot.bin and providing it is 8K will just use it.
+This means we still have to have a fat partition but it'll do for now to get
+us going because it'll blindly load our loader into page 3, switch it to 0
+and then run it.
+
+We run with the following mapping
+
+0000-1FFF      IRQ vectors and boot loader
+2000-3FFF      RAM (switchable): Common, const, commondata etc
+               (we have 2 spare banks here but it's not clear what use they
+               actually are).
+
+4000-7FFF      Kernel data continued (lots of space) 
+
+8000-BFFF      _DISCARD area - blown away when we exec init
+
+C000-FFFF
+       0:      Kernel CODE (fairly full)
+       1:      Kernel CODE2 (fairly full)
+       2:      Mapped at 0x8000-0xBFFF (exchanged with user process buffer)
+       3:      User process
+       4:      User process
+       5:      Mapped at 0x4000-0x7FFF (Kernel data/common)
+       6:      User process (holds the 0x8000-BFFF of the other task)
+       7:      CODE3, Display + Video (some room)
+
+We can move some stuff down into the low 16K (especially once we move the
+common space) We do need to do sort things out a bit and stuff commonmem etc
+in the low 8K to get more room.
+
+User processes live in 2/3 or 2/4. Because we can't swap the lower 16K
+around we exchange it with bank 6 as we go.
+
+To Do:
+
+-      Get the DivIDE interface working
+-      Review DivIDE writes - is the port aliasing safe with a full
+       otir or do we need to avoid port aliasing ?
+-      Check the swap hooks are ok
+-      See what we can get below 0x2000
+-      Set video space in CODE3 bank to 0 not 0xFF for neatness
+-      Look at what is needed for other suitable interfaces. DIVMMC would
+       in particular be good to support but that means another harder
+       bootloader hack
+-      6 or 5bit wide fonts (42, 51 column)
+-      Kempston joystick and mouse
+-      Many of the later Russian clones support putting bank 0 in the low
+       16K, and more than 8 banks. That would need some changes to the bank
+       logic and some interesting detection code. Also they have rather
+       different IDE mappings (SMUC on the Scorpion for example)
+-      Later DivIDE and DivMMC support both a 16K page mode and a lot more
+       RAM (up to 512K). That at the very least would make a kick ass
+       ramdisc/swap device even if the unit lacks the allram switch
+-      Speccyboot (ENC28J60)
+-      SpectraNet (W5100)
+
+
+
+Devices To Look At:
+
+DIVIDE:                These have at least 32K of 8K banks. They are awkward to use
+DIVMMC:                however because you cannot set all of it to RAM and r/w
+               Big ones have up to 512K of RAM (idea for ramdisk), some
+               even battery backup and 16K mode.
+ZXATASP:       PPIDE for spectrum with a 16K low memory paging thing ?
+ZXCF:          Similar CF adapter, 64 banks of 16K write protectable
+               memory in 0000-3FFF
+ZXMMC:         Appears to have no RAM attached so not much use
+Simple IDE:    Same problem but can often co-exist with others.
+SpectraNet:    Diskless Fuzix 8)
+
+Floppy disk interfaces are problematic. The standard Betadisk interface locks
+the I/O ports to its ROM being active, which sucks. The Disciple might be
+doable, need to check the locking and ports.
+
+To set up
+
+make
+
+Set up the HDF and partition it so you've got the 2048 boot sectors from
+a modern fdisk
+Mark partition 1 FAT and bootable
+hdfmonkey put my.hdf BOOT.BIN /fatware/BOOT.BIN
+dd if=strap.bin of=my.hdf bs=1046 seek=1 conv=notrunc
+
+and once it actually gets far enough we can stick the Fuzix partitions on
+partion 2+ along with swap
+
+
+Other option to investigate - unbanked mode
+
+0000-1FFF      Kernel commonmem and writables plus bootstrap
+2000-7FFF      Kernel code/data
+1:C000-FFFF    Kernel code/data
+7:C000-FFFF    Kernel buffers/video RAM
+
+8000-FFFF      User space
+
+That would give us a straight 48K for the kernel, 32K user spaces
+and page mappings of
+
+0:     ??? (Russian clones low 16K RAM), maybe a networking/driver helper bank ?
+1:     Kernel code/data
+2:     8000-BFFF hardwired
+3:     User high
+4:     User high
+5:     4000-7FFF hardwired (Kernel code/data)
+6:     Exchange with 2
+7:     Kernel buffers/video/font code
+
+Other question: does this mean we need two builds
+
+1.     Classic low memory divmmc etc - 8000-FFFF user
+       as above
+
+2.     Big DivMMC/ZXCF etc where there is a 16K bank mode and more
+       RAM in the DivMMC than the system
+
+       0000-3FFF: user / kernel common
+       4000-7FFF: user (copied)
+       8000-BFFF: kernel fixed
+       C000-FFFF: kernel and video and some more user pages for copying
+
+Eg with a 512K DivIDE that would give us over 32K pages so 16 processes without
+hassle.
+
+Could we jump up and down on it enough to get user to be a bit bigger than 32K
+and if so where and how do we handle the extra
+
diff --git a/Kernel/platform-zxdiv/bank128.c b/Kernel/platform-zxdiv/bank128.c
new file mode 100644 (file)
index 0000000..6685402
--- /dev/null
@@ -0,0 +1,166 @@
+/*
+ *     The ZX Spectrum 128 has 8 banks all of which can appear in the top 16K
+ *     but two of whom are permanently wired to appear at 0x4000 and 0x8000.
+ *
+ *     The Russian clones (Pentagon, Scorpion etc) generally also have a way
+ *     to place bank 0 in 0000-3FFF
+ *
+ *     There is no way to switch the 4000-BFFF space so we have to block
+ *     copy stuff around (gak).
+ *
+ *     For now we just handle the 128K spectrum. The pages are allocated as
+ *     follows
+ *
+ *     0 Kernel code bank 1 at C000-FFFF       (will need to change for
+ *                                              pentagon/scorpion)
+ *     1 Kernel code bank 2 at C000-FFFF
+ *     2 Hardwired at 0x8000-0xBFFF (User space)
+ *     3 User space top 16K for process
+ *     4 User space too 16K for other process
+ *     5 Hardwired at 0x4000-0x7FFF (main screen - we don't use) Kernel data
+ *     6 Exchanged with page 2 on task switches
+ *     7 Hardwired for shadow screen (the one we use) at C000 for 6912 bytes
+ *     We use this for video and code bank 3 at C000-FFFF 
+ *
+ *     The Pentagon goes up to 1MB, all in top 16K banks. Page 0 can be
+ *     assigned to the low 16K space. The Scorpion is similar but they don't
+ *     quite agree on which bits do what.
+ *
+ *     The current code just hands out 4 and 3 and relies upon knowing that
+ *     you always exchange 6 and 2. That won't work once we expand to the
+ *     better systems.
+ *
+ *     To confuse things further RAM banks 1/3/5/7 are slower (it's not quite
+ *     the same on the clones). We might want to juggle a few things around
+ *     for best speed.
+ */
+
+#include <kernel.h>
+#include <kdata.h>
+#include <printf.h>
+
+#define DEBUG
+
+/* Kernel is 0, apps are 4 and 3 (top 16K). The live one also has 2 and
+   the other has 6 */
+static unsigned char pfree[MAX_MAPS] = { 4, 3 };
+static unsigned char pfptr = 2;
+
+extern ptptr low_bank;
+extern void dup_low_page(void);
+
+void pagemap_free(ptptr p)
+{
+       if (p->p_page == 0)
+               panic("free0");
+       pfree[pfptr++] = p->p_page;
+       /* Have we dropped the low page owner, if so low page is free */
+       if (low_bank == p)
+               low_bank = NULL;
+}
+
+int pagemap_alloc(ptptr p)
+{
+#ifdef SWAPDEV
+       if (pfptr == 0)
+               swapneeded(p, 1);
+#endif
+       if (pfptr == 0)
+               return ENOMEM;
+       p->p_page = pfree[--pfptr];
+       return 0;
+}
+
+/* Realloc is trivial - we can't do anything useful */
+int pagemap_realloc(usize_t code, usize_t size, usize_t stack)
+{
+       if (size > MAP_SIZE)
+               return ENOMEM;
+       return 0;
+}
+
+uint16_t pagemap_mem_used(void)
+{
+       return (2 - pfptr) * 32;
+}
+
+/*
+ *     Swap out the memory of a process to make room
+ *     for something else.
+ */
+int swapout(ptptr p)
+{
+#ifndef SWAPDEV
+       p; /* to shut the compiler */
+#else
+       uint16_t blk;
+       uint16_t map;
+
+       if (!p->p_page)
+               panic("process already swapped!\n");
+#ifdef DEBUG
+       kprintf("Swapping out %x (%d)\n", p, p->p_page);
+#endif
+
+       /* We mever swap the live process so the second page is always
+          page 6 */
+        if (low_bank == p)
+               panic("swapout");
+
+       /* Are we out of swap ? */
+       map = swapmap_alloc();
+       if (map == 0)
+               return ENOMEM;
+       blk = map * SWAP_SIZE;
+       /* Write the top page and the included uarea stash to disk */
+       swapwrite(SWAPDEV, blk, 0x4000, 0xC000, p->p_page);
+       /* Write the alt bank via the top 16K window */
+       swapwrite(SWAPDEV, blk + 0x20, 0x4000, 0xC000, 6);
+       /* Release the mapping */
+       pagemap_free(p);
+       p->p_page = 0;
+       p->p_page2 = map;
+#ifdef DEBUG
+       kprintf("%x: swapout done %d\n", p, p->p_page);
+#endif
+#endif
+       return 0;
+}
+
+/*
+ * Swap ourself in: must be on the swap stack when we do this. We always
+ * do the swap in to the non exchange bank, as we know we will be running
+ * next.
+ */
+void swapin(ptptr p, uint16_t map)
+{
+#ifndef SWAPDEV
+       p;map; /* to shut the compiler */
+#else
+       uint16_t blk = map * SWAP_SIZE;
+
+#ifdef DEBUG
+       kprintf("Swapin %x, %d\n", p, p->p_page);
+#endif
+       if (!p->p_page) {
+               kprintf("%x: nopage!\n", p);
+               return;
+       }
+       /* Does another process own the low page. If so we need to
+          copy it over (we don't want to load and exchange as the
+          exchange is slower than a copy then loading */
+       if (low_bank != NULL) {
+               kprintf("Low page owned by %x\n", low_bank);
+               dup_low_page();
+       }
+       /* Load the upper 16K back in including the udata cache */
+       swapread(SWAPDEV, blk, 0x4000, 0xC000, p->p_page);
+       /* Load the lower 16K either into the main lower bank (page 2) */
+       swapread(SWAPDEV, blk + 0x20, 0x4000, 0xC000, 2);
+       /* We now own the low page */
+       low_bank = p;
+#ifdef DEBUG
+       kprintf("%x: swapin done %d\n", p, p->p_page);
+#endif
+#endif
+}
diff --git a/Kernel/platform-zxdiv/commonmem.s b/Kernel/platform-zxdiv/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-zxdiv/config.h b/Kernel/platform-zxdiv/config.h
new file mode 100644 (file)
index 0000000..dd050ee
--- /dev/null
@@ -0,0 +1,71 @@
+#define CONFIG_LEVEL_0
+
+#define CONFIG_IDE
+#define CONFIG_SD
+#define SD_DRIVE_COUNT 1               /* For the moment */
+
+/* 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 */
+#define CONFIG_MULTI
+/* Single tasking */
+#undef CONFIG_SINGLETASK
+/* CP/M emulation */
+#undef CONFIG_CPM_EMU
+
+/* Video terminal, not a serial tty */
+#define CONFIG_VT
+/* Keyboard contains not ascii symbols */
+#define CONFIG_UNIKEY
+#define CONFIG_FONT8X8
+#define CONFIG_FONT8X8SMALL
+
+/* Custom banking */
+
+/* We have two mappings from our 128K of memory */
+#define MAX_MAPS       2
+#define MAP_SIZE       0x8000U
+
+/* 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    0x8000  /* also data base */
+#define PROGLOAD    0x8000  /* also data base */
+#define PROGTOP     0xFC00  /* Top of program, base of U_DATA copy */
+#define PROC_SIZE   32   /* 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  2051 */ /* Microdrive 3 : FIXME - configure and probe */
+#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 */
+
+#define SWAPBASE 0x8000
+#define SWAPTOP  0x10000UL
+#define SWAP_SIZE 0x40
+#define MAX_SWAPS 3            /* For now */
+
+/* All our pages get mapped into the top 16K bank for swapping use */
+#define swap_map(x)            ((uint8_t *)(x|0xC000))
+
+#define platform_discard()
diff --git a/Kernel/platform-zxdiv/crt0.s b/Kernel/platform-zxdiv/crt0.s
new file mode 100644 (file)
index 0000000..77f2d98
--- /dev/null
@@ -0,0 +1,121 @@
+        .module crt0
+       ;
+       ;       Our common lives low
+       ;
+        .area _COMMONMEM
+       .area _STUBS
+        .area _CONST
+        .area _INITIALIZER
+       ;
+       ;       The writeables cannot start until 0x2000 but for simplicity
+       ;       we just start at 0x2000 for now, otherwise we have to fight
+       ;       the crappy loaders
+       ;
+       .area _COMMONDATA
+        .area _INITIALIZED
+       ;
+       ;       We move INITIALIZER into INITIALIZED at preparation time
+       ;       then pack COMMONMEM.. end of INITIALIZED after DISCARD
+       ;       in the load image. Beyond that point we just zero.
+       ;
+        .area _DATA
+        .area _BSEG
+        .area _BSS
+        .area _HEAP
+        .area _GSINIT
+        .area _GSFINAL
+       .area _FONT
+       ;
+       ;       All our code is banked at 0xC000
+       ;
+        .area _CODE
+       .area _CODE2
+       ;
+       ; Code3 sits above the display area along with the font and video
+       ; code so that they can access the display easily. It lives at
+       ; 0xDB00 therefore
+       ;
+       .area _CODE3
+        .area _VIDEO
+
+       ; FIXME: We should switch to the ROM font and an ascii remap ?
+        .area _FONT
+       ; Discard is dumped in at 0x8000 and will be blown away later.
+        .area _DISCARD
+
+        ; imported symbols
+        .globl _fuzix_main
+        .globl init_early
+        .globl init_hardware
+       .globl l__DATA
+       .globl s__DATA
+        .globl kstack_top
+
+        .globl unix_syscall_entry
+        .globl nmi_handler
+        .globl interrupt_handler
+
+       .include "kernel.def"
+
+        ; startup code
+       ;
+       ; This is all a bit weird. In order to stop fatware and friends
+       ; screwing life up we load our lowest 8K as the boot.bin on a
+       ; FAT volume. It's a horrible hack but will do for the moment.
+       ; as it ensures we can get the low 8K correct. Otherwise the
+       ; standard firmware locks it and we are screwed.
+       ;
+       ; On entry therefore the low 8K is correctly valid and has loaded
+       ; the kernel image for us from blocks 1+ and all is good.
+       ;
+
+        .area _CODE
+
+       .globl _go
+
+_go:
+
+        di
+
+       ;  We need to wipe the BSS but the rest of the job is done.
+
+       ld hl, #s__DATA
+       ld de, #s__DATA+1
+       ld bc, #l__DATA-1
+       ld (hl), #0
+       ldir
+
+        ld sp, #kstack_top
+
+        ; Configure memory map
+       push af
+        call init_early
+       pop af
+
+        ; Hardware setup
+       push af
+        call init_hardware
+       pop af
+
+        ; Call the C main routine
+       push af
+        call _fuzix_main
+       pop af
+    
+        ; main shouldn't return, but if it does...
+        di
+stop:   halt
+        jr stop
+
+       ; Boot marker at 0x2200
+
+       .area _COMMONMEM
+       .globl _marker
+_marker:
+       .byte 'Z'               ; marker
+       .byte 'B'
+       .word _go               ; boot addresss
+
+       .area _STUBS
+stubs:
+       .ds 768
diff --git a/Kernel/platform-zxdiv/devices.c b/Kernel/platform-zxdiv/devices.c
new file mode 100644 (file)
index 0000000..de6f9ca
--- /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 <devide.h>
+#include <devsd.h>
+#include <blkdev.h>
+
+struct devsw dev_tab[] =  /* The device driver switch table */
+{
+  /* 0: /dev/hd                Hard disc block devices */
+  {  blkdev_open,  no_close,     blkdev_read,   blkdev_write,  blkdev_ioctl },
+  /* 1: /dev/fd        Floppy disc block devices: nope */
+  {  no_open,      no_close,     no_rdwr,  no_rdwr,   no_ioctl },
+  /* 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  },
+  /* 5: 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) - 1)
+       return false;
+    else
+        return true;
+}
+
+void device_init(void)
+{
+#ifdef CONFIG_IDE
+  devide_init();
+#endif
+#ifdef CONFIG_SD
+  devsd_init();
+#endif
+}
diff --git a/Kernel/platform-zxdiv/devtty.c b/Kernel/platform-zxdiv/devtty.c
new file mode 100644 (file)
index 0000000..14e57cc
--- /dev/null
@@ -0,0 +1,204 @@
+#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];
+
+uint8_t vtattr_cap;
+struct vt_repeat keyrepeat;
+static uint8_t kbd_timer;
+
+static tcflag_t console_mask[4] = {
+       _ISYS,
+       _OSYS,
+       _CSYS,
+       _LSYS
+};
+
+tcflag_t *termios_mask[NUM_DEV_TTY + 1] = {
+       NULL,
+       console_mask
+};
+
+/* 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, uint8_t flags)
+{
+       minor;
+}
+
+void tty_sleeping(uint8_t minor)
+{
+       minor;
+}
+
+void tty_data_consumed(uint8_t 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++;
+                                               newkey = 1;
+                                               keybyte = i;
+                                               keybit = n;
+                                       }
+                               }
+                               m >>= 1;
+                       }
+               }
+               keymap[i] = keybuf[i];
+       }
+       if (keysdown && keysdown < 3) {
+               if (newkey) {
+                       keydecode();
+                       kbd_timer = keyrepeat.first;
+               } else if (! --kbd_timer) {
+                       keydecode();
+                       kbd_timer = keyrepeat.continual;
+               }
+       }
+
+}
+
+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'];
+               }
+       }
+       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-zxdiv/devtty.h b/Kernel/platform-zxdiv/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-zxdiv/discard.c b/Kernel/platform-zxdiv/discard.c
new file mode 100644 (file)
index 0000000..1944d62
--- /dev/null
@@ -0,0 +1,31 @@
+#include <kernel.h>
+#include <timer.h>
+#include <kdata.h>
+#include <printf.h>
+#include <devtty.h>
+
+void pagemap_init(void)
+{
+#ifdef SWAPDEV
+  /* Swap */
+  swapmap_init(0);
+  swapmap_init(1);
+  swapmap_init(2);
+#endif
+}
+
+uint8_t platform_param(char *p)
+{
+    used(p);
+    return 0;
+}
+
+/* Nothing to do for the map of init */
+void map_init(void)
+{
+}
+
+void platform_copyright(void)
+{
+}
+
diff --git a/Kernel/platform-zxdiv/divide.c b/Kernel/platform-zxdiv/divide.c
new file mode 100644 (file)
index 0000000..e20ce9e
--- /dev/null
@@ -0,0 +1,65 @@
+#include <kernel.h>
+#include <kdata.h>
+#include <printf.h>
+#include <devide.h>
+#include <blkdev.h>
+#include <platform_ide.h>
+
+/* We have to provide slightly custom methods here because of the banked
+   kernel */
+COMMON_MEMORY
+
+void devide_read_data(void) __naked
+{
+    __asm
+            ld a, (_blk_op+BLKPARAM_IS_USER_OFFSET) ; blkparam.is_user
+            ld hl, (_blk_op+BLKPARAM_ADDR_OFFSET)   ; blkparam.addr
+            ld bc, #IDE_REG_DATA                    ; setup port number
+                                                    ; and count
+            push af
+#ifdef SWAPDEV
+           cp #2
+            jr nz, not_swapin
+            ld a, (_blk_op+BLKPARAM_SWAP_PAGE)     ; blkparam.swap_page
+            call map_for_swap
+            jr doread
+not_swapin:
+#endif
+            or a                                    ; test is_user
+            call nz, map_process_always            ; map user memory first if required
+doread:
+            inir                                    ; transfer first 256 bytes
+            inir                                    ; transfer second 256 bytes
+            pop af
+            or a
+            jp nz, map_kernel_restore               ; else map kernel then return
+            ret
+    __endasm;
+}
+
+void devide_write_data(void) __naked
+{
+    __asm
+            ld a, (_blk_op+BLKPARAM_IS_USER_OFFSET) ; blkparam.is_user
+            ld hl, (_blk_op+BLKPARAM_ADDR_OFFSET)   ; blkparam.addr
+            ld bc, #IDE_REG_DATA                    ; setup port number
+                                                    ; and count
+#ifdef SWAPDEV
+           cp #2
+            jr nz, not_swapout
+            ld a, (_blk_op+BLKPARAM_SWAP_PAGE)     ; blkparam.swap_page
+            call map_for_swap
+            jr dowrite
+not_swapout:
+#endif
+            or a                                    ; test is_user
+            call nz, map_process_always             ; else map user memory first if required
+dowrite:
+            otir                                    ; transfer first 256 bytes
+            otir                                    ; transfer second 256 bytes
+            pop af
+            or a
+            jp nz, map_kernel_restore               ; else map kernel then return
+            ret
+    __endasm;
+}
diff --git a/Kernel/platform-zxdiv/divmmc.c b/Kernel/platform-zxdiv/divmmc.c
new file mode 100644 (file)
index 0000000..5fa9fa5
--- /dev/null
@@ -0,0 +1,73 @@
+#include <kernel.h>
+#include <blkdev.h>
+#include <devsd.h>
+
+__sfr __at 0xE7 divmmc_cs;
+__sfr __at 0xEB divmmc_data;
+
+void sd_spi_raise_cs(void)
+{
+    divmmc_cs = 0x03;          /* Active low */
+}
+
+void sd_spi_transmit_byte(uint8_t b)
+{
+    divmmc_data = b;
+}
+
+uint8_t sd_spi_receive_byte(void)
+{
+    return divmmc_data;
+}
+
+void sd_spi_lower_cs(void)
+{
+    divmmc_data = 0x02;                        /* FIXME we can have a slave */
+}
+
+void sd_spi_clock(bool go_fast)
+{
+}
+
+COMMON_MEMORY
+
+/*
+ * FIXME: swap support
+ *
+ * Could also unroll these a bit for speed
+ */
+
+bool sd_spi_receive_sector(void) __naked
+{
+  __asm
+    ld a, (_blk_op+BLKPARAM_IS_USER_OFFSET)
+    ld hl, (_blk_op+BLKPARAM_ADDR_OFFSET)
+    or a       ; Set the Z flag up and save it, dont do it twice
+    push af
+    call nz,map_process_always
+    ld bc, #0xEB        ; b = 0, c = port
+    inir
+    inir
+    pop af
+    call nz,map_kernel
+    ret
+  __endasm;
+}
+
+bool sd_spi_transmit_sector(void) __naked
+{
+  __asm
+    ld a, (_blk_op+BLKPARAM_IS_USER_OFFSET)
+    ld hl, (_blk_op+BLKPARAM_ADDR_OFFSET)
+    or a       ; Set the Z flag up and save it, dont do it twice
+    push af
+    call nz,map_process_always
+    ld bc, #0xEB
+    otir
+    otir
+    pop af
+    call nz,map_kernel
+    ret
+  __endasm;
+}
+
diff --git a/Kernel/platform-zxdiv/fuzix.lnk b/Kernel/platform-zxdiv/fuzix.lnk
new file mode 100644 (file)
index 0000000..d379a3b
--- /dev/null
@@ -0,0 +1,55 @@
+-mwxuy
+-r
+-i fuzix.ihx
+-b _COMMONMEM=0x2200
+-b _CODE=0xC000
+-b _CODE2=0xC000
+-b _CODE3=0xDB00
+-b _DISCARD=0x8000
+-b BOOT1FEC=0x1FEC
+-b BOOT1FF7=0x1FF7
+-b BOOT1FFE=0x1FFE
+-l z80
+platform-zxdiv/loader-divide.rel
+platform-zxdiv/crt0.rel
+platform-zxdiv/commonmem.rel
+platform-zxdiv/zx128.rel
+platform-zxdiv/zxvideo.rel
+platform-zxdiv/main.rel
+platform-zxdiv/discard.rel
+start.rel
+version.rel
+lowlevel-z80-banked.rel
+usermem_std-z80-banked.rel
+platform-zxdiv/tricks.rel
+timer.rel
+kdata.rel
+usermem.rel
+platform-zxdiv/devices.rel
+devio.rel
+filesys.rel
+process.rel
+inode.rel
+syscall_exec16.rel
+syscall_fs.rel
+syscall_fs2.rel
+syscall_fs3.rel
+syscall_proc.rel
+syscall_other.rel
+tty.rel
+vt.rel
+font8x8.rel
+mm.rel
+platform-zxdiv/bank128.rel
+swap.rel
+devsys.rel
+platform-zxdiv/devtty.rel
+platform-zxdiv/devide.rel
+platform-zxdiv/devide_discard.rel
+platform-zxdiv/devsd.rel
+platform-zxdiv/devsd_discard.rel
+platform-zxdiv/divide.rel
+platform-zxdiv/divmmc.rel
+platform-zxdiv/mbr.rel
+platform-zxdiv/blkdev.rel
+-e
diff --git a/Kernel/platform-zxdiv/kernel.def b/Kernel/platform-zxdiv/kernel.def
new file mode 100644 (file)
index 0000000..24abedc
--- /dev/null
@@ -0,0 +1,13 @@
+; UZI mnemonics for memory addresses etc
+
+; FIXME: THIS IS WRONG - NEED TO SORT OUT MAPPINGS AND DEAL WITH THIS
+U_DATA                      .equ 0x4000       ; (this is struct u_data from kernel.h)
+U_DATA__TOTALSIZE           .equ 0x300        ; 256+256+256 bytes.
+
+U_DATA_STASH               .equ 0xFC00       ; FC00-FEFF
+
+Z80_TYPE                   .equ 1
+
+PROGBASE                   .equ 0x8000
+PROGLOAD                   .equ 0x8000
+
diff --git a/Kernel/platform-zxdiv/loader-divide.s b/Kernel/platform-zxdiv/loader-divide.s
new file mode 100644 (file)
index 0000000..5e27220
--- /dev/null
@@ -0,0 +1,209 @@
+;
+;      DivIDE is a bit of a pain as all the firmware is designed around
+;      ROM BASIC and snapshots. We trick it by providing a fake BOOT.BIN.
+;      Fatware thinks that this is the Fatware code it wants to put in
+;      bank3 and then lock. In fact it's our loader which it will load
+;      and lock for us.
+;
+       .area BOOT      (ABS)
+
+       .globl null_handler
+       .globl unix_syscall_entry
+       .globl interrupt_handler
+
+       .org 0
+
+;
+;      We don't have a JP at 0 as we'd like so our low level code needs
+;      to avoid that check
+;
+loader:
+       di
+       ld a,#0x80
+       out (0xE3),a
+       ; Page the EEPROM in and control transfers there
+       ; not to the jp below
+loader5:
+       ; This is where the EEPROM calls us
+       jp start
+rst_8:
+       .ds 8
+rst_10:
+       .ds 8
+rst_18:
+       .ds 8
+rst_20:
+       .ds 8
+rst_28:
+       .ds 8
+rst_30:
+       jp unix_syscall_entry
+       .ds 5
+rst_38:
+       jp interrupt_handler
+       .ds 0x66-0x3B
+nmi:   ret             ; magic...
+       retn
+
+;
+;      Load and go
+;
+start:
+       di
+       ld bc,#0x7ffd
+
+       ; Map some RAM (must not be RAM2) in 2000-3FFF
+       ld a,#0x40
+       out (0xE3),a
+
+       ; Stack in the first 512 bytes of our data space
+       ld sp,#0x2200
+
+       ; Select the shadow screen bank
+       ld a,#0x07
+       out (c), a
+
+       ; Clear the shadow screen
+       ld hl,#0xc000
+       ld de,#0xc001
+       ld bc,#6144
+       ld (hl),#0
+       ldir
+       ; Attributes to green writing on black
+       ld (hl),#4
+       ld bc,#767
+       ldir
+
+       ; Black border
+       xor a
+       out (254),a
+
+       ; Shadow screen on, bank 0 back at C000-FFFF            
+       ld bc,#0x7ffd
+       ld a,#0x18
+       out (c),a
+
+       ; Screen is now on bank 7
+       ; Memory is on bank 0
+
+       ; Put some RAM in 0x2000-3FFF and keep us locked in the low 8K
+       ; Do not use #0x42, this is magic for allram mode on later DivIDE
+       ld a,#0x40
+       out (0xE3),a
+       ;
+       ;       Ensure the master drive is selected
+       ;
+wait1:
+       in a,(191)
+       rla
+       jr c, wait1
+       ld a,#0xE0
+       out (187),a
+       nop
+wait2:
+       in a,(191)
+       and #0xC0
+       cp #0x40                ; want busy off, drdy
+       jr nz, wait2
+
+       ;
+       ; Load sectors. We shortcut stuff here because we never
+       ; load over 256 sectors
+       ;
+       ;
+       ; We load 112 sectors into 2000-FFFF using bank 0 as the top bank
+       ; We then load 32 sectors into the 16K at C000-FFFF bank 1
+       ; And finally the same for bank 7
+       ; 0x2000 should start ZB then the execution address
+       ;
+       ; For simplicity we don't bother with a stack, we just use IX
+       ;
+       xor a                   ; LBA28 high bits
+       out (179),a
+       out (183),a
+       ld de,#0x6F01           ; Load 111 sectors (2200-FFFF)
+                               ; from sector 1
+       ld hl,#0x2200           ; Starting address to load
+       call load_loop
+
+       ld a,#0x19              ; Select bank 1
+       ld bc,#0x7ffd
+       out (c),a
+       ld d,#0x20              ; Load 32 sectors (C000-FFFF)
+       ld hl,#0xc000
+       call load_loop
+
+       ld a,#0x1F              ; Select bank 7
+       ld bc,#0x7ffd
+       out (c),a
+       ld d,#0x20              ; Load 32 sectors (C000-FFFF)
+       ld hl,#0xC000
+       call load_loop
+
+       ld a,#0x18
+       ld bc,#0x7ffd
+       out (c),a               ; Switch back to bank 0
+       ld hl,#0x2200
+       ; 0x2200 should start with a signature of ZB then the execute
+       ; address
+       ld a,(hl)
+       cp #'Z'
+       jr nz, failed
+       inc hl
+       ld a,(hl)
+       cp #'B'
+       jr nz, failed
+       inc hl
+       ld a,(hl)
+       inc hl
+       ld h,(hl)
+       ld l,a
+       jp (hl)
+
+load_loop:
+       ld a,e
+       inc e
+       out (175),a             ; sector number to load
+       ld a,#1                 ; load one sector
+       out (171),a
+       ld a,#0x20              ; READ
+       out (191),a
+       nop
+wait3:
+       in a, (191)
+       rlca
+       jr c, wait3             ; Busy
+       bit 4,a                 ; DRQ ?
+       jr z, failed            ; Nope - bad
+       ld bc,#163              ; Data port
+       inir
+       inir
+       dec d
+       jr nz, load_loop
+       ret
+
+failed:
+       ld hl,#1
+       jp OutJPHL              ; Into Spectrum ROM and reboot
+
+       .area BOOT1FEC
+
+       ;
+       ;       This has to match the ROM expecations
+       ;
+       out (0xe3),a
+       pop af
+       ret
+
+       .area BOOT1FF7
+
+       ;
+       ; For compatibility with FatWare
+       ;
+OutEI: ei
+OutRet:        ret
+OutJPHL:       jp (hl)
+
+       .area BOOT1FFE
+RomSign:
+       .db 0,14
diff --git a/Kernel/platform-zxdiv/loader.s b/Kernel/platform-zxdiv/loader.s
new file mode 100644 (file)
index 0000000..a866181
--- /dev/null
@@ -0,0 +1,199 @@
+;
+;      DivIDE is a bit of a pain as all the firmware is designed around
+;      ROM BASIC and snapshots. We trick it by providing a fake BOOT.BIN.
+;      Fatware thinks that this is the Fatware code it wants to put in
+;      bank3 and then lock. In fact it's our loader which it will load
+;      and lock for us.
+;
+       .area BOOT      (ABS)
+
+       .globl null_handler
+       .globl unix_syscall_entry
+       .globl interrupt_handler
+
+       .org 0
+
+;
+;      We don't have a JP at 0 as we'd like so our low level code needs
+;      to avoid that check
+;
+loader:
+       di
+       ld a,#0x80
+       out (0xE3),a
+       ; Page the EEPROM in and control transfers there
+       ; not to the jp below
+loader5:
+       ; This is where the EEPROM calls us
+       jp start
+rst_8:
+       .ds 8
+rst_10:
+       .ds 8
+rst_18:
+       .ds 8
+rst_20:
+       .ds 8
+rst_28:
+       .ds 8
+rst_30:
+       jp unix_syscall_entry
+       .ds 5
+rst_38:
+       jp interrupt_handler
+       .ds 0x66-0x3B
+nmi:   ret             ; magic...
+       retn
+
+;
+;      Load and go
+;
+start:
+       ld bc,#0x7ffd
+
+       ; Map some RAM (must not be RAM2) in 2000-3FFF
+       ld a,#0x40
+       out (0xE3),a
+
+       ; Stack in the first 512 bytes of our data space
+       ld sp,#0x2200
+
+       ; Select the shadow screen bank
+       ld a,#0x17
+       out (c), a
+
+       ; Clear the shadow screen
+       ld hl,#0xc000
+       ld de,#0xc001
+       ld bc,#6144
+       ld (hl),#0
+       ldir
+       ; Attributes to green writing on black
+       ld (hl),#4
+       ld bc,#767
+       ldir
+
+       ; Black border
+       xor a
+       out (254),a
+
+       ; Shadow screen on, bank 0 back at C000-FFFF            
+       ld bc,#0x7ffd
+       ld a,#0x18
+       out (c),a
+
+       ; Screen is now on bank 7
+       ; Memory is on bank 0
+
+       ; Put some RAM in 0x2000-3FFF and keep us locked in the low 8K
+       ; Do not use #0x42, this is magic for allram mode on later DivIDE
+       ld a,#0x40
+       out (0xE3),a
+       ;
+       ;       Ensure the master drive is selected
+       ;
+wait1:
+       in a,(191)
+       rla
+       jr c, wait1
+       ld a,#0xE0
+       out (187),a
+       nop
+wait2:
+       in a,(191)
+       and #0xC0
+       cp #0x40                ; want busy off, drdy
+       jr nz, wait2
+
+       ;
+       ; Load sectors. We shortcut stuff here because we never
+       ; load over 256 sectors
+       ;
+       ;
+       ; We load 112 sectors into 2000-FFFF using bank 0 as the top bank
+       ; We then load 32 sectors into the 16K at C000-FFFF bank 1
+       ; And finally the same for bank 7
+       ; 0x2000 should start ZB then the execution address
+       ;
+       ; For simplicity we don't bother with a stack, we just use IX
+       ;
+       xor a                   ; LBA 0
+       out (175),a
+       out (179),a
+       ld de,#0x7001           ; Load 111 sectors (2200-FFFF)
+                               ; from sector 1
+       ld hl,#0x2200           ; Starting address to load
+       call load_loop
+
+       ld a,#0x19              ; Select bank 1
+       ld bc,#0x7ffd
+       ld d,#0x20              ; Load 32 sectors (C000-FFFF)
+       ld hl,#0xc000
+       call load_loop
+
+       ld a,#0x1F              ; Select bank 7
+       ld bc,#0x7ffd
+       out (c),a
+       ld d,#0x20              ; Load 32 sectors (C000-FFFF)
+       ld hl,#0xC000
+       call load_loop
+
+       ld a,#0x18
+       ld bc,#0x7ffd
+       out (c),a               ; Switch back to bank 0
+       ld hl,#0x2200
+       ; 0x2200 should start with a signature of ZB then the execute
+       ; address
+       ld a,(hl)
+       cp #'Z'
+       jr nz, failed
+       inc hl
+       ld a,(hl)
+       cp #'B'
+       jr nz, failed
+       inc hl
+       ld a,(hl)
+       inc hl
+       ld h,(hl)
+       ld l,a
+       jp (hl)
+
+load_loop:
+       ld a,e
+       inc e
+       out (183),a             ; sector number to load
+       ld a,#1                 ; load one sector
+       out (171),a
+       ld a,#0x20              ; READ
+       out (191),a
+       nop
+wait3:
+       in a, (191)
+       rlca
+       jr c, wait3             ; Busy
+       bit 4,a                 ; DRQ ?
+       jr z, failed            ; Nope - bad
+       ld bc,#163              ; Data port
+       inir
+       inir
+       dec d
+       jr nz, load_loop
+       ret
+
+failed:
+       ld hl,#1
+       jp OutJPHL              ; Into Spectrum ROM and reboot
+
+
+       .area BOOT1FF7
+
+       ;
+       ; For compatibility with FatWare
+       ;
+OutEI: ei
+OutRet:        ret
+OutJPHL:       jp (hl)
+
+       .area BOOT1FFE
+RomSign:
+       .db 0,14
diff --git a/Kernel/platform-zxdiv/main.c b/Kernel/platform-zxdiv/main.c
new file mode 100644 (file)
index 0000000..afdacd9
--- /dev/null
@@ -0,0 +1,40 @@
+#include <kernel.h>
+#include <timer.h>
+#include <kdata.h>
+#include <printf.h>
+#include <devtty.h>
+
+uint16_t ramtop = PROGTOP;
+
+/* 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();
+}
+
+size_t strlcpy(char *dst, const char *src, size_t dstsize)
+{
+  size_t len = strlen(src);
+  size_t cp = len >= dstsize ? dstsize - 1 : len;
+  memcpy(dst, src, cp);
+  dst[cp] = 0;
+  return len;
+}
+
+#ifndef SWAPDEV
+/* Adding dummy swapper since it is referenced by tricks.s */
+void swapper(ptptr p)
+{
+  p;
+}
+#endif
\ No newline at end of file
diff --git a/Kernel/platform-zxdiv/platform_ide.h b/Kernel/platform-zxdiv/platform_ide.h
new file mode 100644 (file)
index 0000000..2141fda
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ *     DivIDE interface
+ *
+ *     This is a 16bit interface with a latched data port. Each read
+ *     from A3 fetches a word then returns low then high etc. In the other
+ *     direction it latches then writes.
+ *
+ *     The latch is reset to the first state by any other port access in the
+ *     IDE space (so the command write sets it up nicely for us)
+ */
+
+#define ide_select(x)
+#define ide_deselect()
+
+#define IDE_DRIVE_COUNT        2
+
+#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
+
+#define IDE_NONSTANDARD_XFER
diff --git a/Kernel/platform-zxdiv/rules.mk b/Kernel/platform-zxdiv/rules.mk
new file mode 100644 (file)
index 0000000..a6a6115
--- /dev/null
@@ -0,0 +1,19 @@
+#
+#      ZX128 uses banked kernel images
+#
+CROSS_CCOPTS += --external-banker
+#
+# Tell the core code we are using the banked helpers
+#
+export BANKED=-banked
+#
+export CROSS_CC_SEG1=--codeseg CODE2
+export CROSS_CC_SEG3=--codeseg CODE
+export CROSS_CC_SYS1=--codeseg CODE
+export CROSS_CC_SYS2=--codeseg CODE
+export CROSS_CC_SYS3=--codeseg CODE
+export CROSS_CC_SYS4=--codeseg CODE
+export CROSS_CC_SYS5=--codeseg CODE3
+# The banking default is to put fonts in bank3. We don't want this so build
+# our font as CONST so it lands where we want it
+export CROSS_CC_FONT=--constseg CONST
\ No newline at end of file
diff --git a/Kernel/platform-zxdiv/target.mk b/Kernel/platform-zxdiv/target.mk
new file mode 100644 (file)
index 0000000..3bffcde
--- /dev/null
@@ -0,0 +1 @@
+export CPU = z80
diff --git a/Kernel/platform-zxdiv/tricks.s b/Kernel/platform-zxdiv/tricks.s
new file mode 100644 (file)
index 0000000..dc8b380
--- /dev/null
@@ -0,0 +1,442 @@
+; Based on the Z80 Pack banked code
+;
+; Should unify this somewhat with lib/banked
+;
+
+        .module tricks
+
+        .globl _ptab_alloc
+        .globl _newproc
+        .globl _getproc
+        .globl _platform_monitor
+        .globl trap_illegal
+        .globl _platform_switchout
+        .globl _switchin
+       .globl _low_bank
+       .globl _dup_low_page
+        .globl _dofork
+        .globl _runticks
+        .globl unix_syscall_entry
+        .globl interrupt_handler
+       .globl current_map
+       .globl _ptab
+       .globl _swapper
+       .globl _int_disabled
+
+        ; 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().
+_platform_switchout:
+        di
+        ; 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
+
+       ;
+       ; We are now running on the sleeping process stack. The switchin
+       ; will simply go back to the saved SP above and discard anything
+       ; here
+       ;
+
+       ; Stash the uarea back into process memory
+       ld hl, (U_DATA__U_PAGE)
+       ld a, l
+       ld bc, #0x7ffd
+       or #0x18
+       out (c), a
+
+       ; This includes the stacks, so be careful on restore
+       ld hl, #U_DATA
+       ld de, #U_DATA_STASH
+       ld bc, #U_DATA__TOTALSIZE
+       ldir
+       ld a, (current_map)
+       or #0x18
+       ld bc, #0x7ffd
+       out (c), a
+
+        ; find another process to run (may select this one again)
+       push af
+        call _getproc
+       pop af          ; we can't optimise this as the linker
+                       ; is entitled to patch the 5 bytes here into a
+                       ; banked call
+        push hl
+       push af
+        call _switchin
+
+        ; we should never get here
+        call _platform_monitor
+
+badswitchmsg: .ascii "_switchin: FAIL"
+            .db 13, 10, 0
+
+_switchin:
+        di
+       pop hl  ; far padding
+        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 hl ; far padding
+
+        ld hl, #P_TAB__P_PAGE_OFFSET
+       add hl, de      ; process ptr
+       ;
+       ;       Get ourselves a valid private stack ASAP. We are going to
+       ;       copy udata around and our main stacks are in udata
+       ;
+       ld sp, #_swapstack
+
+        ld a, (hl)     ; 0 swapped, not zero is the bank for C000
+       or a
+       jr nz, not_swapped
+
+       ; Swap the process in (this may swap something else out first)
+       ; The second pushes are C function arguments. SDCC can trample
+       ; these
+
+       push hl         ; Save
+       push de
+       push hl         ; Arguments
+       push de
+       push af
+       call _swapper
+       pop af
+       pop af
+       pop af
+       pop de          ; Restore
+       pop hl
+       ld a, (hl)      ; We should now have a page assigned
+not_swapped:
+       ; We are in DI so we can poke these directly but must not invoke
+       ; any code outside of common
+       or #0x18        ; ROM
+       ; Pages please !
+       ld bc, #0x7ffd
+       out (c), a
+
+       ;       Copy the stash from the user page back down into common
+       ;       The alternate registers are free - we use them for the
+       ;       block copy and for the flipper
+       ;
+       ;       FIXME: Add Tormod's optimisation from the 6809 tree
+       ;
+       exx
+       ld hl, #U_DATA_STASH
+       ld de, #U_DATA
+       ld bc, #U_DATA__TOTALSIZE
+       ldir
+       exx
+
+       ;
+       ;        Remap the kernel proper
+       ;
+
+       ld a, (current_map)
+       or #0x18
+       ld bc, #0x7ffd
+       out (c), a
+        
+       ;       Is our low data in 0x8000 already or do we need to flip
+       ;       it with bank 6. _low_bank holds the page pointer of the
+       ;       task owning the space
+
+       ld hl, (_low_bank)      ; who owns low memory
+       or a
+       sbc hl, de              ; preserve DE
+       jr z, nofliplow         ; skip the flip if we own low bank
+;
+;      Flip low banks over. Preserve DE
+;
+fliplow:
+       exx
+       ld hl, #0x8000
+       ld de, #0xc000
+       ld a, #6 + 0x18
+       ld bc, #0x7ffd
+       out (c), a
+flip2:
+       ld b, #0                ; 256 bytes per outer loop (16K total)
+flip1:
+       ld c, (hl)
+       ld a, (de)
+       ex de, hl
+       ld (hl), c
+       ld (de), a
+       inc hl
+       inc de
+       djnz flip1
+       xor a
+       cp d                    ; Wrapped to 0x0000 ?
+       jr nz, flip1
+
+       ld a, (current_map)
+       or #0x18
+       ld bc, #0x7ffd
+       out (c), a
+       exx
+       ld (_low_bank), de      ; we own it now
+
+nofliplow:
+
+        ; 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 == HL
+        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 any 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)
+
+       ;
+       ; We can now use the stack again
+       ;
+
+        pop iy
+        pop ix
+        pop hl ; return code
+
+        ; enable interrupts, if the ISR isn't already running
+        ld a, (U_DATA__U_ININTERRUPT)
+       ld (_int_disabled),a
+        or a
+        ret nz ; 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 _platform_monitor
+
+; Interrupts should be off when this is called
+_dup_low_page:
+       ld a, #0x06 + 0x18              ;       low page alternate
+       ld bc, #0x7ffd
+       out (c), a
+
+       ld hl, #0x8000                  ;       Fixed
+       ld de, #0xC000                  ;       Page we just mapped in
+       ld bc, #16384
+       ldir
+
+       ld a, (current_map)             ;       restore mapping
+       or #0x18                        ; ROM bits
+       ld bc, #0x7ffd
+       out (c), a
+       ret
+
+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 bc
+        pop de  ; return address
+        pop hl  ; new process p_tab*
+        push hl
+        push de
+       push bc
+
+        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 16K bank copy here, then copy the live uarea
+       ; into the stash of the new process
+
+        ; --------- copy process ---------
+
+        ld hl, (fork_proc_ptr)
+       ld (_low_bank), hl              ;       low bank will become the child
+        ld de, #P_TAB__P_PAGE_OFFSET   ;       bank number
+        add hl, de
+        ; load p_page
+        ld c, (hl)
+       ld hl, (U_DATA__U_PAGE)
+       ld a, l
+
+       ;
+       ; Copy the high page via the bounce buffer
+       ;
+
+       call bankfork                   ;       do the bank to bank copy
+
+       ; FIXME: if we support small apps at C000-FBFF we need to tweak this
+       ; Now copy the 0x8000-0xBFFF area directly
+
+       ld a, #0x06 + 0x18              ;       low page alternate
+       ld bc, #0x7ffd
+       out (c), a
+
+       ld hl, #0x8000                  ;       Fixed
+       ld de, #0xC000                  ;       Page we just mapped in
+       ld bc, #16384
+       ldir
+
+       ; Copy done
+
+       ld a, (U_DATA__U_PAGE)  ; parent memory
+       or #0x18                ; get the right ROMs
+       ld bc, #0x7ffd
+       out (c), a              ; Switch context to parent in 0xC000+
+
+       ; 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
+
+       ;
+       ; And back into the kernel
+       ;
+       ld bc, #0x7ffd
+       ld a, (current_map)
+       or #0x18
+       out (c), 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
+       push af
+        call _newproc
+       pop af
+        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
+
+;
+;      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
+;
+;
+;      Note: this needs reviewing. We now have a lot more program memory
+;      we can use with a lazy copying model
+;
+bankfork:
+       or #0x18                ; ROM bits for the bank
+       ld b, #0x3C             ; 40 x 256 minus 4 sets for the uarea stash/irqs
+       ld hl, #0xC000          ; base of memory to fork (vectors included)
+bankfork_1:
+       push bc                 ; Save our counter and also child offset
+       push hl
+       ld bc, #0x7ffd
+       out (c), 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
+       push bc                 ; save the bank pointers
+       ld bc, #0x7ffd
+       or #0x18                ; ROM bits
+       out (c), a
+       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
+;
+       .area _COMMONDATA
+bouncebuffer:
+       .ds 256
+;      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. We never use both at once
+;      so share with bouncebuffer
+_swapstack:
+_low_bank:
+       .dw _ptab                       ; Init starts owning this
+
diff --git a/Kernel/platform-zxdiv/zx128.s b/Kernel/platform-zxdiv/zx128.s
new file mode 100644 (file)
index 0000000..dd58cf3
--- /dev/null
@@ -0,0 +1,451 @@
+;
+;    ZX Spectrum 128 hardware support
+;
+
+        .module zx128
+
+        ; 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 map_kernel
+        .globl map_process_always
+        .globl map_process
+        .globl map_kernel_di
+        .globl map_process_always_di
+        .globl map_save_kernel
+        .globl map_restore
+       .globl map_process_save
+       .globl map_kernel_restore
+       .globl map_for_swap
+       .globl current_map
+       .globl switch_bank
+
+        .globl _need_resched
+       .globl _int_disabled
+
+        ; exported debugging tools
+        .globl _platform_monitor
+       .globl _platform_reboot
+        .globl outchar
+
+        ; imported symbols
+        .globl _ramsize
+        .globl _procmem
+
+       .globl _vtoutput
+
+        .globl outcharhex
+        .globl outhl, outde, outbc
+        .globl outnewline
+        .globl outstring
+        .globl outstringhex
+
+       ; banking support
+       .globl __bank_0_1
+       .globl __bank_0_2
+       .globl __bank_0_3
+       .globl __bank_1_2
+       .globl __bank_1_3
+       .globl __bank_2_1
+       .globl __bank_2_3
+       .globl __bank_3_1
+       .globl __bank_3_2
+
+       .globl __stub_0_1
+       .globl __stub_0_2
+       .globl __stub_0_3
+       .globl __stub_1_2
+       .globl __stub_1_3
+       .globl __stub_2_1
+       .globl __stub_2_3
+       .globl __stub_3_1
+       .globl __stub_3_2
+
+        .include "kernel.def"
+        .include "../kernel.def"
+
+; -----------------------------------------------------------------------------
+; COMMON MEMORY BANK (below 0xC000)
+; -----------------------------------------------------------------------------
+        .area _COMMONMEM
+
+_platform_monitor:
+       ;
+       ;       Not so much a monitor as wait for space
+       ;
+       ld a, #0x7F
+       in a, (0xFE)
+       rra
+       jr c, _platform_monitor
+
+_platform_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
+
+       .area _COMMONDATA
+
+_int_disabled:
+       .db 1
+
+
+; -----------------------------------------------------------------------------
+; KERNEL MEMORY BANK (above 0xC000, only accessible when the kernel is mapped)
+; -----------------------------------------------------------------------------
+        .area _CODE
+
+;
+;      The memory banker will deal with the map setting
+;
+init_early:
+        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
+
+        ; 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
+
+        ret
+
+;------------------------------------------------------------------------------
+; COMMON MEMORY PROCEDURES FOLLOW
+
+        .area _COMMONMEM
+
+_program_vectors:
+       ret
+
+       ; Swap helper. Map the page in A into the address space such
+       ; that swap_map() gave the correct pointer to use. Undone by
+       ; a map_kernel_{restore}
+map_for_swap:
+        ; bank switching procedure. On entrance:
+        ;  A - bank number to set
+       push af
+       ld a, (current_map)
+       ld (ksave_map), a
+       pop af
+       ; Then fall through to set the bank up
+
+switch_bank:
+       ; Write the store first, that way any interrupt will restore
+       ; the new bank and our out will just be a no-op
+        ld (current_map), a
+       push bc
+        ld bc, #0x7ffd
+       or #0x18           ; Spectrum 48K ROM, Screen in Bank 7
+        out (c), a
+       pop bc
+        ret
+
+
+map_process:
+        ld a, h
+        or l
+        jr z, map_kernel_nosavea
+       push af
+        ld a, (hl)
+       call switch_bank
+       pop af
+       ret
+
+;
+;      We always save here so that existing code works until we have a
+;      clear usage of save/restore forms across the kernel
+;
+map_process_save:
+map_process_always:
+map_process_always_di:
+       push af
+       ld a, (current_map)
+       ld (ksave_map), a
+        ld a, (U_DATA__U_PAGE)
+       call switch_bank
+       pop af
+       ret
+
+;
+;      Save and switch to kernel
+;
+map_save_kernel:
+       push af
+        ld a, (current_map)
+        ld (map_store), a
+       pop af
+;
+;      This may look odd. However the kernel is banked so any
+;      invocation of kernel code in fact runs common code and the
+;      common code will bank in the right kernel bits for us when it calls
+;      out of common into banked code. We do a restore to handle all the
+;      callers who do map_process_always/map_kernel pairs. Probably we
+;      should have some global change to map_process_save/map_kernel_restore
+;
+map_kernel_di:
+map_kernel:
+map_kernel_nosavea:          ; to avoid double reg A saving
+map_kernel_restore:
+       push af
+       ld a, (ksave_map)
+       call switch_bank
+       pop af
+       ret
+
+
+map_restore:
+       push af
+        ld a, (map_store)
+        call switch_bank
+       pop af
+       ret
+
+;
+;      We have no easy serial debug output instead just breakpoint this
+;      address when debugging.
+;
+outchar:
+       ld (_tmpout), a
+       push bc
+       push de
+       push hl
+       push ix
+       ld hl, #1
+       push hl
+       ld hl, #_tmpout
+       push hl
+       push af
+       call _vtoutput
+       pop af
+       pop af
+       pop af
+       pop ix
+       pop hl
+       pop de
+       pop bc
+        ret
+
+       .area _COMMONDATA
+_tmpout:
+       .db 1
+
+current_map:                ; place to store current page number. Is needed
+        .db 0               ; because we have no ability to read 7ffd port
+                            ; to detect what page is mapped currently 
+map_store:
+        .db 0
+
+ksave_map:
+        .db 0
+
+_need_resched:
+        .db 0
+
+       .area _COMMONMEM
+;
+;      Banking helpers
+;
+;      Logical         Physical
+;      0               COMMON (0x4000)
+;      1               0
+;      2               1
+;      3               7
+;
+;
+__bank_0_1:
+       xor a              ; switch to physical bank 0 (logical 1)
+bankina0:
+       ;
+       ;       Get the target address first, otherwise we will change
+       ;       bank and read it from the wrong spot!
+       ;
+       pop hl             ; Return address (points to true function address)
+       ld e, (hl)         ; DE = function to call
+       inc hl
+       ld d, (hl)
+       inc hl
+       push hl            ; Restore corrected return pointer
+       ld bc, (current_map)    ; get current bank into B
+       call switch_bank   ; Move to new bank
+       ; figure out which bank to map on the return path
+       ld a, c
+       or a
+       jr z, __retmap1
+       dec a
+       jr z, __retmap2
+       jr __retmap3
+
+callhl:        jp (hl)
+__bank_0_2:
+       ld a, #1           ; logical 2 -> physical 1
+       jr bankina0
+__bank_0_3:
+       ld a, #7           ; logical 3 -> physical 7
+       jr bankina0
+
+__bank_1_2:
+       ld a, #1
+bankina1:
+       pop hl             ; Return address (points to true function address)
+       ld e, (hl)         ; DE = function to call
+       inc hl
+       ld d, (hl)
+       inc hl
+       push hl            ; Restore corrected return pointer
+       call switch_bank   ; Move to new bank
+__retmap1:
+       ex de, hl
+       call callhl        ; call the function
+       xor a              ; return to bank 1 (physical 0)
+       jp switch_bank
+
+__bank_1_3:
+       ld a, #7
+       jr bankina1
+__bank_2_1:
+       xor a
+bankina2:
+       pop hl             ; Return address (points to true function address)
+       ld e, (hl)         ; DE = function to call
+       inc hl
+       ld d, (hl)
+       inc hl
+       push hl            ; Restore corrected return pointer
+       call switch_bank   ; Move to new bank
+__retmap2:
+       ex de, hl
+       call callhl        ; call the function
+       ld a, #1           ; return to bank 2
+       jp switch_bank
+__bank_2_3:
+       ld a, #7
+       jr bankina2
+__bank_3_1:
+       xor a
+bankina3:
+       pop hl             ; Return address (points to true function address)
+       ld e, (hl)         ; DE = function to call
+       inc hl
+       ld d, (hl)
+       inc hl
+       push hl            ; Restore corrected return pointer
+       call switch_bank   ; Move to new bank
+__retmap3:
+       ex de, hl
+       call callhl        ; call the function
+       ld a, #7           ; return to bank 0
+       jp switch_bank
+
+__bank_3_2:
+       ld a, #1
+       jr bankina3
+
+;
+;      Stubs need some stack munging and use DE
+;
+
+__stub_0_1:
+       xor a
+__stub_0_a:
+       pop hl          ; the return
+       ex (sp), hl     ; write it over the discard
+       ld bc, (current_map)
+       call switch_bank
+       ld a, c
+       or a
+       jr z, __stub_1_ret
+       dec a
+       jr z, __stub_2_ret
+       jr __stub_3_ret
+__stub_0_2:
+       ld a, #1
+       jr __stub_0_a
+__stub_0_3:
+       ld a, #7
+       jr __stub_0_a
+
+__stub_1_2:
+       ld a, #1
+__stub_1_a:
+       pop hl          ; the return
+       ex (sp), hl     ; write it over the discad
+       call switch_bank
+__stub_1_ret:
+       ex de, hl
+       call callhl
+       xor a
+       call switch_bank
+       pop de
+       push de         ; dummy the caller will discard
+       push de         ; FIXME don't we need to use BC and can't we get
+       ret             ; rid of all non 0_x stubs ?
+__stub_1_3:
+       ld a, #7
+       jr __stub_1_a
+
+__stub_2_1:
+       xor a
+__stub_2_a:
+       pop hl          ; the return
+       ex (sp), hl     ; write it over the discad
+       call switch_bank
+__stub_2_ret:
+       ex de, hl       ; DE is our target
+       call callhl
+       ld a,#1
+       call switch_bank
+       pop de
+       push de         ; dummy the caller will discard
+       push de
+       ret
+__stub_2_3:
+       ld a, #7
+       jr __stub_2_a
+
+__stub_3_1:
+       xor a
+__stub_3_a:
+       pop hl          ; the return
+       ex (sp), hl     ; write it over the discad
+       call switch_bank
+__stub_3_ret:
+       ex de, hl
+       call callhl
+       ld a,#7
+       call switch_bank
+       pop de
+       push de         ; dummy the caller will discard
+       push de
+       ret
+__stub_3_2:
+       ld a, #1
+       jr __stub_3_a
diff --git a/Kernel/platform-zxdiv/zxvideo.s b/Kernel/platform-zxdiv/zxvideo.s
new file mode 100644 (file)
index 0000000..fce7baf
--- /dev/null
@@ -0,0 +1,279 @@
+;
+;        zx128 vt primitives
+;
+
+        .module zxvideo
+
+        ; exported symbols
+        .globl _plot_char
+        .globl _scroll_down
+        .globl _scroll_up
+        .globl _cursor_on
+        .globl _cursor_off
+       .globl _cursor_disable
+        .globl _clear_lines
+        .globl _clear_across
+        .globl _do_beep
+       .globl _vtattr_notify
+       .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 #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
+
+        ld b, #0            ; calculating offset in font table
+        ld a, c
+       or a                ; clear carry
+        rla
+        rl b
+        rla
+        rl b
+        rla
+        rl b
+        ld c, a
+
+        ld hl, #_fontdata_8x8-32*8    ; 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
+       push af
+        call _clear_across
+       pop af
+        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_disable:
+_cursor_off:
+        ld de, (cursorpos)
+        call videopos
+        ld a, #7
+        add a, d
+        ld d, a
+        xor a
+        ld (de), a
+_vtattr_notify:
+        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