diff --git a/README.md b/README.md index 2c916a0..f2f1fd3 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ An internal clock on the Apple side is required for this functionality to be ena 2. NoSlotClock 3. Thunderclock 4. ROMX family + 5. MegaFlash (Apple IIc/IIc+ with [MegaFlash](https://github.com/ThomasFok/MegaFlash)) The reason why SPF can't/doesn't use the built-in ProDOS clock interface is it only gives information to the minute resolution - and we need seconds. (More resolution would be nice, but seconds seems to be the best we can hope for in the universe of Apple II clocks.) diff --git a/doc/MegaFlash_Clock.md b/doc/MegaFlash_Clock.md new file mode 100644 index 0000000..7916585 --- /dev/null +++ b/doc/MegaFlash_Clock.md @@ -0,0 +1,75 @@ +# MegaFlash Clock Support - Design Notes + +This document captures the reasoning and design decisions behind adding MegaFlash clock support to SPF. + +## Background + +**MegaFlash** is an internal storage device for Apple IIc/IIc+ computers ([ThomasFok/MegaFlash](https://github.com/ThomasFok/MegaFlash)). It includes: +- Flash storage (128MB/256MB) +- Real-time clock with ProDOS driver +- NTP time sync capability + +SPF's benchmark feature requires second-resolution timing. The standard ProDOS clock interface only provides minute resolution, so SPF uses a compatibility layer that detects and uses various hardware clocks directly. + +## Research Process + +1. **Identification**: MegaFlash was identified from the GitHub repo. The README lists "Real Time Clock with ProDOS clock driver" as a feature. + +2. **Architecture**: MegaFlash is IIc/IIc+ only—it replaces the system ROM. Unlike slot-based cards (Thunderclock), it uses fixed I/O addresses $C0C0-$C0C3. Activation is handled in MegaFlash ROM; SPF does not perform the magic address sequence—it only checks for presence and reads the time. + +3. **API Discovery**: From `common/defines.inc` and `firmware/megaflash.s`: + - I/O: `cmdreg`/`statusreg`=$C0C0, `paramreg`=$C0C1, `datareg`=$C0C2, `idreg`=$C0C3 + - `CMD_GETDEVINFO` ($10) returns signature bytes $88, $74 in paramreg for detection + - `CMD_GETPRODOS25TIME` ($18) returns 6-byte ProDOS 2.5 timestamp (includes seconds) + +4. **Time Format**: From `pico/rtc.c` and `pico/cmdhandler.c`: + - ProDOS legacy (4 bytes): date_lo, date_hi, minute, hour — **no seconds** + - ProDOS 2.5 (6 bytes): t4ms, second, time_lo, time_hi, date_lo, date_hi + - The time word: `time = mday<<11 | hour<<6 | min` (min=bits 0-5, hour=bits 6-10, mday=bits 11-15) + +## Design Decisions + +### 1. Detection Order + +MegaFlash is checked **after** ROMX and **before** slotted clocks. Rationale: +- IIgs and NoSlotClock are checked first (common) +- ROMX is another "replacement" device +- MegaFlash (IIc-specific) before generic slotted clocks +- Thunderclock remains last for slot-based detection + +### 2. ProDOS 2.5 vs ProDOS Legacy Format + +We use `CMD_GETPRODOS25TIME` exclusively because: +- SPF needs **seconds** for elapsed-time benchmarks +- ProDOS legacy format only provides hour and minute +- ProDOS 2.5 format includes the second byte + +### 3. Bit Extraction for Hour and Minute + +The time word layout: `[mday:5][hour:5][min:6]` + +- **Minutes**: `time & $3F` (low 6 bits) +- **Hours**: `(time >> 6) & $1F` (next 5 bits) + +Decomposed for 6502: +- `hour_lo2 = (time_lo >> 6)` — bits 6–7 of time_lo become bits 0–1 of hour +- `hour_hi3 = (time_hi & $07) << 2` — bits 0–2 of time_hi (time bits 8–10) become bits 2–4 of hour +- `hour = hour_lo2 | hour_hi3` (5 bits, 0–23) + +### 4. Hundredths of Seconds + +MegaFlash provides `t4ms` (0–249, units of 4ms), so hundredths could be approximated. We use 0 for consistency with other clocks (GS, ROMX, Thunderclock) that don't expose sub-second resolution. + +### 5. Error Handling + +- **Detection timeout**: If the busy flag stays set for 100 polls, we assume no MegaFlash. +- **GetTime error**: If the status register error bit is set after the time command, we skip updating TimeNow and return (leaving prior values). + +### 6. RTC Not Initialized + +If MegaFlash's RTC has never been set (no NTP, no manual set), the firmware returns all zeros. SPF will show 00:00:00 and benchmarks would report ~0 elapsed time. This is a user configuration issue; we document it in the README. + +## Implementation Notes + +- **MFTime buffer**: 4 bytes for temp storage during extraction. [0] holds intermediate hour bits, [1]=sec, [2–3]=time word. +- **Activation**: Handled in MegaFlash ROM; SPF does not perform the magic address sequence. diff --git a/src/prodos/gettime.asm b/src/prodos/gettime.asm index 560d9c1..796948a 100644 --- a/src/prodos/gettime.asm +++ b/src/prodos/gettime.asm @@ -26,6 +26,8 @@ InitTime: bcc FoundClockNoSlot jsr CheckForROMX bcc FoundClockROMX + jsr CheckForMegaFlash + bcc FoundClockMegaFlash jsr CheckForSlottedClocks rts @@ -59,6 +61,16 @@ FoundClockROMX: sta GetTime+2 rts +FoundClockMegaFlash: +;--------------------------------------------------------- +; Patch the entry point of GetTime to the MegaFlash version +;--------------------------------------------------------- + lda #GetTimeMegaFlash + sta GetTime+2 + rts + GetTime: jsr $0000 rts @@ -176,6 +188,101 @@ GetTimeROMX: sta TimeNow+3 rts +;--------------------------------------------------------- +; MegaFlash clock support (Apple IIc/IIc+ with MegaFlash) +; https://github.com/ThomasFok/MegaFlash +; +; Design notes: doc/MegaFlash_Clock.md +; +; Activation is handled in MegaFlash ROM; we only check presence and read time. +; Detection: CMD_GETDEVINFO returns signature $88,$74 in paramreg. +; +; GetTime: CMD_GETPRODOS25TIME returns 6 bytes. Time word (bytes 2-3) +; packs [mday:5][hour:5][min:6]; we extract hour=(time>>6)&$1F, +; min=time&$3F. Byte 1 = seconds. +;--------------------------------------------------------- +MF_CMDSTATUS = $C0C0 +MF_PARAM = $C0C1 +MF_CMD_GETDEVINFO = $10 +MF_CMD_GETPRODOS25TIME = $18 +MF_SIGNATURE1 = $88 +MF_SIGNATURE2 = $74 + +CheckForMegaFlash: + ; ROM activates MegaFlash if present; just check for presence + ; Send GETDEVINFO command + lda #MF_CMD_GETDEVINFO + sta MF_CMDSTATUS + ; Wait for busy to clear (with timeout) + ldx #100 +: bit MF_CMDSTATUS + bpl :+ + dex + bne :- + ; Timeout - MegaFlash not present + sec + rts +: ; Check for error (bit 6) + bvs CheckMegaFlashFail + ; Verify signature bytes + lda MF_PARAM + cmp #MF_SIGNATURE1 + bne CheckMegaFlashFail + lda MF_PARAM + cmp #MF_SIGNATURE2 + bne CheckMegaFlashFail + ; MegaFlash found + clc + rts +CheckMegaFlashFail: + sec + rts + +GetTimeMegaFlash: + ; Request ProDOS 2.5 format time (includes seconds) + lda #MF_CMD_GETPRODOS25TIME + sta MF_CMDSTATUS + ; Wait for completion +: bit MF_CMDSTATUS + bmi :- + bvs GetTimeMFDone ; Error - skip update + ; Read 6 bytes: [0]=4ms units, [1]=sec, [2-3]=time word, [4-5]=date + lda MF_PARAM ; t4ms - discard + lda MF_PARAM ; seconds + sta MFTime+1 + lda MF_PARAM ; time lo + sta MFTime+2 + lda MF_PARAM ; time hi + sta MFTime+3 + lda MF_PARAM ; date lo - discard + lda MF_PARAM ; date hi - discard + ; Extract: min = time & $3F, hour = (time >> 6) & $1F + lda MFTime+2 + and #$3F + sta TimeNow+1 ; Minutes + lda MFTime+2 + lsr a + lsr a + lsr a + lsr a + lsr a + lsr a + sta MFTime+0 ; temp: hour low 2 bits + lda MFTime+3 + and #$07 ; hour bits from high byte + asl a + asl a + ora MFTime+0 + sta TimeNow ; Hours + lda MFTime+1 + sta TimeNow+2 ; Seconds + lda #$00 + sta TimeNow+3 ; Hundredths (MegaFlash has 4ms, we use 0 for simplicity) +GetTimeMFDone: + rts + +MFTime: .res 4 ; Temp: [0]=hour bits, [1]=sec, [2]=time_lo, [3]=time_hi + CheckForNoSlotClock: jsr PrepNoSlotClock ; Prepare the driver jsr NSCEntry ; Call it to get the time