--- nono/vm/dmac.cpp 2026/04/29 17:04:55 1.1.1.8 +++ nono/vm/dmac.cpp 2026/04/29 17:05:10 1.1.1.10 @@ -4,13 +4,28 @@ // Licensed under nono-license.txt // +// +// DMAC (HD63450) +// + #include "dmac.h" #include "bus.h" #include "interrupt.h" -#include "mpu680x0.h" +#include "mpu.h" +#include "scheduler.h" -std::unique_ptr gDMAC; +// time 時間後に呼び出すコールバックをセットする。 +// func の書式が面倒なのを省略して書きたいため。 +#define CallAfter(func_, time_) do { \ + event.func = ToEventCallback(&DMACDevice::func_); \ + event.time = time_ * 80_nsec; \ + gScheduler->StartEvent(event); \ +} while (0) +// グローバル参照用 +DMACDevice *gDMAC; + +// コンストラクタ DMACDevice::DMACDevice() : inherited("DMAC") { @@ -26,24 +41,22 @@ DMACDevice::DMACDevice() dmac.chan[2].desc = "User"; dmac.chan[3].desc = "ADPCM"; - for (int i = 0; i < countof(event); i++) { - event[i].dev = this; - event[i].func = (DeviceCallback_t)&DMACDevice::Transfer; - event[i].SetName(string_format("DMAC #%d", i)); - event[i].code = i; - } + event.Regist("DMAC"); - monitor.func = (MonitorCallback_t)&DMACDevice::MonitorUpdate; + monitor.func = ToMonitorCallback(&DMACDevice::MonitorUpdate); monitor.SetSize(80, 30); monitor.Regist(ID_MONITOR_DMAC); } +// デストラクタ DMACDevice::~DMACDevice() { + gDMAC = NULL; } +// リセット void -DMACDevice::ResetHard() +DMACDevice::ResetHard(bool poweron) { putlog(2, "リセット"); @@ -66,12 +79,13 @@ DMACDevice::ResetHard() chan->niv = 0x0f; chan->eiv = 0x0f; - } - // イベントをすべて停止 - for (int i = 0; i < countof(event); i++) { - event[i].Stop(); + chan->priority = 0; } + + // イベントを停止 + event.SetName("DMAC"); + gScheduler->StopEvent(event); } uint64 @@ -361,6 +375,8 @@ DMACDevice::Write(uint32 offset, uint32 case DMAC::CPR: putlog(4, "#%d CPR <- $%02x", ch, data); chan->cpr = data & 3; + // 実効プライオリティの上位4bitは cpr に等しい + chan->priority = (chan->cpr << 4); break; case DMAC::DFC: @@ -564,7 +580,7 @@ DMACDevice::MonitorUpdate(Monitor *, Tex val = chan->PeekCSR(); screen.Print(x, y++, "$%02x", val); static const char * const csrname[] = { - "COC", "BTC", "NDT", "ERR", "ACT", "---", "PCT", "PCS", + "COC", "BTC", "NDT", "ERR", "ACT", "-", "PCT", "PCS", }; MonitorReg4(screen, x, y++, val >> 4, &csrname[0]); MonitorReg4(screen, x, y++, val & 0xff, &csrname[4]); @@ -676,7 +692,7 @@ DMACDevice::MonitorUpdate(Monitor *, Tex val = chan->ccr; screen.Print(x, y++, "$%02x", val); static const char * const ccrnames[] = { - "STR", "CNT", "HLT", "SAB", "INT", "---", "---", "---", + "STR", "CNT", "HLT", "SAB", "INT", "-", "-", "-", }; MonitorReg4(screen, x, y++, (val >> 4), &ccrnames[0]); MonitorReg4(screen, x, y++, (val & 0xff), &ccrnames[4]); @@ -731,8 +747,12 @@ DMACDevice::MonitorReg4(TextScreen& scre int x, int y, uint32 reg, const char * const *names) { for (int i = 0; i < 4; i++) { - bool b = reg & (1 << (3 - i)); - screen.Puts(x + i * 4, y, TA::OnOff(b), names[i]); + if (names[i][0] == '-') { + screen.Puts(x + i * 4, y, TA::Disable, "---"); + } else { + bool b = reg & (1 << (3 - i)); + screen.Puts(x + i * 4, y, TA::OnOff(b), names[i]); + } } } @@ -788,7 +808,7 @@ DMAC::Channel::WriteCSR(uint32 data) // 上位4ビットは %1 の書き込みでクリア csr &= ~(data & 0xf0); - // PCT も %1 の書き込みでクリア + // PCT も %1 の書き込みでクリア。 // PCT は PCL が High → Low の時セットなので prev_pcl を下げればよい if ((data & DMAC::CSR_PCT)) { prev_pcl = false; @@ -863,17 +883,17 @@ DMAC::Channel::StartTransfer() // 転送モード switch (GetREQG()) { case DMAC::OCR_REQG_AUTO_LIM: - PANIC("REQG_AUTO_LIM 未実装"); + VMPANIC("REQG_AUTO_LIM 未実装"); break; case DMAC::OCR_REQG_AUTO_MAX: // 自発的に転送を開始する - parent->StartTransfer(this); + parent->StartTransfer(); break; case DMAC::OCR_REQG_EXTERNAL: case DMAC::OCR_REQG_AUTOFIRST: - PANIC("未実装 REQG"); + VMPANIC("未実装 REQG"); break; default: @@ -882,118 +902,189 @@ DMAC::Channel::StartTransfer() } // 転送開始 (デバイス側) -// (チャンネルが転送開始する際にデバイス側の private 変数(event[])が必要に +// (チャンネルが転送開始する際にデバイス側の private 変数(event)が必要に // なるため、プロキシしている) void -DMACDevice::StartTransfer(DMAC::Channel *chan) +DMACDevice::StartTransfer() { - Transfer(event[chan->ch]); + if (event.IsRunning() == false) { + CallAfter(StartCallback, 0); + } } -// 転送 -// (イベントを持つのでこれはデバイスクラスのメソッド) +// 転送開始 void -DMACDevice::Transfer(Event& ev) +DMACDevice::StartCallback(Event& ev) { - int& ch = ev.code; - DMAC::Channel *chan = &dmac.chan[ch]; - int64 rv; + // 転送チャンネルの決定 + int ch = -1; + uint8 prio = 255; + for (int i = 0; i < 4; i++) { + DMAC::Channel *chan = &dmac.chan[i]; + if (chan->active) { + uint8 p = chan->priority; + if (p < prio) { + prio = p; + // 実効プライオリティのラウンドロビン用のところを上げておく + chan->priority = (p & 0xf0) | ((p & 0x0f) >> 1); + ch = i; + } + } + } + if (ch == -1) { + // 転送チャンネルはもうないのでイベントは停止したままにする + event.SetName("DMAC"); + return; + } + xfer_chan = &dmac.chan[ch]; - putlog(4, "#%d Transfer (MTC=$%04x)", ch, chan->mtc); + // 実効プライオリティを同順位の最後に回す + xfer_chan->priority |= 0x08; - rv = 0; - switch (chan->xfertype) { - case 0: // 8ビットポート、8ビットサイズ、パック動作 + if (xfer_chan->GetDIR() == DMAC::OCR_DIR_MtoD) { + xfer_src = xfer_chan->mar; + xfer_dst = xfer_chan->dar; + } else { + xfer_src = xfer_chan->dar; + xfer_dst = xfer_chan->mar; + } + + // XXX バスアービトレーションとか + + xfer_retry = 0; + event.SetName(string_format("DMAC #%d", ch)); + CallAfter(ReadCallback, 1); +} +// 転送エラー +void +DMACDevice::Error(uint64 r) +{ + if ((int64)r == -2) { + // DTACK が出ていない (SPC)。 + // 本当は DTACK が出たことをコールバックしてくれれば + // 効率がいいのだがポーリングでもかまわんだろう。 + xfer_retry++; + putlog(4, "xfer_retry=%d", xfer_retry); + if (xfer_retry < 100) { + // 現在のイベントを再実行 + event.time = 1 * 80_nsec; + gScheduler->StartEvent(event); + return; + } + // タイムアウトしたらバスエラーにフォールスルー + } + + // バスエラー + xfer_chan->csr |= DMAC::CSR_ERR; + xfer_chan->active = false; + ChangeInterrupt(); + CallAfter(StartCallback, 0); +} + +// 転送(読み込み) +void +DMACDevice::ReadCallback(Event& ev) +{ + uint64 r; + + // シングルアドレスモードはサポートしていない + + switch (xfer_chan->xfertype) { + case 0: // 8ビットポート、8ビットサイズ、パック動作 case 1: // 8ビットポート、16ビットサイズ + case 2: // 8ビットポート、32ビットサイズ + VMPANIC("未実装 xfer type"); + + case 3: // 8ビットポート、8ビットサイズ、パックなし動作 + r = vm_phys_read_8(xfer_src); + break; + case 4: // 16ビットポート、8ビットサイズ、パック動作 + case 5: // 16ビットポート、16ビットサイズ + case 6: // 16ビットポート、32ビットサイズ + case 7: // 16ビットポート、8ビットサイズ、パックなし動作 + default: + VMPANIC("未実装 xfer type"); + } + + if ((int64)r < 0) { + Error(r); + return; + } + xfer_retry = 0; + xfer_data = r; + + CallAfter(WriteCallback, 4); +} + +// 転送(書き込み) +void +DMACDevice::WriteCallback(Event& ev) +{ + uint64 r; + putlog(4, "WRITE xfer_data=$%02x", xfer_data); + + switch (xfer_chan->xfertype) { + case 0: // 8ビットポート、8ビットサイズ、パック動作 + case 1: // 8ビットポート、16ビットサイズ case 2: // 8ビットポート、32ビットサイズ - PANIC("未実装 xfer type"); + VMPANIC("未実装 xfer type"); case 3: // 8ビットポート、8ビットサイズ、パックなし動作 - if (chan->GetDIR() == DMAC::OCR_DIR_MtoD) { - rv = TransferM8toD8(chan); - } else { - rv = TransferD8toM8(chan); - } + r = vm_phys_write_8(xfer_dst, xfer_data); break; case 4: // 16ビットポート、8ビットサイズ、パック動作 case 5: // 16ビットポート、16ビットサイズ case 6: // 16ビットポート、32ビットサイズ case 7: // 16ビットポート、8ビットサイズ、パックなし動作 - PANIC("未実装 xfer type"); + default: + VMPANIC("未実装 xfer type"); } - // XXX バスエラーは? - if (rv < 0) { + if ((int64)r < 0) { + Error(r); return; } + CallAfter(DoneCallback, 5); +} + +// 1回の転送完了 +void +DMACDevice::DoneCallback(Event& ev) +{ + putlog(4, "DONE"); + // バスエラーでなければカウンタを更新 // XXX とりあえず。後で書く - switch (chan->GetMAC()) { + switch (xfer_chan->GetMAC()) { case DMAC::SCR_COUNT_UP: - chan->mar += 1; + xfer_chan->mar += 1; break; case DMAC::SCR_COUNT_DOWN: - chan->mar -= 1; + xfer_chan->mar -= 1; break; } - switch (chan->GetDAC()) { + switch (xfer_chan->GetDAC()) { case DMAC::SCR_COUNT_UP: - chan->dar += 1; + xfer_chan->dar += 1; break; case DMAC::SCR_COUNT_DOWN: - chan->dar -= 1; + xfer_chan->dar -= 1; break; } - chan->mtc--; - if (chan->mtc == 0) { + xfer_chan->mtc--; + if (xfer_chan->mtc == 0) { // 転送完了 - chan->csr |= DMAC::CSR_COC; + xfer_chan->csr |= DMAC::CSR_COC; + xfer_chan->active = false; ChangeInterrupt(); - } else { - // 次回の転送 - // XXX とりあえず 1us で1回の転送にする - ev.time = 1_usec; - ev.Start(); } -} -// メモリから 8ビットデバイスへ (パックなし動作) -int64 -DMACDevice::TransferM8toD8(DMAC::Channel *chan) -{ - uint64 data; - int64 rv; - - data = vm_phys_read_8(chan->mar); - if ((int64)data < 0) - return data; - rv = vm_phys_write_8(chan->dar, data); - if (rv < 0) - return rv; - - return 0; -} - -// 8ビットデバイスからメモリへ (パックなし動作) -int64 -DMACDevice::TransferD8toM8(DMAC::Channel *chan) -{ - uint64 data; - int64 rv; - - data = vm_phys_read_8(chan->dar); - if ((int64)data < 0) - return data; - rv = vm_phys_write_8(chan->mar, data); - if (rv < 0) - return rv; - - return 0; + CallAfter(StartCallback, 1); } // ソフトウェアアボート @@ -1001,15 +1092,13 @@ DMACDevice::TransferD8toM8(DMAC::Channel void DMACDevice::AbortTransfer(DMAC::Channel *chan) { - int ch = chan->ch; - putlog(2, "CCR SAB ソフトウェアアボート"); // %1 を書き込むことで実際には SAB はセットされるが、ERR が立つと // SAB をクリアする動作のため、書き込んだ %1 が読めることはない。 // ここでは SAB ビットのセットを省略。 - event[ch].Stop(); + chan->active = false; chan->cer = DMAC::CER_SOFT_ABORT; // ERR ビットが立つと SAB をクリアする chan->csr |= DMAC::CSR_ERR; @@ -1037,19 +1126,30 @@ DMACDevice::ChangeInterrupt() int DMACDevice::InterruptAcknowledge() { - // XXX CPR のプライオリティは未対応、とりあえず前から順 + DMAC::Channel *chan; + int top = -1; + uint8 prio = 255; + + // 割り込みを発生させているトッププライオリティのチャンネルを検索 for (int ch = 0; ch < countof(dmac.chan); ch++) { - DMAC::Channel *chan = &dmac.chan[ch]; + chan = &dmac.chan[ch]; - if (chan->active) { - if ((chan->csr & DMAC::CSR_ERR)) { - return chan->eiv; - } else { - return chan->niv; + if ((chan->ccr & DMAC::CCR_INT) && chan->IsINTR()) { + if (chan->priority < prio) { + prio = chan->priority; + top = ch; } - chan->active = false; + } + } + + if (top >= 0) { + chan = &dmac.chan[top]; + if ((chan->csr & DMAC::CSR_ERR)) { + return chan->eiv; + } else { + return chan->niv; } } // XXX 誰もいなかったら? - PANIC("InterruptAcknowledge no active channels?"); + VMPANIC("InterruptAcknowledge no active channels?"); }