********************************************************************************
* Disk ][ controller card "BOOT0" code, found in the slot ROM. Reads the *
* BOOT1 code from track 0, sector 0, and jumps to it. *
* *
* Copyright Apple Computer Inc. *
* *
* Written by [a genius...Woz?] *
********************************************************************************
* Extracted from AppleWin at $C600. *
* *
* Project created by Andy McFadden, using 6502bench SourceGen v1.5 *
* Last updated 2020/01/15 *
********************************************************************************
STACK .eq $0100 {addr/256}
TWOS_BUFFER .eq $0300 {addr/86} ;holds the 2-bit chunks
CONV_TAB .eq $0356 {addr/128} ;6+2 conversion table
BOOT1 .eq $0800 {addr/256} ;buffer for next stage of loader
IWM_PH0_OFF .eq $c080 ;stepper motor control
IWM_PH0_ON .eq $c081 ;stepper motor control
IWM_MOTOR_ON .eq $c089 ;starts drive spinning
IWM_SEL_DRIVE_1 .eq $c08a ;selects drive 1
IWM_Q6_OFF .eq $c08c ;read
IWM_Q7_OFF .eq $c08e ;WP sense/read
MON_WAIT .eq $fca8 ;delay for (26 + 27*Acc + 5*(Acc*Acc))/2 cycles
MON_IORTS .eq $ff58 ;JSR here to find out where one is
.org $c600
data_ptr .var $26 {addr/2} ;pointer to BOOT1 data buffer
slot_index .var $2b {addr/1} ;slot number << 4
bits .var $3c {addr/1} ;temp storage for bit manipulation
sector .var $3d {addr/1} ;sector to read
found_track .var $40 {addr/1} ;track found
track .var $41 {addr/1} ;track to read
c600: a2 20 ENTRY ldx #$20 ;20/00/03 is the controller signature
;
; Generate a decoder table for 6+2 encoded data.
;
; This stores the values $00-$3f in a table on page 3. The byte values that
; will be decoded are non-consecutive, so the decoder entries occupy various
; locations from $36c to $3d5. Nearby bytes are left unchanged.
;
; We want 64 values that have the high bit set and don't have two consecutive 0
; bits. This is required by the disk hardware. There are 70 possible values,
; so we also mandate that there are two adjacent 1 bits, excluding bit 7. (Note
; that $D5 and $AA, used to identify sector headers, do not meet these criteria,
; which means they never appear in the encoded data.)
;
; In the code below, ASL+BIT+BCS test checks for adjacent 1s: if no two are
; adjacent, the BIT will be zero. If the high bit is set, ASL will set the
; carry.
;
; When we ORA the original and shifted values together, if there were three
; adjacent 0s, there will still be at least two adjacent 0s. We EOR to invert
; the bits, and then look for two adjacent 1s. We do this by just shifting
; right until a 1 shifts into the carry, and if the A-reg is nonzero we know
; there were at least two 1 bits. We need to ignore the bits on the ends:
; nonzero high bit was handled earlier, and the low bit can false-positive
; because ASL always shifts a 0 in (making it look like a 0 in the low bit is
; adjacent to another 0), so we just mask those off with the AND.
;
; For example, we want to decode $A6 to $07. Y=$07 when X=$26...
; TXA --> 0010 0110
; ASL --> 0100 1100 C=0 (high bit is clear)
; BIT --> Z=0 (only possible with adjacent bits)
; ORA --> 0110 1110 (adjacent 0s become visible)
; EOR --> 1001 0001 (turn them into 1s)
; AND --> 0001 0000 (ignore the hi/lo)
; LSR --> 0000 1000, repeat until A=0 C=1
;
c602: a0 00 ldy #$00
c604: a2 03 ldx #$03
CreateDecTabLoop
c606: 86 3c stx bits
c608: 8a txa
c609: 0a asl A ;shift left, putting high bit in carry
c60a: 24 3c bit bits ;does shifted version overlap?
c60c: f0 10 beq :reject ;no, doesn't have two adjacent 1s
c60e: 05 3c ora bits ;merge
c610: 49 ff eor #$ff ;invert
c612: 29 7e and #$7e ;clear hi and lo bits
c614: b0 08 :check_dub0 bcs :reject ;initial hi bit set *or* adjacent 0 bits set
c616: 4a lsr A ;shift right, low bit into carry
c617: d0 fb bne :check_dub0 ;if more bits in byte, loop
c619: 98 tya ;we have a winner... store Y-reg to memory
c61a: 9d 56 03 sta CONV_TAB,x ;actual lookup will be on bytes with hi bit set
c61d: c8 iny ; so they'll read from CONV_TAB-128
c61e: e8 :reject inx ;try next candidate
c61f: 10 e5 bpl CreateDecTabLoop
;
; Prep the hardware.
;
c621: 20 58 ff jsr MON_IORTS ;known RTS
c624: ba tsx
c625: bd 00 01 lda STACK,x ;pull hi byte of our address off stack
c628: 0a asl A ;(we assume no interrupts have hit)
c629: 0a asl A ;multiply by 16
c62a: 0a asl A
c62b: 0a asl A
c62c: 85 2b sta slot_index ;keep this around
c62e: aa tax
c62f: bd 8e c0 lda IWM_Q7_OFF,x ;set to read mode
c632: bd 8c c0 lda IWM_Q6_OFF,x
c635: bd 8a c0 lda IWM_SEL_DRIVE_1,x ;select drive 1
c638: bd 89 c0 lda IWM_MOTOR_ON,x ;spin it up
;
; Blind-seek to track 0.
;
c63b: a0 50 ldy #80 ;80 phases (40 tracks)
c63d: bd 80 c0 :seek_loop lda IWM_PH0_OFF,x ;turn phase N off
c640: 98 tya
c641: 29 03 and #$03 ;mod the phase number to get 0-3
c643: 0a asl A ;double it to 0/2/4/6
c644: 05 2b ora slot_index ;add in the slot index
c646: aa tax
c647: bd 81 c0 lda IWM_PH0_ON,x ;turn on phase 0, 1, 2, or 3
c64a: a9 56 lda #86
c64c: 20 a8 fc jsr MON_WAIT ;wait 19664 cycles
c64f: 88 dey ;next phase
c650: 10 eb bpl :seek_loop
c652: 85 26 sta data_ptr ;A-reg is 0 when MON_WAIT returns
c654: 85 3d sta sector ;so we're looking for T=0 S=0
c656: 85 41 sta track
c658: a9 08 lda #>BOOT1 ;write the output here
c65a: 85 27 sta data_ptr+1
;
; Sector read routine.
;
; Read bytes until we find an address header (D5 AA 96) or data header (D5 AA
; AD), depending on which mode we're in.
;
; This will also be called by the BOOT1 code read from the floppy disk.
;
; On entry:
; X: slot * 16
; $26-27: data pointer
; $3d: desired sector
; $41: desired track
;
c65c: 18 ReadSector clc ;C=0 to look for addr (C=1 for data)
c65d: 08 ReadSector_C php
c65e: bd 8c c0 :rdbyte1 lda IWM_Q6_OFF,x ;wait for byte
c661: 10 fb bpl :rdbyte1 ;not yet, loop
c663: 49 d5 :check_d5 eor #$d5 ;is it $d5?
c665: d0 f7 bne :rdbyte1 ;no, keep looking
c667: bd 8c c0 :rdbyte2 lda IWM_Q6_OFF,x ;grab another byte
c66a: 10 fb bpl :rdbyte2
c66c: c9 aa cmp #$aa ;is it $aa?
c66e: d0 f3 bne :check_d5 ;no, check if it's another $d5
c670: ea nop ;(?)
c671: bd 8c c0 :rdbyte3 lda IWM_Q6_OFF,x ;grab a third byte
c674: 10 fb bpl :rdbyte3
c676: c9 96 cmp #$96 ;is it $96?
c678: f0 09 beq FoundAddress ;winner
c67a: 28 plp ;did we want data?
c67b: 90 df bcc ReadSector ;nope, keep looking
c67d: 49 ad eor #$ad ;yes, see if it's data prologue
c67f: f0 25 beq FoundData ;got it, read the data (note A-reg = 0)
c681: d0 d9 bne ReadSector ;keep looking
;
; Read the sector address data. Four fields, in 4+4 encoding: volume, track,
; sector, checksum.
;
c683: a0 03 FoundAddress ldy #$03 ;sector # is the 3rd item in header
c685: 85 40 :hdr_loop sta found_track ;store $96, then volume, then track
c687: bd 8c c0 :rdbyte1 lda IWM_Q6_OFF,x ;read first part
c68a: 10 fb bpl :rdbyte1
c68c: 2a rol A ;first byte has bits 7/5/3/1
c68d: 85 3c sta bits
c68f: bd 8c c0 :rdbyte2 lda IWM_Q6_OFF,x ;read second part
c692: 10 fb bpl :rdbyte2
c694: 25 3c and bits ;merge them
c696: 88 dey ;is this the 3rd item?
c697: d0 ec bne :hdr_loop ;nope, keep going
c699: 28 plp ;pull this off to keep stack in balance
c69a: c5 3d cmp sector ;is this the sector we want?
c69c: d0 be bne ReadSector ;no, go back to looking for addresses
c69e: a5 40 lda found_track
c6a0: c5 41 cmp track ;correct track?
c6a2: d0 b8 bne ReadSector ;no, try again
c6a4: b0 b7 bcs ReadSector_C ;correct T/S, go find data (branch-always)
;
; Read the 6+2 encoded sector data.
;
; Values range from $96 - $ff. They must have the high bit set, and must not
; have three consecutive zeroes.
;
; The data bytes are written to disk with a rolling XOR to compute a checksum,
; so we read them back the same way. We keep this in the A-reg for the
; duration. The actual value is always in the range [$00,$3f] (6 bits).
;
; On entry:
; A: $00
;
c6a6: a0 56 FoundData ldy #86 ;read 86 bytes of data into $300-355
:read_twos_loop
c6a8: 84 3c sty bits ;each byte has 3 sets of 2 bits, encoded
c6aa: bc 8c c0 :rdbyte1 ldy IWM_Q6_OFF,x
c6ad: 10 fb bpl :rdbyte1
c6af: 59 d6 02 eor $02d6,y ;$02d6 + $96 = $36c, our first table entry
c6b2: a4 3c ldy bits
c6b4: 88 dey
c6b5: 99 00 03 sta TWOS_BUFFER,y ;store these in our page 3 buffer
c6b8: d0 ee bne :read_twos_loop
;
:read_sixes_loop
c6ba: 84 3c sty bits ;read 256 bytes of data into $800
c6bc: bc 8c c0 :rdbyte2 ldy IWM_Q6_OFF,x ;each byte has the high 6 bits, encoded
c6bf: 10 fb bpl :rdbyte2
c6c1: 59 d6 02 eor CONV_TAB-128,y
c6c4: a4 3c ldy bits
c6c6: 91 26 sta (data_ptr),y ;store these in the eventual data buffer
c6c8: c8 iny
c6c9: d0 ef bne :read_sixes_loop
;
c6cb: bc 8c c0 :rdbyte3 ldy IWM_Q6_OFF,x ;read checksum byte
c6ce: 10 fb bpl :rdbyte3
c6d0: 59 d6 02 eor CONV_TAB-128,y ;does it match?
c6d3: d0 87 :another bne ReadSector ;no, try to find one that's undamaged
;
; Decode the 6+2 encoding. The high 6 bits of each byte are in place, now we
; just need to shift the low 2 bits of each in.
;
c6d5: a0 00 ldy #$00 ;update 256 bytes
c6d7: a2 56 :init_x ldx #86 ;run through the 2-bit pieces 3x (86*3=258)
c6d9: ca :decode_loop dex
c6da: 30 fb bmi :init_x ;if we hit $2ff, go back to $355
c6dc: b1 26 lda (data_ptr),y ;foreach byte in the data buffer...
c6de: 5e 00 03 lsr TWOS_BUFFER,x ; grab the low two bits from the stuff at $300-$355
c6e1: 2a rol A ; and roll them into the low two bits of the byte
c6e2: 5e 00 03 lsr TWOS_BUFFER,x
c6e5: 2a rol A
c6e6: 91 26 sta (data_ptr),y
c6e8: c8 iny
c6e9: d0 ee bne :decode_loop
;
; Advance the data pointer and sector number, and check to see if the sector
; number matches the first byte of BOOT1. If it does, we're done. If not, go
; read the next sector.
;
c6eb: e6 27 inc data_ptr+1
c6ed: e6 3d inc sector
c6ef: a5 3d lda sector ;sector we'd read next
c6f1: cd 00 08 cmp BOOT1 ;is next sector < BOOT1?
c6f4: a6 2b ldx slot_index
c6f6: 90 db bcc :another ;yes, go get another sector (note branch x2)
; All done, jump to BOOT1 ($0801).
c6f8: 4c 01 08 jmp BOOT1+1
c6fb: 00 00 00 00+ .junk 5 ;spare bytes