--- nono/vm/scsibus.cpp 2026/04/29 17:04:29 1.1 +++ nono/vm/scsibus.cpp 2026/04/29 17:04:59 1.1.1.9 @@ -1,6 +1,7 @@ // // nono -// Copyright (C) 2020 isaki@NetBSD.org +// Copyright (C) 2020 nono project +// Licensed under nono-license.txt // // SCSI バス @@ -15,16 +16,13 @@ event.SetName(name_); \ event.code = 0; \ event.time = time_; \ - gScheduler->AddEvent(&event); \ + event.Start(); \ } while (0) // コンストラクタ SCSIBus::SCSIBus() + : inherited("SCSIBus") { - logname = "scsibus"; - devname = "SCSIBus"; - - event.dev = this; event.SetName("SCSIBus"); // 複数出来たら名前どうするべか } @@ -33,6 +31,42 @@ SCSIBus::~SCSIBus() { } +// 電源オン +bool +SCSIBus::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; + + return true; +} + +// リセット +void +SCSIBus::ResetHard() +{ + // ここでのイベントは実際にはイニシエータかターゲットのどちらかが + // 時間を伴う処理をしている最中であることを示しているものなので、 + // イニシエータがリセットされたこととバスの動作とは本来関係ないのだが + // ここでは特にイニシエータに次のバイトを転送させる、という目的で + // タイマーイベントが使われており、本体をリセットしたのに入れ違いで + // イニシエータの転送処理が呼び出されてしまうというような事故を + // 防ぐためにもここでタイマーを停止する。 + event.Stop(); +} + // このバスにデバイスを接続する (初期化時に使う) bool SCSIBus::Attach(SCSIDevice *dev, int id) @@ -147,8 +181,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 } } @@ -165,7 +220,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 が立っていれば、ターゲットがセレクションに応答したので @@ -180,7 +245,7 @@ SCSIBus::NegateSEL(uint id) BusFree(id); } } else { - // どうする? + // 下げる分には無視してもいいはずだけど、どうする? PANIC("NegateSEL in %s phase\n", GetPhaseName()); } } @@ -293,10 +358,16 @@ SCSIBus::AssertREQ() // 通知はイベントにしないといけないので。 uint64 after = 0; - uint64 now = gScheduler->GetVirtTime(); + uint64 now = gMPU->GetVirtTime(); 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 { // 転送フェーズでないのに上げることはないと思うけど @@ -309,7 +380,7 @@ SCSIBus::AssertREQ() // ターゲットが REQ を立てようとして所定時間経ったので REQ を立てて // イニシエータの TransferReqCB を呼び出すところ。 void -SCSIBus::TransferReq(int dummy) +SCSIBus::TransferReq(Event& ev) { putlog(4, "AssertREQ"); req = true; @@ -429,7 +500,7 @@ SCSIBus::BusFree(int id) // セレクションフェーズ開始。 void -SCSIBus::SelectionStart() +SCSIBus::SelectionStart(Event& ev) { // この時点でデータバスには自身の ID と相手の ID が立っているはず。 // 一方 sel は SEL 信号線を立てた人のビットを持っているので、 @@ -438,7 +509,7 @@ SCSIBus::SelectionStart() if (__builtin_popcount(target_mask) != 1) { // データバスから自身を除いた結果立ってるビットが1個以外だったら // 誰も応答しないので、何もせず帰ってよい。 - putlog(3, "%s invalid databus=$%02x", __func__, data); + putlog(3, "%s (No target specified; databus=$%02x)", __func__, data); return; } @@ -459,7 +530,7 @@ SCSIBus::SelectionStart() // SelectionStart() にてターゲットが選択されて規定時間経ったので // そろそろターゲットが BSY を立てる頃に呼ばれるコールバック。 void -SCSIBus::Selected(int dummy) +SCSIBus::Selected(Event& ev) { // ターゲットに通知。ターゲットがいることは確定している。 assert(device[target_id]); @@ -467,9 +538,9 @@ SCSIBus::Selected(int dummy) } // ターゲットが BSY を立てて規定時間経ったので -// そろそろイニシエータが SEL を下げることに呼ばれるコールバック。 +// そろそろイニシエータが SEL を下げる頃に呼ばれるコールバック。 void -SCSIBus::SelectionAck(int dummy) +SCSIBus::SelectionAck(Event& ev) { // イニシエータに通知。 assert(device[init_id]); @@ -479,7 +550,7 @@ SCSIBus::SelectionAck(int dummy) // 情報転送フェーズ開始を通知する。 // BSY を上げて情報転送フェーズに移行したところで呼ばれる。 void -SCSIBus::StartTransfer(int dummy) +SCSIBus::StartTransfer(Event& ev) { SetPhase(SCSI::Phase::Transfer);