--- nono/vm/spc.cpp 2026/04/29 17:04:28 1.1 +++ nono/vm/spc.cpp 2026/04/29 17:04:29 1.1.1.2 @@ -7,11 +7,21 @@ #include "mpu.h" #include "mystring.h" #include "scheduler.h" -#include "scsitarget.h" #include "vm.h" +// time 時間後に呼び出すコールバックをセットする。 +// func の書式が面倒なのを省略して書きたいため。 +#define CallAfter(func_, name_, time_) do { \ + event.func = (DeviceCallback_t)&SPCDevice::func_; \ + event.SetName(name_); \ + event.code = 0; \ + event.time = time_; \ + gScheduler->AddEvent(&event); \ +} while (0) + SPCDevice *gSPC; +// コンストラクタ SPCDevice::SPCDevice(uint32 addr) { logname = "spc"; @@ -22,14 +32,22 @@ SPCDevice::SPCDevice(uint32 addr) devlen = 0x2000; config_name = "spc0"; + myid = -1; - monitor.Init(70, 17); + monitor.Init(70, 15); - // イベント + // 汎用イベント // 名前とコールバック関数は都度セットする。 event.dev = this; + event.SetName("SPC phase"); + + // TC カウンタイベント + event_tc.dev = this; + event_tc.code = 0; + event_tc.SetName("SPC Timer"); } +// デストラクタ SPCDevice::~SPCDevice() { } @@ -37,6 +55,10 @@ SPCDevice::~SPCDevice() bool SPCDevice::Init() { + if (!inherited::Init()) { + return false; + } + if (gVM->GetType() == VM::TYPE_LUNA) { regbase = 0xe1000000; regstride = 4; @@ -51,6 +73,7 @@ SPCDevice::Init() return true; } + // 本体リセットスイッチによるリセット void SPCDevice::ResetHard() @@ -88,6 +111,12 @@ SPCDevice::Reset() // OFFになることを NetBSD が期待しているし、 // このビットは落ちるのではないだろうか spc.sdgc = 0x00; + + // 内部状態 + set_atn_in_selection = false; + transfer_command_running = false; + gScheduler->DelEvent(&event); + gScheduler->DelEvent(&event_tc); } uint64 @@ -118,7 +147,7 @@ SPCDevice::Read8(uint32 addr) return data; case SPC::SCMD: - data = GetSCMD(); + data = spc.scmd; putlog(3, "SCMD -> $%02x", data); return data; @@ -150,16 +179,6 @@ SPCDevice::Read8(uint32 addr) return data; case SPC::SSTS: - // ここで DataIn フェーズを進めてみる。 - // 本来 DataIn フェーズでの DREG キューへのデータの充填は - // レジスタアクセスとは無関係に行われるはずだが、そんな機構は - // 面倒なので、(DREG Full/Empty を調べるために) SSTS をアクセスした - // ところで、データがあれば DREG キューを埋めるようにしておく。 - if (phase == SCSI::Phase::DataIn) { - EnqueueToDREG(); - } - - // ここからが本来の SSTS 読み込み data = GetSSTS(); if (loglevel >= 2) { static uint32 prev_ssts = -1U; @@ -194,22 +213,30 @@ SPCDevice::Read8(uint32 addr) // XFER_OUT は DREG アクセスでリセットされる spc.serr &= ~SPC::SERR_XFER_OUT; - // 受信データがあればここで DREG に充填 - EnqueueToDREG(); - if (spc.dreg.Length() > 0) { + // 受信データがあれば取り出す + if (__predict_true(spc.dreg.Length() > 0)) { + bool q_was_full = spc.dreg.IsFull(); data = spc.dreg.Dequeue(); - spc.tc--; if (loglevel >= 4) { - putlog("DREG -> $%02x (q=%d datalen=%d TC=%d)", - data, spc.dreg.Length(), datalen, spc.tc); + putlog("DREG -> $%02x (q=%d TC=%d)", + data, spc.dreg.Length(), spc.tc); } else { putlog(3, "DREG -> $%02x", data); } - if (spc.tc == 0) { + // この読み出しが最後だったらここでビジーフラグを下ろす。 + // ProgramTransfer() 内のコメントも参照。 + if (spc.tc == 0 && spc.dreg.Length() == 0) { + transfer_command_running = false; + spc.ssts &= ~(SPC::SSTS_SPC_BUSY | SPC::SSTS_IN_PROGRESS); SetINTS(SPC::INTS_COMPLETE); } + // この読み出しによってバッファに空きが出来たら転送再開。 + if (transfer_command_running && GetREQ() && q_was_full) { + // XXX 時間は適当 + CallAfter(ProgramTransfer, "SPC ReadDREG to Transfer", t_CLF); + } } else { - // どうなる? + // XXX どうなる? data = 0xff; putlog(3, "DREG -> $%02x (empty)", data); } @@ -272,8 +299,20 @@ SPCDevice::Write8(uint32 addr, uint32 da switch (an) { case SPC::BDID: - myid = data; - putlog(3, "BDID <- $%02x", myid); + putlog(3, "BDID <- $%02x", data); + if (myid == data) { + // 同じなら何もしない + return 0; + } + if (myid == -1) { + // 初回なのでバスに参加 + myid = data; + scsibus->Attach(this, myid); + } else { + // そうでなければ移動? + putlog(0, "BDID 変更 %d to %d not implemented", myid, data); + return 0; + } return 0; case SPC::SCTL: WriteSCTL(data); @@ -300,40 +339,20 @@ SPCDevice::Write8(uint32 addr, uint32 da data &= SPC::PCTL_MASK; putlog(3, "PCTL <- $%02x", data); spc.busfree_intr_enable = (data & SPC::PCTL_BFINT_EN); - SetMSG((data & SPC::PCTL_MSG)); - SetCD((data & SPC::PCTL_CD)); - SetIO((data & SPC::PCTL_IO)); + SetXfer(data); return 0; case SPC::DREG: - putlog(3, "DREG <- $%02x len=%d", data, datalen); + putlog(3, "DREG <- $%02x", data); // XFER_OUT は DREG アクセスでリセットされる spc.serr &= ~SPC::SERR_XFER_OUT; - if (phase == SCSI::Phase::Command) { - // コマンドフェーズなら DREG 使わず cmds に入れていく。 - cmds.push_back(data); - } else if (datalen > 0) { - // data が指定されていればそっちに書き込む。 - *dataptr++ = data; - datalen--; - } - spc.tc--; - if (spc.tc == 0) { - if (phase == SCSI::Phase::Command) { - // コマンドフェーズで全部受け取ったらコマンドを実行。 - if (loglevel >= 3) { - std::string cmdbuf = "Cmd ="; - for (uint i = 0; i < cmds.size(); i++) { - cmdbuf += string_format(" %02x", cmds[i]); - } - putlog("%s", cmdbuf.c_str()); - } - target[tgtid]->ExecCommand(cmds); - } else if (phase == SCSI::Phase::DataOut) { - // データアウトフェーズで全部受け取ったら? - } - SetINTS(SPC::INTS_COMPLETE); + // DREG キューに入れて.. + spc.dreg.Enqueue(data); + // プログラム転送中で REQ が来てれば送信。 + if (transfer_command_running && GetREQ()) { + // XXX 時間は適当 + CallAfter(ProgramTransfer, "SPC WriteDREG to Transfer", t_CLF); } return 0; @@ -341,28 +360,34 @@ SPCDevice::Write8(uint32 addr, uint32 da spc.temp_out = data; putlog(3, "TEMP <- $%02x", spc.temp_out); - // セレクション失敗後 SPC は SEL を下げないので TEMP に $00 を - // 書き込むことで SEL を下げるらしい - if (0) { //spc_phase == SCSIphase::Selection) { - BusFree(); + // Inside (p479) によると、SPC はセレクションタイムアウト時 SEL を + // 下げないので、代わりに TEMP に $00 を書き込むことで SEL を下げる、 + // そうなのだが、データシート PDF には記述が見当たらないし、 + // luna68k の /boot はここで SEL が下げてしまうと期待した動作に + // ならないようだ。 + // SPC はセレクションタイムアウトしたからと言って、データバスに出した + // ID を自動的に取り下げたりはしないので、手動でデータバスをクリア + // してあげないといけない (TEMP に $00 を書き込む)、だったんでは。 + if (0 && GetPhase() == SCSI::Phase::Selection) { + NegateSEL(); } // Out 方向ならバスに出す if (GetIO() == false) { - SetDATA(spc.temp_out); + SetData(spc.temp_out); } return 0; case SPC::TCH: spc.tc = (spc.tc & 0x00ffff) | (data << 16); - putlog(3, "TC(H) = $%06x", spc.tc); + putlog(3, "TCH <- $%02x (TC=$%06x)", data, spc.tc); return 0; case SPC::TCM: spc.tc = (spc.tc & 0xff00ff) | (data << 8); - putlog(3, "TC(M) = $%06x", spc.tc); + putlog(3, "TCM <- $%02x (TC=$%06x)", data, spc.tc); return 0; case SPC::TCL: spc.tc = (spc.tc & 0xffff00) | data; - putlog(3, "TC(L) = $%06x", spc.tc); + putlog(3, "TCL <- $%02x (TC=$%06x)", data, spc.tc); return 0; case SPC::EXBF: @@ -413,7 +438,7 @@ SPCDevice::Peek8(uint32 addr) break; case SPC::SCMD: - data = GetSCMD(); + data = spc.scmd; break; case SPC::TMOD: @@ -496,7 +521,7 @@ SPCDevice::MonitorUpdate() monitor.Clear(); sctl = spc.sctl; - scmd = GetSCMD(); + scmd = spc.scmd; ints = spc.ints; psns = GetPSNS(); sdgc = spc.sdgc; @@ -608,65 +633,86 @@ SPCDevice::MonitorUpdate() "BIE", "", "", "", "", "MSG", "C/D", "I/O" }; MonitorReg(x, y, pctl, pctl_bits); - monitor.Print(x + 15, y, "(%s)", SCSI::GetPhaseName(pctl & 7)); y++; // バス状態 x = 52; y = 0; - // ここは bool を返す GetBSY(), GetSEL() ではなく、 - // 生の BSY, SEL 変数を見ること。 - uint8 bsy = BSY; - uint8 sel = SEL; - monitor.Print(x, y++, "RST :%d", GetRST()); + const char *phname = ""; + uint8 rst = 0; + uint8 bsy = 0; + uint8 sel = 0; + uint8 msg = 0; + uint8 cd = 0; + uint8 io = 0; + uint8 atn = 0; + uint8 req = 0; + uint8 ack = 0; + uint8 data = 0; + if (bus) { + phname = bus->GetPhaseName(); + rst = GetRST(); + bsy = GetBSY(); + sel = GetSEL(); + msg = GetMSG(); + cd = GetCD(); + io = GetIO(); + atn = GetATN(); + req = GetREQ(); + ack = GetACK(); + data = GetData(); + } + monitor.Print(x, y++, "Phase: %s", phname); + monitor.Print(x, y++, "RST :%d", rst); monitor.Print(x, y++, "BSY :%d(________)", bsy ? 1 : 0); monitor.Print(x, y++, "SEL :%d(________)", sel ? 1 : 0); - monitor.Print(x, y++, "MSG :%d", GetMSG()); - monitor.Print(x, y++, "CD :%d", GetCD()); - monitor.Print(x, y++, "IO :%d", GetIO()); - monitor.Print(x, y++, "ATN :%d", GetATN()); - monitor.Print(x, y++, "REQ :%d", GetREQ()); - monitor.Print(x, y++, "ACK :%d", GetACK()); - monitor.Print(x, y++, "DATA:$%02x", GetDATA()); - - // BSY, SEL の内訳 - x = 59; - y = 1; + monitor.Print(x, y++, "MSG :%d", msg); + monitor.Print(x, y++, "CD :%d", cd); + monitor.Print(x, y++, "IO :%d", io); + monitor.Print(x, y++, "ATN :%d", atn); + monitor.Print(x, y++, "REQ :%d", req); + monitor.Print(x, y++, "ACK :%d", ack); + monitor.Print(x, y++, "DATA:$%02x", data); + + // BSY, SEL の内訳。"(76543210)" の順。 + x = 59 + 7; + y = 2; for (int i = 0; i < 8; i++) { if ((bsy & (1 << i))) { - monitor.Print(x + i, y, "%d", i); + monitor.Print(x - i, y, "%d", i); } else { - monitor.Print(x + i, y, "_"); + monitor.Print(x - i, y, "_"); } } y++; for (int i = 0; i < 8; i++) { if ((sel & (1 << i))) { - monitor.Print(x + i, y, "%d", i); + monitor.Print(x - i, y, "%d", i); } else { - monitor.Print(x + i, y, "_"); + monitor.Print(x - i, y, "_"); } } + // 転送フェーズ中なら現在の XferPhase + x = 58; + y += 2; + if (GetPhase() == SCSI::Phase::Transfer) { + monitor.Print(x, y, "(%s)", SCSI::GetXferPhaseName(GetXfer())); + } + // ターゲット x = 0; y = 14; - monitor.Print(x, y, "Target"); - if (tgtid >= 0) { - monitor.Print(x + 6, y, ": #%d", tgtid); - } - y++; - - monitor.Print(x, y, "Phase: %s", SCSI::GetPhaseName(phase)); - y++; - - monitor.Print(x, y, "Command:"); - if (phase < 8 && cmds.size() > 0) { - const char *cmdname = SCSI::GetCommandName(cmds[0]); - if (cmdname) { - monitor.Print(x + 9, y, "%s", cmdname); - } else { - monitor.Print(x + 9, y, "$%02x", cmds[0]); + SCSIDevice *d = bus->GetConnectedTarget(); + SCSITarget *t = dynamic_cast(d); + if (t) { + std::vector cmds = t->GetCmdSeq(); + if (cmds.size() > 0) { + const char *cmdname = SCSI::GetCommandName(cmds[0]); + monitor.Print(x, y, "Command: $%02x", cmds[0]); + if (cmdname) { + monitor.Print(x + 14, y, "%s", cmdname); + } } } @@ -707,41 +753,33 @@ SPCDevice::WriteSCTL(uint32 data) } } -// SCMD レジスタ値の取得 -// Read*() と Peek8() から呼ばれる。 -uint32 -SPCDevice::GetSCMD() const -{ - uint32 data; - - data = spc.scmd & 0xef; - // XXX RST Out もここで読み出せるのかな? - if (GetRST()) { - data |= SPC::SCMD_RST_OUT; - } - - return data; -} - // SCMD レジスタへの書き込み void SPCDevice::WriteSCMD(uint32 data) { + uint32 chg = spc.scmd ^ data; // 変更点 spc.scmd = data; putlog(3, "SCMD <- $%02x", spc.scmd); - // RST Out 操作は SCTL Reset がオフの時だけ有効 - if ((spc.sctl & SPC::SCTL_RESET) == 0) { - SetRST(spc.scmd & SPC::SCMD_RST_OUT); - - if (GetRST() == true) { - putlog(2, "SCSIBus バスリセット"); - - // 即イベント発生させる? - SetINTS(SPC::INTS_RESET_COND); + // Intercept, Program, Terminate Mode の未実装判定は + // 転送開始時のみ表示する。 - // バスフリーフェーズ - BusFree(); + // RST Out 操作は SCTL Reset がオフの時だけ有効 + // と書いてあるけど、SCTL Reset がオンならほぼ全機能無効なのでは? + if ((spc.sctl & SPC::SCTL_RESET) == 0 && (chg & SPC::SCMD_RST_OUT)) { + if ((spc.scmd & SPC::SCMD_RST_OUT)) { + // 0 -> 1 + // すでに RST が立ってたらどうするか + if (GetRST() == false) { + putlog(1, "SCMD RST Out"); + BusRelease(); + AssertRST(); + SetINTS(SPC::INTS_RESET_COND); + } + } else { + // 1 -> 0 + // 特にすることはない? + NegateRST(); } } @@ -754,33 +792,28 @@ SPCDevice::WriteSCMD(uint32 data) SelectCommand(); break; case 2: - putlog(0, "SCMD Reset ATN コマンド (未実装)"); + putlog(1, "SCMD Reset ATN コマンド (未実装)"); break; case 3: - putlog(2, "SCMD Set ATN コマンド (未実装)"); + SetATNCommand(); break; case 4: TransferCommand(); break; case 5: - putlog(2, "SCMD Transfer Pause コマンド"); + putlog(2, "SCMD Transfer Pause コマンド (未実装)"); break; case 6: putlog(2, "SCMD Reset ACK/REQ コマンド"); // イニシエータなら ACK を、ターゲットなら REQ をリセットする。 // ターゲットモードはサポートしないので、操作するのは ACK のみ。 - spc.ackreq = false; - SetACK(false); + NegateACK(); break; case 7: putlog(2, "SCMD Set ACK/REQ コマンド"); // イニシエータなら ACK を、ターゲットなら REQ をセットする。 // ターゲットモードはサポートしないので、操作するのは ACK のみ。 - spc.ackreq = true; - SetACK(true); - if (1) { - target[tgtid]->Ack(); - } + AssertACK(); break; } } @@ -808,28 +841,27 @@ SPCDevice::WriteINTS(uint32 data) // TIMEOUT ビットをクリアした時が if ((ack & SPC::INTS_TIMEOUT)) { // セレクションフェーズなら - if (GetBSY() == false && GetSEL() == true) { + if (GetPhase() == SCSI::Phase::Selection) { // TC がゼロならバスフリー // そうでなければリスタート if (spc.tc == 0) { BusFree(); } else { event.time = spc.tc * t_CLF * 2; - event.name = "SPC SelFailed"; - event.func = - (DeviceCallback_t)&SPCDevice::SelectionFailedCallback; + event.func = (DeviceCallback_t)&SPCDevice::SelectionTimeout; + event.SetName("SPC SelectionFailed"); gScheduler->AddEvent(&event); } } } - putlog(3, "INTS = $%02x", spc.ints); + putlog(3, "INTS <- $%02x (INTS = $%02x)", data, spc.ints); } -// INTS 設定 -// INTS レジスタにビットを立てる要因になる事象が起きた時にコールする。 -// INTS を書き換えて、必要なら割り込みを上げる。 -// INTS レジスタへの書き込みではないので注意。 +// INTS 設定。 +// INTS にビットを立てる要因が起きた時に data にそれを指定して呼び出す。 +// この関数は INTS を書き換えて、必要なら割り込みを上げる。 +// INTS レジスタへの書き込み(割り込み要因のクリア)は WriteINTS() のほう。 void SPCDevice::SetINTS(uint32 data) { @@ -839,10 +871,15 @@ SPCDevice::SetINTS(uint32 data) // 指定された要因を立てる spc.ints |= data; + // Disconnected 割り込みが起きたら ATN を下げる + if ((spc.ints & SPC::INTS_DISCONNECTED) && GetATN()) { + NegateATN(); + } + // 割り込み許可なら割り込み。 // RESET_COND はノンマスカブル割り込み。 if ((spc.sctl & SPC::SCTL_INTR_EN) || (spc.ints & SPC::INTS_RESET_COND)) { -putlog(3, "割り込み ints=%x up=%x", spc.ints, up); + putlog(3, "割り込み ints=$%02x up=$%02x", spc.ints, up); if (gVM->GetType() == VM::TYPE_LUNA) { gMPU->Interrupt(2); } else { @@ -854,12 +891,16 @@ putlog(3, "割り込み ints=%x up=%x", // PSNS レジスタ値の取得。 // Read*() と Peek8() から呼ばれる。 +// scsibus 接続前にもモニタの更新などで呼ばれることに注意。 uint32 SPCDevice::GetPSNS() const { - uint32 data; + uint32 data = 0; + + if (__predict_false(bus == NULL)) { + return 0; + } - data = spc.psns & 0xe0; if (GetREQ()) { data |= SPC::PSNS_REQ; } @@ -875,28 +916,21 @@ SPCDevice::GetPSNS() const if (GetBSY()) { data |= SPC::PSNS_BSY; } - if (GetMSG()) { - data |= SPC::PSNS_MSG; - } - if (GetCD()) { - data |= SPC::PSNS_CD; - } - if (GetIO()) { - data |= SPC::PSNS_IO; - } + data |= (uint32)GetXfer(); return data; } // SSTS レジスタ値の取得。 // Read*() と Peek8() から呼ばれる。 +// scsibus 接続前にもモニタの更新などで呼ばれることに注意。 uint32 SPCDevice::GetSSTS() const { uint32 data; data = spc.ssts & 0xf0; - if (GetRST()) { // RST 信号 + if (__predict_true(bus) && GetRST()) { // RST 信号 data |= SPC::SSTS_RST_IN; } if (spc.tc == 0) { // TC がゼロか @@ -914,25 +948,19 @@ SPCDevice::GetSSTS() const // PCTL レジスタ値の取得。 // Read*() と Peek8() から呼ばれる。 +// scsibus 接続前にもモニタの更新などで呼ばれることに注意。 uint32 SPCDevice::GetPCTL() const { uint32 data; data = 0; + if (__predict_true(bus)) { + data |= (uint32)GetXfer(); + } if (spc.busfree_intr_enable) { data |= SPC::PCTL_BFINT_EN; } - if (GetMSG()) { - data |= SPC::PCTL_MSG; - } - if (GetCD()) { - data |= SPC::PCTL_CD; - } - if (GetIO()) { - data |= SPC::PCTL_IO; - } - return data; } @@ -940,34 +968,36 @@ SPCDevice::GetPCTL() const void SPCDevice::BusFree() { - // バスの状態を変える - B_BusFree(myid); + putlog(3, "BusFree"); + + // ここは BDID 書き込み前 (まだ scsibus に自身をつなげる前) にも呼ばれる。 + // その場合は何もしない。バスフリーのはずでもあるし。 + if (bus == NULL) { + return; + } - // SCSI 仕様は BSY 下ろしてから最大 1200ns 以内に他の信号も下ろす - // SPC 仕様は BSY 下ろしてから最大 (6*t_CLF + 75) ns 以内に - // INTR が上がる。 - // ここでは適当に 600ns とする。 - event.time = 600; - event.name = "SPC BusFree"; - event.func = (DeviceCallback_t)&SPCDevice::BusFreeCallback; - event.code = 0; // SPC からのバスフリー - gScheduler->AddEvent(&event); + BusRelease(); } -// ターゲットがバスフリーフェーズに移行した +// バスをリリースする。バスフリーとバスリセットから呼ばれる void SPCDevice::BusRelease() { - putlog(3, "Target BusRelease"); - - // XXX SPC が SCSI バスからのバスフリーをどのぐらいの時間で - // 検出するのか調べていない。後で調べる - // ここでは適当に 600ns とする。 - event.time = 600; - event.name = "SPC BusFree"; - event.func = (DeviceCallback_t)&SPCDevice::BusFreeCallback; - event.code = 1; // SCSI バスからのバスフリー - gScheduler->AddEvent(&event); + // 自発的にバスをリリースする場合、今のフェーズから自分が変えていい + // BSY、SEL だけを落とす。それ以外の信号線は正しくはないけどとりあえず + // 一括して SCSIHostDevice::BusFreeCB() が落とすことにしてある。 + + switch (GetPhase()) { + case SCSI::Phase::BusFree: + case SCSI::Phase::Transfer: + break; + case SCSI::Phase::Arbitration: + case SCSI::Phase::Selection: + case SCSI::Phase::Reselection: + NegateSEL(); + NegateBSY(); + break; + } } // Select コマンド実行 @@ -976,11 +1006,10 @@ SPCDevice::SelectCommand() { std::string idstr; uint32 data; + uint32 tgtid; uint64 wait; - uint64 now; - // ターゲット ID を求めつつ、表示用の文字列を作成 - tgtid = -1; + // ターゲット ID を表示用に解析 data = spc.temp_out; if (data == 0) { // ID が一切立ってない? @@ -996,28 +1025,12 @@ SPCDevice::SelectCommand() // ターゲット ID がない idstr = string_format("#%d", myid); } else { - tgtid = 31 - __builtin_clz(data); + tgtid = DecodeID(data); idstr = string_format("#%d -> #%d", myid, tgtid); } } putlog(2, "Select コマンド (%s)", idstr.c_str()); - phase = SCSI::Phase::Selection; - // アービトレーションなしの場合 - // - // BSY X\_______________ .. _/~~~~~~~~~~~~~~~ - // |t_FRID| |t_BIDH| - // DB* ________ (6 + TCL) * t_CLF, (7 + TCL) * t_CLF + 140 - // t_IDSL = 11 * t_CLF - 80 - // t_BIDH = t_BLSH = 2 * t_CLF - // t_SHIR = 60 - // // アービトレーションありの場合 // // BSY X\______/~~~~~~~~~~~~~~~~~~~~~~~~~~\_ .. _/^^^^^^^^^^^^^^^ @@ -1035,13 +1048,32 @@ SPCDevice::SelectCommand() // t_AWSL = 0, 80 // t_SIDA = 11 * t_CLF - 30 // t_IDBH = 2 * t_CLF - 80 + // + // ここではバスフリー検出から少なくとも t_BFBL 以上 + t_ARB + t_AWSL 後に + // SEL と DB* と ATN を立てて、そこから t_SIDA + t_IDBH 後に BSY を落とす。 + // + // アービトレーションなしの場合 + // + // BSY X\_______________ .. _/~~~~~~~~~~~~~~~ + // |t_FRID| |t_BIDH| + // DB* ________ (6 + TCL) * t_CLF, (7 + TCL) * t_CLF + 140 + // t_IDSL = 11 * t_CLF - 80 + // t_BIDH = t_BLSH = 2 * t_CLF + // t_SHIR = 60 + // // t_FRID/t_BFBL はたぶん今からではなくバスフリーになった時点から起算。 // t_FRID と t_BFBL は同じ式なのでここでは t_BFBL で統一。 uint64 t_BFBL = (6 + spc.GetTCL()) * t_CLF; // Min - now = gScheduler->GetVirtTime(); - if ((BusPhaseTime + t_BFBL) > now) { - wait = (BusPhaseTime + t_BFBL) - now; + uint64 now = gScheduler->GetVirtTime(); + if ((last_busfree_time + t_BFBL) > now) { + wait = (last_busfree_time + t_BFBL) - now; } else { wait = 0; } @@ -1049,196 +1081,329 @@ SPCDevice::SelectCommand() spc.ssts &= ~(SPC::SSTS_CONN_INIT | SPC::SSTS_CONN_TARG); spc.ssts |= SPC::SSTS_SPC_BUSY; - // アービトレーションフェーズ + // TCH,TCM によるタイマーを始動。 + // N が 0 なら無限大なのでタイマーは動かさない。 + uint32 N = (spc.tc >> 8) << 8; + if (N != 0) { + event_tc.time = (N + 15) * t_CLF * 2; + event_tc.func = (DeviceCallback_t)&SPCDevice::SelectionTimeout; + gScheduler->AddEvent(&event_tc); + } + if ((spc.sctl & SPC::SCTL_ARBIT_EN)) { // アービトレーションありの場合 - Arbitration(myid); - - // 実際にはここで所定の時間経過後に優先度のチェックを行って - // アービトレーションの勝者を決めるのだが、ここでは複数のデバイスが - // 同時にアービトレーションフェーズに入ることはないため、 - // アービトレーションを開始したデバイスが必ずバスの使用権を得る。 - ArbitrationAck(myid); + // ここでは複数のデバイスが同時にアービトレーションフェーズに + // 入ることはないため、アービトレーションを開始したデバイスが + // 必ずバスの使用権を得る。 + // バスフリーから規定時間後に SEL (と DB* と ATN) を上げる。 uint64 t_ARB = 32 * t_CLF - 60; uint64 t_AWSL = 40; // 適当に中間 - uint64 t_SIDA = 11 * t_CLF - 30; - uint64 t_IDBH = 2 * t_CLF - 80; - wait += t_ARB + t_AWSL + t_SIDA + t_IDBH; + CallAfter(Arbitration1, "SPC Arbitration", wait + t_ARB + t_AWSL); } else { - // アービトレーションなしの場合の相当箇所までの時間 + // アービトレーションなしの場合 + + // バスフリーから規定時間後に SEL (と DB*) を上げる。 uint64 t_IDSL = 11 * t_CLF - 80; - wait += t_IDSL; + CallAfter(Selection1, "SPC Selection", wait + t_IDSL); } +} - // 後半の時間もここで足しておく - uint64 t_BLSH = 2 * t_CLF; - uint64 t_SHIR = 60; - wait += t_BLSH + t_SHIR; +// アービトレーションフェーズ開始後、規定時間経過したので SEL を立てるところ。 +void +SPCDevice::Arbitration1(int dummy) +{ + putlog(3, "Arbitration1"); - // セレクションフェーズ - spc.ssts |= SPC::SSTS_CONN_INIT; - InitiatorSelection(tgtid); + spc.ssts |= SPC::SSTS_SPC_BUSY; - if (tgtid >= 0 && target[tgtid] != NULL) { - // セレクション成功 + // 本当は DB* -> SEL -> ATN の順でタイミングも規定されてるけど省略。 + // ここでは SEL がトリガーになるので SEL のアサートは最後に行う。 + SetData(spc.temp_out); + if (set_atn_in_selection) { + AssertATN(); + set_atn_in_selection = false; + } + AssertSEL(); + + uint64 t_SIDA = 11 * t_CLF - 30; + uint64 t_IDBH = 2 * t_CLF - 80; + CallAfter(Arbitration2, "SPC Arbitration2", t_SIDA + t_IDBH); +} - // XXX 途中のターゲットが応答するための時間が抜けているけど - event.time = wait; - event.name = "SPC SelComplete"; - event.func = (DeviceCallback_t)&SPCDevice::SelectionCompleteCallback; - } else { - // セレクション失敗 +// アービトレーションフェーズ中、SEL を立ててから規定時間経過したので BSY を +// 下げるところ。 +void +SPCDevice::Arbitration2(int dummy) +{ + putlog(3, "Arbitration2"); - uint64 timeout = ((spc.tc & 0xffff00) + 15) * t_CLF * 2; - event.time = wait + timeout; - event.name = "SPC SelFailed"; - event.func = (DeviceCallback_t)&SPCDevice::SelectionFailedCallback; - } - gScheduler->AddEvent(&event); + NegateBSY(); } -// Transfer コマンド実行 +// アービトレーションフェーズを使わない場合のセレクションフェーズで、 +// 規定時間経過したので SEL を立てるところ。 void -SPCDevice::TransferCommand() +SPCDevice::Selection1(int dummy) { - uint32 xfer_phase = GetPCTL() & 7; - putlog(2, "SCMD Transfer Command (xfer=%s len=%d)", - SCSI::GetPhaseName(xfer_phase), spc.tc); + putlog(3, "Selection1"); - // Transfer コマンド発行時に REQ がアサートされていれば、SCSI バスの - // 状態と PCTL レジスタの下位3ビットの状態を比較する。一致していれば - // 転送、そうでなければ Service Required 割り込み。 - // XXX Transfer コマンド発行時に REQ がアサートされていなければ、 - // アサートされるまで待つ。 - uint32 stat = 0; - if (GetMSG()) { - stat |= SPC::PCTL_MSG; - } - if (GetCD()) { - stat |= SPC::PCTL_CD; - } - if (GetIO()) { - stat |= SPC::PCTL_IO; - } - if (xfer_phase != stat) { - SetINTS(SPC::INTS_SERV_REQ); - return; - } + spc.ssts |= SPC::SSTS_SPC_BUSY; - // 転送開始前に DREG キューを初期化する。 - // MPU 側が DREG を全部読まずにフェーズを中断とかは別に出来るので。 - spc.dreg.Clear(); + // 本当は DB* -> SEL の順でタイミングも規定されてるけど省略。 + // ここでは SEL がトリガーになるので SEL のアサートは最後に行う。 + SetData(spc.temp_out); + AssertSEL(); +} - // 転送開始 - phase = xfer_phase; - spc.ssts |= SPC::SSTS_SPC_BUSY; - switch (xfer_phase) { - case SCSI::Phase::Command: - // この後 DREG にコマンドが書き込まれる - cmds.clear(); - // XXX どこで初期化する? - datalen = 0; - break; +// セレクションコマンドがタイムアウトした時に呼ばれるコールバック。 +void +SPCDevice::SelectionTimeout(int dummy) +{ + if (loglevel >= 3) { + int tgtid; + uint8 data = GetData(); + data &= ~(1 << myid); + tgtid = DecodeID(data); + if (tgtid == -1) { + putlog("Selection timeout"); + } else { + putlog("Selection timeout (#%d)", tgtid); + } + } - case SCSI::Phase::DataIn: - datalen = target[tgtid]->ExecDataIn((const uint8 **)&dataptr); - // 最初の1バイトはここで DREG に入れておかないと SSTS が変化しない。 - EnqueueToDREG(); - break; + spc.ssts &= ~(SPC::SSTS_CONN_INIT | SPC::SSTS_SPC_BUSY); - case SCSI::Phase::DataOut: - datalen = target[tgtid]->ExecDataOut(&dataptr); - // データアウトの時は DREG へ書き込まれるのを待つだけなので - // ここでは何もしなくていい。 - break; + // TC は実際には減算カウンタのようだが、とりあえず辻褄合わせ + spc.tc = 0; - case SCSI::Phase::Status: - // ステータスバイトを取り込む。 - // 1バイトなので必ず COMPLETE になる。 - spc.dreg.Enqueue(target[tgtid]->GetStatusByte()); - break; + SetINTS(SPC::INTS_TIMEOUT); +} - case SCSI::Phase::MsgIn: - // メッセージバイトを取り込む。 - // 1バイトなので必ず COMPLETE になる。 - spc.dreg.Enqueue(target[tgtid]->GetMessageByte()); +// SetATN コマンド実行 +void +SPCDevice::SetATNCommand() +{ + putlog(2, "SCMD Set ATN Command"); + // Select コマンド前なら、セレクションフェーズで ATN を立てる指示。 + // 接続中(転送フェーズ中?) なら即座に ATN を立てる。 + switch (GetPhase()) { + case SCSI::Phase::BusFree: + set_atn_in_selection = true; + break; + case SCSI::Phase::Arbitration: + case SCSI::Phase::Selection: + case SCSI::Phase::Reselection: + // Select Command 実行中なので実質ここで呼ばれることはないはずだが + case SCSI::Phase::Transfer: + AssertATN(); break; } - - // XXX どこでやる? - // SDGC_XFER_ENABLE なら転送要求ごとに割り込みを上げる。 - // この場合の割り込み要因は SERR レジスタに出るようだ。 - if ((spc.sdgc & SPC::SDGC_XFER_ENABLE)) { - spc.serr |= SPC::SERR_XFER_OUT; - putlog(3, "SDGC XFER_OUT Interrupt"); - SetINTS(0); - } } -// ターゲットから受信したデータ (dataptr, datalen) を DREG キューに入れる。 -// 空いてる限り常に埋まるまで入れる。 -// データが最初から空なら何もしない。 +// Transfer コマンド実行 void -SPCDevice::EnqueueToDREG() +SPCDevice::TransferCommand() { - while (datalen > 0 && spc.dreg.Enqueue(*dataptr)) { - dataptr++; - datalen--; + putlog(2, "SCMD Transfer Command (xfer=%s len=%d)", + SCSI::GetXferPhaseName(GetPCTL() & 7), spc.tc); + + // SCMD レジスタ書き込みのうち Transfer コマンドに関わる未実装機能の + // ログ表示は SCMD レジスタ書き込み時ではなくここで表示。 + if ((spc.scmd & SPC::SCMD_TERM_MODE)) { + putlog(0, "SCMD Padding mode not implemented"); + } + if ((spc.scmd & SPC::SCMD_PROGRAM) == 0) { + putlog(0, "SCMD DMA transfer mode not implemented"); + } + if ((spc.scmd & SPC::SCMD_INTERCEPT)) { + putlog(0, "SCMD Intercept Transfer not implemented"); + } + + transfer_command_running = true; + spc.ssts |= SPC::SSTS_SPC_BUSY | SPC::SSTS_IN_PROGRESS; + + // 転送開始前に DREG キューを初期化する。 + // MPU 側が DREG を全部読まずにフェーズを中断とかはたぶん出来るので。 + // XXX 転送開始前なのかリセットなのか… + spc.dreg.Clear(); + + // Transfer コマンド発行時に REQ がアサートされていなければ + // アサートされるまで待つ。この場合、ここでは何もすることはなく、 + // TransferReqCB() が呼ばれてそこから転送フェーズが動き始める。 + if (GetREQ()) { + // コマンドから転送開始までちょっとだけかかる + // XXX 4 * t_CLF だと NetBSD カーネルがレースコンディションにはまる。 + CallAfter(TransferCommandStart, "SPC SCMD to Start", 0 * t_CLF); } } -// バスフリーフェーズに移行完了のイベントコールバック -// arg: 要因 0=SPC 1=SCSI +// バスフリーコールバック (SCSIBus から呼ばれる) +// id はこのバスフリーを行ったデバイス (0..7) void -SPCDevice::BusFreeCallback(int arg) +SPCDevice::BusFreeCB(int id) { - putlog(2, "SCSIBus バスフリー"); - phase = SCSI::Phase::BusFree; + putlog(4, "BusFree Callback"); - // SPC ビジーとかを下げる - spc.ssts &= ~(SPC::SSTS_CONN_INIT | SPC::SSTS_CONN_TARG | - SPC::SSTS_SPC_BUSY); - spc.ssts &= ~SPC::SSTS_IN_PROGRESS; + inherited::BusFreeCB(id); + last_busfree_time = gScheduler->GetVirtTime(); + + transfer_command_running = false; + spc.ssts &= ~(SPC::SSTS_CONN_INIT | SPC::SSTS_CONN_TARG); + spc.ssts &= ~(SPC::SSTS_SPC_BUSY | SPC::SSTS_IN_PROGRESS); // マニュアルが分かりにくいんだけど、 - // o. イニシエータで SPC がバスフリーにしたとき - // o. BusFree INT enable ビットが立っていてターゲットからのバスフリー + // o イニシエータとして動作時に、SPC が自らバスフリーにした時 + // o BusFree INT enable ビットが立っていて、ターゲットがバスフリーに + // した時 // のいずれかで INTS のバスフリー検出ビットを立てる - if (arg || spc.busfree_intr_enable) { + if (id != myid || spc.busfree_intr_enable) { SetINTS(SPC::INTS_DISCONNECTED); } } -// 所定時間経過後セレクションに成功のイベントコールバック +// ターゲットがセレクションに応答したコールバック (SCSIBus から呼ばれる) void -SPCDevice::SelectionCompleteCallback(int arg) +SPCDevice::SelectionAckCB() { - // ほんとは信号線ごとに時間が進むのだが、全部一度に変化する :) - TargetSelectionAck(tgtid); - InitiatorSelectionAck(); - target[tgtid]->Selected(); + // ターゲットが BSY を立てて応答したので、 + // こちらはデータバスをクリアして SEL を下げる。 + // これによりセレクションフェーズは成功で完了する。 + putlog(4, "SelectionAck Calback"); + SetData(0); + NegateSEL(); + + // タイムアウトイベントを停止 + gScheduler->DelEvent(&event_tc); spc.ssts &= ~SPC::SSTS_SPC_BUSY; - spc.ssts |= SPC::SSTS_IN_PROGRESS; + spc.ssts |= SPC::SSTS_CONN_INIT; + + // Command Complete 割り込みを起こす SetINTS(SPC::INTS_COMPLETE); } -// 所定時間経過後セレクションに失敗のイベントコールバック +// 転送フェーズでターゲットが REQ を立てたコールバック (SCSIBus から呼ばれる) void -SPCDevice::SelectionFailedCallback(int arg) +SPCDevice::TransferReqCB() { - if (loglevel >= 1) { - if (tgtid == -1) { - putlog(2, "セレクションタイムアウト"); + // REQ が立てば Transfer Command 発行前でも Xfer In Progress は立てる + spc.ssts |= SPC::SSTS_IN_PROGRESS; + + if (transfer_command_running) { + // プログラム転送で SDGC_XFER_ENABLE がセットされていれば、 + // データ要求が来ている時に XFER_OUT をセット(?) + if ((spc.sdgc & SPC::SDGC_XFER_ENABLE)) { + spc.serr |= SPC::SERR_XFER_OUT; + } + + // Transfer Command 実行中なら REQ 待ちだったので + // ここでプログラム転送を開始。 + TransferCommandStart(0); + } else { + // プログラム転送中でなくて IN 方向ならここで TEMP に取り込む。 + if (GetIO()) { + spc.temp_in = GetData(); + } + } +} + +// Transfer Command の実行開始部分。 +// TransferCommand() と TransferReqCB() から呼ばれる。 +void +SPCDevice::TransferCommandStart(int dummy) +{ + // Transfer コマンドの発行と REQ がアサートの両方が成立した時点で、 + // SCSI バスの状態と PCTL レジスタの下位3ビットの状態を比較する。 + // 一致していれば転送開始、そうでなければ Service Required 割り込み。 + if ((GetPCTL() & 7) == GetXfer()) { + ProgramTransfer(0); + } else { + transfer_command_running = false; + SetINTS(SPC::INTS_SERV_REQ); + } +} + +// プログラム転送の1バイト分。 +// イベントコールバックとして呼び出すこと。 +// REQ がアサートされているのを確認してから呼び出すこと。 +// (1バイト送信するとターゲットが再び REQ を上げるなどしてくるので、 +// TransferReqCB() か DREG 書き込みから再びここが呼ばれる) +void +SPCDevice::ProgramTransfer(int dummy) +{ + // SDGC_XFER_ENABLE は転送要求ごとに割り込みを上げる。 + // (SPC から見て) 入力操作時は読み込めるデータがある場合、出力操作時は + // キューにバイトを書き込む必要がある場合に、割り込みを上げる。 + + // DREG が送受信不可能ならここでは何もせずに帰るだけでよい。 + // その後 DREG アクセスによって送受信可能になればまたこれが呼ばれる。 + if (GetIO()) { + // Input (ターゲット → イニシエータ) + bool queued = spc.dreg.Enqueue(GetData()); + // SDGC_XFER_ENABLE なら読み込めるデータがある場合に割り込み。 + if ((spc.sdgc & SPC::SDGC_XFER_ENABLE)) { + putlog(3, "SDGC XFER_OUT Interrupt"); + SetINTS(0); + } + if (!queued) { + putlog(4, "Program Transfer (DREG full)"); + return; + } + } else { + // Output (イニシエータ → ターゲット) + if (spc.dreg.Length() != 0) { + SetData(spc.dreg.Dequeue()); } else { - putlog(2, "セレクションタイムアウト (#%d)", tgtid); + putlog(4, "Program Transfer (DREG empty)"); + // SDGC_XFER_ENABLE ならキューに書き込む必要がある場合に割り込み。 + if ((spc.sdgc & SPC::SDGC_XFER_ENABLE)) { + putlog(3, "SDGC XFER_OUT Interrupt"); + SetINTS(0); + } + return; } } - spc.ssts &= ~SPC::SSTS_SPC_BUSY; - // ほんとは TC は減算カウンタになってるんだが - spc.tc = 0; - SetINTS(SPC::INTS_TIMEOUT); + // SDGC XFER_OUT のログと順序が微妙になるけど、 + // DREG full/empty で2行出るのを避けたいので。 + putlog(4, "Program Transfer"); + + // XXX XFER_OUT はどこで落とすんやろうか。 + // データ要求が来たら上げるのだから用が済んだら下ろすのかしら。 + spc.serr &= ~SPC::SERR_XFER_OUT; + + AssertACK(); + + // 転送完了 + spc.tc--; + putlog(4, "Program Transfer: %s q=%d tc=%d", + (GetIO() ? "IN" : "OUT"), spc.dreg.Length(), spc.tc); + // TC がゼロになったら完了。 + if (spc.tc == 0) { + transfer_command_running = false; + SetINTS(SPC::INTS_COMPLETE); + + // SSTS の SPC Busy と Xfer In Progress は、TC==0 だけでは下げずに + // DREG キューが空になるまで待つようだ。 + // この2つのビットはハードウェア転送を実行中の時立てるように読める + // ので転送終わると下げてもよさそうなのだが、ここで下げてしまうと + // LUNA の PROM がステータスフェーズ受信中に SSTS を読んで転送開始 + // 前と誤認してしまうようだ。Read() の DREG のところも参照。 + if (spc.dreg.Length() == 0) { + spc.ssts &= ~(SPC::SSTS_SPC_BUSY | SPC::SSTS_IN_PROGRESS); + } + } + + // メッセージアウトフェーズの最終バイト送信中(?)に ATN を下げる + if (GetXfer() == SCSI::XferPhase::MsgOut && spc.tc == 0) { + NegateATN(); + } + + if (GetXfer() == SCSI::XferPhase::MsgIn && spc.tc == 0) { + // ただしメッセージインフェーズの最終バイトなら ACK を下げない。 + } else { + NegateACK(); + } }