--- nono/vm/spc.cpp 2026/04/29 17:04:42 1.1.1.6 +++ nono/vm/spc.cpp 2026/04/29 17:04:45 1.1.1.7 @@ -5,11 +5,12 @@ // #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 +83,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 は @@ -147,6 +156,7 @@ SPCDevice::Reset() spc.sdgc = 0x00; // 内部状態 + MakeIntsMask(); prev_ints = (uint32)-1; prev_ssts = (uint32)-1; set_atn_in_selection = false; @@ -184,7 +194,7 @@ SPCDevice::Read(uint32 addr) return 0xfc; case SPC::INTS: - data = spc.ints; + data = GetINTS(); if (loglevel >= 3) { if (event_tc.active) { // イベント中は値が変わった時だけ表示 @@ -221,7 +231,7 @@ SPCDevice::Read(uint32 addr) return data; case SPC::SERR: - data = spc.serr; + data = GetSERR(); putlog(3, "SERR -> $%02x", data); return data; @@ -391,7 +401,7 @@ SPCDevice::Peek(uint32 addr) return 0xfc; case SPC::INTS: - return spc.ints; + return GetINTS(); case SPC::PSNS: return GetPSNS(); @@ -400,7 +410,7 @@ SPCDevice::Peek(uint32 addr) return GetSSTS(); case SPC::SERR: - return spc.serr; + return GetSERR(); case SPC::PCTL: return GetPCTL(); @@ -457,11 +467,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; @@ -740,6 +750,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 +775,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 +799,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 +831,10 @@ SPCDevice::WriteSCMD(uint32 data) putlog(2, "SCMD Set ACK/REQ コマンド"); // イニシエータなら ACK を、ターゲットなら REQ をセットする。 // ターゲットモードはサポートしないので、操作するのは ACK のみ。 + if ((spc.scmd & SPC::SCMD_INTERCEPT)) { + // Intercept 動作は未実装 + putlog(0, "SCMD Intercept with SetACK not implemented"); + } AssertACK(); break; } @@ -818,11 +851,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 +872,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 +891,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 +975,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,7 +1019,8 @@ 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)) { @@ -1018,7 +1070,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 +1190,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 { @@ -1281,9 +1334,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 +1361,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 +1406,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 +1453,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 +1468,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 +1477,6 @@ SPCDevice::HardwareTransfer(Event& ev) // DREG full/empty で2行出るのを避けたいので。 putlog(4, "Hardware Transfer"); - // XXX XFER_OUT はどこで落とすんやろうか。 - // データ要求が来たら上げるのだから用が済んだら下ろすのかしら。 - spc.serr &= ~SPC::SERR_XFER_OUT; - AssertACK(); // 転送完了