--- /dev/null
+
+CSRCS = devnascom.c devgm8x9.c devgm833.c
+CSRCS += devices.c main.c devinput.c
+DISCARD_CSRCS = discard.c
+
+ASRCS = nascom.s nascom-pagemode.s nascom-vt.s crt0.s
+ASRCS += tricks.s commonmem.s gm8x9.s gm849a_sasi.s gm833.s
+
+DSRCS = ../dev/devscsi.c ../dev/blkdev.c ../dev/mbr.c
+DISCARD_DSRCS = ../dev/devscsi_discard.c
+
+COBJS = $(CSRCS:.c=.rel)
+AOBJS = $(ASRCS:.s=.rel)
+DISCARD_COBJS = $(DISCARD_CSRCS:.c=.rel)
+DOBJS = $(patsubst ../dev/%.c,%.rel, $(DSRCS))
+DISCARD_DOBJS = $(patsubst ../dev/%.c,%.rel, $(DISCARD_DSRCS))
+OBJS = $(COBJS) $(AOBJS) $(DISCARD_COBJS) $(DOBJS) $(DISCARD_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)
+
+$(COBJS): %.rel: %.c
+ $(CROSS_CC) $(CROSS_CCOPTS) -c $<
+
+$(AOBJS): %.rel: %.s
+ $(CROSS_AS) $(ASOPTS) $<
+
+$(DOBJS): %.rel: ../dev/%.c
+ $(CROSS_CC) $(CROSS_CCOPTS) -c $<
+
+$(DISCARD_DOBJS): %.rel: ../dev/%.c
+ $(CROSS_CC) $(CROSS_CCOPTS) $(CROSS_CC_SEGDISC) -c $<
+
+$(DISCARD_COBJS): %.rel: %.c
+ $(CROSS_CC) $(CROSS_CCOPTS) $(CROSS_CC_SEGDISC) -c $<
+
+clean:
+ rm -f $(OBJS) $(JUNK) core *~ *.asm *.rst *.lst *.sym
+
+image:
+ sdasz80 -o nasboot.s
+ sdldz80 -m -i nasboot.rel
+ makebin -s 1024 nasboot.ihx | dd of=nasboot.bin bs=512 skip=1
+ cp ../fuzix.bin fuzix.com
--- /dev/null
+ Nascom Gemini MAP80
+
+00 Keyboard/system
+01 6402 RX/TX
+02 6402 Status
+03 Unused (mainboard)
+04 PIO0A data
+05 PIO0B data
+06 PIO0A ctrl
+07 PIO0B ctrl
+
+08 IO 1 CTC
+09 IO 1 CTC
+08 IO 1 CTC
+0B IO 1 CTC
+0C IO 2 CTC
+0D IO 2 CTC
+0E IO 2 CTC
+0F IO 2 CTC
+
+10 IO CTC
+11 IO 1 6402 data IO CTC
+12 IO 1 6402 status IO CTC
+13 IO CTC
+
+14 IO 1 PIO1 (1) IO PIO1
+15 IO 1 PIO1 IO PIO1
+16 IO 1 PIO1 IO PIO1
+17 IO 1 PIO1 IO PIO1
+
+18 IO 1 PIO2 IO PIO2
+19 IO 1 PIO2 IO PIO2
+1A IO 1 PIO2 IO PIO2
+1B IO 1 PIO2 IO PIO2
+
+1C IO 1 PIO3 IO PIO3
+1D IO 1 PIO3 IO PIO3
+1E IO 1 PIO3 IO PIO3
+1F IO 1 PIO3 IO PIO3
+
+20 RTC
+21 IO 2 6402 data RTC
+22 IO 2 6402 status RTC
+23 RTC
+
+24 IO 2 PIO1 RTC
+25 IO 2 PIO1 RTC
+26 IO 2 PIO1 RTC
+27 IO 2 PIO1 RTC
+
+28 IO 2 PIO1 RTC
+29 IO 2 PIO1 RTC
+2A IO 2 PIO1 RTC
+2B IO 2 PIO1 RTC
+
+2C IO 2 PIO1 RTC
+2D IO 2 PIO1 RTC
+2E IO 2 PIO1 RTC
+2F IO 2 PIO1 RTC
+
+30 ADC via PIO
+31 ADC via PIO
+32 ADC via PIO
+33 ADC via PIO
+
+80 FPU
+81 FPU
+82 FPU
+
+A0 Pluto
+A1 Pluto
+
+B0 AVC CRTC addr
+B1 AVC CRTC data IVC Data
+B2 AVC CRTC control IVC Handshake
+B3 IVC Reset
+
+B4 GM813 PIO (1)
+B5 GM813 PIO
+B6 GM813 PIO
+B7 GM813 PIO
+
+B8 8250@2MHz
+B9 8250 (2)
+BA 8250
+BB 8250
+BC 8250
+BD 8250
+BE 8250
+BF 8250
+
+C0 MV256
+C1 MV256
+C2 MV256
+C3 MV256
+C4 MV256
+C5 MV256
+C6 MV256
+C7 MV256
+C8 MV256
+C9 MV256
+CA MV256
+CB MV256
+CC MV256
+CD MV256
+CE MV256
+CF MV256
+
+D0 MV256
+
+E0 FDC stat/cmd FDC stat/cmd FDC stat/cmd
+E1 FDC track FDC track FDC track
+E2 FDC sector FDC sector FDC sector
+E3 FDC data FDC data FDC data
+E4 FDC select FDC select/status select/status
+E5 FDC status SCSI control select/status
+E6 SCSI data keyboard
+E7 Reserved keyboard
+E8 Alarm
+E9 Alarm
+EA 6845 register
+EB 6845 data
+EC Video control
+ED Video control
+EE Select video 1
+EF Select video 2
+
+F6 ARFON Speech
+FB Ramdisc
+FC Ramdisc
+FD Ramdisc
+FE GM813 MMU 32K Paging
+FF Page Mode Page Mode Page Mode
+
+
+(1) Nascom PIO's are organized
+Data A
+Data B
+Control A
+Control B
+
+(2) The 8250 also controls the eprom enable via an I/O pin
+- OUT1 switches RS232 and tape (0 = tape)
+- OUT2 switches internalmemory disable (1 = disable)
+
+
+
+Memory Overlays
+
+8000 - BFFF Default for AVC
+
+F000 - FFFF GM813 EPROM
+
+
+Attached to a PIO
+
+GM805 Henelec
+Centronics Printer (info needed)
+GM822 RTC
+EPROM programmer
+AVC vblank etc
+
+To Add
+
+GM848 quad serial - what was the usual base ?
+
+offsets are
+
+00 Dart 0 channel 0 data
+01 Dart 0 channel 1 data
+02 Dart 0 channel 0 ctrl
+03 Dart 0 channel 1 ctrl
+04-07 ditto for DART 1
+08-0C Baud rates for 0/0 0/1 1/0 1/1
+0D-0F Yet another Z80 PIO
+
+MAP80 MPI
+
+WT625
+
--- /dev/null
+Just initial sketches. Nothing tested yet.
+
+Memory Map
+
+0000 - 00FF Vectors }
+0100 - E7FF User Space } Banked Area
+E600 - E7FF udata stash }
+E800 - EFFF Common space }
+F000 - F7FF ROM
+F800 - FBFF Video
+FC00 - FFFF Udata and common data (unbanked block)
+
+Once we have a vaguely sensible port and the split banking we need to move
+things around a bit to avoid stacks FC00-FFFF because of video contention.
+However that means supporting double stack switches as the Cromenco and
+32K/32K banked pair code will need.
+
+Target Hardware
+
+Nascom I/II/III or maybe Gemini GM811/813 (is the 813 a variant port ?)
+
+Required:
+Nascom I/II/II
+ 192K "page mode" RAM (may be able to do a 128K port as well) eg
+ RAM64/GM802
+ A timer interrupt source (* indicates possible options)
+
+Options:
+ GM833 Ramdisc. Driver written, also steals up to 1MB of it for
+ swap if no other swap partition is found on disk. The full theoretical
+ 8MB is supported although 2MB was the most practically possible
+
+ GM809/29/49/49A Floppy. Initial driver code done
+
+ GM829/49/49A SASI/SCSI. Initial driver code done
+
+
+Boot Loaders Needed
+- Nascom Floppy
+- MAP80 VFC
+- Gemini FDC
+- Gemini SASI/SCSI
+- Probably a CP/M boot loader for the other cases
+
+
+
+Options:
+Nascom AVC (384x256 / 768x256) (* if jumpered for Vblank port A bit1)
+Nascom Floppy Controller
+Nascom I/O card (PIO, CTC, UART)
+ Mostek 3882 CTC (Z80 CTC) *
+ 6402 UART
+ MK3881 PIO (Z80 PIO)
+
+GM805 'Henelec' single density floppy
+GM810 IVC
+GM816 (CTC 3x PIO optional serial GM818 - dual 8250) *
+GM822 RTC over PIO ?
+GM832 SVC
+GM837 Climax Colour
+BE847 Maths card
+GM848 quad serial
+GM862 RAM
+MAP80 256K RAM
+MAP80 MPI (serial, CTC, FDC, SASI) ? *
+MAP80 VFC
+WT625 Viewdata
+Other RTC options (some RTC have 2Hz or so interrupt - not much use!)
+
+
+Useful Disk Formats
+
+SS/DD 5.25"
+77 cys 1 side 10x512 sectors per track skew 3
+BSH 4 BLM 15 EXM 1 DSM 186 DRM 127 AL0 0C0H AL1 0 OFS 2
+
--- /dev/null
+;
+; We only have a small amount of true (ie writable) common space. We
+; don't really want stacks and stuff in it (it's contended by video)
+; but for now this will get us going
+;
+ .module commonmem
+
+ .area _COMMONDATA
+
+ .include "../cpu-z80/std-commonmem.s"
+
--- /dev/null
+/* Set if you want RTC support */
+#undef 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
+/* Banked memory set up */
+#define CONFIG_BANK_FIXED
+/* Input device support */
+#define CONFIG_INPUT
+/* Full key up/down support */
+#define CONFIG_INPUT_GRABMAX 3
+/* SCSI or SASI */
+#define CONFIG_SCSI
+
+#define MAX_MAPS 16
+#define MAP_SIZE 0xE600
+
+#define CONFIG_BANKS 1 /* 1 x 60K */
+
+/* Vt definitions */
+/* Although it's a simple display the margins and weird top line mean it's
+ got its own little driver */
+#define VT_WIDTH 48
+#define VT_HEIGHT 16 /* Lie for the moment as the top line is weird */
+#define VT_RIGHT 47
+#define VT_BOTTOM 15
+
+#define TICKSPERSEC 50 /* Ticks per second */
+#define PROGBASE 0x0000 /* Base of user */
+#define PROGLOAD 0x0100 /* Load and run here */
+#define PROGTOP 0xE600 /* Top of program, udata stash follows */
+#define PROC_SIZE 58 /* Memory needed per process */
+
+#define SWAP_SIZE 0x74 /* 58K in blocks (to get the udata stash) */
+#define SWAPBASE 0x0000 /* We swap the lot in one, include the */
+#define SWAPTOP 0xE600 /* vectors so its a round number of sectors */
+
+#define MAX_SWAPS 64 /* Should be plenty (2MB!) */
+
+#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 */
+
+#define MAX_BLKDEV 4
+
+/* Device parameters */
+#define NUM_DEV_TTY 2 /* Tackle 80bus serial later */
+#define TTYDEV BOOT_TTY /* Device used by kernel for messages, panics */
+#define SWAPDEV (swap_dev) /* Device for swapping (dynamic). */
+#define NBUFS 10 /* Number of block buffers - keep in sync with asm! */
+#define NMOUNTS 4 /* 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 _CODE2
+ .area _VIDEO
+ .area _CONST
+ .area _INITIALIZED
+ .area _DATA
+ .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
+ ; Buffers must be directly before discard as they will
+ ; expand over it
+ .area _BUFFERS
+ .area _DISCARD
+ .area _COMMONMEM
+ ; Doesn't matter if these go over the I/O space as they are
+ ; removed at the end of the build
+ .area _INITIALIZER
+
+ ; imported symbols
+ .globl _fuzix_main
+ .globl init_early
+ .globl init_hardware
+ .globl s__DATA
+ .globl l__DATA
+ .globl s__DISCARD
+ .globl l__DISCARD
+ .globl s__BUFFERS
+ .globl l__BUFFERS
+ .globl s__COMMONMEM
+ .globl l__COMMONMEM
+ .globl s__INITIALIZER
+ .globl kstack_top
+
+ ; exports
+ .globl _discard_size
+
+ ; startup code
+ .area _CODE
+
+;
+; We get booted from CP/M or a disk loader.
+;
+start:
+ ld sp, #kstack_top
+ ; move the common memory where it belongs
+ ld hl, #s__DATA
+ ld de, #s__COMMONMEM
+ ld bc, #l__COMMONMEM
+ ldir
+ ; then the discard
+; Discard can just be linked in but is next to the buffers
+ ld de, #s__DISCARD
+ ld bc, #l__DISCARD
+ ldir
+ ; then zero the data area
+ ld hl, #s__DATA
+ ld de, #s__DATA + 1
+ ld bc, #l__DATA - 1
+ ld (hl), #0
+ ldir
+; Zero buffers area
+ ld hl, #s__BUFFERS
+ ld de, #s__BUFFERS + 1
+ ld bc, #l__BUFFERS - 1
+ ld (hl), #0
+ ldir
+ ld hl,#s__COMMONMEM
+ ld de,#s__DISCARD
+ or a
+ sbc hl,de
+ ld (_discard_size),hl
+ call init_early
+ call init_hardware
+ call _fuzix_main
+ di
+stop: halt
+ jr stop
+
+ .area _DISCARD
+_discard_size:
+ .dw 0
--- /dev/null
+#include <kernel.h>
+#include <kdata.h>
+#include <printf.h>
+#include <devgm833.h>
+#include <nascom.h>
+
+/*
+ * GM833 I/O mapped RAM disc
+ *
+ * 512K of RAM on I/O ports. A maximum of 16 boards can in theory be used
+ * although the reality is probably a limit of 4, and that was all CP/M
+ * coped with. It's organized as an array of 128 byte sectors, with 256
+ * per track. In other words its a 16bit block address, 128 byte sector
+ * device. That makes it really simple to drive.
+ *
+ * Multiple cards form one bigger ramdisc, with a theoretical 8MB max but
+ * we expose each card as its own minor device as well as a single unified
+ * device so you can pick. Just don't mix!
+ *
+ * Use minor 0 for 'all', minors 1-16 for units.
+ */
+
+__sfr __at 0xfb gm833_track;
+__sfr __at 0xfc gm833_sector;
+
+static uint8_t openshift;
+static uint8_t openmask;
+static uint8_t num_gm833;
+
+static int gm833_transfer(uint8_t minor, bool is_read, uint8_t rawflag)
+{
+ int ct = 0;
+ uint8_t err = 0;
+
+ /* We support swap, it's after all an ideal swap device! */
+ io_page = 0;
+ if (rawflag == 1) {
+ if (d_blkoff(BLKSHIFT))
+ return -1;
+ io_page = udata.u_page;
+ } else if (rawflag == 2)
+ io_page = swappage;
+
+ /* Shift later minors by 512K per unit */
+ if (minor) {
+ if (udata.u_block + udata.u_nblock > 1024) {
+ udata.u_error = EIO;
+ return -1;
+ }
+ udata.u_block += 1024 * (minor + openshift - 1);
+ } else
+ udata.u_block += 1024 * openshift;
+ /* We might overflow but the largest possible configuration is
+ 8MB so we always just fit */
+ udata.u_nblock *= 4;
+ udata.u_block *= 4;
+
+ while (ct < udata.u_nblock) {
+ gm833_sector = udata.u_block;
+ gm833_track = udata.u_block >> 8;
+ if (is_read)
+ gm833_in(udata.u_dptr);
+ else
+ gm833_out(udata.u_dptr);
+ udata.u_dptr += 128;
+ udata.u_block++;
+ ct++;
+ }
+ return udata.u_nblock << 7;
+}
+
+int gm833_open(uint8_t minor, uint16_t flag)
+{
+ flag;
+ if (minor >= num_gm833 - openshift) {
+ udata.u_error = ENODEV;
+ return -1;
+ }
+ if (minor == 0) {
+ if (openmask) {
+ udata.u_error = EBUSY;
+ return -1;
+ }
+ } else
+ openmask |= (1 << minor);
+ return 0;
+}
+
+int gm833_close(uint8_t minor)
+{
+ openmask &= ~(1 << minor);
+ return 0;
+}
+
+int gm833_read(uint8_t minor, uint8_t rawflag, uint8_t flag)
+{
+ flag;
+ return gm833_transfer(minor, true, rawflag);
+}
+
+int gm833_write(uint8_t minor, uint8_t rawflag, uint8_t flag)
+{
+ flag;
+ return gm833_transfer(minor, false, rawflag);
+}
+
+/* Move to discard ?? */
+
+void gm833_init(void)
+{
+ uint8_t *tmp = tmpbuf();
+ int i;
+
+ gm833_sector = 0;
+ io_page = 0;
+
+ /* Holes are not allowed according to the config rules. If you have
+ holes it breaks - tough */
+ for (i = 0;i < 16; i++) {
+ gm833_track = i << 4;
+ *tmp = 0x90 | i;
+ gm833_out(tmp);
+ gm833_in(tmp);
+ if (*tmp != (0x90 | i))
+ break;
+ }
+ tmpfree(tmp);
+
+ /* i is now the number of units present */
+ num_gm833 = i;
+ if (i == 0)
+ return;
+ /* Steal 512K or 1MB for swap if there is no swap device
+ allocated on disk */
+ if (swap_dev == 0) {
+ /* No swap was found so bag ram disc 1 */
+ swap_dev = 0x0801;
+ /* Add swap */
+ openshift = i >= 2 ? 2 : 1;
+ for (i = 0; i < openshift * 8 ; i++)
+ swapmap_add(i);
+ }
+ kprintf("gm833: %dK found", num_gm833 * 512);
+ kprintf(", %dK allocated for swap.\n", openshift * 512);
+ kputs("\n");
+}
--- /dev/null
+
+extern void gm833_in(uint8_t *addr) __z88dk_fastcall;
+extern void gm833_out(uint8_t *addr) __z88dk_fastcall;
+
+extern int gm833_open(uint8_t minor, uint16_t flag);
+extern int gm833_close(uint8_t minor);
+extern int gm833_read(uint8_t minor, uint8_t rawflag, uint8_t flag);
+extern int gm833_write(uint8_t minor, uint8_t rawflag, uint8_t flag);
+extern void gm833_init(void);
--- /dev/null
+/*
+ * Driver for the GM809/829/849/849A floppy controllers
+ * (The SASI/SCSI has its own driver)
+ */
+
+#include <kernel.h>
+#include <kdata.h>
+#include <printf.h>
+#include <nascom.h>
+#include <devgm8x9.h>
+
+__sfr __at 0xE0 gm8x9_cmd;
+__sfr __at 0xE0 gm8x9_status;
+__sfr __at 0xE1 gm8x9_track;
+__sfr __at 0xE2 gm8x9_sector;
+__sfr __at 0xE3 gm8x9_data;
+__sfr __at 0xE4 gm8x9_ctrl;
+__sfr __at 0xE5 gm8x9_type;
+
+#define SIDE 1
+#define DDENS 2
+#define HDENS 4
+#define EIGHTINCH 8
+
+static uint8_t gm8x9_cursel;
+uint8_t gm8x9_steprate;
+static uint8_t drive_last = 0xFF, flags_last;
+
+/* IBM3740 skew table */
+static uint8_t skew_3740[] = {
+ 1, 7, 13, 19, 25, 5, 11, 17, 23, 3, 9, 15, 21, 2, 8, 14, 20, 26, 6, 12, 18, 24, 4, 10, 16, 22
+};
+
+/* Skewed at format level */
+static uint8_t skew_hard[] = {
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+ 16, 17, 18, 19.20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31
+};
+
+struct gmfd gmfd_drives[MAX_GMFD];
+
+/*
+ * Calculate the drive control value and if need be update it
+ */
+static uint8_t gm8x9_select(uint8_t drive, uint8_t flags)
+{
+ uint8_t ret = 1;
+
+ if (drive_last == drive && flags_last == flags)
+ return 0;
+
+ if (drive_last != drive) {
+ if (drive_last != 0xFF)
+ gmfd_drives[drive_last].track = gm8x9_track;
+ drive_last = drive;
+ gm8x9_track = gmfd_drives[drive].track;
+ gm8x9_steprate = gmfd_drives[drive].steprate;
+ ret = 2;
+ }
+
+ flags_last = flags;
+
+
+ if (gm8x9_type & 0x80) {
+ /* 809 / 829 */
+ drive = 1 << drive;
+ if (flags & SIDE)
+ drive |= 0/*?FIXME?*/;
+ } else {
+ if (flags & HDENS)
+ drive |= 0xB0;
+ if (flags & SIDE)
+ drive |= 0x08;
+ }
+ if (flags & DDENS)
+ drive |= 0x10;
+ if (flags & EIGHTINCH)
+ drive |= 0x20;
+ gm8x9_cursel = drive;
+ gm8x9_ctrl = drive;
+ return ret;
+}
+
+/*
+ * We only support normal block I/O for the moment. We do need to
+ * add swapping!
+ */
+
+static int gm8x9_transfer(uint8_t minor, bool is_read, uint8_t rawflag)
+{
+ int ct = 0;
+ int tries;
+ uint8_t err = 0;
+ uint8_t side, sector, track;
+ irqflags_t irqflags;
+ struct gmfd *fd = gmfd_drives + minor;
+
+ if (rawflag == 2)
+ goto bad2;
+
+ /* Translate everything into physical sectors. d_blkoff does the work
+ for raw I/O we do it for normal block I/O */
+ if (rawflag) {
+ io_page = udata.u_page;
+ if (d_blkoff(fd->bs))
+ return -1;
+ } else {
+ io_page = 0;
+ udata.u_nblock <<= fd->bs;
+ udata.u_block <<= fd->bs;
+ }
+
+ /* Loop through each logical sector translating it into a head/track/sector
+ and then attempting to do the I/O a few times */
+ while (ct < udata.u_nblock) {
+ side = 0;
+ sector = fd->skewtab[udata.u_block % fd->spt];
+ track = udata.u_block / fd->spt;
+ if (sector > fd->ds) {
+ sector -= fd->ds;
+ side = SIDE;
+ }
+ /* Set the drive parameters, also pokes the motor */
+ gm8x9_select(minor, fd->dens | side);
+ /* TODO - any delays ?? */
+
+ /* Make multiple attempts to get the data. If it keeps failing try
+ restoring the head and seeking in order to re-align */
+ for (tries = 0; tries < 5; tries++) {
+ /* Try to get the requested track */
+ if (gm8x9_track != track) {
+ if ((err = gm8x9_seek(track))) {
+ gm8x9_restore();
+ continue;
+ }
+ }
+ /* The timing on these is too tight to do with interrupts on */
+ irqflags = di();
+ if (is_read)
+ err = gm8x9_ioread(udata.u_dptr);
+ else
+ err = gm8x9_iowrite(udata.u_dptr);
+ irqrestore(irqflags);
+
+ /* It worked - exit then inner retry loop and move on */
+ if (err == 0)
+ break;
+ /* Force a head seek */
+ if (tries > 1)
+ gm8x9_restore();
+ }
+ if (tries == 5)
+ goto bad;
+ /* Move on a sector */
+ udata.u_block++;
+ udata.u_dptr += fd->ss;
+ ct++;
+ }
+
+ /* Data read in bytes */
+ return udata.u_nblock << (9 - fd->bs);
+ bad:
+ kprintf("fd%d: error %x\n", minor, err);
+ bad2:
+ udata.u_error = EIO;
+ return -1;
+}
+
+uint8_t gm8x9_density(uint8_t minor, uint8_t flags)
+{
+ flags &= EIGHTINCH;
+ /* Try double density */
+ gm8x9_select(minor, DDENS | flags);
+ if (gm8x9_restore_test() == 0)
+ return DDENS | flags;
+ /* Try single density */
+ gm8x9_select(minor, flags);
+ if (gm8x9_restore_test() == 0)
+ return flags;
+ /* We can only do HD with 5.25/3.5/3 inch media on an 849 or 849A */
+ if ((gm8x9_type & 0x80) || (flags & EIGHTINCH))
+ return 255;
+ gm8x9_select(minor, HDENS | flags);
+ if (gm8x9_restore_test() == 0)
+ return flags | HDENS;
+ /* Nothing worked */
+ return 255;
+}
+
+int gm8x9_open(uint8_t minor, uint16_t flag)
+{
+ uint8_t den;
+ struct gmfd *d = gmfd_drives + minor;
+
+ flag;
+ if (((gm8x9_type & 0x80) && minor > 4) || minor > MAX_GMFD) {
+ udata.u_error = ENODEV;
+ return -1;
+ }
+ if ((den = gm8x9_density(minor, d->dens)) == 255 && !(flag & O_NDELAY)) {
+ udata.u_error = -EIO;
+ return -1;
+ }
+ /* FIXME: how to detect double sided ? */
+ d->dens = den;
+
+ /* Default media types need to add switching ioctls yet */
+ /* Once we also have the media geometry info in the superblock
+ it'll get a *lot* easier */
+ /* Also need to add soft skewing ioctl */
+ d->bs = 0;
+ d->ss = 512;
+ d->ds = 0;
+ memcpy(d->skewtab, skew_hard, MAX_SKEW);
+ switch (den) {
+ case 0:
+ /* 18 tps 128bps double sided */
+ d->spt = 36;
+ d->bs = 2;
+ break;
+ case EIGHTINCH:
+ /* IBM3740: Classic CP/M SS/SD 77 track 26 tps 128bps */
+ d->spt = 26;
+ d->ds = 255;
+ memcpy(d->skewtab, skew_3740, MAX_SKEW);
+ break;
+ case DDENS:
+ case 255:
+ /* IBM PC style 5.25" double density is 18/9 but Nascom like many
+ other systems use 20/10 */
+ d->spt = 20;
+ break;
+ case DDENS | EIGHTINCH:
+ /* There are no real standards here, so use the Cromemco one */
+ d->spt = 32;
+ break;
+ case HDENS:
+ /* IBM PC style 5.25" high density. 3.5" is 36/18 spt */
+ d->spt = 30;
+ break;
+ }
+ d->ss >>= d->bs;
+ if (!d->ds)
+ d->ds = d->spt / 2;
+ return 0;
+}
+
+int gm8x9_read(uint8_t minor, uint8_t rawflag, uint8_t flag)
+{
+ flag;
+ return gm8x9_transfer(minor, true, rawflag);
+}
+
+int gm8x9_write(uint8_t minor, uint8_t rawflag, uint8_t flag)
+{
+ flag;
+ rawflag;
+ minor;
+// return 0;
+ return gm8x9_transfer(minor, false, rawflag);
+}
+
+
+/* TODO discard routine to init this lot */
\ No newline at end of file
--- /dev/null
+#ifndef __DEVFD_DOT_H__
+#define __DEVFD_DOT_H__
+
+/* public interface */
+int gm8x9_read(uint8_t minor, uint8_t rawflag, uint8_t flag);
+int gm8x9_write(uint8_t minor, uint8_t rawflag, uint8_t flag);
+int gm8x9_open(uint8_t minor, uint16_t flag);
+
+/* low level interface */
+uint8_t gm8x9_seek(uint8_t track) __z88dk_fastcall;
+uint8_t gm8x9_restore(void);
+uint8_t gm8x9_restore_test(void);
+uint8_t gm8x9_reset(void);
+uint8_t gm8x9_ioread(uint8_t *dptr) __z88dk_fastcall;
+uint8_t gm8x9_iowrite(uint8_t *dptr) __z88dk_fastcall;
+
+#define MAX_GMFD 16
+#define MAX_SKEW 32
+
+struct gmfd {
+ uint8_t track; /* Saved track value */
+ uint8_t spt; /* Sectors per track (including both sides if DS) */
+ uint8_t bs; /* Block shift to sectors */
+ uint16_t ss; /* Sector size */
+ uint8_t ds; /* Sector that starts second side, 255 = SS */
+ uint8_t dens; /* Density and sides info, inc 8 v 5.25 */
+ uint8_t skewtab[MAX_SKEW]; /* Skew table */
+ uint8_t steprate;
+};
+
+extern struct gmfd gmfd_drives[MAX_GMFD];
+
+
+#endif /* __DEVFD_DOT_H__ */
--- /dev/null
+#include <kernel.h>
+#include <tty.h>
+#include <version.h>
+#include <kdata.h>
+#include <devgm833.h>
+#include <devgm8x9.h>
+#include <devsys.h>
+#include <vt.h>
+#include <devtty.h>
+#include <blkdev.h>
+#include <devscsi.h>
+
+struct devsw dev_tab[] = /* The device driver switch table */
+{
+ /* 0: /dev/hd SCSI/SASI block devices */
+ { blkdev_open, no_close, blkdev_read, blkdev_write, blkdev_ioctl},
+ /* 1: /dev/fd Floppy disc block devices */
+ {gm8x9_open, no_close, gm8x9_read, gm8x9_write, no_ioctl},
+ /* 2: /dev/tty TTY devices */
+ {tty_open, tty_close, tty_read, tty_write, vt_ioctl},
+ /* 3: /dev/lpr Printer devices */
+ {nxio_open, no_close, no_rdwr, no_rdwr, no_ioctl},
+ /* 4: /dev/mem etc System devices (one offs) */
+ {no_open, no_close, sys_read, sys_write, sys_ioctl},
+ /* Pack to 7 with nxio if adding private devices and start at 8 */
+ {nxio_open, no_close, no_rdwr, no_rdwr, no_ioctl},
+ {nxio_open, no_close, no_rdwr, no_rdwr, no_ioctl},
+ {nxio_open, no_close, no_rdwr, no_rdwr, no_ioctl},
+ /* Ram driver */
+ {gm833_open, gm833_close, gm833_read, gm833_write, no_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 <kdata.h>
+#include <input.h>
+#include <devinput.h>
+
+static char buf[32];
+static struct s_queue kqueue = {
+ buf, buf, buf, sizeof(buf), 0, sizeof(buf) / 2
+};
+
+/* Queue a character to the input device */
+void queue_input(uint8_t c)
+{
+ insq(&kqueue, c);
+ wakeup(&kqueue);
+}
+
+int platform_input_read(uint8_t * slot)
+{
+ uint8_t r, k;
+ if (remq(&kqueue, &r)) {
+ remq(&kqueue, &k);
+ *slot++ = KEYPRESS_CODE | r;
+ *slot++ = k;
+ return 2;
+ }
+ return 0;
+}
+
+void platform_input_wait(void)
+{
+ psleep(&kqueue);
+}
+
+int platform_input_write(uint8_t flag)
+{
+ flag;
+ udata.u_error = EINVAL;
+ return -1;
+}
+
+void poll_input(void)
+{
+}
--- /dev/null
+
+extern void queue_input(uint8_t c);
+extern void poll_input(void);
+extern uint8_t vblank;
--- /dev/null
+#include <kernel.h>
+#include <kdata.h>
+#include <printf.h>
+#include <stdbool.h>
+#include <tty.h>
+#include <vt.h>
+#include <devtty.h>
+#include <input.h>
+#include <devinput.h>
+#include <stdarg.h>
+#include <nascom.h>
+
+char tbuf1[TTYSIZ];
+char tbuf2[TTYSIZ];
+
+struct vt_repeat keyrepeat;
+
+__sfr __at 0x01 s6402_data;
+__sfr __at 0x02 s6402_status;
+
+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 == 1)
+ return TTY_READY_NOW;
+ reg = s6402_status;
+ return (reg & 0x40) ? TTY_READY_NOW : TTY_READY_SOON;
+}
+
+void tty_putc(uint8_t minor, unsigned char c)
+{
+ if (minor == 2)
+ s6402_data = c;
+ else
+ vtoutput(&c, 1);
+}
+
+void tty_poll(void)
+{
+ uint8_t reg = s6402_status;
+ if (reg & 0x80) {
+ reg = s6402_data;
+ tty_inproc(2, reg);
+ }
+}
+
+void tty_setup(uint8_t minor)
+{
+ /* The console is a crt/keyboard, the 6402 is set by jumpers */
+}
+
+int tty_carrier(uint8_t minor)
+{
+ return 1;
+}
+
+void tty_sleeping(uint8_t minor)
+{
+ used(minor);
+}
+
+void tty_data_consumed(uint8_t minor)
+{
+ used(minor);
+}
+
+uint8_t keymap[9];
+static uint8_t keyin[9];
+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
+};
+
+__sfr __at 0 kbd_data;
+
+static void keyproc(void)
+{
+ int i;
+ uint8_t key;
+
+ kbd_data = 2; /* Reset the keyboard */
+
+ for (i = 0; i < 9; i++) {
+ /* Read the keyboard row */
+ keyin[i] = kbd_data;
+ kbd_data = 1; /* Clock the keyboard */
+ 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)) {
+ if (keyboard_grab == 3) {
+ queue_input(KEYPRESS_UP);
+ queue_input(keyboard[i][n]);
+ }
+ keysdown--;
+ }
+ }
+ if ((key & m) && !(keymap[i] & m)) {
+ if (!(shiftmask[i] & m)) {
+ keysdown++;
+ newkey = 1;
+ keybyte = i;
+ keybit = n;
+ }
+ }
+ m += m;
+
+ }
+ }
+ keymap[i] = keyin[i];
+ }
+}
+
+/*
+ * The Nascom has a surprisingly complete and good keyboard. It's lacking
+ * only a capslock key and {|}. There are some oddities however
+ *
+ * Escape is shift-enter, cs is shift-backspace, there is a lf/ch button
+ * and a graph button that's basically unused but will no doubt excite
+ * emacs people 8)
+ */
+
+uint8_t keyboard[9][8] = {
+ /* just a shift in the top row */
+ {0, 0, 0, 0, 0, 0, 0, 0},
+ {0, KEY_UP, 't', 'x', 'f', '5', 'b', 'h'},
+ {0, KEY_LEFT, 'y', 'z', 'd', '6', 'n', 'j'},
+ {0, KEY_DOWN, 'u', 's', 'e', '7', 'm', 'k'},
+ {0, KEY_RIGHT, 'i', 'a', 'w', '8', ',', 'l'},
+ /* FIXME: the ? is the graph key - what to do with it */
+ {0, '?', 'o', 'q', '3', '9', '.', ';'},
+ {0, '[', 'p', '1', '2', '0', '/', ':'},
+ {0, ']', 'r', ' ', 'c', '4', 'v', 'g'},
+ /* What to do with ch ? */
+ {0, '?', '@', 0, 0, '-', KEY_ENTER, KEY_BS}
+ /* Ch, @ shift cntrl - ... */
+};
+
+uint8_t shiftkeyboard[9][8] = {
+ /* just a shift in the top row */
+ {0, 0, 0, 0, 0, 0, 0, 0},
+ {0, KEY_UP, 'T', 'X', 'F', '%', 'B', 'H'},
+ {0, KEY_LEFT, 'Y', 'Z', 'D', '&', 'N', 'J'},
+ {0, KEY_DOWN, 'U', 'S', 'E', '\'', 'M', 'K'},
+ {0, KEY_RIGHT, 'I', 'A', 'W', '(', '<', 'L'},
+ /* FIXME key graph ? */
+ {0, '?', 'O', 'Q', KEY_POUND, ')', '>', '+'},
+ {0, ' \\ ', ' P ', ' ! ', ' "', '^', '/', '*'},
+ {0, '_', 'R', ' ', 'C', '$', 'V', 'G'},
+ /* What to do with ch ? */
+ {0, '?', '@', 0, 0, '=', KEY_ESC, KEY_BS},
+ /* Ch, @ shift cntrl - ... */
+};
+
+static uint8_t kbd_timer;
+
+static void keydecode(void)
+{
+ uint8_t c;
+ uint8_t m = 0;
+
+ if ((keymap[8] & 0x20) || (keymap[0] & 0x10)) { /* shift */
+ m = KEYPRESS_SHIFT;
+ c = shiftkeyboard[keybyte][keybit];
+ } else
+ c = keyboard[keybyte][keybit];
+
+ if (keymap[8] & 0x10) { /* control */
+ m |= KEYPRESS_CTRL;
+ if (m & KEYPRESS_SHIFT) { /* shift */
+ if (c == '(')
+ c = '{';
+ if (c == ')')
+ c = '}';
+ if (c == '^')
+ c = '|';
+ } else if (c > 31 && c < 127)
+ c &= 31;
+ }
+ if (c) {
+ switch (keyboard_grab) {
+ case 0:
+ vt_inproc(1, c);
+ break;
+ case 1:
+ if (!input_match_meta(c)) {
+ vt_inproc(1, c);
+ break;
+ }
+ /* Fall through */
+ case 2:
+ queue_input(KEYPRESS_DOWN);
+ queue_input(c);
+ break;
+ case 3:
+ /* Queue an event giving the base key (unshifted)
+ and the state of shift/ctrl/alt */
+ queue_input(KEYPRESS_DOWN | m);
+ queue_input(keyboard[keybyte][keybit]);
+ break;
+ }
+ }
+}
+
+void kbd_poll(void)
+{
+ /* Report any press of the NMI button */
+ if (nmikey) {
+ nmikey = 0;
+ vt_inproc(1, KEY_STOP);
+ }
+ 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_poll(void);
+extern void kbd_poll(void);
+
+#define KEY_ROWS 9
+#define KEY_COLS 8
+extern uint8_t keymap[9];
+extern uint8_t keyboard[9][8];
+extern uint8_t shiftkeyboard[9][8];
+
+#endif
--- /dev/null
+#include <kernel.h>
+#include <devtty.h>
+#include <devscsi.h>
+#include <devgm833.h>
+#include <printf.h>
+#include <blkdev.h>
+#include <tty.h>
+#include <nascom.h>
+
+uint8_t io_page;
+uint16_t bankmap;
+
+void device_init(void)
+{
+#ifdef CONFIG_RTC
+ /* Time of day clock */
+ inittod();
+#endif
+ devscsi_init();
+ /* Must come last as we want to allocate this for swap if no other
+ swap was found */
+ gm833_init();
+}
+
+void map_init(void)
+{
+}
+
+/* Add pagemap codes depending upon the present banks */
+void pagemap_init(void)
+{
+ /* We didn't find any extra page mode memory */
+ if (bankmap < (2 << 8))
+ panic("no pagemode");
+ /* Add the present banks (may be non contiguous). The
+ first bank is the kernel and not available */
+ if (bankmap & 2)
+ pagemap_add(0x22);
+ if (bankmap & 4)
+ pagemap_add(0x44);
+ if (bankmap & 8)
+ pagemap_add(0x88);
+}
+
+/* Swap partition on the hard disk */
+void platform_swap_found(uint8_t letter, uint8_t m)
+{
+ blkdev_t *blk = blk_op.blkdev;
+ uint16_t n;
+ if (swap_dev != 0xFFFF)
+ return;
+ letter -= 'a';
+ kputs("(swap) ");
+ swap_dev = letter << 4 | m;
+ n = blk->lba_count[m - 1] / SWAP_SIZE;
+ if (n > MAX_SWAPS)
+ n = MAX_SWAPS;
+ while (n)
+ swapmap_add(n--);
+}
--- /dev/null
+;
+; Gemini 833 Ramdisc support code
+;
+ .module gm833
+
+ .include "kernel.def"
+ .include "../kernel.def"
+
+ .globl _gm833_in
+ .globl _gm833_out
+
+ .globl map_process_a
+ .globl map_kernel
+ .globl _io_page
+
+ .area _COMMONMEM
+
+_gm833_in:
+ ld a,(_io_page)
+ or a
+ call nz, map_process_a
+ ld bc,#0x80FD
+ inir
+ jp map_kernel
+_gm833_out:
+ ld a,(_io_page)
+ or a
+ call nz, map_process_a
+ ld bc,#0x80FD
+ otir
+ jp map_kernel
--- /dev/null
+ .module gm8x9_sasi
+;
+; SASI/SCSI support for the GM829/49/49A
+; This is basically a dumb bus interface except that it automates
+; ACK handling.
+
+ .include "kernel.def"
+ .include "../kernel.def"
+
+ .globl _si_read
+ .globl _si_write
+ .globl _si_writecmd
+ .globl _si_select
+ .globl _si_reset
+ .globl _si_clear
+ .globl _si_deselect
+
+ .globl _io_page
+ .globl _si_dcb
+
+ .globl map_process_a
+ .globl map_kernel
+
+ .area _COMMONMEM
+;
+; Wait for /REQ. Preserve registers except for A
+; Z = ok, NZ = error, in which case L holds any error info and is != 0
+;
+; FIXME: add timeout and error checks
+;
+si_waitreq:
+ ld l,#1
+si_waitreql:
+ in a,(0xE5)
+ ; What should we do about monitoring BSY, C/D and I/O direction ?
+ bit 0,a
+ jr nz, si_waitreql
+ ld l,#0
+ ret
+;
+; Read a block of bytes into HL
+;
+_si_read:
+ ld c,#0xE6 ; port
+ ld de,(_si_dcb + 16) ; length
+ ld a,(_io_page)
+ or a
+ call nz, map_process_a
+si_readw:
+ call si_waitreq
+ jr nz, si_bad
+ ;
+ ; Each time we see /REQ we write a byte which causes the controller
+ ; to generate /ACK for us
+ ;
+ ini
+ dec de
+ ld a,d
+ or e
+ jr z, si_readw
+si_good:
+ ld hl,#0
+si_bad:
+ jp map_kernel
+
+_si_write:
+ ld b,c
+ ld c,#0xE6
+ ld de,(_si_dcb+16) ; length word
+ ld a,(_io_page)
+ or a
+ call nz, map_process_a
+si_writew: ; wait for REQ
+ call si_waitreq
+ jr nz, si_bad
+ outi
+ dec de
+ ld a,d
+ or e
+ jr nz, si_writew
+ jr si_good
+
+ .area _CODE
+;
+; On entry HL is the length of the command block
+;
+_si_writecmd:
+ in a,(0xE5)
+ ; FIXME: check errors/timeout
+ bit 0,a
+ jr nz, _si_writecmd ; wait for REQ
+ bit 2,a
+ jp nz, si_bad ; phase wrong
+ ex de,hl
+ ld hl,#_si_dcb
+ jp si_writew
+;
+; Select a device
+;
+_si_select:
+ ld a,(_si_dcb + 20)
+ cp #7
+ jr z, si_badsel ; clash of id
+;
+; Now perform the device selection ritual
+;
+ ld b,a
+ ld a,#0x80
+shiftid:
+ rra
+ djnz shiftid
+
+ ld de,#0xFFFF
+selwait:
+ dec de
+ ld a,d
+ or e
+ jr z, timedout
+ ; FIXME errors and timeout
+ in a,(0xE5)
+ ; Wait for BSY to drop so the bus is idle
+ bit 4,a
+ jr z, selwait
+ ; Now put the mask on the bus and assert /SEL
+ ld a,#0x05
+ out (0xE5),a
+selwait2:
+ dec de
+ ld a,d
+ or e
+ jr z, timedout
+ ; FIXME: errors, timeout
+ in a,(0xE5)
+ bit 4,a
+ jr nz, selwait2
+ ; Drive responded, drop /SEL
+ ld a,#0x07
+ out (0xE5),a
+ ld hl,#0
+ ret
+timedout:
+ ld hl,#1
+ ret
+si_badsel:
+ ld hl,#2
+ ret
+
+_si_reset:
+ ; Assert reset
+ ld a,#0x03
+ out (0xE5),a
+ ; FIXME: check spec for delay if needed
+ ld a,#0x07
+ out (0xE5),a
+ ret
+_si_clear:
+ ; Disconnect from the bus
+ ld a,#0x0F
+ out (0xE5),a
+_si_deselect:
+ ret
+
--- /dev/null
+ .module gm8x9
+
+ .include "kernel.def"
+ .include "../kernel.def"
+
+ .globl _gm8x9_ioread
+ .globl _gm8x9_iowrite
+ .globl _gm8x9_reset
+ .globl _gm8x9_seek
+ .globl _gm8x9_restore
+ .globl _gm8x9_restore_test
+
+ .globl _io_page
+ .globl _gm8x9_steprate
+ .globl map_process_a
+ .globl map_kernel
+
+;
+; The 809 is a 1797 with 5.25" (optionally 8") disk support
+; and unlike the Henelec supports double density, and does automatic
+; hardware delays.
+;
+; Usual formats are
+; SD: 35 track 18 sector 128 bytes/sector dual sided
+; DD: 35 track 10 sector 512 bytes/sector dual sided
+;
+; The GM829 is similar but also has an optional SASI interface. The
+; MAP80 VFC is GM809 compatible on the floppy side.
+;
+; E0-E3 are the 1797
+;
+; E4 0-3 selects drives 0-3
+; E4 4 switches density
+; E4 5 switches drive type (829 only)
+;
+; Side select is in type II commands (bit 3) and bit 3 of ctrl ?
+;
+; Writing E4 also turns on the motor
+;
+; On the read side it's the usual bit 7 is DRQ bit 0 is INTRQ
+; Ready *may* be available on bit 1. If not and it's properly jumpered
+; bit 1 is tied to the drive being selected. The spare bits are tied
+; to 0 and the motor on is 0 when running. In other words we can do
+; the wait purely on flags
+;
+; The GM849 moves up to a WD2797 and by this time "normal" drives
+; are 40/80 track. 3.5" and 3" are also supported.
+;
+; E4 changes entirely
+; E4 0-2 select drive by value (0-7)
+; E4 3 is now side only
+; E4 4 is SD/DD as before
+; E4 5 is still 5/8" [1 = 8]
+; E4 7 is 300rpm(DD) or 360rpm(HD)
+;
+; Basically bit 4/5 are speed selectors
+; 5 4
+; 0 0 125Kbit FM
+; 0 1 250Kbit MFM
+; 1 0 250Kbit FM
+; 1 1 500Kbit MFM
+;
+; And bit 7 controls the rotation speed so you can use 500Kbit MFM
+; with 5.25" to read 1.2MB disks (or 1.44MB) if you have a 4MHz CPU
+;
+; E5 bit 7 reads 0 for an 849 and 1 for an 829
+;
+;
+; The read and write data loops here are taken from
+; "GM809 and Eight Inch Drives" 80BUS News July-Oct 1982, by D Parkinson
+; who deserves a medal for figuring this one out.
+;
+; The Nascom FDC is very similar except that E4 is again different
+; 0-3: select drive 0-3
+; 4: side select (if LK.2 is A-C)
+; 5: low if LK3 set (normally high)
+; 6: density
+; 7: low if LK4 set (normally high)
+;
+; Status is on E5 although arranged the same way as the GM8x9
+;
+ .area _COMMONMEM
+
+FDREAD .equ 0x88
+FDWRITE .equ 0xA8
+FDSEEK .equ 0x14 ; + step rate 0-3 1C to keep head loaded
+FDREST .equ 0x00 ; "" ""
+FDRESCHK .equ 0x04 ; "" "" (should this be 0C Check)
+;
+; Read the sector data into (HL). Caller has seeked appropriately
+; and loaded sector register
+;
+_gm8x9_ioread:
+ call motorbusy_check
+ ret nz
+ ld a,(_io_page)
+ or a
+ call nz, map_process_a
+ ; FIXME: need to sort out head loading and also side here
+ ld a,#FDREAD
+ out (0xE0),a ; issue command
+ ex (sp),hl ; check length versus 12h djnz loop FIXME
+ ex (sp),hl
+ ld c,#0xE4 ; magic status
+ jr read_sync
+read_loop:
+ ; We can't ldi this because we need C to be E4
+ ; LDI would be 16 versus 24 but it costs us 8 clocks to move
+ ; C back and forth so it's not a win
+ in a,(0xE3) ; 11
+ ld (hl),a ; 7
+ inc hl ; 6
+read_sync:
+ ; From the moment DRQ goes true we have 54 T states to read it
+ in a,(c) ; 11
+ jr z, read_sync ; 12 / 7
+ jp m, read_loop ; 10
+ ; IRQ or timeout (eg motor off) - ie we finished
+read_status:
+ call map_kernel
+ call motor_check
+ ret nz
+ ;
+ ; Read status
+ ;
+ in a,(0xE0)
+ ld l,a
+ ret
+timed_out:
+ ld l,#0xFF
+ ret
+;
+; Write the sector data to (HL)
+;
+_gm8x9_iowrite:
+ call motorbusy_check
+ ret nz
+ ld a,(_io_page)
+ or a
+ call nz, map_process_a
+ ; FIXME: need to sort out head loading and also side here
+ ld a,#FDWRITE
+ out (0xE0),a ; issue command
+ ex (sp),hl
+ ex (sp),hl
+ ld c,#0xE4 ; 7
+write_loop:
+ ld a,(hl) ; 7
+ inc hl ; 6
+write_wait:
+ ; For write DRQ requires data within 46 T states - hence the
+ ; load of A must occur first
+ in b,(c) ; 12
+ jr z, write_wait ; 12 / 7
+ ; If we load data with an extra byte (as we do on the end), it
+ ; will be ignored.
+ out (0xE3),a ; 11
+ jp m, write_loop ; 10
+ jr read_status
+
+;
+; On error sets NZ and l to the error code
+;
+motorbusy_check:
+ in a,(0xE0)
+ bit 0,a
+ jr z,motor_check
+ ld l,#253
+ ret
+motor_check:
+ in a,(0xE4)
+ bit 1,a
+ ret z
+ ld l,#254
+ ret
+
+ .area _CODE
+;
+; Seek to track L. Assumes motor is running
+;
+_gm8x9_seek:
+ call motorbusy_check
+ ret nz
+ ld a,l
+ out (0xE3),a ; target track
+ ld a,#0x01 ; always valid sector
+ out (0xE2),a
+ ld b,#FDSEEK
+issue_seek:
+ ld a,(_gm8x9_steprate)
+ or b
+issue_cmd:
+ out (0xE0),a
+ ex (sp),hl
+ ex (sp),hl
+ ; FIXME: timeout on the wait loop
+seek_wait_ready:
+ in a,(0xE4)
+ bit 0,a ; check
+ jr nz, seek_wait_ready
+ in a,(0xE0)
+ ld a,l
+ ret
+
+;
+; Restore
+;
+_gm8x9_restore:
+ call motorbusy_check
+ ret nz
+ ld b,#FDREST
+ jr issue_seek
+;
+; Restore and check we can ready the track
+;
+_gm8x9_restore_test:
+ call motorbusy_check
+ ret nz
+ ld b,#FDRESCHK ; FIXME - what headload should we have ?
+ jr issue_seek
+
+;
+; Reset things
+;
+; Needs a timeout check FIXME
+;
+_gm8x9_reset:
+ ld a,#0xD0
+reset_wait:
+ in a,(0xE4)
+ bit 1,a
+ jr nz, reset_wait
+ ld l,#0
+ ret
--- /dev/null
+ .module henelec
+
+;
+; The original NASCOM floppy controller. Single density hooked up over
+; the PIO ports so it didn't need an expansion chassis of any kind
+; Data is wired to the FDC and a drive select latch. Data ends up
+; inverted.
+;
+; The Henelec was a user designed device sold as a Henry's Electric
+; kit and then by Gemini as the GM805.
+;
+; If you've met the PPIDE then it's the same essential model
+;
+; PIO A is wired to the data bus as D0-D7 but inverting
+; PIO B is wired to the control lines as follows
+; bit 7: DRQ
+; bit 6: IRQ
+; bit 5: reset
+; bit 4: !wr for the drive select latch
+; bit 3: !wr for the fdc
+; bit 2: !rd for the fdc
+; bits 1/0: A1/A0 for the FDC
+;
+; PIO B is set up once into bit mode with 7-6 input 5-0 output and
+; PIO A is normally input but for writes goes to output
+;
+; The drive select latch selects between drives based upon bits
+; bit 3: drive 2
+; bit 2: drive 1
+; bit 1: drive 0
+; bit 0: side select
+;
+; The system is usually plugged into a 35 track DSSD drive
+; 35 x 18 x 128 5.25". It does apparently support 8"
+;
+;
+; TODO: format
+; Format causes another problem - you can't 'seek' to an unformatted
+; track so we'll need _hfd_stepin as well. Then you can
+; _hfd_restore ioctl
+; generate track data
+; _hfd_format ioctl
+; read to check
+; _hfd_stepin
+; rinse/repeat
+;
+; FIXME: align methods with gm8x9.s so we can just jump table them
+;
+; FIXME: port addresses are different between a GM813 and a Nascom
+;
+ ;
+ ; I/O methods
+ ;
+ .globl _hfd_select
+ .globl _hfd_io
+ .globl _hfd_reset
+ .globl _hfd_restore
+
+ ;
+ ; Imports
+ ;
+ .globl _hfd_track
+ .globl _hfd_buffer
+ .globl _hfd_seccmd
+ .globl _io_page
+
+ .include "kernel.def"
+ .include "../kernel.def"
+
+ .area _CODE
+
+init_pio:
+ ; Program the PIO
+ ; Make our data port input
+ ld a,#0xFF
+ out (pioa_ctrl),a
+ out (pioa_ctrl),a
+ ; And our control port bit controlled
+ out (piob_data),a
+ out (piob_ctrl),a
+ ld a,#0xC0 ; DRQ and IRQ are input lines
+ out (piob_ctrl),a
+ ret
+
+;
+; Select drive B
+;
+_hfd_select:
+ ld a,(_hfd_drive)
+henelec_drvsel:
+ ld a,#0x3F
+ out (piob_data),a ; ensure all write/read enables are off
+ ld a,b
+ ld (pioa_data),a
+ ld a,#0x1f
+ out (piob_data),a ; write enable for the latch, latches 0
+ ld a,#0x3f
+ out (piob_data),a ; write enable back off
+ ld a,#0xff
+ out (pioa_ctrl),a ; back to being an input
+ out (pioa_ctrl),a
+ call sleep100ms
+ ret
+
+;
+; We might be able to squash more of this out of commonmem
+;
+ .area _COMMONMEM
+;
+; Return value from port c in b
+;
+henelec_in:
+ ld a,c
+ out (piob_data),a ; addres bits
+;
+; We always I think (maybe not data xfer - check)
+; put these back in out methods, so could skip here ?
+;
+ ld a,#0xFF
+ out (pioa_ctrl),a
+ inc a
+ out (pioa_ctrl),a ; input mode
+ ld a,c
+ res 2,c ; pulse RE
+ out (piob_data),a
+ in a,(pioa_data)
+ cpl
+ or a ; does cpl set these FIXME
+ ld b,a
+ ld a,c
+ out (piob_data),a ; pulse over
+ ret
+
+;
+; Send command B to register C
+;
+henelec_out:
+ ld a,c
+ out (piob_data),a ; address bits
+ ld a,#0xFF ; data to output
+ out (pioa_ctrl),a
+ out (pioa_ctrl),a
+ ld a,b ; gets inverted
+ cpl
+ out (pioa_data),a ; data on data lines
+ ld a,c
+ res 3,c ; pulse WE
+ out (piob_data),a
+ ld a,c
+ out (piob_data),a
+ ld a,#0xFF ; back to input
+ out (pioa_ctrl),a
+ inc a
+ out (pioa_ctrl),a
+ ret
+
+henelec_reset:
+ ;
+ ; This magic sequence is what we see DDOS do
+ ;
+ ; Data to output
+ ld a,#0xFF
+ out (pioa_ctrl),a
+ inc a
+ out (pioa_ctrl),a
+ ld b,a ; b = 0
+ call henelec_drvsel
+ ld bc,#SEC_REG
+ out (c),b
+ call wait_a_second
+ ;
+ ; Waggle the reset line
+ ;
+ ld a,#0x2F
+ out (piob_data),a
+ ;
+ ; 1771 delay
+ ;
+ ld b,#20
+l1: djnz l1
+ ;
+ ; Wait for 1771 to go ready
+ ;
+ ld a,#0x3F
+;
+; Wait for motor spin up
+;
+wait_ready:
+ push bc
+ ld c,#STAREG
+ call henelec_in
+ jp p,motor_running
+ ; Poke sector register (seems to write crap to it)
+motor_up:
+ ld c,#SECREG
+ call henelec_out
+ ; And sleep
+ call wait_a_second
+ ; And see if we are ready
+motor_running:
+ ld c,#STAREG
+ call henelec_in
+ jp m,motor_up
+ jp c,motor_running
+ pop bc
+ ret
+;
+; Disk seek
+;
+henelec_seek:
+ call wait_ready
+ ld c,#DATAREG
+ ld a,(_hfdc_track)
+ cpl
+ ld b,a
+ call henelec_out
+ ld bc,#(SEEKCMD*256)+CMDREG
+ call henelec_out
+swait:
+ in a,(piob_data)
+ and #0x40
+ jr z,swait
+ ld c,#STAREG
+ call henelec_in
+ and #0x98
+ ld l,a
+ ; check for a timeout
+ ret
+;
+; Disk read
+; B = sector
+; HL = buffer
+;
+; Length is taken from the media and I'm not sure there
+; is time to do better!
+;
+henelec_diskread:
+ call wait_ready
+ ld c,#SECREG
+ call henelec_out
+ ld bc,#(READCMD * 256)+CMDREG
+ call henelec_out
+ ld bc,#(DATREG*256)+DATREG-4
+ ld a,b
+ out (piob_data),a
+rdwait: in a,(piob_data) ; Check status
+ and #0xC0
+ jp z,rdwait
+ jp p,rdirq
+ ld a,c
+ out (piob_data),a
+ in a,(pioa_data)
+ cpl
+ ld (hl),a
+ ld a,b
+ out (piob_data),a
+ inc hl
+ jp rdwait
+rdirq: ld c,#STAREG
+ call henelec_in
+ ; check timeout etc
+ ret
+;
+; Disk write
+; B = sector
+; HL = buffer
+;
+; Same basic idea
+;
+henelec_diskwrite:
+ call wait_ready
+ ld c,#SECREG
+ call henelec_out
+ ld bc,#(WRITECMD*256)+CMDREG
+ call henelec_out
+ ld a,#0xff
+ out (pioa_ctrl),a
+ out (pioa_ctrl),a
+ ld a,b
+ out (piob_data),a
+ ld bc,#(DATREG*256)+DATREG-8
+wbytel: ld a,(hl)
+ inc hl
+ cpl
+ out (pioa_data),a
+wrwait: in a,(piob_data)
+ and 0xC0
+ jp z, wrwait
+ jp p,wrirq
+ ld a,c
+ out (piob_data),a
+ ld a,b
+ out (piob_data),a
+ jp wbytel
+wrirq:
+ ld c,#STAREG
+ call henelec_int
+ ; check timeout etc
+ ret
+;
+; Fuzix glue
+;
+_hfd_io:
+ ld c, #TRKREG
+ call henelec_in
+ ld a,(_hfd_track)
+ cp b
+ jr z,no_seekw
+ call henelec_seek
+ jr nz,seek_fail
+no_seekw:
+ ld hl,(_hfd_buffer)
+ ld bc,(_hfd_seccmd) ; loads sector into B
+ ld a,(_io_page)
+ or a
+ call nz, map_process_a
+ or a
+ jr z, iowrite
+ call henelec_diskread
+iodone:
+ call map_kernel
+seek_fail:
+ ld l,a ; return the status code
+ ret
+iowrite:
+ call henelec_diskwrite
+ jr iodone
+
+_hfd_reset:
+ call init_pio
+ call henelec_reset
+ ld bc,#0x5500+SECREG
+ call henelec_out
+ ld c,#SECREG
+ call henelec_in
+ ld a,#0x55
+ cp b
+ ld hl,#0
+ ret z
+ dec hl
+ ret
+
+_hfd_restore:
+ ld c, #(CMD_RESTORE * 256) + CMDREG
+ call henelec_out
+ jr swait
--- /dev/null
+; UZI mnemonics for memory addresses etc
+
+U_DATA .equ 0xFC00 ; (this is struct u_data from kernel.h)
+U_DATA__TOTALSIZE .equ 0x200 ; 256+256 (we don't save istack)
+
+U_DATA_STASH .equ 0xE600 ; E800-E9FF
+
+PROGBASE .equ 0x0000
+PROGLOAD .equ 0x0100
+
+Z80_TYPE .equ 1
+
+NBUFS .equ 10
+
+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;
+uint16_t swap_dev;
+
+struct blkbuf *bufpool_end = bufpool + NBUFS;
+
+/* On idle we spin checking for the terminals. Gives us more responsiveness
+ for the polled ports */
+void platform_idle(void)
+{
+ irqflags_t irq = di();
+ tty_poll();
+ irqrestore(irq);
+}
+
+void do_beep(void)
+{
+}
+
+uint8_t platform_param(char *p)
+{
+ used(p);
+ return 0;
+}
+
+void platform_interrupt(void)
+{
+ /* TODO */
+ kbd_poll();
+ tty_poll();
+ timer_interrupt();
+}
+
+/*
+ * FIXME: reclaim to end of usable memory
+ */
+void platform_discard(void)
+{
+ bufptr bp = bufpool_end;
+ extern uint16_t discard_size;
+
+ discard_size /= sizeof(struct blkbuf);
+
+ kprintf("%d buffers reclaimed from discard\n", discard_size);
+
+ bufpool_end += discard_size; /* Reclaim the discard space */
+
+ memset(bp, 0, discard_size * sizeof(struct blkbuf));
+ /* discard_size is in discard so it dies here */
+ for (bp = bufpool + NBUFS; bp < bufpool_end; ++bp) {
+ bp->bf_dev = NO_DEVICE;
+ bp->bf_busy = BF_FREE;
+ }
+}
--- /dev/null
+;
+; NASCOM boot block
+;
+; The ROM loads 512 bytes from track 0, side 0, sector 1 into a buffer
+; and then down to 0x0100
+;
+; The block must start with the code 'NCB' and the next byte is the
+; entry point.
+;
+; The stack is below 0100 and the ROM is fixed at F000
+;
+; It has a jump table at the start with helper routines
+;
+; Our disk is selected
+;
+; FC74/5/6 hold the disk, track, sector
+;
+; track 0-76 - side 0, 77+ side 1!
+;
+; This will not work on other controllers. The MAP80 for example
+; doesn't have the same ROM arrangement and loads sector 0 into
+; 0x0C00, needs 8080 at the start then jumps to n + 2
+;
+ .area ASEG(ABS)
+ .org 0x0100
+ .byte 'N'
+ .byte 'C'
+ .byte 'B'
+
+boot:
+ ld hl,#0x0200 ; Load address of image
+diskloop:
+ ld a,(0xfc76) ; sector 0-9
+ inc a
+ cp #10
+ call nz, newtrack
+ ld (0xfc76),a
+ push hl
+ call 0xf43d ; read block
+ pop hl
+ inc h
+ inc h
+ ld a,#0xE8
+ cp h
+ jr nz, diskloop
+ jp 0x200
+newtrack:
+ ld a,(0xfc75)
+ inc a
+ ld (0xfc75),a
+ xor a
+ ret
+
+
\ No newline at end of file
--- /dev/null
+;
+; NASCOM/Gemini 'page mode' banking
+;
+
+ .module nascom-pagemode
+
+ ; exported symbols
+ .globl init_hardware
+ .globl map_kernel
+ .globl map_process
+ .globl map_process_a
+ .globl map_process_always
+ .globl map_save
+ .globl map_restore
+
+ ; imported symbols
+ .globl _program_vectors
+ .globl _ramsize
+ .globl _procmem
+ .globl _bankmap
+
+ .globl s__COMMONMEM
+ .globl l__COMMONMEM
+
+ .include "kernel.def"
+ .include "../kernel.def"
+
+;
+; We keep the probe routine in the common copy area as we need
+; to bank flip as we probe
+;
+ .area _COMMONMEM
+banktest:
+ out (0xFF),a
+ ld a,#0xAA
+ cp (hl)
+ ret nz
+ inc (hl)
+ ld (de),a ; any old load, just cause bus traffic
+ inc a
+ cp (hl)
+ ret
+
+size_ram:
+ ld hl,#0xE7FF
+ ld de,#0xE7FE
+ ld a,#0xF1
+ out (0xFF),a ; write all
+ ; Now test each bank and see who is present. There can be holes
+ xor a
+ ld c,a
+ ld b,a
+ ld (de),a
+ ld a,#0x11
+size_loop:
+ call banktest
+ jr z,nobank
+ set 0,c ; mask of banks present
+ inc b ; count of banks present
+nobank:
+ sla a
+ sla c
+ jr nc,size_loop
+ ld a,#0x11
+ out (0xFF),a ; back to kernel map
+ srl c
+ ld (_bankmap),bc ; count and mask
+ ; Compute 64 * B
+ ld hl,#0
+ srl b
+ rr l
+ srl b
+ rr l
+ ld h,b
+ ret
+
+ .area _CODE
+
+init_hardware:
+ ld a,#0xF1 ; Write all banks read kernel
+ out (0xFF),a
+ ; set up interrupt vectors for the kernel (also sets up common memory in page 0x000F which is unused)
+ ld hl, #s__COMMONMEM
+ ld d,h
+ ld e,l
+ ld bc, #l__COMMONMEM
+ ldir ; Common copy into each bank
+ ld a,#0x11
+ out (0xFF),a
+ ; Only now is it safe to call into common space
+ call size_ram
+ ld (_ramsize), hl
+ ld de, #64 ; for kernel
+ or a
+ sbc hl, de
+ ld (_procmem), hl
+ ld hl,#0
+ call _program_vectors
+;
+; Need to work out how we find the right IRQ source
+;
+ ret
+
+
+;------------------------------------------------------------------------------
+; COMMON MEMORY PROCEDURES FOLLOW
+
+ .area _COMMONDATA
+
+pagereg: .db 0x11 ; bank 1 R/W
+pagesave: .db 0x11 ; saved copy
+
+ .area _COMMONMEM
+;
+; Mapping set up for the Nascom 'Page mode'
+;
+; The kernel is in bank 1, the user processes in the other banks. We
+; take care to keep common writables in COMMONDATA which is the area
+; of unbanked memory.
+;
+map_kernel:
+ push af
+ ld a,#0x11
+ ld (pagereg),a
+ out (0xFF), a
+ pop af
+ ret
+;
+; Userspace mapping is mode 3, U64K/L32 mapped at L64K/L32
+; Mapping codes 0x63 / 0x73. 0x94 on a bank expanded TRS80 then
+; selects how the upper bank decodes
+;
+map_process:
+ ld a, h
+ or l
+ jr z, map_kernel
+ ld a, (hl)
+map_process_a: ; used by bankfork
+ ld (pagereg),a
+ out (0xFF),a
+ ret
+
+map_process_always:
+ push af
+ push hl
+ ld hl, #U_DATA__U_PAGE
+ ld a, (hl)
+ ld (pagereg),a
+ out (0xFF),a
+ pop hl
+ pop af
+ ret
+
+map_save: push af
+ ld a,(pagereg)
+ ld (pagesave), a
+ pop af
+ ret
+
+map_restore:
+ push af
+ ld a, (pagesave)
+ ld (pagereg), a
+ out (0xFF), a
+ pop af
+ ret
--- /dev/null
+extern uint8_t io_page;
+extern uint16_t swap_dev;
+extern uint8_t nmikey;
+
--- /dev/null
+;
+; Nascom hardware support
+;
+
+ .module nascom
+
+ ; exported symbols
+ .globl init_early
+ .globl init_hardware
+ .globl interrupt_handler
+ .globl _program_vectors
+ .globl platform_interrupt_all
+ .globl _nmikey
+
+ ; 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
+
+ .include "kernel.def"
+ .include "../kernel.def"
+
+ .area _BUFFERS
+
+_bufpool:
+ .ds BUFSIZE * NBUFS
+; -----------------------------------------------------------------------------
+; COMMON MEMORY BANK (0xE800 upwards)
+; -----------------------------------------------------------------------------
+ .area _COMMONMEM
+
+platform_interrupt_all:
+ ret
+
+_platform_monitor:
+_platform_reboot:
+ di
+ jr _platform_reboot
+
+; -----------------------------------------------------------------------------
+; KERNEL MEMORY BANK (below 0xE800, only accessible when the kernel is mapped)
+; -----------------------------------------------------------------------------
+ .area _CODE
+
+init_early:
+ ret
+
+;------------------------------------------------------------------------------
+; COMMON MEMORY PROCEDURES FOLLOW
+
+ .area _COMMONMEM
+
+_program_vectors:
+ ; we are called, with interrupts disabled, by both newproc() and crt0
+ ; will exit with interrupts off
+ di ; just to be sure
+ pop de ; temporarily store return address
+ pop hl ; function argument -- base page number
+ push hl ; put stack back as it was
+ push de
+
+ call map_process
+
+ ; write zeroes across all vectors
+ ld hl, #0
+ ld de, #1
+ ld bc, #0x007f ; program first 0x80 bytes only
+ ld (hl), #0x00
+ ldir
+
+ ; now install the interrupt vector at 0x0038
+ ld a, #0xC3 ; JP instruction
+ ld (0x0038), a
+ ld hl, #interrupt_handler
+ ld (0x0039), hl
+
+ ; set restart vector for UZI system calls
+ ld (0x0030), a ; (rst 30h is unix function call vector)
+ ld hl, #unix_syscall_entry
+ ld (0x0031), hl
+
+ ld (0x0000), a
+ ld hl, #null_handler ; to Our Trap Handler
+ ld (0x0001), hl
+
+ ; Some Nascoms got fitted with an NMI button - what should it do
+ ; ?
+ ld (0x0066), a ; Set vector for NMI
+ ld hl, #nmi_key_handler
+ ld (0x0067), hl
+ jp map_kernel
+
+;
+; This isn't absolutely perfect but as close as we can get
+;
+nmi_key_handler:
+ push af
+ ld a,#1
+ ld (_nmikey),a
+ pop af
+ retn
+
+; outchar: Wait for UART TX idle, then print the char in A
+; destroys: AF
+outchar:
+ push af
+outwait:
+ in a, (0x02)
+ bit 6,a
+ jr z, outwait
+ out (0x1), a
+ ret
+
+ .area _COMMONDATA
+_nmikey: .byte 0
--- /dev/null
+export CPU = z80
--- /dev/null
+
+ .include "../kernel.def"
+ .include "kernel.def"
+
+ .include "../lib/z80fixedbank.s"