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