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