FreeBSDのtarg

[古いPCのSCSIデバイス問題](https://qiita.com/yamori813/items/cb3f53a6847457b4c0d2)でRaSCSIとかありますが、結構高価なのでなにかもっと安上がりな方法がないか調べていたところFreeBSDにもSCSIのターゲットサポートがあることに気がつきました。

これはtargというデバイスでサポートされる仕組みで、実際の処理はユーザランドで動きます。man pageを見るとこのサポートはFreeBSD 3の頃からあるようです。

Sean BrunoさんのBSDCan2009の発表の資料で見つけました。

ユーザランドのサポートプログラムは、 /usr/share/examples/scsi_targetにあります。このプログラムがファイルをSCSIボリュームとして見せかけます。

結構期待して調べたのですが、targをサポートしているSCSIインターフェースはahc(Adaptec:sys/dev/aic7xxx)とisp(Qlogic:sys/dev/isp)だけのようです。同じsys/dev/aic7xxxにあるahdは作りかけのようです。後から気がついたのですが、mpt(LSI:sys/dev/mpt)でもサポートしているようです。

またsys/dev/ipsでgrep TARGET_MODE * | wc -lすると53行もありますし中身もずいぶんあるようです。

http://www.root.org/~nate/freebsd/scsi/cam-target.html

CAM - Common Access Method
SIM - SCSI Interface Modules
CCB - CAM Control Block
ATIO - ccb_accept_tio
INOT - ccb_immediate_notify
CTIO - ccb_scsiio

FreeBSDのSCSIシステムはCAMという抽象化レイヤーが存在します。それが実際のSCSIコマンドになってターゲットに渡り処理が行われています。targはこの逆変換を行っているようです。SCSIコマンドで飛んできたイベントをCAMにしてユーザープロセスに渡して処理を行うような流れになるようです。このため実際のSCSIの処理よりも複雑に見えます。

CAMはOSFでDECの人が作った仕様がANSIになっているようです。ANSIのドキュメントは有料ですが、OSFの仕様はネットで拾えます。

ドキュメントには簡単にできるような事書いてありますが、結構な力技です。

SCSIはホストのinitiatorとデバイスのtargetがありますが、それぞれ複数接続したり、initiatorがtargetになることも可能な仕様でした。ところがパソコンでの利用が進んでホスト一台とデバイスが固定されるようになりました。

初期のSCSIチップのNCR5380は簡単な処理しかせずinitiatorでもtargetでも使えるような仕様でした。それが時間とともにinitiator機能に特化したりtargetはASICに取り込まれたりで、状況が変わってしまいました。

このためtargが実現できるホストアダプターはかなり限られてしまっているのかもしれません。

ホストアダプターのタイプは

* イニシエーターにしかなれない
* イニシエーターかターゲットのどちらか固定
* 同じIDでイニシエーターとターゲットを同時に両方
* 別々のIDでイニシエーターとターゲットを同時に両方（おそらく一般の製品では無いと思います）

だと思われます。イニシエーターとターゲットを同時に両方にサポートする作りこみも可能かもしれませんが、現在はSCSI以外のストレージが主流になっているので、ターゲット専用にしたほうが簡単にできる気がします。

実装方法は以下のようになります。

* ターゲット専用にする
* 開かれたときだけターゲット専用になる
* イニシエーター＆ターゲット両用で動作（現実的じゃないかも）

targの問題点を上げると

* 対応ドライバーが少ない
* 既存ドライバーへの修正が大規模
* ターゲットになれてもチップの仕様がさまざま
* 参考にできるターゲットの実装がほとんどない
* CAMを熟知していないと難しい
* 参照実装が複雑で不適当
* ahc,ispともにLinuxからのポートをベースにしていて醜い
* ahc,ispのTARGET_SUPPORTの実装で名前の付け方が違う
* ispはパラSCSIとファイバーチャンネルのサポートがあるようで複雑で醜い
* ahcはワイド(16bit) SCSIで複雑で醜い
* ahdが未完成のまま放置
* CAMが難解
* 略語がどの名前空間のものか意味不明
* CAMはANSIなのだが、ドキュメントが売り物でfreeではない

参照実装は一番単純な例にすべきですが、ahc,ispはそもそも複雑な上に複数のイニシエーターとLUNをサポートしていてなんだかよくわからなくなっています。参照実装は単一のイニシエーターとLUNが適当だと思います。

岡目八目ですが、

isp/isp_target.cの中でifdef ISP_TARGET_MODEがネストしてます。

isp_freebsd.cの中でATIOがatio7でATIO2がatio2と名前に一貫性がありません。

```c
                case RQSTYPE_ATIO:
                        isp_handle_platform_atio7(isp, (at7_entry_t *) hp);
                        break;
                case RQSTYPE_ATIO2:
                        isp_handle_platform_atio2(isp, (at2_entry_t *) hp);
                        break;
```

CAM_REQ_CMPの代入は必要ないって言うか、そもそもxpt_done(ccb);だけで良いです。

```c
        if (ccb->ccb_h.status != CAM_REQ_CMP) {
                xpt_done(ccb);
                return;
        }

        ccb->ccb_h.status = CAM_REQ_CMP;
        xpt_done(ccb);
}
```

ahcのコードはifdefで切られていない部分もあり、参照実装として極めて不適切です。

着想としては非常に面白と思ったのですが、そもそも多種多様なSCSIドライバを単純化するための抽象化レイヤーを逆に使っているので、やっぱりちょっと無理があるような気がします。

camの実装はcam_sim_alloc()で処理ルーチン(hoge_action)を登録して、そのルーチンでイベント(xpt)とパラメーター(ccb)を受けて処理を行いxpt_done()で構造体を返すのが基本のようです。

targは/dev/targをopenしてread,write,ioctl,kqueueで処理を行います。ioctlでターゲットを有効にして、読み込みはkqueueで待ってreadして書き込みはwriteします。

データは以下のように流れる。

![DIR.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104066/f87298f3-ff61-4e7b-2db1-e4e454dccda1.png)


ところがユーザーランドからカーネルにccbのコピーしている、cam_periph.cのcam_periph_mapmem()ではCAM_DIR_INの場合にはbzero()の処理になる。これだとデーターが空になる。なんでだろう？（バグでした）


FreeBSDではSCSI targetのシュミレーションをするとctlという別のソリューションもあるようです。ctlはカーネル内で処理するようです。FreeBSD 9.1からサポートされている新しいもののようです。