Operating Systems Development Series
FDC Programming
by Mike, 2008, 2009

This series is intended to demonstrate and teach operating system development from the ground up.


8272A Floppy Disk Controller

はじめに

やったー!ついにフロッピーディスクドライブを扱う時が来ました!この章では、フロッピーディスクドライブとフロッピーディスクのプログラミン グについて知るべきことをほとんどすべてカバーします!

この章のメニューはこんな感じです。

  • FDCとFDDの歴史
  • ディスクレイアウト
  • CHS、LBA
  • FDDの構造
  • FDCのハードウエア
  • FDCとのインターフェイス
  • FDCのレジスタとコマンド

歴史

フロッピーディスクコントローラ(FDC)は、フロッピーディスクドライブ(FDD)とインターフェイスするコントローラです。 PCでは通常、NECのPD765 FDCが使用されています。PS/2ではIntel 82077A、ATではIntel 82072Aというマイコンが使われています。

フロッピーディスクドライブ(FDD)は、フロッピーディスクに対してデータの読み書きができる装置である。

1971年、IBMのダイレクト・アクセス・ストレージ製品マネージャであったアラン・シュガートに雇われたデビッド・L・ノーブルは、同社のSystem/370メインフレーム用に新しいストレージ・テープ・フォーマットを開発しようとした。IBMは、初期制御プログラムロード(ICPL)のマイクロコードを再ロードする際に、テープドライブよりも小型で高速なものを作ろうと考えていた。ノブレス氏のチームは、「ミノー」というコードネームで「メモリーディスク」と呼ばれる製品に取り組んでいた。これは、80キロバイトの容量を持つ読み取り専用の8インチ・ディスケットである。1971年に市販され、すべてのSystem/370メインフレームに搭載された。

アラン・シュガートがIBMを退職し、メモレックスに移ると、彼のチームは1972年に最初の読み書き可能なフロッピーディスクドライブである「Memorex 650」を出荷した。

フロッピーディスクは、IBMが8インチ、5インチと1/4インチ、3 1/2 インチのフォーマットを発明した。

ディスクの構造

物理レイアウト

ディスクの構造を理解することが大切です。フロッピーディスクはこんな構造になっています。

一般的な3-1/2インチフロッピーディスクの物理レイアウトです。ここではヘッド1(前面)を見ていますが、セクタは512バイトを表します。トラックはセクタの集合体です。

注:1セクタは512バイトで、フロッピーディスクには1トラックあたり18セクタがあることを覚えておいてください。

上の絵を見て、思い出してください。

  • 各トラックは通常512バイトのセクタに分割されています。フロッピーディスクでは、1トラックあたり18のセクタがあります。
  • シリンダーとは、同じ半径のトラックの集まりのことです(上の写真の赤いトラックは1つのシリンダーです)。
  • フロッピーディスクには2つのヘッドがあります。
  • 合計2880個のセクタがあります。
次に、CHSについて説明します。次はそれを見てみましょう。

シリンダー/ヘッド/セクター(CHS)

セクタ

セクタとは、512バイトのまとまりを表します。つまり、セクタ1はディスクの最初の512バイトを表しています。

ヘッド

ヘッド」(またはフェイス)は、ディスクの側面を表します。ヘッド0が表側、ヘッド1が裏側です。ほとんどのディスクは1面しかないため、ヘッドも1つしかありません(「ヘッド1」)。

トラック

トラックとは、ディスクの周囲にある1つの輪のことです。フロッピーディスクの場合、1つのトラックには18セクタがある。

シリンダーは、1枚のディスクのトラック番号を表します。フロッピーディスクの場合、読み出すトラックを表します。

1トラックあたり18セクタあります。片面80トラック。

CHSを理解する

フロッピーディスクのアドレスはCHS形式である。ディスク上の任意の場所から読み書きするためには、FDCに読み取り/書き込みヘッドをディスク上の正確なトラック、シリンダー、セクタに移動するよう指示する必要がある。

リニアブロックアドレッシング(LBA)

また、LBA(Linear Block Addressing)を使って、より抽象的なディスクの読み書きを行うことも可能です。LBAでは、ディスク上のセクタ0〜2880までの任意のセクタに対して読み書きができる。

フロッピーディスクのインターフェイス

ソフトウェアとフロッピーディスクドライブのインターフェースは、フロッピーディスクコントローラを介して制御します。 フロッピーディスクコントローラの違いにより、ここではオリジナルの8272Aフロッピーディスクコントローラに焦点を当てたいと思います。冒頭の画像は、典型的な8272A集積回路(IC)コントローラです。これが、今回取り上げるICです。

詳細82072Aフロッピーマイクロコントローラ

8272AのICは40ピンあります。ここで見てみましょう。40本のピンを簡単に見ていきますが、電子工作の分野に入ってしまうので、ここでは詳しく見ていきません。

これらのピンのほとんどは、コントローラのプログラミングにはあまり役立ちません。しかし、他のピンは、理解することがより重要です。それでは、見ていきましょう。FDCは、プログラマブル割り込みコントローラ(PIC)、システムバス、およびダイレクトメモリアクセスコントローラと間接的に通信していることがわかります。

  • RESET Pin- FDCをアイドル状態にします。すべての出力ラインをLowにします。Vccピンは、+5V電源入力です。
  • GND Pin- グラウンドピンです。
  • CLK Pin - 標準的な単相8MHz方形波クロック信号です。
  • RD Pin - 現在の動作がリード動作であることをFDCに伝えます。
  • WR Pin:書き込み動作であることを示します。
    • ソフトウェアによるI/Oのリード/ライト動作で、コントロールバスによって設定されます。
  • CS Pin - チップセレクト
  • DB0 - DB7 Pin- 双方向の8ビットデータバスです。システムのプライマリ・データ・バスに間接的に接続されます。
  • A0 Pin -データ/ステータス・レジスタ・セレクト Pinです。ハイ(1)の場合、FDCにそのデータ・レジスタの内容をデータ・バスに配置するよう指示します。Low (0) の場合、ステータス レジスタの内容をデータ バスにコピーします。これは、出力データ・バス・ピンDB0~DB7を通じて行われ、さらに、ソフトウェアで読み出すことができるシステム・データ・バスを介して行われます。
  • DRQ Pin-データ直接メモリアクセス(DMA)要求ピン。このラインがハイ(1)の場合、FDCはDMAリクエストを行っています。
  • DACK Pin -DMAアクノレッジ Pin。コントローラがDMA転送を実行している場合、このラインはLow (0) になります。
  • TC Pin - DMA転送が完了すると、FDCはTerminal Count Pin、TCをHigh (1) に設定します。
  • IDX Pin - FDCがディスクトラックの先頭にいるとき、Highになります。
  • INT Pin - FDCが割り込み要求(IR)を送信すると、High(1)になります。このラインは、プログラマブル割り込みコントローラ (PIC) のIR6に間接的に接続されています。
  • RW/Seek Pin- リード/ライトモードのシークモードを設定します。1: シークモード,0: リード/ライトモード。
  • LCT/DIR Pin -低電流/方向指定 Pinです。
  • FR/STP Pin -フォルトリセット/ステップ Pin。
  • HDL Pin -Head Load Pin。FDDのRead/Writeヘッドをディスケットに接触させるコマンドです。
  • RDY Pin -レディ Pin。FDDがデータ送受信可能な状態にあることを示す。
  • WP/TS Pin -ライトプロテクト/ツーサイド Pin。リード/ライトモードでは、メディアがライトプロテクトされている場合、Highに設定します。 シークモードでは、メディアが2面ある場合、Highに設定します。
  • FLT/TRK0 Pin -フォルト/トラック0ピン。Read/Write モードの場合、FDDフォルトが検出されるとHighに設定されます。
  • PS0 - PS2 Pin-Precompensation (Pre-shift)ピン。MFMモード時、プリコンペンセーションの状態を書き込みます。
  • WR DATA Pin - ライトデータ Pin
  • RD DATA Pin - 読み出しデータ Pin
  • DS0 - DS1 Pin - ドライブセレクト Pin
  • HDSEL Pin -ヘッドセレクト Pin。High(1)でFDDがヘッド1にアクセスするように設定されます。Lowの時、ヘッド0になります。
  • MFM Pin - Highのとき、FDCはMFMモードになります。Lowの時(0)、FMモードで動作します。
  • WE Pin -ライトイネーブル Pinです。
  • VCO Pin -VCO シンク Pin。0 のとき、PLLVCOを禁止する。1のとき、VCOを有効にします。
  • DW Pin - データウィンドウ Pin。PLLで生成され、FDDからのサンプルデータに使用されます。
  • WR CLK Pin - 書き込みクロック
