--- nono/vm/spc.cpp 2026/04/29 17:04:45 1.1.1.7 +++ nono/vm/spc.cpp 2026/04/29 17:05:13 1.1.1.13 @@ -4,33 +4,34 @@ // 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 "mystring.h" +#include "mpu.h" #include "pedec.h" -#include "sram.h" -#include "vm.h" +#include "scheduler.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() + : inherited("SPC") { - logname = "spc"; - devname = "SPC"; // これらは XIOSpace で必要なので、LUNA ではダミー。 // もうちょっといい方法を考えたほうがいい。 devaddr = 0xe96000; @@ -41,30 +42,47 @@ SPCDevice::SPCDevice() // 汎用イベント // 名前とコールバック関数は都度セットする。 - event.dev = this; - event.SetName("SPC phase"); + event.Regist("SPC Phase"); // TC カウンタイベント - event_tc.dev = this; event_tc.code = 0; - event_tc.SetName("SPC Timer"); + event_tc.Regist("SPC Timer"); + + monitor.func = ToMonitorCallback(&SPCDevice::MonitorUpdate); + // サイズは Init で決まる + monitor.Regist(ID_MONITOR_SPC); } // デストラクタ SPCDevice::~SPCDevice() { + gSPC = NULL; } bool SPCDevice::Init() { - if (!inherited::Init()) { - return false; - } - // 実行中に別インスタンスを頻繁に参照しないようにここで vmtype を覚えておく vmtype = gMainApp.GetVMType(); + // 本体 SCSI ID は LUNA も X68030 も 7 固定。 + uint initiator_id = 7; + + // モニタには、接続している SCSI デバイスだけを表示する。 + // そのため ID ではなく前から何番目のデバイスかのリストが欲しい。 + for (int id = 0; id < 8; id++) { + if (id == initiator_id) { + connected_devices.push_back(this); + } else if (target[id].get()) { + connected_devices.push_back(target[id].get()); + } + } + + // この親の Init() は connected_devices 作成後に呼ぶ + if (inherited::Init() == false) { + return false; + } + // 入力クロックは機種によって異なる。 // X68k は 5MHz が入っている。 // LUNA-I はおそらく 6.144MHz だと思われる。LUNA88K は未確認。 @@ -72,7 +90,7 @@ SPCDevice::Init() t_CLF = item.AsInt(); // アクセスウェイト - if (vmtype == VMTYPE_X68030) { + if (vmtype == VMType::X68030) { // InsideOut p.135 read_wait = 18; write_wait = 19; @@ -84,70 +102,31 @@ SPCDevice::Init() } // 割り込み信号線の接続先 - if (vmtype == VMTYPE_X68030) { + if (vmtype == VMType::X68030) { // X68030 は PEDEC (I/O コントローラ) にカスケード - interrupt = gPEDEC.get(); - } else { - interrupt = gInterrupt.get(); - } - - // 本体の SCSI ID は SPC(MB89352) の BDID レジスタに ID が書き込まれる - // のを待たずに、最初から知っていることにする。本来 (少なくとも真っ当な - // システムでは) イニシエータを含むすべての SCSI 機器の SCSI ID は - // 電源を入れる前に、人間が一々手作業で重複なく設定しているはずなので。 - // X68030 は SRAM $ED0070 下位3ビット。LUNA は 7 固定のようだ。 - uint myid0; - if (vmtype == VMTYPE_X68030) { - myid0 = gSRAM->Peek8(0xed0070) & 7; + interrupt = gPEDEC; } else { - myid0 = 7; + interrupt = gInterrupt; } - // モニタには、接続している SCSI デバイスだけを表示する。 - // そのため ID ではなく前から何番目のデバイスかのリストが欲しい。 - for (int id = 0; id < 8; id++) { - if (id == myid0) { - connected_devices.push_back(this); - } else if (target[id].get()) { - connected_devices.push_back(target[id].get()); - } - } - monitor_size = nnSize(70, 15 + connected_devices.size()); + monitor.SetSize(70, 15 + connected_devices.size()); return true; } - -// 本体リセットスイッチによるリセット +// デバイスリセット void -SPCDevice::ResetHard() +SPCDevice::ResetHard(bool poweron) { - Reset(); -} - -// MPU の RESET 命令によるリセット -void -SPCDevice::ResetSoft() -{ - // X68030 は RESET 命令でもリセットがかかる。 - // LUNA は本体回路が分からないし PROM が何回も RESET 命令を発行するので、 - // とりあえず外しておく。 - if (vmtype != VMTYPE_X68030) { - Reset(); - } -} - -// SPC リセット -void -SPCDevice::Reset() -{ - putlog(1, "リセット"); + putlog(1, "Reset"); // 初期値 (p.61) spc.sctl = SPC::SCTL_RESET; spc.ints = 0x00; spc.ssts &= 0x0f; spc.serr = 0x00; + // 変更しないレジスタは次の通り (p.60) + // BDID, TMOD, TC[HML]?, SCMD, PCTL, TEMP(out) // XXX マニュアルには書いていないけど、 // SDGC bit5 の転送ごとに割り込みを発生させるビットが @@ -161,18 +140,20 @@ SPCDevice::Reset() 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 -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); @@ -196,14 +177,14 @@ SPCDevice::Read(uint32 addr) case SPC::INTS: data = GetINTS(); if (loglevel >= 3) { - if (event_tc.active) { + if (event_tc.IsRunning()) { // イベント中は値が変わった時だけ表示 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; } } @@ -217,14 +198,14 @@ SPCDevice::Read(uint32 addr) case SPC::SSTS: data = GetSSTS(); if (loglevel >= 3) { - if (event_tc.active) { + if (event_tc.IsRunning()) { // イベント中は値が変わった時だけ表示 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; } } @@ -241,8 +222,8 @@ SPCDevice::Read(uint32 addr) return data; case SPC::MBC: - putlog(0, "MBC 未実装読み込み"); - return 0xff; + putlog(0, "Read MBC (NOT IMPLEMENTED)"); + return 0x0f; case SPC::DREG: return ReadDREG(); @@ -270,16 +251,18 @@ SPCDevice::Read(uint32 addr) case SPC::EXBF: // MB89352 には存在しないレジスタ return 0xff; + + default: + __unreachable(); } - __unreachable(); } 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) { @@ -292,7 +275,7 @@ SPCDevice::Write(uint32 addr, uint32 dat scsibus->Attach(this, myid); } else { // そうでなければ移動? - putlog(0, "BDID 変更 %d to %d not implemented", myid, data); + putlog(0, "BDID change %d to %d (NOT IMPLEMENTED)", myid, data); return 0; } return 0; @@ -327,10 +310,7 @@ SPCDevice::Write(uint32 addr, uint32 dat return 0; case SPC::PCTL: - data &= SPC::PCTL_MASK; - putlog(3, "PCTL <- $%02x", data); - spc.busfree_intr_enable = (data & SPC::PCTL_BFINT_EN); - SetXfer(data); + WritePCTL(data); return 0; case SPC::MBC: @@ -378,14 +358,16 @@ SPCDevice::Write(uint32 addr, uint32 dat case SPC::EXBF: // MB89352 には存在しないレジスタ return 0; + + default: + __unreachable(); } - __unreachable(); } uint64 -SPCDevice::Peek(uint32 addr) +SPCDevice::Peek(uint32 offset) { - switch (addr) { + switch (offset) { case SPC::BDID: return 1 << myid; @@ -416,13 +398,13 @@ SPCDevice::Peek(uint32 addr) return GetPCTL(); case SPC::MBC: - return 0xff; + return 0x0f; case SPC::DREG: // X68k は SPC の DREQ 信号から DTACK 信号を作っているため、 // DREQ 信号を出さないプログラム転送モードでは DREG へのアクセスが // すべてバスエラーになってしまう。 - if (vmtype == VMTYPE_X68030) { + if (vmtype == VMType::X68030) { if ((spc.scmd & SPC::SCMD_PROGRAM)) { return (uint64)-1; } @@ -444,12 +426,14 @@ SPCDevice::Peek(uint32 addr) case SPC::EXBF: // MB89352 には存在しないレジスタ return 0xff; + + default: + __unreachable(); } - __unreachable(); } void -SPCDevice::MonitorUpdate(TextScreen& monitor) +SPCDevice::MonitorUpdate(Monitor *, TextScreen& screen) { int x; int y; @@ -463,7 +447,7 @@ SPCDevice::MonitorUpdate(TextScreen& mon uint32 pctl; uint32 tc; - monitor.Clear(); + screen.Clear(); sctl = spc.sctl; scmd = spc.scmd; @@ -476,30 +460,30 @@ SPCDevice::MonitorUpdate(TextScreen& mon tc = spc.tc; y = 0; - monitor.Print(0, y++, "BDID: $%02x (ID=%d)", (1 << myid), myid); - monitor.Print(0, y++, "SCTL: $%02x", sctl); - monitor.Print(0, y++, "SCMD: $%02x", scmd); - monitor.Print(0, y++, "INTS: $%02x", ints); - monitor.Print(0, y++, "PSNS: $%02x", psns); - monitor.Print(0, y++, "SDGC: $%02x", sdgc); - monitor.Print(0, y++, "SSTS: $%02x", ssts); - monitor.Print(0, y++, "SERR: $%02x", serr); - monitor.Print(0, y++, "PCTL: $%02x", pctl); - monitor.Print(0, y++, "MBC : $XX"); - monitor.Print(0, y, "DREG: "); + screen.Print(0, y++, "BDID: $%02x (ID=%d)", (1 << myid), myid); + screen.Print(0, y++, "SCTL: $%02x", sctl); + screen.Print(0, y++, "SCMD: $%02x", scmd); + screen.Print(0, y++, "INTS: $%02x", ints); + screen.Print(0, y++, "PSNS: $%02x", psns); + screen.Print(0, y++, "SDGC: $%02x", sdgc); + screen.Print(0, y++, "SSTS: $%02x", ssts); + screen.Print(0, y++, "SERR: $%02x", serr); + screen.Print(0, y++, "PCTL: $%02x", pctl); + screen.Print(0, y++, "MBC : $XX"); + screen.Print(0, y, "DREG: "); { uint i; for (i = 0; i < spc.dreg.Length(); i++) { - monitor.Print(6 + i * 4, y, "$%02x", spc.dreg.Peek(i)); + screen.Print(6 + i * 4, y, "$%02x", spc.dreg.Peek(i)); } for (; i < 8; i++) { - monitor.Print(6 + i * 4, y, TA::Disable, "---"); + screen.Puts(6 + i * 4, y, "---"); } } y++; - monitor.Print(0, y++, "TEMP: $%02x(IN) $%02x(OUT)", + screen.Print(0, y++, "TEMP: $%02x(IN) $%02x(OUT)", spc.temp_in, spc.temp_out); - monitor.Print(0, y++, "TC : $%06x", tc); + screen.Print(0, y++, "TC : $%06x", tc); x = 10; y = 1; @@ -508,14 +492,14 @@ SPCDevice::MonitorUpdate(TextScreen& mon static const char * const sctl_bits[] = { "RST", "CTRL", "DIAG", "ARB", "PARI", "SEL", "RESL", "INTR" }; - MonitorReg(monitor, x, y, sctl, sctl_bits); + MonitorReg(screen, x, y, sctl, sctl_bits); y++; // SCMD static const char * const scmd_bits[] = { - "", "", "", "RST", "INTC", "PROG", "", "TERM" + "", "", "", "RST", "INTC", "PROG", "----", "TERM" }; - MonitorReg(monitor, x, y, scmd, scmd_bits); + MonitorReg(screen, x, y, scmd, scmd_bits); static const char * const scmd_cmd[] = { //12345678901234 "(Bus Release)", @@ -527,35 +511,35 @@ SPCDevice::MonitorUpdate(TextScreen& mon "(ResetACK/REQ)", "(Set ACK/REQ)", }; - monitor.Puts(x, y, scmd_cmd[scmd >> 5]); + screen.Puts(x, y, scmd_cmd[scmd >> 5]); y++; // INTS static const char * const ints_bits[] = { "Sel", "Resl", "Disc", "Cmpl", "SReq", "TOut", "HErr", "Rst" }; - MonitorReg(monitor, x, y, ints, ints_bits); + MonitorReg(screen, x, y, ints, ints_bits); y++; // PSNS static const char * const psns_bits[] = { "REQ", "ACK", "ATN", "SEL", "BSY", "MSG", "C/D", "I/O" }; - MonitorReg(monitor, x, y, psns, psns_bits); + MonitorReg(screen, x, y, psns, psns_bits); y++; // SDGC static const char * const sdgc_bits[] = { - "", "", "XfEn", "", "", "", "", "" + "-", "-", "XfEn", "-", "-", "-", "-", "-" }; - MonitorReg(monitor, x, y, sdgc, sdgc_bits); + MonitorReg(screen, x, y, sdgc, sdgc_bits); y++; // SSTS static const char * const ssts_bits[] = { "", "", "SBSY", "XInP", "RSTI", "TCZ", "Full", "Empt" }; - MonitorReg(monitor, x, y, ssts, ssts_bits); + MonitorReg(screen, x, y, ssts, ssts_bits); static const char * const ssts_conn[] = { //12345678 "(NotConn)", @@ -563,21 +547,21 @@ SPCDevice::MonitorUpdate(TextScreen& mon "(As Init)", "(undef?)", }; - monitor.Puts(x, y, ssts_conn[ssts >> 6]); + screen.Puts(x, y, ssts_conn[ssts >> 6]); y++; // SERR static const char * const serr_bits[] = { - "", "", "XOut", "", "PErr", "", "Shrt", "" + "-", "-", "XOut", "-", "PErr", "-", "Shrt", "-" }; - MonitorReg(monitor, x, y, serr, serr_bits); + MonitorReg(screen, x, y, serr, serr_bits); y++; // PCTL static const char * const pctl_bits[] = { - "BIE", "", "", "", "", "MSG", "C/D", "I/O" + "BIE", "-", "-", "-", "-", "MSG", "C/D", "I/O" }; - MonitorReg(monitor, x, y, pctl, pctl_bits); + MonitorReg(screen, x, y, pctl, pctl_bits); y++; // バス状態 @@ -607,47 +591,47 @@ SPCDevice::MonitorUpdate(TextScreen& mon ack = GetACK(); data = GetData(); } - monitor.Print(x, y++, "Phase: %s", SCSI::GetPhaseName(ph)); - monitor.Puts(x, y++, TA::OnOff(rst), "RST"); - monitor.Puts(x, y++, TA::OnOff(bsy), "BSY"); - monitor.Puts(x, y++, TA::OnOff(sel), "SEL"); - monitor.Puts(x, y++, TA::OnOff(msg), "MSG"); - monitor.Puts(x, y++, TA::OnOff(cd), "CD"); - monitor.Puts(x, y++, TA::OnOff(io), "IO"); - monitor.Puts(x, y++, TA::OnOff(atn), "ATN"); - monitor.Puts(x, y++, TA::OnOff(req), "REQ"); - monitor.Puts(x, y++, TA::OnOff(ack), "ACK"); - monitor.Print(x, y++, "DATA:$%02x", data); + screen.Print(x, y++, "Phase: %s", SCSI::GetPhaseName(ph)); + screen.Puts(x, y++, TA::OnOff(rst), "RST"); + screen.Puts(x, y++, TA::OnOff(bsy), "BSY"); + screen.Puts(x, y++, TA::OnOff(sel), "SEL"); + screen.Puts(x, y++, TA::OnOff(msg), "MSG"); + screen.Puts(x, y++, TA::OnOff(cd), "CD"); + screen.Puts(x, y++, TA::OnOff(io), "IO"); + screen.Puts(x, y++, TA::OnOff(atn), "ATN"); + screen.Puts(x, y++, TA::OnOff(req), "REQ"); + screen.Puts(x, y++, TA::OnOff(ack), "ACK"); + screen.Print(x, y++, "DATA:$%02x", data); y = 2; - monitor.Puts(x + 4, y++, "(________)"); - monitor.Puts(x + 4, y++, "(________)"); - monitor.Puts(x + 4, y++, "+"); - monitor.Puts(x + 4, y++, "|"); - monitor.Puts(x + 4, y++, "+"); + screen.Puts(x + 4, y++, "(________)"); + screen.Puts(x + 4, y++, "(________)"); + screen.Puts(x + 4, y++, "+"); + screen.Puts(x + 4, y++, "|"); + screen.Puts(x + 4, y++, "+"); // BSY, SEL の内訳。"(76543210)" の順。 y = 2; for (int i = 0; i < 8; i++) { if ((bsy & (1 << i))) { - monitor.Print(x + 4 + 8 - i, y, "%d", i); + screen.Print(x + 4 + 8 - i, y, "%d", i); } else { - monitor.Print(x + 4 + 8 - i, y, "_"); + screen.Print(x + 4 + 8 - i, y, "_"); } } y++; for (int i = 0; i < 8; i++) { if ((sel & (1 << i))) { - monitor.Print(x + 4 + 8 - i, y, "%d", i); + screen.Print(x + 4 + 8 - i, y, "%d", i); } else { - monitor.Print(x + 4 + 8 - i, y, "_"); + screen.Print(x + 4 + 8 - i, y, "_"); } } // 転送フェーズ中なら現在の XferPhase y += 2; if (ph == SCSI::Phase::Transfer) { - monitor.Puts(x + 5, y, SCSI::GetXferPhaseName(GetXfer())); + screen.Puts(x + 5, y, SCSI::GetXferPhaseName(GetXfer())); } // デバイス一覧 @@ -658,26 +642,14 @@ SPCDevice::MonitorUpdate(TextScreen& mon // 7: Initiator x = 0; y = 14; - monitor.Print(x, y++, "ID Device Command"); + screen.Print(x, y++, "ID Device Command"); for (const auto d : connected_devices) { - std::string desc = "?"; - switch (d->GetDevType()) { - case SCSI::DevType::Initiator: - desc = "Initiator"; - break; - - case SCSI::DevType::HD: - { - SCSIHD *hd = dynamic_cast(d); - if (hd) { - desc = string_format("HD(%dMB)", - (int)(hd->GetSize() / 1024 / 1024)); - } - break; - } + std::string desc = SCSI::GetDevTypeName(d->GetDevType()); - default: - break; + SCSIDisk *disk = dynamic_cast(d); + if (disk && disk->IsMediumLoaded()) { + desc += string_format("(%dMB)", + (int)(disk->GetSize() / 1024 / 1024)); } // 本体 ID は BDID が設定される前でもエミュレータ的に知ってはいるが、 @@ -689,7 +661,7 @@ SPCDevice::MonitorUpdate(TextScreen& mon } else { idc = '-'; } - monitor.Print(x, y, "%c: %s", idc, desc.c_str()); + screen.Print(x, y, "%c: %s", idc, desc.c_str()); y++; } @@ -711,7 +683,7 @@ SPCDevice::MonitorUpdate(TextScreen& mon } } const char *cmdname = SCSI::GetCommandName(cmds[0]); - monitor.Print(x, y + i, "$%02x %s", cmds[0], cmdname ?: ""); + screen.Print(x, y + i, "$%02x %s", cmds[0], cmdname ?: ""); } } } @@ -720,12 +692,16 @@ SPCDevice::MonitorUpdate(TextScreen& mon // レジスタをビットごとに表示する。MonitorUpdate の下請け。 void -SPCDevice::MonitorReg(TextScreen& monitor, +SPCDevice::MonitorReg(TextScreen& screen, int x, int y, uint32 reg, const char * const *names) { for (int i = 0; i < 8; i++) { - bool b = reg & (1 << (7 - i)); - monitor.Puts(x + i * 5, y, TA::OnOff(b), names[i]); + if (names[i][0] == '-') { + screen.Puts(x + i * 5, y, TA::Disable, "----"); + } else { + bool b = reg & (1 << (7 - i)); + screen.Puts(x + i * 5, y, TA::OnOff(b), names[i]); + } } } @@ -740,7 +716,7 @@ SPCDevice::WriteSCTL(uint32 data) putlog(3, "SCTL <- $%02x", spc.sctl); if ((up & SPC::SCTL_RESET)) { // リセット - Reset(); + ResetHard(false); } else { // 通常動作 } @@ -748,7 +724,7 @@ SPCDevice::WriteSCTL(uint32 data) putlog(2, "Control Reset"); } if ((spc.sctl & SPC::SCTL_DIAG_MODE)) { - putlog(0, "SCTL 未実装 Diag Mode"); + putlog(0, "SCTL Diag Mode (NOT IMPLEMENTED)"); } // 割り込みマスクを更新して信号線の状態を変える @@ -798,11 +774,11 @@ SPCDevice::WriteSCMD(uint32 data) switch (spc.scmd >> 5) { case 0: - putlog(2, "SCMD Bus Release コマンド"); + putlog(2, "SCMD Bus Release command"); if ((spc.scmd & SPC::SCMD_INTERCEPT)) { // BusRelease コマンドの発行と共に Intercept ビットが立っていたら // Service Required 割り込みをリセットする? - putlog(0, "SCMD Intercept with BusRelease not implemented"); + putlog(0, "SCMD Intercept with BusRelease (NOT IMPLEMENTED)"); } BusFree(); break; @@ -810,7 +786,7 @@ SPCDevice::WriteSCMD(uint32 data) SelectCommand(); break; case 2: - putlog(1, "SCMD Reset ATN コマンド (未実装)"); + putlog(1, "SCMD Reset ATN command (NOT IMPLEMENTED)"); break; case 3: SetATNCommand(); @@ -819,21 +795,26 @@ SPCDevice::WriteSCMD(uint32 data) TransferCommand(); break; case 5: - putlog(2, "SCMD Transfer Pause コマンド (未実装)"); + putlog(2, "SCMD Transfer Pause command (NOT IMPLEMENTED)"); break; case 6: - putlog(2, "SCMD Reset ACK/REQ コマンド"); + putlog(2, "SCMD Reset ACK/REQ command"); // イニシエータなら ACK を、ターゲットなら REQ をリセットする。 // ターゲットモードはサポートしないので、操作するのは ACK のみ。 NegateACK(); break; case 7: - putlog(2, "SCMD Set ACK/REQ コマンド"); + putlog(2, "SCMD Set ACK/REQ command"); // イニシエータなら ACK を、ターゲットなら REQ をセットする。 // ターゲットモードはサポートしないので、操作するのは ACK のみ。 if ((spc.scmd & SPC::SCMD_INTERCEPT)) { - // Intercept 動作は未実装 - putlog(0, "SCMD Intercept with SetACK not implemented"); + // Intercept なら DREG を維持したまま (たぶん TEMP で) バスを + // 直接アクセスするようだが、この DREG 維持あたりは未対応。 + // これが影響するのは DREG にデータが滞留してる時だけのはず + // なので、その時だけログを出す。(X68030 IPLROM、NetBSD/x68k) + if (spc.dreg.Length() != 0) { + putlog(0, "SCMD Intercept with SetACK (NOT IMPLEMENTED)"); + } } AssertACK(); break; @@ -866,9 +847,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); } } } @@ -911,9 +892,10 @@ SPCDevice::ChangeInterrupt() // ints_mask は現在の SCTL_INTR_EN 状態に応じたマスクが用意されているので // 単純に AND するだけでよい。 if ((spc.ints & spc.ints_mask) != 0) { - putlog(3, "割り込み ints=$%02x", spc.ints); + putlog(3, "Interrupt (ints=$%02x)", spc.ints); interrupt->AssertINT(this); } else { + putlog(4, "Negate Interrupt"); interrupt->NegateINT(this); } } @@ -965,10 +947,10 @@ SPCDevice::GetSSTS() const if (spc.tc == 0) { // TC がゼロか data |= SPC::SSTS_TC_ZERO; } - if (spc.dreg.Length() == 8) { + if (spc.dreg.IsFull()) { data |= SPC::SSTS_DREG_FULL; } - if (spc.dreg.Length() == 0) { + if (spc.dreg.Empty()) { data |= SPC::SSTS_DREG_EMPTY; } @@ -984,22 +966,24 @@ SPCDevice::GetSERR() const return spc.serr | xfer_out; } +// PCTL レジスタへの書き込み。 +void +SPCDevice::WritePCTL(uint32 data) +{ + data &= SPC::PCTL_MASK; + putlog(3, "PCTL <- $%02x", data); + + spc.busfree_intr_enable = (data & SPC::PCTL_BFINT_EN); + spc.pctl_out = data & (SPC::PCTL_MSG | SPC::PCTL_CD | SPC::PCTL_IO); + // この時点では pctl_out はまだバスに出さない。 + // バスに出すのは転送開始時のはず。 +} + // PCTL レジスタ値の取得。 -// Read*() と Peek8() から呼ばれる。 -// scsibus 接続前にもモニタの更新などで呼ばれることに注意。 uint32 SPCDevice::GetPCTL() const { - uint32 data; - - data = 0; - if (__predict_true(bus != NULL)) { - data |= (uint32)GetXfer(); - } - if (spc.busfree_intr_enable) { - data |= SPC::PCTL_BFINT_EN; - } - return data; + return spc.busfree_intr_enable | spc.pctl_out; } // DREG レジスタの読み込み @@ -1012,22 +996,30 @@ SPCDevice::ReadDREG() // X68k は SPC の DREQ 信号から DTACK 信号を作っているため、 // DREQ 信号を出さないプログラム転送モードでは DREG へのアクセスが // すべてバスエラーになってしまう。 - if (vmtype == VMTYPE_X68030) { + 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, "DREG empty during DMA transferring"); + return (uint64)-2; // DTACK まだ出てないよ + } } } // XFER_OUT は DREG アクセスでリセットされる spc.ints &= ~SPC::INTS_XFER_OUT; - ChangeInterrupt(); + // TransferComplete() が割り込み状態を変化させる可能性があるので + // 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); @@ -1049,10 +1041,13 @@ SPCDevice::ReadDREG() putlog(3, "DREG -> $%02x (empty)", data); } else { // DMA 転送 - // XXX どうする? - PANIC("DMA転送中の DREG empty"); + // DMA 側でリトライするのでここには来ないはず + VMPANIC("DMA転送中の DREG empty"); } } + + // 割り込み状態を確定 + ChangeInterrupt(); return data; } @@ -1063,9 +1058,15 @@ SPCDevice::WriteDREG(uint32 data) // X68k は SPC の DREQ 信号から DTACK 信号を作っているため、 // DREQ 信号を出さないプログラム転送モードでは DREG へのアクセスが // すべてバスエラーになってしまう。 - if (vmtype == VMTYPE_X68030) { + 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; + } } } @@ -1074,7 +1075,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 時間は適当 @@ -1148,7 +1151,7 @@ SPCDevice::SelectCommand() idstr = string_format("#%d -> #%d", myid, tgtid); } } - putlog(2, "Select コマンド (%s)", idstr.c_str()); + putlog(2, "Select command (%s)", idstr.c_str()); // アービトレーションありの場合 // @@ -1190,7 +1193,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 { @@ -1204,9 +1207,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)) { @@ -1239,7 +1242,12 @@ SPCDevice::Arbitration1(Event& ev) // 本当は DB* -> SEL -> ATN の順でタイミングも規定されてるけど省略。 // ここでは SEL がトリガーになるので SEL のアサートは最後に行う。 + // アービトレーションありなら、セレクション/リセレクションフェーズを + // 区別するため PCTL に設定された IO をバスに出す(p.27)。 + // MSG と CD は記述がないけどたぶん一緒に出すだろう。 + // タイミングもどこでか分からんのでとりあえずここで。 SetData(spc.temp_out); + SetXfer(spc.pctl_out); if (set_atn_in_selection) { AssertATN(); set_atn_in_selection = false; @@ -1272,7 +1280,10 @@ SPCDevice::Selection1(Event& ev) // 本当は DB* -> SEL の順でタイミングも規定されてるけど省略。 // ここでは SEL がトリガーになるので SEL のアサートは最後に行う。 + // アービトレーションなしなら PCTL に関係なくセレクションフェーズ (IO=0) + // を開始する(p.27)。MSG と CD は記述がないけどたぶん一緒に出すだろう。 SetData(spc.temp_out); + SetXfer(0); AssertSEL(); } @@ -1286,9 +1297,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); } } @@ -1327,17 +1338,21 @@ void SPCDevice::TransferCommand() { putlog(2, "SCMD Transfer Command (xfer=%s len=0x%x)", - SCSI::GetXferPhaseName(GetPCTL() & 7), spc.tc); + SCSI::GetXferPhaseName(spc.pctl_out), spc.tc); // SCMD レジスタ書き込みのうち Transfer コマンドに関わる未実装機能の // ログ表示は SCMD レジスタ書き込み時ではなくここで表示。 if ((spc.scmd & SPC::SCMD_TERM_MODE)) { - putlog(0, "SCMD Padding mode not implemented"); + putlog(0, "SCMD Padding mode (NOT IMPLEMENTED)"); } transfer_command_running = true; spc.ssts |= SPC::SSTS_SPC_BUSY | SPC::SSTS_IN_PROGRESS; + // PCTL レジスタに設定してあるフェーズをバスに出す。 + // XXX どこでかよく分からんけど、とりあえずこの辺で。 + SetXfer(spc.pctl_out); + // 転送開始前に DREG キューを初期化する。 // MPU 側が DREG を全部読まずにフェーズを中断とかはたぶん出来るので。 // XXX 転送開始前なのかリセットなのか… @@ -1348,8 +1363,7 @@ SPCDevice::TransferCommand() // TransferReqCB() が呼ばれてそこから転送フェーズが動き始める。 if (GetREQ()) { // コマンドから転送開始までちょっとだけかかる - // XXX 4 * t_CLF だと NetBSD カーネルがレースコンディションにはまる。 - CallAfter(TransferCommandStart, "SPC SCMD to Start", 0 * t_CLF); + CallAfter(HardwareTransfer, "SPC SCMD to Start", 4 * t_CLF); } } @@ -1361,7 +1375,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); @@ -1389,7 +1403,7 @@ SPCDevice::SelectionAckCB() NegateSEL(); // タイムアウトイベントを停止 - event_tc.Stop(); + gScheduler->StopEvent(event_tc); spc.ssts &= ~SPC::SSTS_SPC_BUSY; spc.ssts |= SPC::SSTS_CONN_INIT; @@ -1408,31 +1422,20 @@ SPCDevice::TransferReqCB() if (transfer_command_running) { // Transfer Command 実行中なら REQ 待ちだったので // ここでハードウェア転送を開始。 - TransferCommandStart(event); - } else { - // ハードウェア転送中でなくて IN 方向ならここで TEMP に取り込む。 + HardwareTransfer(event); + } + + // データシートでは、マニュアル転送で IN 方向の時データバスの内容を + // TEMP に取り込むと書いてある(p.78)。 + // HardwareTransfer によって状態が変更される可能性があるため + // もう一回チェック。 + if (transfer_command_running == false) { if (GetIO()) { spc.temp_in = GetData(); } } } -// Transfer Command の実行開始部分。 -// TransferCommand() と TransferReqCB() から呼ばれる。 -void -SPCDevice::TransferCommandStart(Event& ev) -{ - // Transfer コマンドの発行と REQ がアサートの両方が成立した時点で、 - // SCSI バスの状態と PCTL レジスタの下位3ビットの状態を比較する。 - // 一致していれば転送開始、そうでなければ Service Required 割り込み。 - if ((GetPCTL() & 7) == GetXfer()) { - HardwareTransfer(event); - } else { - transfer_command_running = false; - SetINTS(SPC::INTS_SERV_REQ); - } -} - // ハードウェア転送の1バイト分。 // イベントコールバックとして呼び出すこと。 // REQ がアサートされているのを確認してから呼び出すこと。 @@ -1445,6 +1448,15 @@ SPCDevice::HardwareTransfer(Event& ev) // (SPC から見て) 入力操作時は読み込めるデータがある場合、出力操作時は // キューにバイトを書き込む必要がある場合に、割り込みを上げる。 + // PCTL のフェーズと SCSI バスのフェーズが一致していれば転送(開始|継続)。 + // そうでなければ Service Required 割り込み。 + if (spc.pctl_out != GetXfer()) { + transfer_command_running = false; + putlog(3, "Hardware Transfer terminated (phase mismatch)"); + SetINTS(SPC::INTS_SERV_REQ); + return; + } + // DREG が送受信不可能ならここでは何もせずに帰るだけでよい。 // その後 DREG アクセスによって送受信可能になればまたこれが呼ばれる。 if (GetIO()) { @@ -1473,9 +1485,6 @@ SPCDevice::HardwareTransfer(Event& ev) return; } } - // SDGC XFER_OUT のログと順序が微妙になるけど、 - // DREG full/empty で2行出るのを避けたいので。 - putlog(4, "Hardware Transfer"); AssertACK();