--- nono/vm/spc.cpp 2026/04/29 17:04:28 1.1 +++ nono/vm/spc.cpp 2026/04/29 17:05:32 1.1.1.17 @@ -1,488 +1,479 @@ // // nono -// Copyright (C) 2017 isaki@NetBSD.org +// Copyright (C) 2020 nono project +// Licensed under nono-license.txt +// + +// +// SPC (MB89352) // #include "spc.h" -#include "mpu.h" -#include "mystring.h" +#include "config.h" +#include "mainapp.h" +#include "monitor.h" +#include "pedec.h" #include "scheduler.h" -#include "scsitarget.h" -#include "vm.h" - -SPCDevice *gSPC; - -SPCDevice::SPCDevice(uint32 addr) -{ - logname = "spc"; - devname = "SPC"; - // X68k と LUNA でアドレスが違う。 - devaddr = addr; - // devlen は XIOSpace に必要なので LUNA ではダミー - devlen = 0x2000; - - config_name = "spc0"; +#include "scsidev.h" - monitor.Init(70, 17); - - // イベント - // 名前とコールバック関数は都度セットする。 - event.dev = this; +// time 時間後に呼び出すコールバックをセットする。 +// func の書式が面倒なのを省略して書きたいため。 +#define CallAfter(func_, name_, time_) do { \ + phase_event.func = ToEventCallback(&SPCDevice::func_); \ + phase_event.time = time_; \ + phase_event.SetName(name_); \ + scheduler->RestartEvent(phase_event); \ +} while (0) + +// コンストラクタ +SPCDevice::SPCDevice() + : inherited(OBJ_SPC) +{ + // 初期値は不定だが表示上困るのでとりあえず。 + myid = 7; + + monitor = gMonitorManager->Regist(ID_MONITOR_SPC, this); + monitor->func = ToMonitorCallback(&SPCDevice::MonitorUpdate); + // サイズは Init で決まる } +// デストラクタ SPCDevice::~SPCDevice() { } bool -SPCDevice::Init() +SPCDevice::Create() { - if (gVM->GetType() == VM::TYPE_LUNA) { - regbase = 0xe1000000; - regstride = 4; - // おそらく 6.144MHz が入っていると思われる - t_CLF = 163; - } else { - regbase = 0xe96021; - regstride = 2; - t_CLF = 200; + if (inherited::Create() == false) { + return false; + } + + scsi.reset(new SCSIHostDevice(this, "spc0")); + if ((bool)scsi == false) { + return false; } + scsi->SetCallback( + ToBusFreeCallback(&SPCDevice::BusFreeCallback), + ToSCSIHostCallback(&SPCDevice::SelectionAckCallback), + ToSCSIHostCallback(&SPCDevice::TransferReqCallback)); return true; } -// 本体リセットスイッチによるリセット -void -SPCDevice::ResetHard() +// 初期化 +bool +SPCDevice::Init() { - // LUNA は本体回路が分からないのでとりあえずこっち。 - if (gVM->GetType() == VM::TYPE_LUNA) { - Reset(); + if (inherited::Init() == false) { + return false; } -} -// MPU の RESET 命令によるリセット -void -SPCDevice::ResetSoft() -{ - // X680x0 はこっちだと分かっている。 - if (gVM->GetType() == VM::TYPE_X68030) { - Reset(); + // 実行中に別インスタンスを頻繁に参照しないようにここで vmtype を覚えておく + vmtype = gMainApp.GetVMType(); + + // 入力クロックは機種によって異なる。 + // X68k は 5MHz が入っている。 + // LUNA-I はおそらく 6.144MHz だと思われる。LUNA-88K は未確認。 + const ConfigItem& item = gConfig->Find("!spc-tCLF"); + t_CLF = item.AsInt(); + + // アクセスウェイト + if (vmtype == VMType::X68030) { + // InsideOut p.135 + read_wait = busdata::Wait(18 * 40_nsec); + write_wait = busdata::Wait(19 * 40_nsec); + } else { + // 他機種は未調査だけど、判明するまでの間とりあえず + // ノーウェイトでない適当な値を入れておく。 + read_wait = busdata::Wait(18 * 40_nsec); + write_wait = busdata::Wait(19 * 40_nsec); + } + + // 割り込み信号線の接続先 + if (vmtype == VMType::X68030) { + // X68030 は PEDEC (I/O コントローラ) にカスケード + interrupt = GetPEDECDevice(); + } else { + interrupt = GetInterruptDevice(); } + + // 汎用イベント + // 名前とコールバック関数は都度セットする。 + phase_event.SetName("SPC Phase"); + scheduler->RegistEvent(phase_event); + + // TC カウンタイベント + timer_event.code = 0; + timer_event.SetName("SPC Timer"); + scheduler->RegistEvent(timer_event); + + // モニタに表示するアドレス + if (vmtype == VMType::X68030) { + baseaddr = 0xe96021; + regoffset = 2; + } else { + baseaddr = 0xe1000000; + regoffset = 4; + } + // 「イニシエータ以外のターゲット数」はここでは確定している。 + monitor->SetSize(80, 15 + scsi->GetOwnedBus()->GetTargetCount() + 1); + + return true; } -// SPC リセット +// デバイスリセット void -SPCDevice::Reset() +SPCDevice::ResetHard(bool poweron) { - 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 の転送ごとに割り込みを発生させるビットが // OFFになることを NetBSD が期待しているし、 // このビットは落ちるのではないだろうか spc.sdgc = 0x00; + + // キューはリセットされる。 + // mb89351-scsi.pdf (日本語) + spc.dreg.Clear(); + + // 内部状態 + MakeIntsMask(); + ChangeInterrupt(); + prev_ints = (uint32)-1; + prev_ssts = (uint32)-1; + set_atn_in_selection = false; + transfer_command_running = false; + scheduler->StopEvent(phase_event); + scheduler->StopEvent(timer_event); + + last_busfree_time = 0; } -uint64 -SPCDevice::Read8(uint32 addr) +busdata +SPCDevice::ReadPort(uint32 offset) { - uint32 an = addr - regbase; - uint8 data; - - // regbase を減算するとレジスタは常に +0 のバイトにある。 - // X68k で +1 (偶数アドレス) はバスエラーになる - // LUNA で +1,+2,+3 はミラー - if (gVM->GetType() == VM::TYPE_X68030) { - if ((an % regstride) != 0) { - return (uint64)-1; - } - } - an /= regstride; + busdata data; - switch (an) { + switch (offset) { case SPC::BDID: data = 1 << myid; - putlog(3, "BDID -> $%02x", data); - return data; + putlog(3, "BDID -> $%02x", data.Data()); + break; case SPC::SCTL: data = spc.sctl; - putlog(3, "SCTL -> $%02x", data); - return data; + putlog(3, "SCTL -> $%02x", data.Data()); + break; case SPC::SCMD: - data = GetSCMD(); - putlog(3, "SCMD -> $%02x", data); - return data; + data = spc.scmd; + putlog(3, "SCMD -> $%02x", data.Data()); + break; case SPC::TMOD: // MB89352 には存在しないレジスタ。 // 下位2ビットは %0 らしいので意味はないだろうけど真似ておく。 - return 0xfc; + data = 0xfc; + break; case SPC::INTS: - data = spc.ints; - if (loglevel >= 2) { - static uint32 prev_ints = -1U; - if (event.active) { + data = GetINTS(); + if (loglevel >= 3) { + uint32 new_ints = data.Data(); + if (timer_event.IsRunning()) { // イベント中は値が変わった時だけ表示 - if (prev_ints != data) { - putlog(3, "INTS -> $%02x", data); - prev_ints = data; + if (prev_ints != new_ints) { + putlogn("INTS -> $%02x", new_ints); + prev_ints = new_ints; } } else { - putlog(3, "INTS -> $%02x", data); + putlogn("INTS -> $%02x", new_ints); prev_ints = -1U; } } - return data; + break; case SPC::PSNS: data = GetPSNS(); - putlog(3, "PSNS -> $%02x", data); - return data; + putlog(3, "PSNS -> $%02x", data.Data()); + break; case SPC::SSTS: - // ここで DataIn フェーズを進めてみる。 - // 本来 DataIn フェーズでの DREG キューへのデータの充填は - // レジスタアクセスとは無関係に行われるはずだが、そんな機構は - // 面倒なので、(DREG Full/Empty を調べるために) SSTS をアクセスした - // ところで、データがあれば DREG キューを埋めるようにしておく。 - if (phase == SCSI::Phase::DataIn) { - EnqueueToDREG(); - } - - // ここからが本来の SSTS 読み込み data = GetSSTS(); - if (loglevel >= 2) { - static uint32 prev_ssts = -1U; - if (event.active) { + if (loglevel >= 3) { + uint32 new_ssts = data.Data(); + if (timer_event.IsRunning()) { // イベント中は値が変わった時だけ表示 - if (prev_ssts != data) { - putlog(3, "SSTS -> $%02x", data); - prev_ssts = data; + if (prev_ssts != new_ssts) { + putlogn("SSTS -> $%02x", new_ssts); + prev_ssts = new_ssts; } } else { - putlog(3, "SSTS -> $%02x", data); + putlogn("SSTS -> $%02x", new_ssts); prev_ssts = -1U; } } - return data; + break; case SPC::SERR: - data = spc.serr; - putlog(3, "SERR -> $%02x", data); - return data; + data = GetSERR(); + putlog(3, "SERR -> $%02x", data.Data()); + break; case SPC::PCTL: data = GetPCTL(); - putlog(3, "PCTL -> $%02x", data); - return data; + putlog(3, "PCTL -> $%02x", data.Data()); + break; case SPC::MBC: - putlog(0, "MBC 未実装読み込み"); - return 0xff; + putlog(0, "Read MBC (NOT IMPLEMENTED)"); + data = 0x0f; + break; case SPC::DREG: - // XFER_OUT は DREG アクセスでリセットされる - spc.serr &= ~SPC::SERR_XFER_OUT; - - // 受信データがあればここで DREG に充填 - EnqueueToDREG(); - if (spc.dreg.Length() > 0) { - data = spc.dreg.Dequeue(); - spc.tc--; - if (loglevel >= 4) { - putlog("DREG -> $%02x (q=%d datalen=%d TC=%d)", - data, spc.dreg.Length(), datalen, spc.tc); - } else { - putlog(3, "DREG -> $%02x", data); - } - if (spc.tc == 0) { - SetINTS(SPC::INTS_COMPLETE); - } - } else { - // どうなる? - data = 0xff; - putlog(3, "DREG -> $%02x (empty)", data); - } - return data; + data = ReadDREG(); + break; case SPC::TEMP: data = spc.temp_in; - putlog(3, "TEMP -> $%02x", data); - return data; + putlog(3, "TEMP -> $%02x", data.Data()); + break; case SPC::TCH: data = spc.tc >> 16; - putlog(3, "TCH -> $%02x", data); - return data; + putlog(3, "TCH -> $%02x", data.Data()); + break; case SPC::TCM: data = (spc.tc >> 8) & 0xff; - putlog(3, "TCM -> $%02x", data); - return data; + putlog(3, "TCM -> $%02x", data.Data()); + break; case SPC::TCL: data = spc.tc & 0xff; - putlog(3, "TCL -> $%02x", data); - return data; + putlog(3, "TCL -> $%02x", data.Data()); + break; case SPC::EXBF: // MB89352 には存在しないレジスタ - return 0xff; - } - PANIC("not reached"); -} + data = 0xff; + break; -uint64 -SPCDevice::Read16(uint32 addr) -{ - // XXX どうしたもんか - if (gVM->GetType() == VM::TYPE_LUNA) { - // LUNA はミラー - return (Read8(addr) << 8) | Read8(addr + 1); - } else { - // X68k は奇数アドレスのみ - return 0xff00 | Read8(addr + 1); + default: + VMPANIC("corrupted offset=%u", offset); } + + data |= read_wait; + data |= BusData::Size1; + return data; } -uint64 -SPCDevice::Write8(uint32 addr, uint32 data) +busdata +SPCDevice::WritePort(uint32 offset, uint32 data) { - uint32 an = addr - regbase; + busdata r; - // regbase を減算するとレジスタは常に +0 のバイトにある。 - // X68k で +1 (偶数アドレス) はバスエラーになる - // LUNA で +1,+2,+3 はミラー - if (gVM->GetType() == VM::TYPE_X68030) { - if ((an % regstride) != 0) { - return (uint64)-1; + switch (offset) { + case SPC::BDID: + { + uint cur_myid = scsi->GetMyID(); + putlog(3, "BDID <- $%02x", data); + if (data == cur_myid) { + // 同じなら何もしない + break; + } + if ((int)cur_myid < 0) { + // 初回なのでバスに参加。myid はこちらでもコピーを覚えておく。 + myid = data; + if (scsi->GetOwnedBus()->AttachDevice(scsi.get(), myid) == false) { + putlog(0, "BDID %u conflicts with existing devices!", myid); + } + } else { + // そうでなければ移動? + putlog(0, "BDID change %u to %u (NOT IMPLEMENTED)", cur_myid, data); } - } - an /= regstride; + break; + } - switch (an) { - case SPC::BDID: - myid = data; - putlog(3, "BDID <- $%02x", myid); - return 0; case SPC::SCTL: WriteSCTL(data); - return 0; + break; case SPC::SCMD: WriteSCMD(data); - return 0; + break; case SPC::TMOD: // MB89352 には存在しないレジスタ - return 0; + break; case SPC::INTS: WriteINTS(data); - return 0; + break; case SPC::SDGC: spc.sdgc = data; putlog(3, "SDGC <- $%02x", spc.sdgc); - return 0; + break; + + case SPC::SSTS: + // XXX 書き込みはどうなる? + break; + + case SPC::SERR: + // XXX 書き込みはどうなる? + break; case SPC::PCTL: - data &= SPC::PCTL_MASK; - putlog(3, "PCTL <- $%02x", data); - spc.busfree_intr_enable = (data & SPC::PCTL_BFINT_EN); - SetMSG((data & SPC::PCTL_MSG)); - SetCD((data & SPC::PCTL_CD)); - SetIO((data & SPC::PCTL_IO)); - return 0; + WritePCTL(data); + break; + + case SPC::MBC: + // XXX 書き込みはどうなる? + break; case SPC::DREG: - putlog(3, "DREG <- $%02x len=%d", data, datalen); - // XFER_OUT は DREG アクセスでリセットされる - spc.serr &= ~SPC::SERR_XFER_OUT; - - if (phase == SCSI::Phase::Command) { - // コマンドフェーズなら DREG 使わず cmds に入れていく。 - cmds.push_back(data); - } else if (datalen > 0) { - // data が指定されていればそっちに書き込む。 - *dataptr++ = data; - datalen--; - } - spc.tc--; - if (spc.tc == 0) { - if (phase == SCSI::Phase::Command) { - // コマンドフェーズで全部受け取ったらコマンドを実行。 - if (loglevel >= 3) { - std::string cmdbuf = "Cmd ="; - for (uint i = 0; i < cmds.size(); i++) { - cmdbuf += string_format(" %02x", cmds[i]); - } - putlog("%s", cmdbuf.c_str()); - } - target[tgtid]->ExecCommand(cmds); - } else if (phase == SCSI::Phase::DataOut) { - // データアウトフェーズで全部受け取ったら? - } - SetINTS(SPC::INTS_COMPLETE); - } - return 0; + putlog(3, "DREG <- $%02x", data); + r = WriteDREG(data); + break; case SPC::TEMP: spc.temp_out = data; putlog(3, "TEMP <- $%02x", spc.temp_out); - // セレクション失敗後 SPC は SEL を下げないので TEMP に $00 を - // 書き込むことで SEL を下げるらしい - if (0) { //spc_phase == SCSIphase::Selection) { - BusFree(); + // Inside (p479) によると、SPC はセレクションタイムアウト時 SEL を + // 下げないので、代わりに TEMP に $00 を書き込むことで SEL を下げる、 + // そうなのだが、データシート PDF には記述が見当たらないし、 + // luna68k の /boot はここで SEL が下げてしまうと期待した動作に + // ならないようだ。 + // SPC はセレクションタイムアウトしたからと言って、データバスに出した + // ID を自動的に取り下げたりはしないので、TEMP に $00 を書き込むことで + // データバスをクリアする、だったんでは。 + if (0 && scsi->GetPhase() == SCSI::Phase::Selection) { + scsi->NegateSEL(); } // Out 方向ならバスに出す - if (GetIO() == false) { - SetDATA(spc.temp_out); + if (scsi->GetIO() == false) { + scsi->SetData(spc.temp_out); } - return 0; + break; case SPC::TCH: spc.tc = (spc.tc & 0x00ffff) | (data << 16); - putlog(3, "TC(H) = $%06x", spc.tc); - return 0; + putlog(3, "TCH <- $%02x (TC=$%06x)", data, spc.tc); + break; case SPC::TCM: spc.tc = (spc.tc & 0xff00ff) | (data << 8); - putlog(3, "TC(M) = $%06x", spc.tc); - return 0; + putlog(3, "TCM <- $%02x (TC=$%06x)", data, spc.tc); + break; case SPC::TCL: spc.tc = (spc.tc & 0xffff00) | data; - putlog(3, "TC(L) = $%06x", spc.tc); - return 0; + putlog(3, "TCL <- $%02x (TC=$%06x)", data, spc.tc); + break; case SPC::EXBF: // MB89352 には存在しないレジスタ - return 0; - } - PANIC("not reached"); -} + break; -uint64 -SPCDevice::Write16(uint32 addr, uint32 data) -{ - // XXX どうしたもんか - if (gVM->GetType() == VM::TYPE_LUNA) { - // LUNA はミラーでバスエラーは起きない - Write8(addr, data >> 8); - Write8(addr + 1, data); - return 0; - } else { - // X68k は奇数アドレスのみ - return Write8(addr + 1, data); + default: + VMPANIC("corrupted offset=%u", offset); } + + r |= write_wait; + r |= BusData::Size1; + return r; } -uint64 -SPCDevice::Peek8(uint32 addr) +busdata +SPCDevice::PeekPort(uint32 offset) { - uint32 an = addr - regbase; - uint8 data; - - // regbase を減算するとレジスタは常に +0 のバイトにある。 - // X68k で +1 (偶数アドレス) はバスエラーになる - // LUNA では +1,+2,+3 はミラー - if (gVM->GetType() == VM::TYPE_X68030) { - if ((an % regstride) != 0) { - return (uint64)-1; - } - } - an /= regstride; - - switch (an) { + switch (offset) { case SPC::BDID: - data = 1 << myid; - break; + return 1 << myid; case SPC::SCTL: - data = spc.sctl; - break; + return spc.sctl; case SPC::SCMD: - data = GetSCMD(); - break; + return spc.scmd; case SPC::TMOD: // MB89352 には存在しないレジスタ。 // 下位2ビットは %0 らしいので意味はないだろうけど真似ておく。 - data = 0xfc; - break; + return 0xfc; case SPC::INTS: - data = spc.ints; - break; + return GetINTS(); case SPC::PSNS: - data = GetPSNS(); - break; + return GetPSNS(); case SPC::SSTS: - data = GetSSTS(); - break; + return GetSSTS(); case SPC::SERR: - data = spc.serr; - break; + return GetSERR(); case SPC::PCTL: - data = GetPCTL(); - break; + return GetPCTL(); case SPC::MBC: - data = 0xff; - break; + return 0x0f; case SPC::DREG: - data = spc.dreg.Peek(0); - break; + // X68k は SPC の DREQ 信号から DTACK 信号を作っているため、 + // DREQ 信号を出さないプログラム転送モードでは DREG へのアクセスが + // すべてバスエラーになってしまう。 + if (vmtype == VMType::X68030) { + if ((spc.scmd & SPC::SCMD_PROGRAM)) { + return BusData::BusErr; + } + } + return spc.dreg.Peek(0); case SPC::TEMP: - data = spc.temp_in; - break; + return spc.temp_in; case SPC::TCH: - data = spc.tc >> 16; - break; + return spc.tc >> 16; case SPC::TCM: - data = (spc.tc >> 8) & 0xff; - break; + return (spc.tc >> 8) & 0xff; case SPC::TCL: - data = spc.tc & 0xff; - break; + return spc.tc & 0xff; case SPC::EXBF: // MB89352 には存在しないレジスタ - data = 0xff; - break; + return 0xff; default: - // XXX 未調査 - return (uint64)-1; + return 0xff; } - return data; } bool -SPCDevice::MonitorUpdate() +SPCDevice::PokePort(uint32 offset, uint32 data) +{ + return false; +} + +void +SPCDevice::MonitorUpdate(Monitor *, TextScreen& screen) { int x; int y; + SCSIBus *bus; uint32 sctl; uint32 scmd; uint32 ints; @@ -493,59 +484,62 @@ SPCDevice::MonitorUpdate() uint32 pctl; uint32 tc; - monitor.Clear(); + screen.Clear(); + bus = scsi->GetOwnedBus(); sctl = spc.sctl; - scmd = GetSCMD(); - ints = spc.ints; + scmd = spc.scmd; + ints = GetINTS(); psns = GetPSNS(); sdgc = spc.sdgc; ssts = GetSSTS(); - serr = spc.serr; + serr = GetSERR(); pctl = GetPCTL(); tc = spc.tc; +#define A(n) (baseaddr + (n) * regoffset) + 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++, "$%08x BDID: $%02x (ID=%u)", A(0), (1 << myid), myid); + screen.Print(0, y++, "$%08x SCTL: $%02x", A(1), sctl); + screen.Print(0, y++, "$%08x SCMD: $%02x", A(2), scmd); + screen.Print(0, y++, "$%08x INTS: $%02x", A(4), ints); + screen.Print(0, y++, "$%08x PSNS: $%02x", A(5), psns); + screen.Print(0, y++, "$%08x SDGC: $%02x", A(5), sdgc); + screen.Print(0, y++, "$%08x SSTS: $%02x", A(6), ssts); + screen.Print(0, y++, "$%08x SERR: $%02x", A(7), serr); + screen.Print(0, y++, "$%08x PCTL: $%02x", A(8), pctl); + screen.Print(0, y++, "$%08x MBC : $XX", A(9)); + screen.Print(0, y, "$%08x DREG: ", A(10)); { uint i; for (i = 0; i < spc.dreg.Length(); i++) { - monitor.Print(6 + i * 4, y, "$%02x", spc.dreg.Peek(i)); + screen.Print(16 + i * 4, y, "$%02x", spc.dreg.Peek(i)); } for (; i < 8; i++) { - monitor.Print(6 + i * 4, y, TA::Disable, "---"); + screen.Puts(16 + i * 4, y, "---"); } } y++; - monitor.Print(0, y++, "TEMP: $%02x(IN) $%02x(OUT)", + screen.Print(0, y++, "$%08x TEMP: $%02x(IN) $%02x(OUT)", A(11), spc.temp_in, spc.temp_out); - monitor.Print(0, y++, "TC : $%06x", tc); + screen.Print(0, y++, "$%08x TC : $%06x", A(12), tc); - x = 10; + x = 20; y = 1; // SCTL static const char * const sctl_bits[] = { "RST", "CTRL", "DIAG", "ARB", "PARI", "SEL", "RESL", "INTR" }; - MonitorReg(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(x, y, scmd, scmd_bits); + MonitorReg(screen, x, y, scmd, scmd_bits); static const char * const scmd_cmd[] = { //12345678901234 "(Bus Release)", @@ -557,35 +551,35 @@ SPCDevice::MonitorUpdate() "(ResetACK/REQ)", "(Set ACK/REQ)", }; - monitor.Print(x, y, "%s", 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(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(x, y, psns, psns_bits); + MonitorReg(screen, x, y, psns, psns_bits); y++; // SDGC static const char * const sdgc_bits[] = { - "", "", "XfEn", "", "", "", "", "" + "-", "-", "XfEn", "-", "-", "-", "-", "-" }; - MonitorReg(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(x, y, ssts, ssts_bits); + MonitorReg(screen, x, y, ssts, ssts_bits); static const char * const ssts_conn[] = { //12345678 "(NotConn)", @@ -593,94 +587,146 @@ SPCDevice::MonitorUpdate() "(As Init)", "(undef?)", }; - monitor.Print(x, y, "%s", 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(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(x, y, pctl, pctl_bits); - monitor.Print(x + 15, y, "(%s)", SCSI::GetPhaseName(pctl & 7)); + MonitorReg(screen, x, y, pctl, pctl_bits); y++; // バス状態 - x = 52; + x = 62; y = 0; - // ここは bool を返す GetBSY(), GetSEL() ではなく、 - // 生の BSY, SEL 変数を見ること。 - uint8 bsy = BSY; - uint8 sel = SEL; - monitor.Print(x, y++, "RST :%d", GetRST()); - monitor.Print(x, y++, "BSY :%d(________)", bsy ? 1 : 0); - monitor.Print(x, y++, "SEL :%d(________)", sel ? 1 : 0); - monitor.Print(x, y++, "MSG :%d", GetMSG()); - monitor.Print(x, y++, "CD :%d", GetCD()); - monitor.Print(x, y++, "IO :%d", GetIO()); - monitor.Print(x, y++, "ATN :%d", GetATN()); - monitor.Print(x, y++, "REQ :%d", GetREQ()); - monitor.Print(x, y++, "ACK :%d", GetACK()); - monitor.Print(x, y++, "DATA:$%02x", GetDATA()); + SCSI::Phase ph = bus->GetPhase(); + uint8 rst = bus->GetRST(); + uint8 bsy = bus->GetBSY(); + uint8 sel = bus->GetSEL(); + uint8 msg = bus->GetMSG(); + uint8 cd = bus->GetCD(); + uint8 io = bus->GetIO(); + uint8 atn = bus->GetATN(); + uint8 req = bus->GetREQ(); + uint8 ack = bus->GetACK(); + uint8 data = bus->GetData(); + + 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; + 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 の内訳 - x = 59; - y = 1; + // BSY, SEL の内訳。"(76543210)" の順。 + y = 2; for (int i = 0; i < 8; i++) { if ((bsy & (1 << i))) { - monitor.Print(x + i, y, "%d", i); + screen.Print(x + 4 + 8 - i, y, "%u", i); } else { - monitor.Print(x + i, y, "_"); + screen.Print(x + 4 + 8 - i, y, "_"); } } y++; for (int i = 0; i < 8; i++) { if ((sel & (1 << i))) { - monitor.Print(x + i, y, "%d", i); + screen.Print(x + 4 + 8 - i, y, "%u", i); } else { - monitor.Print(x + i, y, "_"); + screen.Print(x + 4 + 8 - i, y, "_"); } } - // ターゲット - x = 0; - y = 14; - monitor.Print(x, y, "Target"); - if (tgtid >= 0) { - monitor.Print(x + 6, y, ": #%d", tgtid); + // 転送フェーズ中なら現在の XferPhase + y += 2; + if (ph == SCSI::Phase::Transfer) { + screen.Puts(x + 5, y, SCSI::GetXferPhaseName(scsi->GetXfer())); } - y++; - - monitor.Print(x, y, "Phase: %s", SCSI::GetPhaseName(phase)); - y++; - monitor.Print(x, y, "Command:"); - if (phase < 8 && cmds.size() > 0) { - const char *cmdname = SCSI::GetCommandName(cmds[0]); - if (cmdname) { - monitor.Print(x + 9, y, "%s", cmdname); + // デバイス一覧 + // 0 1 + // 01234567890123456 + // ID Device Command + // 0: HD(1024MB) $06 Read + // 7: Initiator + x = 0; + y = 14; + screen.Print(x, y++, "ID Device Command"); + for (const auto d : bus->GetConnectedDevices()) { + std::string desc = SCSI::GetDevTypeName(d->GetDevType()); + + const SCSIDisk *disk = dynamic_cast(d); + if (disk && disk->IsMediumLoaded()) { + desc += string_format("(%uMB)", + (uint)(disk->GetSize() / 1024 / 1024)); + } + + // 本体 ID は BDID が設定される前でもエミュレータ的に知ってはいるが、 + // BDID への書き込みがされなかったりおかしかったりしたら (上の BDID + // 欄でも分かるけど) ここも反映されるようにしておく。 + char idc; + if ((int)d->GetMyID() >= 0) { + idc = '0' + d->GetMyID(); } else { - monitor.Print(x + 9, y, "$%02x", cmds[0]); + idc = '-'; } + screen.Print(x, y, "%c: %s", idc, desc.c_str()); + y++; } - return true; + // 実行中のコマンド + x = 14; + y = 15; + SCSITarget *t = bus->GetSelectedTarget(); + if (t) { + std::vector cmds = t->GetCmdSeq(); + if (cmds.size() > 0) { + // この ID のデバイス行に表示したい + // XXX もうちょっとマシにしたい + const auto connected_devices = bus->GetConnectedDevices(); + int i = 0; + for (; i < connected_devices.size(); i++) { + if (t == connected_devices[i]) { + break; + } + } + const char *cmdname = SCSI::GetCommandName(cmds[0]); + screen.Print(x, y + i, "$%02x %s", cmds[0], cmdname ?: ""); + } + } } // レジスタをビットごとに表示する。MonitorUpdate の下請け。 void -SPCDevice::MonitorReg(int x, int y, uint32 reg, const char * const *names) +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)); - uint attr = b ? TA::On : TA::Off; - monitor.Print(x + i * 5, y, attr, "%s", 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]); + } } } @@ -695,7 +741,7 @@ SPCDevice::WriteSCTL(uint32 data) putlog(3, "SCTL <- $%02x", spc.sctl); if ((up & SPC::SCTL_RESET)) { // リセット - Reset(); + ResetHard(false); } else { // 通常動作 } @@ -703,84 +749,99 @@ 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)"); } + + // 割り込みマスクを更新して信号線の状態を変える + MakeIntsMask(); + ChangeInterrupt(); } -// SCMD レジスタ値の取得 -// Read*() と Peek8() から呼ばれる。 -uint32 -SPCDevice::GetSCMD() const +// SCTL_INTR_EN の状態から内部変数 ints_mask を作成する。 +void +SPCDevice::MakeIntsMask() { - uint32 data; - - data = spc.scmd & 0xef; - // XXX RST Out もここで読み出せるのかな? - if (GetRST()) { - data |= SPC::SCMD_RST_OUT; + if ((spc.sctl & SPC::SCTL_INTR_EN)) { + spc.ints_mask = SPC::INTS_ENABLE_MASK; + } else { + spc.ints_mask = SPC::INTS_DISABLE_MASK; } - - return data; } // SCMD レジスタへの書き込み void SPCDevice::WriteSCMD(uint32 data) { + uint32 chg = spc.scmd ^ data; // 変更点 spc.scmd = data; putlog(3, "SCMD <- $%02x", spc.scmd); - // RST Out 操作は SCTL Reset がオフの時だけ有効 - if ((spc.sctl & SPC::SCTL_RESET) == 0) { - SetRST(spc.scmd & SPC::SCMD_RST_OUT); - - if (GetRST() == true) { - putlog(2, "SCSIBus バスリセット"); - - // 即イベント発生させる? - SetINTS(SPC::INTS_RESET_COND); + // Program, Terminate Mode の未実装判定は転送開始時のみ表示する。 - // バスフリーフェーズ - BusFree(); + // RST Out 操作は SCTL Reset がオフの時だけ有効 + // と書いてあるけど、SCTL Reset がオンならほぼ全機能無効なのでは? + if ((spc.sctl & SPC::SCTL_RESET) == 0 && (chg & SPC::SCMD_RST_OUT)) { + if ((spc.scmd & SPC::SCMD_RST_OUT)) { + // 0 -> 1 + // すでに RST が立ってたらどうするか + if (scsi->GetRST() == false) { + putlog(1, "SCMD RST Out"); + BusRelease(); + scsi->AssertRST(); + SetINTS(SPC::INTS_RESET_COND); + } + } else { + // 1 -> 0 + // 特にすることはない? + scsi->NegateRST(); } } 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)"); + } BusFree(); break; case 1: SelectCommand(); break; case 2: - putlog(0, "SCMD Reset ATN コマンド (未実装)"); + putlog(1, "SCMD Reset ATN command (NOT IMPLEMENTED)"); break; case 3: - putlog(2, "SCMD Set ATN コマンド (未実装)"); + SetATNCommand(); break; case 4: 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 のみ。 - spc.ackreq = false; - SetACK(false); + scsi->NegateACK(); break; case 7: - putlog(2, "SCMD Set ACK/REQ コマンド"); + putlog(2, "SCMD Set ACK/REQ command"); // イニシエータなら ACK を、ターゲットなら REQ をセットする。 // ターゲットモードはサポートしないので、操作するのは ACK のみ。 - spc.ackreq = true; - SetACK(true); - if (1) { - target[tgtid]->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)"); + } } + scsi->AssertACK(); break; } } @@ -796,10 +857,7 @@ SPCDevice::WriteINTS(uint32 data) // '1' が書き込まれたビットをクリア spc.ints &= ~data; - - // SPC では INTS でビットを落とす行為は、 - // ホストが割り込み要因に対して処理を完了したことを - // SPC に通知する意味が含まれている。 + putlog(3, "INTS <- $%02x (INTS = $%02x)", data, GetINTS()); // DISCONNECTED ビットが立っているときは SPC は SCSI から // セレクション、リセレクション要求が来ても応答しない。 @@ -808,166 +866,299 @@ SPCDevice::WriteINTS(uint32 data) // TIMEOUT ビットをクリアした時が if ((ack & SPC::INTS_TIMEOUT)) { // セレクションフェーズなら - if (GetBSY() == false && GetSEL() == true) { + if (scsi->GetPhase() == SCSI::Phase::Selection) { // TC がゼロならバスフリー // そうでなければリスタート if (spc.tc == 0) { BusFree(); } else { - event.time = spc.tc * t_CLF * 2; - event.name = "SPC SelFailed"; - event.func = - (DeviceCallback_t)&SPCDevice::SelectionFailedCallback; - gScheduler->AddEvent(&event); + timer_event.func = + ToEventCallback(&SPCDevice::SelectionTimeout); + timer_event.time = spc.tc * t_CLF * 2; + scheduler->RestartEvent(timer_event); } } } - putlog(3, "INTS = $%02x", spc.ints); + // 割り込み信号線の状態を変える + ChangeInterrupt(); } -// INTS 設定 -// INTS レジスタにビットを立てる要因になる事象が起きた時にコールする。 -// INTS を書き換えて、必要なら割り込みを上げる。 -// INTS レジスタへの書き込みではないので注意。 +// INTS 設定。 +// 引数 data は割り込み要因(ここは内部要因なので INTS_XFER_OUT も指定可能)。 +// INTS レジスタへの書き込み(割り込み要因のクリア)は WriteINTS() のほう。 void SPCDevice::SetINTS(uint32 data) { - // 立ち上がりでだけ割り込みがかかるわけではないが表示のため。 - uint32 up = ~spc.ints & data; - // 指定された要因を立てる spc.ints |= data; - // 割り込み許可なら割り込み。 - // RESET_COND はノンマスカブル割り込み。 - if ((spc.sctl & SPC::SCTL_INTR_EN) || (spc.ints & SPC::INTS_RESET_COND)) { -putlog(3, "割り込み ints=%x up=%x", spc.ints, up); - if (gVM->GetType() == VM::TYPE_LUNA) { - gMPU->Interrupt(2); - } else { - // とりあえず内蔵だけ - gMPU->Interrupt(1, 0x6c); - } + // Disconnected 割り込みが起きたら ATN を下げる + if ((spc.ints & SPC::INTS_DISCONNECTED) && scsi->GetATN()) { + scsi->NegateATN(); + } + + 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, "Interrupt (ints=$%02x)", spc.ints); + interrupt->AssertINT(this); + } else { + putlog(4, "Negate Interrupt"); + interrupt->NegateINT(this); } } // PSNS レジスタ値の取得。 -// Read*() と Peek8() から呼ばれる。 +// ReadPort() と PeekPort() から呼ばれる。 +// scsibus 接続前にもモニタの更新などで呼ばれることに注意。 uint32 SPCDevice::GetPSNS() const { - uint32 data; + uint32 data = 0; + + if (__predict_false(scsi->GetBus() == NULL)) { + return 0; + } - data = spc.psns & 0xe0; - if (GetREQ()) { + if (scsi->GetREQ()) { data |= SPC::PSNS_REQ; } - if (GetACK()) { + if (scsi->GetACK()) { data |= SPC::PSNS_ACK; } - if (GetATN()) { + if (scsi->GetATN()) { data |= SPC::PSNS_ATN; } - if (GetSEL()) { + if (scsi->GetSEL()) { data |= SPC::PSNS_SEL; } - if (GetBSY()) { + if (scsi->GetBSY()) { data |= SPC::PSNS_BSY; } - if (GetMSG()) { - data |= SPC::PSNS_MSG; - } - if (GetCD()) { - data |= SPC::PSNS_CD; - } - if (GetIO()) { - data |= SPC::PSNS_IO; - } + data |= (uint32)scsi->GetXfer(); return data; } // SSTS レジスタ値の取得。 -// Read*() と Peek8() から呼ばれる。 +// ReadPort() と PeekPort() から呼ばれる。 +// scsibus 接続前にもモニタの更新などで呼ばれることに注意。 uint32 SPCDevice::GetSSTS() const { uint32 data; data = spc.ssts & 0xf0; - if (GetRST()) { // RST 信号 + if (__predict_true(scsi->GetBus() != NULL) && scsi->GetRST()) { // RST 信号 data |= SPC::SSTS_RST_IN; } 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; } 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 レジスタへの書き込み。 +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() から呼ばれる。 uint32 SPCDevice::GetPCTL() const { - uint32 data; + return spc.busfree_intr_enable | spc.pctl_out; +} - data = 0; - if (spc.busfree_intr_enable) { - data |= SPC::PCTL_BFINT_EN; - } - if (GetMSG()) { - data |= SPC::PCTL_MSG; - } - if (GetCD()) { - data |= SPC::PCTL_CD; +// DREG レジスタの読み込み +// (副作用があるので Peek 系からは呼ばないこと) +busdata +SPCDevice::ReadDREG() +{ + busdata data; + + // X68k は SPC の DREQ 信号から DTACK 信号を作っているため、 + // DREQ 信号を出さないプログラム転送モードでは DREG へのアクセスが + // すべてバスエラーになってしまう。 + if (vmtype == VMType::X68030) { + if ((spc.scmd & SPC::SCMD_PROGRAM)) { + data.SetBusErr(); + return data; + } else { + // DMA 転送で DREQ が出てないとき DTACK を出さないのを + // Retry を返すことで表現している。 + if (spc.dreg.Length() == 0) { + putlog(3, "DREG empty during DMA transferring"); + data.SetRetry(); + return data; + } + } } - if (GetIO()) { - data |= SPC::PCTL_IO; + + // XFER_OUT は DREG アクセスでリセットされる + spc.ints &= ~SPC::INTS_XFER_OUT; + // TransferComplete() が割り込み状態を変化させる可能性があるので + // ChangeInterrupt() を最後に呼び出す。 + + // 受信データがあれば取り出す + if (__predict_true(spc.dreg.Length() > 0)) { + bool q_was_full = spc.dreg.IsFull(); + data = spc.dreg.Dequeue(); + if (loglevel >= 4) { + putlogn("DREG -> $%02x (q=%u TC=0x%x)", + data.Data(), spc.dreg.Length(), spc.tc); + } else { + putlog(3, "DREG -> $%02x", data.Data()); + } + // この読み出しが最後だったらこれで転送完了。 + if (spc.tc == 0 && spc.dreg.Length() == 0) { + TransferComplete(); + } + // この読み出しによってバッファに空きが出来たら転送再開。 + if (transfer_command_running && scsi->GetREQ() && q_was_full) { + // XXX 時間は適当 + CallAfter(HardwareTransfer, "SPC ReadDREG to Transfer", t_CLF); + } + } else { + // 受信データがない場合 + if ((spc.scmd & SPC::SCMD_PROGRAM)) { + // XXX プログラム転送中ならどうなる? + data = 0xff; + putlog(3, "DREG -> $%02x (empty)", data.Data()); + } else { + // DMA 転送 + // DMA 側でリトライするのでここには来ないはず + VMPANIC("DMA転送中の DREG empty"); + } } + // 割り込み状態を確定 + ChangeInterrupt(); return data; } +// DREG レジスタへの書き込み +busdata +SPCDevice::WriteDREG(uint32 data) +{ + busdata r; + + // X68k は SPC の DREQ 信号から DTACK 信号を作っているため、 + // DREQ 信号を出さないプログラム転送モードでは DREG へのアクセスが + // すべてバスエラーになってしまう。 + if (vmtype == VMType::X68030) { + if ((spc.scmd & SPC::SCMD_PROGRAM)) { + r.SetBusErr(); + return data; + } else { + // DMA 転送で DREQ が出てないとき DTACK を出さないのを + // Retry を返すことで表現している。 + if (spc.dreg.IsFull()) { + r.SetRetry(); + return r; + } + } + } + + // XFER_OUT は DREG アクセスでリセットされる + spc.ints &= ~SPC::INTS_XFER_OUT; + ChangeInterrupt(); + + // DREG キューに入れて.. + if (spc.dreg.Enqueue(data) == false) { + // XXX キューが一杯だったらどうなるのかは書いてないので分からない。 + // a) キューを押し出して先頭を破棄する + // b) キューの末尾を破棄して新着データで上書きする + // c) キューの内容は変わらず新着データを破棄する + // どれで実装しても「この時何かしら失われるデータがある」こと自体は + // 再現出来ているはずなので、ここではひとまず a) の動作にしておく。 + putlog(0, "DREG Queue overflow"); + spc.dreg.EnqueueForce(data); + } + // ハードウェア転送中で REQ が来てれば送信。 + if (transfer_command_running && scsi->GetREQ()) { + // XXX 時間は適当 + CallAfter(HardwareTransfer, "SPC WriteDREG to Transfer", t_CLF); + } + return r; +} + // バスフリーフェーズに移行する void SPCDevice::BusFree() { - // バスの状態を変える - B_BusFree(myid); + putlog(3, "BusFree"); + + // ここは BDID 書き込み前 (まだ scsibus に自身をつなげる前) にも呼ばれる。 + // その場合は何もしない。バスフリーのはずでもあるし。 + if (scsi->GetBus() == NULL) { + return; + } - // SCSI 仕様は BSY 下ろしてから最大 1200ns 以内に他の信号も下ろす - // SPC 仕様は BSY 下ろしてから最大 (6*t_CLF + 75) ns 以内に - // INTR が上がる。 - // ここでは適当に 600ns とする。 - event.time = 600; - event.name = "SPC BusFree"; - event.func = (DeviceCallback_t)&SPCDevice::BusFreeCallback; - event.code = 0; // SPC からのバスフリー - gScheduler->AddEvent(&event); + BusRelease(); } -// ターゲットがバスフリーフェーズに移行した +// バスをリリースする。バスフリーとバスリセットから呼ばれる void SPCDevice::BusRelease() { - putlog(3, "Target BusRelease"); - - // XXX SPC が SCSI バスからのバスフリーをどのぐらいの時間で - // 検出するのか調べていない。後で調べる - // ここでは適当に 600ns とする。 - event.time = 600; - event.name = "SPC BusFree"; - event.func = (DeviceCallback_t)&SPCDevice::BusFreeCallback; - event.code = 1; // SCSI バスからのバスフリー - gScheduler->AddEvent(&event); + // 自発的にバスをリリースする場合、今のフェーズから自分が変えていい + // BSY、SEL だけを落とす。それ以外の信号線は正しくはないけどとりあえず + // 一括して SCSIHostDevice::BusFreeCB() が落とすことにしてある。 + + switch (scsi->GetPhase()) { + case SCSI::Phase::BusFree: + case SCSI::Phase::Transfer: + break; + case SCSI::Phase::Arbitration: + case SCSI::Phase::Selection: + case SCSI::Phase::Reselection: + scsi->NegateSEL(); + scsi->NegateBSY(); + break; + } } // Select コマンド実行 @@ -976,11 +1167,10 @@ SPCDevice::SelectCommand() { std::string idstr; uint32 data; + uint32 tgtid; uint64 wait; - uint64 now; - // ターゲット ID を求めつつ、表示用の文字列を作成 - tgtid = -1; + // ターゲット ID を表示用に解析 data = spc.temp_out; if (data == 0) { // ID が一切立ってない? @@ -994,30 +1184,14 @@ SPCDevice::SelectCommand() if (data == 0) { // ターゲット ID がない - idstr = string_format("#%d", myid); + idstr = string_format("#%u", myid); } else { - tgtid = 31 - __builtin_clz(data); - idstr = string_format("#%d -> #%d", myid, tgtid); + tgtid = SCSIBus::DecodeID(data); + idstr = string_format("#%u -> #%u", myid, tgtid); } } - putlog(2, "Select コマンド (%s)", idstr.c_str()); - phase = SCSI::Phase::Selection; + putlog(2, "Select command (%s)", idstr.c_str()); - // アービトレーションなしの場合 - // - // BSY X\_______________ .. _/~~~~~~~~~~~~~~~ - // |t_FRID| |t_BIDH| - // DB* ________ (6 + TCL) * t_CLF, (7 + TCL) * t_CLF + 140 - // t_IDSL = 11 * t_CLF - 80 - // t_BIDH = t_BLSH = 2 * t_CLF - // t_SHIR = 60 - // // アービトレーションありの場合 // // BSY X\______/~~~~~~~~~~~~~~~~~~~~~~~~~~\_ .. _/^^^^^^^^^^^^^^^ @@ -1035,13 +1209,32 @@ SPCDevice::SelectCommand() // t_AWSL = 0, 80 // t_SIDA = 11 * t_CLF - 30 // t_IDBH = 2 * t_CLF - 80 + // + // ここではバスフリー検出から少なくとも t_BFBL 以上 + t_ARB + t_AWSL 後に + // SEL と DB* と ATN を立てて、そこから t_SIDA + t_IDBH 後に BSY を落とす。 + // + // アービトレーションなしの場合 + // + // BSY X\_______________ .. _/~~~~~~~~~~~~~~~ + // |t_FRID| |t_BIDH| + // DB* ________ (6 + TCL) * t_CLF, (7 + TCL) * t_CLF + 140 + // t_IDSL = 11 * t_CLF - 80 + // t_BIDH = t_BLSH = 2 * t_CLF + // t_SHIR = 60 + // // t_FRID/t_BFBL はたぶん今からではなくバスフリーになった時点から起算。 // t_FRID と t_BFBL は同じ式なのでここでは t_BFBL で統一。 uint64 t_BFBL = (6 + spc.GetTCL()) * t_CLF; // Min - now = gScheduler->GetVirtTime(); - if ((BusPhaseTime + t_BFBL) > now) { - wait = (BusPhaseTime + t_BFBL) - now; + uint64 now = scheduler->GetVirtTime(); + if ((last_busfree_time + t_BFBL) > now) { + wait = (last_busfree_time + t_BFBL) - now; } else { wait = 0; } @@ -1049,196 +1242,321 @@ SPCDevice::SelectCommand() spc.ssts &= ~(SPC::SSTS_CONN_INIT | SPC::SSTS_CONN_TARG); spc.ssts |= SPC::SSTS_SPC_BUSY; - // アービトレーションフェーズ + // TCH,TCM によるタイマーを始動。 + // N が 0 なら無限大なのでタイマーは動かさない。 + uint32 N = (spc.tc >> 8) << 8; + if (N != 0) { + timer_event.func = ToEventCallback(&SPCDevice::SelectionTimeout); + timer_event.time = (N + 15) * t_CLF * 2; + scheduler->RestartEvent(timer_event); + } + if ((spc.sctl & SPC::SCTL_ARBIT_EN)) { // アービトレーションありの場合 - Arbitration(myid); - // 実際にはここで所定の時間経過後に優先度のチェックを行って - // アービトレーションの勝者を決めるのだが、ここでは複数のデバイスが - // 同時にアービトレーションフェーズに入ることはないため、 - // アービトレーションを開始したデバイスが必ずバスの使用権を得る。 - - ArbitrationAck(myid); + // ここでは複数のデバイスが同時にアービトレーションフェーズに + // 入ることはないため、アービトレーションを開始したデバイスが + // 必ずバスの使用権を得る。 + // バスフリーから規定時間後に SEL (と DB* と ATN) を上げる。 uint64 t_ARB = 32 * t_CLF - 60; uint64 t_AWSL = 40; // 適当に中間 - uint64 t_SIDA = 11 * t_CLF - 30; - uint64 t_IDBH = 2 * t_CLF - 80; - wait += t_ARB + t_AWSL + t_SIDA + t_IDBH; + CallAfter(Arbitration1, "SPC Arbitration", wait + t_ARB + t_AWSL); } else { - // アービトレーションなしの場合の相当箇所までの時間 + // アービトレーションなしの場合 + + // バスフリーから規定時間後に SEL (と DB*) を上げる。 uint64 t_IDSL = 11 * t_CLF - 80; - wait += t_IDSL; + CallAfter(Selection1, "SPC Selection", wait + t_IDSL); } +} - // 後半の時間もここで足しておく - uint64 t_BLSH = 2 * t_CLF; - uint64 t_SHIR = 60; - wait += t_BLSH + t_SHIR; +// アービトレーションフェーズ開始後、規定時間経過したので SEL を立てるところ。 +void +SPCDevice::Arbitration1(Event& ev) +{ + putlog(3, "Arbitration1"); - // セレクションフェーズ - spc.ssts |= SPC::SSTS_CONN_INIT; - InitiatorSelection(tgtid); + spc.ssts |= SPC::SSTS_SPC_BUSY; - if (tgtid >= 0 && target[tgtid] != NULL) { - // セレクション成功 + // 本当は DB* -> SEL -> ATN の順でタイミングも規定されてるけど省略。 + // ここでは SEL がトリガーになるので SEL のアサートは最後に行う。 + // アービトレーションありなら、セレクション/リセレクションフェーズを + // 区別するため PCTL に設定された IO をバスに出す(p.27)。 + // MSG と CD は記述がないけどたぶん一緒に出すだろう。 + // タイミングもどこでか分からんのでとりあえずここで。 + scsi->SetData(spc.temp_out); + scsi->SetXfer(spc.pctl_out); + if (set_atn_in_selection) { + scsi->AssertATN(); + set_atn_in_selection = false; + } + scsi->AssertSEL(); + + uint64 t_SIDA = 11 * t_CLF - 30; + uint64 t_IDBH = 2 * t_CLF - 80; + CallAfter(Arbitration2, "SPC Arbitration2", t_SIDA + t_IDBH); +} - // XXX 途中のターゲットが応答するための時間が抜けているけど - event.time = wait; - event.name = "SPC SelComplete"; - event.func = (DeviceCallback_t)&SPCDevice::SelectionCompleteCallback; - } else { - // セレクション失敗 +// アービトレーションフェーズ中、SEL を立ててから規定時間経過したので BSY を +// 下げるところ。 +void +SPCDevice::Arbitration2(Event& ev) +{ + putlog(3, "Arbitration2"); - uint64 timeout = ((spc.tc & 0xffff00) + 15) * t_CLF * 2; - event.time = wait + timeout; - event.name = "SPC SelFailed"; - event.func = (DeviceCallback_t)&SPCDevice::SelectionFailedCallback; - } - gScheduler->AddEvent(&event); + scsi->NegateBSY(); } -// Transfer コマンド実行 +// アービトレーションフェーズを使わない場合のセレクションフェーズで、 +// 規定時間経過したので SEL を立てるところ。 void -SPCDevice::TransferCommand() +SPCDevice::Selection1(Event& ev) { - uint32 xfer_phase = GetPCTL() & 7; - putlog(2, "SCMD Transfer Command (xfer=%s len=%d)", - SCSI::GetPhaseName(xfer_phase), spc.tc); - - // Transfer コマンド発行時に REQ がアサートされていれば、SCSI バスの - // 状態と PCTL レジスタの下位3ビットの状態を比較する。一致していれば - // 転送、そうでなければ Service Required 割り込み。 - // XXX Transfer コマンド発行時に REQ がアサートされていなければ、 - // アサートされるまで待つ。 - uint32 stat = 0; - if (GetMSG()) { - stat |= SPC::PCTL_MSG; - } - if (GetCD()) { - stat |= SPC::PCTL_CD; - } - if (GetIO()) { - stat |= SPC::PCTL_IO; - } - if (xfer_phase != stat) { - SetINTS(SPC::INTS_SERV_REQ); - return; - } - - // 転送開始前に DREG キューを初期化する。 - // MPU 側が DREG を全部読まずにフェーズを中断とかは別に出来るので。 - spc.dreg.Clear(); + putlog(3, "Selection1"); - // 転送開始 - phase = xfer_phase; spc.ssts |= SPC::SSTS_SPC_BUSY; - switch (xfer_phase) { - case SCSI::Phase::Command: - // この後 DREG にコマンドが書き込まれる - cmds.clear(); - // XXX どこで初期化する? - datalen = 0; - break; - case SCSI::Phase::DataIn: - datalen = target[tgtid]->ExecDataIn((const uint8 **)&dataptr); - // 最初の1バイトはここで DREG に入れておかないと SSTS が変化しない。 - EnqueueToDREG(); - break; + // 本当は DB* -> SEL の順でタイミングも規定されてるけど省略。 + // ここでは SEL がトリガーになるので SEL のアサートは最後に行う。 + // アービトレーションなしなら PCTL に関係なくセレクションフェーズ (IO=0) + // を開始する(p.27)。MSG と CD は記述がないけどたぶん一緒に出すだろう。 + scsi->SetData(spc.temp_out); + scsi->SetXfer(0); + scsi->AssertSEL(); +} - case SCSI::Phase::DataOut: - datalen = target[tgtid]->ExecDataOut(&dataptr); - // データアウトの時は DREG へ書き込まれるのを待つだけなので - // ここでは何もしなくていい。 - break; +// セレクションコマンドがタイムアウトした時に呼ばれるコールバック。 +void +SPCDevice::SelectionTimeout(Event& ev) +{ + if (loglevel >= 3) { + int tgtid; + uint8 data = scsi->GetData(); + data &= ~(1 << myid); + tgtid = SCSIBus::DecodeID(data); + if (tgtid == -1) { + putlogn("Selection timeout"); + } else { + putlogn("Selection timeout (#%u)", tgtid); + } + } - case SCSI::Phase::Status: - // ステータスバイトを取り込む。 - // 1バイトなので必ず COMPLETE になる。 - spc.dreg.Enqueue(target[tgtid]->GetStatusByte()); - break; + spc.ssts &= ~(SPC::SSTS_CONN_INIT | SPC::SSTS_SPC_BUSY); - case SCSI::Phase::MsgIn: - // メッセージバイトを取り込む。 - // 1バイトなので必ず COMPLETE になる。 - spc.dreg.Enqueue(target[tgtid]->GetMessageByte()); + // TC は実際には減算カウンタのようだが、とりあえず辻褄合わせ + spc.tc = 0; - break; - } + SetINTS(SPC::INTS_TIMEOUT); +} + +// SetATN コマンド実行 +void +SPCDevice::SetATNCommand() +{ + putlog(2, "SCMD Set ATN Command"); - // XXX どこでやる? - // SDGC_XFER_ENABLE なら転送要求ごとに割り込みを上げる。 - // この場合の割り込み要因は SERR レジスタに出るようだ。 - if ((spc.sdgc & SPC::SDGC_XFER_ENABLE)) { - spc.serr |= SPC::SERR_XFER_OUT; - putlog(3, "SDGC XFER_OUT Interrupt"); - SetINTS(0); + // Select コマンド前なら、セレクションフェーズで ATN を立てる指示。 + // 接続中(転送フェーズ中?) なら即座に ATN を立てる。 + switch (scsi->GetPhase()) { + case SCSI::Phase::BusFree: + set_atn_in_selection = true; + break; + case SCSI::Phase::Arbitration: + case SCSI::Phase::Selection: + case SCSI::Phase::Reselection: + // Select Command 実行中なので実質ここで呼ばれることはないはずだが + case SCSI::Phase::Transfer: + scsi->AssertATN(); + break; } } -// ターゲットから受信したデータ (dataptr, datalen) を DREG キューに入れる。 -// 空いてる限り常に埋まるまで入れる。 -// データが最初から空なら何もしない。 +// Transfer コマンド実行 void -SPCDevice::EnqueueToDREG() +SPCDevice::TransferCommand() { - while (datalen > 0 && spc.dreg.Enqueue(*dataptr)) { - dataptr++; - datalen--; + putlog(2, "SCMD Transfer Command (xfer=%s len=0x%x)", + SCSI::GetXferPhaseName(spc.pctl_out), spc.tc); + + // SCMD レジスタ書き込みのうち Transfer コマンドに関わる未実装機能の + // ログ表示は SCMD レジスタ書き込み時ではなくここで表示。 + if ((spc.scmd & SPC::SCMD_TERM_MODE)) { + putlog(0, "SCMD Padding mode (NOT IMPLEMENTED)"); + } + + transfer_command_running = true; + spc.ssts |= SPC::SSTS_SPC_BUSY | SPC::SSTS_IN_PROGRESS; + + // PCTL レジスタに設定してあるフェーズをバスに出す。 + // XXX どこでかよく分からんけど、とりあえずこの辺で。 + scsi->SetXfer(spc.pctl_out); + + // 転送開始前に DREG キューを初期化する。 + // MPU 側が DREG を全部読まずにフェーズを中断とかはたぶん出来るので。 + // XXX 転送開始前なのかリセットなのか… + spc.dreg.Clear(); + + // Transfer コマンド発行時に REQ がアサートされていなければ + // アサートされるまで待つ。この場合、ここでは何もすることはなく、 + // OnTransferReq() が呼ばれてそこから転送フェーズが動き始める。 + if (scsi->GetREQ()) { + // コマンドから転送開始までちょっとだけかかる + CallAfter(HardwareTransfer, "SPC SCMD to Start", 4 * t_CLF); } } -// バスフリーフェーズに移行完了のイベントコールバック -// arg: 要因 0=SPC 1=SCSI +// バスフリーコールバック (SCSIHostDevice から呼ばれる)。 +// id はこのバスフリーを行ったデバイス (0..7) void -SPCDevice::BusFreeCallback(int arg) +SPCDevice::BusFreeCallback(uint id) { - putlog(2, "SCSIBus バスフリー"); - phase = SCSI::Phase::BusFree; + putlog(4, "BusFree Callback"); + + last_busfree_time = scheduler->GetVirtTime(); - // SPC ビジーとかを下げる - spc.ssts &= ~(SPC::SSTS_CONN_INIT | SPC::SSTS_CONN_TARG | - SPC::SSTS_SPC_BUSY); - spc.ssts &= ~SPC::SSTS_IN_PROGRESS; + transfer_command_running = false; + spc.ssts &= ~(SPC::SSTS_CONN_INIT | SPC::SSTS_CONN_TARG); + spc.ssts &= ~(SPC::SSTS_SPC_BUSY | SPC::SSTS_IN_PROGRESS); // マニュアルが分かりにくいんだけど、 - // o. イニシエータで SPC がバスフリーにしたとき - // o. BusFree INT enable ビットが立っていてターゲットからのバスフリー + // o イニシエータとして動作時に、SPC が自らバスフリーにした時 + // o BusFree INT enable ビットが立っていて、ターゲットがバスフリーに + // した時 // のいずれかで INTS のバスフリー検出ビットを立てる - if (arg || spc.busfree_intr_enable) { + if (id != myid || spc.busfree_intr_enable) { SetINTS(SPC::INTS_DISCONNECTED); } } -// 所定時間経過後セレクションに成功のイベントコールバック +// ターゲットがセレクションに応答したコールバック +// (SCSIHostDevice から呼ばれる) void -SPCDevice::SelectionCompleteCallback(int arg) +SPCDevice::SelectionAckCallback() { - // ほんとは信号線ごとに時間が進むのだが、全部一度に変化する :) - TargetSelectionAck(tgtid); - InitiatorSelectionAck(); - target[tgtid]->Selected(); + putlog(4, "SelectionAck Callback"); + + // タイムアウトイベントを停止 + scheduler->StopEvent(timer_event); spc.ssts &= ~SPC::SSTS_SPC_BUSY; - spc.ssts |= SPC::SSTS_IN_PROGRESS; + spc.ssts |= SPC::SSTS_CONN_INIT; + + // Command Complete 割り込みを起こす SetINTS(SPC::INTS_COMPLETE); } -// 所定時間経過後セレクションに失敗のイベントコールバック +// 転送フェーズでターゲットが REQ を立てたコールバック +// (SCSIHostDevice から呼ばれる) void -SPCDevice::SelectionFailedCallback(int arg) +SPCDevice::TransferReqCallback() { - if (loglevel >= 1) { - if (tgtid == -1) { - putlog(2, "セレクションタイムアウト"); + // REQ が立てば Transfer Command 発行前でも Xfer In Progress は立てる + spc.ssts |= SPC::SSTS_IN_PROGRESS; + + if (transfer_command_running) { + // Transfer Command 実行中なら REQ 待ちだったので + // ここでハードウェア転送を開始。 + HardwareTransfer(phase_event); + } + + // データシートでは、マニュアル転送で IN 方向の時データバスの内容を + // TEMP に取り込むと書いてある(p.78)。 + // HardwareTransfer によって状態が変更される可能性があるため + // もう一回チェック。 + if (transfer_command_running == false) { + if (scsi->GetIO()) { + spc.temp_in = scsi->GetData(); + } + } +} + +// ハードウェア転送の1バイト分。 +// イベントコールバックとして呼び出すこと。 +// REQ がアサートされているのを確認してから呼び出すこと。 +// (1バイト送信するとターゲットが再び REQ を上げるなどしてくるので、 +// OnTransferReq() か DREG 書き込みから再びここが呼ばれる) +void +SPCDevice::HardwareTransfer(Event& ev) +{ + // SDGC_XFER_ENABLE は転送要求ごとに割り込みを上げる。 + // (SPC から見て) 入力操作時は読み込めるデータがある場合、出力操作時は + // キューにバイトを書き込む必要がある場合に、割り込みを上げる。 + + // PCTL のフェーズと SCSI バスのフェーズが一致していれば転送(開始|継続)。 + // そうでなければ Service Required 割り込み。 + if (spc.pctl_out != scsi->GetXfer()) { + transfer_command_running = false; + putlog(3, "Hardware Transfer terminated (phase mismatch)"); + SetINTS(SPC::INTS_SERV_REQ); + return; + } + + // DREG が送受信不可能ならここでは何もせずに帰るだけでよい。 + // その後 DREG アクセスによって送受信可能になればまたこれが呼ばれる。 + if (scsi->GetIO()) { + // Input (ターゲット → イニシエータ) + bool queued = spc.dreg.Enqueue(scsi->GetData()); + // SDGC_XFER_ENABLE なら読み込めるデータがある場合に割り込み。 + if ((spc.sdgc & SPC::SDGC_XFER_ENABLE)) { + putlog(3, "SDGC XFER_OUT Interrupt"); + SetINTS(SPC::INTS_XFER_OUT); + } + if (!queued) { + putlog(4, "Hardware Transfer (DREG full)"); + return; + } + } else { + // Output (イニシエータ → ターゲット) + if (spc.dreg.Length() != 0) { + scsi->SetData(spc.dreg.Dequeue()); } else { - putlog(2, "セレクションタイムアウト (#%d)", tgtid); + putlog(4, "Hardware Transfer (DREG empty)"); + // SDGC_XFER_ENABLE ならキューに書き込む必要がある場合に割り込み。 + if ((spc.sdgc & SPC::SDGC_XFER_ENABLE)) { + putlog(3, "SDGC XFER_OUT Interrupt"); + SetINTS(SPC::INTS_XFER_OUT); + } + return; } } - spc.ssts &= ~SPC::SSTS_SPC_BUSY; - // ほんとは TC は減算カウンタになってるんだが - spc.tc = 0; - SetINTS(SPC::INTS_TIMEOUT); + + scsi->AssertACK(); + + // 転送完了 + spc.tc--; + putlog(4, "Hardware Transfer: %s q=%u tc=%u", + (scsi->GetIO() ? "IN" : "OUT"), spc.dreg.Length(), spc.tc); + if (spc.tc == 0) { + // どちらでも転送自体はここで完了(?) + transfer_command_running = false; + + if (scsi->GetIO()) { + // Input 時は TC がゼロになって DREG キューを全部読み出した + // ところで完了なので、ここでは何もしない。 + } else { + // Output ならここで転送完了。 + TransferComplete(); + } + } + + // メッセージアウトフェーズの最終バイト送信中(?)に ATN を下げる + if (scsi->GetXfer() == SCSI::XferPhase::MsgOut && spc.tc == 0) { + scsi->NegateATN(); + } + + if (scsi->GetXfer() == SCSI::XferPhase::MsgIn && spc.tc == 0) { + // ただしメッセージインフェーズの最終バイトなら ACK を下げない。 + } else { + scsi->NegateACK(); + } +} + +// 転送完了。 +// HardwareTransfer() と Read() から呼ばれる。 +void +SPCDevice::TransferComplete() +{ + spc.ssts &= ~(SPC::SSTS_SPC_BUSY | SPC::SSTS_IN_PROGRESS); + SetINTS(SPC::INTS_COMPLETE); }