--- nono/vm/spc.cpp 2026/04/29 17:04:42 1.1.1.6 +++ nono/vm/spc.cpp 2026/04/29 17:04:50 1.1.1.8 @@ -5,11 +5,11 @@ // #include "spc.h" +#include "interrupt.h" #include "mainapp.h" #include "mpu680x0.h" #include "mpu88xx0.h" -#include "mystring.h" -#include "scheduler.h" +#include "pedec.h" #include "sram.h" #include "vm.h" @@ -82,6 +82,14 @@ SPCDevice::Init() write_wait = 19; } + // 割り込み信号線の接続先 + if (vmtype == VMTYPE_X68030) { + // X68030 は PEDEC (I/O コントローラ) にカスケード + interrupt = gPEDEC.get(); + } else { + interrupt = gInterrupt.get(); + } + // 本体の SCSI ID は SPC(MB89352) の BDID レジスタに ID が書き込まれる // のを待たずに、最初から知っていることにする。本来 (少なくとも真っ当な // システムでは) イニシエータを含むすべての SCSI 機器の SCSI ID は @@ -108,30 +116,10 @@ SPCDevice::Init() return true; } - -// 本体リセットスイッチによるリセット +// デバイスリセット void SPCDevice::ResetHard() { - Reset(); -} - -// MPU の RESET 命令によるリセット -void -SPCDevice::ResetSoft() -{ - // X68030 は RESET 命令でもリセットがかかる。 - // LUNA は本体回路が分からないし PROM が何回も RESET 命令を発行するので、 - // とりあえず外しておく。 - if (vmtype != VMTYPE_X68030) { - Reset(); - } -} - -// SPC リセット -void -SPCDevice::Reset() -{ putlog(1, "リセット"); // 初期値 (p.61) @@ -147,6 +135,7 @@ SPCDevice::Reset() spc.sdgc = 0x00; // 内部状態 + MakeIntsMask(); prev_ints = (uint32)-1; prev_ssts = (uint32)-1; set_atn_in_selection = false; @@ -156,13 +145,13 @@ SPCDevice::Reset() } uint64 -SPCDevice::Read(uint32 addr) +SPCDevice::Read(uint32 offset) { uint8 data; gMPU->AddCycle(read_wait); - switch (addr) { + switch (offset) { case SPC::BDID: data = 1 << myid; putlog(3, "BDID -> $%02x", data); @@ -184,16 +173,16 @@ SPCDevice::Read(uint32 addr) return 0xfc; case SPC::INTS: - data = spc.ints; + data = GetINTS(); if (loglevel >= 3) { if (event_tc.active) { // イベント中は値が変わった時だけ表示 if (prev_ints != data) { - putlog("INTS -> $%02x", data); + putlogn("INTS -> $%02x", data); prev_ints = data; } } else { - putlog("INTS -> $%02x", data); + putlogn("INTS -> $%02x", data); prev_ints = -1U; } } @@ -210,18 +199,18 @@ SPCDevice::Read(uint32 addr) if (event_tc.active) { // イベント中は値が変わった時だけ表示 if (prev_ssts != data) { - putlog("SSTS -> $%02x", data); + putlogn("SSTS -> $%02x", data); prev_ssts = data; } } else { - putlog("SSTS -> $%02x", data); + putlogn("SSTS -> $%02x", data); prev_ssts = -1U; } } return data; case SPC::SERR: - data = spc.serr; + data = GetSERR(); putlog(3, "SERR -> $%02x", data); return data; @@ -265,11 +254,11 @@ SPCDevice::Read(uint32 addr) } uint64 -SPCDevice::Write(uint32 addr, uint32 data) +SPCDevice::Write(uint32 offset, uint32 data) { gMPU->AddCycle(write_wait); - switch (addr) { + switch (offset) { case SPC::BDID: putlog(3, "BDID <- $%02x", data); if (myid == data) { @@ -373,9 +362,9 @@ SPCDevice::Write(uint32 addr, uint32 dat } uint64 -SPCDevice::Peek(uint32 addr) +SPCDevice::Peek(uint32 offset) { - switch (addr) { + switch (offset) { case SPC::BDID: return 1 << myid; @@ -391,7 +380,7 @@ SPCDevice::Peek(uint32 addr) return 0xfc; case SPC::INTS: - return spc.ints; + return GetINTS(); case SPC::PSNS: return GetPSNS(); @@ -400,7 +389,7 @@ SPCDevice::Peek(uint32 addr) return GetSSTS(); case SPC::SERR: - return spc.serr; + return GetSERR(); case SPC::PCTL: return GetPCTL(); @@ -457,11 +446,11 @@ SPCDevice::MonitorUpdate(TextScreen& mon sctl = spc.sctl; scmd = spc.scmd; - ints = spc.ints; + ints = GetINTS(); psns = GetPSNS(); sdgc = spc.sdgc; ssts = GetSSTS(); - serr = spc.serr; + serr = GetSERR(); pctl = GetPCTL(); tc = spc.tc; @@ -730,7 +719,7 @@ SPCDevice::WriteSCTL(uint32 data) putlog(3, "SCTL <- $%02x", spc.sctl); if ((up & SPC::SCTL_RESET)) { // リセット - Reset(); + ResetHard(); } else { // 通常動作 } @@ -740,6 +729,21 @@ SPCDevice::WriteSCTL(uint32 data) if ((spc.sctl & SPC::SCTL_DIAG_MODE)) { putlog(0, "SCTL 未実装 Diag Mode"); } + + // 割り込みマスクを更新して信号線の状態を変える + MakeIntsMask(); + ChangeInterrupt(); +} + +// SCTL_INTR_EN の状態から内部変数 ints_mask を作成する。 +void +SPCDevice::MakeIntsMask() +{ + if ((spc.sctl & SPC::SCTL_INTR_EN)) { + spc.ints_mask = SPC::INTS_ENABLE_MASK; + } else { + spc.ints_mask = SPC::INTS_DISABLE_MASK; + } } // SCMD レジスタへの書き込み @@ -750,8 +754,7 @@ SPCDevice::WriteSCMD(uint32 data) spc.scmd = data; putlog(3, "SCMD <- $%02x", spc.scmd); - // Intercept, Program, Terminate Mode の未実装判定は - // 転送開始時のみ表示する。 + // Program, Terminate Mode の未実装判定は転送開始時のみ表示する。 // RST Out 操作は SCTL Reset がオフの時だけ有効 // と書いてあるけど、SCTL Reset がオンならほぼ全機能無効なのでは? @@ -775,6 +778,11 @@ SPCDevice::WriteSCMD(uint32 data) switch (spc.scmd >> 5) { case 0: putlog(2, "SCMD Bus Release コマンド"); + if ((spc.scmd & SPC::SCMD_INTERCEPT)) { + // BusRelease コマンドの発行と共に Intercept ビットが立っていたら + // Service Required 割り込みをリセットする? + putlog(0, "SCMD Intercept with BusRelease not implemented"); + } BusFree(); break; case 1: @@ -802,6 +810,15 @@ SPCDevice::WriteSCMD(uint32 data) putlog(2, "SCMD Set ACK/REQ コマンド"); // イニシエータなら ACK を、ターゲットなら REQ をセットする。 // ターゲットモードはサポートしないので、操作するのは ACK のみ。 + if ((spc.scmd & SPC::SCMD_INTERCEPT)) { + // Intercept なら DREG を維持したまま (たぶん TEMP で) バスを + // 直接アクセスするようだが、この DREG 維持あたりは未対応。 + // これが影響するのは DREG にデータが滞留してる時だけのはず + // なので、その時だけログを出す。(X68030 IPLROM、NetBSD/x68k) + if (spc.dreg.Length() != 0) { + putlog(0, "SCMD Intercept with SetACK not implemented"); + } + } AssertACK(); break; } @@ -818,11 +835,7 @@ SPCDevice::WriteINTS(uint32 data) // '1' が書き込まれたビットをクリア spc.ints &= ~data; - putlog(3, "INTS <- $%02x (INTS = $%02x)", data, spc.ints); - - // SPC では INTS でビットを落とす行為は、 - // ホストが割り込み要因に対して処理を完了したことを - // SPC に通知する意味が含まれている。 + putlog(3, "INTS <- $%02x (INTS = $%02x)", data, GetINTS()); // DISCONNECTED ビットが立っているときは SPC は SCSI から // セレクション、リセレクション要求が来ても応答しない。 @@ -843,18 +856,17 @@ SPCDevice::WriteINTS(uint32 data) } } } + + // 割り込み信号線の状態を変える + ChangeInterrupt(); } // INTS 設定。 -// INTS にビットを立てる要因が起きた時に data にそれを指定して呼び出す。 -// この関数は INTS を書き換えて、必要なら割り込みを上げる。 +// 引数 data は割り込み要因(ここは内部要因なので INTS_XFER_OUT も指定可能)。 // INTS レジスタへの書き込み(割り込み要因のクリア)は WriteINTS() のほう。 void SPCDevice::SetINTS(uint32 data) { - // 立ち上がりでだけ割り込みがかかるわけではないが表示のため。 - uint32 up = ~spc.ints & data; - // 指定された要因を立てる spc.ints |= data; @@ -863,16 +875,30 @@ SPCDevice::SetINTS(uint32 data) NegateATN(); } - // 割り込み許可なら割り込み。 - // RESET_COND はノンマスカブル割り込み。 - if ((spc.sctl & SPC::SCTL_INTR_EN) || (spc.ints & SPC::INTS_RESET_COND)) { - putlog(3, "割り込み ints=$%02x up=$%02x", spc.ints, up); - if (vmtype != VMTYPE_X68030) { - gMPU->Interrupt(this, 2); - } else { - // とりあえず内蔵だけ - gMPU680x0->Interrupt(this, 1, 0x6c); - } + ChangeInterrupt(); +} + +// INTS レジスタの値を取得 +uint32 +SPCDevice::GetINTS() const +{ + // ints の上位には別の割り込み状態が保持されている。 + // 下位8bit は INTS レジスタのまま。 + return spc.ints & 0xff; +} + +// 割り込み信号線の状態を変える +void +SPCDevice::ChangeInterrupt() +{ + // 割り込み状態は ints に集約してあり、 + // ints_mask は現在の SCTL_INTR_EN 状態に応じたマスクが用意されているので + // 単純に AND するだけでよい。 + if ((spc.ints & spc.ints_mask) != 0) { + putlog(3, "割り込み ints=$%02x", spc.ints); + interrupt->AssertINT(this); + } else { + interrupt->NegateINT(this); } } @@ -933,6 +959,15 @@ SPCDevice::GetSSTS() const return data; } +// SERR レジスタ値の取得。 +uint32 +SPCDevice::GetSERR() const +{ + // SERR レジスタのうち XFER_OUT ビットだけ ints で保持している。 + uint32 xfer_out = (spc.ints & SPC::INTS_XFER_OUT) >> 3; + return spc.serr | xfer_out; +} + // PCTL レジスタ値の取得。 // Read*() と Peek8() から呼ばれる。 // scsibus 接続前にもモニタの更新などで呼ばれることに注意。 @@ -968,14 +1003,15 @@ SPCDevice::ReadDREG() } // XFER_OUT は DREG アクセスでリセットされる - spc.serr &= ~SPC::SERR_XFER_OUT; + spc.ints &= ~SPC::INTS_XFER_OUT; + ChangeInterrupt(); // 受信データがあれば取り出す if (__predict_true(spc.dreg.Length() > 0)) { bool q_was_full = spc.dreg.IsFull(); data = spc.dreg.Dequeue(); if (loglevel >= 4) { - putlog("DREG -> $%02x (q=%d TC=0x%x)", + putlogn("DREG -> $%02x (q=%d TC=0x%x)", data, spc.dreg.Length(), spc.tc); } else { putlog(3, "DREG -> $%02x", data); @@ -1018,7 +1054,8 @@ SPCDevice::WriteDREG(uint32 data) } // XFER_OUT は DREG アクセスでリセットされる - spc.serr &= ~SPC::SERR_XFER_OUT; + spc.ints &= ~SPC::INTS_XFER_OUT; + ChangeInterrupt(); // DREG キューに入れて.. spc.dreg.Enqueue(data); @@ -1137,7 +1174,7 @@ SPCDevice::SelectCommand() // t_FRID/t_BFBL はたぶん今からではなくバスフリーになった時点から起算。 // t_FRID と t_BFBL は同じ式なのでここでは t_BFBL で統一。 uint64 t_BFBL = (6 + spc.GetTCL()) * t_CLF; // Min - uint64 now = gScheduler->GetVirtTime(); + uint64 now = gMPU->GetVirtTime(); if ((last_busfree_time + t_BFBL) > now) { wait = (last_busfree_time + t_BFBL) - now; } else { @@ -1233,9 +1270,9 @@ SPCDevice::SelectionTimeout(Event& ev) data &= ~(1 << myid); tgtid = DecodeID(data); if (tgtid == -1) { - putlog("Selection timeout"); + putlogn("Selection timeout"); } else { - putlog("Selection timeout (#%d)", tgtid); + putlogn("Selection timeout (#%d)", tgtid); } } @@ -1281,9 +1318,6 @@ SPCDevice::TransferCommand() if ((spc.scmd & SPC::SCMD_TERM_MODE)) { putlog(0, "SCMD Padding 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; @@ -1311,7 +1345,7 @@ SPCDevice::BusFreeCB(int id) putlog(4, "BusFree Callback"); inherited::BusFreeCB(id); - last_busfree_time = gScheduler->GetVirtTime(); + last_busfree_time = gMPU->GetVirtTime(); transfer_command_running = false; spc.ssts &= ~(SPC::SSTS_CONN_INIT | SPC::SSTS_CONN_TARG); @@ -1356,12 +1390,6 @@ SPCDevice::TransferReqCB() 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(event); @@ -1409,7 +1437,7 @@ SPCDevice::HardwareTransfer(Event& ev) // SDGC_XFER_ENABLE なら読み込めるデータがある場合に割り込み。 if ((spc.sdgc & SPC::SDGC_XFER_ENABLE)) { putlog(3, "SDGC XFER_OUT Interrupt"); - SetINTS(0); + SetINTS(SPC::INTS_XFER_OUT); } if (!queued) { putlog(4, "Hardware Transfer (DREG full)"); @@ -1424,7 +1452,7 @@ SPCDevice::HardwareTransfer(Event& ev) // SDGC_XFER_ENABLE ならキューに書き込む必要がある場合に割り込み。 if ((spc.sdgc & SPC::SDGC_XFER_ENABLE)) { putlog(3, "SDGC XFER_OUT Interrupt"); - SetINTS(0); + SetINTS(SPC::INTS_XFER_OUT); } return; } @@ -1433,10 +1461,6 @@ SPCDevice::HardwareTransfer(Event& ev) // DREG full/empty で2行出るのを避けたいので。 putlog(4, "Hardware Transfer"); - // XXX XFER_OUT はどこで落とすんやろうか。 - // データ要求が来たら上げるのだから用が済んだら下ろすのかしら。 - spc.serr &= ~SPC::SERR_XFER_OUT; - AssertACK(); // 転送完了