--- nono/vm/scsibus.cpp 2026/04/29 17:04:45 1.1.1.5 +++ nono/vm/scsibus.cpp 2026/04/29 17:06:00 1.1.1.17 @@ -4,29 +4,114 @@ // Licensed under nono-license.txt // +// // 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 "scsidomain.h" +#include "event.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.time = time_; \ - event.Start(); \ + event->SetCallback(&SCSIBus::func_); \ + event->time = time_; \ + event->SetName(name_); \ + scheduler->RestartEvent(event); \ } while (0) // コンストラクタ -SCSIBus::SCSIBus() +SCSIBus::SCSIBus(SCSIDomain *domain_) + : inherited(OBJ_SCSIBUS) { - logname = "scsibus"; - devname = "SCSIBus"; - - event.dev = this; - event.SetName("SCSIBus"); // 複数出来たら名前どうするべか + domain = domain_; } // デストラクタ @@ -34,10 +119,42 @@ SCSIBus::~SCSIBus() { } -// リセット +// 初期化 +bool +SCSIBus::Init() +{ + // 現時点で管理下にあるデバイス(ターゲット)をバスに接続。 + // イニシエータはこの時点では ID 未定なのでバスには繋がらない。 + Refresh(); + + auto evman = GetEventManager(); + // 複数出来たら名前どうするべか + event = evman->Regist(this, NULL, "SCSIBus"); + + 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,24 +162,15 @@ SCSIBus::ResetHard() // タイマーイベントが使われており、本体をリセットしたのに入れ違いで // イニシエータの転送処理が呼び出されてしまうというような事故を // 防ぐためにもここでタイマーを停止する。 - event.Stop(); + scheduler->StopEvent(event); } -// このバスにデバイスを接続する (初期化時に使う) -bool -SCSIBus::Attach(SCSIDevice *dev, int id) +// デバイス情報を更新する。 +// domain の device[] が変更されたら呼ぶこと。 +void +SCSIBus::Refresh() { - if (device[id] != NULL) { - putlog(0, "id=%d already exist?", id); - return false; - } - putlog(3, "Attach #%d", id); - - // バスとデバイスは相互に接続する。どちらも所有のないただのポインタ。 - device[id] = dev; - dev->Attach(this, id); - - return true; + domain->GetDevices(&device); } // RST を上げる @@ -79,9 +187,9 @@ SCSIBus::AssertRST() rst = true; // 即バスリセットを全員に通知 - for (int i = 0; i < 8; i++) { + for (int i = 0; i < device.size(); i++) { if (device[i]) { - device[i]->BusResetCB(); + device[i]->OnBusReset(); } } @@ -105,14 +213,14 @@ SCSIBus::NegateRST() void SCSIBus::AssertSEL(uint id) { - putlog(4, "%s(#%d)", __func__, 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) { // バスフリーフェーズなら、アービトレーションなしでセレクション @@ -162,8 +270,29 @@ SCSIBus::AssertSEL(uint id) init_id = id; } else { - // どうする? - PANIC("AssertSEL in %s phase\n", GetPhaseName()); + // それ以外(転送フェーズ)中に SEL が立てられたらどうすればいいか。 + // これは転送中に LUNA をリセットすると起きる。LUNA の PROM は SPC に + // 対してバスリセットを発行せずに、セレクションを行おうとするため。 + // SCSI 本には載ってないので分からないが、このような規約違反はたいてい + // イニシエータが検出してバスフリーかバスリセットを行うことが意図 + // されているんじゃないかと想像するけど、今回そのイニシエータが違反 + // してるので、どうするんだろうね。 + // 賢いターゲットデバイスだとこれを検出して自主的にバスフリーへ落ちる + // だろうか。賢くないターゲットデバイスは検出せずにそのまま (そして + // デッドロック) になるだろうか。 + +#if 1 + // XXX とりあえず LUNA で SCSI がなんか刺さった時に本体リセットでも + // 直らない状況を真似するため、ターゲットは何もしないでおく。 +#else + // ターゲットデバイスは賢いので、全員イニシエータ様に忖度して + // 即座にバスフリーへ移行するというのはどうか。 + for (uint i = 0; i < device.size(); i++) { + if (i != id) { + NegateBSY(i); + } + } +#endif } } @@ -171,16 +300,26 @@ 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) { + if ((sel & (1U << id)) == 0) { return; } - sel &= ~(1 << id); + sel &= ~(1U << id); + + if (GetPhase() == SCSI::Phase::BusFree) { + // バスフリーフェーズで SEL を下げる + + // 通常は起きないはずだけど、転送フェーズ中に LUNA をリセットすると + // -> LUNA の PROM が SEL を上げる (プロトコル違反) + // -> ターゲットは転送フェーズ中なのに SEL が上がったのを検出して + // BSY を取り下げてバスフリーに移行する + // -> バスフリーがイニシエータに通知され、イニシエータが SEL を下げる + // となるので、このケースはスルーしてよい。 - if (GetPhase() == SCSI::Phase::Selection) { + } else if (GetPhase() == SCSI::Phase::Selection) { // セレクションフェーズで SEL を下げる if (bsy) { // BSY が立っていれば、ターゲットがセレクションに応答したので @@ -195,8 +334,8 @@ SCSIBus::NegateSEL(uint id) BusFree(id); } } else { - // どうする? - PANIC("NegateSEL in %s phase\n", GetPhaseName()); + // 下げる分には無視してもいいはずだけど、どうする? + VMPANIC("NegateSEL in %s phase", GetPhaseName()); } } @@ -204,14 +343,14 @@ 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))) { + if ((bsy & (1U << id))) { return; } - bsy |= (1 << id); + bsy |= (1U << id); if (GetPhase() == SCSI::Phase::BusFree) { // バスフリーフェーズだったらアービトレーション開始 @@ -232,7 +371,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) { // セレクションフェーズだったら、転送フェーズに移行中。 @@ -242,7 +381,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()); } } @@ -250,14 +389,14 @@ 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) { + if ((bsy & (1U << id)) == 0) { return; } - bsy &= ~(1 << id); + bsy &= ~(1U << id); if (GetPhase() == SCSI::Phase::Transfer) { // 転送フェーズから BSY を下げたらバスフリーに移行。 @@ -276,7 +415,7 @@ SCSIBus::NegateBSY(uint id) } else { // どうする? - PANIC("NegateBSY(#%d) in %s phase\n", id, GetPhaseName()); + VMPANIC("NegateBSY(#%u) in %s phase", id, GetPhaseName()); } } @@ -308,7 +447,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; } @@ -317,6 +456,7 @@ SCSIBus::AssertREQ() after += req_wait; req_wait = 0; } + last_req_time = now + after; CallAfter(TransferReq, "SCSIBus AssertREQ", after); } else { // 転送フェーズでないのに上げることはないと思うけど @@ -327,15 +467,15 @@ SCSIBus::AssertREQ() } // ターゲットが REQ を立てようとして所定時間経ったので REQ を立てて -// イニシエータの TransferReqCB を呼び出すところ。 +// イニシエータの OnTransferReq を呼び出すところ。 void -SCSIBus::TransferReq(Event& ev) +SCSIBus::TransferReq(Event *ev) { putlog(4, "AssertREQ"); req = true; assert(device[init_id]); - device[init_id]->TransferReqCB(); + device[init_id]->OnTransferReq(); } // REQ を下げる @@ -355,7 +495,7 @@ SCSIBus::AssertACK() if (GetPhase() == SCSI::Phase::Transfer) { assert(device[target_id]); - device[target_id]->TransferCB(); + device[target_id]->OnTransfer(); } } @@ -368,7 +508,7 @@ SCSIBus::NegateACK() if (GetPhase() == SCSI::Phase::Transfer) { assert(device[target_id]); - device[target_id]->TransferAckCB(); + device[target_id]->OnTransferAck(); } } @@ -391,16 +531,16 @@ SCSIBus::NegateATN() // バスフリーフェーズに移行。 // id はこのバスフリーを行ったデバイス (0..7) void -SCSIBus::BusFree(int id) +SCSIBus::BusFree(uint id) { putlog(4, "%s", __func__); 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]->BusFreeCB(id); + device[i]->OnBusFree(id); } } @@ -438,18 +578,18 @@ 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() をコールバックして // デバイスを情報転送フェーズに移行させる。 // セレクションフェーズ開始。 void -SCSIBus::SelectionStart(Event& ev) +SCSIBus::SelectionStart(Event *ev) { // この時点でデータバスには自身の ID と相手の ID が立っているはず。 // 一方 sel は SEL 信号線を立てた人のビットを持っているので、 @@ -479,30 +619,31 @@ SCSIBus::SelectionStart(Event& ev) // SelectionStart() にてターゲットが選択されて規定時間経ったので // そろそろターゲットが BSY を立てる頃に呼ばれるコールバック。 void -SCSIBus::Selected(Event& ev) +SCSIBus::Selected(Event *ev) { // ターゲットに通知。ターゲットがいることは確定している。 assert(device[target_id]); - device[target_id]->SelectionCB(); + device[target_id]->OnSelected(); } // ターゲットが BSY を立てて規定時間経ったので // そろそろイニシエータが SEL を下げる頃に呼ばれるコールバック。 void -SCSIBus::SelectionAck(Event& ev) +SCSIBus::SelectionAck(Event *ev) { // イニシエータに通知。 assert(device[init_id]); - device[init_id]->SelectionAckCB(); + device[init_id]->OnSelectionAck(); } // 情報転送フェーズ開始を通知する。 // BSY を上げて情報転送フェーズに移行したところで呼ばれる。 void -SCSIBus::StartTransfer(Event& ev) +SCSIBus::StartTransfer(Event *ev) { SetPhase(SCSI::Phase::Transfer); assert(device[target_id]); - device[target_id]->StartTransferCB(); + device[target_id]->OnStartTransfer(); } +