FDCは、DMA(Direct Memory Access)コントローラの有無にかかわらず、動作させることができます。非DMAモードで動作する場合、プロセッサとFDC間でデータバイトを転送するたびにIRQ 6が発生します。DMAモードでは、プロセッサがFDCにコマンドをロードし、すべてのデータ転送がFDCおよびDMAコントローラの制御下で行われます。

これは重要です!FDCのピンをすべて把握する必要はありません。むしろ、FDCが3つの主要なコントローラと通信していることだけを覚えておいてください。1つは、4つのフロッピーディスクドライブ(FDD)内部コントローラのうちの1つ、プログラマブル割り込みコントローラ(PIC)、ダイレクトメモリアクセス(DMA)コントローラです。 ソフトウェアは、プロセッサの標準IN/OUTポートi/o命令によってFDCと通信をします。

FDCのいくつかのレジスタは、プロセッサのi/oアドレス空間にマッピングされています。標準的なI/Oポート読み取りと同様に、入出力操作の間、プロセッサはコントロールバスのREADまたはWRITEラインと、アドレスバスのポートアドレスを設定します。これは、システムバスまたはISA(Industry Standard Architecture)バスで行われます。

新しいハードウェアでは、FDCはISAバスに直接接続されておらず、スーパーI/O ICとして統合されており、スーパーI/Oのローピンカウントバスを通じてプロセッサと通信する。

なるほど!ソフトウェアがどのようにFDCと通信できるかはわかった。PICとDMAはどこで登場するのでしょうか?

上のピンリストを見てみると、FDCはINTというピンを持っていることがわかります。このラインは、プログラマブル割り込みコントローラIR 6ラインに間接的に接続されています。FDCは、1バイトのデータが読み書きできるようになると、このラインをハイ(1)に引き上げます。これはPICのIR 6ラインもHighに引きます。ここからはPICが制御を行う。他のラインをマスクして、サービス可能かどうかを判断します。プロセッサの割り込みアクノレッジ(INTA)ピンをアクティブにすることで、プロセッサに割り込みを通知します。プロセッサは、割り込みを処理しても安全であることを確認した後、INTAラインをリセットして、PICに処理を許可します。PICは、このIRQが使用するようにマッピングされた割り込みベクタを配置します(PICの初期化中に設定されます)。プロセッサはIRQを受け取り、idtrからそのアドレスを取得し、ほら - 割り込みが呼ばれた。

FDCは、DMAモードで動作するようにプログラムすることもできます。DMAはまだ見ていないコントローラなので、あまり深く立ち入らないようにします。しかし、次の章では、完全を期すために、このことについて触れるかもしれません。FDCはDMAのチャンネル2に接続されています。

FDCのハードウェアはこれで全部です。FDCは1つのシステム内に複数個あり、1つのFDCは最大4台のフロッピーディスクドライブ(FDD)を接続することができます。これは重要なことです。FDCと通信する際、どのFDDを要求しているかを選択しなければならないことがよくあります。

フロッピーインターフェイスケーブル

FDCとFDDの通信は、Western Digital社から進化したIDE(Integrated Drive Electronics)ケーブルとも呼ばれるPATA(Parallel ATA)ケーブルの一種であるフロッピーインターフェースケーブルで行われる。

上のケーブルにねじれがあることにお気づきでしょうか。それを少し短く説明します。このケーブルには40本のピンがあります。この40本のピンを通じて、FDCはケーブルに接続された異なるFDDと会話することができます。

FDCと通信するためのいくつかのレジスタで、コントローラとケーブルの入力ピンを検出することができます。このため、ケーブルの40本のラインを少し見るくらいはしておいた方がよさそうだ。

Floppy Interface Cable Pins
Pin Description Pin Descripton
0 Reset 20 DDRQ
1 Ground 21 Ground
2 Data pin 7 22 I/O Write
3 Data pin 8 23 Ground
4 Data pin 6 24 I/O Read
5 Data pin 9 25 Ground
6 Data pin 5 26 IOCHRDY
7 Data pin 10 27 Cable Select (CS)
8 Data pin 4 28 DDACK
9 Data pin 11 29 Ground
10 Data pin 3 30 Interrupt
11 Data pin 12 31 (No connection)
12 Data pin 2 32 Address 1
13 Data pin 13 33 GPIO_DMA66_Detect
14 Data pin 1 34 Address 0
15 Data pin 14 35 Address 2
16 Data pin 0 36 Chip Select 1
17 Data pin 15 37 Chip Select 3
18 Ground 38 Activity
19 Key or Vcc_in 39 Ground

FDCプログラミング

FDCの動作モード

最近のFDCは、8272マイコンより高機能なものが多くなっています。後方互換性を確保するため、新しいFDCはコントローラにピンを追加し、特定のモードで動作するときに異なるレジスタと通信できるようにします。例えば、ステータスレジスタAモードは、コントローラがPC-ATモードで動作しているときのみアクセス可能です。コントローラのリセット時には、コントローラはデフォルトの82077Aモードで動作します。

IRQの待ち受け

FDCがIRQ 6を使用していることを思い出してください。FDCは、リードまたはライト・コマンドの完了後、あるいはモードによっては1バイトの転送ごとに、1バイトを送信します。また、初期化中にコントローラがリセットされたときにもIRQを送ります。

今回は、FDCをDMAモードで動作させます。つまり、リード、ライト、シーク、キャリブレーションの各コマンドが完了したときと、初期化中にのみ割り込みが入るということです。

しかし、いずれの場合も、コマンドの完了を知るためにIRQが発火するのを待つ必要があることを意味します。これを行うには、IRQが発生したときにグローバルをセットし、 IRQを待つirq_waitのような関数を提供し、それが発生したときにグローバルを リセットするようにすればよい。

では、それをやってみましょう。まずIRQです。

const int FLOPPY_IRQ = 6; //! set when IRQ fires static volatile uint8_t _FloppyDiskIRQ = 0; void _cdecl i86_flpy_irq () { _asm add esp, 12 _asm pushad _asm cli //! irq fired _FloppyDiskIRQ = 1; //! tell hal we are done interruptdone( FLOPPY_IRQ ); _asm sti _asm popad _asm iretd }
これは、PITのIRQと同じくらいシンプルに見えますね?)ああ、そうですか、では、待ちます。
//! wait for irq to fire inline void flpydsk_wait_irq () { //! wait for irq to fire while ( _FloppyDiskIRQ == 0) ; _FloppyDiskIRQ = 0; }
簡単ですね。読み出しや書き込みのようなコマンドを送ったら、flpydsk_wait_irq()を呼び出します。 これが完了したら、コマンドが終了して、続けても安全であることが分かります。クールでしょう?)

