|
|
1.1 root 1: //
2: // nono
1.1.1.3 root 3: // Copyright (C) 2020 nono project
4: // Licensed under nono-license.txt
1.1 root 5: //
6:
1.1.1.14 root 7: //
8: // SCSI デバイス
9: //
10:
1.1.1.11 root 11: // ログレベルは
12: // 1: SCSI コマンドの概要。
13: // デバイスの動作のうち大きな変更のもの
14: // 2: SCSI コマンドの詳細、ダンプ。
15: // デバイスの動作のうち些細なもの、あるいは頻繁なもの
16: // 3: フェーズ遷移
17: // 4: データも含める
1.1 root 18:
1.1.1.11 root 19: #include "scsicmd.h"
1.1 root 20: #include "scsidev.h"
1.1.1.20! root 21: #include "scsidomain.h"
1.1.1.2 root 22: #include "config.h"
23: #include "mainapp.h"
1.1.1.14 root 24: #include "scheduler.h"
1.1.1.11 root 25: #include "uimessage.h"
1.1 root 26:
27: //
1.1.1.11 root 28: // SCSI デバイス共通部分
1.1 root 29: //
30:
31: // コンストラクタ
1.1.1.18 root 32: SCSIDevice::SCSIDevice(uint objid_)
1.1.1.16 root 33: : inherited(objid_)
1.1 root 34: {
1.1.1.18 root 35: myid = -1;
1.1 root 36: }
37:
38: // デストラクタ
39: SCSIDevice::~SCSIDevice()
40: {
41: }
42:
1.1.1.20! root 43: // バスに接続する。
1.1.1.18 root 44: void
1.1.1.20! root 45: SCSIDevice::Attach(SCSIBus *bus_)
1.1.1.18 root 46: {
47: bus = bus_;
48: }
49:
1.1.1.14 root 50:
1.1 root 51: //
1.1.1.18 root 52: // SCSI ホストデバイス (イニシエータ)
1.1 root 53: //
54:
55: // コンストラクタ
1.1.1.20! root 56: SCSIHostDevice::SCSIHostDevice(SCSIDomain *domain_, IODevice *parent_)
! 57: : inherited(OBJ_NONE)
1.1 root 58: {
1.1.1.20! root 59: domain = domain_;
1.1.1.18 root 60: parent = parent_;
61: // イニシエータの ID は実行時にコントローラによって決まる。
1.1.1.5 root 62: devtype = SCSI::DevType::Initiator;
63:
1.1.1.20! root 64: SetLogLevel(parent->loglevel);
1.1 root 65: }
66:
67: // デストラクタ
68: SCSIHostDevice::~SCSIHostDevice()
69: {
70: }
71:
1.1.1.20! root 72: // SCSI ID を設定する。
! 73: // (SCSIDomain からのみ呼ぶこと)
! 74: void
! 75: SCSIHostDevice::SetID(uint id_)
1.1 root 76: {
1.1.1.20! root 77: myid = id_;
1.1 root 78: }
79:
1.1.1.20! root 80: // SCSIBus からのコールバック関数を設定する。
1.1 root 81: void
1.1.1.20! root 82: SCSIHostDevice::SetBusCallback(
1.1.1.18 root 83: BusFreeCallback_t busfree_,
84: SCSIHostCallback_t selack_,
85: SCSIHostCallback_t xferreq_)
86: {
87: BusFreeCallback = busfree_;
88: SelectionAckCallback = selack_;
89: TransferReqCallback = xferreq_;
90: }
91:
92: // バスフリーになった (SCSIBus から呼ばれる)
93: void
94: SCSIHostDevice::OnBusFree(uint id)
1.1 root 95: {
96: // 実際にはバスフリーを検出したデバイスは各自自身が送出している信号を
97: // 下げるのだが、色々面倒なので、ここではホストのバスフリーコールバックが
98: // 一括して全部の線を下げる。
99: // ただしさすがにログがうるさいので、上がってる線だけ下げる。
100:
101: uint32 data;
102: while ((data = GetBSY()) != 0) {
1.1.1.18 root 103: bus->NegateBSY(SCSIBus::DecodeID(data));
1.1 root 104: }
105: while ((data = GetSEL()) != 0) {
1.1.1.18 root 106: bus->NegateSEL(SCSIBus::DecodeID(data));
1.1 root 107: }
108: if (GetREQ()) {
109: NegateREQ();
110: }
111: if (GetACK()) {
112: NegateACK();
113: }
114: if (GetATN()) {
115: NegateATN();
116: }
117: SetXfer(0);
118: SetData(0);
1.1.1.18 root 119:
120: // 親のバスフリーコールバックを呼ぶ
121: (parent->*(BusFreeCallback))(id);
1.1.1.20! root 122:
! 123: // RST が見えてる状態で親に通知はしたので、ここで下ろす。
! 124: if (GetRST()) {
! 125: NegateRST();
! 126: }
1.1 root 127: }
128:
1.1.1.18 root 129: // ターゲットがセレクションに応答した (SCSIBus から呼ばれる)
1.1.1.11 root 130: void
1.1.1.18 root 131: SCSIHostDevice::OnSelectionAck()
1.1 root 132: {
1.1.1.18 root 133: // ターゲットが BSY を立てて応答したので、
134: // こちらはデータバスをクリアして SEL を下げる。
135: // これによりセレクションフェーズは成功で完了する。
136: SetData(0);
137: NegateSEL();
1.1.1.7 root 138:
1.1.1.18 root 139: // 親のバスフリーコールバックを呼ぶ
140: (parent->*(SelectionAckCallback))();
141: }
1.1.1.7 root 142:
1.1.1.18 root 143: // ターゲットが REQ を立てた (SCSIBus から呼ばれる)
144: void
145: SCSIHostDevice::OnTransferReq()
146: {
147: (parent->*(TransferReqCallback))();
1.1.1.11 root 148: }
1.1 root 149:
150:
151: //
152: // SCSI ターゲットデバイスの共通部分
153: //
154:
1.1.1.16 root 155: // コンストラクタ。
1.1.1.20! root 156: SCSITarget::SCSITarget(SCSIDomain *domain_, uint id)
1.1.1.16 root 157: : inherited(OBJ_NONE) // オブジェクト ID で検索することはない
1.1 root 158: {
1.1.1.20! root 159: domain = domain_;
1.1 root 160: myid = id;
161: }
162:
163: // デストラクタ
164: SCSITarget::~SCSITarget()
165: {
166: }
167:
1.1.1.9 root 168: // 電源オン
1.1.1.14 root 169: void
170: SCSITarget::ResetHard(bool poweron)
1.1.1.9 root 171: {
1.1.1.14 root 172: if (poweron) {
173: ClearSense();
174: cmdseq.clear();
175: cmdlen = 0;
176: cmd.reset();
177: bytecount = 0;
178: lastbyte = false;
179: }
1.1.1.9 root 180: }
181:
1.1.1.4 root 182: // バスリセットされた (SCSIBus から呼ばれる)
183: void
1.1.1.18 root 184: SCSITarget::OnBusReset()
1.1.1.4 root 185: {
1.1.1.11 root 186: // センスキーをクリア
187: ClearSense();
188:
1.1.1.5 root 189: // リセットされたので持ってる信号を全部解放。
190: // 一応自分が変えていい信号線だけを落とす。
191: switch (GetPhase()) {
192: case SCSI::Phase::BusFree:
193: break;
194: case SCSI::Phase::Arbitration:
195: case SCSI::Phase::Selection:
196: case SCSI::Phase::Reselection:
197: NegateSEL();
198: NegateBSY();
199: break;
200: case SCSI::Phase::Transfer:
201: NegateBSY();
202: break;
203: }
1.1.1.4 root 204: }
205:
1.1 root 206: // セレクションで選択された (SCSIBus から呼ばれる)
207: void
1.1.1.18 root 208: SCSITarget::OnSelected()
1.1 root 209: {
210: // ターゲットが、自身が選択されていることを認識してから BSY を
211: // 立てるまでの時間はもうバス側でカウントしてあるので、ここでは
212: // ただちに BSY を上げてよい。
1.1.1.18 root 213: putlog(4, "OnSelected");
1.1 root 214: AssertBSY();
215: }
216:
217: // 情報転送フェーズを開始する (SCSIBus から呼ばれる)
218: void
1.1.1.18 root 219: SCSITarget::OnStartTransfer()
1.1 root 220: {
221: // 最初はコマンドフェーズ。(ATN はとりあえず放置)
222: putlog(4, "Start Command Phase");
223: cmdseq.clear();
224: cmdlen = 0;
225: SetXfer(SCSI::XferPhase::Command);
226: AssertREQ();
227: }
228:
229: // 情報転送フェーズ間の遷移について
230: //
1.1.1.18 root 231: // 情報転送フェーズは OnTransfer() で1バイトずつ転送を行い、所定のバイト数
1.1 root 232: // の転送が完了すればフェーズを遷移する。遷移方法は入出力方向別で6通り。
233: // o IN -> IN
234: // o IN -> OUT
235: // o IN -> END
236: // o OUT -> OUT
237: // o OUT -> IN
238: // o OUT -> END
239: //
1.1.1.18 root 240: // IN -> IN の遷移。OnTransfer() で前フェーズの最終バイト受信後、フェーズを
1.1 root 241: // 変更して REQ を立てるところまで。
242: //
1.1.1.18 root 243: // IN -> OUT の遷移。OnTransfer() で前フェーズの最終バイト受信後、フェーズを
1.1 root 244: // 変更して (おそらく {Phase}Begin() を発行してから)、データをセットし REQ
245: // を立てる?
246: //
1.1.1.18 root 247: // IN -> END の遷移。OnTransfer() で前フェーズの最終バイト受信後、BSY を
1.1 root 248: // 下げて終了。
249: //
250: // OUT -> OUT の遷移。前フェーズの最終バイトをイニシエータが受信後に
1.1.1.18 root 251: // OnTransfer() が呼ばれるので、フェーズを変更して (おそらく {Phase}Begin()
1.1 root 252: // を発行してから)、データをセットし REQ を立てる?
253: //
254: // OUT -> IN の遷移。前フェーズの最終バイトをイニシエータが受信後に
1.1.1.18 root 255: // OnTransfer() が呼ばれるので、フェーズを変更して (おそらく {Phase}Begin()
1.1 root 256: // を発行してから)、REQ を立てる?
257: //
258: // OUT -> END の遷移。前フェーズの最終バイトをイニシエータが受信後に
1.1.1.18 root 259: // OnTransfer() が呼ばれるので、BSY を下げる?
1.1 root 260:
261: // 情報転送フェーズでイニシエータが ACK を上げた。(SCSIBus から呼ばれる)
262: // OUT (イニシエータ → ターゲット方向) なら、ターゲットが上げた REQ に応答
263: // してイニシエータがデータをデータバスに乗せてからこれをコールしてくる。
264: // IN (ターゲット → イニシエータ方向) なら、ターゲットがデータをデータバスに
265: // 乗せて REQ を上げたのをイニシエータが受信した後でこれをコールしてくる。
266: void
1.1.1.18 root 267: SCSITarget::OnTransfer()
1.1 root 268: {
269: uint8 data;
270: SCSI::XferPhase xfer;
271:
272: // このフェーズの最終バイトか
273: lastbyte = false;
274:
275: // このフェーズの1バイト処理
276: xfer = GetXfer();
277: if (xfer == SCSI::XferPhase::Command) {
278: // コマンドフェーズだけいろいろ特殊なので別処理。
279: // 受信バッファが異なる、1バイト目で受信長が決まるなど。
280: data = GetData();
1.1.1.18 root 281: putlog(4, "%s: cmdseq[%02u] = $%02x", __func__,
282: (uint)cmdseq.size(), data);
1.1 root 283: cmdseq.push_back(data);
284: if (cmdseq.size() == 1) {
285: // 1バイト目ならこのコマンドが何バイトか調べる
286: switch (cmdseq[0] >> 5) {
287: case 0: cmdlen = 6; break;
288: case 1: cmdlen = 10; break;
289: case 2: cmdlen = 10; break;
290: case 5: cmdlen = 12; break;
291: default:
292: // 3,4 はリサーブ、6,7 はベンダー定義。XXX 来たらどうする?
293: cmdlen = 6;
294: break;
295: }
296: } else if (cmdseq.size() >= cmdlen) {
1.1.1.11 root 297: if (loglevel >= 2
298: #if defined(NO_READWRITE_LOG)
299: && (cmdseq[0] != SCSI::Command::Read6 &&
300: cmdseq[0] != SCSI::Command::Read10 &&
301: cmdseq[0] != SCSI::Command::Write6 &&
302: cmdseq[0] != SCSI::Command::Write10)
303: #endif
304: ) {
1.1 root 305: std::string cmdbuf;
306: const char *name = SCSI::GetCommandName(cmdseq[0]);
307: cmdbuf += string_format("Command \"%s\"", name ?: "?");
308: for (const auto& v : cmdseq) {
1.1.1.11 root 309: cmdbuf += string_format(" %02x", v);
1.1 root 310: }
1.1.1.8 root 311: putlogn("%s", cmdbuf.c_str());
1.1 root 312: }
1.1.1.20! root 313: // 最終バイトを受信したらコマンドを選択。
1.1 root 314: lastbyte = true;
1.1.1.20! root 315: SelectCommand();
! 316: if ((bool)cmd == false) {
! 317: std::string namebuf;
! 318: const char *name = SCSI::GetCommandName(cmdseq[0]);
! 319: if (name) {
! 320: namebuf = std::string(name);
! 321: } else {
! 322: namebuf = string_format("$%02x", cmdseq[0]);
! 323: }
! 324: putlog(0, "Failed to initialize command %s", namebuf.c_str());
! 325: AssertRST();
! 326: }
1.1 root 327: }
328: } else if (GetIO() == false) {
329: // OUT 方向 (ターゲットが受信側)
330: // bytecount は減算方向。
331: cmd->buf.push_back(GetData());
332: bytecount--;
333: if (bytecount == 0) {
334: lastbyte = true;
335: }
336: } else {
337: // IN 方向 (ターゲットが送信側)
338: // bytecount は加算方向。
1.1.1.18 root 339: // データは前のループの終わり (OnTransferAck()の下のほう) で
1.1 root 340: // セットしているのでここでは引き取られた分をカウントする。
341: bytecount++;
342: if (bytecount >= cmd->buf.size()) {
343: lastbyte = true;
344: }
345: }
346:
347: NegateREQ();
348: }
349:
350: // 情報転送フェーズでイニシエータが ACK を下げた。(SCSIBus から呼ばれる)
351: void
1.1.1.18 root 352: SCSITarget::OnTransferAck()
1.1 root 353: {
354: SCSI::XferPhase xfer = GetXfer();
355:
356: // これが最終バイトだったら、フェーズ完了 → フェーズ切り替え。
357: if (lastbyte) {
358: SCSI::XferPhase next;
359: // フェーズ完了
360: switch (xfer) {
361: case SCSI::XferPhase::Command:
1.1.1.20! root 362: next = cmd->ExecCommand(cmdseq);
1.1 root 363: break;
364: case SCSI::XferPhase::DataOut:
365: next = cmd->DoneDataOut();
366: break;
367: case SCSI::XferPhase::DataIn:
368: next = cmd->DoneDataIn();
369: break;
370: case SCSI::XferPhase::MsgOut:
371: next = cmd->DoneMsgOut();
372: break;
373: case SCSI::XferPhase::MsgIn:
374: next = cmd->DoneMsgIn();
375: break;
376: case SCSI::XferPhase::Status:
377: next = cmd->DoneStatus();
378: break;
379: default:
1.1.1.18 root 380: PANIC("%s unknown xfer done %d", __func__, (int)xfer);
1.1 root 381: }
1.1.1.11 root 382: putlog(3, "%s done", SCSI::GetXferPhaseName(xfer));
1.1 root 383:
1.1.1.5 root 384: // 簡略化のため、フェーズ完了時だけ REQ を引き伸ばすことができる
385: if (cmd->optime != 0) {
386: bus->SetOneshotReqWait(cmd->optime);
387: cmd->optime = 0;
388: }
389:
1.1 root 390: if (next != xfer) {
391: // End ならバスフリーにして終了。
392: if (next == SCSI::XferPhase::End) {
1.1.1.20! root 393: cmd.reset();
1.1 root 394: NegateBSY();
395: return;
396: }
397:
398: // (必要なら)フェーズを更新
399: SetXfer(next);
400: xfer = next;
401:
402: // 新しいフェーズの開始処理
403: // XXX ここで bytecount とかをリセットできればいいのだが
404: // Command フェーズで Data フェーズの準備をすることが多いので
405: // 悩ましい。
406: switch (xfer) {
407: case SCSI::XferPhase::DataOut:
408: cmd->buf.clear();
409: bytecount = cmd->recvbytes;
1.1.1.18 root 410: putlog(3, "DataOut begin (%u bytes)", bytecount);
1.1 root 411: break;
412: case SCSI::XferPhase::DataIn:
413: bytecount = 0;
1.1.1.18 root 414: putlog(3, "DataIn begin (%u bytes)", (uint)cmd->buf.size());
1.1 root 415: break;
416: case SCSI::XferPhase::MsgOut:
417: cmd->BeginMsgOut();
418: cmd->buf.clear();
419: bytecount = cmd->recvbytes;
1.1.1.18 root 420: putlog(3, "MsgOut begin (%u bytes)", bytecount);
1.1 root 421: break;
422: case SCSI::XferPhase::MsgIn:
423: cmd->BeginMsgIn();
424: bytecount = 0;
1.1.1.18 root 425: putlog(3, "MsgIn begin (%u bytes)", (uint)cmd->buf.size());
1.1 root 426: break;
427: case SCSI::XferPhase::Status:
428: cmd->BeginStatus();
429: bytecount = 0;
1.1.1.18 root 430: putlog(3, "Status begin (%u bytes)", (uint)cmd->buf.size());
1.1 root 431: break;
432: default:
1.1.1.18 root 433: PANIC("%s unknown xfer begin %d", __func__, (int)xfer);
1.1 root 434: }
435: }
436: }
437:
438: // IN 方向 (ターゲットが送信側) ならここでデータをバスにセット。
439: if (GetIO()) {
440: SetData(cmd->buf[bytecount]);
441: putlog(4, "SetData $%02x", GetData());
442: }
443:
444: // 次のバイト転送を行うために REQ を上げる。
445: AssertREQ();
446: }
447:
1.1.1.20! root 448: // SCSI コマンドのディスパッチャ。
! 449: // C++ の例外は呼び出し側で捕捉している。
1.1 root 450: void
451: SCSITarget::DispatchCmd()
452: {
453: switch (cmdseq[0]) {
454: case SCSI::Command::TestUnitReady: // 0x00
1.1.1.11 root 455: cmd.reset(new SCSICmd(this));
1.1 root 456: break;
457:
1.1.1.2 root 458: case SCSI::Command::RezeroUnit: // 0x01
1.1.1.11 root 459: cmd.reset(new SCSICmd(this));
1.1.1.2 root 460: break;
461:
1.1 root 462: case SCSI::Command::RequestSense: // 0x03
463: cmd.reset(new SCSICmdRequestSense(this));
464: break;
465:
466: case SCSI::Command::Inquiry: // 0x12
467: cmd.reset(new SCSICmdInquiry(this));
468: break;
469:
470: case SCSI::Command::ModeSelect6: // 0x15
1.1.1.11 root 471: case SCSI::Command::ModeSelect10: // 0x55
472: cmd.reset(new SCSICmdModeSelect(this));
1.1 root 473: break;
474:
1.1.1.2 root 475: case SCSI::Command::ModeSense6: // 0x1a
1.1.1.11 root 476: case SCSI::Command::ModeSense10: // 0x5a
477: cmd.reset(new SCSICmdModeSense(this));
1.1 root 478: break;
479:
480: default:
481: // サポートしていないコマンド
482: const char *cname = SCSI::GetCommandName(cmdseq[0]);
483: std::string name;
484: if (cname) {
485: name = string_format(" %s", cname);
486: }
1.1.1.18 root 487: putlog(0, "SCSI Command $%02x%s len=%u not supported",
488: cmdseq[0], name.c_str(), (uint)cmdseq.size());
1.1 root 489:
490: cmd.reset(new SCSICmdNotSupportedCommand(this));
491: break;
492: }
493: }
494:
1.1.1.20! root 495: // コマンド列を指定して対応するコマンドを選択する。
! 496: // 知らないコマンドなら SCSICmdNotSupportedCommand という仮想のコマンドを返す。
! 497: // コマンドインスタンスが生成できなければ NULL を返す。
! 498: // 外部向け。
1.1.1.15 root 499: SCSICmd *
500: SCSITarget::SelectCommand(const std::vector<uint8>& cmdseq_)
501: {
502: cmdseq = cmdseq_;
1.1.1.20! root 503: SelectCommand();
1.1.1.15 root 504: return cmd.get();
505: }
506:
1.1.1.20! root 507: // 内部版。
! 508: // 入力コマンド列はメンバ変数 cmdseq だし、生成した cmd もメンバ変数。
! 509: void
! 510: SCSITarget::SelectCommand()
! 511: {
! 512: cmd.reset();
! 513: // DispatchCmd() の中は new が多いので個別に捕捉せずこっちでやる。
! 514: try {
! 515: DispatchCmd();
! 516: } catch (...) { }
! 517: }
! 518:
1.1.1.11 root 519:
1.1 root 520: //
1.1.1.11 root 521: // SCSI Disk
1.1 root 522: //
523:
524: // コンストラクタ
1.1.1.20! root 525: SCSIDisk::SCSIDisk(SCSIDomain *domain_, uint id, SCSI::DevType devtype_)
! 526: : inherited(domain_, id)
1.1 root 527: {
1.1.1.11 root 528: devtype = devtype_;
529:
530: const char *devtypename = SCSI::GetDevTypeName(devtype);
531:
532: // コンストラクタの初期化子では devtypename を組み込みにくいので、
533: // コンストラクタ本文でログ名などを設定する。その時の作法に
534: // 従っているのですこし面倒な手法が必要。
535: // lib/object.h 参照。
1.1.1.18 root 536: SetName(string_format("SCSI%s%u", devtypename, id));
1.1.1.11 root 537: ClearAlias();
1.1.1.18 root 538: AddAlias(string_format("SCSI%s%u", devtypename, id));
539: AddAlias(string_format("SCSI%u", id));
540: AddAlias(string_format("%s%u", devtypename, id));
1.1.1.11 root 541:
542: switch (devtype) {
543: case SCSI::DevType::HD:
544: removable_device = false;
545: writeable_device = true;
546: blocksize = 512;
547: break;
548: case SCSI::DevType::CD:
549: removable_device = true;
550: writeable_device = false;
551: blocksize = 2048;
552: break;
553: case SCSI::DevType::MO:
554: removable_device = true;
555: writeable_device = true;
556: blocksize = 512;
557: break;
558: default:
1.1.1.18 root 559: PANIC("corrupted devtype=%u", (uint)devtype);
1.1.1.11 root 560: }
1.1.1.10 root 561:
1.1 root 562: }
563:
564: // デストラクタ
1.1.1.11 root 565: SCSIDisk::~SCSIDisk()
1.1 root 566: {
567: }
568:
569: // 初期化
570: bool
1.1.1.11 root 571: SCSIDisk::Init()
1.1 root 572: {
1.1.1.18 root 573: const std::string keybody = string_format("%s-id%u-",
1.1.1.20! root 574: domain->GetConfigName(), myid);
1.1 root 575: const std::string imgkey = keybody + "image";
1.1.1.11 root 576: const std::string wikey = keybody + "writeignore";
1.1.1.5 root 577: const std::string stkey = keybody + "seektime";
1.1 root 578:
1.1.1.11 root 579: // デバイス種別(v[0])とパス(v[1])に分解。
580: // デバイス種別はすでにチェックしてあるのでここでは不要。
1.1.1.3 root 581: const ConfigItem& itemimg = gConfig->Find(imgkey);
1.1.1.11 root 582: const std::string& imageval = itemimg.AsString();
583: auto v = string_split(imageval, ',', 2);
584: std::string filename;
585: if (v.size() > 1) {
586: filename = string_trim(v[1]);
587: }
588:
589: // 固定デバイス(HD)ならファイル名は省略不可
590: if (!IsRemovableDevice() && filename.empty()) {
1.1.1.2 root 591: itemimg.Err("imagefile not specified");
1.1 root 592: return false;
593: }
594:
1.1.1.14 root 595: // リムーバブルデバイスのみ UI からの通知を受け取るイベントを用意
596: if (IsRemovableDevice()) {
1.1.1.16 root 597: scheduler->ConnectMessage(MessageID::SCSIDEV_LOAD(myid), this,
1.1.1.17 root 598: ToMessageCallback(&SCSIDisk::LoadMessage));
1.1.1.16 root 599: scheduler->ConnectMessage(MessageID::SCSIDEV_UNLOAD(myid), this,
1.1.1.17 root 600: ToMessageCallback(&SCSIDisk::UnloadMessage));
1.1.1.14 root 601: }
602:
1.1.1.11 root 603: // オープン前に writeignore をチェック。
604: // この後パス名を処理するとすぐに UI に通知を出すため、それより前で
605: // 行わないといけない。うーん。
1.1.1.20! root 606: write_ignore = gConfig->Find(wikey).AsInt();
1.1 root 607:
1.1.1.11 root 608: // 設定時点でファイルが指定されていればオープン
609: if (filename.empty() == false) {
1.1.1.17 root 610: std::string path = gMainApp.NormalizePath(filename);
1.1.1.11 root 611: if (LoadDisk(path) == false) {
612: return false;
613: }
614: } else {
615: // メディアがロードされないままでも起動時は必ず通知を投げる。
616: // ステータスパネルはこの UIMessage でのみ状態を更新するため。
617: MediaChanged();
1.1 root 618: }
619:
1.1.1.20! root 620: // 平均シークタイム。設定は [msec]、変数は [nsec]。
! 621: // virtio-scsi にはない。
! 622: if (strncmp(stkey.c_str(), "virtio", 6) != 0) {
! 623: seektime = gConfig->Find(stkey).AsInt();
! 624: seektime *= 1_msec;
! 625: }
1.1.1.5 root 626:
1.1 root 627: return true;
628: }
629:
1.1.1.11 root 630: // 電源オン
1.1.1.14 root 631: void
632: SCSIDisk::ResetHard(bool poweron)
1.1.1.11 root 633: {
1.1.1.14 root 634: inherited::ResetHard(poweron);
1.1.1.11 root 635:
1.1.1.14 root 636: if (poweron) {
637: // メディアの取り出し禁止を解除
638: PreventMediumRemoval(false);
639: }
1.1.1.11 root 640: }
641:
1.1.1.18 root 642: // バスリセットされた (SCSIBus から呼ばれる)
1.1.1.11 root 643: void
1.1.1.18 root 644: SCSIDisk::OnBusReset()
1.1.1.11 root 645: {
646: // メディアの取り出し禁止を解除
647: PreventMediumRemoval(false);
648:
1.1.1.18 root 649: inherited::OnBusReset();
1.1.1.11 root 650: }
651:
652: // バックエンドのディスクイメージを開く。
653: // 成功すれば true、失敗すれば false を返す。(ロードされれば true を返す
654: // のではないが、medium_loaded が同じ値になるので使いまわしている)
655: bool
656: SCSIDisk::LoadDisk(const std::string& pathname_)
657: {
1.1.1.15 root 658: bool unloaded;
1.1.1.11 root 659: off_t size;
1.1.1.18 root 660: int w_ok;
1.1.1.11 root 661: bool read_only;
662:
663: // すでにあればクローズ (ここでは通知は行わない)
1.1.1.15 root 664: unloaded = UnloadDisk_internal();
1.1.1.11 root 665:
666: // 新しいイメージをオープン(する準備)
667: pathname = pathname_;
1.1.1.12 root 668: if (image.CreateHandler(pathname) == false) {
1.1.1.11 root 669: goto done;
670: }
671:
1.1.1.18 root 672: w_ok = image.IsWriteable();
673: if (w_ok < 0) {
1.1.1.11 root 674: goto done;
675: }
676:
1.1.1.18 root 677: // 書き込みモードをここで確定
678: if (IsWriteableDevice()) {
679: // 書き込み可能デバイスの場合
680: if (w_ok) {
681: if (write_ignore) {
682: write_mode = RW::WriteIgnore;
683: } else {
684: write_mode = RW::Writeable;
685: }
686: } else {
687: write_mode = RW::WriteProtect;
688: }
689: } else {
690: // 読み込み専用デバイスの場合
691: write_mode = RW::ReadOnly;
692: }
693:
694: // ここでオープン
695: read_only = (GetWriteMode() != RW::Writeable);
696: if (image.Open(read_only, write_ignore) == false) {
697: goto done;
698: }
699:
700: size = image.GetSize();
701:
1.1.1.11 root 702: // セクタ単位になっていること
703: if (size % blocksize != 0) {
1.1.1.18 root 704: warnx("%s: Bad image size(%ju): Not a multiple of blocksize(%u)",
1.1.1.11 root 705: pathname.c_str(), (uintmax_t)size, blocksize);
706: goto done;
707: }
708: // メディアごとの最大サイズを越えていないこと
709: switch (GetDevType()) {
710: case SCSI::DevType::HD:
711: // LBA (32bit) * 512 byte/sector
712: if (size >= 0x200'0000'0000ULL) {
713: warnx("%s: Bad image size(%ju): Too large",
714: pathname.c_str(), (uintmax_t)size);
715: goto done;
716: }
717: break;
718: case SCSI::DevType::CD:
719: // https://cdrfaq.org/faq07.html#S7-6
720: // 63min = 283,500 [sect] = 553.7MB CD-ROM = 635.9MB CD-DA
721: // 74min = 333,000 [sect] = 650.3MB CD-ROM = 746.9MB CD-DA
722: // 80min = 360,000 [sect] = 703.1MB CD-ROM = 807.4MB CD-DA
723: // 90min = 405,000 [sect] = 791.0MB CD-ROM = 908.4MB CD-DA
724: // 99min = 445,500 [sect] = 870.1MB CD-ROM = 999.3MB CD-DA
725: if (size >= 445500 * 2048) {
726: warnx("%s: Bad image size(%ju): Too Large",
727: pathname.c_str(), (uintmax_t)size);
728: goto done;
729: }
730: break;
731: case SCSI::DevType::MO:
732: // 128M は 25 [sect] * 10,000 [sect/track] * 512 [byte/sect]
733: // 230M は 25 [sect] * 17,853 [sect/track] * 512 [byte/sect]
734: // 540M は 25 [sect] * 41,660 [sect/track] * 512 [byte/sect]
735: // 640M は 17 [sect] * 18,256 [sect/track] * 2048 [byte/sect]
736: // 1.3G は 17 [sect] * 35,638 [sect/track] * 2048 [byte/sect]
737: // 2.3G は 17 [sect] * 62,538 [sect/track] * 2048 [byte/sect]
738: //
739: // 今の所 512 byte/sector フォーマットのみ対応しておく。
740: if (size >= 25 * 41660 * 512) {
741: warnx("%s: Bad image size(%ju): 640M or larger MO not supported",
742: pathname.c_str(), (uintmax_t)size);
743: goto done;
744: }
745: break;
746: default:
1.1.1.18 root 747: PANIC("corrupted devtype=%u", (uint)devtype);
1.1.1.11 root 748: }
749:
750: // 書き込み無視なら一応ログ出力
751: if (GetWriteMode() == RW::WriteIgnore) {
752: putmsg(0, "write is ignored");
753: }
754:
755: medium_loaded = true;
1.1.1.15 root 756: // リムーバブルデバイスでだけログを出す
757: if (IsRemovableDevice()) {
758: putlog(1, "Medium loaded");
1.1.1.11 root 759: }
1.1.1.15 root 760:
761: done:
1.1.1.11 root 762: if (medium_loaded == false) {
763: // ここで状態をクリアする
764: Clear();
765: }
1.1.1.15 root 766:
767: // 変化があれば通知
768: if (unloaded || medium_loaded) {
769: MediaChanged();
770: }
1.1.1.11 root 771: return medium_loaded;
772: }
773:
774: // バックエンドのディスクイメージを閉じる。
775: // オープンされてなければ何もしない。
776: void
777: SCSIDisk::UnloadDisk(bool force)
778: {
779: if (IsMediumRemovalAllowed() || force) {
780: if (UnloadDisk_internal()) {
781: // 実際に閉じたら通知
782: MediaChanged();
783: }
784: }
785: }
786:
787: // バックエンドのディスクイメージを閉じる (内部用)。
788: // 実際に閉じれば true を返す。オープンされてなければ何もせず false を返す。
789: // ここでは通知は行わない。
790: bool
791: SCSIDisk::UnloadDisk_internal()
792: {
793: if ((bool)image == false) {
794: return false;
795: }
796:
797: Clear();
798:
799: // 取り出し禁止はされてても解除する
800: if (IsMediumRemovalPrevented()) {
801: // XXX PreventMediumRemoval() を呼ぶかどうするか
802: medium_removal_prevented = false;
803: }
804:
805: putlog(1, "Medium unloaded");
806: return true;
807: }
808:
809: // ディスク状態をクリアする
810: // (メディアを取り外した時など)
811: void
812: SCSIDisk::Clear()
813: {
814: medium_loaded = false;
1.1.1.12 root 815: image.Close();
1.1.1.11 root 816: pathname.clear();
817: write_mode = RW::Writeable;
818: }
819:
820: // メディア状態変更を UI に通知する
821: void
822: SCSIDisk::MediaChanged() const
823: {
824: UIMessage::Post(UIMessage::SCSI_MEDIA_CHANGE, GetMyID());
825: }
826:
827: // メディアを挿入する。
828: // UI スレッドで実行されるので、VM スレッドに通知するだけ。
829: void
830: SCSIDisk::LoadDiskUI(const std::string& pathname_)
831: {
832: new_pathname = pathname_;
1.1.1.16 root 833: scheduler->SendMessage(MessageID::SCSIDEV_LOAD(GetMyID()));
1.1.1.11 root 834: }
835:
836: // メディアを排出する。
837: // UI スレッドで実行されるので、VM スレッドに通知するだけ。
838: void
839: SCSIDisk::UnloadDiskUI(bool force)
840: {
1.1.1.16 root 841: scheduler->SendMessage(MessageID::SCSIDEV_UNLOAD(GetMyID()), force);
1.1.1.11 root 842: }
843:
1.1.1.14 root 844: // メディア挿入メッセージコールバック
1.1.1.11 root 845: void
1.1.1.17 root 846: SCSIDisk::LoadMessage(MessageID msgid, uint32 arg)
1.1.1.11 root 847: {
848: if (LoadDisk(new_pathname) == false) {
849: // 失敗したら UI に通知
850: UIMessage::Post(UIMessage::SCSI_MEDIA_FAILED);
851: }
852: }
853:
1.1.1.14 root 854: // メディア排出メッセージコールバック
1.1.1.11 root 855: void
1.1.1.17 root 856: SCSIDisk::UnloadMessage(MessageID msgid, uint32 arg)
1.1.1.11 root 857: {
1.1.1.14 root 858: bool force = arg;
1.1.1.11 root 859: UnloadDisk(force);
860: }
861:
1.1.1.20! root 862: // SCSI コマンドのディスパッチャ。
! 863: // C++ の例外は呼び出し側で捕捉している。
1.1 root 864: void
1.1.1.11 root 865: SCSIDisk::DispatchCmd()
1.1 root 866: {
867: switch (cmdseq[0]) {
868: case SCSI::Command::Read6: // 0x08
1.1.1.11 root 869: case SCSI::Command::Read10: // 0x28
870: cmd.reset(new SCSICmdRead(this));
1.1 root 871: break;
872:
873: case SCSI::Command::Write6: // 0x0a
1.1.1.11 root 874: case SCSI::Command::Write10: // 0x2a
875: if (IsWriteableDevice()) {
876: cmd.reset(new SCSICmdWrite(this));
877: } else {
878: cmd.reset(new SCSICmdNotSupportedCommand(this));
879: }
1.1 root 880: break;
881:
1.1.1.4 root 882: case SCSI::Command::StartStopUnit: // 0x1b
883: cmd.reset(new SCSICmdStartStopUnit(this));
884: break;
885:
1.1.1.11 root 886: case SCSI::Command::PreventAllowMediumRemoval: // 0x1e
887: if (IsRemovableDevice() == false) {
888: // HD は取り出せないので、とりあえず。
889: // XXX 要調査
890: cmd.reset(new SCSICmdNotSupportedCommand(this));
891: return;
892: }
893: cmd.reset(new SCSICmdPreventAllowMediumRemoval(this));
894: break;
895:
1.1 root 896: case SCSI::Command::ReadCapacity: // 0x25
1.1.1.11 root 897: // DA デバイスでは Read Capacity (0x25)、
898: // CD-ROM デバイスでは Read CDROM Capacity (0x25) だが
899: // 今の所同じ動作なので区別せず処理する。
1.1 root 900: cmd.reset(new SCSICmdReadCapacity(this));
901: break;
902:
1.1.1.11 root 903: case SCSI::Command::SynchronizeCache: // 0x35
904: // このクラスは、物理ドライブにあるようなディスクとの間の
905: // キャッシュ機構は持っていないので、そのキャッシュをフラッシュ
906: // しろという指示に対しては、黙って成功するだけでいい気がする。
907: // このコマンドはオプションだが NetBSD カーネルが使う。
908: //
909: // CD-ROM でキャッシュの同期とは読み出し側のことだろうか?
910: // とりあえず黙って成功しておく。
911: cmd.reset(new SCSICmd(this));
1.1 root 912: break;
913:
1.1.1.11 root 914: case SCSI::Command::ReadTOC: // 0x43
915: if (devtype == SCSI::DevType::CD) {
916: cmd.reset(new SCSICmdReadTOC(this));
917: } else {
918: cmd.reset(new SCSICmdNotSupportedCommand(this));
919: }
920: break;
921:
922: case SCSI::Command::ReadFormatCapacities: // 0x23
923: // SCSI-2 規格でないので詳細不明。
924: // ディスク用か共通かも分からないけど、とりあえず
925: case SCSI::Command::ReadDiscInformation: // 0x51
926: // CD-ROM デバイスのオプションコマンドで
927: // CD-R ライター用(?)らしいので無視する。
928: cmd.reset(new SCSICmdNotSupportedCommand(this));
1.1.1.7 root 929: break;
930:
1.1 root 931: default:
932: // 見付からなければ基本クラス側に任せる
933: inherited::DispatchCmd();
934: break;
935: }
936: }
937:
1.1.1.11 root 938: // Inquiry データを返す
939: bool
1.1.1.18 root 940: SCSIDisk::Inquiry(std::vector<uint8>& buf, uint lun)
1.1.1.11 root 941: {
942: uint8 inqtype;
943: const char *prodname;
944:
945: switch (devtype) {
946: case SCSI::DevType::HD:
947: inqtype = SCSI::InquiryDeviceType::DirectAccess;
948: prodname = "SCSIHD";
949: break;
950: case SCSI::DevType::CD:
951: inqtype = SCSI::InquiryDeviceType::CDROM;
952: prodname = "SCSICD";
953: break;
954: case SCSI::DevType::MO:
955: inqtype = SCSI::InquiryDeviceType::MO;
956: prodname = "SCSIMO";
957: break;
958: default:
1.1.1.18 root 959: PANIC("Unexpected devtype=%u", (uint)devtype);
1.1.1.11 root 960: }
961:
962: buf[0] = inqtype;
963: if (lun != 0) {
964: buf[0] |= SCSI::InquiryQualifier::LU_NotPreseted;
965: }
966: buf[1] = 0;
967: if (IsRemovableDevice()) {
968: buf[1] |= 0x80;
969: }
970: buf[2] = 2; // SCSI-2
971: buf[3] = 2; // Response Data Format = SCSI-2
972: buf[4] = buf.size() - 4; // 追加データ長
973:
974: // 文字列はゼロ終端ではなく、余りは空白で埋める
975: memset(&buf[8], ' ', 36 - 8);
976: memcpy(&buf[8], "NONO", 4);
977: memcpy(&buf[16], prodname, strlen(prodname));
978: memcpy(&buf[32], "0", 1);
979:
980: return true;
981: }
982:
983: // ModeSense コマンドのデバイス固有パラメータを返す
984: uint8
985: SCSIDisk::GetDeviceSpecificParam() const
986: {
987: uint8 param = 0;
988:
989: // DA, MO, CD のデバイス固有パラメータは以下のようになっている。
990: // DPOFUA はキャッシュ制御フラグのサポート(%1)かどうかなので %0 でいい。
991: //
992: // 7 6 5 4 3 2 1 0
993: // +---+---+---+------+---+---+---+---+
994: // DA |WP | 0 |DPOFUA| 0 | WP: Write Protect
995: // +---+---+---+------+---+---+---+---+
996: //
997: // +---+---+---+------+---+---+---+---+
998: // MO |WP | 0 |DPOFUA| 0 |EBC| EBC: Enable Blank Check
999: // +---+---+---+------+---+---+---+---+
1000: //
1001: // +---+---+---+------+---+---+---+---+
1002: // CD | 0 |DPOFUA| 0 |
1003: // +---+---+---+------+---+---+---+---+
1004:
1005: if (GetDevType() == SCSI::DevType::CD) {
1006: // CD には WP ビットの定義がない
1007: } else {
1008: // DA, MO は WP ビットの定義がある。
1009: if (IsMediumLoaded() && GetWriteMode() == RW::WriteProtect) {
1010: param |= 0x80;
1011: }
1012: }
1013:
1014: return param;
1015: }
1016:
1017: bool
1018: SCSIDisk::Read(std::vector<uint8>& buf, uint64 start)
1019: {
1020: if (__predict_false((bool)image == false)) {
1021: return false;
1022: }
1.1.1.12 root 1023: return image.Read(buf.data(), start, buf.size());
1.1.1.11 root 1024: }
1025:
1026: bool
1027: SCSIDisk::Write(const std::vector<uint8>& buf, uint64 start)
1028: {
1029: if (__predict_false((bool)image == false)) {
1030: return false;
1031: }
1032:
1.1.1.12 root 1033: if (GetWriteMode() >= RW::WriteProtect) {
1.1.1.11 root 1034: // 書き込み不可
1035: return false;
1036: }
1.1.1.12 root 1037: return image.Write(buf.data(), start, buf.size());
1.1.1.11 root 1038: }
1039:
1040: off_t
1041: SCSIDisk::GetSize() const
1042: {
1043: if (__predict_false((bool)image == false)) {
1044: return 0;
1045: }
1.1.1.12 root 1046: return image.GetSize();
1.1.1.11 root 1047: }
1048:
1.1.1.2 root 1049: // 指定の場所を自由に読み出す
1050: // (エミュレータ特権アクセス)
1051: bool
1.1.1.11 root 1052: SCSIDisk::Peek(void *buf, uint64 start, uint32 len) const
1.1.1.2 root 1053: {
1.1.1.11 root 1054: if (__predict_false((bool)image == false)) {
1.1.1.2 root 1055: return false;
1056: }
1.1.1.12 root 1057: return image.Read(buf, start, len);
1.1.1.2 root 1058: }
1059:
1.1.1.11 root 1060: // メディアの取り出し可否(取り出し禁止)を設定する
1061: void
1062: SCSIDisk::PreventMediumRemoval(bool val)
1063: {
1064: // 状態が変わったところでログを表示
1065: if (medium_removal_prevented == false && val == true) {
1066: putlog(1, "Medium removal prevented");
1067: } else if (medium_removal_prevented == true && val == false) {
1068: putlog(1, "Medium removal allowed");
1069: }
1070: medium_removal_prevented = val;
1071: }
1.1.1.14 root 1072:
1073: // メディアの書き込みモードを文字列で返す
1074: const char *
1075: SCSIDisk::GetWriteModeStr() const
1076: {
1077: switch (write_mode) {
1078: case RW::ReadOnly: return "ReadOnly";
1079: case RW::WriteProtect: return "WriteProtected";
1080: case RW::Writeable: return "Writeable";
1081: case RW::WriteIgnore: return "WriteIgnore";
1082: default:
1.1.1.16 root 1083: PANIC("corrupted write_mode=%d", (int)write_mode);
1.1.1.14 root 1084: }
1085: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.