--- nono/vm/scsidev.cpp 2026/04/29 17:05:14 1.1.1.15 +++ nono/vm/scsidev.cpp 2026/04/29 17:06:01 1.1.1.23 @@ -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" @@ -28,9 +30,10 @@ // // コンストラクタ -SCSIDevice::SCSIDevice(const std::string& objname_) - : inherited(objname_) +SCSIDevice::SCSIDevice(uint objid_) + : inherited(objid_) { + myid = -1; } // デストラクタ @@ -38,25 +41,29 @@ SCSIDevice::~SCSIDevice() { } +// バスに接続する。 +void +SCSIDevice::Attach(SCSIBus *bus_) +{ + bus = bus_; +} + // -// SCSI ホストデバイス +// SCSI ホストデバイス (イニシエータ) // // コンストラクタ -SCSIHostDevice::SCSIHostDevice(const std::string& objname_) - : inherited(objname_) +SCSIHostDevice::SCSIHostDevice(SCSIDomain *domain_, IODevice *parent_) + : inherited(OBJ_NONE) { + domain = domain_; + parent = parent_; + // イニシエータの ID は実行時にコントローラによって決まる。 devtype = SCSI::DevType::Initiator; - monitor_devs.func = ToMonitorCallback(&SCSIHostDevice::MonitorUpdateDevs); - // サイズは Create で決まる - monitor_devs.Regist(ID_MONITOR_SCSIDEVS); - - // バスデバイスを作成する。 - // ただし自身がバスに参加するのは継承側で行う - // (例えば SPC なら BDID 書き込みで ID が決まった後、など)。 - scsibus.reset(new SCSIBus()); + SetName(string_format("(SCSIHost.%s)", domain_->GetDomainName())); + SetLogLevel(parent->loglevel); } // デストラクタ @@ -64,91 +71,29 @@ SCSIHostDevice::~SCSIHostDevice() { } -// 設定ファイルに使われるこのホストデバイスを示す接頭語を設定する -// (継承クラス側のコンストラクタで config_name を設定後に呼ぶこと) +// SCSI ID を設定する。 +// (SCSIDomain からのみ呼ぶこと) void -SCSIHostDevice::SetConfigName(const char *name_) -{ - config_name = name_; -} - -bool -SCSIHostDevice::Create() +SCSIHostDevice::SetID(uint id_) { - // 設定を読み込む。 - for (int id = 0; id < 8; id++) { - const std::string key = string_format("%s-id%d-image", - GetConfigName(), id); - const ConfigItem& item = gConfig->Find(key); - const std::string& image = item.AsString(); - - // 空ならデバイスなし - if (image.length() == 0) { - // 未接続部分のパラメータは減らしておくか。 - // --show-config するとさすがに無駄に多くて邪魔なので。 - const std::string wikey = string_format("%s-id%d-writeignore", - GetConfigName(), id); - const std::string wpkey = string_format("%s-id%d-writeprotect", - GetConfigName(), id); - const std::string stkey = string_format("%s-id%d-seektime", - GetConfigName(), id); - gConfig->Delete(wikey); - gConfig->Delete(wpkey); - gConfig->Delete(stkey); - continue; - } - - // ',' より前がデバイス種別 (ここではパスは不要) - auto v = string_split(image, ','); - // 比較のため前後の空白を取り除いて小文字にする - std::string type = string_trim(v[0]); - type = string_tolower(type); - - SCSI::DevType target_devtype; - if (type == "hd") { - target_devtype = SCSI::DevType::HD; - } else if (type == "cd") { - target_devtype = SCSI::DevType::CD; - } else if (type == "mo") { - target_devtype = SCSI::DevType::MO; - } else { - item.Err("invaild device type '%s'", type.c_str()); - return false; - } - target[id].reset(new SCSIDisk(this, id, target_devtype)); - scsibus->Attach(target[id].get(), id); - } - - return true; + myid = id_; } -// 初期化。 -// 継承クラスが connected_devices をセットした後で呼ぶこと。 -bool -SCSIHostDevice::Init() +// SCSIBus からのコールバック関数を設定する。 +void +SCSIHostDevice::SetBusCallback( + BusFreeCallback_t busfree_, + SCSIHostCallback_t selack_, + SCSIHostCallback_t xferreq_) { - // 初期化ではないけどログレベルがセットできたここでデバッグ表示。 - // -L オプションの処理は Create() の後なので。 - if (loglevel >= 1) { - for (int id = 0; id < 8; id++) { - std::string typestr; - if (target[id]) { - typestr = SCSI::GetDevTypeName(target[id]->GetDevType()); - putmsgn("%s target #%d %s", GetConfigName(), id, - typestr.c_str()); - } - } - } - - // connected_devices によってモニタの大きさを決定。 - monitor_devs.SetSize(75, (connected_devices.size() - 1) * 4 + 1); - - return true; + BusFreeCallback = busfree_; + SelectionAckCallback = selack_; + TransferReqCallback = xferreq_; } -// バスフリーコールバック (SCSIBus から呼ばれる) +// バスフリーになった (SCSIBus から呼ばれる) void -SCSIHostDevice::BusFreeCB(int id) +SCSIHostDevice::OnBusFree(uint id) { // 実際にはバスフリーを検出したデバイスは各自自身が送出している信号を // 下げるのだが、色々面倒なので、ここではホストのバスフリーコールバックが @@ -157,10 +102,10 @@ SCSIHostDevice::BusFreeCB(int id) uint32 data; while ((data = GetBSY()) != 0) { - bus->NegateBSY(DecodeID(data)); + bus->NegateBSY(SCSIBus::DecodeID(data)); } while ((data = GetSEL()) != 0) { - bus->NegateSEL(DecodeID(data)); + bus->NegateSEL(SCSIBus::DecodeID(data)); } if (GetREQ()) { NegateREQ(); @@ -173,83 +118,35 @@ SCSIHostDevice::BusFreeCB(int id) } SetXfer(0); SetData(0); + + // 親のバスフリーコールバックを呼ぶ + (parent->*(BusFreeCallback))(id); + + // RST が見えてる状態で親に通知はしたので、ここで下ろす。 + if (GetRST()) { + NegateRST(); + } } -// SCSI デバイスモニタ -// (どこでやるのがいいか分からんけど、SCSI デバイスを一括して表示したいので -// とりあえずここで) +// ターゲットがセレクションに応答した (SCSIBus から呼ばれる) void -SCSIHostDevice::MonitorUpdateDevs(Monitor *, TextScreen& screen) +SCSIHostDevice::OnSelectionAck() { - int y; - -// 012345678901234567890123456789012345678901234567890123456789012345678901234 -// ID6: HD(2048MB) -// MediumLoaded Off LogicalBlock 2048 ImageSize 2,147,483,648 -// RemovalPrevent Off WriteProtected - - - screen.Clear(); - - y = 0; - for (const auto dev : connected_devices) { - std::string desc = SCSI::GetDevTypeName(dev->GetDevType()); - std::string pathname; - - SCSIDisk *disk = dynamic_cast(dev); - if (disk && disk->IsMediumLoaded()) { - desc += string_format("(%dMB)", - (int)(disk->GetSize() / 1024 / 1024)); - pathname = disk->GetPathName(); - } - screen.Print(0, y, "ID%c: %s", '0' + dev->GetMyID(), desc.c_str()); - -#if 1 - (void)pathname; -#else - // パス名の非ASCIIどうするか - if (!pathname.empty()) { - if (pathname.size() > screen.GetCol() - 16 - 3) { - pathname = pathname.substr(0, screen.GetCol() - 16 - 3); - pathname += "..."; - } - screen.Puts(16, y, pathname.c_str()); - } -#endif - y++; - - // イニシエータならここまで。(今の所イニシエータとディスクしかない) - if (disk == NULL) { - continue; - } + // ターゲットが BSY を立てて応答したので、 + // こちらはデータバスをクリアして SEL を下げる。 + // これによりセレクションフェーズは成功で完了する。 + SetData(0); + NegateSEL(); - const int x1 = 5; - const int x2 = 29; - const int x3 = 50; - - // 1行目 - screen.Print(x1, y, - (disk->IsRemovableDevice() ? TA::Normal : TA::Disable), - "MediumLoaded %s", - disk->IsMediumLoaded() ? "On" : "Off"); - screen.Print(x2, y, "LogicalBlock %d", disk->GetBlocksize()); - std::string imagesize = format_number(disk->GetSize()); - screen.Print(x3, y, "ImageSize %13s", imagesize.c_str()); - y++; - - // 2行目 - screen.Print(x1, y, - (disk->IsRemovableDevice() ? TA::Normal : TA::Disable), - "RemovalPrevent %s", - disk->IsMediumRemovalPrevented() ? "On" : "Off"); - - screen.Puts(x2, y, - (disk->IsMediumLoaded() ? TA::Normal : TA::Disable), - disk->GetWriteModeStr()); - y++; + // 親のバスフリーコールバックを呼ぶ + (parent->*(SelectionAckCallback))(); +} - y++; - } +// ターゲットが REQ を立てた (SCSIBus から呼ばれる) +void +SCSIHostDevice::OnTransferReq() +{ + (parent->*(TransferReqCallback))(); } @@ -257,13 +154,14 @@ SCSIHostDevice::MonitorUpdateDevs(Monito // SCSI ターゲットデバイスの共通部分 // -// コンストラクタ -SCSITarget::SCSITarget(const std::string& objname_, SCSIHostDevice *h, int id) - : inherited(objname_) +// コンストラクタ。 +SCSITarget::SCSITarget(SCSIDomain *domain_, uint id) + : inherited(OBJ_NONE) // オブジェクト ID で検索することはない { - // ホストへのリンク (所有関係のないポインタ) - host = h; + domain = domain_; myid = id; + + // ログは出すので名前はつけるがそれは継承先で SetName() している。 } // デストラクタ @@ -280,6 +178,7 @@ SCSITarget::ResetHard(bool poweron) cmdseq.clear(); cmdlen = 0; cmd.reset(); + msgseq.clear(); bytecount = 0; lastbyte = false; } @@ -287,7 +186,7 @@ SCSITarget::ResetHard(bool poweron) // バスリセットされた (SCSIBus から呼ばれる) void -SCSITarget::BusResetCB() +SCSITarget::OnBusReset() { // センスキーをクリア ClearSense(); @@ -311,30 +210,37 @@ SCSITarget::BusResetCB() // セレクションで選択された (SCSIBus から呼ばれる) void -SCSITarget::SelectionCB() +SCSITarget::OnSelected() { // ターゲットが、自身が選択されていることを認識してから BSY を // 立てるまでの時間はもうバス側でカウントしてあるので、ここでは // ただちに BSY を上げてよい。 - putlog(4, "Selection Callback"); + putlog(4, "OnSelected"); AssertBSY(); } // 情報転送フェーズを開始する (SCSIBus から呼ばれる) void -SCSITarget::StartTransferCB() +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(); } // 情報転送フェーズ間の遷移について // -// 情報転送フェーズは TransferCB() で1バイトずつ転送を行い、所定のバイト数 +// 情報転送フェーズは OnTransfer() で1バイトずつ転送を行い、所定のバイト数 // の転送が完了すればフェーズを遷移する。遷移方法は入出力方向別で6通り。 // o IN -> IN // o IN -> OUT @@ -343,26 +249,26 @@ SCSITarget::StartTransferCB() // o OUT -> IN // o OUT -> END // -// IN -> IN の遷移。TransferCB() で前フェーズの最終バイト受信後、フェーズを +// IN -> IN の遷移。OnTransfer() で前フェーズの最終バイト受信後、フェーズを // 変更して REQ を立てるところまで。 // -// IN -> OUT の遷移。TransferCB() で前フェーズの最終バイト受信後、フェーズを +// IN -> OUT の遷移。OnTransfer() で前フェーズの最終バイト受信後、フェーズを // 変更して (おそらく {Phase}Begin() を発行してから)、データをセットし REQ // を立てる? // -// IN -> END の遷移。TransferCB() で前フェーズの最終バイト受信後、BSY を +// IN -> END の遷移。OnTransfer() で前フェーズの最終バイト受信後、BSY を // 下げて終了。 // // OUT -> OUT の遷移。前フェーズの最終バイトをイニシエータが受信後に -// TransferCB() が呼ばれるので、フェーズを変更して (おそらく {Phase}Begin() +// OnTransfer() が呼ばれるので、フェーズを変更して (おそらく {Phase}Begin() // を発行してから)、データをセットし REQ を立てる? // // OUT -> IN の遷移。前フェーズの最終バイトをイニシエータが受信後に -// TransferCB() が呼ばれるので、フェーズを変更して (おそらく {Phase}Begin() +// OnTransfer() が呼ばれるので、フェーズを変更して (おそらく {Phase}Begin() // を発行してから)、REQ を立てる? // // OUT -> END の遷移。前フェーズの最終バイトをイニシエータが受信後に -// TransferCB() が呼ばれるので、BSY を下げる? +// OnTransfer() が呼ばれるので、BSY を下げる? // 情報転送フェーズでイニシエータが ACK を上げた。(SCSIBus から呼ばれる) // OUT (イニシエータ → ターゲット方向) なら、ターゲットが上げた REQ に応答 @@ -370,7 +276,7 @@ SCSITarget::StartTransferCB() // IN (ターゲット → イニシエータ方向) なら、ターゲットがデータをデータバスに // 乗せて REQ を上げたのをイニシエータが受信した後でこれをコールしてくる。 void -SCSITarget::TransferCB() +SCSITarget::OnTransfer() { uint8 data; SCSI::XferPhase xfer; @@ -384,8 +290,8 @@ SCSITarget::TransferCB() // コマンドフェーズだけいろいろ特殊なので別処理。 // 受信バッファが異なる、1バイト目で受信長が決まるなど。 data = GetData(); - putlog(4, "TransferCB: cmdseq[%02d] = $%02x", - (int)cmdseq.size(), data); + putlog(4, "%s: cmdseq[%02u] = $%02x", __func__, + (uint)cmdseq.size(), data); cmdseq.push_back(data); if (cmdseq.size() == 1) { // 1バイト目ならこのコマンドが何バイトか調べる @@ -416,10 +322,31 @@ SCSITarget::TransferCB() } 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 は減算方向。 @@ -431,7 +358,7 @@ SCSITarget::TransferCB() } else { // IN 方向 (ターゲットが送信側) // bytecount は加算方向。 - // データは前のループの終わり (TransferAckCB()の下のほう) で + // データは前のループの終わり (OnTransferAck()の下のほう) で // セットしているのでここでは引き取られた分をカウントする。 bytecount++; if (bytecount >= cmd->buf.size()) { @@ -444,7 +371,7 @@ SCSITarget::TransferCB() // 情報転送フェーズでイニシエータが ACK を下げた。(SCSIBus から呼ばれる) void -SCSITarget::TransferAckCB() +SCSITarget::OnTransferAck() { SCSI::XferPhase xfer = GetXfer(); @@ -454,7 +381,7 @@ SCSITarget::TransferAckCB() // フェーズ完了 switch (xfer) { case SCSI::XferPhase::Command: - next = cmd->Command(cmdseq); + next = cmd->ExecCommand(cmdseq); break; case SCSI::XferPhase::DataOut: next = cmd->DoneDataOut(); @@ -463,7 +390,23 @@ SCSITarget::TransferAckCB() 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(); @@ -472,12 +415,12 @@ SCSITarget::TransferAckCB() next = cmd->DoneStatus(); break; default: - PANIC("TransferCB unknown xfer done %d", (int)xfer); + PANIC("%s unknown xfer done %d", __func__, (int)xfer); } putlog(3, "%s done", SCSI::GetXferPhaseName(xfer)); // 簡略化のため、フェーズ完了時だけ REQ を引き伸ばすことができる - if (cmd->optime != 0) { + if (cmd && cmd->optime != 0) { bus->SetOneshotReqWait(cmd->optime); cmd->optime = 0; } @@ -485,6 +428,7 @@ SCSITarget::TransferAckCB() if (next != xfer) { // End ならバスフリーにして終了。 if (next == SCSI::XferPhase::End) { + cmd.reset(); NegateBSY(); return; } @@ -498,33 +442,35 @@ SCSITarget::TransferAckCB() // Command フェーズで Data フェーズの準備をすることが多いので // 悩ましい。 switch (xfer) { + case SCSI::XferPhase::Command: + break; case SCSI::XferPhase::DataOut: cmd->buf.clear(); bytecount = cmd->recvbytes; - putlog(3, "DataOut begin (%d bytes)", bytecount); + putlog(3, "DataOut begin (%u bytes)", bytecount); break; case SCSI::XferPhase::DataIn: bytecount = 0; - putlog(3, "DataIn begin (%d bytes)", (int)cmd->buf.size()); + putlog(3, "DataIn begin (%u bytes)", (uint)cmd->buf.size()); break; case SCSI::XferPhase::MsgOut: cmd->BeginMsgOut(); cmd->buf.clear(); bytecount = cmd->recvbytes; - putlog(3, "MsgOut begin (%d bytes)", bytecount); + putlog(3, "MsgOut begin (%u bytes)", bytecount); break; case SCSI::XferPhase::MsgIn: cmd->BeginMsgIn(); bytecount = 0; - putlog(3, "MsgIn begin (%d bytes)", (int)cmd->buf.size()); + putlog(3, "MsgIn begin (%u bytes)", (uint)cmd->buf.size()); break; case SCSI::XferPhase::Status: cmd->BeginStatus(); bytecount = 0; - putlog(3, "Status begin (%d bytes)", (int)cmd->buf.size()); + putlog(3, "Status begin (%u bytes)", (uint)cmd->buf.size()); break; default: - PANIC("TransferCB unknown xfer begin %d", (int)xfer); + PANIC("%s unknown xfer begin %d", __func__, (int)xfer); } } } @@ -539,7 +485,8 @@ SCSITarget::TransferAckCB() AssertREQ(); } -// SCSI コマンドのディスパッチャ +// SCSI コマンドのディスパッチャ。 +// C++ の例外は呼び出し側で捕捉している。 void SCSITarget::DispatchCmd() { @@ -577,45 +524,69 @@ SCSITarget::DispatchCmd() if (cname) { name = string_format(" %s", cname); } - putlog(0, "SCSI Command $%02x%s len=%d not supported", - cmdseq[0], name.c_str(), (int)cmdseq.size()); + putlog(0, "SCSI Command $%02x%s len=%u not supported", + cmdseq[0], name.c_str(), (uint)cmdseq.size()); cmd.reset(new SCSICmdNotSupportedCommand(this)); break; } } -// コマンド列を指定して対応するコマンドを選択する。なければ 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(SCSIHostDevice *h, int id, SCSI::DevType devtype_) - : inherited("", h, id) +SCSIDisk::SCSIDisk(SCSIDomain *domain_, uint id, SCSI::DevType devtype_) + : inherited(domain_, id) { devtype = devtype_; const char *devtypename = SCSI::GetDevTypeName(devtype); + const char *domainname = domain->GetDomainName(); + // ドライブ自身のログ表示名は "HD(SPC0-6)" のような形式にする。 + // ログ指定はそれに合わせて "spc0-6" のような完全形と + // "hd6" のような短縮形を用意してみる。 + // SCSI コントローラ2台目ではこの短縮形だけなくす? + // // コンストラクタの初期化子では devtypename を組み込みにくいので、 // コンストラクタ本文でログ名などを設定する。その時の作法に // 従っているのですこし面倒な手法が必要。 // lib/object.h 参照。 - SetName(string_format("SCSI%s%d", devtypename, id)); + std::string name = string_format("%s-%u", domainname, id); + SetName(string_format("%s(%s)", devtypename, name.c_str())); ClearAlias(); - AddAlias(string_format("SCSI%d", id)); - AddAlias(string_format("%s%d", devtypename, id)); + AddAlias(name); + AddAlias(string_format("%s%u", devtypename, id)); + + // image はコンストラクタがないので、このあたりで代入する。 + image.SetName(name); switch (devtype) { case SCSI::DevType::HD: @@ -634,7 +605,7 @@ SCSIDisk::SCSIDisk(SCSIHostDevice *h, in blocksize = 512; break; default: - __unreachable(); + PANIC("corrupted devtype=%u", (uint)devtype); } } @@ -648,11 +619,10 @@ SCSIDisk::~SCSIDisk() bool SCSIDisk::Init() { - const std::string keybody = string_format("%s-id%d-", - host->GetConfigName(), myid); + const std::string keybody = string_format("%s-id%u-", + 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])に分解。 @@ -673,64 +643,35 @@ SCSIDisk::Init() // リムーバブルデバイスのみ UI からの通知を受け取るイベントを用意 if (IsRemovableDevice()) { - gScheduler->ConnectMessage(MessageID::SCSIDEV_LOAD(myid), this, - ToMessageCallback(&SCSIDisk::LoadCallback)); - gScheduler->ConnectMessage(MessageID::SCSIDEV_UNLOAD(myid), this, - ToMessageCallback(&SCSIDisk::UnloadCallback)); + scheduler->ConnectMessage(MessageID::SCSIDEV_LOAD(myid), this, + ToMessageCallback(&SCSIDisk::LoadMessage)); + scheduler->ConnectMessage(MessageID::SCSIDEV_UNLOAD(myid), this, + ToMessageCallback(&SCSIDisk::UnloadMessage)); } // オープン前に 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).AsBool(); - write_ignore = wp.AsInt(); - } else { - write_ignore = wi.AsInt(); - } + // writeignore が決まったので状態を初期化。 + Clear(); // 設定時点でファイルが指定されていればオープン if (filename.empty() == false) { - std::string path; - if (filename[0] == '/') { - path = filename; - } else { - // 相対パスなら VM ディレクトリから - path = gMainApp.GetVMDir() + filename; - } - + std::string path = gMainApp.NormalizePath(filename); if (LoadDisk(path) == false) { + itemimg.Err("Could not open %s", path.c_str()); return false; } - } else { - // メディアがロードされないままでも起動時は必ず通知を投げる。 - // ステータスパネルはこの UIMessage でのみ状態を更新するため。 - MediaChanged(); } - // 設定は [msec]、変数は [nsec]。 - seektime = gConfig->Find(stkey).AsInt(); - seektime *= 1_msec; + // 平均シークタイム。設定は [msec]、変数は [tsec]。 + // virtio-scsi にはない。 + if (strncmp(stkey.c_str(), "virtio", 6) != 0) { + seektime = gConfig->Find(stkey).AsInt(); + seektime *= 1_msec; + } return true; } @@ -747,14 +688,14 @@ SCSIDisk::ResetHard(bool poweron) } } -// バスリセット +// バスリセットされた (SCSIBus から呼ばれる) void -SCSIDisk::BusResetCB() +SCSIDisk::OnBusReset() { // メディアの取り出し禁止を解除 PreventMediumRemoval(false); - inherited::BusResetCB(); + inherited::OnBusReset(); } // バックエンドのディスクイメージを開く。 @@ -765,7 +706,6 @@ SCSIDisk::LoadDisk(const std::string& pa { bool unloaded; off_t size; - bool w_ok; bool read_only; // すでにあればクローズ (ここでは通知は行わない) @@ -777,13 +717,49 @@ SCSIDisk::LoadDisk(const std::string& pa goto done; } - if (image.GetInfo(&size, &w_ok) == false) { + // 書き込みモードをここで確定。 + // + // デバイス特性 イメージ write_ignore write_mode + // ------------ ---------- ------------ : ---------- + // 書込み可 書込み可 no Writeable + // 〃 〃 yes WriteIgnore + // 〃 書込み不可 no WriteProtect (A) + // 〃 〃 yes WriteIgnore (B) + // 書込み不可 * * ReadOnly + // + // (A) は「ライトプロテクトした MO を入れた」状態。 + // (B) は「書き換えたくないので書き込み権を落としてあるイメージファイルを + // あたかも書き込めるように見せたい」。(WI マークが出る) + if (IsWriteableDevice()) { + // 書き込み可能デバイスの場合 + if (write_ignore) { + write_mode = RW::WriteIgnore; + } else { + int w_ok = image.IsWriteable(); + if (w_ok < 0) { + goto done; + } else if (w_ok) { + write_mode = RW::Writeable; + } else { + write_mode = RW::WriteProtect; + } + } + } else { + // 読み込み専用デバイスの場合 + write_mode = RW::ReadOnly; + } + + // ここでオープン + read_only = (GetWriteMode() != RW::Writeable); + if (image.Open(read_only, write_ignore) == false) { goto done; } + size = image.GetSize(); + // セクタ単位になっていること if (size % blocksize != 0) { - warnx("%s: Bad image size(%ju): Not a multiple of blocksize(%d)", + warnx("%s: Bad image size(%ju): Not a multiple of blocksize(%u)", pathname.c_str(), (uintmax_t)size, blocksize); goto done; } @@ -826,30 +802,7 @@ SCSIDisk::LoadDisk(const std::string& pa } break; default: - __unreachable(); - } - - // 書き込みモードをここで確定 - if (IsWriteableDevice()) { - // 書き込み可能デバイスの場合 - if (w_ok) { - if (write_ignore) { - write_mode = RW::WriteIgnore; - } else { - write_mode = RW::Writeable; - } - } else { - write_mode = RW::WriteProtect; - } - } else { - // 読み込み専用デバイスの場合 - write_mode = RW::ReadOnly; - } - - // ここでオープン - read_only = (GetWriteMode() != RW::Writeable); - if (image.Open(read_only, write_ignore) == false) { - goto done; + PANIC("corrupted devtype=%u", (uint)devtype); } // 書き込み無視なら一応ログ出力 @@ -919,14 +872,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()); } // メディアを挿入する。 @@ -935,7 +897,7 @@ void SCSIDisk::LoadDiskUI(const std::string& pathname_) { new_pathname = pathname_; - gScheduler->SendMessage(MessageID::SCSIDEV_LOAD(GetMyID())); + scheduler->SendMessage(MessageID::SCSIDEV_LOAD(GetMyID())); } // メディアを排出する。 @@ -943,28 +905,29 @@ SCSIDisk::LoadDiskUI(const std::string& void SCSIDisk::UnloadDiskUI(bool force) { - gScheduler->SendMessage(MessageID::SCSIDEV_UNLOAD(GetMyID()), force); + scheduler->SendMessage(MessageID::SCSIDEV_UNLOAD(GetMyID()), force); } // メディア挿入メッセージコールバック void -SCSIDisk::LoadCallback(MessageID msgid, uint32 arg) +SCSIDisk::LoadMessage(MessageID msgid, uint32 arg) { if (LoadDisk(new_pathname) == false) { // 失敗したら UI に通知 - UIMessage::Post(UIMessage::SCSI_MEDIA_FAILED); + gMainApp.GetUIMessage()->Post(UIMessage::SCSI_MEDIA_FAILED); } } // メディア排出メッセージコールバック void -SCSIDisk::UnloadCallback(MessageID msgid, uint32 arg) +SCSIDisk::UnloadMessage(MessageID msgid, uint32 arg) { bool force = arg; UnloadDisk(force); } -// SCSI コマンドのディスパッチャ +// SCSI コマンドのディスパッチャ。 +// C++ の例外は呼び出し側で捕捉している。 void SCSIDisk::DispatchCmd() { @@ -1041,7 +1004,7 @@ SCSIDisk::DispatchCmd() // Inquiry データを返す bool -SCSIDisk::Inquiry(std::vector& buf, int lun) +SCSIDisk::Inquiry(std::vector& buf, uint lun) { uint8 inqtype; const char *prodname; @@ -1060,7 +1023,7 @@ SCSIDisk::Inquiry(std::vector& bu prodname = "SCSIMO"; break; default: - PANIC("Unexpected devtype=%d", (int)devtype); + PANIC("Unexpected devtype=%u", (uint)devtype); } buf[0] = inqtype; @@ -1184,6 +1147,6 @@ SCSIDisk::GetWriteModeStr() const case RW::Writeable: return "Writeable"; case RW::WriteIgnore: return "WriteIgnore"; default: - __unreachable(); + PANIC("corrupted write_mode=%d", (int)write_mode); } }