--- /dev/null
+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
--- /dev/null
+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
+
--- /dev/null
+/*
+ * 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
+}
--- /dev/null
+;
+; 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"
--- /dev/null
+#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()
--- /dev/null
+ .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
--- /dev/null
+#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
+}
--- /dev/null
+#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;
--- /dev/null
+#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
--- /dev/null
+#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)
+{
+}
+
--- /dev/null
+#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;
+}
--- /dev/null
+#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;
+}
+
--- /dev/null
+-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
--- /dev/null
+; 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
+
--- /dev/null
+;
+; 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
--- /dev/null
+;
+; 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
--- /dev/null
+#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
--- /dev/null
+/*
+ * 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
--- /dev/null
+#
+# 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
--- /dev/null
+export CPU = z80
--- /dev/null
+; 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
+
--- /dev/null
+;
+; 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
--- /dev/null
+;
+; 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