DMAですか?

え?DMAモードでFDCをプログラムしているのですか?でも、まだDMAを説明していませんよ。はい、確かにこれは問題です。

当初、FDCはNon-DMAモードでプログラムしようと思っていました。しかし、この方法は場合によっては有効かもしれませんが、多くのエミュレータや一部のハードウェアではサポートされなくなってきています。そこで、ポータビリティを確保するために、DMA(Direct Memory Access Controller [DMAC])を使うのがベストだと判断したのです。

しかし、まだDMAを詳しく説明していないため、問題が発生しました。DMAインターフェース全体を説明なしに投げ出すよりも、3つの基本的なDMAルーチンをハックして、後でもっと詳しく書き直せばいいと思います ;)

flpydsk_initialize_dmaは基本的に DMA が使用するバッファを物理アドレス 0x1000 - 0x10000 (64k) に作成します。 ディスクからセクターを読み込むとき、DMA はセクターデータをこの場所に置くので、上書きされるように何もないことを確認してください。他の場所を選択することも可能ですが、いくつかのルールがあります。

  • バッファは64kの境界を越えてはいけません。最高のパフォーマンスを得るためには、64kの境界に留まる必要があります。
  • 書き込むメモリ領域は ID マップされているか、フレームアドレスがページにマッピン グされている必要があります。DMAは常に物理メモリで動作する
デモでは0x1000 + 64kをバッファに使っているので、もし変更するのが面倒ならこのままにしておくといいでしょう。

dma_readと dma_writeは、FDCが送信したデータの読み取りまたは書き込みを開始するようDMAに指示するだけです。これは、FDCに読み書きを指示したセクタになります。例えば、FDCにセクタを読むように指示すると、DMAにセクタデータを渡して、設定したバッファ(0x1000の位置)に置くように指示します。クールでしょう?

//! initialize DMA to use phys addr 1k-64k void flpydsk_initialize_dma () { outportb (0x0a,0x06); //mask dma channel 2 outportb (0xd8,0xff); //reset master flip-flop outportb (0x04, 0); //address=0x1000 outportb (0x04, 0x10); outportb (0xd8, 0xff); //reset master flip-flop outportb (0x05, 0xff); //count to 0x23ff (number of bytes in a 3.5" floppy disk track) outportb (0x05, 0x23); outportb (0x80, 0); //external page register = 0 outportb (0x0a, 0x02); //unmask dma channel 2 } //! prepare the DMA for read transfer void flpydsk_dma_read () { outportb (0x0a, 0x06); //mask dma channel 2 outportb (0x0b, 0x56); //single transfer, address increment, autoinit, read, channel 2 outportb (0x0a, 0x02); //unmask dma channel 2 } //! prepare the DMA for write transfer void flpydsk_dma_write () { outportb (0x0a, 0x06); //mask dma channel 2 outportb (0x0b, 0x5a); //single transfer, address increment, autoinit, write, channel 2 outportb (0x0a, 0x02); //unmask dma channel 2 }
もしあなたが上記のコードを理解できなくても、心配しないでください。DMAに関することはすべて書き直し、次のチュートリアルでDMAをより詳しく説明します。

FDCポートマッピング

FDCには4つの外部レジスタがあり、i86のI/Oアドレス空間にマッピングされています。これらは、標準的なI/O命令によってソフトウェアからアクセスすることができます。これらのレジスタを太字にしてみました。

システムによっては、FDCに4つ以上の外部レジスタが用意されている場合があります。

2番目のFDCは、通常、I/Oポート0x370~0x377にマッピングされます。

2つの異なるFDCのために2組のポートがあるので、この表は両方のポートセットを含んでいます。

Floppy Disk Controller Ports
Port (FDC 0) Port (FDC 1) Read/Write Descripton
Primary FDC Registers
0x3F2 0x372 Write Only Digital Output Register (DOR)
0x3F4 0x374 Read Only Main Status Register (MSR)
0x3F5 0x375 Read / Write Data Register
0x3F7 0x377 Read Only AT only. Configuation Control Register (CCR)
0x3F7 0x377 Write Only AT only. Digital Input Register (DIR)
Other FDC Registers
0x3F0 0x370 Read Only PS/2 only. Status Register A (SRA)
0x3F1 0x371 Read Only PS/2 only. Status Register B (SRB)
0x3F4 0x374 Write Only PS/2 only. Data Rate Select Register (DSR)

次の章では、このレジスタを少しずつ詳しく見ていくことにします。重要なものについては、この章を更新して、他のレジスタを網羅することになるかもしれません。今のところ、上に示した最初の4つのレジスタにのみ焦点を当てます。

このコードはすべてこの章の最後にあるデモに含まれていることを忘れないでください。

enum FLPYDSK_IO { FLPYDSK_DOR = 0x3f2, FLPYDSK_MSR = 0x3f4, FLPYDSK_FIFO = 0x3f5, //data register FLPYDSK_CTRL = 0x3f7 };

レジスタ

ステータスレジスタA (SRA) (PS2モードのみ)

このレジスタを知る必要はありません。ここにあるのは完全性のためだけです。

本レジスタは、コントローラのインターフェースピンの状態を監視するためのリード専用レジスタです。 コントローラがPC-ATモードの場合は、本レジスタにアクセスできません。本レジスタは読み出し専用です。

このレジスタの正確なフォーマットは、コントローラのモデルによって異なる場合があります。

  • Bit 0 DIR
  • Bit 1 WP
  • Bit 2 INDX
  • Bit 3 HDSEL
  • Bit 4 TRKO
  • Bit 5 STEP Flip/Flop
  • Bit 6 DRV2
  • Bit 7 INTERRUPT line state (interrupt pending)
警告これらのビットは、コントローラーのモデルによって変更される可能性があります。

このレジスタが複雑に見えても心配しないでください。電子工学の経験がなくても大丈夫です。このレジスタは、このシリーズで使用されることはありません。

ステータスレジスタB (SRB) (PS/2モードのみ)

このレジスタを知る必要はありません。このレジスタを知る必要はありません。

上記のレジスタと同様に、FDCの複数のラインの状態を監視することができます。 FDCがPC-ATモードの時はアクセスできません。このレジスタは読み出し専用である。

  • Bit 0 MOT EN0 (Motor Enable 0)
  • Bit 1 MOT EN1 (Motor Enable 1)
  • Bit 2 WE Flip/Flop
  • Bit 3 Read Data (RDDATA) Flip/Flop
  • Bit 4 Write Data (WRDATA) Flip/Flop
  • Bit 5 Drive Select 0
  • Bit 6 Undefined; Always 1
  • Bit 7 Undefined; Always 1
警告これらのビットは、コントローラーのモデルによって変更される可能性があります。

このレジスタが複雑に見えても心配しないでください。電子工学の経験がなくても、そう見えるかもしれません。このレジスタは、このシリーズで使用されることはありません。

データレート選択レジスタ(DSR)

このレジスタを知る必要はありません。このレジスタを知る必要はありません。

このレジスタは書き込み専用で、駆動制御信号のタイミングを変更することができます。I/Oポート0x3f4(FDC0)または0x374(FDC1)に書き込むことで使用します。

これは8ビット・レジスタです。以下のフォーマットで構成されています。

