--- nono/vm/spc.cpp 2026/04/29 17:04:59 1.1.1.11 +++ nono/vm/spc.cpp 2026/04/29 17:05:09 1.1.1.12 @@ -4,26 +4,30 @@ // Licensed under nono-license.txt // +// +// SPC (MB89352) +// + #include "spc.h" +#include "config.h" #include "interrupt.h" #include "mainapp.h" -#include "mpu680x0.h" -#include "mpu88xx0.h" +#include "mpu.h" #include "pedec.h" +#include "scheduler.h" #include "sram.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.func = ToEventCallback(&SPCDevice::func_); \ event.time = time_; \ - event.Start(); \ + event.SetName(name_); \ + gScheduler->RestartEvent(event); \ } while (0) -std::unique_ptr gSPC; +// グローバル参照用 +SPCDevice *gSPC; // コンストラクタ SPCDevice::SPCDevice() @@ -39,13 +43,13 @@ SPCDevice::SPCDevice() // 汎用イベント // 名前とコールバック関数は都度セットする。 - event.SetName("SPC phase"); + event.Regist("SPC phase"); // TC カウンタイベント event_tc.code = 0; - event_tc.SetName("SPC Timer"); + event_tc.Regist("SPC Timer"); - monitor.func = (MonitorCallback_t)&SPCDevice::MonitorUpdate; + monitor.func = ToMonitorCallback(&SPCDevice::MonitorUpdate); // サイズは Init で決まる monitor.Regist(ID_MONITOR_SPC); } @@ -53,6 +57,7 @@ SPCDevice::SPCDevice() // デストラクタ SPCDevice::~SPCDevice() { + gSPC = NULL; } bool @@ -84,7 +89,7 @@ SPCDevice::Init() } // この親の Init() は connected_devices 作成後に呼ぶ - if (!inherited::Init()) { + if (inherited::Init() == false) { return false; } @@ -109,9 +114,9 @@ SPCDevice::Init() // 割り込み信号線の接続先 if (vmtype == VMTYPE_X68030) { // X68030 は PEDEC (I/O コントローラ) にカスケード - interrupt = gPEDEC.get(); + interrupt = gPEDEC; } else { - interrupt = gInterrupt.get(); + interrupt = gInterrupt; } monitor.SetSize(70, 15 + connected_devices.size()); @@ -121,7 +126,7 @@ SPCDevice::Init() // デバイスリセット void -SPCDevice::ResetHard() +SPCDevice::ResetHard(bool poweron) { putlog(1, "リセット"); @@ -145,8 +150,10 @@ SPCDevice::ResetHard() prev_ssts = (uint32)-1; set_atn_in_selection = false; transfer_command_running = false; - event.Stop(); - event_tc.Stop(); + gScheduler->StopEvent(event); + gScheduler->StopEvent(event_tc); + + last_busfree_time = 0; } uint64 @@ -180,7 +187,7 @@ SPCDevice::Read(uint32 offset) case SPC::INTS: data = GetINTS(); if (loglevel >= 3) { - if (event_tc.active) { + if (event_tc.IsRunning()) { // イベント中は値が変わった時だけ表示 if (prev_ints != data) { putlogn("INTS -> $%02x", data); @@ -201,7 +208,7 @@ SPCDevice::Read(uint32 offset) case SPC::SSTS: data = GetSSTS(); if (loglevel >= 3) { - if (event_tc.active) { + if (event_tc.IsRunning()) { // イベント中は値が変わった時だけ表示 if (prev_ssts != data) { putlogn("SSTS -> $%02x", data); @@ -254,8 +261,10 @@ SPCDevice::Read(uint32 offset) case SPC::EXBF: // MB89352 には存在しないレジスタ return 0xff; + + default: + __unreachable(); } - __unreachable(); } uint64 @@ -359,8 +368,10 @@ SPCDevice::Write(uint32 offset, uint32 d case SPC::EXBF: // MB89352 には存在しないレジスタ return 0; + + default: + __unreachable(); } - __unreachable(); } uint64 @@ -425,8 +436,10 @@ SPCDevice::Peek(uint32 offset) case SPC::EXBF: // MB89352 には存在しないレジスタ return 0xff; + + default: + __unreachable(); } - __unreachable(); } void @@ -713,7 +726,7 @@ SPCDevice::WriteSCTL(uint32 data) putlog(3, "SCTL <- $%02x", spc.sctl); if ((up & SPC::SCTL_RESET)) { // リセット - ResetHard(); + ResetHard(false); } else { // 通常動作 } @@ -844,9 +857,9 @@ SPCDevice::WriteINTS(uint32 data) if (spc.tc == 0) { BusFree(); } else { + event_tc.func = ToEventCallback(&SPCDevice::SelectionTimeout); event_tc.time = spc.tc * t_CLF * 2; - event_tc.func = (DeviceCallback_t)&SPCDevice::SelectionTimeout; - event_tc.Start(); + gScheduler->RestartEvent(event_tc); } } } @@ -892,6 +905,7 @@ SPCDevice::ChangeInterrupt() putlog(3, "割り込み ints=$%02x", spc.ints); interrupt->AssertINT(this); } else { + putlog(4, "割り込み解除"); interrupt->NegateINT(this); } } @@ -995,12 +1009,20 @@ SPCDevice::ReadDREG() if (vmtype == VMTYPE_X68030) { if ((spc.scmd & SPC::SCMD_PROGRAM)) { return (uint64)-1; + } else { + // DMA 転送で DREQ が出てないとき DTACK を出さないのを + // -2 を返すことで表現している + if (spc.dreg.Length() == 0) { + putlog(3, "DMA転送中の DREG empty"); + return (uint64)-2; // DTACK まだ出てないよ + } } } // XFER_OUT は DREG アクセスでリセットされる spc.ints &= ~SPC::INTS_XFER_OUT; - ChangeInterrupt(); + // TransferComplete() が割り込み状態を変化させる可能性があるので + // ChangeInterrupt() を最後に呼び出す。 // 受信データがあれば取り出す if (__predict_true(spc.dreg.Length() > 0)) { @@ -1029,10 +1051,13 @@ SPCDevice::ReadDREG() putlog(3, "DREG -> $%02x (empty)", data); } else { // DMA 転送 - // XXX どうする? - PANIC("DMA転送中の DREG empty"); + // DMA 側でリトライするのでここには来ないはず + VMPANIC("DMA転送中の DREG empty"); } } + + // 割り込み状態を確定 + ChangeInterrupt(); return data; } @@ -1046,6 +1071,12 @@ SPCDevice::WriteDREG(uint32 data) if (vmtype == VMTYPE_X68030) { if ((spc.scmd & SPC::SCMD_PROGRAM)) { return (uint64)-1; + } else { + // DMA 転送で DREQ が出てないとき DTACK を出さないのを + // -2 を返すことで表現している + if (spc.dreg.IsFull()) { + return (uint64)-2; + } } } @@ -1054,7 +1085,9 @@ SPCDevice::WriteDREG(uint32 data) ChangeInterrupt(); // DREG キューに入れて.. - spc.dreg.Enqueue(data); + if (spc.dreg.Enqueue(data) == false) { + VMPANIC("DREG キューオーバーフロー"); + } // ハードウェア転送中で REQ が来てれば送信。 if (transfer_command_running && GetREQ()) { // XXX 時間は適当 @@ -1170,7 +1203,7 @@ SPCDevice::SelectCommand() // t_FRID/t_BFBL はたぶん今からではなくバスフリーになった時点から起算。 // t_FRID と t_BFBL は同じ式なのでここでは t_BFBL で統一。 uint64 t_BFBL = (6 + spc.GetTCL()) * t_CLF; // Min - uint64 now = gMPU->GetVirtTime(); + uint64 now = gScheduler->GetVirtTime(); if ((last_busfree_time + t_BFBL) > now) { wait = (last_busfree_time + t_BFBL) - now; } else { @@ -1184,9 +1217,9 @@ SPCDevice::SelectCommand() // N が 0 なら無限大なのでタイマーは動かさない。 uint32 N = (spc.tc >> 8) << 8; if (N != 0) { + event_tc.func = ToEventCallback(&SPCDevice::SelectionTimeout); event_tc.time = (N + 15) * t_CLF * 2; - event_tc.func = (DeviceCallback_t)&SPCDevice::SelectionTimeout; - event_tc.Start(); + gScheduler->RestartEvent(event_tc); } if ((spc.sctl & SPC::SCTL_ARBIT_EN)) { @@ -1352,7 +1385,7 @@ SPCDevice::BusFreeCB(int id) putlog(4, "BusFree Callback"); inherited::BusFreeCB(id); - last_busfree_time = gMPU->GetVirtTime(); + last_busfree_time = gScheduler->GetVirtTime(); transfer_command_running = false; spc.ssts &= ~(SPC::SSTS_CONN_INIT | SPC::SSTS_CONN_TARG); @@ -1380,7 +1413,7 @@ SPCDevice::SelectionAckCB() NegateSEL(); // タイムアウトイベントを停止 - event_tc.Stop(); + gScheduler->StopEvent(event_tc); spc.ssts &= ~SPC::SSTS_SPC_BUSY; spc.ssts |= SPC::SSTS_CONN_INIT;