micro-sci-a2-controller/firmware/firmware.s
2023-07-16 11:28:35 +09:00

364 lines
14 KiB
ArmAsm

; 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
;
; 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 #<TWOS_BUFFER
sta data_ptr ;so we're looking for T=0 S=0
sta sector
.else
sta data_ptr ;A-reg is 0 when MON_WAIT returns
sta sector ;so we're looking for T=0 S=0
sta track
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