このファイルではフロッピーディスクコントローラーを使用して フロッピーからデータを読み出したり書き込んだりする。 まず、フロッピーディスクコントローラーに関して少し説明する。
まず、フロッピーディスクコントローラーはCPUとデータのやりとりするために いくつかのレジスタを持っている。これらのレジスタはI/Oポートからアクセスでき、 それぞれ次のアドレスでアクセスできる。
デジタル出力レジスタ | 0x03F2 |
メインステータスレジスタ | 0x03F4 |
データレジスタ | 0x03F5 |
次に、FDCに対するコマンドの実行経過について説明する。 一般にFDCがコマンドを実行すると次のような3つの段階を経る。
まず、コマンドフェーズではコマンドに必要なパラメータを FDCに転送する。その次に実行フェーズでFDCが指定されたコマンドを実行する。 最後にFDCから実行結果をリザルトフェーズで受け取る、という形になる。
例として、DMA転送を伴わないrecalibrateコマンドを見ていく。 (といってもリザルトフェーズがない。3つの実行フェーズをそなえていて、 DMAを用いず、わかりやすいコマンドはなかなかない) このコマンドは読み出し/書き込みヘッドをシリンダ0の位置に持っていくコマンドである。
まず、コマンドとを設定してsendCommand関数でコマンドを転送する。この関数にも仕掛けがあって きちんとコマンドが転送できるときに転送するようにしている。
bool FDCDriver::recalibrate() { byte command[] = {0x07, 0x00}; /* recalibrate */ interrupt_ = false; if (!sendCommand(command, sizeof(command))){ info(ERROR, "FDCDriver#recalibrate:command fail\n"); return false; } |
そして、コマンドの転送が終わって実行が終わったら割り込みが入ることになっている。 それまでwaitInterrupt()関数で待つようにしていて、割り込みが入るとこの関数から 返ってくる。そして、senseInterrupt()関数でSENSE INTERRUPT STATUSコマンドをFDC に転送してこの処理を終える。この処理はヘッドの位置を確定するためにも必須の処理で、 このコマンドを送らないと次のコマンドが実行できなかったりする。 そう言えば、waitStatus(0x10, 0x00)は何をやっているんだろう?
while (true) { waitInterrupt(false); waitStatus(0x10, 0x00); if (senseInterrupt()) break; interrupt_ = false; } return true; } |
次に、FDCDriverクラスの内部構造について説明する。
FDCDriverクラスが作成されたときinitialize()関数が呼び出される。 この関数の中ではまず、SPECIFYコマンドを送信するためのデータを設定している。 このコマンドではSRT(ステッピングレート:ヘッドを次のシリンダに移す時間間隔)、 HUT(ヘッドアンロードタイム:ヘッドを読み出し/書き込み位置から元の位置に戻す時間)、 HLT(ヘッドロードタイム:ヘッドを元の位置から読み出し/書き込み位置に置く時間)、 DMAを用いるか否かを設定する。
void FDCDriver::initilize() { byte specifyCommand[] = {FDC_COMMAND_SPECIFY , 0xC1 /* SRT = 4ms HUT = 16ms */ , 0x10 /* HLT = 16ms DMA */ }; |
つぎに、DMA用のバッファーを確保している。バッファーは物理メモリの先頭から 16MBまでの位置になければならない。
/* allocate dma buffer */ dmabuff_ = (byte*)malloc(FDC_DMA_BUFF_SIZE); /* dma buff should be 64kb < dma buff > 16Mb */ if (!dmabuff_ || (dword)dmabuff_ < 64 * 1024 || (dword)dmabuff_ + FDC_DMA_BUFF_SIZE > 16 * 1024 * 1024) { panic("dma buff allocate error"); } |
そして、現在のスレッド情報をメンバ変数に保存している。
/* default wait thread */ waitThread = g_currentThread->thread; |
次にDMA関係の初期化を行う。 0xDA, 0x0Dに書き込むとMaster Clear命令が実行される。 この命令はハードウエアリセットを行い、 ステータス、リクエスト、テンポラリレジスタ、および、 内部First/Lastフリップフロップがクリアされる。また、マスクレジスタは 全てセットされる。
outp8(0xda, 0x00); delay(1); outp8(0x0d, 0x00); delay(1); |
0xD0, 0x08に書き込むとCommand Registerの内容を変更できる。 このレジスタの内訳は各種資料によって違っていたりする。
outp8(0xd0, 0x00); delay(1); outp8(0x08, 0x00); delay(1); |
0xD6, 0x0Bに書き込むとモードレジスタの内容を変更できる。 チャンネル4-7のコントローラーに対しては0xC0 = 11000000b という値を設定している。これはチャンネル4をチャンネル0-3に カスケード接続することを設定している。 チャンネル0-3のコントローラーに対しては0x46 = 01000110b という値を設定している。これはチャンネル2の設定を シングル転送モード・メモリへの書き込みにしている。
outp8(0xd6, 0xc0); delay(1); outp8(0x0b, 0x46); delay(1); |
マスクレジスタの設定
outp8(0xd4, 0x00); delay(1); |
DMA関係の初期化が終わればFDCを初期化する。 まず、デジタル出力レジスタ(DOR)にリセット信号を送る。
/* reset drive */ outp8(FDC_DOR_PRIMARY, FDC_DOR_RESET); |
つぎに、コンフィギュレーションコントロールレジスタ(CCR)を初期化する。 これでデータレートなどを設定できる。しかし、値が0なのでデフォルトの値のままである。
delay(1); outp8(FDC_CCR_PRIMARY, 0); |
次にモーターをオンにする。
motor(ON); |
コマンドを転送して結果を待つ。
/* specify */ if (!sendCommand(specifyCommand, sizeof(specifyCommand))) { info(ERROR, "Specify command failed\n"); motorAutoOff(); return; } |
モーターをオフにして返る。
motorAutoOff(); return; } |
読み出し処理のための関数はいろいろあるが、最終的にはCHS指定で読み出す関数が呼び出される。 この関数は読み出しのためにDMA転送を用いていて少しややこしい形になっている。 全体的な処理内容は次のとおり。
まず、ここでFDCに転送するコマンドを決めている。特に、3から5バイト目で読み込み場所を 設定し、6バイト目の0x02という数字で読み込みサイズを512バイトと設定している。
bool FDCDriver::read(byte track, byte head, byte sector) { byte command[] = {FDC_COMMAND_READ , (head & 1) << 2 , track , head , sector , 0x02 , 0x12 // EOT osask(0x7e, 0x01, 0xff) , 0x1B // GSL , 0xFF // DTL vmware hate 0x00 }; |
読み込みをする前にヘッドを目的の場所にシークする。FDCによっては読み込み時に自動的に シークしてくれるものもあるが、ここでは明示的にシークを行っている。
if (!seek(track)) { info(ERROR, "read#seek:error"); return false; } |
DMAの設定を行っている。上の項目の2から4つ目の処理を行っている。
setupDMARead(512); |
DMAの設定が終われば今度はコマンドを転送している。
interrupt_ = false; if (!sendCommand(command, sizeof(command))) { info(ERROR, "read#send command:error\n"); return false; } |
コマンドを転送し終えたら割り込みが入るまで待っている。但し、割り込みが来るまで 時間がかかるのでその間スレッドの実行を停める様にしている。
waitInterrupt(true); // delay(30000); |
割り込みが来てスレッドの実行が再開されるとリザルトフェーズに入る。 ここで、7バイト分のデータを読み込む。このデータの中には実行結果も 含まれている。
for (int i = 0; i < 7; i++) { results_[i] = getResult(); } |
フロッピーのDMAを使用停止にする。
stopDMA(); // g_console->printf("status=%x", results_[0]); // g_console->printf("%s", ((results_[0] & 0xC0) != 0x00) ? "true":"false"); |
ここで、ステータスレジスタ(コマンドの実行結果に関するレジスタ)の上位2ビットの値を みる。この2ビットはコマンドの実行結果の成否を表わしており、0x00なら正常に終了 したことを表わす。もし、0x00でなければ正常に終了したことにならないのでfalseを返す。
if ((results_[0] & 0xC0) != 0x00) return false; return true; } |
DMAでの設定(データの流れの向き)と転送するコマンドが異なるだけなのでここでは省略する。 (ただ単に面倒くさくなっただけかも)
コマンドフェーズではコマンドバイトを書き込み、リザルトフェーズではリザルトバイトを読み込む 必要がある。これらのバイトデータはデータレジスタにアクセスすることで読み書きできるが、 データレジスタにアクセスする前にメインステータスレジスタを見てデータが読み込めるか、 書き込めるかということを検証しなければならない。その検証をしているのがsendCommand()関数と getResult()関数である。
この関数はパラメータで指定されたコマンドをデータレジスタに転送する関数である。 データレジスタに書き込む前にはメインステータスレジスタの上位2ビットが10となっている 事を確認しなければならない。この確認作業がwaitStatus()関数である。これを確認した後 データレジスタにコマンドを書き込むようにしている。
bool FDCDriver::sendCommand(const byte* command, const byte length) { /* send command */ for (int i = 0; i < length; i++) { waitStatus(0x80 | 0x40, 0x80); /* send command */ outp8(FDC_DR_PRIMARY, command[i]); } return true; } |
この関数はリザルトバイトを1バイトずつ受け取っていく。このとき、データを受け取る前に メインステータスレジスタの上位4ビットが1101となっていることを確認する。 (データの方向を見るだけなら上位2ビットが11となっていることを確認すればいいと思うが・・・)
byte FDCDriver::getResult() { waitStatus(0xd0, 0xd0); return inp8(FDC_DR_PRIMARY); } |
割り込みを待つ関数はwaitInterrupt()関数である。この関数は割り込みが入るまで 待ち続け、割り込みが入ると返るようになっている。引数にfalseを指定すると ループして割り込みを待つが、trueを指定するとスレッドを実行停止状態にして待つ。 トレースして、実効を見ていくのは面倒くさいけどね(笑)
void FDCDriver::waitInterrupt(bool yield) { setWaitThread(g_currentThread->thread); if (yield) { int result; SYSCALL_0(SYSTEM_CALL_WAIT_FDC, result); } while (!interrupt_); } |