prodos-drivers/dclock/dclock.system.s
Joshua Bell b621ac6a4d Add ROMX RTC driver, c/o Jeff Mazur.
This is a modified version of the ROMX Real-Time Clock driver. The changes include:

* Converting the source to ca65.

* Integrating with the driver installer framework.

* Adapting the driver to not modify page 2 beyond $220. The ROMX RTC
  firmware writes bytes to $2B0, and the the original driver placed
  temp code at $250. This can conflict with ProDOS applications that
  use page 2, so the driver was reworked to save/restore anything at
  at $2B0.

Other changes:

* Add a util/ source dir, and cricket/date, quit.system and
  pause.system there.

* Pull the "print current date" logic out of clock drivers into driver
  preamble.
2021-10-05 19:58:31 -07:00

385 lines
13 KiB
ArmAsm

; 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 log_message
scrcode 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)
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 log_message
scrcode PRODUCT, " - "
.byte 0
;; Display the current date
jsr cout_date
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"
;;; ************************************************************