前回でSDCARDでの仮想ディスクができたので、今回はBIOSでのディスク関連のルーチンを作成していきたいと思います。
主なディスク関連のルーチンは次のとおりです。順を追って作成してみます。
home: seldsk: settrk: setsec: setdma: read: write: sectran:
まずは、home: から
本来ならディスク装置のヘッドをホーム(トラック0)に移動することらしいです。ここでは、settrk:(トラック位置のセットルーチン)にトラック00を設定しています。
home:
ld c,0x00 ;track 00
call settrk
ret
seldsk:は ディスクナンバーをセットします。
cレジスタにセットするディスクナンバー(a:>00 B:>01 ・・)が入っているのでセットナンバーにあったディスクパラメータのアドレスをHLレジスターにセットします。存在しない場合はHLレジスタに0x0000をセットします。
また、実際のread/writeにディスクナンバーが必要になりますのでdisk_numberに保存します。
DISKS_MAXには使用するディスク数をセットしておきます。
dpbaseを基準にディスクパラメータは1つのディスク16byte使用しているので、HLレジスタにディスクナンバーをいれて16倍したのち、dpbaseを足して該当のパラメータアドレスを取得しています。
seldsk: ;c reg set disk no
ld hl,0x0000 ;err set hl=0000 return
ld a,c
cp DISKS_MAX
ret nc
ld (disk_number),a
ld l,a
ld h,0x00
add hl,hl
add hl,hl
add hl,hl
add hl,hl
ld de,dpbase
add hl,de
ret
settrk:は呼び出すトラックナンバーがCレジスターに入ってきます。
read/writeで使用するので、track_numberにトラックナンバーを保存します。
settrk: ;set track address given by c
ld hl,track_number
ld (hl),c
ret
setsec:は呼び出すセクタナンバーがCレジスターに入ってきます。
read/writeで使用するので、sector_numberにセクタナンバーを保存します。
setsec: ;set sector numbar given by c
ld hl,sector_number
ld (hl),c
ret
setdma:はread/writeで読み書きするための128byteのバッファアドレスがBCレジスタに入ります。デフォルトは 0x0080になっています。
このアドレス(dma_buff_addr)も実際のread/writeで読み込み先アドレス/書き込み先アドレスとして使用します。
setdma: ;set dma address set bc reg
ld h,b
ld l,c
ld (dma_Buff_addr),hl
ret
sectran:はディスクドライブの同心円状のセクタを早く読み書きするためのスキューという手法のためのルーチンですが、SDCARDではスキューを使う必要はありません。
ただ、今回は8インチ片面単密度のディスクパラメータをそのまま使用しているのでスキュー有りになっています。実際のルーチンはお手本のbiosをそのまま使用しています。
BCレジスタにスキューしたいセクタが、DEにスキュー変換テーブルのアドレスが、HLに変換されたセクタが格納されます。
sectran: ;sector translate
;logical sector > BC
;sector translate teble > de
;return to translate ogical sector > hl
ld b,0
ex de,hl
add hl,bc
ld a,(hl)
ld (sector_number),a
ld l,a
ret
read:は、ドライブナンバー、トラックナンバー、セクターナンバーから、該当のSDCARDのブロック位置の情報を読み込み、DMAアドレスの先頭から格納します。
write:でも使用する共通ルーチンとして、get_sectorを作成し、先程の情報から該当、ブロックナンバーを算出しています。
まずは、ディスクナンバーから、先頭のセクタ位置を算出します。
1ディスク 77トラック、1トラック26セクタあるので、1ディスクは77*26セクタになります。これをデスクナンバーにかければ 各ディスクの先頭セクタがわかります。
次に、トラックナンバーから 先頭セクタ+トラックナンバー*26にてトラックナンバー位置のセクタがわかります。
セクタは 1から26を取るので 1減算して、0から25にします。
そして、トラックナンバー位置のセクタと足すことにより、セクタ位置がわかるので、この位置をSDCARDのブロックナンバーとします。
get_sector:
ld hl,0x0000
ld de,DISK_TRACK * DISK_SECTOR ;1 disk sector >> DISK_TRACK * DISK_SECTOR
ld a,(disk_number)
cp 0x00
jr z,read_track
ld b,a
read_disk01:
add hl,de
djnz read_disk01
read_track:
ld de,DISK_SECTOR ;1 track sector >> DISK_SECTOR
ld a,(track_number)
cp 0x00
jr z,read_sector
ld b,a
read_track01:
add hl,de
djnz read_track01
read_sector:
ld a,(sector_number)
dec a
ld e,a
ld d,0x00
add hl,de
ret
実際のread:はマルチブロックリードのcmd18を使用しました。
複数ブロックの読み込みが可能ですが、ここでは1ブロックだけのリードとなっています。
まずは、get_sectorで該当ブロックがHLレジスタに置かれます。
cmd18コマンド列の該当部分にこのHLレジスタの内容を格納します。
cmd18を発行してレスポンスを待って、1byte読み捨てしてからデータブロックの先頭の0xfeを探します。
続いて、データの先頭128byteだけdma_byff_addrの先頭から格納します。
残りの512-128byteは読み捨てします(128byte*3)
データブロックの最後のcrc 2byteも読み捨てします。
これで 1ブロック 512byte読み終えたので、1byte置いてからcmd12を発行してマルチブロックリードを終了します。
cmd12のレスポンスを確認してから、データ(DI)がLレベル(ビジー)からHレベルになるまで待ちます。
正常に終了したら、Aレジスターに0x00をエラーの場合には0x01をセットして戻ります。
read: ;read next disk record(assuming disk/trk/sec/dma set)
call get_sector ;read block HL reg
ld a,(r7)
bit 6,a
jp z,read_SD_ver2
ld a,h ;SDHC
ld (cmd18+03),a
ld a,l
ld (cmd18+04),a
jr spi_read2
read_SD_ver2:
ld a,h ;Ver2
ld (cmd18+02),a
ld a,l
ld (cmd18+03),a
ld hl,(dma_Buff_addr)
spi_read2:
call dummy_data ;dummy data(0xff) out
ld hl,cmd18
call cmd_out ;cmd17 Issue
call r1_resp
cp 0x00 ;response is 0x00 read mode OK?
jr nz,cmd18_err
cmd18_st:
ld d,0xff
call spi_8bit
call r1_resp ;read data start response?
cp 0xfe ;data start byte 0xfe?
jr nz, cmd18_st
ld b,0x80 ;read data size 128byte
ld hl,(dma_Buff_addr) ;read data buff address
cmd18_r:
call resp ;read data
ld (hl),a
inc hl
djnz cmd18_r
;; dummy read 128byte 2st 3st 4st
ld b,0x80 ;read data size 128byte
cmd18_r2st:
call resp
djnz cmd18_r2st
ld b,0x80
cmd18_r3st:
call resp
djnz cmd18_r3st
ld b,0x80
cmd18_r4st:
call resp
djnz cmd18_r4st
call resp ;crc Skipping read
call resp ;crc Skipping read
ld d,0xff ;dummy data 0xff
call spi_8bit
ld hl,cmd12 ;stop cmd
call cmd_out ;cmd12 Issue
ld d,0xff
call spi_8bit
call r1_resp
cp 0x00 ;response is 0x00 read mode OK?
jr nz,cmd18_err
cmd18_wch3:
call resp
cp 0x00
jr z,cmd18_wch3
call set_cs ;CS="H"
xor a
ret
cmd18_err:
pop hl
call set_cs ;CS="H"
ld a,0x01
ret
write:の方ですがこちらも今回はマルチブロックライトのcmd25を使用しました。
複数ブロックの書き込みが可能ですが、ここでは1ブロックだけのライトとなっています。
まずは、get_sectorで該当ブロックがHLレジスタに置かれます。
cmd25コマンド列の該当部分にこのHLレジスタの内容を格納します。
cmd25を発行してレスポンスを待って、1byte読み捨てしてからデータブロックの先頭の0xfcを書き込みします。
続いて、dma_byff_addrの先頭から 128byte順次書き込みしていきます。
残りの512-128byteは0xe5で書き込み埋めていきます(128byte*3)
データブロックの最後のcrc 2byteは正式なcrc計算はせず0xffを書き込みします。(実際読み出しの際はcrcは無視しています^^;)
これで 1ブロック 512byte書き込が終了しました。
cmd25のデータブロックのレスポンスを確認してから、データ(DI)がLレベル(ビジー)からHレベルになるまで待ちます。
cmd25のストップトークン(oxfd)を発行して、1byte読み捨てしてからデータ(DI)がLレベル(ビジー)からHレベルになるまで待ちます。
これで、マルチブロックライトを終了します。
正常に終了したら、Aレジスターに0x00をエラーの場合には0x01をセットして戻ります。
write:
call get_sector
spi_write1:
ld a,(r7)
bit 6,a
jp z,write_SD_ver2
ld a,h ;SDHC
ld (cmd25+03),a
ld a,l
ld (cmd25+04),a
jr spi_write2
write_SD_ver2: ;Ver2
ld a,h
ld (cmd25+02),a
ld a,l
ld (cmd25+03),a
spi_write2:
call dummy_data
ld hl,cmd25
call cmd_out
call r1_resp
cp 0x00
jp nz,cmd25_err
ld d,0xff
call spi_8bit
ld d,0xfc ;cmd25 data token
call spi_8bit
ld hl,(dma_Buff_addr)
ld b,0x80
cmd25_w:
ld d,(hl)
inc hl
call spi_8bit
djnz cmd25_w
; 2st dummy write(0xe5)
ld b,0x80
cmd25_w2st:
ld d,0xe5
call spi_8bit
djnz cmd25_w2st
; 3st dummy write(0xe5)
ld b,0x80
cmd25_w3st:
ld d,0xe5
call spi_8bit
djnz cmd25_w3st
; 4st dummy write(0xe5)
ld b,0x80
cmd25_w4st:
ld d,0xe5
call spi_8bit
djnz cmd25_w4st
ld d,0xff
call spi_8bit ;crc
ld d,0xff
call spi_8bit ;crc
call r1_resp
and 0x0f
cp 0x05
jr nz,cmd25_err2
cmd25_wch:
call r1_resp
cp 0x00
jr z,cmd25_wch
ld d,0xfd ;cmd25 stop token
call spi_8bit
ld d,0xff
call spi_8bit
cmd25_wch3:
call resp
cp 0x00
jr z,cmd25_wch3
call dummy_data
call set_cs ;set CS
xor a
ret
cmd25_err:
cmd25_err2:
call set_cs ;set cs
ld a,0x01
ret
これで ディスク リード/ライトのルーチンができましたので次回残りのwbootを作成して、cp/mを起動させてみたいと思います。