--- nono/vm/scsidev.cpp 2026/04/29 17:05:29 1.1.1.18 +++ nono/vm/scsidev.cpp 2026/04/29 17:05:51 1.1.1.22 @@ -18,7 +18,9 @@ #include "scsicmd.h" #include "scsidev.h" +#include "scsidomain.h" #include "config.h" +#include "event.h" #include "mainapp.h" #include "scheduler.h" #include "uimessage.h" @@ -39,12 +41,11 @@ SCSIDevice::~SCSIDevice() { } -// このデバイスがバスに接続された (SCSIBus::AttachDevice() から呼ばれる) +// バスに接続する。 void -SCSIDevice::OnAttached(SCSIBus *bus_, uint myid_) +SCSIDevice::Attach(SCSIBus *bus_) { bus = bus_; - myid = myid_; } @@ -53,17 +54,16 @@ SCSIDevice::OnAttached(SCSIBus *bus_, ui // // コンストラクタ -SCSIHostDevice::SCSIHostDevice(IODevice *parent_, const char *config_name_) - : inherited(OBJ_SCSI) +SCSIHostDevice::SCSIHostDevice(SCSIDomain *domain_, IODevice *parent_) + : inherited(OBJ_NONE) { + domain = domain_; parent = parent_; // イニシエータの ID は実行時にコントローラによって決まる。 devtype = SCSI::DevType::Initiator; - // バスデバイスを作成する。 - // ただし自身がバスに参加するのは継承側で行う - // (例えば SPC なら BDID 書き込みで ID が決まった後、など)。 - scsibus.reset(new SCSIBus(this, config_name_)); + SetName(string_format("(SCSIHost.%s)", domain_->GetConfigName())); + SetLogLevel(parent->loglevel); } // デストラクタ @@ -71,30 +71,17 @@ SCSIHostDevice::~SCSIHostDevice() { } -// 初期化。 -bool -SCSIHostDevice::Init() +// SCSI ID を設定する。 +// (SCSIDomain からのみ呼ぶこと) +void +SCSIHostDevice::SetID(uint id_) { - if (inherited::Init() == false) { - return false; - } - - // 初期化ではないけど、ログが使えるようになったここでデバッグ表示。 - if (loglevel >= 1) { - const char *name = scsibus->GetConfigName(); - const auto& conn = scsibus->GetConnectedDevices(); - for (const auto dev : conn) { - putmsgn("%s target #%u %s", - name, dev->GetMyID(), SCSI::GetDevTypeName(dev->GetDevType())); - } - } - - return true; + myid = id_; } -// コールバック関数を設定する。 +// SCSIBus からのコールバック関数を設定する。 void -SCSIHostDevice::SetCallback( +SCSIHostDevice::SetBusCallback( BusFreeCallback_t busfree_, SCSIHostCallback_t selack_, SCSIHostCallback_t xferreq_) @@ -134,6 +121,11 @@ SCSIHostDevice::OnBusFree(uint id) // 親のバスフリーコールバックを呼ぶ (parent->*(BusFreeCallback))(id); + + // RST が見えてる状態で親に通知はしたので、ここで下ろす。 + if (GetRST()) { + NegateRST(); + } } // ターゲットがセレクションに応答した (SCSIBus から呼ばれる) @@ -163,11 +155,13 @@ SCSIHostDevice::OnTransferReq() // // コンストラクタ。 -SCSITarget::SCSITarget(SCSIBus *bus_, uint id) +SCSITarget::SCSITarget(SCSIDomain *domain_, uint id) : inherited(OBJ_NONE) // オブジェクト ID で検索することはない { - bus = bus_; + domain = domain_; myid = id; + + // ログは出すので名前はつけるがそれは継承先で SetName() している。 } // デストラクタ @@ -184,6 +178,7 @@ SCSITarget::ResetHard(bool poweron) cmdseq.clear(); cmdlen = 0; cmd.reset(); + msgseq.clear(); bytecount = 0; lastbyte = false; } @@ -228,11 +223,18 @@ SCSITarget::OnSelected() void SCSITarget::OnStartTransfer() { - // 最初はコマンドフェーズ。(ATN はとりあえず放置) - putlog(4, "Start Command Phase"); + // ATN が立っていればメッセージアウトフェーズ。 + // そうでなければコマンドフェーズ。 cmdseq.clear(); cmdlen = 0; - SetXfer(SCSI::XferPhase::Command); + msgseq.clear(); + if (GetATN()) { + putlog(4, "Start MsgOut Phase by ATN"); + SetXfer(SCSI::XferPhase::MsgOut); + } else { + putlog(4, "Start Command Phase"); + SetXfer(SCSI::XferPhase::Command); + } AssertREQ(); } @@ -320,10 +322,31 @@ SCSITarget::OnTransfer() } putlogn("%s", cmdbuf.c_str()); } - // 最終バイトを受信したらコマンドを選択 - DispatchCmd(); + // 最終バイトを受信したらコマンドを選択。 + lastbyte = true; + SelectCommand(); + if ((bool)cmd == false) { + std::string namebuf; + const char *name = SCSI::GetCommandName(cmdseq[0]); + if (name) { + namebuf = std::string(name); + } else { + namebuf = string_format("$%02x", cmdseq[0]); + } + putlog(0, "Failed to initialize command %s", namebuf.c_str()); + AssertRST(); + } + } + } else if (xfer == SCSI::XferPhase::MsgOut) { + data = GetData(); + putlog(4, "%s: msgseq[%02u] = $%02x", __func__, + (uint)msgseq.size(), data); + msgseq.push_back(data); + bytecount--; + if (bytecount == 0) { lastbyte = true; } + } else if (GetIO() == false) { // OUT 方向 (ターゲットが受信側) // bytecount は減算方向。 @@ -358,7 +381,7 @@ SCSITarget::OnTransferAck() // フェーズ完了 switch (xfer) { case SCSI::XferPhase::Command: - next = cmd->Command(cmdseq); + next = cmd->ExecCommand(cmdseq); break; case SCSI::XferPhase::DataOut: next = cmd->DoneDataOut(); @@ -367,7 +390,23 @@ SCSITarget::OnTransferAck() next = cmd->DoneDataIn(); break; case SCSI::XferPhase::MsgOut: - next = cmd->DoneMsgOut(); + if (cmd) { + next = cmd->DoneMsgOut(); + } else { + // コマンド前のメッセージアウト。 + if (msgseq.size() == 1 && (msgseq[0] & 0x80)) { + putlog(1, "%s: MsgOut $%02x Identify (NOT SUPPORTED)", + __func__, msgseq[0]); + } else { + std::string buf; + for (auto m : msgseq) { + buf += string_format(" %02x", m); + } + putlog(0, "%s: MsgOut%s (NOT SUPPORTED)", + __func__, buf.c_str()); + } + next = SCSI::XferPhase::Command; + } break; case SCSI::XferPhase::MsgIn: next = cmd->DoneMsgIn(); @@ -381,7 +420,7 @@ SCSITarget::OnTransferAck() putlog(3, "%s done", SCSI::GetXferPhaseName(xfer)); // 簡略化のため、フェーズ完了時だけ REQ を引き伸ばすことができる - if (cmd->optime != 0) { + if (cmd && cmd->optime != 0) { bus->SetOneshotReqWait(cmd->optime); cmd->optime = 0; } @@ -389,6 +428,7 @@ SCSITarget::OnTransferAck() if (next != xfer) { // End ならバスフリーにして終了。 if (next == SCSI::XferPhase::End) { + cmd.reset(); NegateBSY(); return; } @@ -402,6 +442,8 @@ SCSITarget::OnTransferAck() // Command フェーズで Data フェーズの準備をすることが多いので // 悩ましい。 switch (xfer) { + case SCSI::XferPhase::Command: + break; case SCSI::XferPhase::DataOut: cmd->buf.clear(); bytecount = cmd->recvbytes; @@ -443,7 +485,8 @@ SCSITarget::OnTransferAck() AssertREQ(); } -// SCSI コマンドのディスパッチャ +// SCSI コマンドのディスパッチャ。 +// C++ の例外は呼び出し側で捕捉している。 void SCSITarget::DispatchCmd() { @@ -489,24 +532,38 @@ SCSITarget::DispatchCmd() } } -// コマンド列を指定して対応するコマンドを選択する。なければ NULL を返す。 -// ROM30 エミュレーション用。 +// コマンド列を指定して対応するコマンドを選択する。 +// 知らないコマンドなら SCSICmdNotSupportedCommand という仮想のコマンドを返す。 +// コマンドインスタンスが生成できなければ NULL を返す。 +// 外部向け。 SCSICmd * SCSITarget::SelectCommand(const std::vector& cmdseq_) { cmdseq = cmdseq_; - DispatchCmd(); + SelectCommand(); return cmd.get(); } +// 内部版。 +// 入力コマンド列はメンバ変数 cmdseq だし、生成した cmd もメンバ変数。 +void +SCSITarget::SelectCommand() +{ + cmd.reset(); + // DispatchCmd() の中は new が多いので個別に捕捉せずこっちでやる。 + try { + DispatchCmd(); + } catch (...) { } +} + // // SCSI Disk // // コンストラクタ -SCSIDisk::SCSIDisk(SCSIBus *bus_, uint id, SCSI::DevType devtype_) - : inherited(bus_, id) +SCSIDisk::SCSIDisk(SCSIDomain *domain_, uint id, SCSI::DevType devtype_) + : inherited(domain_, id) { devtype = devtype_; @@ -553,15 +610,10 @@ SCSIDisk::~SCSIDisk() bool SCSIDisk::Init() { - if (inherited::Init() == false) { - return false; - } - const std::string keybody = string_format("%s-id%u-", - bus->GetConfigName(), myid); + domain->GetConfigName(), myid); const std::string imgkey = keybody + "image"; const std::string wikey = keybody + "writeignore"; - const std::string wpkey = keybody + "writeprotect"; const std::string stkey = keybody + "seektime"; // デバイス種別(v[0])とパス(v[1])に分解。 @@ -591,32 +643,10 @@ SCSIDisk::Init() // オープン前に writeignore をチェック。 // この後パス名を処理するとすぐに UI に通知を出すため、それより前で // 行わないといけない。うーん。 - // - // 変数名は元々 -writeprotect だったが -writeignore に変更した。 - // 単にリネームするだけだと、開発中に古いバージョンを二分探索とかする - // 必要が出来た場合に面倒なので、当面は古いほうも面倒を見る。 - // 十分いらなくなった頃に削除する。 - // - // wi指定 wp指定 - // なし なし wi を採用。 - // なし あり wp を採用。ただし警告 - // あり なし wi を採用。 - // あり あり wi を採用。ここは警告不要でいいだろう - // - const ConfigItem& wi = gConfig->Find(wikey); - const ConfigItem& wp = gConfig->Find(wpkey); - if (wi.GetFrom() == ConfigItem::FromInitial && - wp.GetFrom() != ConfigItem::FromInitial ) - { - // -writeignore (新)がなく -writeprotect (旧) だけが指定されると警告。 - // エラー停止まではしなくていいか。 - warnx("Warning: '%s' is obsolete. Use new '%s'", - wpkey.c_str(), wikey.c_str()); + write_ignore = gConfig->Find(wikey).AsInt(); - write_ignore = wp.AsInt(); - } else { - write_ignore = wi.AsInt(); - } + // writeignore が決まったので状態を初期化。 + Clear(); // 設定時点でファイルが指定されていればオープン if (filename.empty() == false) { @@ -624,15 +654,14 @@ SCSIDisk::Init() if (LoadDisk(path) == false) { return false; } - } else { - // メディアがロードされないままでも起動時は必ず通知を投げる。 - // ステータスパネルはこの UIMessage でのみ状態を更新するため。 - MediaChanged(); } - // 設定は [msec]、変数は [nsec]。 - seektime = gConfig->Find(stkey).AsInt(); - seektime *= 1_msec; + // 平均シークタイム。設定は [msec]、変数は [nsec]。 + // virtio-scsi にはない。 + if (strncmp(stkey.c_str(), "virtio", 6) != 0) { + seektime = gConfig->Find(stkey).AsInt(); + seektime *= 1_msec; + } return true; } @@ -684,17 +713,29 @@ SCSIDisk::LoadDisk(const std::string& pa goto done; } - // 書き込みモードをここで確定 + // 書き込みモードをここで確定。 + // + // デバイス特性 イメージ write_ignore write_mode + // ------------ ---------- ------------ : ---------- + // 書込み可 書込み可 no Writeable + // 〃 〃 yes WriteIgnore + // 〃 書込み不可 no WriteProtect (A) + // 〃 〃 yes WriteIgnore (B) + // 書込み不可 * * ReadOnly + // + // (A) は「ライトプロテクトした MO を入れた」状態。 + // (B) は「書き換えたくないので書き込み権を落としてあるイメージファイルを + // あたかも書き込めるように見せたい」。(WI マークが出る) if (IsWriteableDevice()) { // 書き込み可能デバイスの場合 - if (w_ok) { - if (write_ignore) { - write_mode = RW::WriteIgnore; - } else { + if (write_ignore) { + write_mode = RW::WriteIgnore; + } else { + if (w_ok) { write_mode = RW::Writeable; + } else { + write_mode = RW::WriteProtect; } - } else { - write_mode = RW::WriteProtect; } } else { // 読み込み専用デバイスの場合 @@ -824,14 +865,23 @@ SCSIDisk::Clear() medium_loaded = false; image.Close(); pathname.clear(); - write_mode = RW::Writeable; + + if (IsWriteableDevice()) { + if (write_ignore) { + write_mode = RW::WriteIgnore; + } else { + write_mode = RW::Writeable; + } + } else { + write_mode = RW::ReadOnly; + } } // メディア状態変更を UI に通知する void SCSIDisk::MediaChanged() const { - UIMessage::Post(UIMessage::SCSI_MEDIA_CHANGE, GetMyID()); + gMainApp.GetUIMessage()->Post(UIMessage::SCSI_MEDIA_CHANGE, GetMyID()); } // メディアを挿入する。 @@ -857,7 +907,7 @@ SCSIDisk::LoadMessage(MessageID msgid, u { if (LoadDisk(new_pathname) == false) { // 失敗したら UI に通知 - UIMessage::Post(UIMessage::SCSI_MEDIA_FAILED); + gMainApp.GetUIMessage()->Post(UIMessage::SCSI_MEDIA_FAILED); } } @@ -869,7 +919,8 @@ SCSIDisk::UnloadMessage(MessageID msgid, UnloadDisk(force); } -// SCSI コマンドのディスパッチャ +// SCSI コマンドのディスパッチャ。 +// C++ の例外は呼び出し側で捕捉している。 void SCSIDisk::DispatchCmd() {