--- nono/vm/scsibus.cpp 2026/04/29 17:05:33 1.1.1.13 +++ nono/vm/scsibus.cpp 2026/04/29 17:05:50 1.1.1.16 @@ -8,31 +8,110 @@ // SCSI バス // +// フェーズ遷移についてのメモ。 +// +// イニシエータ(この時点ではまだ候補だが)が BSY を立てることで +// (バスフリーフェーズから) アービトレーションフェーズに移る。 +// 現行では規定時間後に自動的にイニシエータが勝利する(バスは何もしない)。 +// イニシエータが SEL 立てて BSY を下ろすことで、セレクションフェーズに移る。 +// この間の遷移は以下の通り。 +// BSY SEL +// 0 0 BusFree +// ↓ イニシエータ(候補)が BSY を立てる +// 1 0 Arbitration (バスは何もしない) +// ↓ イニシエータが SEL を立てる +// 1 1 Arbitration +// ↓ イニシエータがデータバスにターゲットをセットして BSY を下ろす +// 0 1 Selection +// +// バスはセレクションフェーズ遷移後、規定時間でターゲットに通知。 +// ターゲットは即 BSY を立てるのでバスはこれをイニシエータに通知。 +// イニシエータは SEL を下げるのでバスはこれをターゲットに通知。 +// この間の遷移は以下の通り。 +// BSY SEL +// 0 1 Selection (ターゲットに通知) +// ↓ ターゲットが BSY を立てる +// 1 1 Selection (イニシエータに通知) +// ↓ イニシエータがデータバスと SEL を下げる +// 1 0 Transfer (ターゲットに通知) +// +// バスは Transfer 後、規定時間でターゲットに通知。 +// ターゲットは転送フェーズ(の最初のコマンドフェーズ) に移行する。 + +// 転送フェーズについてのメモ。 +// +// -----> (実線) は関数呼び出し +// - - -> (破線) は関数からの戻り +// 〜〜-> (波線) はイベントコールバックによる呼び出し +// +// マニュアル転送は以下のようになる。 +// 基本的にはターゲットが REQ を上げたことをイベントでイニシエータに通知し、 +// イニシエータは基本的に関数呼び出しでターゲットを操作することにする。 +// +// 非同期転送時の REQ/ACK シーケンスに時間規定はないが、時間を考慮せずに +// 実行してしまうと現実より速い転送が出来てしまう可能性があるので、ここでは +// AssertREQ が TransferReqCB を呼び出すところでタイミングを調整する。 +// 具体的には前回の TransferReqCB から 1usec 以上空けるというのはどうか。 +// +// イニシエータ ターゲット +// ------------ ---------- +// Event 〜〜-> StartTransferCB +// ↓ +// REQ を上げる +// TransferReqCB <-〜〜〜 Event 〜〜〜 +// ACK を上げる +// +------ AssertACK() ---> TransferCB +// 1バイト送受信 +// REQ を下げる +// < - - - - - - - - - - - return; +// (誰かが) +// ACK を下げる +// +------ NegateACK() ---> TransferAckCB +// REQ を上げる 〜〜+ +// < - - - - - - - - - - - return; : +// : +// TransferReqCB <-〜〜〜 Event 〜〜〜〜〜〜〜〜〜〜〜〜+ +// +// ハードウェア転送の場合は ACK 一往復を省略して以下のようにする。 +// Transfer() (TransferCB()) は ACK が立っていなければハードウェア転送と +// 判断する。 +// +// イニシエータ ターゲット +// ------------ ---------- +// Event 〜〜-> StartTransferCB +// ↓ +// REQ を上げる +// TransferReqCB <-〜〜〜 Event 〜〜〜 +// | +// +------- Transfer() ---> TransferCB +// 1バイト送受信 +// REQ を下げる +// REQ を上げる 〜〜+ +// < - - - - - - - - - - - return; : +// : +// TransferReqCB <-〜〜〜 Event 〜〜〜〜〜〜〜〜〜〜〜〜+ +// + #include "scsibus.h" #include "scsidev.h" -#include "config.h" -#include "monitor.h" +#include "scsidomain.h" +#include "event.h" #include "scheduler.h" // time 時間後に呼び出すコールバックをセットする。 // func の書式が面倒なのを省略して書きたいため。 #define CallAfter(func_, name_, time_) do { \ - event.func = ToEventCallback(&SCSIBus::func_); \ - event.time = time_; \ - event.SetName(name_); \ + event->func = ToEventCallback(&SCSIBus::func_); \ + event->time = time_; \ + event->SetName(name_); \ scheduler->RestartEvent(event); \ } while (0) // コンストラクタ -SCSIBus::SCSIBus(SCSIHostDevice *parent_, const char *config_name_) +SCSIBus::SCSIBus(SCSIDomain *domain_) : inherited(OBJ_SCSIBUS) { - parent = parent_; - config_name = config_name_; - - monitor = gMonitorManager->Regist(ID_MONITOR_SCSIDEVS, this); - monitor->func = ToMonitorCallback(&SCSIBus::MonitorUpdate); - // サイズは Create で決まる + domain = domain_; } // デストラクタ @@ -40,71 +119,17 @@ 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); + // 現時点で管理下にあるデバイス(ターゲット)をバスに接続。 + // イニシエータはこの時点では ID 未定なのでバスには繋がらない。 + Refresh(); + + auto evman = GetEventManager(); + // 複数出来たら名前どうするべか + event = evman->Regist(this, NULL, "SCSIBus"); return true; } @@ -140,53 +165,12 @@ SCSIBus::ResetHard(bool poweron) scheduler->StopEvent(event); } -// デバイスを接続する。 -// id に先客がいれば false を返す。成功すれば true を返す。 -bool -SCSIBus::AttachDevice(SCSIDevice *dev, int id) -{ - if (device[id] != NULL) { - return false; - } - putlog(3, "Attach #%u", id); - - device[id] = dev; - MakeConnectedDevices(); - - // 紐付けをデバイスにも通知。 - dev->OnAttached(this, id); - - return true; -} - -// device[] から connected_device を作り直す。 -// device[] はイニシエータを含む固定長の配列。 -// connected_device はイニシエータを (あれば) 含む ID 順の可変長リスト。 +// デバイス情報を更新する。 +// domain の device[] が変更されたら呼ぶこと。 void -SCSIBus::MakeConnectedDevices() +SCSIBus::Refresh() { - 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; + domain->GetDevices(&device); } // RST を上げる @@ -203,7 +187,7 @@ SCSIBus::AssertRST() rst = true; // 即バスリセットを全員に通知 - for (int i = 0; i < 8; i++) { + for (int i = 0; i < device.size(); i++) { if (device[i]) { device[i]->OnBusReset(); } @@ -232,11 +216,11 @@ SCSIBus::AssertSEL(uint id) putlog(4, "%s(#%u)", __func__, id); // すでに上がってたら何もしない - if ((sel & (1 << id))) { + if ((sel & (1U << id))) { return; } - sel |= (1 << id); + sel |= (1U << id); if (GetPhase() == SCSI::Phase::BusFree) { // バスフリーフェーズなら、アービトレーションなしでセレクション @@ -303,7 +287,7 @@ SCSIBus::AssertSEL(uint id) #else // ターゲットデバイスは賢いので、全員イニシエータ様に忖度して // 即座にバスフリーへ移行するというのはどうか。 - for (uint i = 0; i < 8; i++) { + for (uint i = 0; i < device.size(); i++) { if (i != id) { NegateBSY(i); } @@ -319,11 +303,11 @@ SCSIBus::NegateSEL(uint id) putlog(4, "%s(#%u)", __func__, id); // すでに下りてたら何もしない - if ((sel & (1 << id)) == 0) { + if ((sel & (1U << id)) == 0) { return; } - sel &= ~(1 << id); + sel &= ~(1U << id); if (GetPhase() == SCSI::Phase::BusFree) { // バスフリーフェーズで SEL を下げる @@ -362,11 +346,11 @@ SCSIBus::AssertBSY(uint id) putlog(4, "%s(#%u)", __func__, id); // すでに上がってたら何もしない - if ((bsy & (1 << id))) { + if ((bsy & (1U << id))) { return; } - bsy |= (1 << id); + bsy |= (1U << id); if (GetPhase() == SCSI::Phase::BusFree) { // バスフリーフェーズだったらアービトレーション開始 @@ -408,11 +392,11 @@ SCSIBus::NegateBSY(uint id) putlog(4, "%s(#%u)", __func__, id); // すでに下りてたら何もしない - if ((bsy & (1 << id)) == 0) { + if ((bsy & (1U << id)) == 0) { return; } - bsy &= ~(1 << id); + bsy &= ~(1U << id); if (GetPhase() == SCSI::Phase::Transfer) { // 転送フェーズから BSY を下げたらバスフリーに移行。 @@ -485,7 +469,7 @@ SCSIBus::AssertREQ() // ターゲットが REQ を立てようとして所定時間経ったので REQ を立てて // イニシエータの OnTransferReq を呼び出すところ。 void -SCSIBus::TransferReq(Event& ev) +SCSIBus::TransferReq(Event *ev) { putlog(4, "AssertREQ"); req = true; @@ -554,7 +538,7 @@ SCSIBus::BusFree(uint id) SetPhase(SCSI::Phase::BusFree); // 即全デバイスに通知。 - for (int i = 0; i < countof(device); i++) { + for (int i = 0; i < device.size(); i++) { if (device[i]) { device[i]->OnBusFree(id); } @@ -605,7 +589,7 @@ SCSIBus::BusFree(uint id) // セレクションフェーズ開始。 void -SCSIBus::SelectionStart(Event& ev) +SCSIBus::SelectionStart(Event *ev) { // この時点でデータバスには自身の ID と相手の ID が立っているはず。 // 一方 sel は SEL 信号線を立てた人のビットを持っているので、 @@ -635,7 +619,7 @@ SCSIBus::SelectionStart(Event& ev) // SelectionStart() にてターゲットが選択されて規定時間経ったので // そろそろターゲットが BSY を立てる頃に呼ばれるコールバック。 void -SCSIBus::Selected(Event& ev) +SCSIBus::Selected(Event *ev) { // ターゲットに通知。ターゲットがいることは確定している。 assert(device[target_id]); @@ -645,7 +629,7 @@ SCSIBus::Selected(Event& ev) // ターゲットが BSY を立てて規定時間経ったので // そろそろイニシエータが SEL を下げる頃に呼ばれるコールバック。 void -SCSIBus::SelectionAck(Event& ev) +SCSIBus::SelectionAck(Event *ev) { // イニシエータに通知。 assert(device[init_id]); @@ -655,7 +639,7 @@ SCSIBus::SelectionAck(Event& ev) // 情報転送フェーズ開始を通知する。 // BSY を上げて情報転送フェーズに移行したところで呼ばれる。 void -SCSIBus::StartTransfer(Event& ev) +SCSIBus::StartTransfer(Event *ev) { SetPhase(SCSI::Phase::Transfer); @@ -663,79 +647,3 @@ SCSIBus::StartTransfer(Event& ev) 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++; - } -}