This is an old revision of the document!
WORK IN PROGRESS
I've been actively working on disassembling the Apple IIc Plus firmware in order to find out how its 3.5“ firmware works with the unique hardware of the machine to support the “dumb” 3.5" floppy drives.
A lot of it involves knowing how the 3.5 floppies are formatted at the low-level. The Woz state machine is the basis for operations with both the 3.5 and 5.25 floppies, so there are bound to be similarities between the the low level formats, and there are. In particular, the following are common to both:
The 3.5 floppy sectors are formatted as follows:
Address Field | Data Field |
The address field of the 3.5 floppy is as follows
5+ self-syncs | $D5 $AA $96 | TT SS DD FF KK | $DE $AA “off” |
Where TT is the low 6 bits of the track, SS is the sector number, DD is the side and upper bit of the track number, and FF is the format specifier (sides+sector interleave), and KK is the checksum.
The data field is:
5 self-syncs | $D5 $AA $AD | 699 disk bytes | KK KK KK KK | $DE $AA “off” |
In this case, we can see the address and data prologues are the same between the 5.25 and 3.5 formats. The epilogues are defined differently. In we are used to $DE $AA $EB, but instead of $EB we have “off”… this is defined in the official documentation as a “pad byte where the drive electronics were turned off.” Code that reads a 3.5” disk should not assume the byte contains anything useful. In fact, ProDOS ignores this byte even when reading a 5.25 floppy.
Unlike the 5.25 floppy address header, the 3.5 floppy address header is coded in 6&2 format and can be decoded from the standard nibble table.. E.g. if SS contains $96, the address is for sector 0, if it contains $97, the address is for sector 1.
The disk format we are all used to with the 5 1/4 floppies is well-explained in Beneath Apple DOS. To summarize what a sector header looks like for one:
self-syncs | $D5 $AA $96 | VV VV TT TT SS SS KK KK | $DE $AA $EB |
In the case of the 5.25 floppy, the volume, track, and sector are encoded in the 4&4 format - each byte is split into the odd and even bits, interleaved with ones and written to the disk. Decoding involves reading the first byte, shifting it, and ANDing it with the second byte.
Since there is a high degree of code commonality in the main firmware bank of the IIc Plus vs the ROM $4 //c, I've been mostly concentrating on the aux firmware bank, where I found the following code:
; Look for $D5 $AA $96 - Sector address mark LDD06: lda LC0EC ; read IWM bpl LDD06 ; wait for byte LDD0B: cmp #$D5 ; is it $D5? bne LDD00 ; nope, go try again (code not shown) LDD0F: lda LC0EC ; bpl LDD0F ; cmp #$AA ; bne LDD0B ; ldy #$03 ; we are going to get 4 things below LDD1A: lda LC0EC ; bpl LDD1A ; cmp #$96 ; bne LDD0B ; look for address mark some more ; have address mark, now read the address field. sei ; just in case, I guess... it was disabled earlier lda #$00 ; init checksum LDD26: sta $3B ; update checksum LDD28: lda LC0EC ; read byte bpl LDD28 ; wait for it... rol a ; ? sta $3E ; and save it LDD30: lda LC0EC ; next byte bpl LDD30 ; wait for it... and $3E ; ????! wait a second, this looks like 5.25 address data sta LCE04,y ; save in MIG RAM eor $3B ; update checksum dey ; next 2 bytes, it seems... bpl LDD26 ; if not done, read next pair tay ; should be 0 if we processed checksum bne LDCE7 ; if not, I/O error
Note my comments reflecting my surprise at finding this buried in the Apple IIc Plus firmware. This is code that clearly reads the 4 values from the address field of a 5.25 floppy. But why is it here? I left that question in my head as I continued disassembly.
Later on, I found this in the shared SmartPort/ProDOS block device code:
LE3DF: stz LCE0E ; clear this in MIG page 0 tay ; move unit number to Y cmp #$05 ; bcs LE3FB ; if 5 or bigger... lda $07F8 ; slot $Cn was put here cmp #$C6 ; is slot 6? beq LE404 ; treat this one differently
Okay, that's very interesting. The SmartPort code clearly expects and special cases a reference to slot 6, which as we know is used for 5.25 floppies in the IIc Plus (and //c).
Well, time to go poking around.
On a hunch, I decided to go looking at the boot code for the machine. It starts like this:
LC600: ldx #$20 ; Disk II boot code generally starts this way ldy #$00 ; Also contains the ID bytes stz $03 ; The code from here matches the //c stz L003C ; which has a limited retry count for boot sector lda #$60 ; we are at a fixed location, we know this is slot 6 tax ; stx $2B ; save it the various places boot code expects sta $4F ; phy ; lda LC08E,x ; IWM Q7 OFF lda LC08C,x ; IWM Q6 OFF - going to read data register ply ; lda LC0EA,y ; IWM - select drive 1 jsr LC763 ; IIc Plus has this.... //c does not
So what's at $C763?
LC763: ldx #$05 ; 6 things LC765: lda LC56C,x ; Copy from $C56C sta $42,x ; To $42 dex ; bpl LC765 ; jsr LC64E ; Call this thing eor #$28 ; see if A=$28 bne LC775 ; if not, more stuff to do rts ; otherwise, exit LC775: bit LC0E9 ; IWM - turn on drive motor lda #$01 ; sta $42 ; jmp LC58E ; go do more stuff
Hmmm… normally when you write 6 bytes to $42, you are setting up a “ProDOS Intelligent Disk Controller” call as specified in the ProDOS 8 Technical Reference Manual.
So what's at $C56C? $FF $60 $00 $08 $00 $00
Well, that's kind of odd but if it were a block driver call, the command would be $FF (undefined), unit for slot 6, drive 1, buffer $0800, block 0. I guess we'll have to see what the code at $C64E does:
LC64E: sec ; bcs LC652 ; clc ; LC652: jmp LC567 ; more stuff to do
LC567: lda #$C6 ; a slot 6 reference jmp LC510 ; Now *this* is interesting
So I know that $C510 is part of the slot 5 SmartPort/Block Device code… so I will give the context before $C510:
sec ; this is the "Intelligent Disk Device" entry point bcs LC50E ; clc ; this is the SmartPort entry point LC50E: lda #$C5 ; a slot 5 reference LC510: sta $07F8 ; save in screen hole lda #$81 ; save the language card state bit LC012 ; to be restore later with accesses to $C000,X bpl LC523 ; lda #$8B ; bit LC011 ; bpl LC523 ; lda #$83 ; LC523: tax ; bit LC089 ; make sure ROM is present lda #$00 ; ror a ; put carry into A (reflecting what entry point was used) cld ; no decimals here! jsr LC584 ; this switches firmware banks and calls implementation bit LC000,x ; restore LC state bit LC000,x ; lda $0478 ; contains error code, if any ldx $04F8 ; load X from a screen hole cmp #$01 ; set carry if error ora #$00 ; Make sure Z flag is set? rts ;
So the code at $C64E calls the slot 5 block device or SmartPort entry points, but sets up the call to look like it's coming from slot 6! This is kind of a big deal.
Now this makes some sense:
LC763: ldx #$05 ; 6 parameter bytes LC765: lda LC56C,x ; copy from $C56C sta $42,x ; to $42 dex ; bpl LC765 ; jsr LC64E ; call slot 6 ProDOS block device driver eor #$28 ; see if A=$28, the error code for no device connected bne LC775 ; if no error, don't exit yet rts ; LC775: bit LC0E9 ; IWM - turn on drive motor of the unit we just accessed lda #$01 ; lie and say we did a block device driver read command sta $42 ; update command in parameter table jmp LC58E ; see below
OK, so now we could look at $C58E, but I already know what's there, it's code that's been in every Apple //c: a routine to generate a denibbilizing routine that 5.25 boot sectors expect to be present.
When that returns, we go back to the code in slot 6 that brought us here, that mostly looks like the original //c boot code.
So, about that device command $FF? I suspect it's “go boot that thing if possible”
More to come…