From: Alan Cox Date: Wed, 21 Nov 2018 00:34:10 +0000 (+0000) Subject: zxdev: second attempt at exploring a spectrum port X-Git-Url: https://git.ndcode.org/public/gitweb.cgi?a=commitdiff_plain;h=65127dbaf56ec770d1bfcfbec200c543651172bf;p=FUZIX.git zxdev: second attempt at exploring a spectrum port 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 --- diff --git a/Kernel/platform-zxdiv/Makefile b/Kernel/platform-zxdiv/Makefile new file mode 100644 index 00000000..2d79c7a0 --- /dev/null +++ b/Kernel/platform-zxdiv/Makefile @@ -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 index 00000000..578db85e --- /dev/null +++ b/Kernel/platform-zxdiv/README @@ -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 index 00000000..66854024 --- /dev/null +++ b/Kernel/platform-zxdiv/bank128.c @@ -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 +#include +#include + +#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 index 00000000..dc31c24e --- /dev/null +++ b/Kernel/platform-zxdiv/commonmem.s @@ -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 index 00000000..dd050eee --- /dev/null +++ b/Kernel/platform-zxdiv/config.h @@ -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 index 00000000..77f2d985 --- /dev/null +++ b/Kernel/platform-zxdiv/crt0.s @@ -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 index 00000000..de6f9cae --- /dev/null +++ b/Kernel/platform-zxdiv/devices.c @@ -0,0 +1,44 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 index 00000000..14e57cc6 --- /dev/null +++ b/Kernel/platform-zxdiv/devtty.c @@ -0,0 +1,204 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +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 index 00000000..039589fd --- /dev/null +++ b/Kernel/platform-zxdiv/devtty.h @@ -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 index 00000000..1944d62d --- /dev/null +++ b/Kernel/platform-zxdiv/discard.c @@ -0,0 +1,31 @@ +#include +#include +#include +#include +#include + +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 index 00000000..e20ce9e8 --- /dev/null +++ b/Kernel/platform-zxdiv/divide.c @@ -0,0 +1,65 @@ +#include +#include +#include +#include +#include +#include + +/* 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 index 00000000..5fa9fa52 --- /dev/null +++ b/Kernel/platform-zxdiv/divmmc.c @@ -0,0 +1,73 @@ +#include +#include +#include + +__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 index 00000000..d379a3be --- /dev/null +++ b/Kernel/platform-zxdiv/fuzix.lnk @@ -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 index 00000000..24abedce --- /dev/null +++ b/Kernel/platform-zxdiv/kernel.def @@ -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 index 00000000..5e272203 --- /dev/null +++ b/Kernel/platform-zxdiv/loader-divide.s @@ -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 index 00000000..a8661819 --- /dev/null +++ b/Kernel/platform-zxdiv/loader.s @@ -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 index 00000000..afdacd9e --- /dev/null +++ b/Kernel/platform-zxdiv/main.c @@ -0,0 +1,40 @@ +#include +#include +#include +#include +#include + +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 index 00000000..2141fda7 --- /dev/null +++ b/Kernel/platform-zxdiv/platform_ide.h @@ -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 index 00000000..a6a61151 --- /dev/null +++ b/Kernel/platform-zxdiv/rules.mk @@ -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 index 00000000..3bffcde0 --- /dev/null +++ b/Kernel/platform-zxdiv/target.mk @@ -0,0 +1 @@ +export CPU = z80 diff --git a/Kernel/platform-zxdiv/tricks.s b/Kernel/platform-zxdiv/tricks.s new file mode 100644 index 00000000..dc8b380b --- /dev/null +++ b/Kernel/platform-zxdiv/tricks.s @@ -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 index 00000000..dd58cf31 --- /dev/null +++ b/Kernel/platform-zxdiv/zx128.s @@ -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 index 00000000..fce7baff --- /dev/null +++ b/Kernel/platform-zxdiv/zxvideo.s @@ -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