--- nono/vm/scheduler.cpp 2026/04/29 17:04:29 1.1.1.2 +++ nono/vm/scheduler.cpp 2026/04/29 17:04:42 1.1.1.6 @@ -1,13 +1,17 @@ // // nono -// Copyright (C) 2017 isaki@NetBSD.org +// Copyright (C) 2020 nono project +// Licensed under nono-license.txt // +#include "scheduler.h" +#include "config.h" +#include "mainapp.h" #include "mfp.h" -#include "mpu.h" +#include "mpu680x0.h" +#include "mpu88xx0.h" +#include "mythread.h" #include "rtc.h" -#include "scheduler.h" -#include "configfile.h" #include "vm.h" // VM スケジューラは、仮想時間とイベントを管理する。 @@ -19,14 +23,13 @@ static void *scheduler_run(void *arg); -Scheduler *gScheduler; +std::unique_ptr gScheduler; // コンストラクタ Scheduler::Scheduler() { logname = "scheduler"; devname = "Scheduler"; - monitor.Init(68, 30); eventlist.clear(); } @@ -34,35 +37,46 @@ Scheduler::Scheduler() // デストラクタ Scheduler::~Scheduler() { + Terminate(); } // 初期化 // ここで mpu_clock が(非ゼロに)初期化されるとこれ以降 putlog() が使える。 -// そのため、Scheduler デバイスを必ずデバイスリストの先頭に置くことで、 -// これ以外のオブジェクトの Init() は putlog() を使ってよいとできる。 +// そのため、Scheduler デバイスをデバイスリストの先頭のほうに置くことで、 +// これ以降のオブジェクトの Init() は putlog() を使ってよいとできる。 +// BusErrDevice だけは Scheduler より先に作るが BusErrDevice に Init() は +// ないので (= 問題は起きないので)、よいことにする。 bool Scheduler::Init() { - int new_clock; - - // 先に初期値を設定しておく - // MPU クロック数 [kHz] (25MHz = 25000) - if (gVM->GetType() == VM::TYPE_LUNA) { - mpu_clock = 20000; - } else { - mpu_clock = 25000; + const ConfigItem& item = gConfig->Find("mpu-clock"); + const std::string& val = item.AsString(); + if (val.empty()) { + item.Err(); + return false; } - new_clock = gConfig->ReadInt("mpu_clock", mpu_clock); - // ReadInt() は変数があって値が解釈できなければ 0 を返す - if (new_clock == 0) { - gConfig->Err("mpu_clock", "invalid number"); + // 文字列を double に変換 + char *end; + errno = 0; + double f = strtod(val.c_str(), &end); + if (end == val.c_str() || end[0] != '\0' || errno == ERANGE) { + item.Err(); return false; } - mpu_clock = new_clock; + // 設定の "mpu-clock" は MHz 単位だが、変数 mpu_clock は kHz 単位。 + mpu_clock = (int)(f * 1000); + // 1MHz 以下はエラー。実際 10MHz 以下でもいい気がするけど + if (mpu_clock < 1000) { + item.Err(); + return false; + } + + // 起動時引数指定の高速モード + SetFullSpeed(gMainApp.fast_mode); // スケジューラ(VM)スレッド起動 - pthread_t th; - pthread_create(&th, NULL, scheduler_run, NULL); + pthread_create(&thread, NULL, scheduler_run, NULL); + thread_created = true; return true; } @@ -80,8 +94,7 @@ Scheduler::Run() void * scheduler_run(void *arg) { - pthread_setname_np("VM"); - pthread_detach(pthread_self()); + PTHREAD_SETNAME("VM"); gScheduler->ThreadRun(); return NULL; @@ -92,12 +105,24 @@ void Scheduler::ThreadRun() { uint64 cycle; // 今回実行するサイクル数 + uint32 req; // 最初の電源オンを待つ - { + for (;;) { std::unique_lock lock(cvmtx); - cv.wait(lock, [&] { return (atomic_reqflag & REQ_POWER_ON); }); - atomic_reqflag &= ~REQ_POWER_ON; + cv.wait(lock, [&] { return (atomic_reqflag != 0); }); + req = atomic_reqflag.load(); + // 終了 + if ((req & REQ_EXIT)) { + return; + } + // REQ_POWER_ON なら他のフラグを書き戻して抜ける。 + // REQ_{SYNC|FAST} は以降のメインループで処理するため。 + if ((req & REQ_POWER_ON)) { + req &= ~REQ_POWER_ON; + atomic_reqflag.store(req); + break; + } } // ここからは電源オン @@ -105,19 +130,22 @@ Scheduler::ThreadRun() rtimestart = GetRealTime(); rtc_last_clock = rtimestart; - cycle = INT64_MAX; + cycle = 0; mode = 0; for (;;) { - uint32 req; req = atomic_reqflag.exchange(0); if (req) { if ((req & REQ_SYNC)) { // スケジューラを同期モードに mode |= SCHED_SYNC; + // 同期モードでは RAM アクセスはデバイス経由 + ::direct_ram_size = 0; } if ((req & REQ_FAST)) { // スケジューラを高速モードに mode &= ~SCHED_SYNC; + // 高速モードでは RAM は直接アクセス + ::direct_ram_size = ::ram_size; } if ((req & REQ_RUN)) { // MPU が通常状態になった @@ -154,6 +182,15 @@ Scheduler::ThreadRun() req &= ~REQ_POWER_OFF; } + if ((req & REQ_RESET)) { + // リセット要求の場合 + + // 全デバイスをリセット + gVM->ResetHard(); + + req &= ~REQ_RESET; + } + if ((req & REQ_EXIT)) { // 終了要求 // この for ブロックをいきなり抜けるだけなので @@ -163,7 +200,7 @@ Scheduler::ThreadRun() } // 電源オン要求は来ないはず - assert(req == 0); + assertmsg(req == 0, "req=$%x", req); } // RTC は常にホスト時刻で動いており @@ -173,42 +210,46 @@ Scheduler::ThreadRun() rtc_last_clock += 31.25_msec; } - // まず CPU を駆動する。MPU::Run() はデバイスアクセスなどによって - // スケジューラにイベントが登録されると、その命令の終了とともに - // 処理を打ち切って戻ってくる。 - // ここでイベントリストを調べ、その時点から最も早く発生するイベント - // までの必要サイクル数が、次のループでの駆動サイクル数になる。 - - // CPU 駆動 - uint32 outer; - outer = gMPU->Run(cycle); + // イベントリストを調べ現時点から最も早く発生するイベントまでの必要 + // サイクル数を求めて、このサイクル数だけ MPU を駆動する。 + // MPU 実行中にデバイスアクセスなどによってスケジューラにイベントが + // 登録されると、その命令の終了とともに処理を打ち切って戻ってくる。 + // また、電源オン(リセット)時には MPU デバイスが 0 サイクル目で + // MPU リセット例外イベントを起こすようになっている。 // イベント for (;;) { - evcs.lock(); - if (eventlist.empty()) { - cycle = INT64_MAX; - break; - } - Event *e = eventlist.front(); - if (gMPU->total_cycle() < e->cycle) { - // まだ時刻に到達していない - cycle = e->cycle - gMPU->total_cycle(); - break; + Event *e; + // ロック区間 + { + std::lock_guard lock(evcs); + + if (eventlist.empty()) { + cycle = INT64_MAX; + break; + } + e = eventlist.front(); + if (gMPU->total_cycle() < e->cycle) { + // まだ時刻に到達していない + cycle = e->cycle - gMPU->total_cycle(); + break; + } + + // 到達したのでこのイベントをリストから削除 + e->active = false; + eventlist.pop_front(); } - // 到達したのでこのイベントをリストから削除 - e->active = false; - eventlist.pop_front(); - evcs.unlock(); - // コールバック - putlog(2, "イベント '%s' 時刻到達", e->GetName().c_str()); - ((e->dev)->*(e->func))(e->code); + e->dev->putlog(3, "イベント '%s' 時刻到達", e->GetName().c_str()); + ((e->dev)->*(e->func))(*e); // 同時刻のイベントがあるかも知れないのでなくなるまで調べる } - evcs.unlock(); + + // CPU 駆動 + uint32 outer; + outer = gMPU->Run(cycle); // 同期モードなら実時間調整。 // あるいは高速モードであってもストップ状態なら実時間駆動する。 @@ -237,9 +278,10 @@ Scheduler::ThreadRun() // CPU の STOP 状態をこっちのモードに反映。 // このために SCHED_STOP と CPU_REQ_STOP は同じビット位置にしてある。 + // CPU の HALT はスケジューラ的には STOP と同じ扱いでいい。 if (((mode ^ outer) & SCHED_STOP)) { // 状態変更要求 - if ((outer & CPU_REQ_STOP)) { + if ((outer & (CPU_REQ_STOP | CPU_REQ_HALT)) != 0) { atomic_reqflag |= REQ_STOP; } else { atomic_reqflag |= REQ_RUN; @@ -248,11 +290,15 @@ Scheduler::ThreadRun() } } -// スレッド終了指示 +// 必要ならスレッドの終了を指示して完了まで待つ。 void Scheduler::Terminate() { - atomic_reqflag |= REQ_EXIT; + if (thread_created) { + atomic_reqflag |= REQ_EXIT; + pthread_join(thread, NULL); + thread_created = false; + } } // 動作モードを設定する。 @@ -267,12 +313,12 @@ Scheduler::SetFullSpeed(bool enable) } } -// イベントをスケジューラに登録する。 +// 指定のイベントを開始する。 // すでに同イベントが登録されている場合は古いイベントを削除してから // 新しいイベントを再登録となる。 // イベントはワンショットのみ。 void -Scheduler::AddEvent(Event *newev) +Scheduler::StartEvent(Event *newev) { bool inserted = false; bool updated = false; @@ -311,17 +357,17 @@ Scheduler::AddEvent(Event *newev) gMPU->Release(); - putlog(2, "イベント '%s' %s %d.%03d usec 後", + newev->dev->putlog(3, "イベント '%s' %s %d.%03d usec 後", newev->GetName().c_str(), - updated ? "更新" : "追加", + updated ? "更新" : "開始", (int)(newev->time / 1000), (int)(newev->time % 1000)); } -// 指定のイベントを削除する。 +// 指定のイベントを停止する。 // 指定されたイベントが登録されていなければ何もしない。 void -Scheduler::DelEvent(Event *event) +Scheduler::StopEvent(Event *event) { bool found = false; @@ -337,13 +383,13 @@ Scheduler::DelEvent(Event *event) } evcs.unlock(); - // イベントを削除した場合は MPU の実行中断はしなくてよい。 + // イベントを停止した場合は MPU の実行中断はしなくてよい。 // 1msec 後にイベントを追加した後、やっぱりそのイベントを取り消した // 場合 (LUNA の電源オフとか) はここで MPU 処理を中断するよりも // そのまま 1msec 走って問題ない。 if (found) { - putlog(2, "イベント '%s' 削除", event->GetName().c_str()); + event->dev->putlog(3, "イベント '%s' 停止", event->GetName().c_str()); } } @@ -390,8 +436,17 @@ TimeToStr(uint64 t) return std::string(buf, p - buf); } -bool -Scheduler::MonitorUpdate() +// モニターサイズ取得 +nnSize +Scheduler::GetMonitorSize() +{ + // 本当は Init() 完了時点でイベント個数は確定しているはずだけど、 + // そのタイミングで一度だけ呼ばれるフックが今の所ない。 + return nnSize(68, 11 + gEvents.size()); +} + +void +Scheduler::MonitorUpdate(TextScreen& monitor) { int x, y; @@ -405,7 +460,6 @@ Scheduler::MonitorUpdate() monitor.Print(0, y++, "Req: %08x", (uint32)atomic_reqflag); monitor.Print(0, y++, "MPU Speed: %d.%03dMHz", (mpu_clock / 1000), (mpu_clock % 1000)); - uint64 cycle = gMPU->total_cycle(); uint64 vt = GetVirtTime(); uint64 rt = GetRealTime(); uint64 relvt = vt - vtimebase; @@ -430,17 +484,27 @@ Scheduler::MonitorUpdate() // 3.123'456'789 3.123'456'789 0123456789012345678901234 $01234567 x = 0; y++; - monitor.Print(x, y, "Set Time"); - monitor.Print(x + 16, y, "Remain Time"); - monitor.Print(x + 32, y, "Description"); - monitor.Print(x + 58, y, "Code"); + monitor.Puts(x, y, "Set Time"); + monitor.Puts(x + 16, y, "Remain Time"); + monitor.Puts(x + 32, y, "Description"); + monitor.Puts(x + 58, y, "Code"); y++; + + uint64 cycle = gMPU->total_cycle(); for (const auto& e : gEvents) { uint64 rem; - uint attr; + TA attr; if (e->active) { - attr = 0; - rem = Cycle2Vtime(e->cycle - cycle); + attr = TA::Normal; + if (e->cycle > cycle) { + rem = Cycle2Vtime(e->cycle - cycle); + } else { + // XXX 実際には起きないけど、この表示をするにあたって + // スケジューラスレッドと一切調停していないので、仮想時刻が + // e->cycle (イベント発生時刻) をすぎていることがある。 + // すぎてるので残り時間 0 と表示しておく。 + rem = 0; + } } else { attr = TA::Disable; rem = 0; @@ -458,6 +522,4 @@ Scheduler::MonitorUpdate() e->GetName().c_str(), e->code); } - - return true; }