From 28dd214eab4ddde699f99e1ca33a3d29bb55aea9 Mon Sep 17 00:00:00 2001 From: Alan Cox Date: Wed, 29 Aug 2018 22:27:25 +0100 Subject: [PATCH] kernel: very initial Z280 sketch of a port Doesn't really use Z280 features properly yet. Basically a Z80 port running entirely supervisor mode and faking banking with the MMU. --- Kernel/platform-z280rc/Makefile | 55 ++ Kernel/platform-z280rc/README | 50 ++ Kernel/platform-z280rc/commonmem.s | 11 + Kernel/platform-z280rc/config.h | 122 +++++ Kernel/platform-z280rc/crt0.s | 74 +++ Kernel/platform-z280rc/devices.c | 45 ++ Kernel/platform-z280rc/devtty.c | 173 +++++++ Kernel/platform-z280rc/devtty.h | 8 + Kernel/platform-z280rc/discard.c | 88 ++++ Kernel/platform-z280rc/fuzix.lnk | 41 ++ Kernel/platform-z280rc/kernel.def | 44 ++ Kernel/platform-z280rc/main.c | 129 +++++ Kernel/platform-z280rc/platform_ide.h | 18 + Kernel/platform-z280rc/target.mk | 6 + Kernel/platform-z280rc/tricks.s | 13 + Kernel/platform-z280rc/z280.h | 2 + Kernel/platform-z280rc/z280rc.s | 717 ++++++++++++++++++++++++++ 17 files changed, 1596 insertions(+) create mode 100644 Kernel/platform-z280rc/Makefile create mode 100644 Kernel/platform-z280rc/README create mode 100644 Kernel/platform-z280rc/commonmem.s create mode 100644 Kernel/platform-z280rc/config.h create mode 100644 Kernel/platform-z280rc/crt0.s create mode 100644 Kernel/platform-z280rc/devices.c create mode 100644 Kernel/platform-z280rc/devtty.c create mode 100644 Kernel/platform-z280rc/devtty.h create mode 100644 Kernel/platform-z280rc/discard.c create mode 100644 Kernel/platform-z280rc/fuzix.lnk create mode 100644 Kernel/platform-z280rc/kernel.def create mode 100644 Kernel/platform-z280rc/main.c create mode 100644 Kernel/platform-z280rc/platform_ide.h create mode 100644 Kernel/platform-z280rc/target.mk create mode 100644 Kernel/platform-z280rc/tricks.s create mode 100644 Kernel/platform-z280rc/z280.h create mode 100644 Kernel/platform-z280rc/z280rc.s diff --git a/Kernel/platform-z280rc/Makefile b/Kernel/platform-z280rc/Makefile new file mode 100644 index 00000000..fc6123cd --- /dev/null +++ b/Kernel/platform-z280rc/Makefile @@ -0,0 +1,55 @@ +CROSS_CCOPTS += -I../dev/ + + +CSRCS = devtty.c +CSRCS += devices.c main.c + +DISCSRCS = discard.c + +ASRCS = z280rc.s crt0.s +ASRCS += tricks.s commonmem.s + +DISCARD_DSRCS = ../dev/devide_discard.c +DSRCS = ../dev/blkdev.c ../dev/devide.c ../dev/mbr.c + +NSRCS = + +COBJS = $(CSRCS:.c=.rel) +AOBJS = $(ASRCS:.s=.rel) +NOBJS = $(patsubst ../dev/net/%.c,%.rel, $(NSRCS)) +DISCOBJS = $(DISCSRCS:.c=.rel) +DISCARD_DOBJS = $(patsubst ../dev/%.c,%.rel, $(DISCARD_DSRCS)) +DOBJS = $(patsubst ../dev/%.c,%.rel, $(DSRCS)) +DAOBJS = $(patsubst ../dev/%.s,%.rel, $(DASRCS)) + +OBJS = $(COBJS) $(AOBJS) $(NOBJS) $(DISCOBJS) $(DOBJS) $(DISCARD_DOBJS) $(DAOBJS) + +JUNK = *.lst *.asm *.sym *.rst *.lst + +all: $(OBJS) + +$(COBJS): %.rel: %.c + $(CROSS_CC) $(CROSS_CCOPTS) -c $< + +$(DISCOBJS): %.rel: %.c + $(CROSS_CC) $(CROSS_CCOPTS) $(CROSS_CC_SEGDISC) -c $< + +$(DOBJS): %.rel: ../dev/%.c + $(CROSS_CC) $(CROSS_CCOPTS) -c $< + +$(DISCARD_DOBJS): %.rel: ../dev/%.c + $(CROSS_CC) $(CROSS_CCOPTS) $(CROSS_CC_SEGDISC) -c $< + +$(NOBJS): %.rel: ../dev/net/%.c + $(CROSS_CC) $(CROSS_CCOPTS) -c $< + +$(AOBJS): %.rel: %.s + $(CROSS_AS) $(ASOPTS) $< + +$(DAOBJS): %.rel: ../dev/%.s + $(CROSS_AS) $(ASOPTS) $@ $< + +clean: + rm -f $(OBJS) $(JUNK) core *~ + +image: diff --git a/Kernel/platform-z280rc/README b/Kernel/platform-z280rc/README new file mode 100644 index 00000000..7677becd --- /dev/null +++ b/Kernel/platform-z280rc/README @@ -0,0 +1,50 @@ +Minimal Initial Support for Bill Shen's Z280RC + +This is a port in progress. It doesn't work yet. + + +Z280RC is a Z280 based SBC with limited support for RC2014 peripherals. Only +8bit I/O port access to the bus is supported and IM2 cannto be used. In +practice this isn't much of a limitation as the Z280RC is self contained for +all things that matter. + +Requirements + +Z280RC SBC + +Supported + +Z280 on board UART as console +Z280 on board timers for clock tick and baud rate (NEITHER DONE YET) +16bit IDE/CF adapter on the board + +Not Supported (initially) + +Z280 processor modes +Virtual memory +Proper paging +Split I/D + + +Memory Map + +We fake up a Z80 with banked memory and use the MMU to create 8 (for now) +user banks with the top 8K common and the rest per bank, plus a kernel bank +that is the full 64K identity mapped. + +User Mode + +0000-00FF Z80 vectors and RST +0100-DDFF User code +DE00-DFFF udata stash +E000-EFFF Unused +F000-FFFF Common space (lots of room left) + +Kernel Mode + +0000-00FF Z80 vectors and RST +0100-???? Kernel +????-EFFF Buffers +F000-FFFF Common space (lots of room left) + +No external peripherals are currently supported. diff --git a/Kernel/platform-z280rc/commonmem.s b/Kernel/platform-z280rc/commonmem.s new file mode 100644 index 00000000..ed199180 --- /dev/null +++ b/Kernel/platform-z280rc/commonmem.s @@ -0,0 +1,11 @@ +; +; The common memory area traditionally starts with the udata and the +; interrupt stacks. As this is standard in almost all cases you can +; just include the standard implementation. +; + .module commonmem + + .area _COMMONMEM + + .include "../cpu-z80/std-commonmem.s" + diff --git a/Kernel/platform-z280rc/config.h b/Kernel/platform-z280rc/config.h new file mode 100644 index 00000000..b9e20ad4 --- /dev/null +++ b/Kernel/platform-z280rc/config.h @@ -0,0 +1,122 @@ +/* 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 + +/* This is a quick hack before we worry about the real MMU and all the rest + of it */ + +/* Select a banked memory set up */ +#define CONFIG_BANK_FIXED +/* This is the number of banks of user memory available (maximum) */ +#define MAX_MAPS 15 /* 512 KByte... minus the high one */ +/* How big is each bank - in our case 32K, 48K is actually more common. This + is hardware dependant */ +#define MAP_SIZE 0xE000 +/* How many banks do we have in our address space */ +#define CONFIG_BANKS 1 + +/* + * Define the program loading area (needs to match kernel.def) + */ +#define PROGBASE 0x0000 /* Base of user */ +#define PROGLOAD 0x0100 /* Load and run here */ +#define PROGTOP 0xDE00 /* Top of program, base of U_DATA stash */ +#define PROC_SIZE 48 /* Memory needed per process including stash */ +/* + * Definitions for swapping. + */ +#define SWAPDEV (swap_dev) /* A variable for dynamic, or a device major/minor */ +extern unsigned int swap_dev; +#define SWAP_SIZE 0x70 /* 56K in 512 byte blocks */ +#define SWAPBASE 0x0000 /* We swap the lot in one, include the */ +#define SWAPTOP 0xE000 /* vectors so its a round number of sectors */ + +#define MAX_SWAPS 16 /* Maximum number of swapped out processes. + As we use the default 15 process max this + is definitely sufficient (14 would do) */ +/* + * 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)) + +/* Set these two for networking - no point right now */ +//#define CONFIG_NET +//#define CONFIG_NET_NATIVE + + +/* What is the maximum number of /dev/hd devices we have. In theory right now + it's actually 3 - two in the IDE and one on the SD interface */ +#define MAX_BLKDEV 4 +/* Select IDE disk support, and PPIDE (parallel port IDE) as the interface */ +#define CONFIG_IDE +#define CONFIG_PPIDE /* PPIDE is present */ + +/* We will resize the buffers available after boot. This is the normal setting */ +#define CONFIG_DYNAMIC_BUFPOOL +/* Swap will be set up when a suitably labelled partition is seen */ +#define CONFIG_DYNAMIC_SWAP +/* Larger transfers (including process execution) should go directly not via + the buffer cache. For all small (eg bit) systems this is the right setting + as it avoids polluting the small cache with data when it needs to be full + of directory and inode information */ +#define CONFIG_LARGE_IO_DIRECT + +/* Specify this if there is a real time clock capable of reporting seconds. It + will be used to lock the kernel time better to reality. Other details like + Y2K support, or even supporting dates as well don't matter */ +#undef CONFIG_RTC +/* Specify that there is a full real time clock that can supply the date and + time to the system. */ +#undef CONFIG_RTC_FULL +/* Set this if the system has no proper real time clock (or has configurations + where it lacks one). This is not usually needed but for platforms it is also + see platform-sbcv2/main.c on what is needed */ +#undef CONFIG_NO_CLOCK +/* + * How fast does the clock tick (if present), or how many times a second do + * we simulate if not. For a machine without video 10 is a good number. If + * you have video you probably want whatever vertical sync/blank interrupt + * rate the machine has. For many systems it's whatever the hardware gives + * you. + * + * Note that this needs to be divisible by 10 and at least 10. If your clock + * is a bit slower you may need to fudge things somewhat so that the kernel + * gets 10 timer interrupt calls per second. + */ +#define TICKSPERSEC 10 /* Ticks per second */ + +/* + * The device (major/minor) for the console and boot up tty attached to + * init at start up. 512 is the major 2, so all the tty devices are + * 512 + n where n is the tty. + */ +#define BOOT_TTY (512 + 1) /* Set this to default device for stdio, stderr */ + /* In this case, the default is the first TTY device */ +/* + * If you have a mechanism to pass in a root device configuration then + * this holds the address of the buffer (eg a CP/M command line or similar). + * If the configuration is fixed then this can be a string holding the + * configuration. NULL means 'prompt the user'. + */ +#define CMDLINE NULL /* Location of root dev name */ + +/* Device parameters */ +#define NUM_DEV_TTY 1 /* How many tty devices does the platform support */ +#define TTYDEV BOOT_TTY /* Device used by kernel for messages, panics */ +#define NBUFS 5 /* Number of block buffers. Must be 4+ and must match + kernel.def */ +#define NMOUNTS 4 /* Number of mounts at a time */ + +/* This can optionally be set to force a default baud rate, eg if the system + console should match a firmware set rate */ +#define TTY_INIT_BAUD B38400 /* To match ROMWBW */ diff --git a/Kernel/platform-z280rc/crt0.s b/Kernel/platform-z280rc/crt0.s new file mode 100644 index 00000000..d93868a0 --- /dev/null +++ b/Kernel/platform-z280rc/crt0.s @@ -0,0 +1,74 @@ + ; Ordering of segments for the linker. + .area _CODE + .area _CODE2 + .area _HOME + .area _CONST + .area _INITIALIZED + .area _DATA + .area _BSEG + .area _BSS + .area _HEAP + .area _GSINIT + .area _GSFINAL + .area _BUFFERS + .area _DISCARD + .area _VECTORS + .area _COMMONMEM + ; note that areas below here may be overwritten by the heap at runtime, so + ; put initialisation stuff in here + .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 + .globl map_kernel + + ; startup code + .area _CODE + + ; Load at 0x0100 + ; We are executed as a CP/M task identity mapped into the + ; low 64K. Nice and simple. We just expand ourselves all + ; over CP/M and take over. +start: + di + 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 + call init_early + call init_hardware + call _fuzix_main + di +stop: halt + jr stop diff --git a/Kernel/platform-z280rc/devices.c b/Kernel/platform-z280rc/devices.c new file mode 100644 index 00000000..d51eca92 --- /dev/null +++ b/Kernel/platform-z280rc/devices.c @@ -0,0 +1,45 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * This table is the glue that holds all the kernel device driver + * logic together. Each device driver provides methods for + * open, close, read, write and ioctl, although it can opt to use + * defaults as well. + * + * The validdev function is the same for all platforms but has to live + * in the same file as the table. Just paste it into each. + */ + +struct devsw dev_tab[] = /* The device driver switch table */ +{ + /* 0: /dev/hd Hard disc block devices */ + { blkdev_open, no_close, blkdev_read, blkdev_write, blkdev_ioctl }, + /* 1: /dev/fd Floppy disc block devices */ + { no_open, no_close, no_rdwr, no_rdwr , no_ioctl }, + /* 2: /dev/tty TTY devices */ + { tty_open, tty_close, tty_read, tty_write, tty_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 */ +}; + +bool validdev(uint16_t dev) +{ + /* This is a bit uglier than needed but the right hand side is + a constant this way */ + if(dev > ((sizeof(dev_tab)/sizeof(struct devsw)) << 8) - 1) + return false; + else + return true; +} diff --git a/Kernel/platform-z280rc/devtty.c b/Kernel/platform-z280rc/devtty.c new file mode 100644 index 00000000..e9295b16 --- /dev/null +++ b/Kernel/platform-z280rc/devtty.c @@ -0,0 +1,173 @@ +/* + * This file implements the serial ports for the platform. Fuzix implements + * a reasonable subset of the System 5 termios. Certain things that are + * rarely relevant like XCASE, delay fills and parity are left to the + * driver if desired. + * + */ + +#include +#include +#include +#include +#include +#include + +/* Remember these are in I/O FExxxx range */ +__sfr __at 0x12 uart_tsr; +__sfr __at 0x14 uart_rsr; +__sfr __at 0x16 uart_rdr; +__sfr __at 0x18 uart_tdr; + +/* + * One buffer for each tty. For now just the console + */ +static char tbuf1[TTYSIZ]; + +/* + * One entry per tty. The 0th entry is never used as tty minor 0 is + * special (/dev/tty) and it's cheaper to waste a few bytes that keep + * doing subtractions. + */ +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}, +}; + +/* Write to system console. This is the backend to all the kernel messages, + kprintf(), panic() etc. */ + +void kputchar(char c) +{ + if (c == '\n') + tty_putc(1, '\r'); + tty_putc(1, c); +} + +/* + * See if the given tty is able to transmit data without blocking. This + * may be done by checking the hardware, or if there is a software + * transmit queue by checking the queue is full. + * + * There are three possible returns + * TTY_READY_NOW means fire away + * TTY_READY_SOON means we will spin trying until pre-empted. As the + * 8bit processors are slow relative to baud rates it's often + * more efficient to do this + * TTY_READY_LATER means we will give up the CPU. This is best if the + * baud rate is low, the link is blocked by flow control signals + * or the CPU is fast. + * + * If TTY_READY_LATER is returned then the kernel will also call + * tty_sleeping(minor) before sleeping on the tty so that the driver + * can turn on or off tx complete interrupts. + * + * A video display that never blocks will just return TTY_READY_NOW + */ +uint8_t tty_writeready(uint8_t minor) +{ + uint8_t r, iob; + used(minor); + iob = io_bank_set(0xFE); + r = uart_tsr & 0x01 ? TTY_READY_NOW : TTY_READY_SOON; + io_bank_set(iob); + return r; +} + +/* + * Write a character to a tty. This is the normal user space path for + * each outbound byte. It gets called in the normal tty flow, but may + * also be called from an interrupt to echo characters even if the + * tty is busy. This one reason to implement a small transmit queue. + * + * If the character echo doesn't fit just drop it. It should pretty much + * never occur and there is nothing else to do. + */ +void tty_putc(uint8_t minor, unsigned char c) +{ + uint8_t iob; + used(minor); + iob = io_bank_set(0xFE); + uart_tdr = c; + io_bank_set(iob); +} + +/* + * 16x50 conversion betwen a Bxxxx speed rate (see tty.h) and the values + * to stuff into the chip. + */ +static uint16_t clocks[] = { + 12, /* Not a real rate */ + 2304, + 1536, + 1047, + 857, + 768, + 384, + 192, + 96, + 48, + 24, + 12, + 6, + 3, + 2, + 1 +}; + +/* + * This function is called whenever the terminal interface is opened + * or the settings changed. It is responsible for making the requested + * changes to the port if possible. Strictly speaking it should write + * back anything that cannot be implemented to the state it selected. + * + * That needs tidying up in many platforms and we also need a proper way + * to say 'this port is fixed config' before making it so. + */ +void tty_setup(uint8_t minor) +{ + used(minor); +} + +/* + * This function is called when the kernel is about to sleep on a tty. + * We don't care about this. + */ +void tty_sleeping(uint8_t minor) +{ + used(minor); +} + +/* + * Return 1 if the carrier on the terminal is raised. If the port has + * no carrier signal always return 1. It is used to block a port on open + * until carrier. + */ +int tty_carrier(uint8_t minor) +{ + used(minor); + return 1; +} + +/* + * When the input queue is part drained this method is called from the + * kernel so that hardware flow control signals can be updated. + */ +void tty_data_consumed(uint8_t minor) +{ + used(minor); +} + +/* + * Our platform specific code so we have a function to call to poll the + * serial ports for activity. + */ +void tty_poll(void) +{ + uint8_t iob = io_bank_set(0xFE); + /* Should be IRQ driven but we might not be so poll anyway if + pending. IRQs are off here so this is safe */ + if (uart_rsr & 0x10) + tty_inproc(1, uart_rdr); + io_bank_set(iob); +} diff --git a/Kernel/platform-z280rc/devtty.h b/Kernel/platform-z280rc/devtty.h new file mode 100644 index 00000000..f0188e86 --- /dev/null +++ b/Kernel/platform-z280rc/devtty.h @@ -0,0 +1,8 @@ +#ifndef _DEVTTY_H +#define _DEVTTY_H + +extern void tty_poll(void); +extern uint8_t timermsr; + + +#endif diff --git a/Kernel/platform-z280rc/discard.c b/Kernel/platform-z280rc/discard.c new file mode 100644 index 00000000..0b11a133 --- /dev/null +++ b/Kernel/platform-z280rc/discard.c @@ -0,0 +1,88 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +extern int strcmp(const char *, const char *); + +/* + * Everything in this file ends up in discard which means the moment + * we try and execute init it gets blown away. That includes any + * variables declared here so beware! + */ + +/* + * We get passed each kernel command line argument. if we return 1 then + * we claim it, if not it gets passed to init. It's perfectly acceptable + * to act on a match and return to also pass it to init if you need to. + */ +uint8_t platform_param(unsigned char *p) +{ + used(p); + return 0; +} + +/* + * Set up our memory mappings. This is not needed for simple banked memory + * only more complex setups such as 16K paging. + */ +void map_init(void) +{ +} + +/* + * Add all the available pages to the list of pages we an use. If this + * is runtime dynamic check to make sure you don't add more than MAX_MAPS + * of them. On some machines with a lot of RAM the implementation turns + * the excess into a RAM disc + * + * The mapping can be logical numbers 1-n, or can be physical values to + * write into registers. Whatever works best. The 0 value is however + * reserved throughout to indicate kernel mode in calls, and also + * to mean swapped out for processes. If your bank 0 is user space you + * might need to just dec/inc before using it in an I/O port or similar + * to avoid confusion. + * + * Kernel in bank 0, user in banks 1-8. That's enough to get us going + * until we do real Z280 management + */ +void pagemap_init(void) +{ + uint8_t i; + for (i = 1; i <= 8; i++) + pagemap_add(i); +} + +/* + * 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_add(n--); +} + +/* + * Called after interrupts are enabled in order to enumerate and set up + * any devices. In our case we simply need to probe the IDE and SD card. + */ +void device_init(void) +{ + devide_init(); +} diff --git a/Kernel/platform-z280rc/fuzix.lnk b/Kernel/platform-z280rc/fuzix.lnk new file mode 100644 index 00000000..f0a1d4c8 --- /dev/null +++ b/Kernel/platform-z280rc/fuzix.lnk @@ -0,0 +1,41 @@ +-mwxuy +-i fuzix.ihx +-b _CODE=0x0100 +-b _VECTORS=0xF000 +-b _INITIALIZER=0xFD00 +-l z80 +platform-z280rc/crt0.rel +platform-z280rc/commonmem.rel +platform-z280rc/z280rc.rel +start.rel +version.rel +lowlevel-z80.rel +usermem.rel +usermem_std-z80.rel +platform-z280rc/tricks.rel +platform-z280rc/main.rel +platform-z280rc/discard.rel +timer.rel +kdata.rel +platform-z280rc/devices.rel +devio.rel +filesys.rel +process.rel +inode.rel +syscall_exec16.rel +syscall_fs.rel +syscall_fs2.rel +syscall_fs3.rel +syscall_proc.rel +syscall_other.rel +tty.rel +mm.rel +swap.rel +bankfixed.rel +devsys.rel +platform-z280rc/devtty.rel +platform-z280rc/blkdev.rel +platform-z280rc/mbr.rel +platform-z280rc/devide.rel +platform-z280rc/devide_discard.rel +-e diff --git a/Kernel/platform-z280rc/kernel.def b/Kernel/platform-z280rc/kernel.def new file mode 100644 index 00000000..5765fb52 --- /dev/null +++ b/Kernel/platform-z280rc/kernel.def @@ -0,0 +1,44 @@ +; FUZIX mnemonics for memory addresses etc +; +; +; The U_DATA address. If we are doing a normal build this is the start +; of common memory. We do actually have a symbol for udata so +; eventually this needs to go away +; +U_DATA .equ 0xE000 ; (this is struct u_data from kernel.h) +U_DATA__TOTALSIZE .equ 0x200 ; 256+256 bytes) +; +; Space for the udata of a switched out process within the bank of +; memory that it uses. Normally placed at the very top +; +U_DATA_STASH .equ 0xDE00 ; DE00-DFFF +; +; Z80 systems start program space at 0, and load at 0x100 so that the +; low 256 bytes are free for syscall vectors and the like, with some +; also used as a special case by the CP/M emulator. +; +PROGBASE .equ 0x0000 +PROGLOAD .equ 0x0100 +; +; CPU type +; 0 = CMOS Z80 (set this for Z280 for now) +; 1 = NMOS Z80 (also works with CMOS) +; 2 = Z180 +; +; If either NMOS or CMOS may be present pick NMOS as the NMOS build +; contains extra code to work around an erratum n the NUMS Z80 +; +Z80_TYPE .equ 0 +; +; For special platforms that have external memory protectuon hardware +; Just say 0. +; +Z80_MMU_HOOKS .equ 0 +; +; Set this if the platform has swap enabled in config.h +; +CONFIG_SWAP .equ 1 +; +; The number of disk buffers. Must match config.h +; +NBUFS .equ 5 diff --git a/Kernel/platform-z280rc/main.c b/Kernel/platform-z280rc/main.c new file mode 100644 index 00000000..5b625fb6 --- /dev/null +++ b/Kernel/platform-z280rc/main.c @@ -0,0 +1,129 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +uint16_t ramtop = PROGTOP; +uint16_t swap_dev = 0xFFFF; + +/* + * This routine is called continually when the machine has nothing else + * it needs to execute. On a machine with entirely interrupt driven + * hardware this could just halt for interrupt. + */ +void platform_idle(void) +{ + /* Disable interrupts so we don't accidentally process a polled tty + and interrupt call at once and make a mess */ + irqflags_t irq = di(); + tty_poll(); + /* Restore prior state. */ + irqrestore(irq); +} + +/* + * This routine is called from the interrupt handler code to process + * interrupts. All of the nasty stuff (register saving, bank switching, + * reti instructions) is dealt with for you. + */ +void platform_interrupt(void) +{ + tty_poll(); + timer_interrupt(); +} + +/* This points to the last buffer in the disk buffers. There must be at least + four buffers to avoid deadlocks. */ +struct blkbuf *bufpool_end = bufpool + NBUFS; + +/* + * We pack discard into the memory image is if it were just normal + * code but place it at the end after the buffers. When we finish up + * booting we turn everything from the buffer pool to common into + * buffers. This blows away the _DISCARD segment. + */ +void platform_discard(void) +{ + /* We start our common block with the vectors as they must be 4K + aligned */ + uint16_t discard_size = (uint16_t)vectors - (uint16_t)bufpool_end; + bufptr bp = bufpool_end; + + discard_size /= sizeof(struct blkbuf); + + kprintf("%d buffers added\n", discard_size); + + bufpool_end += discard_size; + + memset( bp, 0, discard_size * sizeof(struct blkbuf) ); + + for( bp = bufpool + NBUFS; bp < bufpool_end; ++bp ){ + bp->bf_dev = NO_DEVICE; + bp->bf_busy = BF_FREE; + } +} + +/****************************************************************************/ +/* The innermost part of the transfer routines has to live in common memory */ +/* since it must be able to bank switch to the user memory bank. */ +/****************************************************************************/ + +/* IDE Port I/O: Z280 edition. We know the bank is the usual default 0 here */ + +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 +#ifdef SWAPDEV + cp #2 + jr nz, not_swapin + ld a, (_blk_op+BLKPARAM_SWAP_PAGE) ; blkparam.swap_page + call map_for_swap + jr swapin +not_swapin: +#endif + or a ; test is_user + call nz, map_process_always ; map user memory first if required +swapin: +;; inirw ; transfer 256 words + .db 0xED, 0x92 + or a ; test is_user + ret z ; done if kernel memory transfer + jp map_kernel ; else map kernel then return + __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 +#ifdef SWAPDEV + cp #2 + jr nz, not_swapout + ld a, (_blk_op+BLKPARAM_SWAP_PAGE) ; blkparam.swap_page + call map_for_swap + jr swapout +not_swapout: +#endif + or a ; test is_user + call nz, map_process_always ; else map user memory first if required +swapout: +;; otirw ; transfer 256 words + .db 0xED, 0x93 + or a ; test is_user + ret z ; done if kernel memory transfer + jp map_kernel ; else map kernel then return + __endasm; +} diff --git a/Kernel/platform-z280rc/platform_ide.h b/Kernel/platform-z280rc/platform_ide.h new file mode 100644 index 00000000..0f8e13ba --- /dev/null +++ b/Kernel/platform-z280rc/platform_ide.h @@ -0,0 +1,18 @@ +#define ide_select(x) +#define ide_deselect() + +/*16bit, no altstatus/control */ +#define IDE_REG_DATA 0xC0 +#define IDE_REG_ERROR 0xC2 +#define IDE_REG_FEATURES 0xC2 +#define IDE_REG_SEC_COUNT 0xC5 +#define IDE_REG_LBA_0 0xC7 +#define IDE_REG_LBA_1 0xC9 +#define IDE_REG_LBA_2 0xCB +#define IDE_REG_LBA_3 0xCD +#define IDE_REG_DEVHEAD 0xCD +#define IDE_REG_STATUS 0xCF +#define IDE_REG_COMMAND 0xCF + +/* The data register should be accessed using inirw/otirw and similar */ +#define IDE_NONSTANDARD_XFER diff --git a/Kernel/platform-z280rc/target.mk b/Kernel/platform-z280rc/target.mk new file mode 100644 index 00000000..b97b60c9 --- /dev/null +++ b/Kernel/platform-z280rc/target.mk @@ -0,0 +1,6 @@ +# +# Tell the build system what processor type we are using +# For now pretend to be a Z80 +# +export CPU = z80 +export USERCPU = z80 diff --git a/Kernel/platform-z280rc/tricks.s b/Kernel/platform-z280rc/tricks.s new file mode 100644 index 00000000..1eaafcaa --- /dev/null +++ b/Kernel/platform-z280rc/tricks.s @@ -0,0 +1,13 @@ +; +; For simple banked systems there is a standard implementation. The +; only reason to do otherwise is for speed. A custom bank logic aware +; bank to bank copier will give vastly better fork() performance. +; +; As this is meant to be a simple reference port we use the standard +; approach. The morbidly curious can read the TRS80 model 1 bank to +; bank copier. +; + .include "../kernel.def" + .include "kernel.def" + + .include "../lib/z80fixedbank.s" diff --git a/Kernel/platform-z280rc/z280.h b/Kernel/platform-z280rc/z280.h new file mode 100644 index 00000000..6fb9e21e --- /dev/null +++ b/Kernel/platform-z280rc/z280.h @@ -0,0 +1,2 @@ +extern uint8_t io_bank_set(uint8_t bank) __z88dk_fastcall; +extern uint8_t vectors[]; diff --git a/Kernel/platform-z280rc/z280rc.s b/Kernel/platform-z280rc/z280rc.s new file mode 100644 index 00000000..27baf47c --- /dev/null +++ b/Kernel/platform-z280rc/z280rc.s @@ -0,0 +1,717 @@ +; +; Z280 RC support. +; +; Minimal for now to get us up and running +; + + .module sbcv2 + + ; exported symbols + .globl init_early + .globl init_hardware + .globl interrupt_handler + .globl _program_vectors + .globl map_kernel + .globl map_process + .globl map_process_a + .globl map_process_always + .globl map_save + .globl map_restore + .globl map_for_swap + .globl platform_interrupt_all + .globl _kernel_flag + + ; 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 trap_illegal + .globl outcharhex + .globl outstring + .globl outhl + .globl null_handler + .globl nmi_handler + .globl _inint + .globl kstack_top + + .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 + +; ----------------------------------------------------------------------------- +; COMMON MEMORY BANK (kept even when we task switch) +; ----------------------------------------------------------------------------- + .area _COMMONMEM + +; +; This method is invoked early in interrupt handling before any +; complex handling is done. It's useful on a few platforms but +; generally a ret is all that is needed +; +platform_interrupt_all: + ret + +; +; If you have a ROM monitor you can get back to then do so, if not +; fall into reboot. +; +_platform_monitor: +; +; Reboot the system if possible, halt if not. On a system where the +; ROM promptly wipes the display you may want to delay or wait for +; a keypress here (just remember you may be interrupts off, no kernel +; mapped so hit the hardware). +; +_platform_reboot: + jr _platform_reboot + +; ----------------------------------------------------------------------------- +; KERNEL MEMORY BANK (may be below 0x8000, only accessible when the kernel is +; mapped) +; ----------------------------------------------------------------------------- + .area _CODE + +; +; This routine is called very early, before the boot code shuffles +; things into place. We assume the bootstrap already set up +; +; - bus timing and initialization +; - bus timing and control +; - cache +; +init_early: + ld c,#0x16 + ld hl,#0x0000 +; ldctl (c),hl ; set interrupt status + .byte 0xED, 0x6E + ld c,#0x06 + ld hl,#_vectors ; Vectors on 4K boundary + ld l,h ; Work around crappy linker + ld h,#0 +; ldctl (c),hl ; into int trap/vector ptr + .byte 0xED, 0x6E + ld c,#0x08 + ld l,#0 +; ldctl (c),hl ; clear I/O page + .byte 0xED, 0x6E + ld c,#0x10 + ld l,#0x04 ; no user I/O no EPU +; ldctl (c),hl ; set trap control (can't set + ; stack trap until have user mode + ; done) + .byte 0xED, 0x6E + ret + +; ----------------------------------------------------------------------------- +; DISCARD is memory that will be recycled when we exec init +; ----------------------------------------------------------------------------- + .area _DISCARD +; +; After the kernel has shuffled things into place this code is run. +; It's the best place to breakpoint or trace if you are not sure your +; kernel is loading and putting itself into place properly. +; +; It's required jobs are to set up the vectors, ramsize (total RAM), +; and procmem (total memory free to processs), as well as setting the +; interrupt mode but *not* enabling interrupts. Many platforms also +; program up support hardware like PIO and CTC devices here. +; +init_hardware: + call map_kernel + ld l,#0xFF + call _io_bank_set + push hl + ld hl,#0xBBE0 ; MMU on S and U, no split I/D + ; We may want to consider MMU off in + ; supervisor later on - is there a perf + ; gain versus MMU on ?? + ld c,#0xF0 +; outw (c),hl + .byte 0xED,0xBF + pop hl + call _io_bank_set + + ld hl,#2048 + ld (_ramsize), hl + ld hl,#1984 + ld (_procmem), hl + + ; set up interrupt vectors for the kernel (also sets up common memory in page 0x000F which is unused) + ld hl, #0 + push hl + call _program_vectors + pop hl + + ; Our CTC is fed from the system clock / 4 which gives us + ; 7.372800Mhz input. We want a semsible multiple of 10Hz and + ; for a fast CPU 50 or 100 isn't a bad value. On a single CTC + ; counter our best shot is 120Mhz (61440 divider exactly) + + ld l,#0xFE + call _io_bank_set + push hl + ld a,#0xA0 ; Timer, interrupting + out (0xE0),a + ld hl,#61440 ; 120Hz + ld c,#0xE2 +; outw (c),hl + .byte 0xED,0xBF + ld a,#0xC0 ; Enable, gate + out (0xE1),a + pop hl + call _io_bank_set + + +; im 3 ; set CPU interrupt mode + .byte 0xED, 0x4E + + ret + +; +; Bank switching unsurprisingly must be in common memory space so it's +; always available. This is a simple hack to get us going. The Z280 +; has a real MMU and virtual memory. We just treat it like a bunch of +; banks. +; + .area _COMMONMEM + +mapreg: .db 0 ; Our map register is write only so keep a copy +mapsave: .db 0 ; Saved copy of the previous map (see map_save) + +_kernel_flag: + .db 1 ; We start in kernel mode + +; +; This is invoked with a NULL argument at boot to set the kernel +; vectors and then elsewhere in the kernel when the kernel knows +; a bank may need vectors writing to it. +; +; Will need rewriting when we do things properly +; +_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 + + ; set restart vector for FUZIX system calls + ; once we do real Z280 mode we'll need this to syscall + 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 + ; Fall into map_kernel + +; +; Mapping set up for the Z280. This is hack to get us going +; +; We map the low 64K 1:1 in kernel mode, keep the top 8K fixed and +; map the others by 'bank' for user mode. We don't actually use the +; hardware user mode yet. It's all just a bodge to get us up and +; running +; +map_kernel: + push af + xor a + call map_process_a ; do all the logic in one place with + pop af ; kernel as entry 0 in the table + ret + ; map_process is called with HL either NULL or pointing to the + ; page mapping. Unlike the other calls it's allowed to trash AF +map_process: + ld a, h + or l + jr z, map_kernel +map_process_hl: + ld a, (hl) ; and fall through + ; + ; With a simple bank switching system you need to provide a + ; method to switch to the bank in A without corrupting any + ; other registers. The stack is safe in common memory. + ; For swap you need to provide what for simple banking is an + ; identical routine. +map_for_swap: +map_process_a: ; used by bankfork + push bc + push de + push hl + ld (mapreg), a ; bank + ld c,#0x08 +;; ldctl hl,(c) + .byte 0xED, 0x66 + push hl + ld l,#0xff ; MMU is I/O bank FF +;; ldctl (c),hl + .byte 0xED, 0x6E + ld l,a + ld h,#0 + add hl,hl ; 16 words per bank + add hl,hl + add hl,hl + add hl,hl + add hl,hl + ld de,#frames ; Lazy - look it up it's only a hack for now + ld a,#0x10 + out (0x44),a ; pointer to 0x10 (system pages) + ld bc,#0x0FF4 +;; otirw + .db 0xED, 0x93 + pop hl + ld c,#0x08 +;; ldctl (c),hl ; previous bank register + .byte 0xED, 0x6E + pop hl + pop de + pop bc + ret + + ; + ; Map the current process into memory. We do this by extracting + ; the bank value from u_page. + ; +map_process_always: + push af + push hl + ld hl, #U_DATA__U_PAGE + call map_process_hl + pop hl + pop af + ret + + ; + ; Save the existing mapping. The place you save it to needs to + ; be in common memory as you have no idea what bank is live + ; +map_save: push af + ld a, (mapreg) + ld (mapsave), a + pop af + ret + ; + ; Restore the saved bank. Note that you don't need to deal with + ; stacking of banks (we never recursively use save/restore), and + ; that we may well call save and decide not to call restore. + ; +map_restore: + push af + ld a, (mapsave) + call map_process_a + pop af + ret + ; + ; Used for low level debug. Output the character in A without + ; corrupting other registers. May block. Interrupts and memory + ; state are undefined + ; +outchar: + push af + push bc + push de + push hl + ld l,#0xFE + call _io_bank_set +twait: in a,(0x12) + bit 0,a + jr z, twait + pop af + out (0x18),a + call _io_bank_set + pop hl + pop de + pop bc + pop af + ret + +; +; These belong in core Z280 code eventually +; + .globl _io_bank_set + .globl _flush_cpu_cache + + +_io_bank_set: + ld c,#8 + ex de,hl ; save the new value in DE +;; ldctl hl,(c) ; read old into HL + .byte 0xED, 0x66 + ex de,hl ; switch bank +;; ldctl (c),hl ; new into HL + .byte 0xED, 0x6E + ex de,hl ; and back again to return old + ret + +_flush_cpu_cache: + .byte 0xED, 0x65 + ret + + +; +; Mapping tables for speed +; +frames: + .word 0x0000 ; Identity map kernel + .word 0x0010 + .word 0x0020 + .word 0x0030 + .word 0x0040 + .word 0x0050 + .word 0x0060 + .word 0x0070 + .word 0x0080 + .word 0x0090 + .word 0x00A0 + .word 0x00B0 + .word 0x00C0 + .word 0x00D0 + .word 0x00E0 ; Common + .word 0x00F0 ; + + .word 0x0100 + .word 0x0110 + .word 0x0120 + .word 0x0130 + .word 0x0140 + .word 0x0150 + .word 0x0160 + .word 0x0170 + .word 0x0180 + .word 0x0190 + .word 0x01A0 + .word 0x01B0 + .word 0x01C0 + .word 0x01D0 + .word 0x00E0 ; Common + .word 0x00F0 ; + + .word 0x0200 + .word 0x0210 + .word 0x0220 + .word 0x0230 + .word 0x0240 + .word 0x0250 + .word 0x0260 + .word 0x0270 + .word 0x0280 + .word 0x0290 + .word 0x02A0 + .word 0x02B0 + .word 0x02C0 + .word 0x02D0 + .word 0x00E0 ; Common + .word 0x00F0 ; + + .word 0x0300 + .word 0x0310 + .word 0x0320 + .word 0x0330 + .word 0x0340 + .word 0x0350 + .word 0x0360 + .word 0x0370 + .word 0x0380 + .word 0x0390 + .word 0x03A0 + .word 0x03B0 + .word 0x03C0 + .word 0x03D0 + .word 0x00E0 ; Common + .word 0x00F0 ; + + .word 0x0400 + .word 0x0410 + .word 0x0420 + .word 0x0430 + .word 0x0440 + .word 0x0450 + .word 0x0460 + .word 0x0470 + .word 0x0480 + .word 0x0490 + .word 0x04A0 + .word 0x04B0 + .word 0x04C0 + .word 0x04D0 + .word 0x00E0 ; Common + .word 0x00F0 ; + + .word 0x0500 + .word 0x0510 + .word 0x0520 + .word 0x0530 + .word 0x0540 + .word 0x0550 + .word 0x0560 + .word 0x0570 + .word 0x0580 + .word 0x0590 + .word 0x05A0 + .word 0x05B0 + .word 0x05C0 + .word 0x05D0 + .word 0x00E0 ; Common + .word 0x00F0 ; + + .word 0x0600 + .word 0x0610 + .word 0x0620 + .word 0x0630 + .word 0x0640 + .word 0x0650 + .word 0x0660 + .word 0x0670 + .word 0x0680 + .word 0x0690 + .word 0x06A0 + .word 0x06B0 + .word 0x06C0 + .word 0x06D0 + .word 0x00E0 ; Common + .word 0x00F0 ; + + .word 0x0700 + .word 0x0710 + .word 0x0720 + .word 0x0730 + .word 0x0740 + .word 0x0750 + .word 0x0760 + .word 0x0770 + .word 0x0780 + .word 0x0790 + .word 0x07A0 + .word 0x07B0 + .word 0x07C0 + .word 0x07D0 + .word 0x00E0 ; Common + .word 0x00F0 ; + + .word 0x0700 + .word 0x0710 + .word 0x0720 + .word 0x0730 + .word 0x0740 + .word 0x0750 + .word 0x0760 + .word 0x0770 + .word 0x0780 + .word 0x0790 + .word 0x07A0 + .word 0x07B0 + .word 0x07C0 + .word 0x07D0 + .word 0x00E0 ; Common + .word 0x00F0 ; + + +; +; Interrupt vectors. Kernel and identity mapped +; + .area _VECTORS + +SUPER .equ 0x0000 ; Supervisor, all ints masked + + .globl _vectors + +_vectors: + .word #SUPER ; 00 Reserved + .word invalid + .word #SUPER ; 04 NMI + .word nmi_handler + .word #SUPER ; 08 External Line A + .word external + .word #SUPER ; 0C External Line B + .word external + .word #SUPER ; 10 External Line C + .word external + .word #SUPER ; 14 CTC 0 + .word ctc0 + .word #SUPER ; 18 CTC 1 + .word ctc1 + .word #SUPER ; 1C Reserved + .word invalid ; The broken undocumented CTC + .word #SUPER ; 20 CTC 2 + .word invalid + .word #SUPER ; 24 DMA 0 + .word invalid + .word #SUPER ; 28 DMA 1 + .word invalid + .word #SUPER ; 2C DMA 2 + .word invalid + .word #SUPER ; 30 DMA 3 + .word invalid + .word #SUPER ; 34 UART rx + .word ttyint + .word #SUPER ; 38 UART tx + .word invalid + .word #SUPER ; 3C Single-step + .word sstep + .word #SUPER ; 40 Breakpoint + .word sigtrap + .word #SUPER ; 44 Divison by zero + .word sigfpe + .word #SUPER ; 48 Stack overflow + .word stackover + .word #SUPER ; 4C Access violation + .word sigsegv + .word #SUPER ; 50 System call + .word invalid + .word #SUPER ; 54 Privileged instruction + .word sigill + .word #SUPER ; 58 EPU + .word sigill + .word #SUPER ; 5C EPU + .word sigill + .word #SUPER ; 60 EPU + .word sigill + .word #SUPER ; 64 EPU + .word invalid + .word #SUPER ; 68 Reserved + .word invalid + .word #SUPER ; 6C Reserved + .word invalid + + ; What can follow then is 384 word size vectors for INT A B C + ; for IM2 style external I/O which we don't use + + .area _COMMONMEM + +SIGILL .equ 4 +SIGTRAP .equ 5 +SIGFPE .equ 8 +SIGSEGV .equ 11 + + .globl _irq_source + +_irq_source: + .byte 0 + +invalid: + ld hl,#invalidint + call outstring + jr trapexit + +external: + ; Interrupt off the RC2014 bus + push af + xor a +irqcall: + ld (_irq_source),a + ; This bit belongs in a future Z280 lowlevel not here + push de + push hl + xor a + call _io_bank_set + push hl + call interrupt_handler + pop hl + call _io_bank_set + pop hl + pop de + pop af + jr trapexit +ctc0: + push af + ld a,#1 + jr irqcall +ctc1: + push af + ld a,#2 + jr irqcall + +ttyint: + push af + ld a,#3 + jr irqcall + +sstep: +sigtrap: + push af + ld a,#SIGTRAP + jr sig_or_die +sigfpe: + push af + ld a,#SIGFPE + jr sig_or_die +sigsegv: + push af + ld a,#SIGSEGV + jr sig_or_die +sigill: + push af + ld a,#SIGILL +sig_or_die: + ; Once we have proper supervisor/user we can just check the pushed + ; status to see what to do. For now fudge it roughly. + push af + ld a,(_inint) + or a + jr nz, diediedie + ld a,(U_DATA__U_INSYS) + or a + jr nz, diediedie + ; TODO - we need to go via the syscall signal path as we need this + ; to be synchronous. Will need to be in the lowlevel-z280 code when + ; we get there + jr trapexit + +stackover: + ld sp,#kstack_top + ld hl,#stackfault + call outstring + jp _platform_monitor + +trapexit: + ; Discard the data + inc sp + inc sp + ; retil + .byte 0xed, 0x55 + +invalidint: + .asciz 'INVIRQ' +stackfault: + .asciz 'STKFLT' + +diediedie: + call outcharhex + pop af ; discard + pop hl ; cause + call outhlcolon + pop hl ; status + call outhlcolon + pop hl ; pc + call outhlcolon + jp _platform_monitor + +outhlcolon: + call outcharhex + ld a,#':' + jp outhl -- 2.34.1