sc114: initial draft of an SC114 port using SCM monitor as much as possible
authorAlan Cox <alan@linux.intel.com>
Sat, 5 Jan 2019 21:06:25 +0000 (21:06 +0000)
committerAlan Cox <alan@linux.intel.com>
Sat, 5 Jan 2019 21:06:25 +0000 (21:06 +0000)
It's also designed to work on the SC108, except we don't have a suitable bank
aware monitor yet.

20 files changed:
Kernel/platform-sc114/Makefile [new file with mode: 0644]
Kernel/platform-sc114/README [new file with mode: 0644]
Kernel/platform-sc114/commonmem.s [new file with mode: 0644]
Kernel/platform-sc114/config.h [new file with mode: 0644]
Kernel/platform-sc114/crt0.s [new file with mode: 0644]
Kernel/platform-sc114/devices.c [new file with mode: 0644]
Kernel/platform-sc114/devtty.c [new file with mode: 0644]
Kernel/platform-sc114/devtty.h [new file with mode: 0644]
Kernel/platform-sc114/discard.c [new file with mode: 0644]
Kernel/platform-sc114/fuzix.lnk [new file with mode: 0644]
Kernel/platform-sc114/ide.c [new file with mode: 0644]
Kernel/platform-sc114/kernel.def [new file with mode: 0644]
Kernel/platform-sc114/main.c [new file with mode: 0644]
Kernel/platform-sc114/platform_ide.h [new file with mode: 0644]
Kernel/platform-sc114/rc2014.h [new file with mode: 0644]
Kernel/platform-sc114/rules.mk [new file with mode: 0644]
Kernel/platform-sc114/sc114.s [new file with mode: 0644]
Kernel/platform-sc114/target.mk [new file with mode: 0644]
Kernel/platform-sc114/tricks.s [new file with mode: 0644]
Kernel/platform-sc114/usermem.s [new file with mode: 0644]