  • Bit 0 DRATE SEL0
  • Bit 1 DRATE SEL1
  • Bit 2 PRE-COMP 0
  • Bit 3 PRE-COMP 1
  • Bit 4 PRE-COMP 2
  • Bit 5 Must be 0
  • Bit 6 POWER DOWN: Deactivates internal clocks and shuts off the internal oscillator
  • Bit 7 S/W RESET: Reset the internal oscillator

PRE-COMP 0 - PRE-COMP 2は少し複雑です。これらは,フロッピーディスクなどの磁気媒体で発生するビットシフトに対してWRDATA出力ピンを調整するものです。事前補償の遅延を調整するために、これらのビットを以下のいずれかに設定します。

  • 000 Default (250-500 Kbps, 125 ns. 1 Mbps, 41.67 ns)
  • 110 250 ns
  • 101 208.33 ns
  • 100 166.67 ns
  • 011 125 ns
  • 010 83.34 ns
  • 001 41.67 ns
  • 111 Disabled

DRATE SEL0~DERATE SEL1でデータレートを設定します。有効な値は以下の通りです。

  • 00 500 Kbps
  • 10 250 Kbps
  • 01 300 Kbps
  • 11 1 Mbps
警告ドライブが処理できる以上のデータレートを設定すると、エラーが発生することがあります。

デジタル出力レジスタ(DOR)

やったー!最初の有用なレジスタです!これは知っておくべき重要なことです。

FDDのモータ制御、動作モード(DMA、IRQ)、リセット、ドライブなど、FDCの様々な機能を制御できる書き込み専用レジスタです。フォーマットは次のとおりです。

