--- nono/vm/mfp.cpp 2026/04/29 17:04:32 1.1.1.3 +++ nono/vm/mfp.cpp 2026/04/29 17:05:50 1.1.1.20 @@ -1,30 +1,126 @@ // // nono -// Copyright (C) 2017 isaki@NetBSD.org +// Copyright (C) 2020 nono project +// Licensed under nono-license.txt // +// +// MFP (MC68901) +// + +// タイマーについて。 +// +// ディレイモードでは、プリスケーラで設定した回数だけ入力をカウントすると +// メインカウンタにパルスが入る。このパルスはメインカウンタを 1 減らす。 +// メインカウンタが $01 になった次のパルスで (おそらく外から見ると $00 に +// ならないからこういう言い回しなのだが要するに 1 減らして内部的に $00 に +// なったらでいいと思う)、TxDR をリロードして「タイムアウトパルス」を起こす。 +// タイムアウトパルスが起きると、割り込みを(許可されていれば)上げると共に、 +// カウンタをリロードして、アウトプットピンの状態をトグルする。(MC68901.pdf) +// +// 1つのタイマーあたり +// o タイマー開始時のカウンタ初期値 +// o タイマー開始時刻 +// o 次回のリロード値 +// を保持する。 +// +// タイムアウトパルスとタイムアウトパルスの間は一定時間ごとにメインカウンタの +// 値が減算されていってるだけだし、割り込みを有効にしていなければメインカウンタ +// はタイムアウト周期でこの減算を繰り返しているだけなので、TxDR の読み出し値は +// 読み出し時刻から計算で求めることができる。 +// +// 現在値 = 初期値 - int((現在時刻 - 開始時刻) / メインカウンタ周期) % 初期値 +// +// 例えば、プリスケーラ 16分周(4usec)、カウント 5 の場合 +// +// (1)(2) (3) +// v v t+26 +// t+0 +4 +8 +12 +16 +20 +24 v +28 +32 +36 +40 [usec] +// ---+----+----+----+----+----+----+-----+----+----+----+--> time +// TxDR 5 5 4 3 2 1 5 4 3 2 1 5 +// +// (1) で TxDR に 5 をセット、(2) t+0 でタイマースタートして、(3) t+26 で +// TxDR を読み出された場合、値は +// 5 - int(((t+26 - t+0) % 20_usec) / 4_usec) +// = 5 - int(6_usec / 4_usec) +// = 5 - 1 +// = 4 +// +// 一方割り込みが必要な場合は、(2) でタイマースタートして、(4)、(4')… の +// タイミングでタイマーイベントにより割り込みを起こす。 +// +// (2) (4) (4') +// v v v +// ---+----+----+----+----+----+----+----+----+----+----+--> time +// TxDR 5 5 4 3 2 1 5 4 3 2 1 5 + +// 実時間追従について。 +// +// MFP の実時間追従はタイマーの「割り込みに対してのみ」作用する。 +// そもそも実時間追従とは、LUNA ではこの手のタイマーが固定のシステムクロック +// しかなくこれによるタイマー割り込み HZ 回でゲスト OS の時刻が1秒進むという +// ゲスト実装の特性と、割り込みの頻度は比較的低いことを利用した技法。 +// 従って MFP でも 10msec 程度のタイマー割り込みであれば動作する。 +// +// 一方 TxDR の減算 (プリスケーラ 200 分周でも 1 カウンタ減算が 50usec) は、 +// 実時間軸を作っている syncer イベントの分解能 (仮想時間で 50msec) より +// 遥かに高いため、これを (syncer の) 実時間軸に追従させることは現状不可能。 +// 例えば、より上位の割り込みが起きない Timer-C 割り込みのハンドラ冒頭で +// TCDR を読み出すと、通常はリロードされた初期値が読めるはずだが、これが +// 何が読めるか分からないという状態になる。 +// NetBSD/x68k の timecounter=mfp はまさにこの TxDR の読み出しで時間差を計測 +// して (10msec よりも高精度な 50usec 粒度で) 時間を進めようとするものなので +// (のはず)、TxDR の読み出しが信頼できなくなるこのモードでは使用できない。 +// 一方 timecounter=clockinterrupt はタイマー割り込み1回で 1/HZ 秒ずつ進める +// (そのため時間の分解能は 1/HZ 秒 = 10msec になる) モード(のはず…)のため、 +// 実時間追従でも動作する。 +// +// nono のオプション clock-sync の値、動作モード(等速 or フルスピード) と +// ゲスト OS NetBSD の sysctl kern.timecounter.hardware の値の組み合わせは +// 以下のようになる。 +// +// clock-sync mode sysctl timecounter +// ---------- ---- --------------- +// virtual 等速 mfp : ◯ +// virtual 等速 clockinterrupt : ◯ +// virtual フル mfp : △ +// virtual フル clockinterrupt : △ +// real 等速 mfp : × +// real 等速 clockinterrupt : ◯ +// real フル mfp : × +// real フル clockinterrupt : ◯ +// +// ◯: ゲスト OS の時間は実時間通りに進む。 +// △: VM 内の時間は実時間から切り離されてフルスピード時間軸で進む。 +// ×: ゲスト OS の時間が正しく進まない。 + +// Timer-B は 13 usec のタイムアウトごとに反転する出力ピン TOB を自身の +// TC, RC (UART の送受信クロック入力) に入れているだけで、割り込みは +// 使っていない。実機ではこれを変更するとキーボードと正しく通信できない +// ことになると思う。nono ではキーボードとの通信にこのクロック入力は +// 使っていないので、Timer-B は実質何もしていない。 + #include "mfp.h" +#include "event.h" +#include "interrupt.h" +#include "keyboard.h" +#include "monitor.h" #include "mpu.h" -#include "mystring.h" #include "scheduler.h" +#include "syncer.h" +#include "x68kkbd.h" -std::unique_ptr gMFP; +// InsideOut p.135 +/*static*/ const busdata +MFPDevice::wait = busdata::Wait(24 * 40_nsec); // コンストラクタ MFPDevice::MFPDevice() + : inherited(OBJ_MFP) { - logname = "mfp"; - devname = "MFP"; - devaddr = baseaddr; - devlen = 0x2000; - monitor.Init(65, 17); - - for (int ch = 0; ch < countof(event); ch++) { - event[ch].dev = this; - event[ch].func = (DeviceCallback_t)&MFPDevice::TimerCallback; - event[ch].code = ch; - event[ch].SetName(string_format("MFP Timer-%c", ch + 'A')); - } + monitor = gMonitorManager->Regist(ID_MONITOR_MFP, this); + monitor->func = ToMonitorCallback(&MFPDevice::MonitorUpdate); + monitor->SetSize(77, 26); } // デストラクタ @@ -32,50 +128,103 @@ MFPDevice::~MFPDevice() { } -// MPU の RESET 命令によるリセット -void -MFPDevice::ResetSoft() +// 初期化 +bool +MFPDevice::Init() { - // TxDR, UDR, TSR はクリアされない + interrupt = GetInterruptDevice(); + keyboard = dynamic_cast(GetKeyboard()); - // ? - mfp.tsr = 0x80; - - // それ以外のすべてのレジスタはクリア? - mfp.vr = 0; - - // タイマーはすべてストップなのでイベントを削除 - for (int ch = 0; ch < countof(event); ch++) { - event[ch].Stop(); - } - - // ペンディング割り込みクリア + // クロックの同期方法。 + syncer = GetSyncer(); + sync_rt = syncer->GetSyncRealtime(); + + // TxDR は 1-256 の範囲でなければいけないので。 + for (int ch = 0; ch < countof(mfp.timer); ch++) { + mfp.timer[ch].tdr = 256; + } + + auto evman = GetEventManager(); + for (int ch = 0; ch < event.size(); ch++) { + event[ch] = evman->Regist(this, ch, + ToEventCallback(&MFPDevice::TimerCallback), + string_format("MFP Timer-%c", ch + 'A')); + } + + // キーボード周りは x68kkbd.cpp のコメント参照 + key_event = evman->Regist(this, + ToEventCallback(&MFPDevice::KeyCallback), + "MFP USART"); + key_event->time = 3750_usec; + return true; } -// アドレスデコーダ -inline uint64 -MFPDevice::Decoder(uint32 addr) const +// リセット +void +MFPDevice::ResetHard(bool poweron) { - // XXX 折り返しは? - addr -= baseaddr; - - // 偶数アドレスはバスエラー - if ((addr & 1) == 0) - return (uint64)-1; + // 3.3 RESET OPERATION には + // All internal registers are cleared expect the TxDR, UDR, and TSR. + // と書いてあるが、6.2.1 には TxDR はリセット時に $00 と書いてある。 + // 内部カウンタは影響を受けずに next_tdr だけリセットされるという + // 意味だろうか。 + mfp.aer = 0; + mfp.ddr = 0; + mfp.ier.w = 0; + mfp.ipr.w = 0; + mfp.isr.w = 0; + mfp.imr.w = 0; + for (int i = 0; i < countof(mfp.timer); i++) { + mfp.timer[i].tcr = 0; + mfp.timer[i].next_tdr = 256; + } + mfp.scr = 0; + mfp.ucr = 0; + mfp.rsr = 0; + // XXX TSR は変化しないがたぶんバッファはクリアされるので BE は立つ + // ということかな。 + mfp.tsr = MFP::TSR_BE; + + // All timers are stopped. + for (int ch = 0; ch < event.size(); ch++) { + StopTimerEvent(ch); + } + scheduler->StopEvent(key_event); + + // USART RX and TX are disabled. + // SO line is placed in HighZ. + // The interrupt channels are disabled, and + // any pending interrupts are cleared. + // GPIO lines are placed in HighZ. + // timer outputs are driven low. + // External signals are nagated. + // VR is initialized to $00 (not $0f). + mfp.vr_vec = 0; + mfp.vr_s = 0; + + // CIRQ | FMIRQ | EXPON | ALARM にしておく + mfp.gpip = 0x6b; + + // Human68k モードなら、10msec ごとに Timer-C 割り込みを上げる。 + // 時間計測用。 + if (gMainApp.human_mode) { + WritePort(MFP::VR, 0x40); + WritePort(MFP::IERB, 0x20); + WritePort(MFP::TCDR, 200); + WritePort(MFP::TCDCR, 0x70); + WritePort(MFP::IMRB, 0x20); + } - return addr >> 1; + ChangeInterrupt(); } -uint64 -MFPDevice::Read8(uint32 addr) +busdata +MFPDevice::ReadPort(uint32 offset) { - uint8 data; - - switch (Decoder(addr)) { - default: - return (uint64)-1; + busdata data; + switch (offset) { case MFP::GPIP: data = mfp.gpip; break; @@ -110,16 +259,16 @@ MFPDevice::Read8(uint32 addr) data = mfp.imr.b; break; case MFP::VR: - data = mfp.vr; + data = mfp.GetVR(); break; case MFP::TACR: - data = mfp.tcr[0]; + data = mfp.timer[0].tcr; break; case MFP::TBCR: - data = mfp.tcr[1]; + data = mfp.timer[1].tcr; break; case MFP::TCDCR: - data = (mfp.tcr[2] << 4) | mfp.tcr[3]; + data = mfp.GetTCDCR(); break; case MFP::TADR: data = GetTDR(0); @@ -146,143 +295,162 @@ MFPDevice::Read8(uint32 addr) data = mfp.tsr; break; case MFP::UDR: + // UDR からデータを読み出したら Buffer Full をクリア data = mfp.udr; + ClearBF(); + break; + default: + data = 0xff; break; } - return data; -} -uint64 -MFPDevice::Read16(uint32 addr) -{ - return (0xff00 | Read8(addr + 1)); + if (__predict_false(loglevel >= 3)) { + if (offset == MFP::TCDR) { + putlog(4, "%s -> $%02x", regname[offset], data.Data()); + } else if (offset < MFP::RegMax) { + putlogn("%s -> $%02x", regname[offset], data.Data()); + } else { + putlogn("$%06x -> $%02x", mpu->GetPaddr(), data.Data()); + } + } + data |= wait; + data |= BusData::Size1; + return data; } -uint64 -MFPDevice::Write8(uint32 addr, uint32 data) +busdata +MFPDevice::WritePort(uint32 offset, uint32 data) { - switch (Decoder(addr)) { - default: - return (uint64)-1; - + switch (offset) { case MFP::GPIP: - putlog(0, "GPIP <- $%02x 未実装書き込み", data); + putlog(0, "Write GPIP <- $%02x (NOT IMPLEMENTED)", data); mfp.gpip = data; break; case MFP::AER: - putlog(0, "AER <- $%02x 未実装書き込み", data); + putlog(2, "%s <- $%02x", regname[offset], data); mfp.aer = data; break; case MFP::DDR: if (data != 0) { - putlog(0, "DDR <- $%02x 未実装書き込み", data); + putlog(0, "Write DDR <- $%02x (NOT IMPLEMENTED)", data); } mfp.ddr = data; break; case MFP::IERA: - putlog(2, "IERA <- $%02x", data); - mfp.ier.a = data; + putlog(2, "%s <- $%02x", regname[offset], data); + SetIER((data << 8) | mfp.ier.b); break; case MFP::IERB: - putlog(2, "IERB <- $%02x", data); - mfp.ier.b = data; + putlog(2, "%s <- $%02x", regname[offset], data); + SetIER((mfp.ier.a << 8) | data); break; case MFP::IPRA: - putlog(2, "IPRA <- $%02x", data); + putlog(2, "%s <- $%02x", regname[offset], data); mfp.ipr.a = data; + ChangeInterrupt(); break; case MFP::IPRB: - putlog(2, "IPRB <- $%02x", data); + putlog(2, "%s <- $%02x", regname[offset], data); mfp.ipr.b = data; + ChangeInterrupt(); break; case MFP::ISRA: - putlog(2, "ISRA <- $%02x", data); + putlog(2, "%s <- $%02x", regname[offset], data); mfp.isr.a = data; break; case MFP::ISRB: - putlog(2, "ISRB <- $%02x", data); + putlog(2, "%s <- $%02x", regname[offset], data); mfp.isr.b = data; break; case MFP::IMRA: - putlog(2, "IMRA <- $%02x", data); + putlog(2, "%s <- $%02x", regname[offset], data); mfp.imr.a = data; + ChangeInterrupt(); break; case MFP::IMRB: - putlog(2, "IMRB <- $%02x", data); + putlog(2, "%s <- $%02x", regname[offset], data); mfp.imr.b = data; + ChangeInterrupt(); break; case MFP::VR: - mfp.vr = data & 0xf0; - putlog(2, "VR <- $%02x", mfp.vr); + mfp.vr_vec = data & 0xf0; + mfp.vr_s = data & 0x08; + putlog(2, "%s <- $%02x", regname[offset], mfp.GetVR()); break; case MFP::TACR: - putlog(2, "TACR <- $%02x", data); + putlog(2, "%s <- $%02x", regname[offset], data); SetTCR(0, data); break; case MFP::TBCR: - putlog(2, "TBCR <- $%02x", data); + putlog(2, "%s <- $%02x", regname[offset], data); SetTCR(1, data); break; case MFP::TCDCR: - putlog(2, "TCDCR <- $%02x", data); + putlog(2, "%s <- $%02x", regname[offset], data); SetTCR(2, data >> 4); SetTCR(3, data & 7); break; case MFP::TADR: - putlog(2, "TADR <- $%02x", data); + putlog(2, "%s <- $%02x", regname[offset], data); SetTDR(0, data); break; case MFP::TBDR: - putlog(2, "TBDR <- $%02x", data); + putlog(2, "%s <- $%02x", regname[offset], data); SetTDR(1, data); break; case MFP::TCDR: - putlog(2, "TCDR <- $%02x", data); + putlog(2, "%s <- $%02x", regname[offset], data); SetTDR(2, data); break; case MFP::TDDR: - putlog(2, "TDDR <- $%02x", data); + putlog(2, "%s <- $%02x", regname[offset], data); SetTDR(3, data); break; case MFP::SCR: - putlog(0, "SCR <- $%02x 未実装書き込み", data); + putlog(0, "SCR <- $%02x (NOT IMPLEMENTED)", data); mfp.scr = data; break; case MFP::UCR: - putlog(0, "UCR <- $%02x 未実装書き込み", data); - mfp.ucr = data & 0xfe; + putlog(2, "%s <- $%02x", regname[offset], data); + SetUCR(data); break; case MFP::RSR: - putlog(0, "RSR <- $%02x 未実装書き込み", data); - mfp.rsr = data; + putlog(2, "%s <- $%02x", regname[offset], data); + SetRSR(data); break; case MFP::TSR: - putlog(0, "TSR <- $%02x 未実装書き込み", data); + if ((data & 0x0e)) { + // SO 端子関係の B, H, L ビットは未実装 + putlog(0, "TSR <- $%02x (B,H,L bit NOT IMPLEMENTED)", data); + } mfp.tsr = (mfp.tsr & 0xf0) | (data & 0x0f); break; case MFP::UDR: - putlog(0, "UDR <- $%02x 未実装書き込み", data); - mfp.udr = data; + if (__predict_false(loglevel >= 2)) { + if (data == 0x40 || data == 0x41) { + // MSCTRL だけ頻度が高いのでログレベルを上げておく + putlog(3, "%s <- $%02x", regname[offset], data); + } else { + putlogn("%s <- $%02x", regname[offset], data); + } + } + SetUDR(data); + break; + default: break; } - return 0; -} -uint64 -MFPDevice::Write16(uint32 addr, uint32 data) -{ - return Write8(addr + 1, data); + busdata r = wait; + r |= BusData::Size1; + return r; } -uint64 -MFPDevice::Peek8(uint32 addr) +busdata +MFPDevice::PeekPort(uint32 offset) { uint8 data; - switch (Decoder(addr)) { - default: - return (uint64)-1; - + switch (offset) { case MFP::GPIP: data = mfp.gpip; break; @@ -317,16 +485,16 @@ MFPDevice::Peek8(uint32 addr) data = mfp.imr.b; break; case MFP::VR: - data = mfp.vr; + data = mfp.GetVR(); break; case MFP::TACR: - data = mfp.tcr[0]; + data = mfp.timer[0].tcr; break; case MFP::TBCR: - data = mfp.tcr[1]; + data = mfp.timer[1].tcr; break; case MFP::TCDCR: - data = (mfp.tcr[2] << 4) | mfp.tcr[3]; + data = mfp.GetTCDCR(); break; case MFP::TADR: data = GetTDR(0); @@ -355,72 +523,134 @@ MFPDevice::Peek8(uint32 addr) case MFP::UDR: data = mfp.udr; break; + default: + data = 0xff; } return data; } bool -MFPDevice::MonitorUpdate() +MFPDevice::PokePort(uint32 offset, uint32 data) +{ + return false; +} + +void +MFPDevice::MonitorUpdate(Monitor *, TextScreen& screen) { + MFP mfp_; + std::array tdr_; int x; int y; - monitor.Clear(); + // ローカルにコピー。 + memcpy(&mfp_, &mfp, sizeof(mfp)); + for (int ch = 0; ch < tdr_.size(); ch++) { + tdr_[ch] = GetTDR(ch); + } + + screen.Clear(); // 0 1 2 3 4 5 6 - // 012345678901234567890123456789012345678901234567890123456789012345 - // IER IMR IPR ISR Mode TDR Cnt - // An:MPSC TxEmpty Enable Mask Pend Serv Timer-A Delay(50us) 255 255 + // 01234567890123456789012345678901234567890123456789012345678901234567890 + // $e88001(GPIP):$00 $e88007(IERA):$00 $e88019(TACR):$00 $e88027 + // IER IMR IPR ISR Mode TDR Cnt + // An:MPSC TxEmpty Enable Mask Pend Serv Timer-A Delay(50us) 255 255 + +#define A(x) (baseaddr + (x * 2) + 1) + + // レジスタ生値 + // 1列目(GPIP 関連と VR) + x = 0; + y = 0; + screen.Print(x, y++, "$%06x(GPIP):$%02x", A(MFP::GPIP), mfp_.gpip); + screen.Print(x, y++, "$%06x(AER): $%02x", A(MFP::AER), mfp_.aer); + screen.Print(x, y++, "$%06x(DDR): $%02x", A(MFP::DDR), mfp_.ddr); + screen.Print(x, y++, "$%06x(VR): $%02x", A(MFP::VR), mfp_.GetVR()); + // 2列目(割り込み関連) + x = 20; + y = 0; + screen.Print(x, y++, "$%06x(IERA):$%02x", A(MFP::IERA), mfp_.ier.a); + screen.Print(x, y++, "$%06x(IERB):$%02x", A(MFP::IERB), mfp_.ier.b); + screen.Print(x, y++, "$%06x(IPRA):$%02x", A(MFP::IPRA), mfp_.ipr.a); + screen.Print(x, y++, "$%06x(IPRB):$%02x", A(MFP::IPRB), mfp_.ipr.b); + screen.Print(x, y++, "$%06x(ISRA):$%02x", A(MFP::ISRA), mfp_.isr.a); + screen.Print(x, y++, "$%06x(ISRB):$%02x", A(MFP::ISRB), mfp_.isr.b); + screen.Print(x, y++, "$%06x(IMRA):$%02x", A(MFP::IMRA), mfp_.imr.a); + screen.Print(x, y++, "$%06x(IMRB):$%02x", A(MFP::IMRB), mfp_.imr.b); + // 3列目(タイマー関連) + x = 40; + y = 0; + screen.Print(x, y++, "$%06x(TACR): $%02x", A(MFP::TACR), mfp_.timer[0].tcr); + screen.Print(x, y++, "$%06x(TBCR): $%02x", A(MFP::TBCR), mfp_.timer[1].tcr); + screen.Print(x, y++, "$%06x(TCDCR):$%02x", A(MFP::TCDCR), mfp_.GetTCDCR()); + screen.Print(x, y++, "$%06x(TADR): $%02x", A(MFP::TADR), tdr_[0]); + screen.Print(x, y++, "$%06x(TBDR): $%02x", A(MFP::TBDR), tdr_[1]); + screen.Print(x, y++, "$%06x(TCDR): $%02x", A(MFP::TCDR), tdr_[2]); + screen.Print(x, y++, "$%06x(TDDR): $%02x", A(MFP::TDDR), tdr_[3]); + // 4列目(USART 関連) + x = 61; + y = 0; + screen.Print(x, y++, "$%06x(SCR):$%02x", A(MFP::SCR), mfp_.scr); + screen.Print(x, y++, "$%06x(UCR):$%02x", A(MFP::UCR), mfp_.ucr); + screen.Print(x, y++, "$%06x(RSR):$%02x", A(MFP::RSR), mfp_.rsr); + screen.Print(x, y++, "$%06x(TSR):$%02x", A(MFP::TSR), mfp_.tsr); + screen.Print(x, y, "$%06x(UDR):", A(MFP::UDR)); + if ((int16)mfp_.udr >= 0) { + screen.Print(x + 13, y, "$%02x", mfp_.udr); + } else { + screen.Print(x + 13, y, "---"); + } // 割り込み関連 - monitor.Print(0, 0, " %-12s %-6s %-4s %-4s %-4s", - "", "IER", "IMR", "IPR", "ISR"); - y = 1; + y = 9; + screen.Print(0, y++, " %-6s %-4s %-4s %-4s", + "IER", "IMR", "IPR", "ISR"); for (int i = 15; i >= 0; i--, y++) { - uint16 mask = (1 << i); - monitor.Print(0, y, "%c%d:%-12s %-6s %-4s %-4s %-4s", + uint16 mask = (1U << i); + screen.Print(0, y, "%c%u:%-12s %-6s %-4s %-4s %-4s", i >= 8 ? 'A' : 'B', (i % 8), intrname[i], - (mfp.ier.w & mask) ? "Enable" : "", - (mfp.imr.w & mask) ? "" : "Mask", // %0 でマスクする - (mfp.ipr.w & mask) ? "Pend" : "", - (mfp.isr.w & mask) ? "Serv" : ""); + (mfp_.ier.w & mask) ? "Enable" : "", + (mfp_.imr.w & mask) ? "" : "Mask", // %0 でマスクする + (mfp_.ipr.w & mask) ? "Pend" : "", + (mfp_.isr.w & mask) ? "Serv" : ""); } // タイマー関連 - x = 39; - y = 1; + x = 40; + y = 9; + screen.Print(x, y++, " Mode TDR Next"); for (int ch = 0; ch < 4; ch++) { - y = ch + 1; - monitor.Print(x, y, "Timer-%c", ch + 'A'); - if (mfp.tcr[ch] == 0) { - monitor.Print(x + 8, y, "Stopped"); - } else if (mfp.tcr[ch] == 8) { - monitor.Print(x + 8, y, "Event"); - } else if (mfp.tcr[ch] > 8) { - monitor.Print(x + 8, y, "Pulse(%s)", prescale_str[mfp.tcr[ch] - 8]); + screen.Print(x, y, "Timer-%c", ch + 'A'); + uint8 tcr = mfp_.timer[ch].tcr; + if (tcr == 0) { + screen.Print(x + 8, y, "Stopped"); + } else if (tcr < 8) { + screen.Print(x + 8, y, "Delay(%s)", prescale_str[tcr]); + } else if (tcr == 8) { + screen.Print(x + 8, y, "Event"); } else { - monitor.Print(x + 8, y, "Delay(%s)", prescale_str[mfp.tcr[ch]]); + screen.Print(x + 8, y, "Pulse(%s)", prescale_str[tcr - 8]); } - monitor.Print(x + 19, y, "%3d", GetTDR(ch)); - monitor.Print(x + 23, y, "%3d", mfp.tdr[ch]); + screen.Print(x + 20, y, "%3u", tdr_[ch]); + screen.Print(x + 25, y, "%3u", mfp_.timer[ch].next_tdr & 0xff); + y++; } // GPIP 関連 - x = 39; - y = 8; - monitor.Print(x, y++, "GPIP Name GPIP AER DDR"); + x = 40; + y = 9 + 8; + screen.Puts(x, y++, " GPIP AER DDR"); for (int i = 7; i >= 0; i--, y++) { - uint mask = (1 << i); - monitor.Print(x, y, "b%d %-6s %d %d %s", i, gpipname[i], - (mfp.gpip & mask) ? 1 : 0, - (mfp.aer & mask) ? 1 : 0, - (mfp.ddr & mask) ? "OUT" : "IN"); + uint mask = (1U << i); + screen.Print(x, y, "b%u %-6s %u %u %s", i, gpipname[i], + (mfp_.gpip & mask) ? 1 : 0, + (mfp_.aer & mask) ? 1 : 0, + (mfp_.ddr & mask) ? "OUT" : "IN"); } - - return true; } /*static*/ const char * @@ -456,16 +686,16 @@ MFPDevice::gpipname[] = { }; // TxCR の設定値から分周期間というかメインカウンタ1回分の時間を返す -/*static*/ const int +/*static*/ const uint64 MFPDevice::prescale_ns[8] = { 0, - 1000, // /4 - 2500, // /10 - 4000, // /16 - 12500, // /50 - 16000, // /64 - 25000, // /100 - 50000, // /200 + 1000_nsec, // /4 + 2500_nsec, // /10 + 4000_nsec, // /16 + 12500_nsec, // /50 + 16000_nsec, // /64 + 25000_nsec, // /100 + 50000_nsec, // /200 }; /*static*/ const char * @@ -480,284 +710,303 @@ MFPDevice::prescale_str[8] = { "50us", }; -// タイマーのチャンネルからベクタ(ビット番号) に変換する -/*static*/ const int -MFPDevice::timer_vector[4] = { 13, 8, 5, 4 }; +// タイマーのチャンネルから MFP 割り込みチャンネル番号に変換する +/*static*/ const uint +MFPDevice::timer_vector[4] = { + MFP::INTR_TIMER_A, + MFP::INTR_TIMER_B, + MFP::INTR_TIMER_C, + MFP::INTR_TIMER_D, +}; -// このタイマーで割り込みを上げる必要があるかどうかを返す -bool -MFPDevice::IsTimerIntrEnable(uint ch) const +// IER[AB] に new_ier (16 ビット全体) をセットする。 +void +MFPDevice::SetIER(uint16 new_ier) { - assertmsg(ch < 4, "ch=%u", ch); - __assume(ch < 4); - - // 割り込みが無効なら false - if ((mfp.ier.w & (1 << timer_vector[ch])) == 0) { - return false; + // raising はこの書き込みで %0 -> %1 に変化するビットだけを立てたもの、 + // falling はこの書き込みで %1 -> %0 に変化するビットだけを立てたもの。 + uint16 raising = new_ier & (mfp.ier.w ^ new_ier); + uint16 falling = mfp.ier.w & (mfp.ier.w ^ new_ier); + + // タイマー用の event[] は、割り込みを起こす必要がある時しか回さない + // 構造なので、割り込みなしでスタートしたタイマーをその動作中に後から + // 割り込み許可に変更したら、ここで何事もなかったようにタイマーイベントを + // 開始しなければいけない。 + for (int ch = 0; ch < countof(mfp.timer); ch++) { + uint timer_mask = 1U << timer_vector[ch]; + if ((raising & timer_mask) != 0 && mfp.timer[ch].IsDelay()) { + event[ch]->SetName(string_format("MFP Timer-%c", ch + 'A')); + RestartTimerEvent(ch); + } } - // 割り込みがマスクされていれば false - if ((mfp.imr.w & (1 << timer_vector[ch])) == 0) { - return false; - } + // IER を下げたところは IPR も下げる。(紙資料 p4-4) + mfp.ipr.w &= ~falling; - // そうでなければ、割り込みを上げる必要がある - return true; + // どちらにしても新しい IER によって信号線の状態は変える。 + mfp.ier.w = new_ier; + ChangeInterrupt(); +} + +// このタイマーの現在のメインカウンタの値を求める。 +inline uint8 +MFP::Timer::GetCounter(uint64 now, uint64 period) const +{ + uint8 data = tdr - (((now - start) / period) % tdr); + return data; } // TxCR をセットする void MFPDevice::SetTCR(int ch, uint32 data) { + MFP::Timer& timer = mfp.timer[ch]; uint32 prev; - prev = mfp.tcr[ch]; + prev = timer.tcr; data &= 0x0f; - mfp.tcr[ch] = data; - if (mfp.tcr[ch] > 0x08) { - putlog(0, "T%cCR サポートしていないモード $%02x", - ch + 'A', mfp.tcr[ch]); - return; - } - if (mfp.tcr[ch] == 8) { - // イベントカウントモード - - // TDR をロードする - mfp.initial_tdr[ch] = mfp.tdr[ch]; - putlog(1, "Timer-%c イベントカウントモード", ch + 'A'); - return; - - } else if (mfp.tcr[ch] == 0) { - // タイマー停止 - if (prev != 0) { - putlog(1, "Timer-%c 停止", ch + 'A'); + timer.tcr = data; + if (data == 0) { + if (prev == 0) { + // 0 -> 0 なら何も起きない + return; + } else if (prev < 8) { + // タイマー停止 + putlog(1, "Timer-%c Stop", ch + 'A'); + + // ここで TDR を確定させる。 + // タイマー動作中の TDR の参照はイベント開始時刻から求めていたが + // イベントを停止すると求められなくなるので、停止前に求めておく。 + uint64 period = prescale_ns[prev]; + uint64 now = scheduler->GetVirtTime(); + mfp.timer[ch].tdr = timer.GetCounter(now, period); + + // でイベント停止 + StopTimerEvent(ch); + return; + } else if (prev == 8) { + // イベントカウントモードから停止しても何も起きないはず + } else { + // パルス幅測定モード (未実装) } - mfp.timer_start[ch] = 0; - event[ch].Stop(); return; - } else { - // タイマー開始 - int scale = prescale_ns[mfp.tcr[ch]]; - if (prev == 0) { - // タイマー停止中に開始 - // ここで initial_tdr にロード - mfp.initial_tdr[ch] = mfp.tdr[ch] ?: 256; - mfp.timer_start[ch] = gScheduler->GetVirtTime(); - } else { - // タイマー動作中に(再)開始 + } else if (data < 8) { + // タイマー開始。 - // タイマー動作中に TxCR に書き込むと、TxDR は影響を受けずに、 - // プリスケーラのカウンタがリセットされる。 - // 例えば、TCR = 0x01 (プリスケール 10)、TDR = 0x05 で、 - // プリスケールカウントが12回進行した時点で TCR に再び 0x01 - // (プリスケール10) が書き込まれた場合、以下のようになるはず。 - // - // time -> - // prescale cnt| 0 1 2 3 4 5 6 7 8 9 0 1 2 - // main count | 5 ----------------> 4 --> - // * ここで TCR 書き込み - // TCR書き込み後のprescale cnt | 0 1 2 3 4 5 6 7 8 9 0 1 - // TCR書き込み後のmain count | 4 ----------------> 3 -> - uint64 ellapsed; - uint deccount; - - // タイマー開始時からの経過時間 - ellapsed = gScheduler->GetVirtTime() - mfp.timer_start[ch]; - - // タイマー開始時から今までのメインカウンタ減算回数 - deccount = ellapsed / scale; - - // 再設定 - mfp.initial_tdr[ch] -= deccount; - mfp.timer_start[ch] += gScheduler->GetVirtTime(); - } - - // 割り込みを上げる必要があれば、カウントダウン終了時にイベントを - // 起こす。 - // 割り込みを上げる必要がない場合は、TxDR を読み出す時に正しい値が - // 算出できるだけでよくて、それは timer_start 等により行えているので - // 他には何もしなくてよい。Timer-B 対策。 + // タイマー開始時は TDR はリロードしない。 + // 割り込みを上げる必要がある時だけイベントを使う。 + + uint64 period = prescale_ns[data]; + uint64 now = scheduler->GetVirtTime(); + timer.start = now; if (IsTimerIntrEnable(ch)) { - event[ch].time = mfp.initial_tdr[ch] * scale; - event[ch].Start(); + event[ch]->time = period * timer.tdr; + event[ch]->SetName(string_format("MFP Timer-%c", ch + 'A')); + if (sync_rt) { + // 自分の実時間軸の基準をここにする。 + stime[ch] = syncer->GetRealTime(); + // event.time はこの後変動するので、周期は別で覚えておく。 + rt_period[ch] = event[ch]->time; + + scheduler->StartRealtimeEvent(event[ch], + stime[ch], rt_period[ch]); + } else { + scheduler->RestartEvent(event[ch]); + } + } else { + event[ch]->SetName(string_format("MFP Timer-%c FreeRun", ch + 'A')); } - putlog(1, "Timer-%c ディレイモード開始(%d.%d x %d usec)", + putlog(1, "Timer-%c Start Delay mode(%u x %u.%uusec)", ch + 'A', - scale / 1000, - (scale / 100) % 10, - mfp.initial_tdr[ch]); + timer.tdr, + (uint)period / 1000, + (uint)(period / 100) % 10); + return; + + } else if (data == 8) { + // イベントカウントモード + + // TDR をロードする + timer.next_tdr = timer.tdr; + putlog(1, "Timer-%c Event count mode", ch + 'A'); + StopTimerEvent(ch); + return; + + } else { + putlog(0, "T%cCR Pulse-Width Measurement Mode $%02x (NOT IMPLEMENTED)", + ch + 'A', data); + StopTimerEvent(ch); + return; } } // TxDR の読み出し -// タイマーデータレジスタの現在値は保持しておらず、 -// タイマー開始時刻と減算量が分かっているので読み出しのたびに求める。 -// この操作に副作用はないので Peek/Monitor でも使用可能。 +// (副作用はないので Peek 系からも呼び出してよい) uint8 MFPDevice::GetTDR(int ch) const { - uint64 ellapsed; - uint64 count; - uint32 data; - - // イベントカウントモードなら initial_tdr が読める? - if (mfp.tcr[ch] == 8) { - return mfp.initial_tdr[ch]; - } - - // タイマー停止中なら TxDR と同じでよい? - if (mfp.tcr[ch] == 0) { - return mfp.tdr[ch]; - } - - // タイマー開始時からの経過時間 - ellapsed = gScheduler->GetVirtTime() - mfp.timer_start[ch]; - - // タイマー開始時から現在までのメインカウンタ減算回数 - count = ellapsed / prescale_ns[mfp.tcr[ch]]; - - // カウント終了時に割り込みが上がる設定になっていれば - // count は必ず 0 から initail_tdr-1 までの範囲だが、 - // カウント終了時に割り込みが上がる設定になっていない場合は - // 経過時間とそれに伴い count はひたすら増える。 - // そのため initial_tdr で modulo する。 - count %= mfp.initial_tdr[ch]; - - // count は減算回数だが、 - // TxDR レジスタの読み出し値は減算カウンタの現在値。 - // TxDR = 10 で開始して count = 0 のうちは TxDR 読み出しは 10。 - // TxDR = 10 で開始して count = 1 のうちは TxDR 読み出しは 9。 - // : - // TxDR = 10 で開始して count = 9 のうちは TxDR 読み出しは 1。 - data = mfp.initial_tdr[ch] - count; + const MFP::Timer& timer = mfp.timer[ch]; - return data; + if (timer.IsStopped()) { + // 停止中なら tdr に現在値が保持されている + return timer.tdr & 0xff; + + } else if (timer.IsDelay()) { + // ディレイモード動作中なら現在値は保持していないので、算出する。 + // XXX sync_rt == true で割り込み有効にしているタイマーの + // TDR を読み出しても割り込みと整合しないがもう仕方ない。 + uint64 period = prescale_ns[timer.tcr]; + uint64 now = scheduler->GetVirtTime(); + return timer.GetCounter(now, period); + + } else { + // イベントモード、パルス幅測定モードは未対応 + return timer.tdr & 0xff; + + } } // TxDR への書き込み void MFPDevice::SetTDR(int ch, uint32 data) { - uint64 ellapsed; - uint64 scale; - uint64 period; - uint64 timer_end; - - mfp.tdr[ch] = data; - - // イベントカウントモードなら即ロード - if (mfp.tcr[ch] == 8) { - mfp.initial_tdr[ch] = mfp.tdr[ch]; - } - - // カウント終了時に割り込みが上がる設定になっていれば、 - // そのイベントコールバックで tdr を initial_tdr にロードするが、 - // 割り込みが上がらない設定の場合、そのままでは tdr をリロードする - // 機会がない。そのためこのケースでは書き込み時点の次のカウント終了 - // 時にイベントを起こす必要がある。 - // - // TCR=1(プリスケール10)、TDR=5 で 38カウント目に TDR がセットされた - // 場合、ここから 12カウント目がリロードタイミング。 - // - // pre | 0 ... 0 ... 0 ... 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 - // main| 5 --> 4 --> 3 --> 2 ------------->* - // |<--------------------->| - // イベントを起こすまでの時間 - if (IsTimerIntrEnable(ch) == false) { - // タイマー開始時から現在までの経過時間 - ellapsed = gScheduler->GetVirtTime() - mfp.timer_start[ch]; - // プリスケールカウント時間 (上でいう1カウントの時間) - scale = prescale_ns[mfp.tcr[ch]]; - if (__predict_true(scale > 0)) { - // 1タイマー期間(上でいう50カウントに相当する時間) - period = scale * mfp.initial_tdr[ch]; - // タイマー開始時から次のタイムアップ時刻までの時間 - timer_end = ((ellapsed + period - 1) / period) * period; - // タイムアップ時刻 - timer_end = mfp.timer_start[ch] + timer_end; - } else { - // タイムアップ時刻 - timer_end = mfp.timer_start[ch]; + MFP::Timer& timer = mfp.timer[ch]; + + if (data == 0) { + data = 256; + } + timer.next_tdr = data; + + if (timer.IsStopped()) { + // 停止中の書き込みはメインカウンタも更新。 + timer.tdr = data; + + } else if (timer.IsDelay()) { + // ディレイモード動作中の書き込みは + // メインカウンタは影響を受けない。 + // 割り込みが回っていれば次の TimerCallback() で自動的にリロードされる + // ので、ここでは何もしなくてよい。 + // フリーランの場合は、仕方ないので次のタイムアウトパルスで + // 1回コールバックを呼んでリロードしてもらう。 + if (IsTimerIntrEnable(ch) == false) { + RestartTimerEvent(ch); } - // 現在から次のタイムアップ時刻まで - event[ch].time = timer_end - gScheduler->GetVirtTime(); - event[ch].Start(); - - putlog(3, "Timer-%c TDR 更新予約 %d.%09d", ch + 'A', - (int)(event[ch].time / (1000 * 1000 * 1000)), - (int)(event[ch].time % (1000 * 1000 * 1000))); + + } else { + // イベントモード、パルス幅測定モードは未対応 + putlog(0, "Write T%cDR $%02x during unsupported mode (NOT IMPLEMENTED)", + ch + 'A', timer.next_tdr); } } // イベントコールバック void -MFPDevice::TimerCallback(int arg) +MFPDevice::TimerCallback(Event *ev) { - int ch = arg; - int prevtdr; - uint64 ellapsed; - uint64 scale; - uint64 period; - uint64 timer_end; - - // タイムアップした時刻を計算。現在時刻ではない。 - // 現在時刻はタイムアップした直後の命令境界の時刻なので。 - - // タイマー開始時から現在までの経過時間 - ellapsed = gScheduler->GetVirtTime() - mfp.timer_start[ch]; - // 1プリスケール期間 - scale = prescale_ns[mfp.tcr[ch]]; - // 1タイマー期間 - period = scale * mfp.initial_tdr[ch]; - // タイマー開始時から直近のタイムアップ時刻まで - timer_end = (ellapsed / period) * period; - - // TxDR がゼロになったので TxDR をリロード - prevtdr = mfp.initial_tdr[ch]; - mfp.initial_tdr[ch] = mfp.tdr[ch] ?: 256; - // 今タイムアップ時刻を次のタイマー起点に更新 - mfp.timer_start[ch] += timer_end; - - // 割り込み有効なら - if (IsTimerIntrEnable(ch)) { - // 1タイマー期間を新しい TDR で再計算 - period = prescale_ns[mfp.tcr[ch]] * mfp.initial_tdr[ch]; - // イベント再設定 - event[ch].time = period; - event[ch].Start(); + int ch = ev->code; + MFP::Timer& timer = mfp.timer[ch]; - // 割り込みを上げる - gMPU680x0->Interrupt(6, mfp.vr + timer_vector[ch]); + // TDR がゼロになったところで呼ばれるので、TDR をリロードする + if (__predict_false(timer.tdr != timer.next_tdr)) { + timer.tdr = timer.next_tdr; + timer.start = 0; + // TDR (のリロード値) が変わったのでタイマー周りも再設定。 + uint64 period = prescale_ns[timer.tcr]; + ev->time = period * timer.tdr; + if (sync_rt) { + rt_period[ch] = ev->time; + } } - if (mfp.initial_tdr[ch] != prevtdr) { - putlog(1, "Timer-%c ディレイモード更新(%u.%u x %u usec)", - ch + 'A', - (uint)(scale / 1000), - (uint)((scale / 100) % 10), - (uint)mfp.initial_tdr[ch]); + // カウントが変わったり基準点がまだなければ、基準点も更新。 + if (__predict_false(timer.start == 0)) { + uint64 now = scheduler->GetVirtTime(); + timer.start = now; + } + + // 割り込みを上げる + SetInterrupt(timer_vector[ch]); + + // 次回の割り込みを起こす必要があれば、次のイベントをセット。 + // フリーラン動作中に TCR を書き換えられた時にはワンショットで来る。 + if (__predict_true(IsTimerIntrEnable(ch))) { + if (sync_rt) { + // 実時間追従動作の場合。 + + // 足す前は前回の割り込み発生時刻なので、足すと現在時刻。 + stime[ch] += rt_period[ch]; + + scheduler->StartRealtimeEvent(ev, stime[ch], rt_period[ch]); + } else { + scheduler->RestartEvent(ev); + } + } else { + // タイマー動作中に IER を下げた時にはここで次回以降のイベントを停止。 + ev->SetName(string_format("MFP Timer-%c FreeRun", ch + 'A')); } } +// 次のタイムアウトパルス時刻にイベントを起こす。 +void +MFPDevice::RestartTimerEvent(int ch) +{ + MFP::Timer& timer = mfp.timer[ch]; + + assert(timer.start != 0); + + uint64 now = scheduler->GetVirtTime(); + uint64 period = prescale_ns[timer.tcr]; + uint64 timeout = period * timer.tdr; + event[ch]->time = timeout - ((now - timer.start) % timeout); + scheduler->RestartEvent(event[ch]); +} + +// タイマーイベントを停止する。 +void +MFPDevice::StopTimerEvent(int ch) +{ + scheduler->StopEvent(event[ch]); + event[ch]->SetName(string_format("MFP Timer-%c Stopped", ch + 'A')); + mfp.timer[ch].start = 0; +} + +// タイマー割り込みを行うなら true を返す。 +// タイマーの動作、停止状態ではなく、あくまで割り込み設定のほう。 +bool +MFPDevice::IsTimerIntrEnable(int ch) const +{ + // 割り込みを起こすかどうかは IER のみで決まる。 + return ((mfp.ier.w & (1U << timer_vector[ch])) != 0); +} + +// HSync 入力 +void +MFPDevice::SetHSync(bool hsync) +{ + SetGPIP(MFP::GPIP_HSYNC, hsync); +} + // VDisp 入力 void MFPDevice::SetVDisp(bool vdisp) { + MFP::Timer& timer = mfp.timer[0]; + // TAI への入力はイベントカウントモードの時のみ - if (mfp.tcr[0] == 8) { + if (timer.IsEvent()) { // AER が示す方向と同じエッジ方向の時 - bool aer = (mfp.aer & (1 << MFP::GPIP_VDISP)); + bool aer = (mfp.aer & (1U << MFP::GPIP_VDISP)); if (aer == vdisp) { // カウンタを減算 - mfp.initial_tdr[0]--; - if (mfp.initial_tdr[0] == 0) { + timer.tdr--; + if (timer.tdr == 0) { // 0 になったらリロード - mfp.initial_tdr[0] = mfp.tdr[0]; + timer.tdr = timer.next_tdr; - // XXX ここで割り込みを上げる + SetInterrupt(MFP::INTR_TIMER_A); } } } @@ -766,13 +1015,27 @@ MFPDevice::SetVDisp(bool vdisp) SetGPIP(MFP::GPIP_VDISP, vdisp); } +// 電源ボタン状態入力 (PowerDevice から呼ばれる) +void +MFPDevice::SetPowSW(bool powsw) +{ + SetGPIP(MFP::GPIP_POWSW, powsw); +} + +// ALARM 信号入力 (RTC の ALARM_OUT 出力端子) +void +MFPDevice::SetAlarmOut(bool alarm) +{ + SetGPIP(MFP::GPIP_ALARM, alarm); +} + // GPIP への入力。 // num はビット番号(0..7)。 void MFPDevice::SetGPIP(int num, bool val) { - bool aer = (mfp.aer & (1 << num)); - bool old = (mfp.gpip & (1 << num)); + bool aer = (mfp.aer & (1U << num)); + bool old = (mfp.gpip & (1U << num)); // 立ち上がりか立ち下がりで割り込みを上げる (AER による) // AER OLD VAL Interrupt @@ -785,12 +1048,261 @@ MFPDevice::SetGPIP(int num, bool val) // 1 1 0 0 // 1 1 1 0 if ((old != val) && (aer == val)) { - // XXX ここで割り込みを上げる + // ここで割り込みを上げる + // XXX GPIP ビット番号と INTR の対応どうなってんの + int intr; + if (num < 4) { + intr = num; + } else if (num < 6) { + intr = num + 2; + } else { + intr = num - MFP::GPIP_CIRQ + MFP::INTR_CRTC_IRQ; + } + SetInterrupt(intr); } if (val) { - mfp.gpip |= (1 << num); + mfp.gpip |= (1U << num); } else { - mfp.gpip &= ~(1 << num); + mfp.gpip &= ~(1U << num); } } + +// UCR への書き込み +void +MFPDevice::SetUCR(uint32 data) +{ + mfp.ucr = data; + + if ((mfp.ucr & MFP::UCR_CLK) == 0) { + putlog(0, "UCR CLK=0 (NOT IMPLEMENTED)"); + } + if (((mfp.ucr & MFP::UCR_WL) >> 5) != 0) { + putlog(0, "UCR WL=%u (NOT IMPLEMENTED)", (mfp.ucr >> 5) & 3); + } + if (((mfp.ucr & MFP::UCR_ST) >> 3) != 1) { + putlog(0, "UCR ST=%u (NOT IMPLEMENTED)", (mfp.ucr >> 3) & 3); + } +} + +// RSR への書き込み +void +MFPDevice::SetRSR(uint32 data) +{ + uint32 old = mfp.rsr; + + mfp.rsr = data; + + if ((old & MFP::RSR_RE) == 0 && (mfp.rsr & MFP::RSR_RE)) { + // RE 0 -> 1 + } + if ((old & MFP::RSR_RE) && (mfp.rsr & MFP::RSR_RE) == 0) { + // RE 1 -> 0 + // 上位4ビットのステータスを %0 に + mfp.rsr &= ~(MFP::RSR_OE | MFP::RSR_PE | MFP::RSR_FE); + ClearBF(); + } +} + +// RSR::BF (Buffer Full) をセットする +void +MFPDevice::SetBF() +{ + mfp.rsr |= MFP::RSR_BF; +} + +// RSR::BF (Buffer Full) をクリアする +void +MFPDevice::ClearBF() +{ + mfp.rsr &= ~MFP::RSR_BF; + + // バッファが空いたのでキーボードに次の転送を催促。 + // 実際のハードウェアでは、キーボードが本体 MFP が Ready になるのを待って + // いるが、それに相当。 + // システムポートの KEYCTRL はキーボード側で処理している。 + if (RxIsReady()) { + keyboard->Ready(); + } +} + +// UDR への書き込み +void +MFPDevice::SetUDR(uint32 data) +{ + if ((mfp.tsr & MFP::TSR_TE)) { + keyboard->Command(data); + } +} + +// __ +// RR 線の状態を返す。アサートなら true。 +bool +MFPDevice::IsRR() const +{ + // __ + // 実際の RR 信号線は、BF が立っていて、PE | FE がクリアされている場合に + // アサートされる。ただしエミュレータでは Parity Error も Frame Error も + // 起きないので、この2ビットは無視する。 + return ((mfp.rsr & MFP::RSR_BF) != 0); +} + +// キーボードからの受信が可能なら true を返す。 +bool +MFPDevice::RxIsReady() const +{ + // __ + // RR は本来 MFP から CPU/DMAC など上位に対しての Ready で、 + // UDR にデータが用意できたので引き取ってほしいの意。 + // X680x0 ではこれを CPU/DMAC (上位) 向けではなく、下位のキーボードからの + // フロー制御に流用しており、 + // 上位への(本来の)「UDR にデータが用意できた」(IsRR() = true) は、 + // キーボードに対しては「UDR にデータがあるので送信禁止」、 + // 上位への(本来の)「UDR にデータが用意できていない」(IsRR() = false) は、 + // キーボードに対しては「UDR は空いているので送信してよい」と + // ここで意味が論理反転することに注意。 + // + // また、システムポートの KEYCTRL は X680x0Keyboard 側で処理しているので + // こちらでは不要。 + // + // UDR が空いていてキーボードからデータが送られてきている間は RR は Ready + // だが、そもそもキーボードは送出作業中で、新たな送信はできないはずなので + // この場合、つまり key_event の動作中も false (送信不可) にする。 + + return !IsRR() && !key_event->IsRunning(); +} + +// キーボードからデータを受信 +void +MFPDevice::Rx(uint32 data) +{ + // ここは受信したビットをシフトレジスタへ入れ始めたところに相当する + // ので、まだ Buffer Full は立てない。 + + key_event->code = data; + scheduler->RestartEvent(key_event); +} + +// キーボードからのデータが受信し終わった頃に呼ばれるイベント。 +void +MFPDevice::KeyCallback(Event *ev) +{ + // UDR にデータを置いて Buffer Full をセット + mfp.udr = ev->code; + SetBF(); + + // Manual 7-5 7.2.1 Receiver Interrupt Channels より + // | 割り込みは1文字受信ごとに1回しか発生しませんが、2つの専用割り込み + // | チャネルにより、正常な受信状態と異常な受信状態に別々のベクター番号を + // | 割り当てることができます。受信したワードにエラーがあり、エラー割り込み + // | チャネルが有効な場合は、エラーチャネルのみに割り込みが発生します。 + // | しかし、エラーチャネルが無効の場合、エラー状態の割り込みは、 + // | バッファフル状態の割り込みと一緒にバッファフル割り込みチャネルに + // | 生成されます。どのエラー条件で割り込みが発生したかを判断するためには、 + // | 常にRSRを読み出す必要があります。 + // XXX: まだエラー状態は実装されていない、なおエラーは起きないので + // メモだけのままかも知れない。 + + SetInterrupt(MFP::INTR_RXFULL); +} + +// 必要なら割り込みを上げる。 +// ch は MFP 内の割り込みチャンネル番号。MFP::INTR_* +void +MFPDevice::SetInterrupt(uint ch) +{ + assertmsg(ch < 16, "ch=%u", ch); + __assume(ch < 16); + + uint b = (1U << ch); + + // IER で割り込みが無効なら何もしない + if ((mfp.ier.w & b) == 0) { + return; + } + + // IPR にペンディングを立てる + mfp.ipr.w |= b; + + // 割り込み信号線の状態を変える + ChangeInterrupt(); +} + +// 割り込み信号線の状態を変える +void +MFPDevice::ChangeInterrupt() +{ + // ペンディングの割り込みがあればアサートされる、 + // IPR をクリアするか IMR をクリアすると /IRQ はネゲートされる。 + bool irq = ((mfp.ipr.w & mfp.imr.w) != 0); + interrupt->ChangeINT(this, irq); +} + +// 割り込みアクノリッジ +busdata +MFPDevice::InterruptAcknowledge() +{ + busdata data; + + // IPR が誰も立ってなければ、無視するっぽい? + // 実際には MFP がバスエラーを出しているのではなく、MFP は俺知らない + // と思って放置してると上位回路 (たぶん SAKI ちゃん) がタイムアウトして + // MPU にバスエラー応答する。 + uint32 pending = mfp.ipr.w; + if (__predict_false(pending == 0)) { + data.SetBusErr(); + } else { + // ch はこの時点で IPR に立ってる一番優先度の高いチャンネルのビット + int ch = 31 - __builtin_clz(pending); + uint b = (1U << ch); + + // MPU にベクタを引き渡したので Pending をクリア + mfp.ipr.w &= ~b; + ChangeInterrupt(); + + // ソフトウェア EOI なら In Service を立てる + if (mfp.vr_s) { + mfp.isr.w |= b; + } + + data = mfp.vr_vec + ch; + } + + // /IEI ネゲート時の /IACK と /IEI の関係がいまいち分からないけど、 + // とりあえず /IACK がアサートされてから 3 MFP クロック後に + // データバスにベクタを供給、次の 1 MFP クロックで DTACK 応答。 + // で次の 1 MFP クロック以内に CPU が /IACK を下げるとして、 + // 計 5 MFP クロック (= 1.25_usec) としておく。 + const busdata intack_wait = busdata::Wait(5 * 250_nsec); + data |= intack_wait; + + return data; +} + +/*static*/ const char * +MFPDevice::regname[MFP::RegMax] = { + "GPIP", + "AER", + "DDR", + "IERA", + "IERB", + "IPRA", + "IPRB", + "ISRA", + "ISRB", + "IMRA", + "IMRB", + "VR", + "TACR", + "TBCR", + "TCDCR", + "TADR", + "TBDR", + "TCDR", + "TDDR", + "SCR", + "UCR", + "RSR", + "TSR", + "UDR", +};