diff --git a/Kernel/platform-sc114/Makefile b/Kernel/platform-sc114/Makefile
new file mode 100644 (file)
index 0000000..eb58531
--- /dev/null
@@ -0,0 +1,52 @@
+ASRCS = crt0.s tricks.s commonmem.s sc114.s usermem.s
+CSRCS = devices.c main.c devtty.c ide.c
+DISCARD_CSRCS = discard.c
+DISCARD_DSRCS = ../dev/devide_discard.c ../dev/ds1302_discard.c
+DSRCS = ../dev/devide.c ../dev/mbr.c ../dev/blkdev.c
+DSRCS +=  ../dev/ds1302.c
+DASRCS = ../dev/ds1302_rc2014.s
+NSRCS = 
+
+AOBJS = $(ASRCS:.s=.rel)
+COBJS = $(CSRCS:.c=.rel)
+DISCARD_COBJS = $(DISCARD_CSRCS:.c=.rel)
+DISCARD_DOBJS = $(patsubst ../dev/%.c,%.rel, $(DISCARD_DSRCS))
+DOBJS = $(patsubst ../dev/%.c,%.rel, $(DSRCS))
+DAOBJS = $(patsubst ../dev/%.s,%.rel, $(DASRCS))
+NOBJS = $(patsubst ../dev/net/%.c,%.rel, $(NSRCS))
+
+OBJS  = $(AOBJS) $(COBJS) $(DOBJS) $(DAOBJS) $(DISCARD_DOBJS) $(DISCARD_COBJS) $(NOBJS)
+
+CROSS_CCOPTS += -I../dev/ -I../dev/net/
+
+JUNK = *.rel *.lst *.asm *.sym *.rst *.map *.ihx *.bin
+
+all:   $(OBJS)
+
+$(AOBJS): %.rel: %.s
+       $(CROSS_AS) $(ASOPTS) $<
+
+$(COBJS): %.rel: %.c
+       $(CROSS_CC) $(CROSS_CCOPTS) -c $<
+
+$(DOBJS): %.rel: ../dev/%.c
+       $(CROSS_CC) $(CROSS_CCOPTS) -c $<
+
+$(DAOBJS): %.rel: ../dev/%.s
+       $(CROSS_AS) $(ASOPTS) $@ $<
+
+$(DISCARD_COBJS): %.rel: %.c
+       $(CROSS_CC) $(CROSS_CCOPTS) $(CROSS_CC_SEGDISC) -c $<
+
+$(DISCARD_DOBJS): %.rel: ../dev/%.c
+       $(CROSS_CC) $(CROSS_CCOPTS) $(CROSS_CC_SEGDISC) -c $<
+
+$(NOBJS): %.rel: ../dev/net/%.c
+       $(CROSS_CC) $(CROSS_CCOPTS) -c $<
+
+clean:
+       rm -f $(OBJS) $(JUNK)  core *~ diskstrap fuzix.rom
+
+image:
+       sdasz80 -o loader.s
+       sdldz80 -i loader.rel
diff --git a/Kernel/platform-sc114/README b/Kernel/platform-sc114/README
new file mode 100644 (file)
index 0000000..0286940
--- /dev/null
@@ -0,0 +1,77 @@
+Experimental port for the SC114 mainboard plus CF card plus RTC card. This
+port tries to use the SCM ROM as far as possible and also is intended to
+support the SC108 via the same port. It's probably possible to add SC101 if
+anyone wanted to.
+
+The other SCM systems are different
+- RC2014 has its own port and normally runs ROMWBW with 512K RAM/ROM
+- The RC2014 64K RAM / 32K ROM system has a custom port and can't use the SCM
+  as it needs the ROM for the OS kernel core
+- Linc has a single 16K banked window and would need to spectrum 128K like port
+  as well as banked kernel
+- Tom's SBC has no banked RAM so would need a custom port with the kernel in
+  ROM like the small RC2014 build
+
+This is a design that happens to have 128K RAM but the 64K banks are flipped
+directly. Fortunately there is a ROM which lives in both banks.
+
+For the moment we stick the OS in RAM (it would be entertaining to explore
+putting the kernel in ROM but as it's not trivially reprogrammable flash
+lets leave that for a while). The SCM ROM must have the new API's
+
+0x2A:
+       DE = address in secondary bank to read
+
+       returns value in A.
+
+0x2B:
+       A = byte to write to secondary bank
+       DE = address in secondary bank to write
+
+
+Memory map
+
+Kernel
+
+0000-00FF      Vectors (present in both banks)
+0100-EFFF      Kernel (common above 0x8000 for ROM dodging)
+F000-F1FF      UDATA
+F200-FCFF      Common (mostly valid in both banks)
+FD00-FFFF      ROM monitor owned
+
+User
+
+0000-00FF      Vectors (present in both banks)
+0100-EFFF      User space
+F000-F1FF      Kernel stack alternative (used during some bank switch ops)
+F200-F2FF      Istack alternate (we may take interrupts in either bank)
+F300-FCFF      Common (mostly valid in both banks)
+FD00-FFFF      ROM monitor owned
+
+
+TO DO
+
+Write a proper bootloader that can use partition tables properly
+Use the extra space to put back some kernel bits we binned from -tiny
+Build a suitably high CP/M emuation and test it
+CTC support and CTC no-rtc support
+PPIDE ?
+Finish SC108 compatibility
+
+
+
+Long term fun
+- OS core in ROM, high memory holding rest (like -tiny) but with two user space
+banks ??
+- Clean up the serial code (on all RC2014)
+
+
+Boot Loader
+
+The standard loader in the ROM is the CP/M loader. It loads 24 sectors (12K)
+from sector 0 moves them to the top 12K of RAM and jumps to the address held
+in 0xFFFE/FFFF
+
+We subvert this loader and our own loader lives with a standard PC style
+partition table and boots our OS. We ought to replace this with a nice loader
+that can load CP/M from a partition as well as Fuzix from a file system.
diff --git a/Kernel/platform-sc114/commonmem.s b/Kernel/platform-sc114/commonmem.s
new file mode 100644 (file)
index 0000000..355bd53
--- /dev/null
@@ -0,0 +1,10 @@
+;
+;      Standard common. Nothing special here except to remember that writes
+;      to common space won't write through all banks
+;
+        .module commonmem
+
+        .area _COMMONMEM
+
+       .include "../cpu-z80/std-commonmem.s"
+
diff --git a/Kernel/platform-sc114/config.h b/Kernel/platform-sc114/config.h
new file mode 100644 (file)
index 0000000..0e6957d
--- /dev/null
@@ -0,0 +1,74 @@
+/* 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 */
+#undef CONFIG_MULTI
+/* Swap based one process in RAM */
+#define CONFIG_SWAP_ONLY
+#define CONFIG_PARENT_FIRST
+#define MAXTICKS 20
+/* Permit large I/O requests to bypass cache and go direct to userspace */
+#define CONFIG_LARGE_IO_DIRECT
+/* One memory bank */
+#define CONFIG_BANKS   1
+#define TICKSPERSEC 10      /* Ticks per second */
+#define PROGBASE    0x0000  /* also data base */
+#define PROGLOAD    0x0100  /* also data base */
+#define PROGTOP     0xF000  /* Top of program */
+#define KERNTOP            0xF000  /* Grow buffers to 0xF000 */
+
+#define PROC_SIZE   61   /* Memory needed per process (inc udata) */
+
+#define SWAPDEV     (swap_dev) /* A variable for dynamic, or a device major/minor */
+extern unsigned int swap_dev;
+#define SWAP_SIZE   0x7A       /* 61K in blocks (prog + udata) */
+#define SWAPBASE    0x0000     /* start at the base of user mem */
+#define SWAPTOP            0xF000      /* Swap out program */
+#define CONFIG_SPLIT_UDATA     /* Adjacent addresses but different bank! */
+#define UDATA_BLKS  1          /* One block of udata */
+#define UDATA_SIZE  512                /* 512 bytes of udata */
+#define MAX_SWAPS   16         /* We will size if from the partition */
+/* Swap will be set up when a suitably labelled partition is seen */
+#define CONFIG_DYNAMIC_SWAP
+
+/* Until we have a bootloader properly done */
+#define CONFIG_MBR_OFFSET      65536
+/*
+ *     When the kernel swaps something it needs to map the right page into
+ *     memory using map_for_swap and then turn the user address into a
+ *     physical address. For a simple banked setup there is no conversion
+ *     needed so identity map it.
+ */
+#define swap_map(x)    ((uint8_t *)(x))
+
+/* We need a tidier way to do this from the loader */
+#define CMDLINE        NULL  /* Location of root dev name */
+#define BOOTDEVICENAMES "hd#,fd,,rd"
+
+#define CONFIG_DYNAMIC_BUFPOOL /* we expand bufpool to overwrite the _DISCARD segment at boot */
+#define NBUFS    4        /* Number of block buffers, keep in line with space reserved in zeta-v2.s */
+#define NMOUNTS         2        /* Number of mounts at a time */
+
+#define MAX_BLKDEV 4       /* 1 ROM disk, 1 RAM disk, 1 floppy, 1 IDE */
+
+/* On-board DS1302, we can read the time of day from it */
+#define CONFIG_RTC
+#define CONFIG_RTC_FULL
+#define CONFIG_NO_CLOCK
+
+/* IDE/CF support */
+#define CONFIG_IDE
+
+/* Device parameters */
+#define NUM_DEV_TTY 2
+
+/* UART0 as the console */
+#define BOOT_TTY (512 + 1)
+#define TTY_INIT_BAUD B115200  /* Hardwired generally */
+
+#define TTYDEV   BOOT_TTY /* Device used by kernel for messages, panics */
+
+#define platform_copyright()
diff --git a/Kernel/platform-sc114/crt0.s b/Kernel/platform-sc114/crt0.s
new file mode 100644 (file)
index 0000000..712e42f
--- /dev/null
@@ -0,0 +1,84 @@
+; 2015-02-20 Sergey Kiselev
+; 2013-12-18 William R Sowerbutts
+
+        .module crt0
+
+        ; 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 _HOME     ; compiler stores __mullong etc in here if you use them
+        .area _CODE2
+        .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 _BUFFERS     ; _BUFFERS grows to consume all before it (up to KERNTOP)
+        .area _INITIALIZER ; binman copies this to the right place for us
+        .area _GSINIT      ; unused
+        .area _GSFINAL     ; unused
+        .area _DISCARD
+       .area _DISCARDL
+       .area _DISCARDH
+        .area _COMMONMEM
+
+        ; exported symbols
+        .globl init
+
+        ; imported symbols
+        .globl _fuzix_main
+        .globl init_hardware
+        .globl s__INITIALIZER
+        .globl s__COMMONMEM
+        .globl l__COMMONMEM
+        .globl s__DISCARD
+        .globl l__DISCARD
+        .globl s__DATA
+        .globl l__DATA
+        .globl kstack_top
+
+       .include "kernel.def"
+
+        ; startup code (0x100)
+        .area _CODE
+
+       ;
+       ;       Our loader will enter with A holding the platform ID
+       ;       We preserve A carefully into init_hardware
+       ;
+init:
+        di
+        ; switch to stack in high memory
+        ld sp, #kstack_top
+
+        ; move the common memory where it belongs    
+        ld hl, #s__DATA
+        ld de, #s__COMMONMEM
+        ld bc, #l__COMMONMEM
+        ldir
+        ; and the discard
+        ld de, #s__DISCARD
+        ld bc, #l__DISCARD
+        ldir
+
+        ld hl, #s__DATA
+        ld de, #s__DATA + 1
+        ld bc, #l__DATA - 1
+        ld (hl), #0
+        ldir
+
+        ; Hardware setup
+        call init_hardware
+
+        ; Call the C main routine
+        call _fuzix_main
+    
+        ; fuzix_main() shouldn't return, but if it does...
+        di
+stop:   halt
+        jr stop
diff --git a/Kernel/platform-sc114/devices.c b/Kernel/platform-sc114/devices.c
new file mode 100644 (file)
index 0000000..1a52d3e
--- /dev/null
@@ -0,0 +1,39 @@
+#include <kernel.h>
+#include <version.h>
+#include <kdata.h>
+#include <tty.h>
+#include <devsys.h>
+#include <devfd.h>
+#include <devrd.h>
+#include <devtty.h>
+#include <blkdev.h>
+#include <ds1302.h>
+
+struct devsw dev_tab[] =  /* The device driver switch table */
+{
+/*   open          close       read            write           ioctl */
+  /* 0: /dev/hd - block device interface */
+#ifdef CONFIG_IDE
+  {  blkdev_open,   no_close,   blkdev_read,    blkdev_write,  blkdev_ioctl},
+#else
+  {  no_open,      no_close,   no_rdwr,        no_rdwr,        no_ioctl},
+#endif
+  /* 1: /dev/fd - Floppy disk block devices */
+  {  no_open,      no_close,   no_rdwr,        no_rdwr,        no_ioctl},
+  /* 2: /dev/tty -- serial ports */
+  {  tty_open,     tty_close,  tty_read,       tty_write,      tty_ioctl},
+  /* 3: RAM disk */
+  {  no_open,      no_close,   no_rdwr,        no_rdwr,        no_ioctl},
+  /* 4: /dev/mem etc      System devices (one offs) */
+  {  no_open,      no_close,   sys_read,       sys_write,      sys_ioctl},
+};
+
+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) + 255)
+               return false;
+       else
+               return true;
+}
diff --git a/Kernel/platform-sc114/devtty.c b/Kernel/platform-sc114/devtty.c
new file mode 100644 (file)
index 0000000..759ed27
--- /dev/null
@@ -0,0 +1,131 @@
+#include <kernel.h>
+#include <kdata.h>
+#include <printf.h>
+#include <stdbool.h>
+#include <tty.h>
+#include <devtty.h>
+#include <rc2014.h>
+#include <scm_monitor.h>
+
+/*
+ *     We drive the tty interface via the SCM monitor layer
+ *
+ *     Right now that costs us RTS/CTS, char size and parity handling
+ */
+
+static char tbuf1[TTYSIZ];
+static char tbuf2[TTYSIZ];
+
+uint8_t numtty = 1;
+
+static tcflag_t uart0_mask[4] = {
+       _ISYS,
+       _OSYS,
+       CBAUD|_CSYS,
+       _LSYS
+};
+
+static tcflag_t uart1_mask[4] = {
+       _ISYS,
+       _OSYS,
+       CBAUD|CSYS,
+       _LSYS,
+};
+
+tcflag_t *termios_mask[NUM_DEV_TTY + 1] = {
+       NULL,
+       uart0_mask,
+       uart1_mask
+};
+
+struct s_queue ttyinq[NUM_DEV_TTY + 1] = {     /* ttyinq[0] is never used */
+       {NULL, NULL, NULL, 0, 0, 0},
+       {tbuf1, tbuf1, tbuf1, TTYSIZ, 0, TTYSIZ / 2},
+       {tbuf2, tbuf2, tbuf2, TTYSIZ, 0, TTYSIZ / 2},
+};
+
+static uint8_t baudmap[] = {
+       0, 0, 0, 0, 0, 0,       /* No map for < 300 baud */
+       0x0C,   /* 300 */
+       0x0B,   /* 600 */
+       0x0A,   /* 1200 */
+       0x09,   /* 2400 */
+       0x08,   /* 4800 */
+       0x07,   /* 9600 */
+       0x05,   /* 19200 */
+       0x04,   /* 38400 */
+       0x03,   /* 57600 */
+       0x02,   /* 115200 */
+};
+
+/* FIXME: TODO set the initial value from the monitor - except it won't tell us */
+void tty_setup(uint8_t minor, uint8_t flags)
+{
+       tcflag_t *tptr = &ttydata[minor].termios.c_cflag;
+       uint8_t baud = *tptr & CBAUD;
+       if (baud < B300) {
+               baud = B300;
+               *tptr &= ~CBAUD;
+               *tptr |= B300;
+       }
+       scm_setbaud(((uint16_t)minor) << 8) |baudmap[baud]);
+}
+
+int tty_carrier(uint8_t minor)
+{
+       return 1;
+}
+
+/* We always DI before a ROM call so this is safe */
+void tty_pollirq(void)
+{
+       int16_t c = scm_input(1);
+       if (c >= 0)
+               tty_inproc(1, c);
+       if (numtty > 1) {
+               c = scm_input(2);
+               if (c >= 0)
+                       tty_inproc(2, c);
+       }
+}
+
+/* The SCM monitor doesn't have a check for ready for port n, we have to
+   fake stuff up a bit using the supplied 'try and send but might fail' */
+
+static uint16_t ttyq[NUM_DEV_TTY + 1];
+
+void tty_putc(uint8_t minor, unsigned char c)
+{
+       uint16_t val = ((uint16_t)minor << 8) | c;
+       if (scm_output(val))
+               ttyq[minor] = val;      /* Minor means it won't be 0 */
+}
+
+ttyready_t tty_writeready(uint8_t minor)
+{
+       /* If nothing is queued we are good */
+       if (!ttyq[minor])
+               return TTY_READY_NOW;
+       /* If we can't write the queued character we are busy */
+       if (scm_output(ttyq[minor]))
+               return TTY_READY_SOON;
+       /* Wipe the queue and report good */
+       ttyq[minor] = 0;
+       return TTY_READY_NOW;
+}
+
+void tty_sleeping(uint8_t minor)
+{
+}
+
+void tty_data_consumed(uint8_t minor)
+{
+}
+
+/* kernel writes to system console -- never sleep! */
+void kputchar(char c)
+{
+       tty_putc(TTYDEV - 512, c);
+       if (c == '\n')
+               tty_putc(TTYDEV - 512, '\r');
+}
diff --git a/Kernel/platform-sc114/devtty.h b/Kernel/platform-sc114/devtty.h
new file mode 100644 (file)
index 0000000..669c287
--- /dev/null
@@ -0,0 +1,9 @@
+#ifndef __DEVTTY_DOT_H__
+#define __DEVTTY_DOT_H__
+
+void tty_putc(uint8_t minor, unsigned char c);
+void tty_pollirq_sio(void);
+
+extern uint8_t numtty;
+
+#endif
diff --git a/Kernel/platform-sc114/discard.c b/Kernel/platform-sc114/discard.c
new file mode 100644 (file)
index 0000000..209544a
--- /dev/null
@@ -0,0 +1,66 @@
+#include <kernel.h>
+#include <kdata.h>
+#include <printf.h>
+#include <devtty.h>
+#include <ds1302.h>
+#include <devide.h>
+#include <blkdev.h>
+#include "config.h"
+
+void map_init(void)
+{
+}
+
+/*
+ *     This function is called for partitioned devices if a partition is found
+ *     and marked as swap type. The first one found will be used as swap. We
+ *     only support one swap device.
+ */
+void platform_swap_found(uint8_t letter, uint8_t m)
+{
+       blkdev_t *blk = blk_op.blkdev;
+       uint16_t n;
+       if (swap_dev != 0xFFFF)
+               return;
+       letter -= 'a';
+       kputs("(swap) ");
+       swap_dev = letter << 4 | m;
+       n = blk->lba_count[m - 1] / SWAP_SIZE;
+       if (n > MAX_SWAPS)
+               n = MAX_SWAPS;
+       while(n)
+               swapmap_init(n--);
+}
+
+void system_init(void)
+{
+       scm_version();
+       switch(scm_hw_info >> 8) {
+       case SCM_SC114:
+               kputs("Small Computer 114.\n");
+               /* RAM switch port 30 bit 0, ROM port 38 bit 0 */
+               /* Second ACIA or an SIO/2 means two serial */
+               if (scm_hw_info & 6)
+                       numtty = 2;
+               break;
+       case SCM_SC108:
+               kputs("Small Computer 108.\n");
+               /* RAM switch port 38 bit 7, ROM port 38 bit 0 */
+               /* Want to merge in this port */
+       /* And the SCM SC101 uses outs to port 18 to select ram bank 0 ,
+          19 1, and 1c/1d for ROM in and out. However there's only one of it
+          so probably not worth the trouble ! */
+       default:
+               /* Unsupported */
+               panic("Unsupported platform.\n");
+       }
+}
+
+void device_init(void)
+{
+       system_init();
+       ds1302_init();
+#ifdef CONFIG_IDE
+       devide_init();
+#endif
+}
diff --git a/Kernel/platform-sc114/fuzix.lnk b/Kernel/platform-sc114/fuzix.lnk
new file mode 100644 (file)
index 0000000..a168196
--- /dev/null
@@ -0,0 +1,44 @@
+-mwxuy
+-i fuzix.ihx
+-b _CODE=0x0100
+-b _COMMONMEM=0xF000
+-l z80
+platform-sc114/crt0.rel
+platform-sc114/commonmem.rel
+platform-sc114/sc114.rel
+start.rel
+version.rel
+lowlevel-z80-thunked.rel
+platform-sc114/tricks.rel
+platform-sc114/main.rel
+timer.rel
+kdata.rel
+platform-sc114/devices.rel
+devio.rel
+filesys.rel
+process.rel
+inode.rel
+syscall_exec16.rel
+syscall_fs.rel
+syscall_proc.rel
+syscall_fs2.rel
+syscall_fs3.rel
+syscall_other.rel
+mm.rel
+swap.rel
+simple.rel
+tty.rel
+devsys.rel
+usermem.rel
+platform-sc114/discard.rel
+platform-sc114/devtty.rel
+platform-sc114/mbr.rel
+platform-sc114/blkdev.rel
+platform-sc114/devide.rel
+platform-sc114/devide_discard.rel
+platform-sc114/ide.rel
+platform-sc114/usermem.rel
+platform-sc114/ds1302.rel
+platform-sc114/ds1302_discard.rel
+platform-sc114/ds1302_rc2014.rel
+-e
diff --git a/Kernel/platform-sc114/ide.c b/Kernel/platform-sc114/ide.c
new file mode 100644 (file)
index 0000000..e76d678
--- /dev/null
@@ -0,0 +1,71 @@
+#define _IDE_PRIVATE
+
+#include <kernel.h>
+#include <kdata.h>
+#include <printf.h>
+#include <stdbool.h>
+#include <timer.h>
+#include <devide.h>
+#include <blkdev.h>
+
+/*
+ *     We need slightly custom transfer routines for the IDE controller
+ *     as we have no common stack to use.
+ */
+
+COMMON_MEMORY
+
+void devide_read_data(void) __naked
+{
+    __asm
+            ld a, (_blk_op+BLKPARAM_IS_USER_OFFSET) ; blkparam.is_user
+            ld hl, (_blk_op+BLKPARAM_ADDR_OFFSET)   ; blkparam.addr
+            ld bc, #IDE_REG_DATA                    ; setup port number
+                                                    ; and count
+           cp #2
+            jr nz, not_swapin
+            ld a, (_blk_op+BLKPARAM_SWAP_PAGE)     ; blkparam.swap_page
+not_swapin:
+            ; At this point A = 0 kernel, A = 1, user
+            rrca
+            or #1
+            out (0x38),a
+            rlca
+            out (0x30),a
+            inir                                    ; transfer first 256 bytes
+            inir                                    ; transfer second 256 bytes
+            ld a,#0x01
+            out (0x38),a
+            rlca
+            out (0x30),a
+            ret
+    __endasm;
+}
+
+void devide_write_data(void) __naked
+{
+    __asm
+            ld a, (_blk_op+BLKPARAM_IS_USER_OFFSET) ; blkparam.is_user
+            ld hl, (_blk_op+BLKPARAM_ADDR_OFFSET)   ; blkparam.addr
+            ld bc, #IDE_REG_DATA                    ; setup port number
+                                                    ; and count
+           cp #2
+            jr nz, not_swapout
+            ld a, (_blk_op+BLKPARAM_SWAP_PAGE)     ; blkparam.swap_page
+not_swapout:
+            ; At this point A = 0 kernel, A = 1 user
+            rrca
+            or #1
+            out (0x38),a
+            rlca
+            out (0x30),a
+            otir                                    ; transfer first 256 bytes
+            otir                                    ; transfer second 256 bytes
+            xor a
+            ld a,#0x01
+            out (0x38),a
+            rlca
+            out (0x30),a
+            ret
+    __endasm;
+}
diff --git a/Kernel/platform-sc114/kernel.def b/Kernel/platform-sc114/kernel.def
new file mode 100644 (file)
index 0000000..37a5db6
--- /dev/null
@@ -0,0 +1,49 @@
+; FUZIX mnemonics for memory addresses etc
+
+U_DATA                 .equ    0xF000  ; (this is struct u_data from kernel.h)
+U_DATA__TOTALSIZE      .equ    0x300   ; 256+256+256 bytes.
+Z80_TYPE               .equ    0       ; CMOS
+
+Z80_MMU_HOOKS          .equ 0
+
+CONFIG_SWAP            .equ 1
+
+PROGBASE               .equ    0x0000
+PROGLOAD               .equ    0x0100
+
+; Mnemonics for I/O ports etc
+
+CONSOLE_RATE           .equ    115200
+
+CPU_CLOCK_KHZ          .equ    7372
+
+; Base address of SIO/2 chip 0x80
+; For the Scott Baker SIO card adjust the order to match rc2014.h
+SIOA_C         .EQU    0x80
+SIOA_D         .EQU    SIOA_D+1
+SIOB_C         .EQU    SIOA_D+2
+SIOB_D         .EQU    SIOA_D+3
+; Z80 CTC ports
+CTC_CH0                .equ    0x88    ; CTC channel 0 and interrupt vector
+CTC_CH1                .equ    0x89    ; CTC channel 1 (periodic interrupts)
+CTC_CH2                .equ    0x8A    ; CTC channel 2
+CTC_CH3                .equ    0x8B    ; CTC channel 3
+
+ACIA_C          .EQU     0x80
+ACIA_D          .EQU     0x81
+ACIA_RESET      .EQU     0x03
+ACIA_RTS_HIGH_A      .EQU     0xD6   ; rts high, xmit interrupt disabled
+ACIA_RTS_LOW_A       .EQU     0x96   ; rts low, xmit interrupt disabled
+
+NBUFS          .equ    4
+
+;
+;      Select where to put the high code - in our case we need this
+;      in common
+;
+.macro HIGH
+               .area _COMMONMEM
+.endm
+
+HIGHPAGE       .equ    0       ; We only have 1 page byte and the low page
+                               ; isn't used
diff --git a/Kernel/platform-sc114/main.c b/Kernel/platform-sc114/main.c
new file mode 100644 (file)
index 0000000..7faac94
--- /dev/null
@@ -0,0 +1,120 @@
+#include <kernel.h>
+#include <kdata.h>
+#include <printf.h>
+#include <timer.h>
+#include <devtty.h>
+#include <devfd.h>
+#include <rtc.h>
+#include <ds1302.h>
+#include <scm_monitor.h>
+
+extern unsigned char irqvector;
+struct blkbuf *bufpool_end = bufpool + NBUFS;  /* minimal for boot -- expanded after we're done with _DISCARD */
+uint16_t swap_dev = 0xFFFF;
+uint16_t ramtop = 0xF000;
+uint8_t need_resched = 0;
+
+
+void platform_discard(void)
+{
+       while (bufpool_end < (struct blkbuf *) (KERNTOP - sizeof(struct blkbuf))) {
+               memset(bufpool_end, 0, sizeof(struct blkbuf));
+#if BF_FREE != 0
+               bufpool_end->bf_busy = BF_FREE; /* redundant when BF_FREE == 0 */
+#endif
+               bufpool_end->bf_dev = NO_DEVICE;
+               bufpool_end++;
+       }
+       kprintf("Buffers available: %d\n", bufpool_end - bufpool);
+}
+
+static uint8_t idlect;
+
+void platform_idle(void)
+{
+       /* Check the clock. We try and reduce the impact of the clock on
+          latency by not doing it so often. 256 may be too small a divide
+          need to see what 1/10th sec looks like in poll loops */
+       if (!++idlect)
+               sync_clock();
+}
+
+uint8_t platform_param(unsigned char *p)
+{
+       used(p);
+       return 0;
+}
+
+void platform_interrupt(void)
+{
+       tty_pollirq();
+}
+
+/*
+ *     Logic for tickless system. If you have an RTC you can ignore this.
+ */
+
+static uint8_t newticks = 0xFF;
+static uint8_t oldticks;
+
+static uint8_t re_enter;
+
+/*
+ *     Hardware specific logic to get the seconds. We really ought to enhance
+ *     this to check minutes as well just in case something gets stuck for
+ *     ages.
+ */
+static void sync_clock_read(void)
+{
+       uint8_t s;
+       oldticks = newticks;
+       ds1302_read_clock(&s, 1);
+       s = (s & 0x0F) + (((s & 0xF0) >> 4) * 10);
+       newticks = s;
+}
+
+/*
+ *     The OS core will invoke this routine when idle (via platform_idle) but
+ *     also after a system call and in certain other spots to ensure the clock
+ *     is roughly valid. It may be called from interrupts, without interrupts
+ *     or even recursively so it must protect itself using the framework
+ *     below.
+ *
+ *     Having worked out how much time has passed in 1/10ths of a second it
+ *     performs that may timer_interrupt events in order to advance the clock.
+ *     The core kernel logic ensures that we won't do anything silly from a
+ *     jump forward of many seconds.
+ *
+ *     We also choose to poll the ttys here so the user has some chance of
+ *     getting control back on a messed up process.
+ */
+void sync_clock(void)
+{
+       irqflags_t irq = di();
+       int16_t tmp;
+       if (!re_enter++) {
+               sync_clock_read();
+               if (oldticks != 0xFF) {
+                       tmp = newticks - oldticks;
+                       if (tmp < 0)
+                               tmp += 60;
+                       tmp *= 10;
+                       while(tmp--) {
+                               timer_interrupt();
+                       }
+               }
+       }
+       re_enter--;
+       if (re_enter > 1)
+               kputs("oops");
+       irqrestore(irq);
+}
+
+/*
+ *     This method is called if the kernel has changed the system clock. We
+ *     don't work out how much work we need to do by using it as a reference
+ *     so we don't care.
+ */
+void update_sync_clock(void)
+{
+}
diff --git a/Kernel/platform-sc114/platform_ide.h b/Kernel/platform-sc114/platform_ide.h
new file mode 100644 (file)
index 0000000..d2313d3
--- /dev/null
@@ -0,0 +1,9 @@
+#define ide_select(x)
+#define ide_deselect()
+
+/* 8bit, no altstatus/control */
+#define IDE_8BIT_ONLY
+#define IDE_REG_CS1_BASE   0x10
+
+/* Due to our strange banking needs */
+#define IDE_NONSTANDARD_XFER
diff --git a/Kernel/platform-sc114/rc2014.h b/Kernel/platform-sc114/rc2014.h
new file mode 100644 (file)
index 0000000..21ee491
--- /dev/null
@@ -0,0 +1,25 @@
+#ifndef __RC2014_SIO_DOT_H__
+#define __RC2014_SIO_DOT_H__
+
+/* Needs generalizing and tidying up across the RC2014 systems */
+
+#include "config.h"
+
+#define SIO0_IVT 8
+
+/* Standard RC2014 */
+#define SIO0_BASE 0x80
+__sfr __at (SIO0_BASE + 0) SIOA_C;
+__sfr __at (SIO0_BASE + 1) SIOA_D;
+__sfr __at (SIO0_BASE + 2) SIOB_C;
+__sfr __at (SIO0_BASE + 3) SIOB_D;
+
+/* ACIA is at same address as SIO but we autodetect */
+
+#define ACIA_BASE 0x80
+__sfr __at (ACIA_BASE + 0) ACIA_C;
+__sfr __at (ACIA_BASE + 1) ACIA_D;
+
+extern void sio2_otir(uint8_t port) __z88dk_fastcall;
+
+#endif
diff --git a/Kernel/platform-sc114/rules.mk b/Kernel/platform-sc114/rules.mk
new file mode 100644 (file)
index 0000000..c3701b0
--- /dev/null
@@ -0,0 +1 @@
+export BANKED=-thunked
diff --git a/Kernel/platform-sc114/sc114.s b/Kernel/platform-sc114/sc114.s
new file mode 100644 (file)
index 0000000..e06981a
--- /dev/null
@@ -0,0 +1,499 @@
+;
+;      SC114 Initial Support
+;
+
+        .module sc114
+
+        ; exported symbols
+        .globl init_hardware
+        .globl interrupt_handler
+        .globl _program_vectors
+       .globl _kernel_flag
+       .globl map_page_low
+       .globl map_kernel_low
+       .globl map_user_low
+       .globl map_save_low
+       .globl map_restore_low
+       .globl _platform_doexec
+       .globl _platform_reboot
+       .globl _int_disabled
+       .globl _scm_romcall
+
+        ; exported debugging tools
+        .globl _platform_monitor
+        .globl outchar
+
+        ; imported symbols
+        .globl _ramsize
+        .globl _procmem
+        .globl istack_top
+        .globl istack_switched_sp
+       .globl kstack_top
+        .globl unix_syscall_entry
+        .globl outcharhex
+
+       .globl _scm_reset
+       .globl _scm_conout
+       .globl scm_farput
+
+       .globl s__COMMONMEM
+       .globl l__COMMONMEM
+
+        .include "kernel.def"
+        .include "../kernel.def"
+
+
+;
+; Buffers (we use asm to set this up as we need them in a special segment
+; so we can recover the discard memory into the buffer pool
+;
+
+       .globl _bufpool
+       .area _BUFFERS
+
+_bufpool:
+       .ds BUFSIZE * NBUFS
+
+; -----------------------------------------------------------------------------
+;
+;      Because of the weird memory model this is a bit different to the
+;      usual Z80 setup.
+;
+; -----------------------------------------------------------------------------
+        .area _COMMONMEM
+
+_int_disabled:
+       .db 1
+
+;
+;      Ugly but the API happens to use A B and C so all the nice ways to
+;      ROM page are not available. On the SC108 this will stick us back
+;      in the kernel mapping but that's fine - we were there anyway
+;
+;      Interrupts must be off for a ROM call
+;
+_scm_romcall:
+       di
+       ex af,af'
+       xor a
+       out (0x38),a            ; ROM in
+       ex af,af'
+       rst 0x30
+       ex af,af'
+       inc a
+       out (0x38),a
+       ex af,af'
+       ld a,(_int_disabled)
+       or a
+       ret nz
+       ei
+       ret
+_platform_monitor:
+       ; Reboot ends up back in the monitor
+_platform_reboot:
+       jp _scm_reset
+
+       
+; -----------------------------------------------------------------------------
+;
+;      Our MMU is write only, but if we put a value in each bank in a fixed
+;      place we have a fast way to see which bank is which
+;
+; -----------------------------------------------------------------------------
+
+banknum:
+       .byte 0x81              ; copied into far bank then set to 1
+
+; -----------------------------------------------------------------------------
+;      All of discard gets reclaimed when init is run
+;
+;      Discard must be above 0x8000 as we need some of it when the ROM
+;      is paged in during init_hardware
+; -----------------------------------------------------------------------------
+       .area _DISCARD
+
+init_hardware:
+       ; A holds the platform
+       cp #0x08                ; SC114
+       jr z, is_sc114
+       ; Nope - must be an SC108
+       ; Reprogram the mapping logic
+       ld de,#0x0181
+       ld (_bank0to1),de
+       ld de,#0x8101
+       ld (_bank1to0),de
+       ld a,#0x38
+       ld (_bankport),a
+is_sc114:
+       ld hl, #128
+        ld (_ramsize), hl
+       ld hl,#64
+        ld (_procmem), hl
+
+       ld de,#s__COMMONMEM
+       ld bc,#l__COMMONMEM
+
+       ;
+       ;       Use the ROM helper to get our common into the secondary
+       ;       memory bank.
+       ;
+install_common:
+       ld a,(de)
+       push bc
+       push de
+       call scm_farput
+       pop de
+       pop bc
+       inc de
+       dec bc
+       ld a,b
+       or c
+       jr nz, install_common
+       ; A = 0
+       ; SC108 is already back in the right RAM bank, make sure SC114 is
+       out (0x30),a
+       ld a,#0x01
+       ld (banknum),a          ; and correct page
+
+       ; We now have our common in place. We can do the rest ourselves
+
+       ; Put the low stubs into place in the kernel
+       ld hl,#stubs_low
+       ld de,#0
+       ld bc,#0x68
+       ldir
+       ld hl,#stubs_low
+       ld ix,#0
+       ld bc,#0x68
+       call ldir_to_user
+
+        im 1 ; set CPU interrupt mode
+        ret
+
+;
+;      Our memory setup is weird and common is kind of meaningless here
+;
+           .area _CODE
+
+_kernel_flag:
+           .db 1       ; We start in kernel mode
+map_save_low:
+map_kernel_low:
+map_restore_low:
+map_user_low:
+map_page_low:
+           ret
+
+_program_vectors:
+           ret
+
+;
+; outchar: Wait for UART TX idle, then print the char in A
+; destroys: AF
+;
+outchar:
+       push bc
+       push de
+       push hl
+       call _scm_conout
+       pop hl
+       pop de
+       pop bc
+       ret
+
+
+; Don't be tempted to put the symbol in the code below ..it's relocated
+; to zero. Instead define where it ends up.
+
+_platform_doexec       .equ    0x18
+
+        .area _DISCARD
+
+       .globl rst38
+       .globl stubs_low
+;      This exists at the bottom of each page. We move these into place
+;      from discard.
+;
+stubs_low:
+           .byte 0
+stub0:     .word 0             ; cp/m emu changes this
+           .byte 0             ; cp/m emu I/O byte
+           .byte 0             ; cp/m emu drive and user
+           jp 0                ; cp/m emu bdos entry point
+rst8:
+           ret
+           nop
+           nop
+           nop
+           nop
+           nop
+           nop
+           nop      
+rst10:
+           ret
+           nop
+           nop
+           nop
+           nop
+           nop
+           nop
+           nop
+rst18:                         ; execve entry to user space
+           ld a,#0x01
+           out (0x30),a
+           ei
+           jp (hl)
+           nop
+           nop
+rst20:     ret
+           nop
+           nop
+           nop
+           nop
+           nop
+           nop
+           nop
+rst28:     ret
+           nop
+           nop
+           nop
+           nop
+           nop
+           nop
+           nop
+rst30:     jp syscall_high
+           nop
+           nop
+           nop
+           nop
+           nop
+;
+;      We only have 38-4F available for this in low space
+;
+rst38:     jp interrupt_high           ; Interrupt handling stub
+           nop
+           nop
+           nop
+           nop
+           nop
+           .ds 0x26
+nmi_handler:           ; Should be at 0x66
+           retn
+
+;
+;      This stuff needs to live somewhere, anywhere out of the way (so we
+;      use common). We need to copy it to the same address on both banks
+;      so place it in common as we will put common into both banks
+;
+
+           .area _COMMONMEM
+
+           .globl ldir_to_user
+           .globl ldir_from_user
+
+; FIXME: save a byte and some setup by makign byte 2 of 0to1 the first
+; of 1to0
+_bank0to1:
+          .dw 0x0001                   ; 0x0181 for SC108
+_bank1to0:
+          .dw 0x0100                   ; 0x8101 for SC108
+_bankport:
+          .db 0x30                     ; 0x38 for SC108
+;
+;      This needs some properly optimized versions!
+;
+ldir_to_user:
+           ld de,(_bank0to1)           ; from bank 0 to bank  1
+ldir_far:
+           push bc
+           ld bc,(_bankport)
+           exx
+           pop bc                      ; get BC into alt bank
+far_ldir_1:
+           exx
+           out (c),d
+           ld a,(hl)
+           inc hl
+           out (c),e
+           ld (ix),a
+           inc ix
+           exx
+           dec bc
+           ld a,b
+           or c
+           jr nz, far_ldir_1
+           ld a,(_bank0to1)            ; gets us the bank 0 value */
+           exx
+           out (c),a
+           exx
+           ret
+ldir_from_user:
+           ld de,#0x0100
+           jr ldir_far
+;
+;      High stubs. Present in each bank in the top 256 bytes
+;
+interrupt_high:
+           push af
+           push de
+           push hl
+           ex af,af'
+           push af
+           push bc
+           exx
+           push bc
+           push de
+           push hl
+           push ix
+           push iy
+           ld a,(banknum)
+           ld c,a
+           ;
+           ; The trick going on here is that an SC108 will respond to
+           ;  0x38 bit 7 = RAM bank 0 = ROM and ignore 0x30
+            ; but the SC114 will respond to 0x38 bit 0 only (ROM) and
+           ; to 0x30 bit 0 for RAM.
+           ;
+           ld a,#0x01
+           out (0x38),a                ; Bank 0
+           rlca
+           out (0x30),a
+           ld (istack_switched_sp),sp  ; istack is positioned to be valid
+           ld sp,#istack_top           ; in both banks. We just have to
+           ;
+           ;   interrupt_handler may come back on a different stack in
+           ;   which case bc is junk. Fortuntely we never pre-empt in
+           ;   kernel so the case we care about bc is always safe. This is
+           ;   not a good way to write code and should be fixed! FIXME
+           ;
+           push bc
+           call interrupt_handler      ; switch on the right SP
+           pop bc
+           ; Restore stack pointer to user. This leaves us with an invalid
+           ; stack pointer if called from user but interrupts are off anyway
+           ld sp,(istack_switched_sp)
+           ; On return HL = signal vector E= signal (if any) A = page for
+           ; high
+           or a
+           jr z, kernout
+           ; Returning to user space
+           ld a,#0x81                  ; User bank
+           out (0x38),a
+           rlca
+           out (0x30),a
+           ; User stack is now valid
+           ; back on user stack
+           xor a
+           cp e
+           call nz, sigpath
+pops:
+           ex af,af'
+           exx
+           pop iy
+           pop ix
+           pop hl
+           pop de
+           pop bc
+           exx
+           pop bc
+           pop af
+           ex af,af'
+           pop hl
+           pop de
+           pop af
+           ei
+           ret
+kernout:
+           ; restore bank - if we interrupt mid user copy or similar we
+           ; have to put the right bank back
+           ld a,c
+           out (0x38),a
+           rlca
+           out (0x30),a
+           jr pops
+           
+sigpath:
+           push de             ; signal number
+           ld de,#irqsigret
+           push de             ; clean up
+           jp (hl)
+irqsigret:
+           inc sp              ; drop signal number
+           inc sp
+           ret
+
+syscall_high:
+           push ix
+           ld ix,#0
+           add ix,sp
+           push de             ; the syscall if must preserve de for now
+                               ; needs fixing when we change the syscall
+                               ; API for Z80 to something less sucky
+           ld a,4(ix)
+           ld c,6(ix)
+           ld b,7(ix)
+           ld e,8(ix)
+           ld d,9(ix)
+           ld l,10(ix)
+           ld h,11(ix)
+           push hl
+           ld l,12(ix)
+           ld h,13(ix)
+           pop ix
+           di
+           ; BUG: syscall corrupts AF' - should we just define some
+           ; alt register corruptors for new API - would be sanest fix
+           ex af, af'          ; Ick - find a better way to do this bit !
+           ld a,#0x01
+           out (0x38),a
+           rlca
+           out (0x30),a
+           ex af,af'
+           ; Stack now invalid
+           ld (U_DATA__U_SYSCALL_SP),sp
+           ld sp,#kstack_top
+           call unix_syscall_entry
+           ; FIXME check di rules
+           ; stack now invalid. Grab the new sp before we unbank the
+           ; memory holding it
+           ld sp,(U_DATA__U_SYSCALL_SP)
+           ld a,#0x81          ; back to the user page
+           out (0x38),a
+           rlca
+           out (0x30),a
+           xor a
+           cp h
+           call nz, syscall_sigret
+           ; FIXME for now do the grungy C flag HL DE stuff from
+           ; lowlevel-z80 until we fix the ABI
+           ld bc,#0
+           ld a,h
+           or l
+           jr nz, error
+           ex de,hl
+           pop de
+           pop ix
+           ei
+           ret
+error:     scf
+           pop de
+           pop ix
+           ei
+           ret
+syscall_sigret:
+           ld a,l              ; DEBUG
+           push hl             ; save errno
+           push de             ; save retval
+           ld l,h
+           ld h,#0
+           push hl             ; signal
+           ld hl,#syscall_sighelp
+           push hl             ; vector
+           ret
+syscall_sighelp:
+           pop de              ; discard signal
+           pop de              ; recover error info
+           pop hl
+           ld h,#0             ; clear signal bit
+           ret
+
diff --git a/Kernel/platform-sc114/target.mk b/Kernel/platform-sc114/target.mk
new file mode 100644 (file)
index 0000000..3bffcde
--- /dev/null
@@ -0,0 +1 @@
+export CPU = z80
diff --git a/Kernel/platform-sc114/tricks.s b/Kernel/platform-sc114/tricks.s
new file mode 100644 (file)
index 0000000..0113d75
--- /dev/null
@@ -0,0 +1,5 @@
+        .include "kernel.def"
+        .include "../kernel.def"
+
+       .include "../lib/z80single.s"
+
diff --git a/Kernel/platform-sc114/usermem.s b/Kernel/platform-sc114/usermem.s
new file mode 100644 (file)
index 0000000..81c92ee
--- /dev/null
@@ -0,0 +1,156 @@
+;
+;      We have a custom implementation of usermem. We really need
+;      to optimize ldir_to/from_user.
+;
+        .module usermem
+
+       .include "kernel.def"
+        .include "../kernel.def"
+
+        ; exported symbols
+        .globl __uget
+        .globl __ugetc
+        .globl __ugetw
+
+        .globl __uput
+        .globl __uputc
+        .globl __uputw
+        .globl __uzero
+
+       .globl ldir_from_user
+       .globl ldir_to_user
+
+        .area _COMMONMEM
+
+
+uputget:
+        ; load DE with the byte count
+        ld c, 8(ix) ; byte count
+        ld b, 9(ix)
+       ld a, b
+       or c
+       ret z           ; no work
+        ; load HL with the source address
+        ld l, 4(ix) ; src address
+        ld h, 5(ix)
+        ; load DE with destination address
+        ld e, 6(ix)
+        ld d, 7(ix)
+       ret
+
+__uget:
+       push ix
+       ld ix,#0
+       add ix, sp
+       call uputget
+       jr z, uget_out
+       push de
+       pop ix
+       call ldir_from_user
+uget_out:
+       pop ix
+       ld hl,#0
+       ret
+
+__uput:
+       push ix
+       ld ix,#0
+       add ix,sp
+       call uputget
+       jr z, uget_out
+       push de
+       pop ix
+       call ldir_to_user
+       jr uget_out
+
+;
+;      The kernel IRQ code will restore the bank if it interrupts this
+;      logic
+;
+__uzero:
+       pop de
+       pop hl
+       pop bc
+       push bc
+       push hl
+       push de
+       ld a,b
+       or c
+       ret z
+       ld a,#0x81
+       out (0x38),a            ; user bank
+       rlca
+       out (0x30),a
+       ld (hl),#0
+       dec bc
+       ld a,b
+       or c
+       jr z, uout
+       ld e,l
+       ld d,h
+       inc de
+       ldir
+uout:
+       ld a,#1
+       out (0x38),a
+       rlca
+       out (0x30),a
+       ret
+
+__ugetc:
+       pop bc
+       pop hl
+       push hl
+       push bc
+       ld a,#0x81
+       out (0x38),a
+       rlca
+       out (0x30),a
+       ld l,(hl)
+       ld h,#0
+       jr uout
+
+__ugetw:
+       pop bc
+       pop hl
+       push hl
+       push bc
+       ld a,#0x81
+       out (0x38),a
+       rlca
+       out (0x30),a
+       ld a,(hl)
+       inc hl
+       ld h,(hl)
+       ld l,a
+       jr uout
+       
+__uputc:
+       pop bc
+       pop de
+       pop hl
+       push hl
+       push de
+       push bc
+       ld a,#0x81
+       out (0x38),a
+       rlca
+       out (0x30),a
+       ld (hl),e
+       jr uout
+
+__uputw:
+       pop bc
+       pop de
+       pop hl
+       push hl
+       push de
+       push bc
+       ld a,#0x81
+       out (0x38),a
+       rlca
+       out (0x30),a
+       ld (hl),e
+       inc hl
+       ld (hl),d
+       jr uout