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