--- nono/vm/scsidev.cpp 2026/04/29 17:05:03 1.1.1.12 +++ nono/vm/scsidev.cpp 2026/04/29 17:05:46 1.1.1.21 @@ -4,6 +4,10 @@ // Licensed under nono-license.txt // +// +// SCSI デバイス +// + // ログレベルは // 1: SCSI コマンドの概要。 // デバイスの動作のうち大きな変更のもの @@ -14,8 +18,10 @@ #include "scsicmd.h" #include "scsidev.h" +#include "scsidomain.h" #include "config.h" #include "mainapp.h" +#include "scheduler.h" #include "uimessage.h" // @@ -23,9 +29,10 @@ // // コンストラクタ -SCSIDevice::SCSIDevice(const std::string& objname_) - : inherited(objname_) +SCSIDevice::SCSIDevice(uint objid_) + : inherited(objid_) { + myid = -1; } // デストラクタ @@ -33,24 +40,28 @@ 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 = (MonitorCallback_t)&SCSIHostDevice::MonitorUpdateDevs; - // サイズは Create で決まる - monitor_devs.Regist(ID_MONITOR_SCSIDEVS); - - // バスデバイスを作成する。 - // ただし自身がバスに参加するのは継承側で行う - // (例えば SPC なら BDID 書き込みで ID が決まった後、など)。 - scsibus.reset(new SCSIBus()); + SetLogLevel(parent->loglevel); } // デストラクタ @@ -58,91 +69,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(80, (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) { // 実際にはバスフリーを検出したデバイスは各自自身が送出している信号を // 下げるのだが、色々面倒なので、ここではホストのバスフリーコールバックが @@ -151,10 +100,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(); @@ -167,104 +116,35 @@ SCSIHostDevice::BusFreeCB(int id) } SetXfer(0); SetData(0); -} - -// SCSI デバイスモニタ -// (どこでやるのがいいか分からんけど、SCSI デバイスを一括して表示したいので -// とりあえずここで) -void -SCSIHostDevice::MonitorUpdateDevs(Monitor *, TextScreen& screen) -{ - int x; - int y; - - 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++; + // 親のバスフリーコールバックを呼ぶ + (parent->*(BusFreeCallback))(id); - // イニシエータならここまで。(今の所イニシエータとディスクしかない) - if (disk == NULL) { - continue; - } + // RST が見えてる状態で親に通知はしたので、ここで下ろす。 + if (GetRST()) { + NegateRST(); + } +} - // 1列目 - x = 5; - screen.Print(x, y, - (disk->IsRemovableDevice() ? TA::Normal : TA::Disable), - "MediumLoaded %s", - disk->IsMediumLoaded() ? "On" : "Off"); - screen.Print(x, y + 1, - (disk->IsRemovableDevice() ? TA::Normal : TA::Disable), - "RemovalPrevent %s", - disk->IsMediumRemovalPrevented() ? "On" : "Off"); - - // 2列目 - x = 29; - const char *wstr; - switch (disk->GetWriteMode()) { - case SCSIDisk::RW::ReadOnly: wstr = "ReadOnly"; break; - case SCSIDisk::RW::WriteProtect: wstr = "WriteProtected"; break; - case SCSIDisk::RW::Writeable: wstr = "Writeable"; break; - case SCSIDisk::RW::WriteIgnore: wstr = "WriteIgnored"; break; - default: __unreachable(); - } - screen.Puts(x, y, - (disk->IsMediumLoaded() ? TA::Normal : TA::Disable), wstr); +// ターゲットがセレクションに応答した (SCSIBus から呼ばれる) +void +SCSIHostDevice::OnSelectionAck() +{ + // ターゲットが BSY を立てて応答したので、 + // こちらはデータバスをクリアして SEL を下げる。 + // これによりセレクションフェーズは成功で完了する。 + SetData(0); + NegateSEL(); - // 3列目 - x = 50; - uint64 v = disk->GetSize(); - std::string bytes; - if (v < 1000) { - bytes = string_format("%u", (uint32)v); - } else if (v < 1000 * 1000) { - bytes = string_format("%u,%03u", - (uint)(v / 1000), - (uint)(v % 1000)); - } else if (v < 1000 * 1000 * 1000) { - bytes = string_format("%u,%03u,%03u", - (uint)(v / 1000 / 1000), - (uint)((v / 1000) % 1000), - (uint)(v % 1000)); - } else if (v < 1000ULL * 1000 * 1000 * 1000) { - bytes = string_format("%u,%03u,%03u,%03u", - (uint)(v / 1000 / 1000 / 1000), - (uint)((v / 1000 / 1000) % 1000), - (uint)((v / 1000) % 1000), - (uint)(v % 1000)); - } else { - bytes = string_format("%ju", (uintmax_t)v); - } - screen.Print(x, y, "TotalBytes %s", bytes.c_str()); - screen.Print(x, y + 1, "LogicalBlock %d", disk->GetBlocksize()); + // 親のバスフリーコールバックを呼ぶ + (parent->*(SelectionAckCallback))(); +} - y += 3; - } +// ターゲットが REQ を立てた (SCSIBus から呼ばれる) +void +SCSIHostDevice::OnTransferReq() +{ + (parent->*(TransferReqCallback))(); } @@ -272,12 +152,11 @@ 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; } @@ -287,22 +166,22 @@ SCSITarget::~SCSITarget() } // 電源オン -bool -SCSITarget::PowerOn() +void +SCSITarget::ResetHard(bool poweron) { - ClearSense(); - cmdseq.clear(); - cmdlen = 0; - cmd.reset(); - bytecount = 0; - lastbyte = false; - - return true; + if (poweron) { + ClearSense(); + cmdseq.clear(); + cmdlen = 0; + cmd.reset(); + bytecount = 0; + lastbyte = false; + } } // バスリセットされた (SCSIBus から呼ばれる) void -SCSITarget::BusResetCB() +SCSITarget::OnBusReset() { // センスキーをクリア ClearSense(); @@ -326,18 +205,18 @@ 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"); @@ -349,7 +228,7 @@ SCSITarget::StartTransferCB() // 情報転送フェーズ間の遷移について // -// 情報転送フェーズは TransferCB() で1バイトずつ転送を行い、所定のバイト数 +// 情報転送フェーズは OnTransfer() で1バイトずつ転送を行い、所定のバイト数 // の転送が完了すればフェーズを遷移する。遷移方法は入出力方向別で6通り。 // o IN -> IN // o IN -> OUT @@ -358,26 +237,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 に応答 @@ -385,7 +264,7 @@ SCSITarget::StartTransferCB() // IN (ターゲット → イニシエータ方向) なら、ターゲットがデータをデータバスに // 乗せて REQ を上げたのをイニシエータが受信した後でこれをコールしてくる。 void -SCSITarget::TransferCB() +SCSITarget::OnTransfer() { uint8 data; SCSI::XferPhase xfer; @@ -399,8 +278,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バイト目ならこのコマンドが何バイトか調べる @@ -431,9 +310,20 @@ 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 (GetIO() == false) { // OUT 方向 (ターゲットが受信側) @@ -446,7 +336,7 @@ SCSITarget::TransferCB() } else { // IN 方向 (ターゲットが送信側) // bytecount は加算方向。 - // データは前のループの終わり (TransferAckCB()の下のほう) で + // データは前のループの終わり (OnTransferAck()の下のほう) で // セットしているのでここでは引き取られた分をカウントする。 bytecount++; if (bytecount >= cmd->buf.size()) { @@ -459,7 +349,7 @@ SCSITarget::TransferCB() // 情報転送フェーズでイニシエータが ACK を下げた。(SCSIBus から呼ばれる) void -SCSITarget::TransferAckCB() +SCSITarget::OnTransferAck() { SCSI::XferPhase xfer = GetXfer(); @@ -469,7 +359,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(); @@ -487,7 +377,7 @@ 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)); @@ -500,6 +390,7 @@ SCSITarget::TransferAckCB() if (next != xfer) { // End ならバスフリーにして終了。 if (next == SCSI::XferPhase::End) { + cmd.reset(); NegateBSY(); return; } @@ -516,30 +407,30 @@ SCSITarget::TransferAckCB() 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); } } } @@ -554,7 +445,8 @@ SCSITarget::TransferAckCB() AssertREQ(); } -// SCSI コマンドのディスパッチャ +// SCSI コマンドのディスパッチャ。 +// C++ の例外は呼び出し側で捕捉している。 void SCSITarget::DispatchCmd() { @@ -592,22 +484,46 @@ SCSITarget::DispatchCmd() if (cname) { name = string_format(" %s", cname); } - putlog(0, "未実装 SCSI コマンド $%02x%s len=%d", - 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; } } +// コマンド列を指定して対応するコマンドを選択する。 +// 知らないコマンドなら SCSICmdNotSupportedCommand という仮想のコマンドを返す。 +// コマンドインスタンスが生成できなければ NULL を返す。 +// 外部向け。 +SCSICmd * +SCSITarget::SelectCommand(const std::vector& cmdseq_) +{ + cmdseq = cmdseq_; + 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_; @@ -617,10 +533,11 @@ SCSIDisk::SCSIDisk(SCSIHostDevice *h, in // コンストラクタ本文でログ名などを設定する。その時の作法に // 従っているのですこし面倒な手法が必要。 // lib/object.h 参照。 - SetName(string_format("SCSI%s%d", devtypename, id)); + SetName(string_format("SCSI%s%u", devtypename, id)); ClearAlias(); - AddAlias(string_format("SCSI%d", id)); - AddAlias(string_format("%s%d", devtypename, id)); + AddAlias(string_format("SCSI%s%u", devtypename, id)); + AddAlias(string_format("SCSI%u", id)); + AddAlias(string_format("%s%u", devtypename, id)); switch (devtype) { case SCSI::DevType::HD: @@ -639,14 +556,9 @@ SCSIDisk::SCSIDisk(SCSIHostDevice *h, in blocksize = 512; break; default: - __unreachable(); + PANIC("corrupted devtype=%u", (uint)devtype); } - // リムーバブルデバイスのみ UI からの通知を受け取るイベントを用意 - if (IsRemovableDevice()) { - event = new Event(this); - event->SetName(GetName() + " MediaChange"); - } } // デストラクタ @@ -658,11 +570,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])に分解。 @@ -681,39 +592,25 @@ SCSIDisk::Init() return false; } + // リムーバブルデバイスのみ UI からの通知を受け取るイベントを用意 + if (IsRemovableDevice()) { + 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).AsInt(); - write_ignore = wp.AsInt(); - } else { - write_ignore = wi.AsInt(); - } + // writeignore が決まったので状態を初期化。 + Clear(); // 設定時点でファイルが指定されていればオープン if (filename.empty() == false) { - const std::string path = gMainApp.GetVMDir() + filename; + std::string path = gMainApp.NormalizePath(filename); if (LoadDisk(path) == false) { return false; } @@ -723,35 +620,36 @@ SCSIDisk::Init() 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; } // 電源オン -bool -SCSIDisk::PowerOn() +void +SCSIDisk::ResetHard(bool poweron) { - if (inherited::PowerOn() == false) { - return false; - } + inherited::ResetHard(poweron); - // メディアの取り出し禁止を解除 - PreventMediumRemoval(false); - - return true; + if (poweron) { + // メディアの取り出し禁止を解除 + PreventMediumRemoval(false); + } } -// バスリセット +// バスリセットされた (SCSIBus から呼ばれる) void -SCSIDisk::BusResetCB() +SCSIDisk::OnBusReset() { // メディアの取り出し禁止を解除 PreventMediumRemoval(false); - inherited::BusResetCB(); + inherited::OnBusReset(); } // バックエンドのディスクイメージを開く。 @@ -760,13 +658,13 @@ SCSIDisk::BusResetCB() bool SCSIDisk::LoadDisk(const std::string& pathname_) { - bool changed = false; + bool unloaded; off_t size; - bool w_ok; + int w_ok; bool read_only; // すでにあればクローズ (ここでは通知は行わない) - changed = UnloadDisk_internal(); + unloaded = UnloadDisk_internal(); // 新しいイメージをオープン(する準備) pathname = pathname_; @@ -774,13 +672,39 @@ SCSIDisk::LoadDisk(const std::string& pa goto done; } - if (image.GetInfo(&size, &w_ok) == false) { + w_ok = image.IsWriteable(); + if (w_ok < 0) { + goto done; + } + + // 書き込みモードをここで確定 + 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; } + 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; } @@ -823,30 +747,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); } // 書き込み無視なら一応ログ出力 @@ -855,21 +756,21 @@ SCSIDisk::LoadDisk(const std::string& pa } medium_loaded = true; - changed = true; -done: - if (changed) { - // HD 初期化時はログに出さない - if (IsRemovableDevice()) { - putlog(1, "Medium loaded"); - } - - // 変化があれば通知 - MediaChanged(); + // リムーバブルデバイスでだけログを出す + if (IsRemovableDevice()) { + putlog(1, "Medium loaded"); } + + done: if (medium_loaded == false) { // ここで状態をクリアする Clear(); } + + // 変化があれば通知 + if (unloaded || medium_loaded) { + MediaChanged(); + } return medium_loaded; } @@ -916,7 +817,16 @@ 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 に通知する @@ -926,15 +836,13 @@ SCSIDisk::MediaChanged() const UIMessage::Post(UIMessage::SCSI_MEDIA_CHANGE, GetMyID()); } - // メディアを挿入する。 // UI スレッドで実行されるので、VM スレッドに通知するだけ。 void SCSIDisk::LoadDiskUI(const std::string& pathname_) { new_pathname = pathname_; - event->func = (DeviceCallback_t)&SCSIDisk::LoadCallback; - event->Start(); + scheduler->SendMessage(MessageID::SCSIDEV_LOAD(GetMyID())); } // メディアを排出する。 @@ -942,14 +850,12 @@ SCSIDisk::LoadDiskUI(const std::string& void SCSIDisk::UnloadDiskUI(bool force) { - event->func = (DeviceCallback_t)&SCSIDisk::UnloadCallback; - event->code = force; - event->Start(); + scheduler->SendMessage(MessageID::SCSIDEV_UNLOAD(GetMyID()), force); } -// メディア挿入コールバック +// メディア挿入メッセージコールバック void -SCSIDisk::LoadCallback(Event& ev) +SCSIDisk::LoadMessage(MessageID msgid, uint32 arg) { if (LoadDisk(new_pathname) == false) { // 失敗したら UI に通知 @@ -957,15 +863,16 @@ SCSIDisk::LoadCallback(Event& ev) } } -// メディア排出コールバック +// メディア排出メッセージコールバック void -SCSIDisk::UnloadCallback(Event& ev) +SCSIDisk::UnloadMessage(MessageID msgid, uint32 arg) { - bool force = ev.code; + bool force = arg; UnloadDisk(force); } -// SCSI コマンドのディスパッチャ +// SCSI コマンドのディスパッチャ。 +// C++ の例外は呼び出し側で捕捉している。 void SCSIDisk::DispatchCmd() { @@ -1042,7 +949,7 @@ SCSIDisk::DispatchCmd() // Inquiry データを返す bool -SCSIDisk::Inquiry(std::vector& buf, int lun) +SCSIDisk::Inquiry(std::vector& buf, uint lun) { uint8 inqtype; const char *prodname; @@ -1061,7 +968,7 @@ SCSIDisk::Inquiry(std::vector& bu prodname = "SCSIMO"; break; default: - assert(false); + PANIC("Unexpected devtype=%u", (uint)devtype); } buf[0] = inqtype; @@ -1174,3 +1081,17 @@ SCSIDisk::PreventMediumRemoval(bool val) } medium_removal_prevented = val; } + +// メディアの書き込みモードを文字列で返す +const char * +SCSIDisk::GetWriteModeStr() const +{ + switch (write_mode) { + case RW::ReadOnly: return "ReadOnly"; + case RW::WriteProtect: return "WriteProtected"; + case RW::Writeable: return "Writeable"; + case RW::WriteIgnore: return "WriteIgnore"; + default: + PANIC("corrupted write_mode=%d", (int)write_mode); + } +}