NCR53C500のドライバ

targ用にPCMCIAカードのNCR53C500のドライバを作ってみました。既存のtarg対応のドライバは複雑で何をする必要があるのかわかり難いので、できるだけ簡潔に処理して、targ対応実装の参考にできるコードにしたつもりです。

https://github.com/yamori813/esp-targ

残念ながらNCR53C500のデーターシートは手に入らなかったので、NCR53C96などのデータシートを参考にしました。

このチップはNCR53C90系のチップですが拡張されたFIFO(PIOとも呼ばれます)があります。このサイズは128バイトで送受信別々にあります。

FIFOはPCIでDMAが使えるようになる前の技術で、ディスクのような大量のデータを扱うインターフェースでは今日使われることはありません。

FIFOはFirst In First Outの略です。下記の図では読み出すと左のデータが出てきます。

![fifo3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104066/42cad2ca-312b-b446-6ef4-c66160180c78.png)

フルになる前に読み出しを行わないと、オーバーランになりデータが失われます。

![fifo2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104066/5747d67b-751b-6dac-33de-851b17d27a01.png)

NCR93C500はレジスタセットがset 0とset 1の二つ有りPIOはset 1でアクセスできます。NCR53C96などはset 0だけでset 1へのアクセスはncvのコードを参考にしました。

NCR53C90由来の16バイトのFIFOもあります。このためPIOが空やフルになっても16バイトのFIFOが空やフルになるまではオーバーランやアンダーランにはなりません。

![FIFO128.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104066/27762f00-7916-b954-42d9-5720bdf2df50.png)

なので十分に早いCPUがビジーループでPIOを読み書きする場合は空やフルをチェックして処理するので十分です。

NCR53C500はFreeBSDではncvというデバイスドライバでサポートされていました。このドライバはFreeBSD/PC-9801由来のものでした。しかしespにもNCR53C9x系のドライバがあり、またNetBSDはespでNCR53C500をサポートしていたので、NetBSDのコードやncvのコードをespにマージして込みこまれるようにしました。

もはやイニシエータとして使うことはないので、ターゲット専用で実装します。

FreeBSDのドライバは各バスの処理のコードとコアの処理のファイルに分かれています。espの場合、SUNのバスのためのesp_sbus.cやPCIバスのためのesp_pci.cや今回追加したesp_pccard.cが前者で、ncr53c9x.cが後者になります。

SCSIのターゲットの受信データはイニシエーター（ホスト）から見ると書き込みになりNCRCMD_RECDATAコマンドで処理します。送信は読み込みになりNCRCMD_SNDDATAで処理します。

NCRCMD_RECDATAの場合はコマンドを発行してからPIOを読み出しますが、NCRCMD_SNDDATAはPIOに書き込んでからコマンドを発行する必要がありました。

![FIFO_RS.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104066/a84a3a84-c724-beda-e772-33c222e42712.png)

送信するINQUERYなどの情報はすべて128バイト以下で128バイトを越えるデータは実際のディスクのデータでセクターの大きさの512バイトの倍数になります。

この点を利用してNCRCMD_SNDDATAでの事前書き込みは128バイトかそれ以下になり、それ以降の処理やNCRCMD_RECDATAの処理はすべて128バイト単位として処理するようにしました。

1セクター分の512バイトのデータのNCRCMD_RECDATAの場合の4回分のPIOのフルで処理すればいいのですが、なぜか最後のフルがとれなかったので1バイト余分にサイズを指定して処理するようにしました。

あとPIOの状態はNCR_PSTATで確認できるのですが、READ/WRITEどちらのPIOの状態なのか明示的な指示がなく、ちょっと不思議仕様です。おそらく発行されたコマンドによって変わるような気もします。

ターゲットとしてイニシエータからのセレクトを受け取るにはNCRCMD_ENSELコマンドを発行する必要があります。このコマンドは一回限り有効なので処理が終わった後に再度発行する必要があります。

NCR53C500ではSCSI IDは一つしか設定できないので、複数のIDのセレクトを受ける事はできません。

SCSIでは一つのIDに複数のLUNを持てるのですが、複雑になるので一つしかサポートしません。

セレクトは割り込みで確認できその時にCDBがFIFOに入っているのでそれをtargにxpt_done()で投げればユーザーランドまで届きます。

ユーザランドで処理されて戻ってきたCCBはデーターフェーズを処理するケースと処理しないケースがあります。この時処理しないケースでは、NCRCMD_TERMSEQを発行してバスを開放します。このコマンドの処理が終わると、NCRINTR_DIS | NCRINTR_FCで割り込みが上がるので、NCRCMD_ENSELを再発行しておきます。

データーフェーズを処理する場合はPIOを読んだり書いたりします。

ある程度動くようになったのですが、PIOがエラーを起こしてとても不安定な状態でした。

ビジーループで想定外の状況で無限ループになるとカーネルが固まってしまうので、エラーケースや回数などでログを出して抜けるようにするのがいいです。

あたり前田のクラッカーですが、ビジーループの無限ループはカーネルのブレイクもできません。

たまたまUSBスピーカーでストリーミングを流しながら確認したところ、PIOのエラーでほとんど使えなくなり、割り込みが影響してるという事に気がつきました。

考えてみるとPIOを処理している最中に他の処理の割り込みで遅延が発生するとオーバーランやアンダーランを発生させてしまうことがあるようです。このためPIOの処理の最中は割り込み禁止にしました。

intr_disable()とintr_restore()で割り込み禁止、解除しています。他のドライバーコードを見るとあまり使っているものはなく、本来は使うべきではないのかもしれませんが、FIFOという性質上しかたないと思われます。

NCR53C500の元もとのドライバのncvはPIOの読み書きを割り込みルーチン内でおこなっていたので、実質的に割り込み禁止の状態でした。

PIOの読み書きが終わるとNCRINTR_FC割り込みが上がるので、そこでNCRCMD_TERMSEQコマンドを発行して、その後のNCRINTR_DIS | NCRINTR_FCで割り込みは上と同じ処理になります。

もともとイニシエーター用に書かれたコードでしたがターゲットで使うコマンドもちゃんとdefineされていて、助かりました。

targサポートの実装はそれぞれのチップ毎に違ってくると思います。PCIなどでDMAが使える環境であれば、より効率よく処理できると思います。targetになるれるチップであれば、おおよそこのコードと同じ流れで処理できるのではないでしょうか。

PC-9801FA2のSCSIディスクとして使っていて、MS-Windows 3.1のインストールなども問題なく行えるようになり、安定してます。