From 5cf3426967a008aa51617af4e997151d1da6e4c8 Mon Sep 17 00:00:00 2001 From: Joshua Bell Date: Mon, 21 Dec 2020 19:58:27 -0800 Subject: [PATCH] Add AE DClock driver, based on work by M.G. Fixes #3 --- Makefile | 2 +- README.md | 25 ++- dclock/Makefile | 34 ++++ dclock/README.md | 13 ++ dclock/dclock.system.s | 408 +++++++++++++++++++++++++++++++++++++ inc/apple2.inc | 2 + ns.clock/ns.clock.system.s | 10 +- package.sh | 1 + 8 files changed, 489 insertions(+), 6 deletions(-) create mode 100644 dclock/Makefile create mode 100644 dclock/README.md create mode 100644 dclock/dclock.system.s diff --git a/Makefile b/Makefile index 88b752f..bceed09 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -targets := ns.clock cricket bbb selector ram.drv quit +targets := ns.clock cricket dclock bbb selector ram.drv quit .PHONY: all $(targets) diff --git a/README.md b/README.md index 6e5c175..adbe299 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Build Status](https://travis-ci.org/a2stuff/prodos-drivers.svg?branch=master)](https://travis-ci.org/a2stuff/prodos-drivers) -# What are ProDOS drivers/modifications? +# What are ProDOS "drivers"? The ProDOS operating system for the Apple II executes the first `.SYSTEM` file found in the boot directory on startup. A common pattern is to have the boot directory contain several "driver" files that customize ProDOS by installing drivers for hardware or modify specific parts of the operating system. These include: @@ -19,15 +19,36 @@ Early versions of these drivers would often invoke a specific file on completion This repository collects several drivers and uses common code to chain to the next `.SYSTEM` file, suporting network drives. +## What is present here? + +This repo includes The following drivers/modifications: + +* Real-time Clock drivers + * No-Slot Clock + * Cricket!, + * Applied Engineering DClock +* RAM Disk drivers + * RAMWorks Driver by Glen E. Bredon +* Quit dispatcher/selector (`BYE` routines) + * Selector (from ProDOS 1.x) + * Bird's Better Bye (from ProDOS 2.0) + * Buh-Bye (an enhanced version of Bird's Better Bye) + +In addition, `QUIT.SYSTEM` is present which isn't a driver but which immediately invokes the QUIT handler (a.k.a. program selector). + +Some date/time utilities for The Cricker! clock are also included. + ## How do you use these? -The intent is that you use a tool like Copy II Plus or [Apple II DeskTop](https://github.com/a2stuff/a2d) to copy and arrange the SYSTEM files on your boot disk as you see fit. An example boot disk catalog might include: +The intent is that you use a tool like Copy II Plus or [Apple II DeskTop](https://github.com/a2stuff/a2d) to copy and arrange the SYSTEM files on your boot disk as you see fit. An example boot disk image catalog that is used on multiple different hardware configurations might include: * `PRODOS` - the operating system, e.g. [ProDOS 2.4](https://prodos8.com/) * `NS.CLOCK.SYSTEM` - install No-Slot Clock driver, if present +* `DCLOCK.SYSTEM` - install DClock clock driver, if present * `CRICKET.SYSTEM` - install Cricket! clock driver, if present * `RAM.DRV.SYSTEM` - install RamWorks RAM disk driver, if present * `BUHBYE.SYSTEM` - install a customized Quit handler to replace the built-in one * `QUIT.SYSTEM` - invoke the Quit handler immediately, as a program selector +* `BASIC.SYSTEM` - which will not be automatically invoked, but is available to manually invoke Alternately, you might want to install some drivers then immediately launch into BASIC. In that case, put `BASIC.SYSTEM` after the drivers in place of `QUIT.SYSTEM`. diff --git a/dclock/Makefile b/dclock/Makefile new file mode 100644 index 0000000..b67c0b5 --- /dev/null +++ b/dclock/Makefile @@ -0,0 +1,34 @@ + +CAFLAGS = --target apple2enh --list-bytes 0 +LDFLAGS = --config apple2-asm.cfg + +OUTDIR = out + +HEADERS = $(wildcard *.inc) $(wildcard ../inc/*.inc) + +TARGETS = \ + $(OUTDIR)/dclock.system.SYS + +# For timestamps +MM = $(shell date "+%-m") +DD = $(shell date "+%-d") +YY = $(shell date "+%-y") +DEFINES = -D DD=$(DD) -D MM=$(MM) -D YY=$(YY) + +.PHONY: clean all +all: $(OUTDIR) $(TARGETS) + +$(OUTDIR): + mkdir -p $(OUTDIR) + +clean: + rm -f $(OUTDIR)/*.o + rm -f $(OUTDIR)/*.list + rm -f $(TARGETS) + +$(OUTDIR)/%.o: %.s $(HEADERS) + ca65 $(CAFLAGS) $(DEFINES) --listing $(basename $@).list -o $@ $< + +$(OUTDIR)/%.SYS: $(OUTDIR)/%.o + ld65 $(LDFLAGS) -o $@ $< + xattr -wx prodos.AuxType '00 20' $@ diff --git a/dclock/README.md b/dclock/README.md new file mode 100644 index 0000000..7a9d21e --- /dev/null +++ b/dclock/README.md @@ -0,0 +1,13 @@ +# Applied Engineering DClock — ProDOS Clock Driver + +This is based on a disassembly of the Applied Engineering driver for the DClock real time clock add on for the Apple IIc. + +> NOTE: Currently untested! + +Like other drivers here, this one will: + +* Conditionally install, only if a DClock is detected. + * Only attempts detection if there is not already a clock driver. + * Only attempts detection if the system is a an Apple IIc +* If detected, installs into ProDOS directly, following Technical Reference Manual requirements. +* Chains to the next `.SYSTEM` file in the directory. diff --git a/dclock/dclock.system.s b/dclock/dclock.system.s new file mode 100644 index 0000000..151c579 --- /dev/null +++ b/dclock/dclock.system.s @@ -0,0 +1,408 @@ +; Fully disassembled and analyzed source to AE +; DCLOCK.SYSTEM by M.G. - 04/18/2017 +; https://gist.github.com/mgcaret/7f0d7aeec169e90809c7cfaab9bf183b +; Further modified by @inexorabletash - 12/21/2020 + +; There are critical bugs in the original AE code: +; * When driver loader is initially probing it corrupts the +; Apple //c Memory Expansion Card: +; - it saves, but fails to restore, data at address $080000 +; - it fails to reset slinky pointer, and *will* trash $080000-$080007 +; * When the clock is read, it corrupts data at address $08xx01 +; - John Brooks spotted this, [M.G.] totally missed this. +; This version of the code has fixes permanently applied. + + .setcpu "65C02" + .linecont + + .feature string_escapes + + .include "apple2.inc" + .include "apple2.mac" + .include "opcodes.inc" + + .include "../inc/apple2.inc" + .include "../inc/macros.inc" + .include "../inc/prodos.inc" + +; zero page locations +SCRATCH := $0B ; scratch value for BCD range checks +SAVEBYTE := $0C ; slinky overwritten byte save location +BCDTMP := $3A ; location clock driver uses for BCD->Binary + +; buffers & other spaces +INBUF := $0200 ; input buffer +CLOCKBUF := $0300 ; clock buffer + +; I/O and hardware +C8OFF := $CFFF ; C8xx ROM off +SLOT4ROM := $C400 ; Slot 4 ROM space +SLOT4IO := $C0C0 ; Slot 4 I/O space +DPTRL := SLOT4IO+0 ; Slinky data ptr low +DPTRM := SLOT4IO+1 ; Slinky data ptr middle +DPTRH := SLOT4IO+2 ; Slinky data ptr high +DATA := SLOT4IO+3 ; Slinky data byte + +;;; ************************************************************ + .include "../inc/driver_preamble.inc" +;;; ************************************************************ + +;;; ============================================================ +;;; +;;; Driver Installer +;;; +;;; ============================================================ + + .define PRODUCT "DClock" + +;;; ============================================================ +;;; Ensure there is not a previous clock driver installed. +;;; And that this is a IIc. And that the clock is present. + +.proc maybe_install_driver + lda MACHID + and #$01 ; existing clock card? + bne done + + lda VERSION ; IIc identification byte 1 + cmp #$06 + bne done + lda ZIDBYTE ; IIc identification byte 2 + cmp #$00 + bne done + + jsr ClockRead + jsr ValidTime + bcc InstallDriver + + ;; Show failure message + jsr zstrout + scrcode "\r\r\r", PRODUCT, " - Not Found." + .byte 0 + +done: rts +.endproc + +; ---------------------------------------------------------------------------- +; Install clock driver +.proc InstallDriver + + ;; Copy into address at DATETIME vector, update the vector and + ;; update MACHID bits to signal a clock is present. + ptr := $A5 + + ;; Update absolute addresses within driver + lda DATETIME+1 + sta ptr + clc + adc #(regulk - driver - 1) + sta regulk_addr + lda DATETIME+2 + sta ptr+1 + adc #0 + sta regulk_addr+1 + + ;; Copy driver into appropriate bank + lda RWRAM1 + lda RWRAM1 + ldy #sizeof_driver-1 + +loop: lda driver,y + sta (ptr),y + dey + bpl loop + + ;; Set the "Recognizable Clock Card" bit + lda MACHID + ora #$01 + sta MACHID + + lda #OPC_JMP_abs + sta DATETIME + + ;; Invoke the driver to init the time + jsr DATETIME + + lda ROMIN2 + + ;; Display success message + jsr zstrout + scrcode "\r\r\r", PRODUCT, " - Installed " + .byte 0 + + ;; Display the current date + lda DATELO+1 ; month + ror a + pha + lda DATELO + pha + rol a + rol a + rol a + rol a + and #%00001111 + jsr cout_number + + lda #HI('/') ; / + jsr COUT + + pla ; day + and #%00011111 + jsr cout_number + + lda #HI('/') ; / + jsr COUT + + pla ; year + jsr cout_number + jsr CROUT + + rts ; done! +.endproc + +; ---------------------------------------------------------------------------- +; enable slinky registers, set adddress and save byte we intend to trash +.proc SlinkyEnable + lda C8OFF ; not needed on //c, but release $C8xx firmware + lda SLOT4ROM ; enable slinky registers + lda #$08 ; set addr $080000 + sta DPTRH + stz DPTRM + stz DPTRL + lda DATA ; read data byte + sta SAVEBYTE ; save it to restore later + rts +.endproc +; ---------------------------------------------------------------------------- +; Routine to restore trashed byte in slinky RAM +.proc SlinkyRestore + lda #$08 ; set adddr $080000 + sta DPTRH + stz DPTRM + stz DPTRL + lda SAVEBYTE ; get saved byte + sta DATA ; and put it back + lda C8OFF ; not needed on //c, but release $C8xx firmware + rts +.endproc +; ---------------------------------------------------------------------------- +; Write 8 bits to clock +.proc ClockWrite8b + ldx #$08 ; set adddr $080000 + stx DPTRH + stz DPTRM +: stz DPTRL ; restore low byte to 0 + sta DATA ; write byte + lsr a ; next bit into 0 position + dex + bne :- + rts +.endproc +; ---------------------------------------------------------------------------- +; unlock the clock by writing the magic bit sequence +.proc ClockUnlock + ldy #$08 +: lda unlock,y + jsr ClockWrite8b ; write 8 bits + dey + bne :- + rts +unlock = * - 1 + .byte $5c, $a3, $3a, $c5, $5c, $a3, $3a, $c5 +.endproc +; ---------------------------------------------------------------------------- +; Read 8 bits from the clock +.proc ClockRead8b + ldx #$08 ; set adddr $080000 + stz DPTRL + stz DPTRM + stx DPTRH +: pha ; save accumulator + lda DATA ; get data byte + lsr a ; bit 0 into carry + pla ; restore accumulator + ror a ; put read bit into position + dex + bne :- + rts +.endproc +; ---------------------------------------------------------------------------- +; read the clock data into memory at CLOCKBUF +; WARNING: unfixed code never restores byte we trashed +.proc ClockRead + jsr SlinkyEnable + jsr ClockUnlock + ldy #$00 +: jsr ClockRead8b + sta CLOCKBUF,y + iny + cpy #$08 ; have we read 8 bytes? + bcc :- ; nope + jsr SlinkyRestore + rts +.endproc +; ---------------------------------------------------------------------------- +; validate the DClock data makes sense +; return carry clear if it does, carry set if it does not +.proc ValidTime + ; validate ms + ldx #$00 + ldy #$99 + lda CLOCKBUF + jsr CheckBCD + bcs :+ + ; validate seconds + ldx #$00 + ldy #$59 + lda CLOCKBUF+$01 + jsr CheckBCD + bcs :+ + ; validate minutes + ldx #$00 + ldy #$59 + lda CLOCKBUF+$02 + jsr CheckBCD + bcs :+ + ; validate hours + ldx #$00 + ldy #$23 + lda CLOCKBUF+$03 + jsr CheckBCD + bcs :+ + ; validate day of week + ldx #$01 + ldy #$07 + lda CLOCKBUF+$04 + jsr CheckBCD + bcs :+ + ; validate day of month + ldx #$01 + ldy #$31 + lda CLOCKBUF+$05 + jsr CheckBCD + bcs :+ + ; validate month + ldx #$01 + ldy #$12 + lda CLOCKBUF+$06 + jsr CheckBCD + bcs :+ + ; validate year + ldx #$00 + ldy #$99 + lda CLOCKBUF+$07 + jsr CheckBCD + bcs :+ + clc ; all good + rts +: sec ; problem + rts +.endproc +; ---------------------------------------------------------------------------- +; Check BCD number in range of [x,y] +; return carry clear if it is, carry set if it is not +.proc CheckBCD + sed ; decimal mode + stx SCRATCH ; lower bound into scratch + cmp SCRATCH ; compare it + bcc :++ ; fail if out of range + sty SCRATCH ; upper bound into scratch + cmp SCRATCH ; compare it + beq :+ ; OK if equal + bcs :++ ; fail if out of range +: cld ; in range + clc + rts +: cld ; not in range + sec + rts +.endproc + +; ---------------------------------------------------------------------------- +; clock driver code inserted into ProDOS + +driver: + lda #$08 ; useless instruction + php + sei + lda SLOT4ROM ; activate slinky registers + ; ($08 from above overwritten) + stz DPTRL ; set slinky address to $08xx00 + ldy #$08 ; also counter for unlock bytes + sty DPTRH + lda DATA ; get destroyed byte + ; (slinky now at $08xx01) + pha ; save value on stack + ; unlock dclock registers + regulk_addr := *+1 +ubytlp: lda regulk,y ; self-modified + ldx #$08 ; bit counter +ubitlp: stz DPTRL ; reset pointer to $08xx00 + sta DATA ; write to $08xx00 + lsr a ; next bit into 0 position + dex + bne ubitlp + dey + bne ubytlp + ; now read 64 bits (8 bytes) from dclock + ldx #$08 ; byte counter +rbytlp: ldy #$08 ; bit counter +rbitlp: pha + lda DATA ; data byte + lsr a ; bit 0 into carry + pla + ror a ; carry into bit 7 + dey + bne rbitlp + ; got 8 bits now, convert from BCD to binary + pha + and #$0F + sta BCDTMP + pla + and #$F0 + lsr a + pha + adc BCDTMP + sta BCDTMP + pla + lsr a + lsr a + adc BCDTMP + ; place in input buffer, which is OK because the ThunderClock driver does this + sta INBUF-1,x + dex + bne rbytlp + ; done copying, now put necessary values into ProDOS time locations + ; copy hours to ProDOS hours + lda INBUF+4 + sta TIMEHI + ; copy minutes to ProDOS minutes + lda INBUF+5 + sta TIMELO + ; copy month ... + lda INBUF+1 + lsr a + ror a + ror a + ror a + ; ... and day of month to ProDOS month/day + ora INBUF+2 + sta DATELO + ; copy year and final bit of month to ProDOS year/month + lda INBUF + rol a + sta DATEHI + stz DPTRL ; set slinky back to $08xx00 + pla ; get saved byte + sta DATA ; put it back + plp + rts +; DS1215 unlock sequence (in reverse) +regulk = * - 1 + .byte $5C, $A3, $3A, $C5, $5C, $A3, $3A, $C5 + + sizeof_driver := * - driver + .assert sizeof_driver <= 125, error, "Clock code must be <= 125 bytes" + +;;; ************************************************************ + .include "../inc/driver_postamble.inc" +;;; ************************************************************ diff --git a/inc/apple2.inc b/inc/apple2.inc index 545ef97..0379d06 100644 --- a/inc/apple2.inc +++ b/inc/apple2.inc @@ -53,6 +53,8 @@ INIT := $FB2F SETTXT := $FB39 TABV := $FB5B SETPWRC := $FB6F +VERSION := $FBB3 +ZIDBYTE := $FBC0 BELL1 := $FBDD HOME := $FC58 CLREOL := $FC9C diff --git a/ns.clock/ns.clock.system.s b/ns.clock/ns.clock.system.s index ff1460f..e899a6e 100644 --- a/ns.clock/ns.clock.system.s +++ b/ns.clock/ns.clock.system.s @@ -146,15 +146,18 @@ slot: .byte 0 .proc install_driver ptr := $A5 + ;; Update absolute addresses within driver lda DATETIME+1 sta ptr clc adc #(unlock - driver - 1) - sta ld3+1 + sta unlock_addr lda DATETIME+2 sta ptr+1 adc #0 - sta ld3+2 + sta unlock_addr+1 + + ;; Copy driver into appropriate bank lda RWRAM1 lda RWRAM1 ldy #sizeof_driver-1 @@ -227,7 +230,8 @@ ld1: lda $C304 ; self-modified ;; Unlock the NSC by bit-banging. uloop: -ld3: lda unlock-1,x ; self-modified + unlock_addr := *+1 + lda unlock-1,x ; self-modified sec ror a ; a bit at a time : pha diff --git a/package.sh b/package.sh index d7326e4..8f906e0 100755 --- a/package.sh +++ b/package.sh @@ -27,6 +27,7 @@ add_file "cricket/out/set.date.BIN" "set.date#062000" "/$VOLNAME/ add_file "cricket/out/set.time.BIN" "set.time#062000" "/$VOLNAME/CRICKET.UTIL" add_file "cricket/out/test.BIN" "test#062000" "/$VOLNAME/CRICKET.UTIL" add_file "ns.clock/out/ns.clock.system.SYS" "ns.clock.system#FF0000" "/$VOLNAME" +add_file "dclock/out/dclock.system.SYS" "dclock.system#FF0000" "/$VOLNAME" add_file "quit/out/quit.system.SYS" "quit.system#FF0000" "/$VOLNAME" add_file "ram.drv/out/ram.drv.system.SYS" "ram.drv.system#FF0000" "/$VOLNAME"