; 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?] (CR: Andy Hertzfeld says it was Dick Huston) ; ; Extracted from AppleWin at $C600. ; ; Project created by Andy McFadden, using 6502bench SourceGen v1.5 ; Last updated 2020/01/15 ; ; Converted for use with Micro-SCI repro project 2023 Chris RYU STACK := $0100 ;{addr/256} .ifdef SECSIZE_16 TABLE_ENTRY := $02d6 .else TABLE_ENTRY := BOOT1 .endif TWOS_BUFFER := $0300 ;{addr/86} ;holds the 2-bit chunks CONV_TAB := $0356 ;{addr/128} ;6+2 conversion table BOOT1 := $0800 ;{addr/256} ;buffer for next stage of loader IWM_PH0_OFF := $c080 ;stepper motor control IWM_PH0_ON := $c081 ;stepper motor control IWM_MOTOR_ON := $c089 ;starts drive spinning IWM_SEL_DRIVE_1 := $c08a ;selects drive 1 IWM_Q6_OFF := $c08c ;read IWM_Q7_OFF := $c08e ;WP sense/read MON_WAIT := $fca8 ;delay for (26 + 27*Acc + 5*(Acc*Acc))/2 cycles PRERR := $ff2d ; 13-sector only MON_IORTS := $ff58 ;JSR here to find out where one is data_ptr := $26 ;{addr/2} ;pointer to BOOT1 data buffer slot_index := $2b ;{addr/1} ;slot number << 4 bits := $3c ;{addr/1} ;temp storage for bit manipulation sector := $3d ;{addr/1} ;sector to read .ifdef SECSIZE_13 found_track := $2a ;{addr/1} ;track found signature := $b5 .else found_track := $40 ;{addr/1} ;track found signature := $96 .endif track := $41 ;{addr/1} ;track to read .org $c600 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 ; ldy #$00 .ifdef SECSIZE_16 ldx #$03 CreateDecTabLoop: stx bits txa asl A ;shift left, putting high bit in carry bit bits ;does shifted version overlap? beq reject ;no, doesn't have two adjacent 1s ora bits ;merge eor #$ff ;invert and #$7e ;clear hi and lo bits check_dub0: bcs reject ;initial hi bit set *or* adjacent 0 bits set lsr A ;shift right, low bit into carry bne check_dub0 ;if more bits in byte, loop tya ;we have a winner, store Y to memory sta CONV_TAB,x ;actual lookup will be on bytes with ;hi bit set iny ; so they'll read from CONV_TAB-128 reject: inx ;try next candidate bpl CreateDecTabLoop .else CreateDecTabLoop: LDA #$03 STA bits CLC DEY TYA LC60B: BIT bits BEQ CreateDecTabLoop ROL bits BCC LC60B CPY #$D5 ; $D5 is reserved to indicate header BEQ CreateDecTabLoop DEX TXA STA TABLE_ENTRY,Y BNE CreateDecTabLoop .endif ; ; Prep the hardware. ; jsr MON_IORTS ;known RTS tsx lda STACK,x ;pull hi byte of our address off stack .ifdef SECSIZE_13 pha .endif asl A ;(we assume no interrupts have hit) asl A ;multiply by 16 asl A asl A sta slot_index ;keep this around tax .ifdef SECSIZE_13 lda #$d0 ; ck fixme pha .endif lda IWM_Q7_OFF,x ;set to read mode lda IWM_Q6_OFF,x lda IWM_SEL_DRIVE_1,x ;select drive 1 lda IWM_MOTOR_ON,x ;spin it up ; ; Blind-seek to track 0. ; ldy #80 ;80 phases (40 tracks) seek_loop: lda IWM_PH0_OFF,x ;turn phase N off tya and #$03 ;mod the phase number to get 0-3 asl A ;double it to 0/2/4/6 ora slot_index ;add in the slot index tax lda IWM_PH0_ON,x ;turn on phase 0, 1, 2, or 3 lda #86 jsr MON_WAIT ;wait 19664 cycles dey ;next phase bpl seek_loop .ifdef SECSIZE_13 lda #>TWOS_BUFFER sta data_ptr+1 ;A-reg is 0 when MON_WAIT returns lda #BOOT1 ;write the output here sta data_ptr+1 .endif ; ; 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 ; ReadSector: clc ;C=0 to look for addr (C=1 for data) ReadSector_C: php @rdbyte1: lda IWM_Q6_OFF,x ;wait for byte bpl @rdbyte1 ;not yet, loop @check_d5: eor #$d5 ;is it $d5? bne @rdbyte1 ;no, keep looking @rdbyte2: lda IWM_Q6_OFF,x ;grab another byte bpl @rdbyte2 cmp #$aa ;is it $aa? bne @check_d5 ;no, check if it's another $d5 nop ;(?) @rdbyte3: lda IWM_Q6_OFF,x ;grab a third byte bpl @rdbyte3 cmp #signature ;is it $96? beq FoundAddress ;winner plp ;did we want data? bcc ReadSector ;nope, keep looking eor #$ad ;yes, see if it's data prologue beq FoundData ;got it, read the data (note A-reg = 0) bne ReadSector ;keep looking ; ; Read the sector address data. Four fields, in 4+4 encoding: volume, track, ; sector, checksum. ; FoundAddress: ldy #$03 ;sector # is the 3rd item in header .ifdef SECSIZE_13 hdr_loop: sty found_track ;store $96, then volume, then track .else hdr_loop: sta found_track ;store $96, then volume, then track .endif @rdbyte1: lda IWM_Q6_OFF,x ;read first part bpl @rdbyte1 rol A ;first byte has bits 7/5/3/1 sta bits @rdbyte2: lda IWM_Q6_OFF,x ;read second part bpl @rdbyte2 and bits ;merge them dey ;is this the 3rd item? .ifdef SECSIZE_13 bne @rdbyte1 ;nope, keep going .else bne hdr_loop ;nope, keep going .endif plp ;pull this off to keep stack in balance cmp sector ;is this the sector we want? bne ReadSector ;no, go back to looking for addresses .ifdef SECSIZE_16 lda found_track cmp track ;correct track? bne ReadSector ;no, try again .endif bcs ReadSector_C ;correct T/S, 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 ; FoundData: .ifdef SECSIZE_13 ldy #$9a .else ldy #86 ;read 86 bytes of data into $300-355 .endif read_twos_loop: sty bits ;each byte has 3 sets of 2 bits, encoded @rdbyte1: ldy IWM_Q6_OFF,x bpl @rdbyte1 eor TABLE_ENTRY,y ;$02d6 + $96 = $36c, first table entry ldy bits dey .ifdef SECSIZE_13 sta TABLE_ENTRY,y .else sta TWOS_BUFFER,y ;store these in our page 3 buffer .endif bne read_twos_loop ; read_sixes_loop: sty bits ;read 256 bytes of data into $800 @rdbyte2: ldy IWM_Q6_OFF,x ;each byte has the high 6 bits, encoded bpl @rdbyte2 .ifdef SECSIZE_13 eor TABLE_ENTRY,y .else eor CONV_TAB-128,y .endif ldy bits sta (data_ptr),y ;store these in the eventual data buffer iny bne read_sixes_loop ; @rdbyte3: ldy IWM_Q6_OFF,x ;read checksum byte bpl @rdbyte3 .ifdef SECSIZE_13 eor TABLE_ENTRY,y ;does it match? .else eor CONV_TAB-128,y ;does it match? .endif another: bne ReadSector ;no, try to find one that's undamaged .ifdef SECSIZE_13 GRP := $33 RTS DENIB: TAY LC6D2: LDX #0 LC6D4: LDA BOOT1,Y LSR ROL TWOS_BUFFER+4*GRP,X LSR ROL TWOS_BUFFER+3*GRP,X STA bits LDA (data_ptr),Y ASL ASL ASL ORA bits STA (data_ptr),Y INY INX CPX #GRP BNE LC6D4 DEC found_track BNE LC6D2 CPY TWOS_BUFFER BNE ERROR JMP TWOS_BUFFER+1 ERROR: JMP PRERR .byte $FF .else ; ; 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. ; ldy #$00 ;update 256 bytes init_x: ldx #86 ;run through the 2-bit pieces 3x (86*3=258) decode_loop: dex bmi init_x ;if we hit $2ff, go back to $355 lda (data_ptr),y ;foreach byte in the data buffer... lsr TWOS_BUFFER,x ; grab the low two bits from the stuff ; at $300-$355 rol A ; and roll them into the low two bits ; of the byte lsr TWOS_BUFFER,x rol A sta (data_ptr),y iny 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. ; inc data_ptr+1 inc sector lda sector ;sector we'd read next cmp BOOT1 ;is next sector < BOOT1? ldx slot_index bcc another ;yes, go get another sector ;(note branch x2) ; All done, jump to BOOT1 ($0801). jmp BOOT1+1 .byte 0, 0, 0, 0, 0 ;spare bytes .endif