--- nono/vm/scsibus.cpp 2026/04/29 17:04:36 1.1.1.3 +++ nono/vm/scsibus.cpp 2026/04/29 17:05:10 1.1.1.10 @@ -4,29 +4,28 @@ // Licensed under nono-license.txt // +// // SCSI バス +// #include "scsibus.h" #include "scsidev.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_); \ + gScheduler->RestartEvent(event); \ } while (0) // コンストラクタ SCSIBus::SCSIBus() + : inherited("SCSIBus") { - logname = "scsibus"; - devname = "SCSIBus"; - - event.dev = this; - event.SetName("SCSIBus"); // 複数出来たら名前どうするべか + event.Regist("SCSIBus"); // 複数出来たら名前どうするべか } // デストラクタ @@ -34,6 +33,37 @@ SCSIBus::~SCSIBus() { } +// 電源オン/リセット +void +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; + } + + // ここでのイベントは実際にはイニシエータかターゲットのどちらかが + // 時間を伴う処理をしている最中であることを示しているものなので、 + // イニシエータがリセットされたこととバスの動作とは本来関係ないのだが + // ここでは特にイニシエータに次のバイトを転送させる、という目的で + // タイマーイベントが使われており、本体をリセットしたのに入れ違いで + // イニシエータの転送処理が呼び出されてしまうというような事故を + // 防ぐためにもここでタイマーを停止する。 + gScheduler->StopEvent(event); +} + // このバスにデバイスを接続する (初期化時に使う) bool SCSIBus::Attach(SCSIDevice *dev, int id) @@ -148,8 +178,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 < 8; i++) { + if (i != id) { + NegateBSY(i); + } + } +#endif } } @@ -166,7 +217,17 @@ SCSIBus::NegateSEL(uint id) sel &= ~(1 << id); - if (GetPhase() == SCSI::Phase::Selection) { + if (GetPhase() == SCSI::Phase::BusFree) { + // バスフリーフェーズで SEL を下げる + + // 通常は起きないはずだけど、転送フェーズ中に LUNA をリセットすると + // -> LUNA の PROM が SEL を上げる (プロトコル違反) + // -> ターゲットは転送フェーズ中なのに SEL が上がったのを検出して + // BSY を取り下げてバスフリーに移行する + // -> バスフリーがイニシエータに通知され、イニシエータが SEL を下げる + // となるので、このケースはスルーしてよい。 + + } else if (GetPhase() == SCSI::Phase::Selection) { // セレクションフェーズで SEL を下げる if (bsy) { // BSY が立っていれば、ターゲットがセレクションに応答したので @@ -181,8 +242,8 @@ SCSIBus::NegateSEL(uint id) BusFree(id); } } else { - // どうする? - PANIC("NegateSEL in %s phase\n", GetPhaseName()); + // 下げる分には無視してもいいはずだけど、どうする? + VMPANIC("NegateSEL in %s phase", GetPhaseName()); } } @@ -218,7 +279,7 @@ SCSIBus::AssertBSY(uint id) } else if (GetPhase() == SCSI::Phase::Arbitration) { // アービトレーションフェーズだったら、アービトレーションに参加。 // 今は起きない。 - PANIC("AssertBSY(#%d) in arbitration", id); + VMPANIC("AssertBSY(#%d) in arbitration", id); } else if (GetPhase() == SCSI::Phase::Selection) { // セレクションフェーズだったら、転送フェーズに移行中。 @@ -228,7 +289,7 @@ SCSIBus::AssertBSY(uint id) CallAfter(SelectionAck, "SCSIBus Selection Ack", 90_nsec); } else { // どうする? - PANIC("AssertBSY(#%d) in %s phase\n", id, GetPhaseName()); + VMPANIC("AssertBSY(#%d) in %s phase", id, GetPhaseName()); } } @@ -262,7 +323,7 @@ SCSIBus::NegateBSY(uint id) } else { // どうする? - PANIC("NegateBSY(#%d) in %s phase\n", id, GetPhaseName()); + VMPANIC("NegateBSY(#%d) in %s phase", id, GetPhaseName()); } } @@ -298,6 +359,12 @@ SCSIBus::AssertREQ() if (now < last_req_time + 1_usec) { after = (last_req_time + 1_usec) - now; } + // ウェイトが指示されていれば入れる (シークタイムなど) + if (req_wait != 0) { + after += req_wait; + req_wait = 0; + } + last_req_time = now + after; CallAfter(TransferReq, "SCSIBus AssertREQ", after); } else { // 転送フェーズでないのに上げることはないと思うけど @@ -310,7 +377,7 @@ SCSIBus::AssertREQ() // ターゲットが REQ を立てようとして所定時間経ったので REQ を立てて // イニシエータの TransferReqCB を呼び出すところ。 void -SCSIBus::TransferReq(int dummy) +SCSIBus::TransferReq(Event& ev) { putlog(4, "AssertREQ"); req = true; @@ -430,7 +497,7 @@ SCSIBus::BusFree(int id) // セレクションフェーズ開始。 void -SCSIBus::SelectionStart() +SCSIBus::SelectionStart(Event& ev) { // この時点でデータバスには自身の ID と相手の ID が立っているはず。 // 一方 sel は SEL 信号線を立てた人のビットを持っているので、 @@ -460,7 +527,7 @@ SCSIBus::SelectionStart() // SelectionStart() にてターゲットが選択されて規定時間経ったので // そろそろターゲットが BSY を立てる頃に呼ばれるコールバック。 void -SCSIBus::Selected(int dummy) +SCSIBus::Selected(Event& ev) { // ターゲットに通知。ターゲットがいることは確定している。 assert(device[target_id]); @@ -468,9 +535,9 @@ SCSIBus::Selected(int dummy) } // ターゲットが BSY を立てて規定時間経ったので -// そろそろイニシエータが SEL を下げることに呼ばれるコールバック。 +// そろそろイニシエータが SEL を下げる頃に呼ばれるコールバック。 void -SCSIBus::SelectionAck(int dummy) +SCSIBus::SelectionAck(Event& ev) { // イニシエータに通知。 assert(device[init_id]); @@ -480,7 +547,7 @@ SCSIBus::SelectionAck(int dummy) // 情報転送フェーズ開始を通知する。 // BSY を上げて情報転送フェーズに移行したところで呼ばれる。 void -SCSIBus::StartTransfer(int dummy) +SCSIBus::StartTransfer(Event& ev) { SetPhase(SCSI::Phase::Transfer);