From: Alan Cox Date: Mon, 22 Jan 2018 00:10:29 +0000 (+0000) Subject: platform-appleiie: Initial sketches on how a IIe port might look X-Git-Url: https://git.ndcode.org/public/gitweb.cgi?a=commitdiff_plain;h=f4faa1cb17ec57df0ce4d8bfa3da78aebb3a13a9;p=FUZIX.git platform-appleiie: Initial sketches on how a IIe port might look --- diff --git a/Kernel/platform-appleiie/Makefile b/Kernel/platform-appleiie/Makefile new file mode 100644 index 00000000..b49ca19a --- /dev/null +++ b/Kernel/platform-appleiie/Makefile @@ -0,0 +1,35 @@ + +CSRCS = devtty.c devhd.c +CSRCS += devices.c main.c + +ASRCS = appleii.s crt0.s +ASRCS += tricks.s commonmem.s + +COBJS = $(CSRCS:.c=$(BINEXT)) +AOBJS = $(ASRCS:.s=$(BINEXT)) +OBJS = $(COBJS) $(AOBJS) + +JUNK = $(CSRCS:.c=.o) $(CSRCS:.c=.s) $(ASRCS:.s=.o) + +all: $(OBJS) + +$(COBJS): %$(BINEXT): %.c + $(CROSS_CC) $(CROSS_CCOPTS) $(CROSS_CC_SEG1) $< + +$(AOBJS): %$(BINEXT): %.s + $(CROSS_AS) $(ASOPTS) $< -o $*$(BINEXT) + +clean: + rm -f $(OBJS) $(JUNK) core *~ + +image: + $(CROSS_LD) -o ../fuzix.bin --mapfile ../fuzix.map -C ld65.cfg crt0.o commonmem.o \ + appleii.o ../start.o ../version.o ../lowlevel-6502.o ../simple.o \ + ../swap.o \ + tricks.o main.o ../timer.o ../kdata.o devhd.o devices.o ../vt.o \ + ../devio.o ../filesys.o ../process.o ../inode.o ../syscall_fs.o \ + ../syscall_proc.o ../syscall_other.o ../mm.o ../bankfixed.o \ + ../tty.o ../devsys.o ../syscall_fs2.o ../syscall_fs3.o \ + ../syscall_exec16.o \ + ../usermem.o ../usermem_std-6502.o devtty.o + dd if=../fuzix.bin of=fuzix.img bs=512 skip=1 diff --git a/Kernel/platform-appleiie/README b/Kernel/platform-appleiie/README new file mode 100644 index 00000000..022e2dae --- /dev/null +++ b/Kernel/platform-appleiie/README @@ -0,0 +1,127 @@ +Early Draft For Apple IIe + +Assumptions: +- Single process in memory at a time +- 128K machine +- Some kind of timer/clock present that can provide an interrupt + +System Problems To Solve: +- Do we call disk devices via the PASCAL or ProDOS firmware gloop +- Can some or any of the devices handle being asked to do I/O to alt + bank and/or language card spaces +- How to drive the 140K floppies +- C000-CFFF are unavailable putting a big hole mid memory, how do we handle + that sanely on the user side. +- Can we do more banking tricks to get more of Fuzix in language card space +- How do we cleanly deal with Z and S when we can't load directly into them +- How do you tell a ram drive from a hard disk +- How do we pick the right swap device +- Can we put discard over other bits of low memory so we at least can blow + some of it away into the second text screen and maybe merge the rest with + buffers +- Glue for talking to the mouse, appletalk, clock etc +- Can we bank the syscall blocks to make best use of the wacky 4K window + in order to get networking in ? +- Can we stuff fonts into an alt bank if we do graphics (eg on ramworks) +- Can we hide the tty buffers in the 4K funny altbank bit ? +- Non sucky console I/O + +Architectural Problems To Solve: +- Support interrupt enabled simple swap mode +- The swapper has no understanding of the fact we have not just udata/C stack + to swap along with user space but also a kernel S and Z +- There is no way to move the S stack around. The tricks.s proposed code has + a suggested trick but that implies we'll need to play interesting games + at swap time +- Pre-emption in IRQ logic for 6502 Apple style +- 6502 dynamic loader and ZP assignment is needed for AppleIIe. Also maybe + an opportunity to tweak the binary format to create two banks normally + relocated together as one, but can be split (eg stuff libc/runtime high). + - and perhaps factors in to the 'shared library' idea where we have the + shared library in high memory with a jump table, and a small block of + app space for writable library data. Runtime however would be too expensive + to jump table so would need to build a fixed size version that matched up + on 6502 and 65C02 - doable but tedious. +- HTF do we load it all into memory ? Do we need a ProDOS loader where we + just blow up /RAM, load all the extras into alt bank from files and then + do a giant shuffle when we kill off ProDOS ? + +Later + +- Add a multiple binary kernel to use RAMworks banking +- Can we do double hires then ? + + +Use a modern cc65 from https://github.com/cc65/cc65 + +Our planned memory mapping looks like this + + 0000-00FF Zero Page + 0100-01FF Stack (Kernel/Interrupt/Firmware usually) + 0200-03FF Firmware reserved for now (to be cautious) + 0400-07FF Half of 80 column display / All of 40 + 0800-1FFF Discard area + 2000-BEFF Kernel load and execution area (covers double + hi-res space) + BF00-BFFF ProDOS leaves us info here before we kill it off + C000-CFFF I/O + D000-FFFF Kernel (with a 4K extra overlay if we need it) + +And in alt banks + + 0000-00FF Alt ZP (used for user space and a few bytes for + our IRQ helpers etc) + 0100-01FF User stack + 0200-03FF Reserve for now + 0400-07FF Half of 80 column display / Free in 40 + 0800-BFFF Application 47.5 K + C000-CFFF I/O + D000-FEFF ?? (cc65 runtime 'shared' libc ?) + FF00-FFFF Stubs and extra IRQ paging logic + +For multiple bank (Ramworks) we could instead use bank 2+ for most apps +and bank 0 would then be kernel with a hole at 2000-3FFF for video and the +rest for the OS (with the video output/scroll etc hiding in the 4K hole in +the language ram space along with the font) + + +TODO +---- +- Most of the work! This is just a sketch but in particular I've yet to + * Write the hack code that preserves a bit of S for swapping + * Write any of the glue to the protocol convertor methods + * Figure out how to load it off disk (some kind of prodos boot + strap system file or fuzix itself loading the rest of the code ? + (could we do some kind of unpack or is there too much non zero ?) + Trouble is we only get 2000-BEFF which is 40K, or 0800-BEFF with + the cc65 loader (46848 bytes) + + For unpacking we might need to build an image with the data, udata + and bss all low (just over 8K) and stuff the loader into it at + $2000 so that we do + + Load at $2000 + Move blocks about + Jump out of BSS + wipe BSS + + By my maths it still wouldn't fit. + + Maybe for that matter boot in DOS3 as it won't muck about with + banks on us ? + +- Look at banking system calls. The core kernel is designed to allow the + syscalls to be banked (SYS1-SYS5), each of which is about $A00 bytes. That + would let us hide the syscalls in the language ROM space along with common + and use the awkward other 4K. We might need to do this to get network in. + + +- Test signal handling paths +- Fix brk() checking +- Fix execl() execle() in userspace (so init can be fully tested) +- Add pre-emption logic to the interrupt return path (and a clock based + check for non-timer machines on syscall paths is needed in the core code + as it's not yet a supported configuration) + + + diff --git a/Kernel/platform-appleiie/appleii.s b/Kernel/platform-appleiie/appleii.s new file mode 100644 index 00000000..b2a572cd --- /dev/null +++ b/Kernel/platform-appleiie/appleii.s @@ -0,0 +1,545 @@ +; +; Apple IIe platform functions +; + + .export init_early + .export init_hardware + .export _program_vectors + .export map_kernel + .export map_process + .export map_process_always + .export map_save + .export map_restore + + .export _unix_syscall_i + .export _platform_interrupt_i + .export platform_doexec + + .export _hd_map + + ; exported debugging tools + .export _trap_monitor + .export _trap_reboot + .export outchar + .export ___hard_di + .export ___hard_ei + .export ___hard_irqrestore + .export vector + + .import interrupt_handler + .import _ramsize + .import _procmem + .import nmi_handler + .import unix_syscall_entry + .import kstack_top + .import istack_switched_sp + .import istack_top + .import _unix_syscall + .import _platform_interrupt + .import _kernel_flag + .import stash_zp + .import pushax + + .import outcharhex + .import outxa + .import incaxy + + .import _create_init_common + + .include "kernel.def" + .include "../kernel02.def" + .include "zeropage.inc" + +; +; syscall is jsr [$00fe] (for now anyway) +; +syscall = $FE +; ----------------------------------------------------------------------------- +; COMMON MEMORY BANK (0x0200 upwards after the common data blocks) +; ----------------------------------------------------------------------------- + .segment "COMMONMEM" + +; +; Fixme - can we get back to the AppleII monitor ? +; +_trap_monitor: + jmp _trap_monitor + +_trap_reboot: + jmp _trap_reboot ; FIXME: original ROM map and jmp + +___hard_di: + php + sei ; Save old state in return to C + pla ; Old status + rts +___hard_ei: + cli ; on 6502 cli enables IRQs!!! + rts + +___hard_irqrestore: + and #4 ; IRQ flag + bne irq_on + cli + rts +irq_on: + sei + rts + +; ----------------------------------------------------------------------------- +; KERNEL MEMORY BANK (only accessible when the kernel is mapped) +; ----------------------------------------------------------------------------- + .code + +init_early: + rts + +init_hardware: + ; set system RAM size for test purposes + lda #128 + sta _ramsize + lda #0 + sta _ramsize+1 + lda #64 + sta _procmem + lda #0 + sta _procmem+1 + rts +; +; We will set the vectors and stubs up early in boot for both banks so +; need do nothing here. +; +_program_vectors: + rts + +;------------------------------------------------------------------------------ +; COMMON MEMORY PROCEDURES FOLLOW + + .segment "COMMONMEM" +; +; For swap only this is trivial. The later iie + ramworks kernel +; will have far more to do as we'll need to load a page register +; from the process page. We need these routines in common so always +; available. (Probably crt0.s will need to coppy commonmem to both +; language banks not just the vectors) +; +; Select the auxiliary memory between $200 and $BFFF. If we copy +; common high into each bank then we'll want to switch the language +; bank around too +; +map_process_always: + sta $C003 + sta $C005 + sta $C055 + rts +; +; X,A points to the map table of this process but it doesn't +; matter as we have one process +; +map_process: + cmp #0 + bne map_process_always + cpx #0 + bne map_process_always +; +; Map in the kernel +; $200-$BFFF from main bank (we don't flip ALTZP in core kenrel that +; would cause a train wreck). Note that once we do syscall banking +; we have to think *which* language bank mapping to set. +; +map_kernel: + sta $C002 + sta $C004 + sta $C054 + rts + +; +; Restore mapping. This may not be sufficient. We may need to do a +; complex save/restore if we do clever stuff in future so that an +; interrupt can occur while we are in other mappings or when we have +; varied language mappings. Think about 80 col video as a starter +; +map_restore: + lda saved_map + beq map_kernel + bne map_process_always +; +; Save the current mapping. For the moment we just track if we are +; user or kernel. We may eventually need to track language bank +; switches, video and more. +; +map_save: + pha + lda $C013 + and #$80 + sta saved_map + pla + rts + +saved_map: .byte 0 + +; outchar: Wait for UART TX idle, then print the char in a without +; corrupting other registers + +outchar: +; We don't have an easy way to do this - could vtoutput ?? + rts + +; +; Code that will live in each bank at the same address and is copied +; there at boot. +; + .segment "STUBS" +; +; Interrupt vector logic. Keep this in platform for the 6502 so that +; we can use the shorter one for the CMOS chip +; +vector: + pha + txa + pha + tya + pha + cld + +; Remember where we are coming from. Also remember we may not be +; going back quite the same way. For single process in memory however +; it's not too hard - if we are in kernel mapping an IRQ will always +; exit in kernel mapping. + + jsr map_save + sta $C008 ; Kernel ZP and stack + +; +; Q: do we want to spot brk() instructions and signal them ? +; +; +; Save the old stack ptr +; + jsr stash_zp ; Save zero page bits + tsx ; and save the 6502 stack ptr + stx istack_switched_sp ; in uarea/stacks +; +; Hope the user hasn't used all the CPU stack +; +; FIXME: we should check here if S is too low and if so set it high +; and deliver SIGKILL +; +; Configure the C stack to the i stack +; + lda #istack_top + sta sp+1 + jsr interrupt_handler +; +; Reload the previous value into the stack ptr +; + ldx istack_switched_sp + txs ; recover 6502 stack + jsr stash_zp ; restore zero page bits + + lda saved_map ; we entered kernel map + beq exit_as_kernel ; so we exit that way + ; no premption or signals + +; +; TODO: pre-emption +; + + + +; +; Signal handling on 6502 is foul as the C stack may be inconsistent +; during an IRQ. We push a new complete rti frame below the official +; one, along with a vector and the signal number. The glue in the +; app is expected to switch to a signal stack or similar, pop the +; values, invoke the signal handler and then return. +; + + lda U_DATA__U_CURSIG + beq irqout + + + sta $C009 ; Back to the users S and ZP + tay + tsx + txa + sec + sbc #6 ; move down past the existing rti + tax + txs + lda #>irqout + pha + lda #PROGLOAD + 20 + lda #0 + pha ; dummy flags, with irq enable + rti ; return on the fake frame + ; if the handler returns + ; rather than doing a longjmp + ; we'll end up at irqout and pop the + ; real frame +irqout: + jsr map_restore +exit_as_kernel: + pla + tay + pla + tax + pla + rti +; +; sp/sp+1 are the C stack of the userspace +; with the syscall number in X +; Y indicates the number of bytes of argument +; +syscall_entry: + php + sei + cld + + stx U_DATA__U_CALLNO + + ; No arguments - skip all the copying and stack bits + cpy #0 + beq noargs + + ; Remove the arguments. This is fine as by the time we go back + ; to the user stack we'll have finished with them + lda sp + sta ptr1 + ldx sp+1 + stx ptr1+1 + jsr incaxy + sta sp + stx sp+1 + + ; + ; We copy the arguments but need to deal with the compiler + ; stacking in the reverse order. At this point ptr1 points + ; to the last byte of the arguments (first argument). We go + ; down the stack copying words up the argument list. + ; + ldx #0 +copy_args: + dey + lda (ptr1),y ; copy the arguments over + sta U_DATA__U_ARGN+1,x + dey + lda (ptr1),y + sta U_DATA__U_ARGN,x + inx + inx + cpy #0 + bne copy_args +noargs: + ; + ; Now we need to stack switch. Save the adjusted stack we want + ; for return + ; + lda sp + pha + lda sp+1 + pha + tsx + stx U_DATA__U_SYSCALL_SP +; +; We save a copy of the high byte of sp here as we may need it to get +; the brk() syscall right. +; + sta U_DATA__U_SYSCALL_SP + 1 +; +; Now switch to the kernel ZP/S +; + sta $C008 + ldx #$FF + txs +; +; +; Set up the C stack +; + lda #kstack_top + sta sp+1 + + cli +; +; Caution: We may enter here and context switch and another task +; exit via its own syscall returning in its own memory context. +; +; Don't assume anything we stored statically *except* the uarea +; will be different. The uarea is banked in and out (or copied in +; more awkward systems). +; + jsr unix_syscall_entry + + sei +; +; Correct the system stack +; + ldx U_DATA__U_SYSCALL_SP + + sta $C009 ; Switch to user stsck and Zp + + txs +; +; From that recover the C stack and the syscall buf ptr +; + pla + sta sp+1 + pla + sta sp + lda U_DATA__U_CURSIG + beq syscout + tay + + tsx ; Move past existing return stack + dex + dex + dex + txs + + ; + ; The signal handler might make syscalls so we need to get + ; our return saved and return the right value! + ; + lda U_DATA__U_ERROR + pha + lda U_DATA__U_RETVAL + pha + lda U_DATA__U_RETVAL+1 + pha + lda #>sigret ; Return address + pha + lda #PROGLOAD ; For the relocation engine + lda #ZPBASE + ldy #0 + jmp (ptr1) ; Enter user application + +; +; Straight jumps no funny banking issues yet +; If we start using the alternate language page we'll need to save +; and restre that state plus have stubs in both copies. +; +_unix_syscall_i: + jmp _unix_syscall +_platform_interrupt_i: + jmp _platform_interrupt + + .segment "COMMONDATA" + +_hd_map: + .res 1 + + .code + ; Dummy stubs for now + .export _block_rw + +_block_rw: + rts diff --git a/Kernel/platform-appleiie/commonmem.s b/Kernel/platform-appleiie/commonmem.s new file mode 100644 index 00000000..a7695d46 --- /dev/null +++ b/Kernel/platform-appleiie/commonmem.s @@ -0,0 +1,41 @@ +; +; User data, C kernel stacks, C interrupt stacks and ZP switch for +; Fuzix. These need to always be visible when running C code. +; + ; exported symbols + .export _ub + .export _udata + .export kstack_top + .export istack_top + .export istack_switched_sp + .export CTemp + + .segment "COMMONDATA" + .include "zeropage.inc" + +; +; In 6502 land these are the C stacks, we will need to handle the +; hardware stack separately, and also to save sp,sp+1 etc on irqs +; +; Declared as BSS so no non zero bytes here please +; +_ub: ; first 512 bytes: starts with struct u_block, with the kernel stack working down from above +_udata: +kstack_base: + .res 512,0 +kstack_top: + + ; next 256 bytes: 253 byte interrupt stack, then 3 byte saved stack pointer +istack_base: + .res 254,0 +istack_top: +istack_switched_sp: .word 0 +; +; Finally we tack the ZP save area for interrupts on the end +; +; Swap space for the the C temporaries (FIXME - we stash sp twice right now) +; +CTemp: + .res 2 ; sp + .res 2 ; sreg + .res (zpsavespace-4) ; Other stuff diff --git a/Kernel/platform-appleiie/config.h b/Kernel/platform-appleiie/config.h new file mode 100644 index 00000000..5e800367 --- /dev/null +++ b/Kernel/platform-appleiie/config.h @@ -0,0 +1,64 @@ +//#define CONFIG_RAMWORKS + +#ifdef CONFIG_RAMWORKS +/* Multiple processes in memory at once */ +#define CONFIG_MULTI +/* Use fixed banks for now. It's simplest and we've got so much memory ! */ +#define CONFIG_BANK_FIXED +#define MAX_MAPS 15 +#define MAP_SIZE 0xB800 +#else +/* One process in memory for base 128K system - 6502 just isnt compact enough + for a two process setup as Z80 would do for 128K */ +#define CONFIG_BANKS 1 /* 1 bank per process */ +#define CONFIG_MULTI /* Multi-tasking */ +#define CONFIG_SWAP_ONLY /* One process in memory rest by swap */ +#define CONFIG_SPLIT_UDATA /* We'll need to do some work on this.. */ +#define UDATA_SIZE 512 +#define UDATA_BLKS 1 + +/* HACK for now */ +#define SWAPDEV (1 << 5) +#endif + + +/* 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 +/* Acct syscall support */ +#undef CONFIG_ACCT + +#define CONFIG_CALL_R2L /* Runtime stacks arguments backwards */ + +#define TICKSPERSEC 50 /* Ticks per second. Needs work as will vary */ +#define MAPBASE 0x0800 /* We map from 0x0800 */ +#define PROGBASE 0x0800 /* also data base */ +#define PROGLOAD 0x0800 +#define PROGTOP 0xC000 /* When we hit the data space */ + +#define SWAP_SIZE 0x60 /* 48K - allow for udata and our magic */ +#define SWAPBASE 0x0800 +#define SWAPTOP 0xC000 +#define MAX_SWAPS PTABSIZE +#define swap_map(x) ((uint8_t *)(x)) + +/* Support a 40 column console for now */ +#define CONFIG_VT +#define VT_RIGHT 39 +#define VT_BOTTOM 24 + +#define BOOT_TTY 513 /* Set this to default device for stdio, stderr */ + +/* We need a tidier way to do this from the loader */ +#define CMDLINE NULL /* Location of root dev name */ + +/* Device parameters */ +#define NUM_DEV_TTY 1 /* For now until we add console switches */ +#define TTYDEV BOOT_TTY /* Device used by kernel for messages, panics */ +#define NBUFS 6 /* Number of block buffers */ +#define NMOUNTS 3 /* Number of mounts at a time */ + +#define platform_discard() diff --git a/Kernel/platform-appleiie/crt0.s b/Kernel/platform-appleiie/crt0.s new file mode 100644 index 00000000..c2b6ed55 --- /dev/null +++ b/Kernel/platform-appleiie/crt0.s @@ -0,0 +1,99 @@ +; +; +; When we knock this into proper shape it'll need to begin by using +; ProDOS to load the chunks of image and stuff them into bank 1, then +; kill off ProDOS and move them into the language space. +; +; For now we put discard low, so we can in theory switch video mode +; and blow it away (at least for lower resolution modes) +; +; + ; imported symbols + .import init_early + .import init_hardware + .import _fuzix_main + .import kstack_top + .import vector + .import nmi_handler + + .import __BSS_RUN__, __BSS_SIZE__ + .importzp ptr1, ptr2, tmp1 + + ; startup code @0 + .include "zeropage.inc" + +; +; So we end up first in the image +; + .segment "START" + +entry: +; +; We are entered at $2000 from ProDOS +; + lda #'1' + sta $0400 ; signal our arrival + + sei ; interrupts off + cld ; decimal off + ldx #$FF + txs ; Stack (6502 not C) + + sta $C000 ; 40 column + sta $C050 ; text + sta $C054 ; page 1 + sta $C00F ; alt char set on + sta $C000 ; 80 store off + + lda #'2' + sta $0400 + + lda #kstack_top + sta sp+1 + + lda #<__BSS_RUN__ + sta ptr1 + lda #>__BSS_RUN__ + sta ptr1+1 + + lda #'3' + sta $0400 + + lda #0 + tay + ldx #>__BSS_SIZE__ + beq bss_wipe_tail +bss_wiper_1: sta (ptr1),y + iny + bne bss_wiper_1 + inc ptr1+1 + dex + bne bss_wiper_1 + +bss_wipe_tail: + cpy #<__BSS_SIZE__ + beq gogogo + sta (ptr1),y + iny + bne bss_wipe_tail + +gogogo: + lda #'4' + sta $0400 + + jsr init_early + lda #'5' + sta $0400 + jsr init_hardware + lda #'6' + sta $0400 + jsr _fuzix_main ; Should never return + sei ; Spin +stop: jmp stop + + .segment "VECTORS" + .addr vector + .addr $2000 ; does it matter ??? + .addr nmi_handler diff --git a/Kernel/platform-appleiie/devhd.c b/Kernel/platform-appleiie/devhd.c new file mode 100644 index 00000000..4d0839da --- /dev/null +++ b/Kernel/platform-appleiie/devhd.c @@ -0,0 +1,125 @@ +/* + * There are essentially two kinds of disks on the Apple IIe/c + * - The ancient 140K floppies which need an OS level driver + * - SmartPort type devices with a common (and quite nice) firmware + * interface. + * + * This driver handles SmartPort devices. It assumes no more than 32 + * devices per slot and no more than 8 slots. Device partitioning + * on the Apple IIe can be a strange mix of hardware and software. + * Usually the firmware presents the real device as multiple actual + * smartport devices. + * + * TODO: + * PASCAL or ProDOS entry points ? + * if it reports removable ensure we flush buffers on close + * if it reports formatting add an ioctl + * if it doesn't report interruptible ensure the asm cli's it + * + */ + +#include +#include +#include +#include + +extern uint8_t hd_map; +uint8_t rw_cmd[7]; +uint8_t block_units[8]; +uint8_t readonly; + +static int hd_transfer(uint8_t minor, bool is_read, uint8_t rawflag) +{ + uint16_t dptr, nb; + uint8_t err; + + /* FIXME: swap support */ + if(rawflag == 1 && d_blkoff(9)) + return -1; + + hd_map = rawflag; + + dptr = (uint16_t)udata.u_dptr; + nb = udata.u_nblock; + + while (udata.u_nblock--) { + /* Fill in the protocol convertor information */ + /* Note hd_map will be fun to handle */ +#ifdef CONFIG_PASCAL + rw_cmd[0] = is_read ? 0x03 : 0x04; + rw_cmd[1] = minor & 0x1F; + rw_cmd[2] = dptr; + rw_cmd[3] = dptr >> 8; + rw_cmd[4] = udata.u_block; + rw_cmd[5] = udata.u_block >> 8; + rw_cmd[6] = 0; /* If we do big disks this will be 16-23 */ +#else + /* DOS mode only allows 2 devices/slot */ + rw_cmd[0] = is_read ? 1 : 2; + /* FIXME: check these are correct shifts */ + rw_cmd[1] = ((minor & 0xE0) >> 2); + rw_cmd[1] |= (minor & 3) << 6; + rw_cmd[2] = dptr; + rw_cmd[3] = dptr >> 8; + rw_cmd[4] = udata.u_block; + rw_cmd[5] = udata.u_block >> 8; +#endif + + err = block_rw(); + + if (err) { + kprintf("hd%d: disk error %x\n", err); + udata.u_error = EIO; + return -1; + } + + udata.u_block++; + dptr += 512; + + } + return nb; +} + +int hd_open(uint8_t minor, uint16_t flag) +{ + uint8_t slot = minor >> 5; + used(flag); + +#ifndef CONFIG_PASCAL + /* FIXME: may need a core tweak to match */ + if ((readonly & (1 << slot)) && O_ACCMODE(flag)) { + udata.u_error = EROFS; + return -1; + } +#endif + /* Non block device slots have 0 here */ + if ((minor & 0x1F) >= block_units[slot]) { + udata.u_error = ENXIO; + return -1; + } + return 0; +} + +int hd_read(uint8_t minor, uint8_t rawflag, uint8_t flag) +{ + used(flag); + return hd_transfer(minor, true, rawflag); +} + +int hd_write(uint8_t minor, uint8_t rawflag, uint8_t flag) +{ + used(flag); + return hd_transfer(minor, false, rawflag); +} + +/* This assumes a ProDOS type structure is present and we are doing + ProDOS things */ +void hd_install(uint8_t slot) +{ + uint8_t *p = (uint8_t *)0xC000 + (((uint16_t)slot) << 8); + + if (!(p[254] & 4)) + readonly |= (1 << slot); + block_units[slot] = (p[254] >> 4) & 3 + 1; + /* And at this point we ought to scan it for a swap signature */ +} diff --git a/Kernel/platform-appleiie/devhd.h b/Kernel/platform-appleiie/devhd.h new file mode 100644 index 00000000..ed8a82e6 --- /dev/null +++ b/Kernel/platform-appleiie/devhd.h @@ -0,0 +1,14 @@ +#ifndef __DEVHD_DOT_H__ +#define __DEVHD_DOT_H__ + +/* public interface */ +int hd_read(uint8_t minor, uint8_t rawflag, uint8_t flag); +int hd_write(uint8_t minor, uint8_t rawflag, uint8_t flag); +int hd_open(uint8_t minor, uint16_t flag); +void hd_install(uint8_t slot); + +extern uint8_t rw_cmd[7]; +extern uint8_t block_rw(void); +extern uint8_t block_units[8]; + +#endif /* __DEVHD_DOT_H__ */ diff --git a/Kernel/platform-appleiie/device.h b/Kernel/platform-appleiie/device.h new file mode 100644 index 00000000..6f4c1e26 --- /dev/null +++ b/Kernel/platform-appleiie/device.h @@ -0,0 +1,6 @@ +#ifndef __DEVICE_DOT_H__ +#define __DEVICE_DOT_H__ + +extern void mod_control(uint8_t set, uint8_t clr); + +#endif /* __DEVICE_DOT_H__ */ diff --git a/Kernel/platform-appleiie/devices.c b/Kernel/platform-appleiie/devices.c new file mode 100644 index 00000000..8f2563d7 --- /dev/null +++ b/Kernel/platform-appleiie/devices.c @@ -0,0 +1,39 @@ +#include +#include +#include +#include +#include +#include +#include + +struct devsw dev_tab[] = /* The device driver switch table */ +{ +// minor open close read write ioctl +// ----------------------------------------------------------------- + /* 0: /dev/fd Floppy disc block devices */ + { hd_open, no_close, hd_read, hd_write, no_ioctl }, + /* 1: /dev/hd Hard disc block devices (absent) */ + { nxio_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 */ + { 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 }, + /* 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; +} + +void device_init(void) +{ +} + diff --git a/Kernel/platform-appleiie/devtty.c b/Kernel/platform-appleiie/devtty.c new file mode 100644 index 00000000..12cbbb2b --- /dev/null +++ b/Kernel/platform-appleiie/devtty.c @@ -0,0 +1,188 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +/* Ignore super serial and friends for the moment */ + +static volatile uint8_t *kbd_read = (volatile uint8_t *)0xC000; +static volatile uint8_t *kbd_strobe = (volatile uint8_t *)0xC010; +/* Assume a //c for the moment */ +static volatile uint8_t *irq_check = (volatile uint8_t *)0xC041; +static volatile uint8_t *irq_reset = (volatile uint8_t *)0xC019; + +static char tbuf1[TTYSIZ]; +PTY_BUFFERS; + +uint8_t vtattr_cap = 0; + +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}, + PTY_QUEUES +}; + +/* tty1 is the screen tty2+ are the serial ports */ + +/* Output for the system console (kprintf etc) */ +void kputchar(uint8_t c) +{ + if (c == '\n') + tty_putc(1, '\r'); + tty_putc(1, c); +} + +ttyready_t tty_writeready(uint8_t minor) +{ + return TTY_READY_NOW; +} + +void tty_putc(uint8_t minor, unsigned char c) +{ + vtoutput(&c,1); +} + +void tty_setup(uint8_t minor) +{ + minor; +} + +void tty_sleeping(uint8_t minor) +{ + minor; +} + +int tty_carrier(uint8_t minor) +{ + minor; + return 1; +} + +/* Beware - this kbd access also disables 80store */ +void tty_poll(void) +{ + uint8_t x; + + x = *kbd_read; + if (x & 0x80) { + tty_inproc(1, x & 0x7F); + x = *kbd_strobe; + } +} + +uint8_t check_timer(void) +{ + /* For now asume mouse card IIc - hack. Once we have proper IRQ + handling in place we can key this appropriately */ + if (*irq_check & 0x80) { + *irq_reset; + return 1; + } + return 0; +} + +void platform_interrupt(void) +{ + tty_poll(); + if (check_timer()) + timer_interrupt(); +} + +/* Video driver: Some of this would be better in asm, especially the scrolling */ + +/* Line start table for 40 or 80 column. The only difference is that for + 40 column you add X, for 80 column you add X/2 and bit 0 of X tells you + if its alternate (0) or main (1) memory. */ +static volatile uint8_t *vtmap[24] = { + (volatile uint8_t *)0x400, + (volatile uint8_t *)0x480, + (volatile uint8_t *)0x500, + (volatile uint8_t *)0x580, + (volatile uint8_t *)0x600, + (volatile uint8_t *)0x680, + (volatile uint8_t *)0x700, + (volatile uint8_t *)0x780, + + (volatile uint8_t *)0x428, + (volatile uint8_t *)0x4A8, + (volatile uint8_t *)0x528, + (volatile uint8_t *)0x5A8, + (volatile uint8_t *)0x628, + (volatile uint8_t *)0x6A8, + (volatile uint8_t *)0x728, + (volatile uint8_t *)0x7A8, + + (volatile uint8_t *)0x450, + (volatile uint8_t *)0x4D0, + (volatile uint8_t *)0x550, + (volatile uint8_t *)0x5D0, + (volatile uint8_t *)0x650, + (volatile uint8_t *)0x6D0, + (volatile uint8_t *)0x750, + (volatile uint8_t *)0x7D0 +}; + +/* Simple driver for 40 column text */ + +void plot_char(int8_t y, int8_t x, uint16_t c) +{ + *(vtmap[y] + x) = ((uint8_t)c) | 128; +} + +/* Point at ourselves so the first dummy cursor_off is harmless */ +static volatile uint8_t *cursorptr = (uint8_t *)&cursorptr; + +void cursor_off(void) +{ + *cursorptr |= 128; +} + +void cursor_on(int8_t y, int8_t x) +{ + cursorptr = vtmap[y] + x; + *cursorptr &= 127; +} + +void clear_across(int8_t y, int8_t x, int16_t l) +{ + volatile uint8_t *addr = vtmap[y] + x; + memset(addr, ' '|0x80, l); +} + +void clear_lines(int8_t y, int8_t n) +{ + volatile uint8_t *addr; + while(n--) { + addr = vtmap[y++]; + memset(addr, ' '|0x80, 40); + } +} + +void vtattr_notify(void) +{ +} + +void scroll_up(void) +{ + uint8_t y; + for (y = 1; y <= 23; y++) { + volatile uint8_t *src = vtmap[y]; + volatile uint8_t *dst = vtmap[y-1]; + memcpy(dst, src, 40); + } +} + +void scroll_down(void) +{ + uint8_t y; + for (y = 23; y > 1; y--) { + volatile uint8_t *src = vtmap[y-1]; + volatile uint8_t *dst = vtmap[y]; + memcpy(dst, src, 40); + } +} + diff --git a/Kernel/platform-appleiie/devtty.h b/Kernel/platform-appleiie/devtty.h new file mode 100644 index 00000000..da0afba5 --- /dev/null +++ b/Kernel/platform-appleiie/devtty.h @@ -0,0 +1,6 @@ +#ifndef __DEVTTY_DOT_H__ +#define __DEVTTY_DOT_H__ + +extern void tty_poll(void); + +#endif diff --git a/Kernel/platform-appleiie/kernel.def b/Kernel/platform-appleiie/kernel.def new file mode 100644 index 00000000..61bfff4c --- /dev/null +++ b/Kernel/platform-appleiie/kernel.def @@ -0,0 +1,9 @@ +; UZI mnemonics for memory addresses etc + +; (this is struct u_data from kernel.h) +U_DATA .set $0200 +; 256+256+256 bytes. +U_DATA__TOTALSIZE .set $300 + +PROGLOAD .set $2000 +ZPBASE .set $0 diff --git a/Kernel/platform-appleiie/ld65.cfg b/Kernel/platform-appleiie/ld65.cfg new file mode 100644 index 00000000..5f32c42f --- /dev/null +++ b/Kernel/platform-appleiie/ld65.cfg @@ -0,0 +1,38 @@ +MEMORY { + RAMZ: start = $0000, size = $0100, type = rw, fill = yes; + STACK: start = $0100, size = $0100, type = rw, fill = yes; + RAM0: start = $0800, size = $17FF, type = rw, fill = yes; + RAM1: start = $2000, size = $9EFF, type = rw, fill = yes; + RAM2: start = $D000, size = $2FFA, type = rw, fill = yes; + RAM3: start = $FFFA, size = $0010, type = rw, fill = yes; +} + +SEGMENTS { + ZEROPAGE: load = RAMZ, type = zp, define = yes; + COMMONDATA: load = RAM2, type = bss; + COMMONMEM: load = RAM2, type = rw; + + START: load = RAM1, type = ro; + CODE: load = RAM1, type = ro, define = yes; + RODATA: load = RAM1, type = ro; + STUBS: load = RAM2, type = ro, define = yes; + DATA: load = RAM2, type = rw, define = yes; + BSS: load = RAM2, type = bss, define = yes; + + SEG1: load = RAM1, type = ro; + SEG2: load = RAM1, type = ro; + SEG3: load = RAM1, type = ro; + SYS1: load = RAM1, type = ro; + SYS2: load = RAM1, type = ro; + SYS3: load = RAM1, type = ro; + SYS4: load = RAM2, type = ro; + SYS5: load = RAM0, type = ro; + DISCARD: load = RAM0, type = ro; + DISCARDDATA: load = RAM0, type = ro; + + VECTORS: load = RAM3, type = ro; +} + +FILES { + %O: format = bin; +} diff --git a/Kernel/platform-appleiie/main.c b/Kernel/platform-appleiie/main.c new file mode 100644 index 00000000..df71342c --- /dev/null +++ b/Kernel/platform-appleiie/main.c @@ -0,0 +1,151 @@ +#include +#include +#include +#include +#include +#include + +uint8_t kernel_flag = 1; +uint8_t cols = 40; +uint8_t card_present; +uint8_t model; +#define APPLE_UNKNOWN 0 +#define APPLE_IIE 1 +#define APPLE_IIC 2 + +void platform_idle(void) +{ + irqflags_t flags = di(); + tty_poll(); + irqrestore(flags); +} + +void do_beep(void) +{ + /* Strobe speaker bit - TODO */ +} + + +/* Below wants to end up mostly in discard */ + +#ifdef CONFIG_RAMWORKS +/* + * Map handling: We have flexible paging. Each map table consists of a set of pages + * with the last page repeated to fill any holes. + */ + +void pagemap_init(void) +{ + int i; + /* Bank 0 is the kernel */ + for (i = 15 ; i > 0; i--) + pagemap_add(i * 8); +} + +#endif + +void map_init(void) +{ +} + +extern int strcmp(const char *, const char *); + +uint8_t platform_param(char *p) +{ + if (strcmp(p,"40") == 0) { + cols = 40; + return 1; + } + if (strcmp(p,"80") == 0) { + cols = 80; + return 1; + } + return 0; +} + +static void unsupported(void) +{ + kputs(" (unsupported) "); +} + +/* Slot detection on Apple is a bit magic. It started out as sneaky ROM + peeking and evolved later into proper protocols */ + +void scan_slots(void) +{ + uint8_t i = 1; /* slot 0 is the motherboard */ + volatile uint8_t *card = (volatile uint8_t *)0xC100; + + for (i = 1; i < 8; i++, card += 0x100) { + kprintf("\n%c - ", '0' + i); + if (!(card_present & (1 << i))) { + kputs("empty"); + continue; + } + if (card[1] == 0x20 && card[3] == 0x00 && card[5] == 0x03) { + /* storage */ + + /* Old ROM floppy drive: needs custom driver */ + if (card[255] == 0xFF) { + kputs("13 sector/track floppy"); + unsupported(); + } + /* New ROM floppy drive: needs custom driver */ + else if (card[255] == 0x00) { + kputs("16 sector/track floppy"); + unsupported(); + } else { /* We use the firmware interface */ + kputs("storage"); + hd_install(i); + } + continue; + } + if (card[1] == 0xB0 && card[2] == 0x20) { + kputs("tablet"); + unsupported(); + continue; + } + /* This one is important, we must find it to get a period timer */ + if (card[5] == 0x38 && card[7] == 0x18 && card[11] == 0x01 && + card[12] == 0x20 && card[0xFB] == 0xD6) { + kputs("mouse"); + continue; + } + /* Serial cards: from dumb to dumber */ + if (card[5] == 0x38 && card[7] == 0x18) { + if (card[11] == 0x01 && card[12] == 0x31) + kputs("super serial"); + else { + kputs("serial"); + unsupported(); + } + continue; + } + if (card[0] == 0x08 && card[2] == 0x28 && card[4] == 0x5B && + card[6] == 0x70) { + kputs("clock"); + continue; + } + /* TODO cards with pascal idents not in the above */ + kputs("unknown"); + } +} + +/* Process the ProDOS bits */ +void breadcrumbs(void) +{ + uint8_t *dos = (uint8_t *)0xBF00; + uint8_t bits = dos[0x98]; + + card_present = dos[0x99]; + + if ((bits & 0x30) != 0x30) + panic("128K memory required"); + if ((bits & 0x34) == 0x20) + model = APPLE_IIE; + if ((bits & 0x34) == 0x24) + model = APPLE_IIC; + /* IIgs is rather different */ + if (model == APPLE_UNKNOWN) + panic("Apple IIe or IIc required"); +} diff --git a/Kernel/platform-appleiie/target.mk b/Kernel/platform-appleiie/target.mk new file mode 100644 index 00000000..eb53ebcf --- /dev/null +++ b/Kernel/platform-appleiie/target.mk @@ -0,0 +1 @@ +export CPU = 6502 diff --git a/Kernel/platform-appleiie/tricks.s b/Kernel/platform-appleiie/tricks.s new file mode 100644 index 00000000..78e0dac9 --- /dev/null +++ b/Kernel/platform-appleiie/tricks.s @@ -0,0 +1,290 @@ +; +; 6502 version +; + .export _switchout + .export _switchin + .export _dofork + .export _ramtop + + .import _chksigs + .import _trap_monitor + + .import map_kernel + .import _swapper + .import _swapout + + .import _newproc + .import _getproc + .import _runticks + .import _inint + .import outstring + .import outxa + .import outcharhex + + .include "kernel.def" + .include "../kernel02.def" + .include "zeropage.inc" + +; FIXME: review but all but some of the fork logic looks safe to go in code + .segment "COMMONMEM" + +; ramtop must be in common for single process swapping cases +; and its a constant for the others from before init forks so it'll be fine +; here +_ramtop: + .word $C000 + +; Switchout switches out the current process, finds another that is READY, +; possibly the same process, and switches it in. When a process is +; restarted after calling switchout, it thinks it has just returned +; from switchout(). +; +; FIXME: make sure we optimise the switch to self case higher up the stack! +; +; This function can have no arguments or auto variables. +_switchout: + sei + + jsr _chksigs +; +; Put the C stack on the CPU stack, and store that in U_SP +; + lda #0 ; return code + pha + pha + lda sp ; C stack + pha + lda sp+1 + pha + tsx + stx U_DATA__U_SP ; Save it + + ; set inint to false + lda #0 + sta _inint + + ; find another process to run (may select this one again) returns it + ; in x,a + jsr _getproc + jsr _switchin + ; we should never get here + jsr _trap_monitor + +badswitchmsg: .byte "_switchin: FAIL" + .byte 13, 10, 0 + +; +; On entry x,a holds the process to switch in +; +_switchin: + sei + ; Take a second saved set as we are going to swap stacks and ZP + ; with a CPU that hasn't got sufficient registers to keep it on + ; CPU + sta switch_proc_ptr + stx switch_proc_ptr+1 + ldy #P_TAB__P_PAGE_OFFSET + lda (ptr1),y + + bne not_swapped + + lda U_DATA__U_PTAB + ldx U_DATA__U_PTAB+1 + ; + ; FIXME - need the extra logic to swap out the kernel Z/S bits of + ; interest. + ; +;FIXME jsr _swapoutudz + ; + jsr _swapout + + ; We have to worry about stacks here. We need to load in the + ; kernel C stack and CPU stack for the new process over the one + ; we are running upon. On most processors we just select another + ; stack but for the CPU stack on 6502 we can't do this. + ; + ; We do have the alternate ZP and stack but remember those hold + ; the user space ZP and stack. What we actually do is gross + ; + + lda switch_proc_ptr + ldx switch_proc_ptr + 1 + + ; After swapout the ZP and S in memory are the old user ones, that + ; means they are effectively free memory + + sta $C009 ; Switch to Alt ZP + + ; + ; Set up a plausible ZP and S. S points somewhere in the + ; right area. We don't care exactly so don't reload. + ; + lda #swapstack_top + sta sp+1 + ; + ; We now have a constructed environment to run the + ; swap helper on the alt stack to load the real ZP and S + ; +;FIXME jsr _swapudz ; Swap in real Udata/Stack via + ; special platform routine + + ; + ; We then run the swapper in the normal fashion and it will not + ; touch our kernel ZP and S so all is good. Note that we are still + ; on the swapstack for C. We'll only fix that up at the end. + ; + sta $C008 ; Back to real C stack + + ldx U_DATA__U_SP ; Use the memory below the + ; CPU stack we swapped in + txs + + lda switch_proc_ptr + ldx switch_proc_ptr + 1 + + ; Swap in the user process and FIXME teach swapper about 6502 S/Z + ; user pages. + + jsr _swapper + + + +not_swapped: + ; Make sure we swapped in the right process + lda U_DATA__U_PTAB + cmp switch_proc_ptr + bne switchinfail + ldx U_DATA__U_PTAB+1 + cpx switch_proc_ptr+1 + bne switchinfail + ; XA holds the process ptr as a sidde effect, now construct a + ; pointer + sta ptr1 + stx ptr1+1 + ; Set it to running + ldy #P_TAB__P_STATUS_OFFSET + lda #P_RUNNING + sta (ptr1),y + ; And copy the page offset into the udata + ldy #P_TAB__P_PAGE_OFFSET + lda #0 + lda (ptr1),y + sta U_DATA__U_PAGE + ; Fix up the stack pointer from the one we are hiding in + ldx U_DATA__U_SP + txs + + lda #0 + sta _runticks + sta _runticks+1 + + pla + sta sp+1 + pla + sta sp + lda _inint + beq swtchdone ; in ISR, leave interrupts off + cli +swtchdone: + pla ; Return code + tax + pla + rts + +switchinfail: + lda ptr1+1 + jsr outcharhex + lda ptr1 + jsr outcharhex + lda #badswitchmsg + jsr outstring + ; something went wrong and we didn't switch in what we asked for + jmp _trap_monitor + + +; +; Do the hard work for a fork. Actually in a swap only environment +; there isn't that much to do. We swap out the existing image as is +; and it's the parent, and we keep the memory copy as child. +; +_dofork: +; ; always disconnect the vehicle battery before performing maintenance + sei ; should already be the case ... belt and braces. + + ; new process in X, get parent pid into y + + sta fork_proc_ptr + stx fork_proc_ptr+1 + + ldy #P_TAB__P_PID_OFFSET + sta ptr1 + stx ptr1+1 + + ; Save the stack pointer and critical registers. + ; 6502 at least doesn't have too many of those 8) + + ; When this process (the parent) is switched back in, it will be as if + ; it returns with the value of the child's pid. + lda (ptr1),y + pha + iny + lda (ptr1),y + pha + + ; save kernel stack pointer -- when it comes back in the parent we'll be in + ; _switchin which will immediately return (appearing to be _dofork() + ; returning) the child PID. + lda sp + pha + lda sp+1 + pha + tsx + stx U_DATA__U_SP + + ; now we're in a safe state for _switchin to return in the parent + ; process so write it out to disk. + + lda fork_proc_ptr + ldx fork_proc_ptr+1 + + jsr _swapout + + ; now the save operation is complete we can get rid of the stuff + ; _switchin will be expecting from our copy of the stack. + + pla + pla + pla + pla + + lda fork_proc_ptr + ldx fork_proc_ptr+1 + + ; Perform the necessary magic to turn into the child + jsr _newproc + + ; any calls to map process will now map the childs memory + ; runticks = 0; + + lda #0 + sta _runticks + sta _runticks+1 + + ; in the child process, fork() returns zero. + tax + + ; And we exit, with the kernel mapped, the child now being deemed + ; to be the live uarea. The parent is frozen in time and space as + ; if it had done a switchout(). + rts + + .data + +switch_proc_ptr: .word 0 +fork_proc_ptr: .word 0 ; (C type is struct p_tab *) -- address of child process p_tab entry +swapstack: + .res 128,0 +swapstack_top: diff --git a/Kernel/platform-appleiie/zeropage.inc b/Kernel/platform-appleiie/zeropage.inc new file mode 100644 index 00000000..1ba03586 --- /dev/null +++ b/Kernel/platform-appleiie/zeropage.inc @@ -0,0 +1,26 @@ +; +; zeropage.inc +; +; (C) Copyright 2002-2012, Ullrich von Bassewitz (uz@cc65.org) +; + +; Assembler include file that imports the runtime zero page locations used +; by the compiler, ready for usage in asm code. + + + .globalzp sp, sreg, regsave + .globalzp ptr1, ptr2, ptr3, ptr4 + .globalzp tmp1, tmp2, tmp3, tmp4 + .globalzp regbank + +; The size of the register bank +regbanksize = 6 + +; The total amount of zero page space used +zpspace = 26 + +; The amount of space that needs to be saved by an interrupt handler that +; calls C code (does not include the register bank, which is saved by the +; generated C code if required). +zpsavespace = zpspace - regbanksize +