From 72d856a37b937926a454a53d37cada0b8af92657 Mon Sep 17 00:00:00 2001 From: Alan Cox Date: Fri, 30 Nov 2018 00:36:59 +0000 Subject: [PATCH] zx+3: ZX spectrum plus 3 port based on the TC2068 and 128K work Not yet working --- Kernel/platform-zx+3/README | 91 ++++++ Kernel/platform-zx+3/commonmem.s | 8 + Kernel/platform-zx+3/config.h | 72 +++++ Kernel/platform-zx+3/crt0.s | 131 ++++++++ Kernel/platform-zx+3/devices.c | 46 +++ Kernel/platform-zx+3/devinput.c | 103 +++++++ Kernel/platform-zx+3/devinput.h | 2 + Kernel/platform-zx+3/devtty.c | 150 ++++++++++ Kernel/platform-zx+3/devtty.h | 19 ++ Kernel/platform-zx+3/discard.c | 43 +++ Kernel/platform-zx+3/fdc765.s | 396 +++++++++++++++++++++++++ Kernel/platform-zx+3/fdc765_platform.h | 5 + Kernel/platform-zx+3/fuzix.lnk | 49 +++ Kernel/platform-zx+3/kernel.def | 14 + Kernel/platform-zx+3/loader.s | 183 ++++++++++++ Kernel/platform-zx+3/main.c | 82 +++++ Kernel/platform-zx+3/platform_ide.h | 29 ++ Kernel/platform-zx+3/plus3.s | 245 +++++++++++++++ Kernel/platform-zx+3/rules.mk | 1 + Kernel/platform-zx+3/target.mk | 1 + Kernel/platform-zx+3/tricks.s | 5 + Kernel/platform-zx+3/zxvideo.s | 28 ++ 22 files changed, 1703 insertions(+) create mode 100644 Kernel/platform-zx+3/README create mode 100644 Kernel/platform-zx+3/commonmem.s create mode 100644 Kernel/platform-zx+3/config.h create mode 100644 Kernel/platform-zx+3/crt0.s create mode 100644 Kernel/platform-zx+3/devices.c create mode 100644 Kernel/platform-zx+3/devinput.c create mode 100644 Kernel/platform-zx+3/devinput.h create mode 100644 Kernel/platform-zx+3/devtty.c create mode 100644 Kernel/platform-zx+3/devtty.h create mode 100644 Kernel/platform-zx+3/discard.c create mode 100644 Kernel/platform-zx+3/fdc765.s create mode 100644 Kernel/platform-zx+3/fdc765_platform.h create mode 100644 Kernel/platform-zx+3/fuzix.lnk create mode 100644 Kernel/platform-zx+3/kernel.def create mode 100644 Kernel/platform-zx+3/loader.s create mode 100644 Kernel/platform-zx+3/main.c create mode 100644 Kernel/platform-zx+3/platform_ide.h create mode 100644 Kernel/platform-zx+3/plus3.s create mode 100644 Kernel/platform-zx+3/rules.mk create mode 100644 Kernel/platform-zx+3/target.mk create mode 100644 Kernel/platform-zx+3/tricks.s create mode 100644 Kernel/platform-zx+3/zxvideo.s diff --git a/Kernel/platform-zx+3/README b/Kernel/platform-zx+3/README new file mode 100644 index 00000000..8ede4b9c --- /dev/null +++ b/Kernel/platform-zx+3/README @@ -0,0 +1,91 @@ +ZX Spectrum +2A/+3 + +The memory mapping on this machine is a bit friendlier but it's still single +(large) process in memory at a time to get the best result. Banked kernel +2 x 32K is also doable but probably not such a good use of the machine + +This port really shows we need to support parent-first single in memory + +Memory Model + +We use +2A/+3 'special paging mode'. It's special, but limited. Sufficient for +a nice one process in memory model however + +One quirk of this box is that banks 4-7 run at an effective 2.5-3Mhz due to +video wait states but 0-3 run the full 3.5. Thus we want to land some things +in 0-3 and should ponder that carefully. + +The plus 3 memory map is fairly simple. + +There are four maps + +0/1/2/3 +4/5/6/7 +4/5/6/3 +4/7/6/3 + +and the video can be at the bottom of 5 or 7 + +As we can't get 5 anywhere convenient video has to go in 7 + +To get started we run with + +User + +0 / 1 / 2 Common 3 + +Kernel + +4 / 5 / 6 Common 3 + +Video (routines in common) + +4 / 7 / 6 Common 3 + +External buffers in 7 eventually (using 4/7/6/3 mapping) + +Annoyingly there is no easy way to do direct video except maybe once we +do the video in loader hint hack then we can flip the screen to 5 and +load an app from 5D00-wherever. Still ugly. + + +TODO + +Write boot block loader and load image (packed) from FDC +Write FDC drivers +Move buffers into upper part of bank 7 to see if can get kernel below +C000 + E000 upwards to get bigger user image ? + +Q for all these machines - if ports in 4000-7FFF range are contended does +it speed up our divide/divmmc code to unroll it and also set b to 0xFF +every 128 so that we never contend + +Eventually consider trying to do thunked single process with C000 sized +user but screen mapped and buffers in rest of 7 ? + +0-3 Kernel +4-7 User, video in 7 + +0/4 hold page0 copies, 3 7 both hold pageh copies - fine as video is +in bottom of that chunk. + +Would give direct mapped video + + +FDC + +Reuse amstradnc code + +How to twiddle tc ?? - do we in fact even have that ability ? looks like +we instead have to discard or stuff bytes if sector size mismatched + +add delays + +Use the jp p and other tricks to make timing on the spectrum +for r/w loops + +Ensure we have the correct head settle on a seek + +motor on needs big delay loop but can be done without di on (yay!) + +motor off is software diff --git a/Kernel/platform-zx+3/commonmem.s b/Kernel/platform-zx+3/commonmem.s new file mode 100644 index 00000000..88b6bba6 --- /dev/null +++ b/Kernel/platform-zx+3/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 _COMMONMEM + + .include "../cpu-z80/std-commonmem.s" diff --git a/Kernel/platform-zx+3/config.h b/Kernel/platform-zx+3/config.h new file mode 100644 index 00000000..5f7ff33e --- /dev/null +++ b/Kernel/platform-zx+3/config.h @@ -0,0 +1,72 @@ +#define CONFIG_IDE +#define CONFIG_LARGE_IO_DIRECT /* We support direct to user I/O */ +/* 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 + +/* Input layer support */ +#define CONFIG_INPUT +#define CONFIG_INPUT_GRABMAX 3 +/* Video terminal, not a serial tty */ +#define CONFIG_VT +/* Keyboard contains non-ascii symbols */ +#define CONFIG_UNIKEY +#define CONFIG_FONT8X8 +#define CONFIG_FONT8X8SMALL + +/* Swap based one process in RAM */ +#define CONFIG_SWAP_ONLY +#define CONFIG_SPLIT_UDATA +#define UDATA_BLKS 1 +#define UDATA_SIZE 0x200 +#define CONFIG_DYNAMIC_BUFPOOL +#define CONFIG_DYNAMIC_SWAP + +/* Custom banking */ + +/* 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 0x0000 /* also data base */ +#define PROGLOAD 0x0100 /* also data base */ +#define PROGTOP 0xC000 /* Top of program, below C000 for simplicity + to get going */ + +#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 NBUFS 5 /* Number of block buffers */ +#define NMOUNTS 4 /* Number of mounts at a time */ +#define MAX_BLKDEV 4 /* 2 IDE drives, 2 SD drive */ + +#define SWAPBASE 0x0000 +#define SWAPTOP 0xC000UL +#define SWAP_SIZE 0x61 /* 48K + udata */ +#define MAX_SWAPS 16 +#define SWAPDEV (swap_dev) /* Device for swapping (dynamic). */ + +/* We swap by hitting the user map */ +#define swap_map(x) ((uint8_t *)(x)) diff --git a/Kernel/platform-zx+3/crt0.s b/Kernel/platform-zx+3/crt0.s new file mode 100644 index 00000000..ca9f2a80 --- /dev/null +++ b/Kernel/platform-zx+3/crt0.s @@ -0,0 +1,131 @@ + .module crt0 + + ; + ; High space - read only + ; + + .area _CODE + .area _CODE2 + ; + ; Tru and keep code in the top 32K + ; + + + ; + ; Our common lives low + ; + .area _CODE3 + .area _VIDEO ; must end below 0x4000 + .area _INITIALIZED + .area _HOME + .area _CONST + + ; + ; Beyond this point we just zero. + ; + + .area _DATA + .area _BSEG + .area _BSS + .area _HEAP + .area _GSINIT + .area _GSFINAL + ; + ; Finally the buffers so they can expand + ; + .area _BUFFERS + + .area _DISCARD + ; Somewhere to throw it out of the way + .area _INITIALIZER + + + .area _COMMONMEM + .area _FONT + + ; imported symbols + .globl _fuzix_main + .globl init_early + .globl init_hardware + .globl l__BUFFERS + .globl s__BUFFERS + .globl l__COMMONMEM + .globl s__COMMONMEM + .globl l__DATA + .globl s__DATA + .globl l__DISCARD + .globl s__DISCARD + .globl kstack_top + + .globl unix_syscall_entry + .globl nmi_handler + .globl interrupt_handler + + .include "../kernel.def" + .include "kernel.def" + + ; + ; startup code + ; + ; We loaded the rest of the kernel from disk and jumped here + ; + + .area _CODE + + .globl _start + +_start: + + di + + ld sp, #kstack_top + + ld sp,#kstack_top + ; + ; move the common memory where it belongs + ld hl, #s__DATA + ld de, #s__COMMONMEM + ld bc, #l__COMMONMEM + ldir + ; then the discard + ld de, #s__DISCARD + ld bc, #l__DISCARD + ldir + ; then zero the data area + ld hl, #s__DATA + ld de, #s__DATA + 1 + ld bc, #l__DATA - 1 + ld (hl), #0 + ldir + ; and buffers + ld hl, #s__BUFFERS + ld de, #s__BUFFERS + 1 + ld bc, #l__BUFFERS - 1 + ld (hl), #0 + ldir + + ; Configure memory map + call init_early + + ; Hardware setup + call init_hardware + + ; Call the C main routine + call _fuzix_main + + ; main shouldn't return, but if it does... + di +stop: halt + jr stop + + .area _BUFFERS +; +; Buffers (we use asm to set this up as we need them in a special segment +; so we can recover the discard memory into the buffer pool +; + + .globl _bufpool + .area _BUFFERS + +_bufpool: + .ds BUFSIZE * NBUFS diff --git a/Kernel/platform-zx+3/devices.c b/Kernel/platform-zx+3/devices.c new file mode 100644 index 00000000..821852cd --- /dev/null +++ b/Kernel/platform-zx+3/devices.c @@ -0,0 +1,46 @@ +#include +#include +#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 */ + { devfd_open, no_close, devfd_read, devfd_write, no_ioctl }, + /* 2: /dev/tty TTY devices */ + { tty_open, tty_close, tty_read, tty_write, gfx_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-zx+3/devinput.c b/Kernel/platform-zx+3/devinput.c new file mode 100644 index 00000000..179e973c --- /dev/null +++ b/Kernel/platform-zx+3/devinput.c @@ -0,0 +1,103 @@ +/* + * Joysticks off the AY sound chip + */ +#include +#include +#include +#include + +static uint8_t ay_p1_save, ay_p2_save; + +__sfr __at 0xF5 ay_reg; +__sfr __banked __at 0x01F6 ay_player1; +__sfr __banked __at 0x02F6 ay_player2; + +static char buf[32]; + +static struct s_queue kqueue = { + buf, buf, buf, sizeof(buf), 0, sizeof(buf) / 2 +}; + +/* Queue a character to the input device */ +void queue_input(uint8_t c) +{ + insq(&kqueue, c); + wakeup(&kqueue); +} + +static uint8_t ay_encode(uint8_t r) +{ + uint8_t k = 0; + r = ~r; + if (r & 1) + k = STICK_DIGITAL_U; + if (r & 2) + k |= STICK_DIGITAL_D; + if (r & 4) + k |= STICK_DIGITAL_L; + if (r & 8) + k |= STICK_DIGITAL_R; + if (r & 128) + k |= BUTTON(0); + return k; +} + +static uint8_t ay_js(uint8_t *slot) +{ + uint8_t r = ay_player1; + if (r == ay_p1_save) + return 0; + ay_p1_save = r; + *slot++ = STICK_DIGITAL; + *slot = ay_encode(r); + return 2; +} + +static uint8_t ay_js2(uint8_t *slot) +{ + uint8_t r = ay_player2; + if (r == ay_p2_save) + return 0; + ay_p2_save = r; + *slot++ = STICK_DIGITAL | 1; + *slot = ay_encode(r); + return 2; +} + +int platform_input_read(uint8_t *slot) +{ + uint8_t r, k; + if (remq(&kqueue, &r)) { + remq(&kqueue, &k); + *slot++ = KEYPRESS_CODE | r; + *slot = k; + return 2; + } + + ay_reg = 0x0E; + if (ay_js(slot)) + return 2; + if (ay_js2(slot)) + return 2; + return 0; +} + +void platform_input_wait(void) +{ + psleep(&kqueue); /* We wake this on timers so it works for sticks */ +} + +int platform_input_write(uint8_t flag) +{ + flag; + udata.u_error = EINVAL; + return -1; +} + +void poll_input(void) +{ + ay_reg=0x0E; + if (ay_player1 != ay_p1_save || ay_player2 != ay_p2_save) + wakeup(&kqueue); +} + \ No newline at end of file diff --git a/Kernel/platform-zx+3/devinput.h b/Kernel/platform-zx+3/devinput.h new file mode 100644 index 00000000..981f7024 --- /dev/null +++ b/Kernel/platform-zx+3/devinput.h @@ -0,0 +1,2 @@ +extern void queue_input(uint8_t); +extern void poll_input(void); diff --git a/Kernel/platform-zx+3/devtty.c b/Kernel/platform-zx+3/devtty.c new file mode 100644 index 00000000..c81e18f7 --- /dev/null +++ b/Kernel/platform-zx+3/devtty.c @@ -0,0 +1,150 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +char tbuf1[TTYSIZ]; + +uint8_t vtattr_cap = VTA_INVERSE|VTA_FLASH|VTA_UNDERLINE; +uint8_t curattr; + +static tcflag_t console_mask[4] = { + _ISYS, + _OSYS, + _CSYS, + _LSYS +}; + +tcflag_t *termios_mask[NUM_DEV_TTY + 1] = { + NULL, + console_mask +}; + + +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) +{ +} + + +/* This is used by the vt asm code, but needs to live in the kernel */ +uint16_t cursorpos; + +/* For now we only support 64 char mode - we should add the mode setting + logic an dother modes FIXME */ +static struct display specdisplay = { + 0, + 512, 192, + 512, 192, + 0xFF, 0xFF, + FMT_TIMEX64, + HW_UNACCEL, + GFX_VBLANK|GFX_MAPPABLE|GFX_TEXT, + 0 +}; + +static struct videomap specmap = { + 0, + 0, + 0x4000, + 14336, + 0, + 0, + 0, + MAP_FBMEM|MAP_FBMEM_SIMPLE +}; + +/* + * Graphics ioctls. Very minimal for this platform. It's a single fixed + * mode with direct memory mapping. + */ +int gfx_ioctl(uint8_t minor, uarg_t arg, char *ptr) +{ + if (minor != 1 || arg >> 8 != 0x03) + return vt_ioctl(minor, arg, ptr); + switch(arg) { + case GFXIOC_GETINFO: + return uput(&specdisplay, ptr, sizeof(struct display)); + case GFXIOC_MAP: + return uput(&specmap, ptr, sizeof(struct videomap)); + case GFXIOC_UNMAP: + return 0; + case GFXIOC_WAITVB: + /* Our system clock is vblank */ + timer_wait++; + psleep(&timer_interrupt); + timer_wait--; + chksigs(); + if (udata.u_cursig) { + udata.u_error = EINTR; + return -1; + } + return 0; + } + return -1; +} + +void vtattr_notify(void) +{ + /* Attribute byte fixups: not hard as the colours map directly + to the spectrum ones */ + if (vtattr & VTA_INVERSE) + curattr = ((vtink & 7) << 3) | (vtpaper & 7); + else + curattr = (vtink & 7) | ((vtpaper & 7) << 3); + if (vtattr & VTA_FLASH) + curattr |= 0x80; + /* How to map the bright bit - we go by either */ + if ((vtink | vtpaper) & 0x10) + curattr |= 0x40; +} diff --git a/Kernel/platform-zx+3/devtty.h b/Kernel/platform-zx+3/devtty.h new file mode 100644 index 00000000..40a67f5c --- /dev/null +++ b/Kernel/platform-zx+3/devtty.h @@ -0,0 +1,19 @@ +#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]; + +extern uint8_t timer_wait; + +extern int gfx_ioctl(uint8_t minor, uarg_t arg, char *ptr); + +extern uint8_t vtborder; + +#endif diff --git a/Kernel/platform-zx+3/discard.c b/Kernel/platform-zx+3/discard.c new file mode 100644 index 00000000..724a24f4 --- /dev/null +++ b/Kernel/platform-zx+3/discard.c @@ -0,0 +1,43 @@ +#include +#include +#include +#include +#include +#include + +uint8_t platform_param(char *p) +{ + return 0; +} + +/* Nothing to do for the map of init */ +void map_init(void) +{ +} + +void platform_copyright(void) +{ +} + +/* + * This function is called for partitioned devices if a partition is found + * and marked as swap type. The first one found will be used as swap. We + * only support one swap device. + */ +void platform_swap_found(uint8_t letter, uint8_t m) +{ + blkdev_t *blk = blk_op.blkdev; + uint16_t n; + if (swap_dev != 0xFFFF) + return; + letter -= 'a'; + kputs("(swap) "); + swap_dev = letter << 4 | m; + n = blk->lba_count[m - 1] / SWAP_SIZE; + if (n > MAX_SWAPS) + n = MAX_SWAPS; +#ifdef SWAPDEV + while (n) + swapmap_init(n--); +#endif +} diff --git a/Kernel/platform-zx+3/fdc765.s b/Kernel/platform-zx+3/fdc765.s new file mode 100644 index 00000000..e7112409 --- /dev/null +++ b/Kernel/platform-zx+3/fdc765.s @@ -0,0 +1,396 @@ +; +; 765 Floppy Controller Support +; +; This is based upon the Amstrad NC200 driver by David Given +; +; It differs on the Sinclair in the following ways +; +; - The timings are tighter (3.5Mhz) so we use in a,(c) jp p and other +; tricks to make the clocks. Even so it should be in uncontended RAM +; +; - Sinclair doesn't expose the tc line, so if the 765 decides to +; expect more data or feed us more data all we can do is dump it or +; feed it crap until it shuts up +; +; - We do motor and head loading delays (possibly some of those should +; be backported - FIXME) +; +; - We don't hang if the controller tells us no more data when we +; think we need to feed it command bytes (BACKPORT NEEDED) +; +; TODO +; Initialize drive step rate etc (we rely on the firmware for now) +; Step rate +; Head load/unload times +; Write off time af +; +; Or do we rely on the boot loader to have gotten this right (can't +; for drive > 0) FIXME +; +; + .module fdc765 + + .include "kernel.def" + .include "../kernel.def" + + .globl map_process_always + .globl map_kernel + + .globl _fd765_do_nudge_tc + .globl _fd765_do_recalibrate + .globl _fd765_do_seek + .globl _fd765_do_read + .globl _fd765_do_write + .globl _fd765_do_read_id + .globl _fd765_motor_on + .globl _fd765_motor_off + + .globl _fd765_track + .globl _fd765_head + .globl _fd765_sector + .globl _fd765_status + .globl _fd765_buffer + .globl _fd765_is_user + .globl _fd765_sectors + .globl _fd765_disc + + + .globl diskmotor + + .area _COMMONMEM + +; +; Twiddle the Terminal Count line to the FDC. Not supported by the +; Spectrum +3 +; +_fd765_do_nudge_tc: + ret + +; Writes A to the FDC data register. + +fd765_tx: + push bc + ex af, af' + ld bc,#0x2ffd ; floppy register (16bit access) +fd765_tx_loop: + in a, (c) + add a + jr nc, fd765_tx_loop + ; FIXME: backport this fix + add a + jr c, fd765_tx_exit ; controller doesn't want data ?? + ex af, af' + ld b,#0x3f + out (c), a + ex (sp),hl + ex (sp),hl +fd765_tx_exit: + pop bc + ; FIXME: is our delay quite long enough for spec ? + ; might need them to be ex (sp),ix ? + ret + +; Reads bytes from the FDC data register until the FDC tells us to stop (by +; lowering DIO in the status register). + +fd765_read_status: + ld hl, #_fd765_status + ld c, #0xfd ; we flip between 2ffd 3ffd as we go +read_status_loop: + ld b,#0x2f ; control port + in a, (c) + rla ; RQM... + jr nc, read_status_loop ; ...low, keep waiting + rla ; DIO... + ret nc ; ...low, no more data + ld b,#0x3f ; data port + in a,(c) ; INI ? FIXME + ld (hl),a + inc hl + ex (sp),hl ; wait for the 765A + ex (sp),hl + ex (sp),hl + ex (sp),hl + jr read_status_loop ; next byte +_fd765_status: + .ds 8 ; 8 bytes of status data + +; Sends the head/drive byte of a command. +; (Drive #0 is always used.) +; FIXME: add drive 1 support + +send_head: + ld a, (_fd765_head) + add a + add a + jr fd765_tx + +; Performs a RECALIBRATE command. + +_fd765_do_recalibrate: + ld a, #0x07 ; RECALIBRATE + call fd765_tx + ld a, #0x00 ; drive #0 FIXME + call fd765_tx + jr wait_for_seek_ending + +; Performs a SEEK command. + +_fd765_do_seek: + ld a, #0x0f ; SEEK + call fd765_tx + call send_head ; specified head, drive #0 + ld a, (_fd765_track) ; specified track + call fd765_tx + jr wait_for_seek_ending +_fd765_track: + .db 0 +_fd765_head: + .db 0 +_fd765_sector: + .db 0 +_fd765_disc: + .db 0 + +; Waits for a SEEK or RECALIBRATE command to finish by polling SENSE INTERRUPT STATUS. +wait_for_seek_ending: + ld a, #0x08 ; SENSE INTERRUPT STATUS + call fd765_tx + call fd765_read_status + + ld a, (_fd765_status) + bit 5, a ; SE, seek end + jr z, wait_for_seek_ending + + ; Now settle the head (FIXME: what is the right value ?) + ld a, #30 ; 30ms +; +; This assumes uncontended timing +; +wait_ms: + push bc +wait_ms_loop: + ld b,#0xDC +wait_ms_loop2: + dec b + jr nz, wait_ms_loop2 + dec a + jr nz, wait_ms_loop + pop bc + ret + +_fd765_motor_off: + xor a + ld (diskmotor),a + ; The request to update to the kernel map will fix the motor, and + ; we know our current map is kernel + jp map_kernel + +_fd765_motor_on: + ld a,#1 + ld (diskmotor),a + ; Take effect + call map_kernel + ; Now wait for spin up + + ld e,#10 ; FIXME right value ?? +wait2: + ; The classic Z80 KHz timing loop + ld bc,#0x3548 ; 3.548MHz +wait1: + dec bc + ld a,b + or c + jr nz, wait1 + dec e + jr nz, wait2 + ret +; +; Reads a 512-byte sector, after having previously saught to the right track. +; +; We need to be doubly careful here as the 765A has a 'feature' whereby it +; won't report an overrun on the last byte so we must always make timing +; +_fd765_do_read: + ld a, #0x46 ; READ SECTOR MFM + + ; FIXME: need to return a last cmd byte here and write it + ; after this crap or we may miss if we write just the sector hits + ; the head (BACKPORT ME ??) + call setup_read_or_write + + ld a, (_fd765_is_user) + or a + push af + call nz, map_process_always + + di ; performance critical, + ; run with interrupts off + + xor a + call fd765_tx ; send the final unused 0 byte + ; to fire off the command + ld hl, (_fd765_buffer) + ld bc, #0x2ffd + ld de, #0x2000 ; so we can make timing + jp read_wait +; +; First 256 bytes +; +read_loop: + ld b,#0x3f ; data port + ini + ld b,#0x2f ; control port + dec e + jp z, read_wait2 +read_wait: + in a,(c) ; read the fdc status + jp p, read_wait + and d + jp nz, read_loop + jp read_finished +; +; Second 256 bytes +; +read_loop2: + ld b,#0x3f ; data port + ini + ld b,#0x2f ; control port + dec e + jp z, read_flush_wait +read_wait2: + in a,(c) ; read the fdc status + jp p, read_wait2 + and d + jp nz, read_loop2 + jp read_finished +; +; Flush out any extra data (no tc control) +; +read_flush: + ld b,#0x3f + in a,(c) + ld b,#0x2f +read_flush_wait: + in a,(c) + jp p, read_flush_wait + and d + jp nz, read_flush +; +; And done +; +read_finished: + ld (_fd765_buffer), hl + call _fd765_do_nudge_tc ; Tell FDC we've finished + ei + + call fd765_read_status + + pop af + ret z + jp map_kernel + +; +; Write is much like read just the other direction +; +_fd765_do_write: + di ; performance critical, run with + ; interrupts off + ld a, #0x45 ; WRITE SECTOR MFM + call setup_read_or_write + + ld a, (_fd765_is_user) + or a + push af + call nz, map_process_always + + xor a + call fd765_tx ; send the final unused 0 byte + ; to fire off the command + ld hl, (_fd765_buffer) + ld bc, #0x2ffd + ld de,#0x2000 ; to make timing + jp write_wait + +write_loop: + ld b,#0x3f + outi + ld b,#0x2f + dec e + jp z, write_wait2 +write_wait: + in a,(c) + jp p, write_wait + and d + jp nz, write_loop + jp write_finished +write_loop2: + ld b,#0x3f + outi + ld b,#0x2f + dec e + jp z, write_flush_wait +write_wait2: + in a,(c) + jp p, write_wait2 + and d + jp nz, write_loop2 + jp write_finished +write_flush: + ld b,#0x3f + in a,(c) + ld b,#0x2f +write_flush_wait: + in a,(c) + jp p, write_flush_wait + and d + jp nz, write_flush +write_finished: + ld (_fd765_buffer), hl + call _fd765_do_nudge_tc ; Tell FDC we've finished + ei + call fd765_read_status + + pop af + ret z + jp map_kernel + +; Given an FDC opcode in A, sets up a read or write. + +setup_read_or_write: + call fd765_tx ; 0: send opcode (in A) + call send_head ; 1: specified head, drive #0 + ld a, (_fd765_track) ; 2: specified track + call fd765_tx + ld a, (_fd765_head) ; 3: specified head + call fd765_tx + ld a, (_fd765_sector) ; 4: specified sector + ld b, a + call fd765_tx + ld a, #2 ; 5: bytes per sector: 512 + call fd765_tx + ld a, (_fd765_sectors) + add b ; add first sector + dec a ; 6: last sector (*inclusive*) + call fd765_tx + ld a, #27 ; 7: Gap 3 length (27 is standard for 3.5" drives) + call fd765_tx + ; We return with the final unused 0 value not written. We need all + ; the other stuff lined up before we write this. + ret + +_fd765_buffer: + .dw 0 +_fd765_is_user: + .db 0 +_fd765_sectors: + .db 0 + +; Read the next sector ID off the disk. +; (Only used for debugging.) + +_fd765_do_read_id: + ld a, #0x4a ; READ MFM ID + call fd765_tx + call send_head ; specified head, drive 0 + jp fd765_read_status diff --git a/Kernel/platform-zx+3/fdc765_platform.h b/Kernel/platform-zx+3/fdc765_platform.h new file mode 100644 index 00000000..f431ac05 --- /dev/null +++ b/Kernel/platform-zx+3/fdc765_platform.h @@ -0,0 +1,5 @@ +/* + * Platform specifics + */ + +#define FDC_MOTOR_TIMEOUT 10 /* Seconds */ diff --git a/Kernel/platform-zx+3/fuzix.lnk b/Kernel/platform-zx+3/fuzix.lnk new file mode 100644 index 00000000..dddbc108 --- /dev/null +++ b/Kernel/platform-zx+3/fuzix.lnk @@ -0,0 +1,49 @@ +-mwxuy +-i fuzix.ihx +-l z80 +-b _CODE=0x0100 +-b _COMMONMEM=0xF000 +platform-zx+3/crt0.rel +platform-zx+3/commonmem.rel +platform-zx+3/plus3.rel +platform-zx+3/zxvideo.rel +platform-zx+3/main.rel +platform-zx+3/discard.rel +start.rel +version.rel +lowlevel-z80.rel +usermem_std-z80.rel +platform-zx+3/tricks.rel +timer.rel +kdata.rel +usermem.rel +platform-zx+3/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 +simple.rel +swap.rel +devsys.rel +devinput.rel +platform-zx+3/devtty.rel +platform-zx+3/devide.rel +platform-zx+3/devide_discard.rel +platform-zx+3/divide.rel +platform-zx+3/mbr.rel +platform-zx+3/blkdev.rel +platform-zx+3/devinput.rel +platform-zx+3/zxkeyboard.rel +platform-zx+3/devfdc765.rel +platform-zx+3/fdc765.rel +-e diff --git a/Kernel/platform-zx+3/kernel.def b/Kernel/platform-zx+3/kernel.def new file mode 100644 index 00000000..3b0c8c89 --- /dev/null +++ b/Kernel/platform-zx+3/kernel.def @@ -0,0 +1,14 @@ +; UZI mnemonics for memory addresses etc + +; We stick it straight after the tag +U_DATA .equ 0xF000 ; (this is struct u_data from kernel.h) +U_DATA__TOTALSIZE .equ 0x200 ; 256+256+256 bytes. + +Z80_TYPE .equ 1 + +PROGBASE .equ 0x0000 +PROGLOAD .equ 0x0100 + +NBUFS .equ 5 + +Z80_MMU_HOOKS .equ 0 diff --git a/Kernel/platform-zx+3/loader.s b/Kernel/platform-zx+3/loader.s new file mode 100644 index 00000000..5eb45500 --- /dev/null +++ b/Kernel/platform-zx+3/loader.s @@ -0,0 +1,183 @@ +; +; Boot block for +2A/+3 +; + + .area BOOT (ABS) ; 0xFE00 max 512 bytes + + .org 0xFE00 + + .byte 0 ; +3 + .byte 0 ; single sided + .byte 40 ; 40 track + .byte 9 ; 9 spt + .byte 2 ; 512 byte sectors + .byte 1 ; reserved track (meaningless) + .byte 3 ; blocks (meaningless) + .byte 2 ; directory length (meaningless) + .byte 0x2A ; gap length + .byte 0x52 ; page length + .byte 0,0,0,0,0 ; reserved + .byte 0 ; checksum (fixed up by tool) + +; +; Entered here in 4/7/6/3 mapping with SP FE00 and the disk motor +; just turned off +; +start: + di +; +; The boot loader turns the motor off, so turn it back on but wait +; a bit otherwise some 3.5" disks won't boot right +; +motor_on: + ld bc,#0x1ffd + ld a,#0x0D ; special paging 4/5/6/3. motor on + out (c),a + + ; Wait for the motor to catch back up + ld bc,#0x3548 +waitm: + dec bc + ld a,b + or c + jr nz, waitm +; +; Ok now we can try and load stuff +; 9 sectors per track and we need to start from 0/2 + + ld de,#0x02 + ld hl,#0x0100 + jr dosector + +; +; Main track loading loop +; +load_track: + inc d ; Next track + ld e,#1 ; Sectors start at 1 + ld a,d ; Cycle border colour + and #0x03 + add #4 + out (0xFE),a + push de ; Seek to the needed track + call seek_track + pop de +dosector: ; Loop through track D loading sector E + push de + call load_sector ; Load 512 bytes into HL and adjust HL + pop de + ld a,#0xFD ; Load up to FD00 + cp h + jp z, 0x0100 ; And then run it + inc e ; Next sector + ld a,#10 ; New track ? + cp e + jr nz, dosector ; Nope + jr load_track ; Move on and seek a track +; +; Helpers - seek to track D +; +seek_track: + ld a,#0x0f + call fd765_tx + xor a + call fd765_tx ; disk 0 side 0 + ld a,d + call fd765_tx +wait_done: + ld a,#0x08 + call fd765_tx + call read_status + bit 5, a + jr z, wait_done + ld a, #30 ; 30ms +; +; Head settle +; +; This assumes uncontended timing +; +wait_ms: + ld b,#0xDC +wait_ms_loop2: + dec b + jr nz, wait_ms_loop2 + dec a + jr nz, wait_ms + ret + +; +; Load sector E of track D into HL and adjust HL +; +; Load at just doing track loads in one command! FIXME +; +load_sector: + ld a,#0x46 + call fd765_tx ; read MFM + xor a ; Drive 0 head 0 + call fd765_tx + ld a, d ; Track D + call fd765_tx + xor a ; Head 0 + call fd765_tx + ld a,e ; Sector E + call fd765_tx + ld a,#2 ; 512 bytes + call fd765_tx + ld a,e ; Last sector (inclusive) + call fd765_tx + ld a,#27 + call fd765_tx + xor a + call fd765_tx + + ld c,#0xfd ; low bits of I/O + ld d,#0x20 ; mask + jp read_wait +read_loop: + ld b,#0x3f + ini + ld b,#0x2f +read_wait: + in a,(c) + jp p,read_wait + and d + jp nz,read_loop +read_status: + ld c,#0xfd + push hl + ld hl,#status +read_status_loop: + ld b,#0x2f + in a,(c) + rla + jr nc, read_status_loop + rla + jr nc, done_status + ld b,#0x3f + ini + ex (sp),hl + ex (sp),hl + ex (sp),hl + ex (sp),hl + jr read_status_loop +done_status: + ld a, (status) + pop hl + ret +status: .ds 8 + +fd765_tx: + ex af, af' + ld bc,#0x2ffd ; floppy register (16bit access) +fd765_tx_loop: + in a, (c) + add a + jr nc, fd765_tx_loop + add a + ret c + ex af, af' + ld b,#0x3f + out (c), a + ex (sp),ix + ex (sp),ix + ret diff --git a/Kernel/platform-zx+3/main.c b/Kernel/platform-zx+3/main.c new file mode 100644 index 00000000..00230ec0 --- /dev/null +++ b/Kernel/platform-zx+3/main.c @@ -0,0 +1,82 @@ +#include +#include +#include +#include +#include +#include + +uint16_t ramtop = PROGTOP; +uint16_t swap_dev = 0xFFFF; + +/* 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 */ + __asm + halt + __endasm; +} + +uint8_t timer_wait; + +void platform_interrupt(void) +{ + tty_pollirq(); + timer_interrupt(); + poll_input(); + if (timer_wait) + wakeup(&timer_interrupt); +} + +/* + * So that we don't suck in a library routine we can't use from + * the runtime + */ + +int strlen(const char *p) +{ + int len = 0; + while(*p++) + len++; + return len; +} + +/* This points to the last buffer in the disk buffers. There must be at least + four buffers to avoid deadlocks. */ +struct blkbuf *bufpool_end = bufpool + NBUFS; + +/* + * We pack discard into the memory image is if it were just normal + * code but place it at the end after the buffers. When we finish up + * booting we turn everything from the buffer pool to the start of + * user space into buffers. + * + * We don't touch discard. Discard is just turned into user space. + */ +void platform_discard(void) +{ + uint16_t discard_size = 0xC000U - (uint16_t)bufpool_end; + bufptr bp = bufpool_end; + + discard_size /= sizeof(struct blkbuf); + + kprintf("%d buffers added\n", discard_size); + + bufpool_end += discard_size; + + memset( bp, 0, discard_size * sizeof(struct blkbuf) ); + + for( bp = bufpool + NBUFS; bp < bufpool_end; ++bp ){ + bp->bf_dev = NO_DEVICE; + bp->bf_busy = BF_FREE; + } +} + +#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-zx+3/platform_ide.h b/Kernel/platform-zx+3/platform_ide.h new file mode 100644 index 00000000..2141fda7 --- /dev/null +++ b/Kernel/platform-zx+3/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-zx+3/plus3.s b/Kernel/platform-zx+3/plus3.s new file mode 100644 index 00000000..65ef348e --- /dev/null +++ b/Kernel/platform-zx+3/plus3.s @@ -0,0 +1,245 @@ +; +; Spectrum +3 support + + .module plus3 + + ; exported symbols + .globl init_early + .globl init_hardware + .globl _program_vectors + .globl platform_interrupt_all + .globl interrupt_handler + .globl unix_syscall_entry + .globl null_handler + .globl nmi_handler + + .globl map_kernel + .globl map_process_always + .globl map_process + .globl map_kernel_di + .globl map_process_always_di + .globl map_save_kernel + .globl map_restore + .globl map_kernel_restore + .globl map_for_swap + .globl map_video + .globl current_map + + .globl _need_resched + .globl _int_disabled + .globl _vtborder + .globl diskmotor + + ; exported debugging tools + .globl _platform_monitor + .globl _platform_reboot + .globl outchar + + ; imported symbols + .globl _ramsize + .globl _procmem + + .globl _vtoutput + .globl _vtinit + + .globl outcharhex + .globl outhl, outde, outbc + .globl outnewline + .globl outstring + .globl outstringhex + + .include "kernel.def" + .include "../kernel.def" + +; ----------------------------------------------------------------------------- +; COMMON MEMORY BANK (below 0x4000) +; ----------------------------------------------------------------------------- + .area _COMMONMEM + +_platform_monitor: + ; + ; Not so much a monitor as wait for space + ; + ld bc,#0x1ffd + ld a,#0x01 + out (c),a ; keep us mapped, turn off motors + ld a, #0x7F + in a, (0xFE) + rra + jr c, _platform_monitor + +_platform_reboot: + di + ld bc,#0x7ffd + ld a,#0x03 + out (c),a ; set 128K paging to put page 3 at the top (ie us) + ; and ROM in + ld bc,#0x1ffd + xor a ; flip to normal paging 128K ROM motor off + out (c),a + rst 0 ; back into our booter + +platform_interrupt_all: + ret + + .area _COMMONMEM + +_int_disabled: + .db 1 + +_vtborder: ; needs to be common + .db 0 + + +; ----------------------------------------------------------------------------- +; KERNEL CODE BANK (below 0xC000) +; ----------------------------------------------------------------------------- + .area _CODE + +init_early: + call _program_early_vectors + ret + +init_hardware: + ; set system RAM size + ld hl, #128 + ld (_ramsize), hl + ld hl, #64 ; 64K for kernel/screen/etc (FIXME) + ld (_procmem), hl + + ld bc,#0x7ffd + ld a,#0x0B ; bank 3 (common) in high in either mapping + ; video bank 7 + out (c),a ; and we should have special mapping + ; already by now + + ; screen initialization + call _vtinit + + ret + +;------------------------------------------------------------------------------ +; COMMON MEMORY PROCEDURES FOLLOW + + .area _COMMONMEM + +_program_early_vectors: + call map_process + call set_vectors + call map_kernel +set_vectors: + ; write zeroes across all vectors + ld hl, #0 + ld de, #1 + ld bc, #0x007f ; program first 0x80 bytes only + ld (hl), #0x00 + ldir + + ; now install the interrupt vector at 0x0038 + ld a, #0xC3 ; JP instruction + ld (0x0038), a + ld hl, #interrupt_handler + ld (0x0039), hl + + ; set restart vector for FUZIX system calls + ld (0x0030), a ; (rst 30h is unix function call vector) + ld hl, #unix_syscall_entry + ld (0x0031), hl + + ld (0x0000), a + ld hl, #null_handler ; to Our Trap Handler + ld (0x0001), hl + + ld (0x0066), a ; Set vector for NMI + ld hl, #nmi_handler + ld (0x0067), hl + +_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_process: + ld a, h + or l + jr z, map_kernel +map_for_swap: +map_process_always: +map_process_always_di: + push af + ld a,#0x5 ; 4 5 6 3 + jr map_a_pop +; +; Save and switch to kernel +; +map_save_kernel: + push af + ld a, (current_map) + ld (map_store), a + pop af +map_kernel_di: +map_kernel: +map_kernel_restore: + push af +map_a_pop: + push bc + ld a,#0x01 ; 0/1/2/3 + ld (current_map),a + ld bc,(diskmotor) + or c + ld bc,#0x1ffd + out (c),a + pop bc + pop af + ret + +map_video: + push af + ld a,#0x07 ; 4 7 6 3 + jr map_a_pop + +map_restore: + push af + ld a, (map_store) + ld (current_map),a + jr map_a_pop + +; +; 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 + call _vtoutput + pop af + pop af + pop ix + pop hl + pop de + pop bc + ret + + .area _COMMONMEM +_tmpout: + .db 1 + +current_map: ; place to store current page number. Is needed + .db 0 ; because we have no ability to read 0xF4 port + ; to detect what page is mapped currently +map_store: + .db 0 + +_need_resched: + .db 0 + +diskmotor: + .db 0 diff --git a/Kernel/platform-zx+3/rules.mk b/Kernel/platform-zx+3/rules.mk new file mode 100644 index 00000000..b24847af --- /dev/null +++ b/Kernel/platform-zx+3/rules.mk @@ -0,0 +1 @@ +export CROSS_CC_SYS5=--codeseg CODE3 diff --git a/Kernel/platform-zx+3/target.mk b/Kernel/platform-zx+3/target.mk new file mode 100644 index 00000000..3bffcde0 --- /dev/null +++ b/Kernel/platform-zx+3/target.mk @@ -0,0 +1 @@ +export CPU = z80 diff --git a/Kernel/platform-zx+3/tricks.s b/Kernel/platform-zx+3/tricks.s new file mode 100644 index 00000000..0113d75b --- /dev/null +++ b/Kernel/platform-zx+3/tricks.s @@ -0,0 +1,5 @@ + .include "kernel.def" + .include "../kernel.def" + + .include "../lib/z80single.s" + diff --git a/Kernel/platform-zx+3/zxvideo.s b/Kernel/platform-zx+3/zxvideo.s new file mode 100644 index 00000000..250ec839 --- /dev/null +++ b/Kernel/platform-zx+3/zxvideo.s @@ -0,0 +1,28 @@ +; +; 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 _fontdata_8x8 + .globl _curattr + .globl _vtattr + + ; Build the video library as the only driver + +ZXVID_ONLY .equ 1 + + .area _COMMONMEM + + .include "../dev/zx/video.s" + -- 2.34.1