Work in progress.
--- /dev/null
+
+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
--- /dev/null
+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
+
--- /dev/null
+;
+; 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"
+
--- /dev/null
+/* 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);
--- /dev/null
+ ; 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
--- /dev/null
+#include <kernel.h>
+#include <kdata.h>
+#include <printf.h>
+#include <devfd.h>
+
+#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);
+}
--- /dev/null
+#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__ */
--- /dev/null
+/*
+ * 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 <kernel.h>
+#include <kdata.h>
+#include <vt.h>
+#include <graphics.h>
+#include <devgfx.h>
+
+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;
+}
--- /dev/null
+#ifndef _DEV_GFX_H
+#define _DEV_GFX_H
+
+#include <graphics.h>
+
+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
--- /dev/null
+/*
+ * WD1010 hard disk driver
+ */
+
+#define _HD_PRIVATE
+#include <kernel.h>
+#include <kdata.h>
+#include <printf.h>
+#include <devhd.h>
+#include <diskgeom.h>
+
+/* 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);
+}
+
--- /dev/null
+#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__ */
--- /dev/null
+#define _HD_PRIVATE
+#include <kernel.h>
+#include <kdata.h>
+#include <printf.h>
+#include <devhd.h>
+#include <diskgeom.h>
+
+/*
+ * 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);
+}
--- /dev/null
+#include <kernel.h>
+#include <tty.h>
+#include <version.h>
+#include <kdata.h>
+#include <devfd.h>
+#include <devhd.h>
+#include <devsys.h>
+#include <devlpr.h>
+#include <devdw.h>
+#include <vt.h>
+#include <devtty.h>
+#include <devgfx.h>
+
+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;
+}
+
--- /dev/null
+#include <kernel.h>
+#include <version.h>
+#include <kdata.h>
+#include <devlpr.h>
+
+#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;
+}
--- /dev/null
+#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
--- /dev/null
+#include <kernel.h>
+#include <kdata.h>
+#include <printf.h>
+#include <stdbool.h>
+#include <tty.h>
+#include <vt.h>
+#include <devtty.h>
+#include <stdarg.h>
+
+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;
+ }
+ }
+}
--- /dev/null
+#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
--- /dev/null
+#include <kernel.h>
+#include <devhd.h>
+#include <devtty.h>
+#include <tty.h>
+
+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
+}
--- /dev/null
+;
+; 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
--- /dev/null
+-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
--- /dev/null
+; 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
--- /dev/null
+#include <kernel.h>
+#include <timer.h>
+#include <kdata.h>
+#include <printf.h>
+#include <devtty.h>
+
+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
+
--- /dev/null
+#
+# 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
+
--- /dev/null
+export CPU = z80
--- /dev/null
+
+ .include "../kernel.def"
+ .include "kernel.def"
+
+ .include "../lib/z80fixedbank-banked.s"
--- /dev/null
+;
+; 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)
--- /dev/null
+;
+; 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
+
--- /dev/null
+.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