--- nono/vm/spc.cpp 2026/04/29 17:04:56 1.1.1.10 +++ nono/vm/spc.cpp 2026/04/29 17:04:59 1.1.1.11 @@ -58,13 +58,36 @@ SPCDevice::~SPCDevice() bool SPCDevice::Init() { + // 実行中に別インスタンスを頻繁に参照しないようにここで vmtype を覚えておく + vmtype = gMainApp.GetVMType(); + + // 本体の 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; + } else { + myid0 = 7; + } + + // モニタには、接続している 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()); + } + } + + // この親の Init() は connected_devices 作成後に呼ぶ if (!inherited::Init()) { return false; } - // 実行中に別インスタンスを頻繁に参照しないようにここで vmtype を覚えておく - vmtype = gMainApp.GetVMType(); - // 入力クロックは機種によって異なる。 // X68k は 5MHz が入っている。 // LUNA-I はおそらく 6.144MHz だと思われる。LUNA88K は未確認。 @@ -91,27 +114,6 @@ SPCDevice::Init() 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; - } else { - myid0 = 7; - } - - // モニタには、接続している 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.SetSize(70, 15 + connected_devices.size()); return true; @@ -128,6 +130,8 @@ SPCDevice::ResetHard() spc.ints = 0x00; spc.ssts &= 0x0f; spc.serr = 0x00; + // 変更しないレジスタは次の通り (p.60) + // BDID, TMOD, TC[HML]?, SCMD, PCTL, TEMP(out) // XXX マニュアルには書いていないけど、 // SDGC bit5 の転送ごとに割り込みを発生させるビットが @@ -307,10 +311,7 @@ SPCDevice::Write(uint32 offset, uint32 d 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: @@ -473,7 +474,7 @@ SPCDevice::MonitorUpdate(Monitor *, Text screen.Print(6 + i * 4, y, "$%02x", spc.dreg.Peek(i)); } for (; i < 8; i++) { - screen.Print(6 + i * 4, y, TA::Disable, "---"); + screen.Puts(6 + i * 4, y, "---"); } } y++; @@ -493,7 +494,7 @@ SPCDevice::MonitorUpdate(Monitor *, Text // SCMD static const char * const scmd_bits[] = { - "", "", "", "RST", "INTC", "PROG", "", "TERM" + "", "", "", "RST", "INTC", "PROG", "----", "TERM" }; MonitorReg(screen, x, y, scmd, scmd_bits); static const char * const scmd_cmd[] = { @@ -526,7 +527,7 @@ SPCDevice::MonitorUpdate(Monitor *, Text // SDGC static const char * const sdgc_bits[] = { - "", "", "XfEn", "", "", "", "", "" + "-", "-", "XfEn", "-", "-", "-", "-", "-" }; MonitorReg(screen, x, y, sdgc, sdgc_bits); y++; @@ -548,14 +549,14 @@ SPCDevice::MonitorUpdate(Monitor *, Text // SERR static const char * const serr_bits[] = { - "", "", "XOut", "", "PErr", "", "Shrt", "" + "-", "-", "XOut", "-", "PErr", "-", "Shrt", "-" }; 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(screen, x, y, pctl, pctl_bits); y++; @@ -640,24 +641,12 @@ SPCDevice::MonitorUpdate(Monitor *, Text y = 14; 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 が設定される前でもエミュレータ的に知ってはいるが、 @@ -704,8 +693,12 @@ 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)); - screen.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]); + } } } @@ -969,22 +962,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 レジスタの読み込み @@ -1224,7 +1219,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; @@ -1257,7 +1257,10 @@ SPCDevice::Selection1(Event& ev) // 本当は DB* -> SEL の順でタイミングも規定されてるけど省略。 // ここでは SEL がトリガーになるので SEL のアサートは最後に行う。 + // アービトレーションなしなら PCTL に関係なくセレクションフェーズ (IO=0) + // を開始する(p.27)。MSG と CD は記述がないけどたぶん一緒に出すだろう。 SetData(spc.temp_out); + SetXfer(0); AssertSEL(); } @@ -1312,7 +1315,7 @@ 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 レジスタ書き込み時ではなくここで表示。 @@ -1323,6 +1326,10 @@ SPCDevice::TransferCommand() transfer_command_running = true; spc.ssts |= SPC::SSTS_SPC_BUSY | SPC::SSTS_IN_PROGRESS; + // PCTL レジスタに設定してあるフェーズをバスに出す。 + // XXX どこでかよく分からんけど、とりあえずこの辺で。 + SetXfer(spc.pctl_out); + // 転送開始前に DREG キューを初期化する。 // MPU 側が DREG を全部読まずにフェーズを中断とかはたぶん出来るので。 // XXX 転送開始前なのかリセットなのか… @@ -1333,8 +1340,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); } } @@ -1393,31 +1399,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 がアサートされているのを確認してから呼び出すこと。 @@ -1430,6 +1425,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()) { @@ -1458,9 +1462,6 @@ SPCDevice::HardwareTransfer(Event& ev) return; } } - // SDGC XFER_OUT のログと順序が微妙になるけど、 - // DREG full/empty で2行出るのを避けたいので。 - putlog(4, "Hardware Transfer"); AssertACK();