From 1abba9ff6155a74589d5368b5b925c5f9a4c00bb Mon Sep 17 00:00:00 2001 From: Alan Cox Date: Tue, 22 May 2018 01:21:03 +0100 Subject: [PATCH] trs80 model 1: initial commit tree Work in progress. --- Kernel/platform-trs80m1/Makefile | 37 ++ Kernel/platform-trs80m1/README | 104 ++++++ Kernel/platform-trs80m1/commonmem.s | 10 + Kernel/platform-trs80m1/config.h | 62 ++++ Kernel/platform-trs80m1/crt0.s | 84 +++++ Kernel/platform-trs80m1/devfd.c | 102 ++++++ Kernel/platform-trs80m1/devfd.h | 15 + Kernel/platform-trs80m1/devgfx.c | 73 ++++ Kernel/platform-trs80m1/devgfx.h | 12 + Kernel/platform-trs80m1/devhd.c | 178 ++++++++++ Kernel/platform-trs80m1/devhd.h | 58 ++++ Kernel/platform-trs80m1/devhd_discard.c | 96 +++++ Kernel/platform-trs80m1/devices.c | 37 ++ Kernel/platform-trs80m1/devlpr.c | 52 +++ Kernel/platform-trs80m1/devlpr.h | 8 + Kernel/platform-trs80m1/devtty.c | 252 ++++++++++++++ Kernel/platform-trs80m1/devtty.h | 17 + Kernel/platform-trs80m1/discard.c | 36 ++ Kernel/platform-trs80m1/floppy.s | 443 ++++++++++++++++++++++++ Kernel/platform-trs80m1/fuzix.lnk | 48 +++ Kernel/platform-trs80m1/kernel.def | 17 + Kernel/platform-trs80m1/main.c | 76 ++++ Kernel/platform-trs80m1/rules.mk | 20 ++ Kernel/platform-trs80m1/target.mk | 1 + Kernel/platform-trs80m1/tricks.s | 5 + Kernel/platform-trs80m1/trs80-bank.s | 261 ++++++++++++++ Kernel/platform-trs80m1/trs80.s | 136 ++++++++ Kernel/platform-trs80m1/trs80load.s | 158 +++++++++ 28 files changed, 2398 insertions(+) create mode 100644 Kernel/platform-trs80m1/Makefile create mode 100644 Kernel/platform-trs80m1/README create mode 100644 Kernel/platform-trs80m1/commonmem.s create mode 100644 Kernel/platform-trs80m1/config.h create mode 100644 Kernel/platform-trs80m1/crt0.s create mode 100644 Kernel/platform-trs80m1/devfd.c create mode 100644 Kernel/platform-trs80m1/devfd.h create mode 100644 Kernel/platform-trs80m1/devgfx.c create mode 100644 Kernel/platform-trs80m1/devgfx.h create mode 100644 Kernel/platform-trs80m1/devhd.c create mode 100644 Kernel/platform-trs80m1/devhd.h create mode 100644 Kernel/platform-trs80m1/devhd_discard.c create mode 100644 Kernel/platform-trs80m1/devices.c create mode 100644 Kernel/platform-trs80m1/devlpr.c create mode 100644 Kernel/platform-trs80m1/devlpr.h create mode 100644 Kernel/platform-trs80m1/devtty.c create mode 100644 Kernel/platform-trs80m1/devtty.h create mode 100644 Kernel/platform-trs80m1/discard.c create mode 100644 Kernel/platform-trs80m1/floppy.s create mode 100644 Kernel/platform-trs80m1/fuzix.lnk create mode 100644 Kernel/platform-trs80m1/kernel.def create mode 100644 Kernel/platform-trs80m1/main.c create mode 100644 Kernel/platform-trs80m1/rules.mk create mode 100644 Kernel/platform-trs80m1/target.mk create mode 100644 Kernel/platform-trs80m1/tricks.s create mode 100644 Kernel/platform-trs80m1/trs80-bank.s create mode 100644 Kernel/platform-trs80m1/trs80.s create mode 100644 Kernel/platform-trs80m1/trs80load.s diff --git a/Kernel/platform-trs80m1/Makefile b/Kernel/platform-trs80m1/Makefile new file mode 100644 index 00000000..f3c811ef --- /dev/null +++ b/Kernel/platform-trs80m1/Makefile @@ -0,0 +1,37 @@ + +CSRCS = devlpr.c devtty.c devfd.c devhd.c devgfx.c +CSRCS += devices.c main.c +DISCARD_CSRCS = discard.c devhd_discard.c + +ASRCS = trs80.s trs80-bank.s crt0.s +ASRCS += tricks.s commonmem.s floppy.s + +COBJS = $(CSRCS:.c=.rel) +AOBJS = $(ASRCS:.s=.rel) +DISCARD_COBJS = $(DISCARD_CSRCS:.c=.rel) +OBJS = $(COBJS) $(AOBJS) $(DISCARD_COBJS) $(DOBJS) + +CROSS_CCOPTS += -I../dev/ + +JUNK = $(CSRCS:.c=.lst) $(CSRCS:.c=.asm) $(CSRCS:.c=.sym) $(ASRCS:.s=.lst) $(ASRCS:.s=.sym) $(CSRCS:.c=.rst) $(ASRCS:.s=.rst) + +all: $(OBJS) trs80load.bin + +$(COBJS): %.rel: %.c + $(CROSS_CC) $(CROSS_CCOPTS) $(CROSS_CC_SEG1) -c $< + +$(AOBJS): %.rel: %.s + $(CROSS_AS) $(ASOPTS) $< + +$(DISCARD_COBJS): %.rel: %.c + $(CROSS_CC) $(CROSS_CCOPTS) $(CROSS_CC_SEGDISC) -c $< + +clean: + rm -f $(OBJS) $(JUNK) core *~ + +image: trs80load.bin + +trs80load.bin: trs80load.s + sdasz80 -o trs80load.s + sdldz80 -i trs80load.rel + makebin -s 256 trs80load.ihx trs80load.bin diff --git a/Kernel/platform-trs80m1/README b/Kernel/platform-trs80m1/README new file mode 100644 index 00000000..e54bcba1 --- /dev/null +++ b/Kernel/platform-trs80m1/README @@ -0,0 +1,104 @@ +TRS80 Model 1 + + This is just a development tree. It doesn't yet have a loader nor + has it been tested! + +Emulator Bugs: + The emulator is horribly insecure, it's default is to allow all + sorts of direct access to things. Even if you turn this off I've + had it segfault with FUZIX bugs which suggests its not too secure. + + Repeating instructions like LDIR appear to be misemulated. LDIR + is performed an emulated block copy, not as an iterated LDI. The + real processor actually implements LDIR as "LDI, if not done + PC -= 2". FUZIX doesn't do any overlapped LDIR tricks so shouldn't + be affected. + + The interrupt flags are misemulated. A patch for this is in the + Fuzix tree and is needed to run Fuzix on xtrs/sdltrs + +Requirements: + TRS80 Model 1 + Preferably the lower case mod + Supermem or compatible expansion + Floppy drive or Hard drive (strongly recommend the latter) + +Memory Map: + 0000-3FFF Various fixed model 1 functionality + 4000-41FF Kept clear for ROM bits + 4200-7FFF Kernel data, common etc + 8000-FFFF Bank0: kernel code + 8000-FFFF Bank1: kernel code + + Remaining banks of 32K are user apps. + + The kernel doesn't actually need 80K but it also doesn't fit in + the available 48K unbanked either (although a minimalish setup might) + + Need to move buffers out of line using the external buffers code so we + can better balance memory + +Drivers: + Display 64x16 In progress (mod from existing) + Keyboard Just an address change I think + Hard drive Straight model 4 port + Floppy In progress (initial code design) + Floppy drivers do not yet deal with double sided disks or sd/dd + media detection + Hard disk reads block 0, and handle partitions of some form + including finding where 'swap' lives + +Setting It Up (current xtrs: https://github.com/TimothyPMann/xtrs) + + This needs patches currently in progress + + # Tool to make disk images + make tools/makejv3 + # Double density single sided 40 track boot disk + tools/makejv3 dd40s /dev/zero disk1-0 + # Build kernel and boot block (edit Makefile, set target then) + make + # Add pieces to the disk (boot block in sector 0, kernel at end) + dd if=zout/trs80load.cim of=disk1-0 bs=1 seek=8960 conv=notrunc + dd if=fuzix.bin of=disk1-0 bs=1 seek=142336 conv=notrunc + # Once we get that far you can also put a filesystem in the lower + # blocks + # + xtrs -model 1 -emtsafe --supermem + +To set up for hard disk, create a hard disk with the xtrs tools and + + mkdisk -h hard1-0 + # This assumes the default disk size + cd Standalone/filesystem-src + ./build-filesystem trs80.hd 256 21760 + dd if=trs80.hd of=hard1-0 bs=256 seek=1 conv=notrunc + tools/trslabel hard1-0 + # + xtrs -model 1 -emtsafe -supermem + + +You will still need a boot floppy at this point but just boot with device +0 (or hit return). Swap is configured to be on the end of the hard disk + + + +Banking Models + +Currently Supported: + Alpha Technology Supermem upper 32K banking only. + +Not Yet Started: + "Selector" for Model 1. Port 31 allows memory reshuffling away from +the model 1 default. Either the upper or lower 32K is switchable but not +both at once. bits 4/5 control the selection between a further 4 32K banks. + + + +Useful rom addresses + +04C3 -> 64 column +04F6 -> 32 column + +0060 -> 14.5*BC uS delay + diff --git a/Kernel/platform-trs80m1/commonmem.s b/Kernel/platform-trs80m1/commonmem.s new file mode 100644 index 00000000..bafd590b --- /dev/null +++ b/Kernel/platform-trs80m1/commonmem.s @@ -0,0 +1,10 @@ +; +; We have no real common on the TRS80so just tuck it up at the top of +; memory leaving room for the keyboard and video (3K) +; + .module commonmem + + .area _COMMONMEM + + .include "../cpu-z80/std-commonmem.s" + diff --git a/Kernel/platform-trs80m1/config.h b/Kernel/platform-trs80m1/config.h new file mode 100644 index 00000000..ef657a45 --- /dev/null +++ b/Kernel/platform-trs80m1/config.h @@ -0,0 +1,62 @@ +/* Set if you want RTC support and have an RTC on ports 0xB0-0xBC */ +#define CONFIG_RTC +/* Enable to make ^Z dump the inode table for debug */ +#undef CONFIG_IDUMP +/* Enable to make ^A drop back into the monitor */ +#undef CONFIG_MONITOR +/* Profil syscall support (not yet complete) */ +#undef CONFIG_PROFIL +/* Multiple processes in memory at once */ +#define CONFIG_MULTI +/* Single tasking */ +#undef CONFIG_SINGLETASK +/* Video terminal, not a serial tty */ +#define CONFIG_VT +/* Simple character addressed device */ +#define CONFIG_VT_SIMPLE +/* Banked memory set up */ +#define CONFIG_BANK_FIXED + +#define MAX_MAPS 16 + +#define MAP_SIZE 0x8000 + +#define CONFIG_BANKS 2 /* 2 x 32K */ + +/* Vt definitions */ +#define VT_BASE ((uint16_t)0x3C00 +#define VT_WIDTH 64 +#define VT_HEIGHT 16 +#define VT_RIGHT 63 +#define VT_BOTTOM 15 + +#define TICKSPERSEC 40 /* Ticks per second */ +#define PROGBASE 0x8000 /* Base of user */ +#define PROGLOAD 0x8000 /* Load and run here */ +#define PROGTOP 0xFE00 /* Top of program, base of U_DATA stash */ +#define PROC_SIZE 32 /* Memory needed per process */ + +#define SWAP_SIZE 0x40 /* 32K in blocks */ +#define SWAPBASE 0x8000 /* We swap the lot in one, include the */ +#define SWAPTOP 0x8000 /* vectors so its a round number of sectors */ + +#define MAX_SWAPS 16 /* Should be plenty (512K!) */ + +#define swap_map(x) ((uint8_t *)(x)) + +#define BOOT_TTY (512 + 1) /* 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 2 +#define TTYDEV BOOT_TTY /* Device used by kernel for messages, panics */ +#define SWAPDEV (swap_dev) /* Device for swapping (dynamic). */ +#define NBUFS 5 /* Number of block buffers - keep in sync with asm! */ +#define NMOUNTS 2 /* Number of mounts at a time */ +/* Reclaim the discard space for buffers */ +#define CONFIG_DYNAMIC_BUFPOOL + +extern void platform_discard(void); diff --git a/Kernel/platform-trs80m1/crt0.s b/Kernel/platform-trs80m1/crt0.s new file mode 100644 index 00000000..a31b6c9d --- /dev/null +++ b/Kernel/platform-trs80m1/crt0.s @@ -0,0 +1,84 @@ + ; Ordering of segments for the linker. + ; WRS: Note we list all our segments here, even though + ; we don't use them all, because their ordering is set + ; when they are first seen. + .area _CODE + .area _CODE1 + .area _CODE2 + .area _DISCARD2 + .area _VIDEO + .area _COMMONMEM + .area _STUBS + .area _CONST + .area _INITIALIZED + .area _BSEG + .area _BSS + .area _HEAP + ; note that areas below here may be overwritten by the heap at runtime, so + ; put initialisation stuff in here + .area _GSINIT + .area _GSFINAL + .area _DATA + .area _BUFFERS + .area _INITIALIZER + ; Buffers must be directly before discard as they will + ; expand over it + + ; imported symbols + .globl _fuzix_main + .globl init_early + .globl init_hardware + .globl s__DATA + .globl l__DATA + .globl s__BUFFERS + .globl l__BUFFERS + .globl s__COMMONMEM + .globl l__COMMONMEM + .globl s__INITIALIZER + .globl kstack_top + .globl bufend + + ; exports + .globl _discard_size + + ; startup code + .area _CODE + +; +; Once the loader completes it jumps here +; +start: + ld sp, #kstack_top + ; then zero the data area + ld hl, #s__DATA + ld de, #s__DATA + 1 + ld bc, #l__DATA - 1 + ld (hl), #0 + ldir +; Zero buffers area + ld hl, #s__BUFFERS + ld de, #s__BUFFERS + 1 + ld bc, #l__BUFFERS - 1 + ld (hl), #0 + ldir + ld hl,#0x8000 + ld de,#bufend + or a + sbc hl,de + ld (_discard_size),hl + call init_early + call init_hardware + push af + call _fuzix_main + pop af + di +stop: halt + jr stop + + .area _DATA +_discard_size: + .dw 0 + + .area _STUBS +stubs: + .ds 768 diff --git a/Kernel/platform-trs80m1/devfd.c b/Kernel/platform-trs80m1/devfd.c new file mode 100644 index 00000000..7a154850 --- /dev/null +++ b/Kernel/platform-trs80m1/devfd.c @@ -0,0 +1,102 @@ +#include +#include +#include +#include + +#define MAX_FD 4 + +#define OPDIR_NONE 0 +#define OPDIR_READ 1 +#define OPDIR_WRITE 2 + +#define FD_READ 0x80 /* 2797 needs 0x88, 1797 needs 0x80 */ +#define FD_WRITE 0xA0 /* Likewise A8 v A0 */ + +static uint8_t motorct; + +/* Extern as they live in common */ +extern uint8_t fd_map, fd_tab[MAX_FD]; +extern uint8_t fd_selected; +extern uint8_t fd_cmd[6]; + +/* + * We only support normal block I/O for the moment. We do need to + * add swapping! + */ +static uint8_t selmap[4] = { 0x01, 0x02, 0x04, 0x08 }; + +static int fd_transfer(uint8_t minor, bool is_read, uint8_t rawflag) +{ + int ct = 0; + int tries; + uint8_t err = 0; + uint8_t *driveptr = fd_tab + minor; + + if(rawflag == 2) + goto bad2; + + /* FIXME: We force DD for now */ + err = fd_motor_on(selmap[minor]|0x80); + if (err) + goto bad; + + if (*driveptr == 0xFF) + fd_reset(driveptr); + + fd_map = rawflag; + if (rawflag && d_blkoff(BLKSHIFT)) + return -1; + + udata.u_nblock *= 2; + + fd_cmd[0] = is_read ? FD_READ : FD_WRITE; + fd_cmd[1] = udata.u_block / 9; /* 2 sectors per block */ + fd_cmd[2] = ((udata.u_block % 9) << 1) + 1; /*eww.. */ + fd_cmd[3] = is_read ? OPDIR_READ: OPDIR_WRITE; + fd_cmd[4] = ((uint16_t)udata.u_dptr) & 0xFF; + fd_cmd[5] = ((uint16_t)udata.u_dptr) >> 8; + + while (ct < udata.u_nblock) { + for (tries = 0; tries < 4 ; tries++) { + err = fd_operation(driveptr); + if (err == 0) + break; + if (tries > 1) + fd_reset(driveptr); + } + /* FIXME: should we try the other half and then bale out ? */ + if (tries == 4) + goto bad; + fd_cmd[5]++; /* Move on 256 bytes in the buffer */ + fd_cmd[2]++; /* Next sector for 2nd block */ + ct++; + } + return udata.u_nblock << 8; +bad: + kprintf("fd%d: error %x\n", minor, err); +bad2: + udata.u_error = EIO; + return -1; +} + +int fd_open(uint8_t minor, uint16_t flag) +{ + flag; + if(minor >= MAX_FD) { + udata.u_error = ENODEV; + return -1; + } + return 0; +} + +int fd_read(uint8_t minor, uint8_t rawflag, uint8_t flag) +{ + flag; + return fd_transfer(minor, true, rawflag); +} + +int fd_write(uint8_t minor, uint8_t rawflag, uint8_t flag) +{ + flag;rawflag;minor; + return fd_transfer(minor, false, rawflag); +} diff --git a/Kernel/platform-trs80m1/devfd.h b/Kernel/platform-trs80m1/devfd.h new file mode 100644 index 00000000..672cf907 --- /dev/null +++ b/Kernel/platform-trs80m1/devfd.h @@ -0,0 +1,15 @@ +#ifndef __DEVFD_DOT_H__ +#define __DEVFD_DOT_H__ + +/* public interface */ +int fd_read(uint8_t minor, uint8_t rawflag, uint8_t flag); +int fd_write(uint8_t minor, uint8_t rawflag, uint8_t flag); +int fd_open(uint8_t minor, uint16_t flag); + +/* low level interface */ +uint16_t fd_reset(uint8_t *driveptr); +uint16_t fd_operation(uint8_t *driveptr); +uint16_t fd_motor_on(uint16_t drivesel); +uint16_t fd_motor_off(uint16_t driveptr); + +#endif /* __DEVFD_DOT_H__ */ diff --git a/Kernel/platform-trs80m1/devgfx.c b/Kernel/platform-trs80m1/devgfx.c new file mode 100644 index 00000000..6ea6cbaa --- /dev/null +++ b/Kernel/platform-trs80m1/devgfx.c @@ -0,0 +1,73 @@ +/* + * Graphics logic for the TRS80 graphics add on board + * + * FIXME: - turn the gfx on/off when we switch tty + * - tie gfx to one tty and report EBUSY for the other on enable + */ + +#include +#include +#include +#include +#include + +static const struct display trsdisplay[1] = { + { + /* Once we get around to it this is probably best described as + 128 x 96 sixel */ + 0, + 64, 16, + 64, 16, + 255, 255, + FMT_TEXT, + HW_UNACCEL, + GFX_MULTIMODE|GFX_TEXT, + 2, + 0 + } +}; + +/* Assumes a Tandy board */ +static const struct videomap trsmap = { + 0, + 0, + 0x3C00, + 0x0400, /* Directly mapped */ + 0, 0, /* No segmentation */ + 1, /* Standard spacing */ + MAP_FBMEM_SIMPLE|MAP_FBMEM +}; + +static uint8_t vmode; + +int gfx_ioctl(uint8_t minor, uarg_t arg, char *ptr) +{ + uint8_t m; + int err; + + if (arg >> 8 != 0x03) + return vt_ioctl(minor, arg, ptr); + + switch(arg) { + case GFXIOC_GETINFO: + return uput(&trsdisplay[vmode], ptr, sizeof(struct display)); + case GFXIOC_GETMODE: + case GFXIOC_SETMODE: + m = ugetc(ptr); + if (m != 0) + break; + if (arg == GFXIOC_GETMODE) + return uput(&trsdisplay[m], ptr, sizeof(struct display)); + vmode = m; + return 0; + case GFXIOC_UNMAP: + return 0; + /* Users can "map" 8) the MMIO into their process and use the + card directly */ + case GFXIOC_MAP: + if (vmode == 0) + break; + return uput(&trsmap, ptr, sizeof(trsmap)); + } + return -1; +} diff --git a/Kernel/platform-trs80m1/devgfx.h b/Kernel/platform-trs80m1/devgfx.h new file mode 100644 index 00000000..c29616d8 --- /dev/null +++ b/Kernel/platform-trs80m1/devgfx.h @@ -0,0 +1,12 @@ +#ifndef _DEV_GFX_H +#define _DEV_GFX_H + +#include + +extern int gfx_ioctl(uint8_t minor, uarg_t arg, char *ptr); +extern void video_cmd(uint8_t *ptr); +extern void video_read(uint8_t *ptr); +extern void video_write(uint8_t *ptr); +extern void video_exg(uint8_t *ptr); + +#endif diff --git a/Kernel/platform-trs80m1/devhd.c b/Kernel/platform-trs80m1/devhd.c new file mode 100644 index 00000000..81381c7b --- /dev/null +++ b/Kernel/platform-trs80m1/devhd.c @@ -0,0 +1,178 @@ +/* + * WD1010 hard disk driver + */ + +#define _HD_PRIVATE +#include +#include +#include +#include +#include + +/* Used by the asm helpers */ +uint8_t hd_page; + +/* Swap is scanned for so not constant */ +uint16_t swap_dev; + +/* Seek and restore low 4 bits are the step rate, read/write support + multi-sector mode but not all emulators do .. */ + +struct minipart parts[MAX_HD]; + +/* Wait for the drive to show ready */ +uint8_t hd_waitready(void) +{ + uint8_t st; + do { + st = hd_status; + } while (!(st & 0x40)); + return st; +} + +/* Wait for DRQ or an error */ +uint8_t hd_waitdrq(void) +{ + uint8_t st; + do { + st = hd_status; + } while (!(st & 0x09)); + return st; +} + +uint8_t hd_xfer(bool is_read) +{ + /* Error ? */ + if (hd_status & 0x01) + return hd_status; + if (is_read) + hd_xfer_in(udata.u_dptr); + else + hd_xfer_out(udata.u_dptr); + /* Should be returning READY, and maybe SEEKDONE */ + return hd_status; +} + +int hd_transfer(uint8_t minor, bool is_read, uint8_t rawflag) +{ + uint16_t ct = 0; + staticfast uint8_t tries; + uint8_t err = 0; + uint8_t cmd = HDCMD_READ; + uint8_t head; + uint8_t sector; + staticfast uint16_t cyl; + uint8_t dev = minor >> 4; + staticfast struct minipart *p; + + p = &parts[dev]; + + /* FIXME: We only support 512 byte access chunks even for raw I/O */ + hd_page = 0; + if (rawflag == 1) { + if (d_blkoff(BLKSHIFT)) + return -1; + hd_page = udata.u_page; /* Kernel */ + } else if (rawflag == 2) + hd_page = swappage; + + udata.u_nblock *= 2; + + if (!is_read) + cmd = HDCMD_WRITE; + + /* TRS80 hard disk are 32 sectors/track, 256 byte sectors */ + + hd_precomp = p->g.precomp; + hd_seccnt = 1; + + sector = udata.u_block; + sector = (sector << 1) & 0x1E; + + cyl = udata.u_block >> 4; + + /* Do the maths once and on 16 bit numbers */ + head = cyl % p->g.head; + cyl /= p->g.head; + if (minor) + cyl += p->cyl[(minor-1)&0x0F]; + + while (ct < udata.u_nblock) { + /* Head next bits, plus drive */ + hd_sdh = 0x80 | head | (dev << 3); + hd_secnum = sector; + /* cylinder bits */ + hd_cyllo = cyl & 0xFF; + hd_cylhi = cyl >> 8; + + for (tries = 0; tries < 4; tries++) { + /* issue the command */ + hd_cmd = cmd; + /* DRQ will go high once the controller is ready + for us */ + err = hd_waitdrq(); + if (!(err & 1)) { + err = hd_xfer(is_read); + /* Ready, no error ? */ + if ((err & 0x41) == 0x40) + break; + } else + kprintf("hd%d: err %x\n", minor, err); + + if (tries > 1) { + hd_cmd = HDCMD_RESTORE | p->g.seek; + if (hd_waitready() & 1) + kprintf("hd%d: restore error %z\n", minor, err); + } + } + /* FIXME: should we try the other half and then bale out ? */ + if (tries == 3) + goto bad; + ct++; + udata.u_dptr += 256; + sector++; + /* Cheaper than division! */ + if (sector == 32) { + sector = 0; + head++; + if (head == p->g.head) { + head = 0; + cyl++; + } + } + } + return ct << 8; +bad: + if (err & 1) + kprintf("hd%d: error %x\n", minor, hd_err); + else + kprintf("hd%d: status %x\n", minor, err); +bad2: + udata.u_error = EIO; + return -1; +} + +int hd_open(uint8_t minor, uint16_t flag) +{ + uint8_t dev = minor >> 4; + flag; + if (dev > MAX_HD || parts[dev].g.head == 0 || + (minor && parts[dev].cyl[(minor-1)&0x0F] == 0xFFFF)) { + udata.u_error = ENODEV; + return -1; + } + return 0; +} + +int hd_read(uint8_t minor, uint8_t rawflag, uint8_t flag) +{ + flag; + return hd_transfer(minor, true, rawflag); +} + +int hd_write(uint8_t minor, uint8_t rawflag, uint8_t flag) +{ + flag; + return hd_transfer(minor, false, rawflag); +} + diff --git a/Kernel/platform-trs80m1/devhd.h b/Kernel/platform-trs80m1/devhd.h new file mode 100644 index 00000000..2f06e0bd --- /dev/null +++ b/Kernel/platform-trs80m1/devhd.h @@ -0,0 +1,58 @@ +#ifndef __DEVHD_DOT_H__ +#define __DEVHD_DOT_H__ + +/* public interface */ +extern int hd_read(uint8_t minor, uint8_t rawflag, uint8_t flag); +extern int hd_write(uint8_t minor, uint8_t rawflag, uint8_t flag); +extern int hd_open(uint8_t minor, uint16_t flag); + +extern void hd_probe(void); + +#ifdef _HD_PRIVATE + +__sfr __at 0xC0 hd_wpbits; /* Write protect and IRQ (not used) */ +__sfr __at 0xC1 hd_ctrl; /* Reset and enable bits */ +__sfr __at 0xC8 hd_data; +__sfr __at 0xC9 hd_precomp; /* W/O */ +__sfr __at 0xC9 hd_err; /* R/O */ +__sfr __at 0xCA hd_seccnt; +__sfr __at 0xCB hd_secnum; +__sfr __at 0xCC hd_cyllo; +__sfr __at 0xCD hd_cylhi; +__sfr __at 0xCE hd_sdh; +__sfr __at 0xCF hd_status; /* R/O */ +__sfr __at 0xCF hd_cmd; + +#define HDCMD_RESTORE 0x10 +#define HDCMD_READ 0x20 +#define HDCMD_WRITE 0x30 +#define HDCMD_VERIFY 0x40 /* Not on the 1010 later only */ +#define HDCMD_FORMAT 0x50 +#define HDCMD_INIT 0x60 /* Ditto */ +#define HDCMD_SEEK 0x70 + +#define RATE_4MS 0x08 /* 4ms step rate for hd (conservative) */ + +#define HDCTRL_SOFTRESET 0x10 +#define HDCTRL_ENABLE 0x08 +#define HDCTRL_WAITENABLE 0x04 + +#define HDSDH_ECC256 0x80 + +/* Seek and restore low 4 bits are the step rate, read/write support + multi-sector mode but not all emulators do .. */ + +#define MAX_HD 4 + +extern struct minipart parts[MAX_HD]; + +extern uint8_t hd_waitready(void); +extern uint8_t hd_waitdrq(void); +extern uint8_t hd_xfer(bool is_read); + +/* helpers in common memory for the block transfers */ +extern int hd_xfer_in(uint8_t *addr); +extern int hd_xfer_out(uint8_t *addr); + +#endif +#endif /* __DEVHD_DOT_H__ */ diff --git a/Kernel/platform-trs80m1/devhd_discard.c b/Kernel/platform-trs80m1/devhd_discard.c new file mode 100644 index 00000000..313b9688 --- /dev/null +++ b/Kernel/platform-trs80m1/devhd_discard.c @@ -0,0 +1,96 @@ +#define _HD_PRIVATE +#include +#include +#include +#include +#include + +/* + * Mini part processing. This and mbr and other things all one day + * need to become a bit more unified. + */ +static void hd_swapon(struct minipart *p, unsigned int d, unsigned int i) +{ + uint16_t cyls; + if (i == 14 || p->cyl[i+1] == 0xFFFF) + cyls = p->g.cyl; + else + cyls = p->cyl[i+1]; + cyls -= p->cyl[i]; + cyls *= p->g.head; + /* This is now the number of sets of 32 sectors (16K) in swap. + We need 32K per process: hardwire it here - FIX if you change + the mapping model */ + + swap_dev = (d << 4) + i + 1; + + if (cyls >= MAX_SWAPS) + cyls = MAX_SWAPS - 1; + for (i = 0; i < cyls; i++) { + swapmap_init(i); + } + kputs("swap-"); +} + +void hd_probe(void) +{ + unsigned int dev = 0; + unsigned int i; + /* Second half of second block */ + struct minipart *p; + + udata.u_dptr = tmpbuf(); + p = (struct minipart *)(udata.u_dptr + 128); + + for (dev = 0; dev < 4; dev++) { + hd_sdh = 0x80 | (dev << 3); + hd_cmd = HDCMD_RESTORE | RATE_4MS; + if (hd_waitready() & 1) { + if ((hd_err & 0x12) == 0x12) + continue; + } + hd_seccnt = 1; + hd_sdh = 0x80 | (dev << 3); + hd_secnum = 1; + hd_cyllo = 0; + hd_cylhi = 0; + hd_cmd = HDCMD_READ; + if (hd_waitdrq() & 1) + continue; + if((hd_xfer(1) & 0x41) != 0x40) + continue; + kprintf("hd%c: ", dev + 'a'); + if (p->g.magic != MP_SIG_0) { + p->g.cyl = 1; + p->g.head = 1; + p->g.sec = 32; + p->g.precomp = 0; + p->g.seek = 10; + p->g.secsize = 8; + for (i = 0; i < 15; i++) + p->cyl[i] = 0xFFFFU; + kputs("(unlabelled)\n"); + } else { + for (i = 0; i < 15; i++) { + if (p->cyl[i] != 0xFFFFU) { + if (p->type[i] == 0x56) { + /* Configure swap */ + hd_swapon(p, dev, i); + } + kprintf("hd%c%d ", dev + 'a', i + 1); + } + } + kputs("\n"); + } + if (p->g.seek) { + p->g.seek /= 2; + if (p->g.seek == 0) + p->g.seek = 1; + } + /* Set the step rate */ + hd_cmd = HDCMD_SEEK | p->g.seek; + hd_waitready(); + memcpy(&parts[dev], p, sizeof(parts[dev])); + } + tmpfree(udata.u_dptr); +} diff --git a/Kernel/platform-trs80m1/devices.c b/Kernel/platform-trs80m1/devices.c new file mode 100644 index 00000000..0f138135 --- /dev/null +++ b/Kernel/platform-trs80m1/devices.c @@ -0,0 +1,37 @@ +#include +#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 */ + { hd_open, no_close, hd_read, hd_write, no_ioctl }, + /* 1: /dev/fd Floppy disc block devices */ + { fd_open, no_close, fd_read, fd_write, no_ioctl }, + /* 2: /dev/tty TTY devices */ + { tty_open, trstty_close, tty_read, tty_write, gfx_ioctl }, + /* 3: /dev/lpr Printer devices */ + { lpr_open, lpr_close, no_rdwr, lpr_write, no_ioctl }, + /* 4: /dev/mem etc System devices (one offs) */ + { no_open, no_close, sys_read, sys_write, sys_ioctl }, +}; + +bool validdev(uint16_t dev) +{ + /* This is a bit uglier than needed but the right hand side is + a constant this way */ + if(dev > ((sizeof(dev_tab)/sizeof(struct devsw)) << 8) - 1) + return false; + else + return true; +} + diff --git a/Kernel/platform-trs80m1/devlpr.c b/Kernel/platform-trs80m1/devlpr.c new file mode 100644 index 00000000..a55ae297 --- /dev/null +++ b/Kernel/platform-trs80m1/devlpr.c @@ -0,0 +1,52 @@ +#include +#include +#include +#include + +#define lp *((volatile uint8_t *)0x37E8) + +int lpr_open(uint8_t minor, uint16_t flag) +{ + minor; flag; // shut up compiler + return 0; +} + +int lpr_close(uint8_t minor) +{ + minor; // shut up compiler + return 0; +} + +static uint8_t iopoll(void) +{ + /* Ought to be a core helper for this lot ? */ + if (need_reschedule()) + _sched_yield(); + if (chksigs()) { + if (!udata.u_done) { + udata.u_error = EINTR; + udata.u_done = (usize_t)-1; + } + return 1; + } + return 0; +} + +int lpr_write(uint8_t minor, uint8_t rawflag, uint8_t flag) +{ + char *p = udata.u_base; + minor; rawflag; flag; // shut up compiler + + while(udata.u_done < udata.u_count) { + /* Note; on real hardware it might well be necessary to + busy wait a bit just to get acceptable performance */ + while (lp & 0x80) { + if (iopoll()) + return udata.u_done; + } + /* FIXME: tidy up ugetc and sysio checks globally */ + lp = ugetc(p++); + udata.u_done++; + } + return udata.u_done; +} diff --git a/Kernel/platform-trs80m1/devlpr.h b/Kernel/platform-trs80m1/devlpr.h new file mode 100644 index 00000000..7765c187 --- /dev/null +++ b/Kernel/platform-trs80m1/devlpr.h @@ -0,0 +1,8 @@ +#ifndef __DEVLPR_DOT_H__ +#define __DEVLPR_DOT_H__ + +int lpr_open(uint8_t minor, uint16_t flag); +int lpr_close(uint8_t minor); +int lpr_write(uint8_t minor, uint8_t rawflag, uint8_t flag); + +#endif diff --git a/Kernel/platform-trs80m1/devtty.c b/Kernel/platform-trs80m1/devtty.c new file mode 100644 index 00000000..eed87dbd --- /dev/null +++ b/Kernel/platform-trs80m1/devtty.c @@ -0,0 +1,252 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +char tbuf1[TTYSIZ]; +char tbuf2[TTYSIZ]; + +uint8_t curtty; /* output side */ +uint8_t inputtty; /* input side */ +static struct vt_switch ttysave[2]; +static uint8_t vtbackbuf[VT_WIDTH * VT_HEIGHT]; +struct vt_repeat keyrepeat; + +uint8_t *vtbase[2] = { 0xF800, vtbackbuf }; + +__sfr __at 0xE8 tr1865_ctrl; +__sfr __at 0xE9 tr1865_baud; +__sfr __at 0xEA tr1865_status; +__sfr __at 0xEB tr1865_rxtx; + +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 }, + { tbuf2, tbuf2, tbuf2, TTYSIZ, 0, TTYSIZ/2 } +}; + +/* Write to system console */ +void kputchar(char c) +{ + if(c=='\n') + tty_putc(1, '\r'); + tty_putc(1, c); +} + +ttyready_t tty_writeready(uint8_t minor) +{ + uint8_t reg; + if (minor != 3) + return TTY_READY_NOW; + reg = tr1865_status; + return (reg & 0x40) ? TTY_READY_NOW : TTY_READY_SOON; +} + +void vtbuf_init(void) +{ + memset(vtbackbuf, ' ', VT_WIDTH * VT_HEIGHT); +} + +void tty_putc(uint8_t minor, unsigned char c) +{ + if (minor == 2) + tr1865_rxtx = c; + else + vtoutput(&c, 1); +} + +void tty_interrupt(void) +{ + uint8_t reg = tr1865_status; + if (reg & 0x80) { + reg = tr1865_rxtx; + tty_inproc(3, reg); + } +} + +/* Called to set baud rate etc */ +static const uint8_t trsbaud[] = { + 0,0,1,2, 3,4,5,6, 7,10,14, 15 +}; + +static const uint8_t trssize[4] = { + 0x00, 0x40, 0x20, 0x60 +}; + +void tty_setup(uint8_t minor) +{ + uint8_t baud; + uint8_t ctrl; + if (minor != 2) + return; + baud = ttydata[2].termios.c_cflag & CBAUD; + if (baud > B19200) { + ttydata[2].termios.c_cflag &= ~CBAUD; + ttydata[2].termios.c_cflag |= B19200; + baud = B19200; + } + baud = trsbaud[baud]; + tr1865_baud = baud | (baud << 4); + + ctrl = 3; + if (ttydata[2].termios.c_cflag & PARENB) { + if (ttydata[2].termios.c_cflag & PARODD) + ctrl |= 0x80; + } else + ctrl |= 0x8; /* No parity */ + ctrl |= trssize[(ttydata[2].termios.c_cflag & CSIZE) >> 4]; + tr1865_ctrl = ctrl; +} + +int trstty_close(uint8_t minor) +{ + if (minor == 2 && ttydata[2].users == 0) + tr1865_ctrl = 0; /* Drop carrier */ + return tty_close(minor); +} + +int tty_carrier(uint8_t minor) +{ + if (minor != 2) + return 1; + if (tr1865_ctrl & 0x80) + return 1; + return 0; +} + +void tty_sleeping(uint8_t minor) +{ + used(minor); +} + +uint8_t keymap[8]; +static uint8_t keyin[8]; +static uint8_t keybyte, keybit; +static uint8_t newkey; +static int keysdown = 0; +static uint8_t shiftmask[8] = { + 0, 0, 0, 0, 0, 0, 0, 7 +}; + +static void keyproc(void) +{ + int i; + uint8_t key; + + for (i = 0; i < 8; i++) { + /* Set one of A0 to A7, and read the byte we get back. + Invert that to get a mask of pressed buttons */ + keyin[i] = *(uint8_t *)(0xF400 | (1 << i)); + key = keyin[i] ^ keymap[i]; + if (key) { + int n; + int m = 1; + for (n = 0; n < 8; 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 += m; + + } + } + keymap[i] = keyin[i]; + } +} + +uint8_t keyboard[8][8] = { + {'@', 'a', 'b', 'c', 'd', 'e', 'f', 'g' }, + {'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o' }, + {'p', 'q', 'r', 's', 't', 'u', 'v', 'w' }, + {'x', 'y', 'z', '[', '\\', ']', '^', '_' }, + {'0', '1', '2', '3', '4', '5', '6', '7' }, + {'8', '9', ':', ';', ',', '-', '.', '/' }, + { KEY_ENTER, KEY_CLEAR, KEY_STOP, KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT, ' '}, + { 0, 0, 0, 0, KEY_F1, KEY_F2, KEY_F3, 0 } +}; + +uint8_t shiftkeyboard[8][8] = { + {'@', 'A', 'B', 'C', 'D', 'E', 'F', 'G' }, + {'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O' }, + {'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W' }, + {'X', 'Y', 'Z', '{', '|', '}', '^', '_' }, + {'0', '!', '"', '#', '$', '%', '&', '\'' }, + {'(', ')', '*', '+', '<', '=', '>', '?' }, + { KEY_ENTER, KEY_CLEAR, KEY_STOP, KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT, ' '}, + { 0, 0, 0, 0, KEY_F1, KEY_F2, KEY_F3, 0 } +}; + +static uint8_t capslock = 0; +static uint8_t kbd_timer; + +static void keydecode(void) +{ + uint8_t c; + + if (keybyte == 7 && keybit == 3) { + capslock = 1 - capslock; + return; + } + + if (keymap[7] & 3) { /* shift */ + c = shiftkeyboard[keybyte][keybit]; + } else + c = keyboard[keybyte][keybit]; + + /* The keyboard lacks some rather important symbols so remap them + with control */ + if (keymap[7] & 4) { /* control */ + if (keymap[7] & 3) { /* shift */ + if (c == '(') + c = '{'; + if (c == ')') + c = '}'; + if (c == '-') + c = '_'; + if (c == '/') + c = '``'; + if (c == '<') + c = '^'; + } else { + if (c == '(') + c = '['; + else if (c == ')') + c = ']'; + else if (c == '-') + c = '|'; + else if (c > 31 && c < 127) + c &= 31; + } + } + else if (capslock && c >= 'a' && c <= 'z') + c -= 'a' - 'A'; + if (c) + vt_inproc(inputtty+1, c); +} + +void kbd_interrupt(void) +{ + newkey = 0; + keyproc(); + if (keysdown && keysdown < 3) { + if (newkey) { + keydecode(); + kbd_timer = keyrepeat.first; + } else if (! --kbd_timer) { + keydecode(); + kbd_timer = keyrepeat.continual; + } + } +} diff --git a/Kernel/platform-trs80m1/devtty.h b/Kernel/platform-trs80m1/devtty.h new file mode 100644 index 00000000..12714e4d --- /dev/null +++ b/Kernel/platform-trs80m1/devtty.h @@ -0,0 +1,17 @@ +#ifndef _DEVTTY_H +#define _DEVTTY_H + +extern void tty_interrupt(void); +extern void kbd_interrupt(void); +extern int trstty_close(uint8_t minor); +extern void vtbuf_init(void); + +#define KEY_ROWS 8 +#define KEY_COLS 8 +extern uint8_t keymap[8]; +extern uint8_t keyboard[8][8]; +extern uint8_t shiftkeyboard[8][8]; + +extern uint8_t *vtbase[2]; +extern uint8_t curtty; +#endif diff --git a/Kernel/platform-trs80m1/discard.c b/Kernel/platform-trs80m1/discard.c new file mode 100644 index 00000000..9dcac114 --- /dev/null +++ b/Kernel/platform-trs80m1/discard.c @@ -0,0 +1,36 @@ +#include +#include +#include +#include + +void device_init(void) +{ + vtbuf_init(); +#ifdef CONFIG_RTC + /* Time of day clock */ + inittod(); +#endif + hd_probe(); + tty_setup(3); +} + +void map_init(void) +{ +} + +uint8_t nbanks = 2; /* Default 2 banks, unless port 94 probe updates */ + +void pagemap_init(void) +{ +#ifdef CONFIG_MAP94 + int i = nbanks - 1; + while (i) { + pagemap_add(i); /* Mode 3, U64K low 32K mapped as low 32K */ + pagemap_add(i|0x80); /* Mode 3, U64K high 32K mapped as low 32K */ + i--; + } +#else + pagemap_add(0x63); /* Mode 3, U64K low 32K mapped as low 32K */ + pagemap_add(0x73); /* Mode 3, U64K high 32K mapped as low 32K */ +#endif +} diff --git a/Kernel/platform-trs80m1/floppy.s b/Kernel/platform-trs80m1/floppy.s new file mode 100644 index 00000000..a40b6a9b --- /dev/null +++ b/Kernel/platform-trs80m1/floppy.s @@ -0,0 +1,443 @@ +; +; Core floppy routines for the TRS80 1791 FDC +; Based on the 6809 code +; +; FIXME: better drive spin up wait +; FIXME: tandy doubler +; FIXME: correct step rates (per drive ?) +; FIXME: precompensation ?? +; FIXME: support speed setting (turbo cards) +; + .globl _fd_reset + .globl _fd_operation + .globl _fd_motor_on + .globl _fd_motor_off + .globl _fd_map + .globl _fd_selected + .globl _fd_tab + .globl _fd_cmd + .globl map_kernel, map_process_always + +; +; The 1791 is memory mapped +; +FDCREG .equ 0x37EC +FDCTRK .equ 0x37ED +FDCSEC .equ 0x37EE +FDCDATA .equ 0x37EF +; +; Drive select is also more complicated than other models +; +LATCHD0 .equ 0x37E1 ; also drive select +LATCHD1 .equ 0x37E3 +LATCHD2 .equ 0x37E5 +LATCHD3 .equ 0x37E7 + +; +; And the select is arranged as +; bit 0-3 select drives, bit 3 also selects side (so you can't have +; a disk 3 with any double sided drive) +; + +; +; The final bit of weirdness is magic writes control the doubler. This +; actually *changes* the chip currently connected to those addresses +; so things like track must be written in the right order! +; +; Percom (to cmd port) +P_SET_FM .equ 0xFE +P_SET_MFM .equ 0xFF +; Tandy (to track register) +T_SET_FM .equ 0xA0 +T_SET_MFM .equ 0x80 +T_UNSET_PRECOMP .equ 0xC0 +T_SET_PRECOMP .equ 0xE0 + +; +; It's our responsibility to wait 1 second for motor on and to allow +; 80ms for head load. (and we can call 0060 for 14.65 * BC us delay) +; + +; +; interrupt register reports 0x80 for interrut, 0x40 for drq +; (0x20 is the unrelated reset button) +; + +; +; Structures we use +; +; +; Per disk structure to hold device state +; +TRKCOPY .equ 0 + +; +; Command issue +; +CMD .equ 0 +TRACK .equ 1 +SECTOR .equ 2 +DIRECT .equ 3 ; 0 = read 2 = write 1 = status +DATA .equ 4 + + .area _COMMONMEM +; +; Set up and perform a disk operation +; +; IX points to the command block +; BC points to the buffer +; DE points to the track reg copy +; +; Drive must already be selected and density set up +; +fdsetup: + ld hl,#FDCTRK + ld a, (de) + ld (hl), a ; Load track register + cp TRACK(ix) + jr z, fdiosetup ; Is it the one we wanted + ; + ; So we can verify + ; + inc hl ; now FDCTRK + ld a, TRACK(ix) + ld (hl), a + inc hl ; now FDCSEC + ld a, SECTOR(ix) + ld (hl), a + ; + ; Need to seek the disk + ; + ld hl,#FDCREG + ld a, #0x18 ; seek FIXME: need to set step rate + ld (hl), a + call waitcmd + and #0x18 ; error bits + jr z, fdiosetup + ; seek failed, not good +setuptimeout: ; NE = bad + ld a, #0xff ; we have no idea where we are, force a seek + ld (de), a ; zap track info + ld l,#0xFF ; report failure + ret +; +; Try and kick the controller back into sanity +; +bad_cmd: + ld a,(hl) ; clear status + ld (hl),#0xD0 ; force interrupt + pop af + ld l,#255 + ret + +waitcmd: + ex (sp),hl + ex (sp),hl + ex (sp),hl + ex (sp),hl ; 87 clocks + the call (17) = 104 +waitcmdl: + bit 0,(hl) + jr z, waitfail + rlca + ld a,(last_drive) ; Keep the motor spinning + ld (LATCHD0),a + jr c, waitcmdl + ld a,(hl) ; Status + ret +waitfail: + ld a,#255 + ret +; +; Head in the right place +; +fdiosetup: + ld a, TRACK(ix) + ld (de), a ; save track + + ; FIXME: select which controller first etc + + ld de, #FDCSEC ; sector register + ld a, SECTOR(ix) + ld (FDCSEC), a + + inc de ; now points at FDCDATA + + ld hl, #FDCREG ; status/cmd + ld a,(hl) ; clear status + + ld a, DIRECT(ix) + dec a + ld a, CMD(ix) ; 0 - none, 1 - in, 2 = out + ld (hl),a + push af ; 11 + ex (sp),hl ; 30 Delay 55us (98 clocks) + ex (sp),hl ; 49 + ex (sp),hl ; 68 + ex (sp),hl ; 87 + add a,#0 ; 94 + di ; 98 + bit 0,(hl) ; controller busy ? + jr z, bad_cmd + pop af + jr z, fdio_in + jr nc, fdio_out + jr fd_xferdone +; +; Read from the disk - HL points to the target buffer. This is very +; tight timing on a 2MHz Z80 with a doubler. +; +; We point HL at control/status, and DE at the data port. We can't do +; this with IX offsets as we don't have enough clocks for it. +; +fdio_in: +fdio_do_in: + ld a,#0x83 ; Wait for the controller to go + and (hl) ; ready + jp po, fdio_do_in +fdio_xfer_in: + ld a,(de) ; 7 data from the port ASAP + ld (bc),a ; 7 to memory + inc bc ; 6 aligned buffer would be 4 + ; + ; Old trick. What we are effectively doing is synchronizing + ; the controller and CPU knowing the worst case misalignment + ; + ; We simply don't have time for this to be a loop + ; + ; Our best case is 44 clocks per loop so 25us / loop. We have + ; 31us when running double density so will alternate between + ; 44 clock loops and 63 clock (35us) loops. + ; + bit 1,(hl) ; 12 clocks + jr nz, fdio_xfer_in ; 7 clocks if not taken + bit 1,(hl) + jr nz, fdio_xfer_in + bit 1,(hl) + jr nz, fdio_xfer_in + bit 1,(hl) ; we may have a CPU speed upgrade + jr nz, fdio_xfer_in ; fitted... + bit 1,(hl) + jr nz, fdio_xfer_in + bit 1,(hl) + jr nz, fdio_xfer_in + ; + ; If another byte hasn't turned up in 114 clocks then either + ; it failed or it finished. Until that happens we can't afford + ; to check anything else + ; +fd_xferdone: + ld l,(hl) ; read the status + ld (hl),#0xD0 ; force interrupt + ei + ret ; pass C code the status byte + +; +; Very similar to the above but going the other way +; +fdio_out: + ld (hl),a ; issue command +fdio_do_out: + ld a,#0x83 ; Wait for the controller to go + and (hl) ; ready + jp po, fdio_out +fdio_xfer_out: + ld a,(bc) ; 7 data from the user + ld (de),a ; 7 to the port + inc bc ; 6 aligned buffer would be 4 + ; + ; Old trick. What we are effectively doing is synchronizing + ; the controller and CPU knowing the worst case misalignment + ; + ; We simply don't have time for this to be a loop + ; + ; Our best case is 44 clocks per loop so 25us / loop. We have + ; 31us when running double density so will alternate between + ; 44 clock loops and 63 clock (35us) loops. + ; + bit 1,(hl) ; 12 clocks + jr nz, fdio_xfer_out ; 7 clocks if not taken + bit 1,(hl) + jr nz, fdio_xfer_out + bit 1,(hl) + jr nz, fdio_xfer_out + bit 1,(hl) ; we may have a CPU speed upgrade + jr nz, fdio_xfer_out ; fitted... + bit 1,(hl) + jr nz, fdio_xfer_out + bit 1,(hl) + jr nz, fdio_xfer_out +; +; Now tidy up +; + jr fd_xferdone + + +; +; C glue interface. +; +; +; Reset to track 0, wait for the command then idle +; +; fd_reset(uint8_t *drvptr) +; +_fd_reset: + pop de + pop hl + push hl + push de + ld a, #1 + ld (FDCSEC), a + xor a + ld (FDCTRK), a + ld a, #0x0C + ld (FDCREG), a ; restore + ld a, #0xFF + ld (hl), a ; Zap track pointer + call waitcmd + cp #0xff + ret z + and #0x99 ; Error bit from the reset + ret nz + ld (hl), a ; Track 0 correctly hit (so 0) + ret +; +; fd_operation(uint16_t *cmd, uint16_t *drive) +; +; The caller must ensure the drive has been selected and the motor is +; running. +; +_fd_operation: + ld a, (_fd_map) + or a + call nz, map_process_always + pop bc ; return address + pop de ; drive track ptr + push de + push bc + push ix + ld ix, #_fd_cmd + ld c, DATA(ix) + ld b, DATA+1(ix) + call fdsetup ; Set up for a command + ld h, #0 + pop ix + ld a, (_fd_map) + or a + ret z + jp map_kernel +; +; Delay loops +; +wait45: ; wait 45ms for head load. HL points to + ld bc,#2662 ; latch - D is latch value, preserve HL/DE +wait45l: + dec bc ; 6 30 cycles per loop (16.9us) + ld a,b ; 4 + or c ; 4 + jr nz, wait45l ; 12/7 + ret + +; +; Wait about 500ms while poking the motor to keep it spinning. +; +waitdisk: + ld b,#11 +waitdiskl: + push bc + call wait45l + ld (hl),d ; tickle the motor + pop bc + djnz waitdiskl + ret +; +; +; C interface fd_motor_on(uint16_t drivesel) +; +; Selects this drive and turns on the motors. Also pass in the +; choice of density +; +; bits 0-3: select that drive (yes side and drive 3 clash) +; bit 3: side (must rewrite each drive change) +; bit 7: set for double density (MFM) +; +; +_fd_motor_on: + pop de + pop bc + push bc + push de + + ; + ; Is the motor running ? + ; + ld a,(LATCHD0) + ld e, a ; save the latch status (motor on bit) + rlca + ld a,(last_drive) + jr nc, must_config ; if the motor is off always do set up + ; + ; Are we changing our selection ? + ; + cp c + jr nz, must_config + ; + ; Motor running, same configuration. Poke the selection + ; so the motor stays running. + ; + ld (LATCHD0),a + ret + +must_config: + ld a,c ; Save the new configuration + ld (last_drive),a + + ld hl,#FDCREG + + and #0x7F ; We borrowed bit 7 for our own use + ld (LATCHD0), a ; Selects the actual disk we want + ld d,a ; Save latch value + rl c ; Bit 7 into C + ld a,#0xFE ; Figure out the density + adc a,#0 ; FE or FF according to density + di + ld (hl),a ; if a doubler is present this switches FDC + ; Do we need a delay here (eg if there is no doubler present) + ; and do we need to avoid the FE/FF scribbles on doubler-less hw + ; as we are writing twice to the FDC a few clocks apart otherwise + ld (hl),#0xD0 ; Hit it over the head with a hammer + ei + + ld hl,#LATCHD0 ; used by waitdisk too + + bit 7,e ; was the motor running + jr z, motor_running + + call waitdisk ; wait 500ms or so for spin up +motor_running: + ld (hl),d + call wait45 ; Wait 45ms for the head to load + ld (hl),d ; Reset timer + ret + +; +; C interface fd_motor_off(void) +; +; Turns off the drive motors, deselects all drives +; +; Not sure we need this. +; +_fd_motor_off: + xor a + ld (LATCHD0),a + ret + +last_drive: + .db 0xff +_fd_map: + .db 0 +_fd_selected: + .db 0xFF +_fd_tab: + .db 0xFF, 0xFF, 0xFF, 0xFF +_fd_cmd: + .ds 6 diff --git a/Kernel/platform-trs80m1/fuzix.lnk b/Kernel/platform-trs80m1/fuzix.lnk new file mode 100644 index 00000000..213593f1 --- /dev/null +++ b/Kernel/platform-trs80m1/fuzix.lnk @@ -0,0 +1,48 @@ +-mwxuy +-r +-i fuzix.ihx +-b _INITIALIZER=0x0001 +-b _CODE=0x4200 +-b _CODE1=0x8000 +-b _CODE2=0x8000 +-b _COMMONMEM=0x4300 +-l z80 +platform-trs80m1/crt0.rel +platform-trs80m1/commonmem.rel +platform-trs80m1/trs80.rel +platform-trs80m1/trs80-bank.rel +start.rel +version.rel +lowlevel-z80-banked.rel +usermem.rel +usermem_std-z80-banked.rel +platform-trs80m1/tricks.rel +platform-trs80m1/main.rel +timer.rel +kdata.rel +platform-trs80m1/devfd.rel +platform-trs80m1/floppy.rel +platform-trs80m1/devhd.rel +platform-trs80m1/devhd_discard.rel +platform-trs80m1/devgfx.rel +platform-trs80m1/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 +mm.rel +swap.rel +bankfixed.rel +vt.rel +devsys.rel +platform-trs80m1/devlpr.rel +platform-trs80m1/devtty.rel +platform-trs80m1/discard.rel +-e diff --git a/Kernel/platform-trs80m1/kernel.def b/Kernel/platform-trs80m1/kernel.def new file mode 100644 index 00000000..ac7c0254 --- /dev/null +++ b/Kernel/platform-trs80m1/kernel.def @@ -0,0 +1,17 @@ +; UZI mnemonics for memory addresses etc + +U_DATA .equ 0x4300 ; (this is struct u_data from kernel.h) +U_DATA__TOTALSIZE .equ 0x200 ; 256+256 (we don't save istack) + +U_DATA_STASH .equ 0xFE00 ; FE00-FFFF + +PROGBASE .equ 0x8000 +PROGLOAD .equ 0x8000 + +Z80_TYPE .equ 1 + +NBUFS .equ 5 + +Z80_MMU_HOOKS .equ 0 + +CONFIG_SWAP .equ 1 diff --git a/Kernel/platform-trs80m1/main.c b/Kernel/platform-trs80m1/main.c new file mode 100644 index 00000000..3d03046a --- /dev/null +++ b/Kernel/platform-trs80m1/main.c @@ -0,0 +1,76 @@ +#include +#include +#include +#include +#include + +uint16_t ramtop = PROGTOP; + +uint8_t vtattr_cap; + +struct blkbuf *bufpool_end = bufpool + NBUFS; + +/* We need to spin here because we don't have interrupts for the UART on the + model I */ +void platform_idle(void) +{ + irqflags_t irq = di(); + platform_interrupt(); + irqrestore(irq); +} + +void do_beep(void) +{ +} + +uint8_t platform_param(char *p) +{ + used(p); + return 0; +} + +void platform_interrupt(void) +{ + uint8_t irq = *((volatile uint8_t *)0x37E0); + uint8_t dummy; + /* FIXME: do we care about floppy interrupts */ + if (irq & 0x80) + *((volatile uint8_t *)0x37EC); + else { + tty_interrupt(); + kbd_interrupt(); + if (irq & 0x80) { /* FIXME??? */ + timer_interrupt(); + *((volatile uint8_t *)0x37E0); /* Ack the timer */ + } + } +} + +/* + * We can't recover discard space usefully... yet. I have a cunning plan + * involving external buffers in the spare bank space 8) + */ + +void platform_discard(void) +{ +} + +#ifdef CONFIG_RTC + +__sfr __at 0xB0 rtc_secl; +__sfr __at 0xB1 rtc_sech; + +/* FIXME: the RTC is optional so we should test for it first */ +uint8_t platform_rtc_secs(void) +{ + uint8_t sl, rv; + /* BCD encoded */ + do { + sl = rtc_secl; + rv = sl + rtc_sech * 10; + } while (sl != rtc_secl); + return rv; +} + +#endif + diff --git a/Kernel/platform-trs80m1/rules.mk b/Kernel/platform-trs80m1/rules.mk new file mode 100644 index 00000000..64073e54 --- /dev/null +++ b/Kernel/platform-trs80m1/rules.mk @@ -0,0 +1,20 @@ +# +# TRS80 model 1 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 CODE1 +export CROSS_CC_SEG2=--codeseg CODE2 +export CROSS_CC_SEG3=--codeseg CODE1 +export CROSS_CC_VIDEO=--codeseg CODE2 +# +export CROSS_CC_SYS1=--codeseg CODE1 +export CROSS_CC_SYS2=--codeseg CODE1 +export CROSS_CC_SYS3=--codeseg CODE1 +export CROSS_CC_SYS4=--codeseg CODE2 +export CROSS_CC_SYS5=--codeseg CODE2 +export CROSS_CC_SEGDISC=--codeseg DISCARD2 --constseg DISCARD2 + diff --git a/Kernel/platform-trs80m1/target.mk b/Kernel/platform-trs80m1/target.mk new file mode 100644 index 00000000..3bffcde0 --- /dev/null +++ b/Kernel/platform-trs80m1/target.mk @@ -0,0 +1 @@ +export CPU = z80 diff --git a/Kernel/platform-trs80m1/tricks.s b/Kernel/platform-trs80m1/tricks.s new file mode 100644 index 00000000..08a3be56 --- /dev/null +++ b/Kernel/platform-trs80m1/tricks.s @@ -0,0 +1,5 @@ + + .include "../kernel.def" + .include "kernel.def" + + .include "../lib/z80fixedbank-banked.s" diff --git a/Kernel/platform-trs80m1/trs80-bank.s b/Kernel/platform-trs80m1/trs80-bank.s new file mode 100644 index 00000000..f89ac75d --- /dev/null +++ b/Kernel/platform-trs80m1/trs80-bank.s @@ -0,0 +1,261 @@ +; +; TRS 80 banking logic for the base Model 4 and 4P +; + + .module trs80bank + + ; exported symbols + .globl init_hardware + .globl map_kernel + .globl map_process + .globl map_process_a + .globl map_process_always + .globl map_process_save + .globl map_kernel_restore + .globl map_save + .globl map_restore + + ; imported symbols + .globl _program_vectors + .globl _ramsize + .globl _procmem + + .globl s__COMMONMEM + .globl l__COMMONMEM + + .include "kernel.def" + .include "../kernel.def" + +; ----------------------------------------------------------------------------- +; KERNEL MEMORY BANK (below 0xE800, only accessible when the kernel is mapped) +; ----------------------------------------------------------------------------- + .area _CODE + +init_hardware: + ; set system RAM size + ; TODO - sizing + ld hl, #144 + ld (_ramsize), hl + ld hl, #(144-80) ; 80K for kernel + ld (_procmem), hl + + ; set up interrupt vectors for the kernel (also sets up common memory in page 0x000F which is unused) + ld hl, #0 + push hl + call _program_vectors + pop hl + + im 1 ; set CPU interrupt mode + ret + + +;------------------------------------------------------------------------------ +; COMMON MEMORY PROCEDURES FOLLOW + + .area _COMMONMEM + +ksave_map: .db 0x00 ; saved kernel map version +map_port: .db 0x43 ; cheap hack so we can ld bc and out (c),b +map_reg: .db 0x00 ; current value written to map register +map_store: .db 0x00 ; save value from map_save +; +; Map in the kernel. We are in common and all kernel entry points are +; in common. Thus someone will always have banked in the right page +; before jumping out of common. Thus we don't need to do anything but +; restore the last saved kernel map! +; +map_kernel: +map_kernel_restore: + push af + ld a, (ksave_map) + ld (map_reg), a + out (0x43), a + pop af + ret +; +; Select the bank for the relevant process. Update the ksave_map so we +; can restore the correct kernel mapping when banked. +; +map_process: + ld a, h + or l + jr z, map_kernel +map_process_hl: + ld a, (map_reg) + ld (ksave_map),a ; for map_kernel_restore + ld a,(hl) ; udata page + ld (map_reg),a + out (0x43), a + ret + +map_process_a: ; used by bankfork + push af + ld a, (map_reg) + ld (ksave_map), a + pop af + ld (map_reg), a + out (0x43), a + ret + +map_process_save: +map_process_always: + push af + ld a, (map_reg); + ld (map_store), a + pop af + ret + +map_save: push af + ld a, (map_reg) + ld (map_store), a + pop af + ret + +map_restore: + push af + ld a, (map_store) + ld (map_reg), a + out (0x43), a + pop af + ret + +; +; This lot is tricky. +; + .globl __bank_0_1 + .globl __bank_0_2 + .globl __bank_1_2 + .globl __bank_2_1 + .globl __stub_0_1 + .globl __stub_0_2 + +; +; Start with the easy ones - we are going from common +; to a bank. We can use registers here providing the compiler expects +; them to be destroyed by the called function, and on the return +; providing they are also not a return value. In practice that means +; we need to avoid IX going in and IX HL DE coming out. +; +; The linker rewrote +; push af call foo pop af +; into +; call __bank_0_1 defw foo +; +; We expand these into separate functions as they are executed a fair +; bit +; +; The only hard case to understand logically here is calls and stubs +; from common to a bank. In those cases we must save the previous bank +; and restore it. We do this because we optimise the performance by +; making calls into COMMON or CODE free of bank work so we can put +; performance sensitive widely used routines (like compiler helpers) +; there. The cost of that is that if we do call back out of common +; space we have to do the restore as we return. +; +__bank_0_2: ; Call from common to bank 2 + ld a,#1 + jr bankina +__bank_0_1: ; Call from common to bank 1 + xor a +bankina: + pop hl ; Get the return address + ld e,(hl) ; The two bytes following it are the true + inc hl ; function to call + ld d,(hl) + inc hl + push hl + ld bc,(map_port) + ld (map_reg),a + out (c),a + ex de,hl + ld a,b + or a + jr z, retbank1 ; Arrange that we put the banks back as they + call callhl ; were before the call. Needed because calls + ld a,#1 ; to common routines don't do banking so + ld (map_reg),a ; the return won't either. + out (0x43),a + ret +retbank1: + call callhl + xor a + ld (map_reg),a + out (0x43),a + ret + +__bank_1_2: + ld a,#1 + pop hl ; Get the return address + ld e,(hl) ; The two bytes following it are the true + inc hl ; function to call + ld d,(hl) + inc hl + push hl + ld bc,(map_port) + ld (map_reg),a + out (c),a + ex de,hl + call callhl + xor a + ld (map_reg),a + out (0x43),a + ret + +__bank_2_1: + xor a + pop hl ; Get the return address + ld e,(hl) ; The two bytes following it are the true + inc hl ; function to call + ld d,(hl) + inc hl + push hl + ld bc,(map_port) + ld (map_reg),a + out (c),a + ex de,hl + call callhl + ld a,#1 + ld (map_reg),a + out (0x43),a + ret + +; +; Stubs are similar. Where there is a function pointer the linker adds +; a call in common and resolves the function pointer to the code in +; common. The function stub it adds is simply +; +; ld de,#realaddr +; call __stub_0_%d where %d is the target bank +; +; For the same reason as bank calls we need to restore the previous +; banking setup. +; +__stub_0_1: + xor a + jr stub_call +__stub_0_2: + ld a,#1 +stub_call: + pop hl ; return address + ex (sp),hl ; swap the junk word for the return + ld bc,(map_port) + ld (map_reg),a + out (c),a + ex de,hl + ld a,b + or a + jr z, stub_ret_1 + call callhl + ld a,#1 + jr stub_ret +stub_ret_1: + call callhl ; invoke code in other bank + xor a ; and back to bank 1 +stub_ret: + ld (map_reg),a + out (0x43),a + pop bc ; bc is now the return + push bc ; stack it twice + push bc + ret ; and ret - can't use jp (ix) or jp (hl) here +callhl: jp (hl) diff --git a/Kernel/platform-trs80m1/trs80.s b/Kernel/platform-trs80m1/trs80.s new file mode 100644 index 00000000..0307f221 --- /dev/null +++ b/Kernel/platform-trs80m1/trs80.s @@ -0,0 +1,136 @@ +; +; TRS 80 hardware support +; + + .module trs80 + + ; exported symbols + .globl init_early + .globl init_hardware + .globl interrupt_handler + .globl _program_vectors + .globl platform_interrupt_all + + ; hard disk helpers + .globl _hd_xfer_in + .globl _hd_xfer_out + ; and the page from the C code + .globl _hd_page + + ; exported debugging tools + .globl _platform_monitor + .globl _platform_reboot + .globl outchar + + ; imported symbols + .globl _ramsize + .globl _procmem + .globl istack_top + .globl istack_switched_sp + .globl unix_syscall_entry + .globl outcharhex + .globl null_handler + .globl map_kernel + .globl map_process + .globl map_process_a + .globl map_process_always + .globl map_save + .globl map_restore + + .globl s__COMMONMEM + .globl l__COMMONMEM + + .globl _bufpool + .globl bufend + + .include "kernel.def" + .include "../kernel.def" + + .area _BUFFERS + +_bufpool: + .ds BUFSIZE * NBUFS +bufend: +; ----------------------------------------------------------------------------- +; COMMON MEMORY BANK (0xE800 upwards) +; ----------------------------------------------------------------------------- + .area _COMMONMEM + +_platform_monitor: + di + halt + +platform_interrupt_all: + ret + +_platform_reboot: + di + ld sp,#0xffff + xor a + out (0x43),a + rst 0 + +; ----------------------------------------------------------------------------- +; KERNEL MEMORY BANK (above 0x8000) +; ----------------------------------------------------------------------------- + .area _CODE + +init_early: + ret + +;------------------------------------------------------------------------------ +; COMMON MEMORY PROCEDURES FOLLOW + + .area _COMMONMEM + +_program_vectors: + ret + +_rom_vectors: + ld a,#0xC3 + ld (0x4012), a + ld hl, #interrupt_handler + ld (0x4013), hl + + ; set restart vector for UZI system calls + ld (0x400F), a ; (rst 30h is unix function call vector) + ld hl, #unix_syscall_entry + ld (0x4010), hl + + jp map_kernel + +; outchar: Wait for UART TX idle, then print the char in A +; destroys: AF +outchar: + out (0xEB), a + ret + +; +; Swap helpers +; +_hd_xfer_in: + pop de + pop hl + push hl + push de + ld a, (_hd_page) + or a + call nz, map_process_a + ld bc, #0xC8 ; 256 bytes from 0xC8 + inir + call map_kernel + ret + +_hd_xfer_out: + pop de + pop hl + push hl + push de + ld a, (_hd_page) + or a + call nz, map_process_a + ld bc, #0xC8 ; 256 bytes to 0xC8 + otir + call map_kernel + ret + diff --git a/Kernel/platform-trs80m1/trs80load.s b/Kernel/platform-trs80m1/trs80load.s new file mode 100644 index 00000000..f4a22f94 --- /dev/null +++ b/Kernel/platform-trs80m1/trs80load.s @@ -0,0 +1,158 @@ +.z80 +; +; TRS80 bootblock (256 bytes) +; +; FIXME rewrite for model 1 +; +.area BOOT (ABS) +.org 0x000 +start: + ld a, #0x86 ; kernel map, 80 column, no remap + out (0x84), a ; video page 1 + ld a, #0x50 ; 80 column, sound, altchars off, + ; ext I/O on , 4MHz + out (0xEC), a + ld hl, #0x4300 + ld de, #0x0 + ld bc, #256 + ldir + jp go +; +; Assume the boot ROM left us on track 0 and motor on +; +floppy_read: + ld bc, #0x81F4 ; select drive + out (c), b + out (0xF2), a ; sector please + ld a, #0x80 ; READ + out (0xF0), a + ld b, #100 +l1: djnz l1 + ld de, #0x8116 + ld bc, #0xF3 +flopin: in a, (0xF0) + and e + jr z, flopin + ini + ld a, d +flopind: + out (0xF4), a + ini + jr nz, flopind +flopstat: + in a, (0xF0) + and #0x19 + bit 0, a + jr nz, flopstat + or a + ret z + ld de, #0xF850 + call prints + .ascii 'Read\0' +fail: jr fail +; +go: + ld sp, #0xEE00 ; temp stackpointer + + ; load the 6845 parameters + ld hl, #_ctc6845 ; reverse order + ld bc, #0x0F88 +ctcloop: out (c), b ; select register + ld a, (hl) + out (0x89), a ; output data + dec hl + djnz ctcloop + + ; clear screen + ld hl, #0xF800 + ld de, #0xF801 + ld bc, #0x07FF + ld (hl), #' ' + ldir + ld de, #0xF800 + call prints + .ascii 'TRS80Load 0.2\0' + + ld hl, #0x0100 +; + + +lastsec: ld a, (tracknum) + inc a + ld (tracknum), a + cp #40 + jr z, booted + out (0xF3), a + ld a, #0x18 ; seek + out (0xF0), a + ld b, #100 +cmd1: djnz cmd1 +cmdwait: in a, (0xF0) + bit 0, a + jr z, seekstat + bit 7, a + jr z, cmdwait +seekstat: + in a, (0xF0) + and #0x18 + bit 0, a + jr nz, seekstat + or a + jr z, secmove + ld de, #0xF850 + call prints + .ascii 'seek\0' +bad: jr bad +secmove: xor a + dec a + ld (secnum), a +nextsec: + ld a, (secnum) + inc a + ld (secnum), a + cp #18 + jr z, lastsec + push hl + call floppy_read + pop hl + inc h + jr nextsec + +tracknum: .db 28 ; tracks 29-39 (50688 bytes) + + ; ctc6845 registers in reverse order + .db 99 + .db 80 + .db 85 + .db 10 + .db 25 + .db 4 + .db 24 + .db 24 + .db 0 + .db 9 + .db 101 + .db 9 + .db 0 + .db 0 +secnum: .db 0 ; recycle last crc value as secnum so we fit 256 bytes +_ctc6845: .db 0 + + +prints: + pop hl +printsl: + ld a, (hl) + inc hl + or a + jr z, out + ld (de), a + inc de + jr printsl +out: jp (hl) + +booted: ld de, #0xF850 + call prints + .ascii 'Booting..\0' + +; OS starts on the following byte -- 2.34.1