--- nono/vm/scsibus.cpp 2026/04/29 17:04:50 1.1.1.6 +++ nono/vm/scsibus.cpp 2026/04/29 17:05:33 1.1.1.13 @@ -4,29 +4,35 @@ // Licensed under nono-license.txt // +// // SCSI バス +// #include "scsibus.h" #include "scsidev.h" +#include "config.h" +#include "monitor.h" +#include "scheduler.h" // time 時間後に呼び出すコールバックをセットする。 // func の書式が面倒なのを省略して書きたいため。 #define CallAfter(func_, name_, time_) do { \ - event.func = (DeviceCallback_t)&SCSIBus::func_; \ - event.SetName(name_); \ - event.code = 0; \ + event.func = ToEventCallback(&SCSIBus::func_); \ event.time = time_; \ - event.Start(); \ + event.SetName(name_); \ + scheduler->RestartEvent(event); \ } while (0) // コンストラクタ -SCSIBus::SCSIBus() +SCSIBus::SCSIBus(SCSIHostDevice *parent_, const char *config_name_) + : inherited(OBJ_SCSIBUS) { - logname = "scsibus"; - devname = "SCSIBus"; + parent = parent_; + config_name = config_name_; - event.dev = this; - event.SetName("SCSIBus"); // 複数出来たら名前どうするべか + monitor = gMonitorManager->Regist(ID_MONITOR_SCSIDEVS, this); + monitor->func = ToMonitorCallback(&SCSIBus::MonitorUpdate); + // サイズは Create で決まる } // デストラクタ @@ -34,10 +40,96 @@ SCSIBus::~SCSIBus() { } -// リセット +// 動的なコンストラクション +bool +SCSIBus::Create() +{ + const char *name = GetConfigName(); + + // 設定を読み込む。 + for (int id = 0; id < 8; id++) { + const std::string key = string_format("%s-id%u-image", name, id); + const ConfigItem& item = gConfig->Find(key); + const std::string& image = item.AsString(); + + // 空ならデバイスなし + if (image.empty()) { + // 未接続部分のパラメータは減らしておくか。 + // --show-config するとさすがに無駄に多くて邪魔なので。 + const std::string wikey = string_format("%s-id%u-writeignore", + name, id); + const std::string wpkey = string_format("%s-id%u-writeprotect", + name, id); + const std::string stkey = string_format("%s-id%u-seektime", + name, 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)); + AttachDevice(target[id].get(), id); + } + + // ここの connected_devices はイニシエータを含んでいない数 + monitor->SetSize(75, connected_devices.size() * 4 + 1); + + return true; +} + +// 初期化 +bool +SCSIBus::Init() +{ + if (inherited::Init() == false) { + return false; + } + + event.SetName("SCSIBus"); // 複数出来たら名前どうするべか + scheduler->RegistEvent(event); + + return true; +} + +// 電源オン/リセット void -SCSIBus::ResetHard() +SCSIBus::ResetHard(bool poweron) { + if (poweron) { + phase = SCSI::Phase::BusFree; + rst = false; + req = false; + ack = false; + atn = false; + sel = 0; + bsy = 0; + xfer = 0; + data = 0; + + last_req_time = 0; + req_wait = 0; + init_id = -1; + target_id = -1; + } + // ここでのイベントは実際にはイニシエータかターゲットのどちらかが // 時間を伴う処理をしている最中であることを示しているものなので、 // イニシエータがリセットされたこととバスの動作とは本来関係ないのだが @@ -45,26 +137,58 @@ SCSIBus::ResetHard() // タイマーイベントが使われており、本体をリセットしたのに入れ違いで // イニシエータの転送処理が呼び出されてしまうというような事故を // 防ぐためにもここでタイマーを停止する。 - event.Stop(); + scheduler->StopEvent(event); } -// このバスにデバイスを接続する (初期化時に使う) +// デバイスを接続する。 +// id に先客がいれば false を返す。成功すれば true を返す。 bool -SCSIBus::Attach(SCSIDevice *dev, int id) +SCSIBus::AttachDevice(SCSIDevice *dev, int id) { if (device[id] != NULL) { - putlog(0, "id=%d already exist?", id); return false; } - putlog(3, "Attach #%d", id); + putlog(3, "Attach #%u", id); - // バスとデバイスは相互に接続する。どちらも所有のないただのポインタ。 device[id] = dev; - dev->Attach(this, id); + MakeConnectedDevices(); + + // 紐付けをデバイスにも通知。 + dev->OnAttached(this, id); return true; } +// device[] から connected_device を作り直す。 +// device[] はイニシエータを含む固定長の配列。 +// connected_device はイニシエータを (あれば) 含む ID 順の可変長リスト。 +void +SCSIBus::MakeConnectedDevices() +{ + connected_devices.clear(); + + for (int id = 0; id < 8; id++) { + if (device[id]) { + connected_devices.push_back(device[id]); + } + } +} + +// 接続済みの (イニシエータを除く) デバイス数を返す。 +uint +SCSIBus::GetTargetCount() const +{ + uint count = 0; + + for (int id = 0; id < 8; id++) { + if ((bool)target[id]) { + count++; + } + } + + return count; +} + // RST を上げる void SCSIBus::AssertRST() @@ -81,7 +205,7 @@ SCSIBus::AssertRST() // 即バスリセットを全員に通知 for (int i = 0; i < 8; i++) { if (device[i]) { - device[i]->BusResetCB(); + device[i]->OnBusReset(); } } @@ -105,7 +229,7 @@ SCSIBus::NegateRST() void SCSIBus::AssertSEL(uint id) { - putlog(4, "%s(#%d)", __func__, id); + putlog(4, "%s(#%u)", __func__, id); // すでに上がってたら何もしない if ((sel & (1 << id))) { @@ -173,7 +297,10 @@ SCSIBus::AssertSEL(uint id) // だろうか。賢くないターゲットデバイスは検出せずにそのまま (そして // デッドロック) になるだろうか。 - // XXX とりあえず +#if 1 + // XXX とりあえず LUNA で SCSI がなんか刺さった時に本体リセットでも + // 直らない状況を真似するため、ターゲットは何もしないでおく。 +#else // ターゲットデバイスは賢いので、全員イニシエータ様に忖度して // 即座にバスフリーへ移行するというのはどうか。 for (uint i = 0; i < 8; i++) { @@ -181,6 +308,7 @@ SCSIBus::AssertSEL(uint id) NegateBSY(i); } } +#endif } } @@ -188,7 +316,7 @@ SCSIBus::AssertSEL(uint id) void SCSIBus::NegateSEL(uint id) { - putlog(4, "%s(#%d)", __func__, id); + putlog(4, "%s(#%u)", __func__, id); // すでに下りてたら何もしない if ((sel & (1 << id)) == 0) { @@ -223,7 +351,7 @@ SCSIBus::NegateSEL(uint id) } } else { // 下げる分には無視してもいいはずだけど、どうする? - PANIC("NegateSEL in %s phase\n", GetPhaseName()); + VMPANIC("NegateSEL in %s phase", GetPhaseName()); } } @@ -231,7 +359,7 @@ SCSIBus::NegateSEL(uint id) void SCSIBus::AssertBSY(uint id) { - putlog(4, "%s(#%d)", __func__, id); + putlog(4, "%s(#%u)", __func__, id); // すでに上がってたら何もしない if ((bsy & (1 << id))) { @@ -259,7 +387,7 @@ SCSIBus::AssertBSY(uint id) } else if (GetPhase() == SCSI::Phase::Arbitration) { // アービトレーションフェーズだったら、アービトレーションに参加。 // 今は起きない。 - PANIC("AssertBSY(#%d) in arbitration", id); + VMPANIC("AssertBSY(#%u) in arbitration", id); } else if (GetPhase() == SCSI::Phase::Selection) { // セレクションフェーズだったら、転送フェーズに移行中。 @@ -269,7 +397,7 @@ SCSIBus::AssertBSY(uint id) CallAfter(SelectionAck, "SCSIBus Selection Ack", 90_nsec); } else { // どうする? - PANIC("AssertBSY(#%d) in %s phase\n", id, GetPhaseName()); + VMPANIC("AssertBSY(#%u) in %s phase", id, GetPhaseName()); } } @@ -277,7 +405,7 @@ SCSIBus::AssertBSY(uint id) void SCSIBus::NegateBSY(uint id) { - putlog(4, "%s(#%d)", __func__, id); + putlog(4, "%s(#%u)", __func__, id); // すでに下りてたら何もしない if ((bsy & (1 << id)) == 0) { @@ -303,7 +431,7 @@ SCSIBus::NegateBSY(uint id) } else { // どうする? - PANIC("NegateBSY(#%d) in %s phase\n", id, GetPhaseName()); + VMPANIC("NegateBSY(#%u) in %s phase", id, GetPhaseName()); } } @@ -335,7 +463,7 @@ SCSIBus::AssertREQ() // 通知はイベントにしないといけないので。 uint64 after = 0; - uint64 now = gMPU->GetVirtTime(); + uint64 now = scheduler->GetVirtTime(); if (now < last_req_time + 1_usec) { after = (last_req_time + 1_usec) - now; } @@ -344,6 +472,7 @@ SCSIBus::AssertREQ() after += req_wait; req_wait = 0; } + last_req_time = now + after; CallAfter(TransferReq, "SCSIBus AssertREQ", after); } else { // 転送フェーズでないのに上げることはないと思うけど @@ -354,7 +483,7 @@ SCSIBus::AssertREQ() } // ターゲットが REQ を立てようとして所定時間経ったので REQ を立てて -// イニシエータの TransferReqCB を呼び出すところ。 +// イニシエータの OnTransferReq を呼び出すところ。 void SCSIBus::TransferReq(Event& ev) { @@ -362,7 +491,7 @@ SCSIBus::TransferReq(Event& ev) req = true; assert(device[init_id]); - device[init_id]->TransferReqCB(); + device[init_id]->OnTransferReq(); } // REQ を下げる @@ -382,7 +511,7 @@ SCSIBus::AssertACK() if (GetPhase() == SCSI::Phase::Transfer) { assert(device[target_id]); - device[target_id]->TransferCB(); + device[target_id]->OnTransfer(); } } @@ -395,7 +524,7 @@ SCSIBus::NegateACK() if (GetPhase() == SCSI::Phase::Transfer) { assert(device[target_id]); - device[target_id]->TransferAckCB(); + device[target_id]->OnTransferAck(); } } @@ -418,7 +547,7 @@ SCSIBus::NegateATN() // バスフリーフェーズに移行。 // id はこのバスフリーを行ったデバイス (0..7) void -SCSIBus::BusFree(int id) +SCSIBus::BusFree(uint id) { putlog(4, "%s", __func__); @@ -427,7 +556,7 @@ SCSIBus::BusFree(int id) // 即全デバイスに通知。 for (int i = 0; i < countof(device); i++) { if (device[i]) { - device[i]->BusFreeCB(id); + device[i]->OnBusFree(id); } } @@ -465,11 +594,11 @@ SCSIBus::BusFree(int id) // 200usec 以内…なので適当に間をとって 100usec 後に Selection() コールバック // を呼び出す(1+2)。 // -// Selection() コールバック(が呼び出す device->SelectionCB()) は通常 BSY を +// Selection() コールバック(が呼び出す device->OnSelected()) は通常 BSY を // アサートするはずであり、そこから 90nsec 後に SelectionAck() コールバック // を呼び出す(3)。 // -// SelectionAck() コールバック(が呼び出す device->SelectionAckCB()) は通常 +// SelectionAck() コールバック(が呼び出す device->OnSelectionAck()) は通常 // SEL をネゲートするはずであり、そこから情報転送フェーズへの移行には特に // 規定はなさそうなので、400nsec 後に StartTransfer() をコールバックして // デバイスを情報転送フェーズに移行させる。 @@ -510,7 +639,7 @@ SCSIBus::Selected(Event& ev) { // ターゲットに通知。ターゲットがいることは確定している。 assert(device[target_id]); - device[target_id]->SelectionCB(); + device[target_id]->OnSelected(); } // ターゲットが BSY を立てて規定時間経ったので @@ -520,7 +649,7 @@ SCSIBus::SelectionAck(Event& ev) { // イニシエータに通知。 assert(device[init_id]); - device[init_id]->SelectionAckCB(); + device[init_id]->OnSelectionAck(); } // 情報転送フェーズ開始を通知する。 @@ -531,5 +660,82 @@ SCSIBus::StartTransfer(Event& ev) SetPhase(SCSI::Phase::Transfer); assert(device[target_id]); - device[target_id]->StartTransferCB(); + device[target_id]->OnStartTransfer(); +} + +// SCSI デバイスモニタ +// (どこでやるのがいいか分からんけど、SCSI デバイスを一括して表示したいので +// とりあえずここで) +void +SCSIBus::MonitorUpdate(Monitor *, TextScreen& screen) +{ + 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; + + const SCSIDisk *disk = dynamic_cast(dev); + if (disk && disk->IsMediumLoaded()) { + desc += string_format("(%uMB)", + (uint)(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; + } + + 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 %u", 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++; + + y++; + } }