|
|
1.1 root 1: //
2: // nono
3: // Copyright (C) 2022 nono project
4: // Licensed under nono-license.txt
5: //
6:
7: //
8: // 実時間同期
9: //
10:
11: #include "syncer.h"
12: #include "config.h"
1.1.1.7 root 13: #include "event.h"
1.1.1.4 root 14: #include "monitor.h"
1.1.1.3 root 15: #include "mpu680x0.h"
1.1 root 16: #include "scheduler.h"
17:
18: //#define DEBUG
19:
20: #if defined(DEBUG)
21: #define DPRINTF(fmt...) printf(fmt);
22: #else
23: #define DPRINTF(fmt...) /**/
24: #endif
25:
26: // パフォーマンス測定を行う実時間間隔
27: static constexpr uint64 PERF_INTERVAL = 1000_msec;
28:
29: // コンストラクタ
30: Syncer::Syncer()
31: : inherited(OBJ_SYNCER)
32: {
33: for (int i = 0; i < perfq.Capacity(); i++) {
34: perfq.Enqueue(1000);
35: }
36: }
37:
38: // デストラクタ
39: Syncer::~Syncer()
40: {
41: }
42:
43: // 初期化
44: bool
45: Syncer::Init()
46: {
1.1.1.3 root 47: mpu680x0 = dynamic_cast<MPU680x0Device *>(mpu);
1.1.1.8 ! root 48: thman = gMainApp.GetThreadManager();
1.1.1.3 root 49:
1.1.1.5 root 50: mode = SYNCMODE_POWEROFF;
1.1 root 51: overslept = 0;
1.1.1.5 root 52: dmac_idle = 1;
1.1 root 53:
54: // 高速モード指定
55: bool fast_mode = gConfig->Find("fast-mode").AsInt();
1.1.1.8 ! root 56: NotifyFullSpeed(fast_mode);
1.1 root 57:
1.1.1.6 root 58: // クロックの同期方法
59: const ConfigItem& sync_item = gConfig->Find("clock-sync");
60: auto clock_sync = sync_item.AsString();
61: if (clock_sync == "real") {
62: sync_realtime = true;
63: } else if (clock_sync == "virtual") {
64: sync_realtime = false;
65: } else {
66: sync_item.Err();
67: return false;
68: }
69:
1.1 root 70: scheduler->ConnectMessage(MessageID::SCHEDULER_SYNCMODE, this,
1.1.1.2 root 71: ToMessageCallback(&Syncer::ChangeModeMessage));
1.1 root 72:
1.1.1.7 root 73: auto evman = GetEventManager();
74: sync_event = evman->Regist(this,
75: ToEventCallback(&Syncer::SyncCallback),
76: "Sync");
77: sync_event->time = 50_msec;
1.1.1.3 root 78:
1.1 root 79: return true;
80: }
81:
82: void
83: Syncer::StartRealTime()
84: {
85: realtime.Start();
86: }
87:
88: void
89: Syncer::StopRealTime()
90: {
91: realtime.Stop();
92: }
93:
94: // 時間の始まり
95: void
96: Syncer::StartTime()
97: {
98: scheduler->StartEvent(sync_event);
99:
100: // 諸々の初期化が終わった頃にタイマーを開始。
101: // Stopwatch.Restart() は .Net 由来なので
102: // Restart() は経過時間をリセットしてからタイマーを開始の意。
103: realtime.Restart();
104:
105: rtime = realtime.Elapsed();
106: rtime_epoch = rtime;
107: vtime_epoch = scheduler->GetVirtTime();
108:
109: // パフォーマンス表示用。
110: // 最初の集計が行われる起動後1秒までは、この値が表示されるので
111: // インチキして 100% にしておく。
1.1.1.7 root 112: clock_khz = mpu->GetClockSpeed();
1.1 root 113: perf_counter = 100;
114: last_perf_rtime = rtime_epoch;
115: last_perf_vtime = vtime_epoch;
116: next_perf_rtime = PERF_INTERVAL;
117: }
118:
119: // CPU 状態変更(通常/STOP/HALT)を指示する。
120: // CPU が呼ぶはずなので自スレッドからのみ呼ばれるはず。
121: void
1.1.1.8 ! root 122: Syncer::NotifyCPUMode(uint32 cpumode)
1.1 root 123: {
124: uint32 newmode;
125:
1.1.1.5 root 126: halt = cpumode & SYNCMODE_CPU_HALT;
127: cpu_idle = cpumode & SYNCMODE_CPU_STOP;
128:
129: newmode = (mode & ~SYNCMODE_CPU_MASK) | halt | (cpu_idle & dmac_idle);
130:
131: ChangeMode(newmode);
132: }
133:
134: // DMAC のアクティブ状態を指示する。
135: void
1.1.1.8 ! root 136: Syncer::NotifyDMACActive(bool active)
1.1.1.5 root 137: {
138: uint32 newmode;
139:
140: dmac_idle = active ? 0 : SYNCMODE_IDLE;
141:
142: newmode = (mode & ~SYNCMODE_IDLE) | (cpu_idle & dmac_idle);
1.1 root 143:
144: ChangeMode(newmode);
145: }
146:
147: // 高速モード/同期モードの変更を指示する。
148: // true なら高速モード、false なら同期モード。
149: // 他スレッドから呼んでもよい。
150: void
1.1.1.8 ! root 151: Syncer::NotifyFullSpeed(bool enable)
1.1 root 152: {
153: scheduler->SendMessage(MessageID::SCHEDULER_SYNCMODE, enable);
154: }
155:
156: // キー入力状態の変更を指示する。
157: void
1.1.1.8 ! root 158: Syncer::NotifyKeyPressed(bool pressed)
1.1 root 159: {
160: uint32 newmode;
161:
162: if (pressed) {
1.1.1.5 root 163: newmode = mode | SYNCMODE_KEY;
1.1 root 164: } else {
1.1.1.5 root 165: newmode = mode & ~SYNCMODE_KEY;
1.1 root 166: }
167:
168: ChangeMode(newmode);
169: }
170:
171: // 電源状態の変更を指示する。
172: void
1.1.1.8 ! root 173: Syncer::NotifyPowerOffMode(bool poweroff)
1.1 root 174: {
175: uint32 newmode;
176:
177: if (poweroff) {
1.1.1.5 root 178: newmode = mode | SYNCMODE_POWEROFF;
1.1 root 179: } else {
1.1.1.5 root 180: newmode = mode & ~SYNCMODE_POWEROFF;
1.1 root 181: }
182:
183: ChangeMode(newmode);
184: }
185:
1.1.1.8 ! root 186: // サウンド出力状態の変更を指示する。
! 187: void
! 188: Syncer::NotifySoundRunning(bool running)
! 189: {
! 190: uint32 newmode;
! 191:
! 192: if (running) {
! 193: newmode = mode | SYNCMODE_SOUND;
! 194: } else {
! 195: newmode = mode & ~SYNCMODE_SOUND;
! 196: }
! 197:
! 198: ChangeMode(newmode);
! 199: }
! 200:
1.1 root 201: // 動作状態変更のメッセージコールバック
202: void
1.1.1.2 root 203: Syncer::ChangeModeMessage(MessageID msgid, uint32 arg)
1.1 root 204: {
205: uint32 newmode;
206:
207: if (msgid == MessageID::SCHEDULER_SYNCMODE) {
208: if (arg) {
1.1.1.5 root 209: newmode = mode & ~SYNCMODE_SYNC;
1.1 root 210: } else {
1.1.1.5 root 211: newmode = mode | SYNCMODE_SYNC;
1.1 root 212: }
213:
214: ChangeMode(newmode);
215: }
216: }
217:
1.1.1.5 root 218: // newmode に従ってスケジューラの動作状態を変更。
1.1 root 219: // 自スレッドから呼ぶこと。
220: void
221: Syncer::ChangeMode(uint32 newmode)
222: {
223: // いずれかでも変化していれば..
224: if (mode != newmode) {
225: // 動作モード変更要因を全部調べた結果、変更が必要か。
226: // mode、newmode どちらもゼロ(高速モード)か非ゼロ(通常モード)か
227: // なので、等値比較ではなく二値にしてから比較。
228: if ((bool)mode != (bool)newmode) {
229: // モードが変わるのでここで一旦同期をとる
230: DoSync();
231:
232: if (newmode == 0) {
233: // 高速モードに
234: putlog(2, "Fast mode");
235: } else {
236: // 同期モードに
237: putlog(2, "Sync mode");
238:
239: // 同期用の基準点を設置。
240: // ゲストが STOP 命令による定常状態にいる時は
241: // 割り込みなど短期間だけ高速モードになるのを繰り返す。
242: // スリープ粒度の粗い NetBSD ホストでは同期モードに
243: // 切り替えるたびに基準点を移動していると、スリープ
244: // しすぎてしまうので、実時間と仮想時間のズレが小さい場合は
245: // 基準点を移動せず無視する。
246: uint64 vtime = scheduler->GetVirtTime();
247: uint64 rspan = rtime - rtime_epoch;
248: uint64 vspan = vtime - vtime_epoch;
249: if (vspan > rspan + overslept) {
250: rtime_epoch = rtime;
251: vtime_epoch = vtime;
252: }
253: }
254: }
255:
256: // ブール比較の後で、全ビットを維持したまま代入。
257: mode = newmode;
258: }
259: DPRINTF("ChangeMode end mode=$%x\n", mode);
260: }
261:
262: // 同期イベント
263: void
1.1.1.7 root 264: Syncer::SyncCallback(Event *ev)
1.1 root 265: {
266: DPRINTF("SyncCallback\n");
267: DoSync();
268:
269: // 次回のイベントを再登録
270: scheduler->StartEvent(ev);
271: }
272:
273: // 同期処理
274: // ここで仮想時間を実時間と同期させる。
275: void
276: Syncer::DoSync()
277: {
278: // 実時間をここで更新
279: rtime = realtime.Elapsed();
280: DPRINTF("Sync begin rtime=%s\n", SecToStr(rtime).c_str());
281:
282: // 統計情報
283: sync_count++;
284: // XXX どこでやる?
285: if (__predict_false(rtime >= next_perf_rtime)) {
286: CalcPerf();
287: }
288:
289: // XXX 次回 Start する時のイベント間隔をここ(DoSync())で事前に決めている
1.1.1.7 root 290: sync_event->time = 50_msec;
1.1 root 291:
292: // 同期モードなら..
293: if (mode != 0) {
294: uint64 vtime = scheduler->GetVirtTime();
295: uint64 rspan = rtime - rtime_epoch;
296: uint64 vspan = vtime - vtime_epoch;
297: if (vspan > rspan) {
298: // 仮想時間のほうが進んでいれば、スリープして待つ
299: uint64 diff = vspan - rspan;
300: uint64 prev = rtime;
301:
302: DPRINTF("Sync wait_for=%s\n", SecToStr(diff).c_str());
303: scheduler->Sleep(diff);
304:
305: // スリープしすぎた時間を計測。
306: // この時間にはスリープ時間を計算するための時間も含む。
307: rtime = realtime.Elapsed();
308: DPRINTF("Sync wakeup rtime=%s\n", SecToStr(rtime).c_str());
309: uint64 realslept = rtime - prev;
310: if (realslept > diff) {
311: uint64 over = realslept - diff;
312: if (over > 1_sec) {
313: // スリープ中にサスペンドされたと推定する
314: putlog(1, "時刻再同期(スリープ中の実時間跳躍)");
315: rtime_epoch = rtime;
316: vtime_epoch = vtime;
317: } else if (over > overslept) {
318: overslept = over;
319: }
320: } else if (realslept < diff) {
321: uint64 under = diff - realslept;
322: if (under > overslept) {
1.1.1.7 root 323: sync_event->time = 0;
1.1 root 324: }
325: }
326: } else if (rspan > vspan + overslept) {
327: // 実時間のほうが時間精度より進んでいれば、サスペンドとかから
328: // 復帰してきた状態だと推定して、基準時刻を再同期する。
329: putlog(1, "時刻再同期(実行中の実時間跳躍)");
330: rtime_epoch = rtime;
331: vtime_epoch = vtime;
332: } else {
333: // 同期の範囲内にいるので何もしなくていい
334: }
335: }
336: }
337:
338: void
339: Syncer::CalcPerf()
340: {
341: uint64 vtime = scheduler->GetVirtTime();
342: uint64 rperf = rtime - last_perf_rtime;
343: uint64 vperf = vtime - last_perf_vtime;
344:
345: // 百分率で小数以下1桁分までとなるよう整数で計算する
346: perfq.EnqueueForce((uint32)(vperf * 1000 / rperf));
347: // 移動平均を求める (perfq は常に要素数分埋めてある)
1.1.1.7 root 348: uint ma = perfq.GetMovingAverage();
1.1 root 349: // 最下位を四捨五入して捨てる (整数%になる)
350: ma = (ma + 5) / 10;
351: perf_counter = ma;
352:
353: // 直近1秒の Sync 回数
354: last_sync_count = sync_count;
355: sync_count = 0;
356:
1.1.1.3 root 357: // 関係ないけど、ついでにここで MPU 内の各種移動平均をとる。
358: if (mpu680x0) {
359: mpu680x0->CalcStat();
360: }
361:
1.1.1.8 ! root 362: // ついでにここで各スレッドの CPU 利用率も調べる。
! 363: thman->CalcPerf(rtime);
! 364:
1.1 root 365: last_perf_rtime = rtime;
366: last_perf_vtime = vtime;
367: next_perf_rtime = rtime + PERF_INTERVAL;
368: }
369:
370: // パフォーマンス測定用に実時間をログ出力
371: void
372: Syncer::PutlogRealtime()
373: {
374: uint64 vtime = scheduler->GetVirtTime();
375:
376: // 電源オン時からここまでの時間を表示するだけでリセット等はしない。
1.1.1.3 root 377: putlog(0, "Elapsed since power-on: %u.%03u sec, %6.2f%%",
378: (uint)(rtime / (1000 * 1000 * 1000)),
379: (uint)((rtime / (1000 * 1000)) % 1000),
1.1 root 380: (double)vtime / rtime * 100);
381:
382: #if 0
383: // イベントキューの性能評価用
384: uint64 sum = 0;
385: for (const auto e : events) {
386: sum += e->count;
387: }
388: putlog(0, "%lu events done, %6.3fM/sec", sum, (double)sum / rtime * 1000);
389: #endif
390: }
391:
392: // モニタ更新の下請け (Scheduler から呼ばれる)
393: int
1.1.1.6 root 394: Syncer::MonitorUpdateSub(TextScreen& screen, uint64 tmp_vtime)
1.1 root 395: {
396: int y = 0;
397:
1.1.1.6 root 398: // 一応ローカルコピー。
399: uint32 tmp_mode = mode;
400: uint64 tmp_rtime = rtime;
401: uint64 sync_rt_delta = tmp_rtime - rtime_epoch;
402: uint64 sync_vt_delta = tmp_vtime - vtime_epoch;
403:
404: screen.Print(0, y, "Mode: 0x%04x (", tmp_mode);
405: switch (tmp_mode & SYNCMODE_CPU_MASK) {
1.1.1.5 root 406: case SYNCMODE_CPU_NORMAL:
1.1 root 407: screen.Puts(14, y, TA::Off, "Run");
408: break;
1.1.1.5 root 409: case SYNCMODE_CPU_STOP:
1.1 root 410: screen.Puts(14, y, TA::On, "STOP");
411: break;
1.1.1.5 root 412: case SYNCMODE_CPU_HALT:
1.1 root 413: screen.Puts(14, y, TA::On, "HALT");
414: break;
415: }
1.1.1.6 root 416: if ((tmp_mode & SYNCMODE_SYNC)) {
1.1 root 417: screen.Puts(19, y, TA::On, "Sync");
418: } else {
419: screen.Puts(19, y, TA::Off, "Full");
420: }
1.1.1.7 root 421: screen.Puts(24, y, TA::OnOff((tmp_mode & SYNCMODE_KEY)), "Key");
422: screen.Puts(29, y, TA::OnOff((tmp_mode & SYNCMODE_POWEROFF)), "POff");
1.1.1.8 ! root 423: screen.Puts(34, y, TA::OnOff((tmp_mode & SYNCMODE_SOUND)), "Sound");
! 424: screen.Puts(39, y, ")");
1.1 root 425: y++;
426:
427: // 日まで表示しようとするときは桁位置を動かす
1.1.1.6 root 428: int width = std::max(tmp_rtime, tmp_vtime) < 86400_sec ? 20 : 25;
1.1 root 429:
1.1.1.6 root 430: std::string trt = TimeToStr(tmp_rtime);
431: std::string tvt = TimeToStr(tmp_vtime);
432: // 012345678901234567
433: screen.Print(0, y++, "Total Real Time: %*s", width, trt.c_str());
434: screen.Print(4, y++, "Virtual Time: %*s", width, tvt.c_str());
435: screen.Puts(0, y + 0, "Sync Real Time:");
436: screen.Puts(4, y + 1, "Virtual Time:");
437: if (tmp_mode != 0) {
438: std::string srt = TimeToStr(sync_rt_delta);
439: std::string svt = TimeToStr(sync_vt_delta);
440: screen.Print(18, y + 0, "%*s", width, srt.c_str());
441: screen.Print(18, y + 1, "%*s", width, svt.c_str());
442: }
443: y += 2;
1.1 root 444:
1.1.1.3 root 445: screen.Puts(0, y, "Rating MPU Speed :");
446: if (__predict_true((clock_khz % 1000) == 0)) {
447: screen.Print(21, y++, "%4u.%1uMHz",
448: clock_khz / 1000, (clock_khz / 100) % 10);
449: } else {
450: screen.Print(21, y++, "%4u.%03uMHz",
451: clock_khz / 1000, clock_khz % 1000);
452: }
453: uint eff_khz = clock_khz * perf_counter / 100;
454: screen.Print(0, y++, "Effective MPU Speed: %4u.%1uMHz (%4u%%)",
455: (eff_khz / 1000), (eff_khz / 100) % 10, perf_counter);
1.1 root 456:
457: int x = 52;
458: int y1 = 0;
459: screen.Print(x, y1++, "Max OverSlept: %13s", SecToStr(overslept).c_str());
460: screen.Print(x, y1++, "Sync per sec : %4u", last_sync_count);
461:
462: return y;
463: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.