  • Bits 0-1 DR1, DR2
    • 00 - Drive 0
    • 01 - Drive 1
    • 10 - Drive 2
    • 11 - Drive 3
  • Bit 2 REST
    • 0 - Reset controller
    • 1 - Controller enabled
  • Bit 3 Mode
    • 0 - IRQ channel
    • 1 - DMA mode
  • Bits 4 - 7 Motor Control (Drives 0 - 3)
    • 0 - Stop Motor for drive
    • 1 - Start Motor for drive
これは簡単です!基本的に、FDCの機能を制御するコマンドを送信するときは、どのドライブに対応するか(1つのFDCで4台のFDDと通信できることを思い出してください)、コントローラーのリセット状態、動作モード(FDCはDMAモードとIRQモードの両方で動作できることを思い出してください)、特定のFDD内部モーターの状態を選択するビットパターンを構築するだけでよいのです。

以下はその例です。例えば、最初のフロッピードライブ(FDD 0)のモーターを始動させたいとします。FDDに対する読み取りまたは書き込み操作を行う前に、FDDのモーターを始動させる必要があります!これを開始するには、モーターを開始または停止したいドライブに対応するビット (4-7) を設定するだけです。DORはプロセッサのi/oアドレス空間であるポート0x3f2にマッピングされているので、これは非常に簡単です。 まず、レジスタのビットマスクを作成し、読みやすさを向上させます。このコードはすべて、このチュートリアルの最後にあるデモに含まれていることを忘れないでください。

enum FLPYDSK_DOR_MASK { FLPYDSK_DOR_MASK_DRIVE0 = 0, //00000000 = here for completeness sake FLPYDSK_DOR_MASK_DRIVE1 = 1, //00000001 FLPYDSK_DOR_MASK_DRIVE2 = 2, //00000010 FLPYDSK_DOR_MASK_DRIVE3 = 3, //00000011 FLPYDSK_DOR_MASK_RESET = 4, //00000100 FLPYDSK_DOR_MASK_DMA = 8, //00001000 FLPYDSK_DOR_MASK_DRIVE0_MOTOR = 16, //00010000 FLPYDSK_DOR_MASK_DRIVE1_MOTOR = 32, //00100000 FLPYDSK_DOR_MASK_DRIVE2_MOTOR = 64, //01000000 FLPYDSK_DOR_MASK_DRIVE3_MOTOR = 128 //10000000 };
上記のビットマスクを使えば、設定したいビットをビットごとにORするだけでよいのです。つまり、フロッピーディスク・ドライブ0用のモーターを始動させる場合です。
outportb (FLPYDSK_DOR, FLPYDSK_DOR_MASK_DRIVE0_MOTOR | FLPYDSK_DOR_MASK_RESET);
FLPYDSK_DORは0x3f2として定義され、これはDOR FDCレジスタのi/oアドレスであることを思い出してください。また、上記はコントローラをリセットしています。

この同じモーターをオフにするには、モータービットを設定せずに同じコマンドを送信するだけです。

outportb (FLPYDSK_DOR, FLPYDSK_DOR_MASK_RESET);

警告警告: モーターが起動するまでに少し時間がかかります!内蔵FDDのモーターは機械的なものであり、他の機械装置と同様に、実行中のソフトウェアの速度より遅くなる傾向があることを思い出してください。このため、FDDモーターを起動するときは、必ず少し時間をおいてから読み書きを行ってください。

DORは書き込み専用のレジスタです。これを強制するために、ルーチンを作成します。

void flpydsk_write_dor (uint8_t val ) { //! write the digital output register outportb (FLPYDSK_DOR, val); }
次の重要なレジスタに移りましょう。

メインステータスレジスタ(MSR)

メインステータスレジスタ(MSR)は、特定のビットフォーマットに従っています。これは予想外だったでしょう?さて、ここで話を元に戻しましょう(シャレです)。これがMSRのフォーマットです。
  • Bit 0 - FDD 0: 1 if FDD is busy in seek mode
  • Bit 1 - FDD 1: 1 if FDD is busy in seek mode
  • Bit 2 - FDD 2: 1 if FDD is busy in seek mode
  • Bit 3 - FDD 3: 1 if FDD is busy in seek mode
    • 0: The selected FDD is not busy
    • 1: The selected FDD is busy
  • Bit 4 - FDC Busy; Read or Write command in progress
    • 0: Not busy
    • 1: Busy
  • Bit 5 - FDC in Non DMA mode
    • 0: FDC in DMA mode
    • 1: FDC not in DMA mode
  • Bit 6 - DIO: direction of data transfer between the FDC IC and the CPU
    • 0: FDC expecting data from CPU
    • 1: FDC has data for CPU
  • Bit 7 - RQM: Data register is ready for data transfer
    • 0: Data register not ready
    • 1: Data register ready
このMSRはシンプルなものです。FDCとディスク・ドライブの現在のステータス情報が含まれています。 コマンドを送信したり、FDDから読み出す前に、FDCの現在のステータスを常にチェックして、準備ができていることを確認する必要があるのです。

ここでは、このMSRからビジー状態かどうかを読み取る例を示します。まず、コードで使用されるビットマスクを定義します。上に示した形式に従っていることに注意してください。

enum FLPYDSK_MSR_MASK { FLPYDSK_MSR_MASK_DRIVE1_POS_MODE = 1, //00000001 FLPYDSK_MSR_MASK_DRIVE2_POS_MODE = 2, //00000010 FLPYDSK_MSR_MASK_DRIVE3_POS_MODE = 4, //00000100 FLPYDSK_MSR_MASK_DRIVE4_POS_MODE = 8, //00001000 FLPYDSK_MSR_MASK_BUSY = 16, //00010000 FLPYDSK_MSR_MASK_DMA = 32, //00100000 FLPYDSK_MSR_MASK_DATAIO = 64, //01000000 FLPYDSK_MSR_MASK_DATAREG = 128 //10000000 };
簡単でしょう?FLPYDSR_MSRが0x3f4で、MSRのi/oポート・アドレスであることが分かっていれば、あとはこれを実行すればいいだけです。
if ( inportb (FLPYDSK_MSR) & FLPYDSK_MSR_MASK_BUSY ) //! FDC is busy
リードやライトのコマンドを送るときは、このビットが0になるまで待てばいいだけです。

読みやすくするために、これをルーチンに隠すことにしましたので、ここに紹介します。このルーチンは、FDCのステータスを返すだけです。

uint8_t flpydsk_read_status () { //! just return main status register return inportb (FLPYDSK_MSR); }

テープドライブレジスタ(TDR)

このレジスタを知る必要はありません。このレジスタを知る必要はありません。

このレジスタにより、ドライブの初期化中に特定のドライブにテープ・ドライブ・サポートを割り当てることができます。このレジスタはリード/ライト・レジスタで、8ビット・サイズです。 ただし、最初の2ビットだけが定義されています。ドライブ0はフロッピーブートデバイスとして予約されているため、選択することはできません。このため、以下のビットリストには含まれていません。

  • 00: None.
  • 01: Drive 1
  • 10: Drive 2
  • 11: Drive 3
ハードウェア・リセットのみがこのレジスタをリセットします。ソフトウェア・リセットは何の効果もありません。テープドライブのことをあまり知らなくても心配しないでください。 このレジスタは私たちには関係ありませんし、このシリーズでは使用しません。 ここでは、念のためだけです。

データ・レジスタ

これは8ビットまたは16ビットのリード/ライト・レジスタです。レジスタの実際のサイズは、コントローラの種類によって異なります。すべてのコマンド・パラメーターとディスク・データの転送は、このデータ・レジスタに読み書きされます。このレジスタは、特定のビット・フォーマットに従わず、一般的なデータ用に使用されます。I/Oポート0x3f5(FDC 0)または0x375(FDC 1)を通じてアクセスします。

注:このレジスタを読み書きする前に、まずマスター・ステータス・レジスタ(MSR)のステータスを読んで、このレジスタが有効であることを常に確認する必要があります。

覚えておいてください。すべてのコマンド・バイトとコマンド・パラメータは、このレジスタを通じてFDCに送信されます。 この例については、以下のコマンドのセクションで説明しますので、まだあまり気にしないでください。

無効なコマンドが発行された場合、データ・レジスタから返される値は0x80です。

以下のルーチンは、このレジスタから読み出し、デモで使用しています。データ・レジスタが安全に読み書きできるようになるまで待ち、その後、読み出し(read_data関数)または書き込み(send_command関数)を行うようにしています。

void flpydsk_send_command (uint8_t cmd) { //! wait until data register is ready. We send commands to the data register for (int i = 0; i < 500; i++ ) if ( flpydsk_read_status () & FLPYDSK_MSR_MASK_DATAREG ) return outportb (FLPYDSK_FIFO, cmd); } uint8_t flpydsk_read_data () { //! same as above function but returns data register for reading for (int i = 0; i < 500; i++ ) if ( flpydsk_read_status () & FLPYDSK_MSR_MASK_DATAREG ) return inportb (FLPYDSK_FIFO); }

デジタル入力レジスタ(DIR)

このレジスタを知る必要はありません。このレジスタを知る必要はありません。

さて、デジタル出力レジスタ(DOR)があったので、このレジスタが来ることは予想できたと思います :)このレジスタは、コントローラのすべての動作モードで読み取り専用です。PC-ATモードでは、ビット7のみが定義され、他のビットは未定義であり、使用しないでください。他の動作モードでは、Bit7は未定義です。

ビット7(DSK CHG)は、FDCのDSK CHGピンをモニタします。本章の冒頭のピン配置を見ると,DSK CHG 端子がないことが分かります。これは、FDCの新モデルとオリジナル・モデルの違いに関係しています。新しいモデルは、DMA GATE、DRATE SEL0/1など、FDCの新しいピンを監視するために、このレジスタの異なるビットを追加および変更しました。このレジスタの値は、FDCの動作モードに固有です。

このレジスタのビットは、モデル間で変更される可能性があることに注意してください。

コンフィギュレーション制御レジスタ(CCR)

PC/ATモードでは、このレジスタはデータレートセレクトレジスタ(DSR)として知られ、最初の2ビット(ビット0=DRATE SEL0、ビット1=DRATE SEL1)だけが設定されます。もう一度見てみましょう。
  • 00 500 Kbps
  • 10 250 Kbps
  • 01 300 Kbps
  • 11 1 Mbps

ビット2は、30型/CCRモードではNOPRECとなり、機能はありません。その他のビットは未定義で、コントローラによって変更される可能性があります。

他のレジスタと同様に、このレジスタに書き込みができるようにルーチンを作りました。

void flpydsk_write_ccr (uint8_t val) { //! write the configuation control outportb (FLPYDSK_CTRL, val); }

コマンド

概要

コマンドは、FDCに接続されたFDDを制御し、読み出しや書き込みなどのさまざまな操作を行うために使用されます。つまり、ポート0x3f5(FDC 0)または0x375(FDC 1)のデータ・レジスタにOUTアセンブリ言語命令を書き込むのです。

警告コマンドまたはパラメータ・バイトを送信する前に、まずメイン・ステータス・レジスタ(MSR)のビット 7 をテストして、データ・レジスタがデータを受信する準備ができていることを確認してください。

コマンドは13種類(コントローラによってはそれ以上)あります。各コマンドは1バイトから9バイトの大きさです。FDCは、最初のコマンド・バイトから、何バイトを期待すればよいかを知っています。つまり、最初のバイトは、FDCに何をして欲しいかを伝える実際のコマンドです。FDCは、このコマンドからさらに何バイトを期待すればよいかを知っています(コマンド・パラメーター)。

コマンドはトラックの片方のヘッドにしか作用しません。もし両方のヘッドで動作させたい場合は、Multiple Track Bitを設定する必要があります。これらのコマンドの多くはビットフォーマットに従います(後述)。ここがややこしいところです。

コマンドバイトの上位ビットは、コマンドのオプション設定用です。私はこれを拡張コマンドビットと呼んでいますが、正式な名称はありません。これらのビットのうち、私たちが使用する多くのコマンドに共通するビットが2つあります。これらのビットは、後でコマンドバイトの中で見ていきます。

さて、まずコマンドリストを見てみましょう。次に、それぞれのコマンドを個別に見ていきます。これらはすべてコマンドバイトの最初の4ビットしか使っていないことに注意してください。

enum FLPYDSK_CMD { FDC_CMD_READ_TRACK = 2, FDC_CMD_SPECIFY = 3, FDC_CMD_CHECK_STAT = 4, FDC_CMD_WRITE_SECT = 5, FDC_CMD_READ_SECT = 6, FDC_CMD_CALIBRATE = 7, FDC_CMD_CHECK_INT = 8, FDC_CMD_WRITE_DEL_S = 9, FDC_CMD_READ_ID_S = 0xa, FDC_CMD_READ_DEL_S = 0xc, FDC_CMD_FORMAT_TRACK = 0xd, FDC_CMD_SEEK = 0xf };
FDCにコマンドを送信するには、データ・レジスタ(別名FIFO)に書き込む必要があります。 これを行うには、まずMSRのビットをチェックして、データ・レジスタの準備ができるのを待つ必要があります。flpydsk_read_status ()はMSRからの値を返すだけなので、これをすべてシンプルなメソッドの中に隠してしまいましょう。
void flpydsk_send_command (uint8_t cmd) { //! wait until data register is ready. We send commands to the data register for (int i = 0; i < 500; i++ ) if ( flpydsk_read_status () & FLPYDSK_MSR_MASK_DATAREG ) return outportb (FLPYDSK_FIFO, cmd); }

拡張コマンドビット

これらのコマンドのいくつかは、コマンドを実行する前に数バイトを渡す必要があります。 また、数バイトを返すものもあります。読みやすくするために、すべてのコマンド、フォーマット、パラメータ・バイトを表にまとめました。各コマンドには説明とサンプルルーチンが付いています。

さて、拡張コマンド・ビットの話をしたときに、上のコマンドが4ビットしかないことを思い出してください。上位4ビットは、さまざまな用途に使用することができます。

例えば、Write Sectorコマンドは、M F 0 0 0 1 1 0という形式で、最初の4ビット(0 1 1 0)がコマンドバイト、上位4ビット(M F 0 0)が異なる設定を表します。Mはマルチトラック、Fはコマンドに対してどのような密度モードで動作させるかを選択するために設定されています。

以下、共通ビットの一覧です。

  • M - MultiTrack Operation
    • 0: Operate on one track of the cylinder
    • 1: Operate on both tracks of the cylinder
  • F - FM/MFM Mode Setting
    • 0: Operate in FM (Single Density) mode
    • 1: Operate in MFM (Double Density) mode
  • S - Skip Mode Setting
    • 0: Do not skip deleted data address marks
    • 1: Skip deleted data address marks
  • HD - Head Number
  • DR0 - DR1 - Drive Number Bits (2 bits for up to 4 drives)
M、F、Sビットは多くのコマンドに共通なので、それらをうまく列挙することにしました。これらを設定するには、これらの設定と使用したいコマンドをビット単位でORするだけです。
enum FLPYDSK_CMD_EXT { FDC_CMD_EXT_SKIP = 0x20, //00100000 FDC_CMD_EXT_DENSITY = 0x40, //01000000 FDC_CMD_EXT_MULTITRACK = 0x80 //10000000 };

GAP 3

GAP 3は、物理ディスクのセクタ間のスペースを指します。GPL (Gap Length) の一種です。
enum FLPYDSK_GAP3_LENGTH { FLPYDSK_GAP3_LENGTH_STD = 42, FLPYDSK_GAP3_LENGTH_5_14= 32, FLPYDSK_GAP3_LENGTH_3_5= 27 };
コマンドによっては、GAP 3のコードを渡す必要があるため、それを使用します:)。

セクタあたりのバイト数

コマンドによっては、セクタあたりのバイト数を渡す必要があります。しかし、これらは任意のサイズにすることはできず、常に式に従います。
2^n * 128, where ^ denotes "to the power of"
nは0〜7の数字です。FDCではセクタあたり16Kバイトまで選択することができます。しかし、ほとんどのドライブはそれをサポートしていないかもしれません。

我々のリストには、最も一般的な

enum FLPYDSK_SECTOR_DTL { FLPYDSK_SECTOR_DTL_128 = 0, FLPYDSK_SECTOR_DTL_256 = 1, FLPYDSK_SECTOR_DTL_512 = 2, FLPYDSK_SECTOR_DTL_1024 = 4 };
...ですから、もしコマンドがセクタあたりのバイト数を渡すことを要求してきたら、512と書いてはいけません!むしろ、FLPYDSK_SECTOR_DTL_512と書いて、これは2です。

コマンドにパラメータを渡す方法

思い起こせば、多くのコマンドはパラメータを渡すことを要求しています。パラメータを渡すには、コマンドが送られたのと同じように送ればよいのです。例えば、specifyコマンドは、2つのパラメータを渡す必要があります。これがないとコマンドは起動しませんので...
flpydsk_send_command (FDC_CMD_SPECIFY); flpydsk_send_command (data); flpydsk_send_command (data2);
これが全てです。)

コマンドから戻り値を取得する方法

返り値を無視できるプログラミングの関数と違って、FDCは返り値を何らかの形で処理する必要があります。もちろん、無視することもできますが、FDCから返り値をもらわなければなりません。それが終わらないと、FDCはそれ以上のコマンドを許可しません。

コマンドがデータを返す場合は、FIFO(データ・レジスタ)に--1つずつ--返されます。 したがって、それらを読むには、FIFOを継続的に読み込んで、返されたデータをすべて取得する必要があります。

注:コマンドがデータを返す場合、割り込みを送信しますので、それを待つ必要があります。これは、コマンドが終了し、FIFOから戻り値を読んでも安全であることを知るための方法です。

戻り値の良い例は、セクタの読み出しコマンドです。このコマンドでは、IRQを待つ必要があるので、完了したことがわかり、7バイトが返されます。そこで、返されたデータ・バイトをすべて読むために、データ・レジスタから一度に1つずつ読み出す必要があります。

for (int j=0; j<7; j++) flpydsk_read_data ();
もちろん、エラーチェックのために、いくつかの返り値は実際にチェックする必要があります。

セクタを書き込む

  • Format: M F 0 0 0 1 1 0
  • Paramaters:
    • x x x x x HD DR DR0
    • Cylinder
    • Head
    • Sector Number
    • Sector Size
    • Track Length
    • Length of GAP3
    • Data Length
  • Return:
    • Return byte 0: ST0
    • Return byte 1: ST1
    • Return byte 2: ST2
    • Return byte 3: Current cylinder
    • Return byte 4: Current head
    • Return byte 5: Sector number
    • Return byte 6: Sector size

FDDからセクタを読み出すコマンドです。セクタ内の1バイトごとにFDCは割り込み6を発行し、ディスクから読み出したバイトをデータレジスタに入れ、読み込めるようにします。

セクタの読み出し

  • Format: M F S 0 0 1 1 0
  • Paramaters:
    • x x x x x HD DR1 DR0 = HD=head DR0/DR1=Disk
    • Cylinder
    • Head
    • Sector Number
    • Sector Size
    • Track Length
    • Length of GAP3
    • Data Length
  • Return:
    • Return byte 0: ST0
    • Return byte 1: ST1
    • Return byte 2: ST2
    • Return byte 3: Current cylinder
    • Return byte 4: Current head
    • Return byte 5: Sector number
    • Return byte 6: Sector size

FDDからセクタを読み出すコマンドです。セクタ内の1バイトごとにFDCは割り込み6を発行し、ディスクから読み込んだバイトをデータレジスタに入れ、読み込めるようにします。

以下は、デモで使用したルーチンです。まず、DMAをセットアップして、読み出し動作の準備をします。次に、セクタ読み出しコマンド(FDC_CMD_READ_SECT)を実行し、コマンドのM、F、Sビットを設定します(マルチトラック・リード、倍密度、削除済みアドレス・マークをスキップします。これらの一覧は上記を参照してください)。

その後、コマンドのパラメータを全て渡し、読み出しコマンドを開始します。セクタサイズパラメータはFLPYDSK_SECTOR_DTL_512 (bytes per sector)で、これは思い起こせば値2です(詳細は上記のBytes per sectorの項を参照してください)。 次のパラメータはトラックあたりのセクター(18)。 次のパラメータはGAP3長です。標準的な3-1/2 "フロッピーディスクのGAP 3長(FLPYDSK_GAP3_LENGTH_3_5、27)の値を渡します。

Data Lengthパラメータ・バイトはセクタ・サイズが0の場合のみ有効です。 それ以外の場合は 0xff となります。

このコマンドは完了後にIRQを送信するため、IRQを待つ必要があります。

void flpydsk_read_sector_imp (uint8_t head, uint8_t track, uint8_t sector) { uint32_t st0, cyl; //! set the DMA for read transfer flpydsk_dma_read (); //! read in a sector flpydsk_send_command ( FDC_CMD_READ_SECT | FDC_CMD_EXT_MULTITRACK | FDC_CMD_EXT_SKIP | FDC_CMD_EXT_DENSITY); flpydsk_send_command ( head << 2 | _CurrentDrive ); flpydsk_send_command ( track); flpydsk_send_command ( head); flpydsk_send_command ( sector); flpydsk_send_command ( FLPYDSK_SECTOR_DTL_512 ); flpydsk_send_command ( ( ( sector + 1 ) >= FLPY_SECTORS_PER_TRACK ) ? FLPY_SECTORS_PER_TRACK : sector + 1 ); flpydsk_send_command ( FLPYDSK_GAP3_LENGTH_3_5 ); flpydsk_send_command ( 0xff ); //! wait for irq flpydsk_wait_irq (); //! read status info for (int j=0; j<7; j++) flpydsk_read_data (); //! let FDC know we handled interrupt flpydsk_check_int (&st0,&cyl); }
...IRQが発生した後、7つのリターンバイトをすべて読み込んでいます。そして、SENSE_INTERRUPTコマンドをflpydsk_check_int()で送信し、FDCに割り込みを処理したことを知らせます。(以下の割り込みの状態をチェックするセクションを参照してください)

待てよ...。データはどこにあるのでしょうか?上のコマンドを見ると、FDCにデータをどこに置くか教えていません。 これは、面白い問題を提起していると思いませんか?

FDCの動作モードにもよりますが、Non-DMAモードでは、1バイトごとにIRQ 6が発火します。ディスクから読み込んだデータのバイトはFIFOにあります。DMAモードでは、DMAにデータを渡し、DMAはそのデータをバッファ(DMAに指示した場所)に入れる。

つまり、この場合、DMAバッファを0x1000に設定したことを覚えていますか?上記のルーチンを呼び出した後、セクタデータは0x1000になります!クールでしょう?DMAに別のアドレスを与えることで、その位置を変更することができます。

ドライブデータの修正/指定

  • Format: 0 0 0 0 0 0 1 1
  • Paramaters:
    • S S S S H H H H - S=Step Rate H=Head Unload Time
    • H H H H H H H NDM - H=Head Load Time NDM=0 (DMA Mode) or 1 (DMA Mode)
  • Return: None

このコマンドは、FDC に接続されているメカニカル・ドライブの制御情報を FDC に渡すために使用されます。このコマンドでの作業を容易にするために、このコマンドのためのルーチンを書きましょう。

void flpydsk_drive_data (uint32_t stepr, uint32_t loadt, uint32_t unloadt, bool dma ) { uint32_t data = 0; flpydsk_send_command (FDC_CMD_SPECIFY); data = ( (stepr & 0xf) << 4) | (unloadt & 0xf); flpydsk_send_command (data); data = (loadt) << 1 | (dma==true) ? 1 : 0; flpydsk_send_command (data); }

ステータス確認

  • Format: 0 0 0 0 0 1 0 0
  • Paramaters:
    • x x x x x HD DR1 DR0
  • Return:
    • Byte 0: Status Register 3 (ST3)

このコマンドは、ドライブのステータスを返します。

ドライブのキャリブレート

  • Format: 0 0 0 0 0 1 1 1
  • Paramaters:
    • x x x x x 0 DR1 DR0
  • Return: None

このコマンドはリード/ライト・ヘッドをシリンダー0に配置するために使用されます。80トラック以上のディスクの場合、このコマンドを数回発行する必要があります。このコマンドを発行した後は、必ず正しいトラックであることを確認すること(Check Interrupt Statusコマンド)。

コマンドを発行した後、まだシリンダー0に到達していない場合は、再度コマンドを発行する。シリンダ0を見つけたら、モータを停止させ、successを返す。10回やってダメならやめる。

このコマンドの実行中は、モーターが動いていることを確認する必要があることに注意してください。また、SENSE_INTERRUPTコマンド(flpydsk_check_int()コール)を使って、現在のシリンダを取得している点にも注目。

int flpydsk_calibrate (uint32_t drive) { uint32_t st0, cyl; if (drive >= 4) return -2; //! turn on the motor flpydsk_control_motor (true); for (int i = 0; i < 10; i++) { //! send command flpydsk_send_command ( FDC_CMD_CALIBRATE ); flpydsk_send_command ( drive ); flpydsk_wait_irq (); flpydsk_check_int ( &st0, &cyl); //! did we fine cylinder 0? if so, we are done if (!cyl) { flpydsk_control_motor (false); return 0; } } flpydsk_control_motor (false); return -1; }

インタラプトステータス確認

  • Format: 0 0 0 0 1 0 0 0
  • Paramaters: None
  • Return:
    • Byte 0: Status Register 0 (ST0)
    • Byte 1: Current Cylinder

このコマンドは、割り込みが復帰したときの FDC の状態に関する情報を確認するために使用します。

void flpydsk_check_int (uint32_t* st0, uint32_t* cyl) { flpydsk_send_command (FDC_CMD_CHECK_INT); *st0 = flpydsk_read_data (); *cyl = flpydsk_read_data (); }

シーク/パークヘッド

  • Format: 0 0 0 0 1 1 1 1
  • Paramaters:
    • x x x x x HD DR1 DR0 - HD=Head DR1/DR0 = drive
    • Cylinder
  • Return: None

このコマンドは、リード/ライト・ヘッドを特定のシリンダーに移動させるために使用されます。calibrateコマンドと同様に、このコマンドを複数回送信する必要がある場合があります。check_int()を呼び出すことで、毎回現在のシリンダーを取得していることに注意。そして、現在のシリンダーが探しているシリンダーであるかどうかをテストします。そうでない場合は、もう一度試行する。もしそうであれば、成功を返します。

int flpydsk_seek ( uint32_t cyl, uint32_t head ) { uint32_t st0, cyl0; if (_CurrentDrive >= 4) return -1; for (int i = 0; i < 10; i++ ) { //! send the command flpydsk_send_command (FDC_CMD_SEEK); flpydsk_send_command ( (head) << 2 | _CurrentDrive); flpydsk_send_command (cyl); //! wait for the results phase IRQ flpydsk_wait_irq (); flpydsk_check_int (&st0,&cyl0); //! found the cylinder? if ( cyl0 == cyl) return 0; } return -1; }

無効なコマンド

無効なコマンドがFDCに送信された場合、FDCはそれを無視してスタンバイ状態になります。

FDCをリセットする

コントローラの無効化

DOR RESETラインがLowであれば、コントローラはディスエーブル状態になります。つまり、DORレジスタに0を書き込むだけで、コントローラはディセーブルになります。
void flpydsk_disable_controller () { flpydsk_write_dor (0); }

コントローラの有効化

コントローラをイネーブルにするには、DORのRESETラインをHighに設定します。また、FDCをDMAモードで動作させたいので、そのビットもDORに設定する必要があります。
void flpydsk_enable_controller () { flpydsk_write_dor ( FLPYDSK_DOR_MASK_RESET | FLPYDSK_DOR_MASK_DMA); }
コントローラを無効化した後に有効化すると、割り込みが発生します。この間、コントローラやドライブのコンフィギュレーションを再初期化する必要があります。

FDCの初期化

コントローラのリセット中、コントローラを再初期化する必要があります。コントローラのリセット後、IRQ6が発行されます。発火後、FDCに接続されているすべてのドライブにSENSE_INTERRUPTコマンドを送信する必要があります(flpydsk_check_intを4回呼び出すことで行います)。

その後、コントローラを再設定する時間があります。CCR(構成制御レジスタ)はデータ・レートのために2ビットしかないことを思い出してください。両方を0に設定することで、データレートを500Kbpsに設定します。これは素晴らしいデフォルト値です。

次にflpydsk_drive_dataを呼び出し、Fix Drive Data / Specifyコマンドをコントローラに送信して、次のようなドライブの機械的情報を設定します。ステップ速度、ヘッドのロードとアンロードの時間、DMAモードをサポートしているかどうかなどです。

その後、ドライブを再キャリブレーションし、シリンダー0に配置します。

void flpydsk_reset () { uint32_t st0, cyl; //! reset the controller flpydsk_disable_controller (); flpydsk_enable_controller (); flpydsk_wait_irq (); //! send CHECK_INT/SENSE INTERRUPT command to all drives for (int i=0; i<4; i++) flpydsk_check_int (&st0,&cyl); //! transfer speed 500kb/s flpydsk_write_ccr (0); //! pass mechanical drive info. steprate=3ms, unload time=240ms, load time=16ms flpydsk_drive_data (3,16,240,true); //! calibrate the disk flpydsk_calibrate ( _CurrentDrive ); }
リセットの後、ドライブは私たちが使用できる状態になります。

更新と変更

文字列からint型への変換 - stdio.h/stdio.cpp

このデモをよりインタラクティブにするために、標準ライブラリにある文字列を整数に変換するための3つの関数を組み込みました。このデモでは、ユーザーから入力された文字列を使用可能な整数に変換するために、atoiを使用しています。

フロッピーディスクドライバのインストール - flpydsk.cpp

フロッピーディスクドライバには、デモが簡単にセットアップできるように、すばらしいインストールルーチンが付属しています。このルーチンは、HAL の setvect () ルーチンを使って割り込みハンドラをインストールし、転送用の DMA を初期化し、コントローラをリセットして使用可能な状態にするだけです。
void flpydsk_install (int irq) { //! install irq handler setvect (irq, i86_flpy_irq); //! initialize the DMA for FDC flpydsk_initialize_dma (); //! reset the fdc flpydsk_reset (); //! set drive information flpydsk_drive_data (13, 1, 0xf, true); }
デモでは、初期化中にこの関数を呼び出し、ドライバから読み込もうとする前にドライバをセットアップしています。

任意のセクタを読み込む - LBAとCHS - flpydsk.cpp

ドライバは、2つのすばらしい関数の背後にCHSの詳細を隠します。ドライブはCHS(シリンダ/ヘッド/セクタ)で動作し、LBA(リニアブロックアドレッシング)については何も知らないことを知っているので、この2つの間で変換するルーチンを提供する必要があります。この方法では、物理的な CHS を気にすることなく、単にセクタ番号を渡して読み書きすることができます。

LBAをCHSに変換する公式を覚えていますか?ここでそれを応用してみましょう。

void flpydsk_lba_to_chs (int lba,int *head,int *track,int *sector) { *head = ( lba % ( FLPY_SECTORS_PER_TRACK * 2 ) ) / ( FLPY_SECTORS_PER_TRACK ); *track = lba / ( FLPY_SECTORS_PER_TRACK * 2 ); *sector = lba % FLPY_SECTORS_PER_TRACK + 1; }
FLPY_SECTORS_PER_TRACKは18です。素晴らしい!これで、この関数を呼び出すだけで、任意のリニア・セクタ番号をCHSの位置に変換できるようになりました!クールでしょう?

ディスクから任意のセクタを読み込めるようにしたいので、そのためのルーチンを提供します。 また、コントローラに読み込みコマンドを送信するコードを含むflpydsk_read_sector_impが既にあるので、このルーチンは非常にシンプルになります。

uint8_t* flpydsk_read_sector (int sectorLBA) { if (_CurrentDrive >= 4) return 0; //! convert LBA sector to CHS int head=0, track=0, sector=1; flpydsk_lba_to_chs (sectorLBA, &head, &track, �or); //! turn motor on and seek to track flpydsk_control_motor (true); if (flpydsk_seek (track, head) != 0) return 0; //! read sector and turn motor off flpydsk_read_sector_imp (head, track, sector); flpydsk_control_motor (false); //! warning: this is a bit hackish return (uint8_t*) DMA_BUFFER; }
デモがセクタを読みたいときはいつでも、このルーチンを呼び出します。このルーチンはセクタをディスク上の物理的な位置(CHS)に変換します。そして、モーターを起動し、このセクタがあるシリンダを探します。その後、flpydsk_read_sector_impを呼び出して実際にセクタを読み込むマジックを行い、その後モータをオフにします。

flpydsk_read_sector_imp の呼び出しの後、セクタのデータは DMA バッファにあるはずです。 このバッファへのポインタを返すと、バッファには今読み込んだセクタのデータが入っています。クールでしょう?

これはすべてを結びつける魔法のルーチンです。)

新しい読み込みコマンド - main.cpp

このデモは前回のデモの上に構築されています。このため、前回のデモで構築されたコマンドライン・インターフェイス(CLI)を維持しています。このため、このデモは今までで最も複雑なデモになっています。

CLI のコマンドのリストに、ディスクから任意のセクタを読み込むことができるreadという新しいコマンドを追加しました。このコマンドは、このチュートリアルで作成したフロッピーディスクドライバを使用しています。

このコマンドは関数の中にあり、デモではreadとタイプすることで実行されます。これは512バイトを読みやすいように128バイトの4つのブロックに分割してダンプします。各ブロックの後、次のチャンクに進むためにキーを押すように促されます。これは新しいatoi関数を使って、入力されたセクタ番号(LBAセクタ番号)をintに変換し、それを読み込んでいます。親愛なる読者の皆さん、これが魔法を起こす関数なのです。

void cmd_read_sect () { uint32_t sectornum = 0; char sectornumbuf [4]; uint8_t* sector = 0; DebugPrintf ("\n\rPlease type in the sector number [0 is default] >"); get_cmd (sectornumbuf, 3); sectornum = atoi (sectornumbuf); DebugPrintf ("\n\rSector %i contents:\n\n\r", sectornum); //! read sector from disk sector = flpydsk_read_sector ( sectornum ); //! display sector if (sector!=0) { int i = 0; for ( int c = 0; c < 4; c++ ) { for (int j = 0; j < 128; j++) DebugPrintf ("0x%x ", sector[ i + j ]); i += 128; DebugPrintf("\n\rPress any key to continue\n\r"); getch (); } } else DebugPrintf ("\n\r*** Error reading sector from disk"); DebugPrintf ("\n\rDone."); }

まとめ

イヤー、これは長いチュートリアルです。より良く、より完全なものにするために、いくつか変更を加えるかもしれません :)

次回のチュートリアルでは、DMAについて説明します。DMAをプログラミングするためのインターフェイスを作成し、FDCドライバでよりよく使用できるようにします。この後...またファイルシステムの話になると思います(笑)。心配しないでください - そのあとはマルチタスクです!