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