--- nono/vm/mfp.cpp 2026/04/29 17:04:36 1.1.1.4 +++ nono/vm/mfp.cpp 2026/04/29 17:04:55 1.1.1.10 @@ -4,22 +4,20 @@ // Licensed under nono-license.txt // -#include "keyboard.h" #include "mfp.h" +#include "interrupt.h" +#include "keyboard.h" #include "mpu680x0.h" -#include "mystring.h" -#include "scheduler.h" +#include "x68kkbd.h" std::unique_ptr gMFP; // コンストラクタ MFPDevice::MFPDevice() + : inherited("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; @@ -29,10 +27,13 @@ MFPDevice::MFPDevice() } // キーボード周りは x68kkbd.cpp のコメント参照 - key_event.dev = this; key_event.func = (DeviceCallback_t)&MFPDevice::KeyCallback; key_event.time = 3750_usec; key_event.SetName("MFP USART"); + + monitor.func = (MonitorCallback_t)&MFPDevice::MonitorUpdate; + monitor.SetSize(77, 26); + monitor.Regist(ID_MONITOR_MFP); } // デストラクタ @@ -40,36 +41,53 @@ MFPDevice::~MFPDevice() { } -// MPU の RESET 命令によるリセット +// デバイスリセット void -MFPDevice::ResetSoft() +MFPDevice::ResetHard() { - // TxDR, UDR, TSR はクリアされない - - // ? - mfp.tsr = 0x80; + // All internal registers are cleared expect the TxDR, UDR, and TSR. + 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.tcr); i++) { + mfp.tcr[i] = 0; + } + mfp.scr = 0; + mfp.ucr = 0; + mfp.rsr = 0; + // XXX TSR は変化しないがたぶんバッファはクリアされるので BE は立つ + // ということかな。 + mfp.tsr = MFP::TSR_BE; - // それ以外のすべてのレジスタはクリア? - mfp.vr = 0; - - // タイマーはすべてストップなのでイベントを削除 + // All timers are stopped. for (int ch = 0; ch < countof(event); ch++) { event[ch].Stop(); } key_event.Stop(); - // 信号線 - rr = false; - - // ペンディング割り込みクリア + // 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; } uint64 -MFPDevice::Read(uint32 addr) +MFPDevice::Read(uint32 offset) { uint8 data; - switch (addr) { + gMPU->AddCycle(24); // InsideOut p.135 + + switch (offset) { case MFP::GPIP: data = mfp.gpip; break; @@ -104,7 +122,7 @@ MFPDevice::Read(uint32 addr) data = mfp.imr.b; break; case MFP::VR: - data = mfp.vr; + data = mfp.GetVR(); break; case MFP::TACR: data = mfp.tcr[0]; @@ -140,7 +158,9 @@ MFPDevice::Read(uint32 addr) data = mfp.tsr; break; case MFP::UDR: + // UDR からデータを読み出したら Buffer Full をクリア data = mfp.udr; + ClearBF(); break; default: data = 0xff; @@ -150,9 +170,11 @@ MFPDevice::Read(uint32 addr) } uint64 -MFPDevice::Write(uint32 addr, uint32 data) +MFPDevice::Write(uint32 offset, uint32 data) { - switch (addr) { + gMPU->AddCycle(24); // InsideOut p.135 + + switch (offset) { case MFP::GPIP: putlog(0, "GPIP <- $%02x 未実装書き込み", data); mfp.gpip = data; @@ -178,10 +200,12 @@ MFPDevice::Write(uint32 addr, uint32 dat case MFP::IPRA: putlog(2, "IPRA <- $%02x", data); mfp.ipr.a = data; + ChangeInterrupt(); break; case MFP::IPRB: putlog(2, "IPRB <- $%02x", data); mfp.ipr.b = data; + ChangeInterrupt(); break; case MFP::ISRA: putlog(2, "ISRA <- $%02x", data); @@ -194,14 +218,17 @@ MFPDevice::Write(uint32 addr, uint32 dat case MFP::IMRA: putlog(2, "IMRA <- $%02x", data); mfp.imr.a = data; + ChangeInterrupt(); break; case MFP::IMRB: putlog(2, "IMRB <- $%02x", 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, "VR <- $%02x", mfp.GetVR()); break; case MFP::TACR: putlog(2, "TACR <- $%02x", data); @@ -264,11 +291,11 @@ MFPDevice::Write(uint32 addr, uint32 dat } uint64 -MFPDevice::Peek(uint32 addr) +MFPDevice::Peek(uint32 offset) { uint8 data; - switch (addr) { + switch (offset) { case MFP::GPIP: data = mfp.gpip; break; @@ -303,7 +330,7 @@ MFPDevice::Peek(uint32 addr) data = mfp.imr.b; break; case MFP::VR: - data = mfp.vr; + data = mfp.GetVR(); break; case MFP::TACR: data = mfp.tcr[0]; @@ -347,26 +374,73 @@ MFPDevice::Peek(uint32 addr) return data; } -bool -MFPDevice::MonitorUpdate() +void +MFPDevice::MonitorUpdate(Monitor *, TextScreen& screen) { int x; int y; - monitor.Clear(); + screen.Clear(); // 0 1 2 3 4 5 6 - // 012345678901234567890123456789012345678901234567890123456789012345 + // 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.tcr[0]); + screen.Print(x, y++, "$%06x(TBCR): $%02x", A(MFP::TBCR), mfp.tcr[1]); + screen.Print(x, y++, "$%06x(TCDCR):$%02x", A(MFP::TCDCR), + (mfp.tcr[2] << 4) | mfp.tcr[3]); + screen.Print(x, y++, "$%06x(TADR): $%02x", A(MFP::TADR), GetTDR(0)); + screen.Print(x, y++, "$%06x(TBDR): $%02x", A(MFP::TBDR), GetTDR(1)); + screen.Print(x, y++, "$%06x(TCDR): $%02x", A(MFP::TCDR), GetTDR(2)); + screen.Print(x, y++, "$%06x(TDDR): $%02x", A(MFP::TDDR), GetTDR(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", + screen.Print(0, y, "%c%d:%-12s %-6s %-4s %-4s %-4s", i >= 8 ? 'A' : 'B', (i % 8), intrname[i], @@ -378,37 +452,36 @@ MFPDevice::MonitorUpdate() // タイマー関連 x = 39; - y = 1; + y = 9; + screen.Print(x, y++, " Mode TDR Ini"); for (int ch = 0; ch < 4; ch++) { - y = ch + 1; - monitor.Print(x, y, "Timer-%c", ch + 'A'); + screen.Print(x, y, "Timer-%c", ch + 'A'); if (mfp.tcr[ch] == 0) { - monitor.Print(x + 8, y, "Stopped"); + screen.Print(x + 8, y, "Stopped"); } else if (mfp.tcr[ch] == 8) { - monitor.Print(x + 8, y, "Event"); + screen.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 + 8, y, "Pulse(%s)", prescale_str[mfp.tcr[ch] - 8]); } else { - monitor.Print(x + 8, y, "Delay(%s)", prescale_str[mfp.tcr[ch]]); + screen.Print(x + 8, y, "Delay(%s)", prescale_str[mfp.tcr[ch]]); } - monitor.Print(x + 19, y, "%3d", GetTDR(ch)); - monitor.Print(x + 23, y, "%3d", mfp.tdr[ch]); + screen.Print(x + 19, y, "%3d", GetTDR(ch)); + screen.Print(x + 23, y, "%3d", mfp.initial_tdr[ch]); + y++; } // GPIP 関連 x = 39; - y = 8; - monitor.Puts(x, y++, "GPIP Name GPIP AER DDR"); + 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], + screen.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"); } - - return true; } /*static*/ const char * @@ -468,7 +541,7 @@ MFPDevice::prescale_str[8] = { "50us", }; -// タイマーのチャンネルからベクタ(ビット番号) に変換する +// タイマーのチャンネルから MFP 割り込みチャンネル番号に変換する /*static*/ const int MFPDevice::timer_vector[4] = { MFP::INTR_TIMER_A, @@ -477,27 +550,6 @@ MFPDevice::timer_vector[4] = { MFP::INTR_TIMER_D, }; -// このタイマーで割り込みを上げる必要があるかどうかを返す -bool -MFPDevice::IsTimerIntrEnable(uint ch) const -{ - assertmsg(ch < 4, "ch=%u", ch); - __assume(ch < 4); - - // 割り込みが無効なら false - if ((mfp.ier.w & (1 << timer_vector[ch])) == 0) { - return false; - } - - // 割り込みがマスクされていれば false - if ((mfp.imr.w & (1 << timer_vector[ch])) == 0) { - return false; - } - - // そうでなければ、割り込みを上げる必要がある - return true; -} - // TxCR をセットする void MFPDevice::SetTCR(int ch, uint32 data) @@ -508,12 +560,50 @@ MFPDevice::SetTCR(int ch, uint32 data) data &= 0x0f; mfp.tcr[ch] = data; - if (mfp.tcr[ch] > 0x08) { - putlog(0, "T%cCR サポートしていないモード $%02x", - ch + 'A', mfp.tcr[ch]); + if (data == 0) { + if (prev == 0) { + // 0 -> 0 なら何も起きない + return; + } else if (prev < 8) { + // タイマー停止 + putlog(1, "Timer-%c 停止", ch + 'A'); + + // ここで TDR を確定させる。 + // タイマー動作中の TDR の参照はイベント終了時刻から現在値を逆算 + // していたが、イベントを停止すると求められなくなるので、イベント + // 停止前に求めておく。 + uint64 period = prescale_ns[prev]; + uint64 now = gMPU->GetVirtTime(); + mfp.tdr[ch] = ((event[ch].vtime - now) + period - 1) / period; + + // でイベント停止 + event[ch].Stop(); + return; + } else if (prev == 8) { + // イベントカウントモードから停止しても何も起きないはず + } else { + // パルス幅測定モード (未実装) + } return; - } - if (mfp.tcr[ch] == 8) { + + } else if (data < 8) { + // タイマー開始 + + // TDR をリロード(?) + mfp.tdr[ch] = mfp.initial_tdr[ch]; + // タイマースタート + uint64 period = prescale_ns[mfp.tcr[ch]]; + event[ch].time = period * (mfp.tdr[ch] ?: 256); + event[ch].Start(); + + putlog(1, "Timer-%c ディレイモード開始(%u x %u.%uusec)", + ch + 'A', + mfp.initial_tdr[ch], + (uint)period / 1000, + (uint)(period / 100) % 10); + return; + + } else if (data == 8) { // イベントカウントモード // TDR をロードする @@ -521,218 +611,86 @@ MFPDevice::SetTCR(int ch, uint32 data) putlog(1, "Timer-%c イベントカウントモード", ch + 'A'); return; - } else if (mfp.tcr[ch] == 0) { - // タイマー停止 - if (prev != 0) { - putlog(1, "Timer-%c 停止", ch + 'A'); - } - 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 { - // タイマー動作中に(再)開始 - - // タイマー動作中に 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 対策。 - if (IsTimerIntrEnable(ch)) { - event[ch].time = mfp.initial_tdr[ch] * scale; - event[ch].Start(); - } - - putlog(1, "Timer-%c ディレイモード開始(%d.%d x %d usec)", - ch + 'A', - scale / 1000, - (scale / 100) % 10, - mfp.initial_tdr[ch]); + putlog(0, "T%cCR パルス幅測定モード $%02x 未実装", + ch + 'A', data); + return; } } // TxDR の読み出し -// タイマーデータレジスタの現在値は保持しておらず、 -// タイマー開始時刻と減算量が分かっているので読み出しのたびに求める。 -// この操作に副作用はないので Peek/Monitor でも使用可能。 +// (副作用はないので Peek 系からも呼び出してよい) uint8 MFPDevice::GetTDR(int ch) const { - uint64 ellapsed; - uint64 count; - uint32 data; + uint8 tcr = mfp.tcr[ch]; - // イベントカウントモードなら initial_tdr が読める? - if (mfp.tcr[ch] == 8) { - return mfp.initial_tdr[ch]; - } - - // タイマー停止中なら TxDR と同じでよい? - if (mfp.tcr[ch] == 0) { + if (tcr == 0) { + // 停止中なら tdr に現在値が保持されている return mfp.tdr[ch]; - } - - // タイマー開始時からの経過時間 - ellapsed = gScheduler->GetVirtTime() - mfp.timer_start[ch]; - // タイマー開始時から現在までのメインカウンタ減算回数 - count = ellapsed / prescale_ns[mfp.tcr[ch]]; + } else if (tcr < 8) { + // ディレイモード動作中なら現在値は保持していないので、 + // プリスケーラと終了時刻から算出。 + + uint64 period = prescale_ns[tcr]; + uint64 now = gMPU->GetVirtTime(); + return ((event[ch].vtime - now) + period - 1) / period; - // カウント終了時に割り込みが上がる設定になっていれば - // 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; + } else { + // イベントモード、パルス幅測定モードは未対応 + return mfp.tdr[ch]; - return data; + } } // TxDR への書き込み void MFPDevice::SetTDR(int ch, uint32 data) { - uint64 ellapsed; - uint64 scale; - uint64 period; - uint64 timer_end; + uint8& tcr = mfp.tcr[ch]; - mfp.tdr[ch] = data; + if (tcr == 0) { + // 停止中の書き込みは両方更新 + mfp.tdr[ch] = data; + mfp.initial_tdr[ch] = data; + + } else if (tcr < 8) { + // ディレイモード動作中の書き込み + // メインカウンタは影響を受けず、次回値を更新するだけ。 + mfp.initial_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]; - } - // 現在から次のタイムアップ時刻まで - 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, "T%cDR サポートしてないモード中の書き込み $%02x", + ch + 'A', data); } } // イベントコールバック 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; - // 割り込みを上げる - gMPU680x0->Interrupt(this, 6, mfp.vr + timer_vector[ch]); - } + // TDR がゼロになったところで呼ばれるので、TDR をリロードする + mfp.tdr[ch] = mfp.initial_tdr[ch]; - 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]); - } + // 割り込みを上げる + SetInterrupt(timer_vector[ch]); + + // 次のイベントをセット + // XXX 1回のイベントで複数のタイムアウトパルスを跨ぐ状況は考慮してない + + // period が1メインパルス期間 + // time が1タイムアウトパルス期間 + uint64 period = prescale_ns[mfp.tcr[ch]]; + uint64 time = period * (mfp.tdr[ch] ?: 256); + // 次のタイムアウトパルスは、今回のタイムアウト時刻から数える + // (現在時刻はそれを通り過ぎた任意の時刻なので) + uint64 now = gMPU->GetVirtTime(); + ev.time = ev.vtime + time - now; + ev.Start(); } // VDisp 入力 @@ -745,12 +703,12 @@ MFPDevice::SetVDisp(bool vdisp) bool aer = (mfp.aer & (1 << MFP::GPIP_VDISP)); if (aer == vdisp) { // カウンタを減算 - mfp.initial_tdr[0]--; - if (mfp.initial_tdr[0] == 0) { + mfp.tdr[0]--; + if (mfp.tdr[0] == 0) { // 0 になったらリロード - mfp.initial_tdr[0] = mfp.tdr[0]; + mfp.tdr[0] = mfp.initial_tdr[0]; - // XXX ここで割り込みを上げる + SetInterrupt(MFP::INTR_TIMER_A); } } } @@ -815,15 +773,36 @@ MFPDevice::SetRSR(uint32 data) if ((old & MFP::RSR_RE) == 0 && (mfp.rsr & MFP::RSR_RE)) { // RE 0 -> 1 - rr = true; } if ((old & MFP::RSR_RE) && (mfp.rsr & MFP::RSR_RE) == 0) { // RE 1 -> 0 - rr = false; - mfp.rsr &= 0x0f; // 上位4ビットのステータスを %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 はキーボード側で処理している。 + X68030Keyboard *x68kkbd = dynamic_cast(gKeyboard.get()); + x68kkbd->Ready(); +} + // UDR への書き込み void MFPDevice::SetUDR(uint32 data) @@ -833,28 +812,49 @@ MFPDevice::SetUDR(uint32 data) } } -// システムポート#4 の KEY CTRL の状態を受け取る。 -void -MFPDevice::SetKeyCtrl(bool val) +// __ +// RR 線の状態を返す。アサートなら true。 +bool +MFPDevice::IsRR() const { - key_ctrl = val; - putlog(1, "KEYCTRL = %d", key_ctrl); + // __ + // 実際の RR 信号線は、BF が立っていて、PE | FE がクリアされている場合に + // アサートされる。ただしエミュレータでは Parity Error も Frame Error も + // 起きないので、この2ビットは無視する。 + return ((mfp.rsr & MFP::RSR_BF) != 0); } -// RR (Receiver Ready) 線の状態を返す。true なら Ready。 -// これはシステムポート#4 の KEY CTRL の状態も加味したもの。 +// キーボードからの受信が可能なら true を返す。 bool -MFPDevice::IsRR() const +MFPDevice::RxIsReady() const { - return rr && key_ctrl; + // __ + // 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) { - // Ready を下げる - rr = false; + // ここは受信したビットをシフトレジスタへ入れ始めたところに相当する + // ので、まだ Buffer Full は立てない。 key_event.code = data; key_event.Start(); @@ -862,18 +862,78 @@ MFPDevice::Rx(uint32 data) // キーボードからのデータが受信し終わった頃に呼ばれるイベント。 void -MFPDevice::KeyCallback(int code) +MFPDevice::KeyCallback(Event& ev) { - // Ready を元に戻す - rr = true; + // UDR にデータを置いて Buffer Full をセット + mfp.udr = ev.code; + SetBF(); - mfp.udr = code; - mfp.rsr |= MFP::RSR_BF; + // 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); + gInterrupt->ChangeINT(this, irq); +} + +// 割り込みアクノリッジ +int +MFPDevice::InterruptAcknowledge() +{ + uint32 pending = mfp.ipr.w; - // XXX この辺要再考察 - mfp.isr.a |= 0x10; - // 割り込み有効でマスクされてない時だけ - if ((mfp.ier.a & 0x10) && (mfp.imr.a & 0x10)) { - gMPU680x0->Interrupt(this, 6, mfp.vr + MFP::INTR_RXFULL); + // ch はこの時点で IPR に立ってる一番優先度の高いチャンネルのビット + // XXX 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; } + + return mfp.vr_vec + ch; }