|
|
1.1 root 1: //
2: // nono
1.1.1.3 root 3: // Copyright (C) 2020 nono project
4: // Licensed under nono-license.txt
1.1 root 5: //
6:
1.1.1.8 root 7: //
8: // RTC の共通部分
9: //
10:
1.1 root 11: #include "rtc.h"
1.1.1.6 root 12: #include "config.h"
1.1.1.8 root 13: #include "scheduler.h"
1.1.1.9 root 14: #include "syncer.h"
1.1 root 15:
1.1.1.8 root 16: // コンストラクタ
1.1.1.9 root 17: RTCDevice::RTCDevice()
18: : inherited(OBJ_RTC)
1.1 root 19: {
20: }
21:
1.1.1.8 root 22: // デストラクタ
1.1 root 23: RTCDevice::~RTCDevice()
24: {
25: }
26:
1.1.1.9 root 27: // 初期化
1.1.1.6 root 28: bool
29: RTCDevice::Init()
1.1 root 30: {
1.1.1.9 root 31: if (inherited::Init() == false) {
32: return false;
33: }
34:
35: syncer = GetSyncer();
36:
1.1.1.8 root 37: // TODO: use_localtime を設定可能にするべきかどうか
1.1 root 38:
1.1.1.8 root 39: // 時刻固定オプション (パフォーマンス測定用)。
1.1.1.6 root 40: // パフォーマンス測定をする際に、RTC が実時間を返すと
41: // ゲストの状態が毎回異なることになるため、
42: // いつ何時も同じ値を返すことで、世界から隔絶された
43: // VM を作り、パフォーマンス測定を安定させる。
44: force_fixed = gConfig->Find(".rtc-force-fixed").AsInt();
1.1.1.8 root 45:
46: // クロックの同期方法
47: const ConfigItem& item_sync = gConfig->Find("clock-sync");
48: auto clock_sync = item_sync.AsString();
49: if (clock_sync == "real") {
50: sync_rt = true;
51: } else if (clock_sync == "virtual") {
52: sync_rt = false;
53: } else {
54: item_sync.Err();
55: return false;
56: }
57:
58: rtc_tm.tm_year = -1;
59: rtc_tm.tm_mon = -1;
60: rtc_tm.tm_mday = -1;
61: rtc_tm.tm_wday = -1;
62: rtc_tm.tm_hour = -1;
63: rtc_tm.tm_min = -1;
64: rtc_tm.tm_sec = -1;
65:
66: // 日付、時刻の直接指定
67: const ConfigItem item_date = gConfig->Find(".rtc-date");
68: std::string str_date = item_date.AsString();
69: if (!str_date.empty()) {
70: int yy, mm, dd, w;
71: if (ParseDate(str_date, &yy, &mm, &dd, &w) == false) {
72: item_date.Err();
73: return false;
74: }
75: rtc_tm.tm_year = yy - 1900;
76: rtc_tm.tm_mon = mm - 1;
77: rtc_tm.tm_mday = dd;
78: if (w != -1) {
79: rtc_tm.tm_wday = w;
80: }
81: }
82:
83: const ConfigItem item_time = gConfig->Find(".rtc-time");
84: std::string str_time = item_time.AsString();
85: if (!str_time.empty()) {
86: int hh, mm, ss;
87: if (ParseTime(str_time, &hh, &mm, &ss) == false) {
88: item_time.Err();
89: return false;
90: }
91: rtc_tm.tm_hour = hh;
92: rtc_tm.tm_min = mm;
93: rtc_tm.tm_sec = ss;
94: }
95:
1.1.1.10! root 96: event.func = ToEventCallback(&RTCDevice::Callback);
! 97: event.time = period;
! 98: event.SetName("RTC ClockIn");
! 99: scheduler->RegistEvent(event);
! 100:
1.1.1.8 root 101: return true;
102: }
103:
104: // 時間の始まり
105: void
106: RTCDevice::StartTime()
107: {
108: struct timeval tv;
109: struct tm tm;
110:
1.1.1.6 root 111: if (force_fixed) {
112: // 固定の時刻を設定
113: struct tm fixed {
114: .tm_sec = 16,
115: .tm_min = 22,
116: .tm_hour = 13 - 9,
117: .tm_mday = 27,
118: .tm_mon = 7 - 1,
119: .tm_year = 2018 - 1900,
120: };
121: tv.tv_sec = timegm(&fixed);
122: tv.tv_usec = 0;
123: } else {
124: // 起動した時の現在時刻で設定する
125: gettimeofday(&tv, NULL);
126: }
127:
1.1 root 128: if (use_localtime) {
129: localtime_r(&tv.tv_sec, &tm);
130: } else {
131: gmtime_r(&tv.tv_sec, &tm);
132: }
1.1.1.6 root 133:
134: // 日付、時刻の直接指定があればここで差し替える。
1.1.1.8 root 135: if (rtc_tm.tm_year != -1) {
136: tm.tm_year = rtc_tm.tm_year;
137: tm.tm_mon = rtc_tm.tm_mon;
138: tm.tm_mday = rtc_tm.tm_mday;
139: if (rtc_tm.tm_wday != -1) {
140: tm.tm_wday = rtc_tm.tm_wday;
1.1.1.6 root 141: }
142: }
1.1.1.8 root 143: if (rtc_tm.tm_hour != -1) {
144: tm.tm_hour = rtc_tm.tm_hour;
145: tm.tm_min = rtc_tm.tm_min;
146: tm.tm_sec = rtc_tm.tm_sec;
1.1.1.6 root 147: }
148:
1.1 root 149: // 秒未満の初期値もそれなりの精度にしておく (1000*1000 / 32 = 31250)
1.1.1.6 root 150: cnt = tv.tv_usec / 31250;
151: SetSec(tm.tm_sec);
152: SetMin(tm.tm_min);
153: SetHour(tm.tm_hour);
1.1 root 154: // wday も tm_wday も日曜が 0
1.1.1.6 root 155: SetWday(tm.tm_wday);
156: SetMday(tm.tm_mday);
157: // mon は 1 から、tm_mon は 0 から。
158: SetMon(tm.tm_mon + 1);
159: SetYear(tm.tm_year + 1900);
160: SetLeap(tm.tm_year & 3);
161:
1.1.1.9 root 162: stime = syncer->GetRealTime();
1.1.1.8 root 163:
1.1.1.9 root 164: scheduler->StartEvent(event);
1.1 root 165: }
166:
1.1.1.6 root 167: // デバッグオプションの日付文字列を解析する。
168: // 書式は "yyyy-mm-dd[-w]"。曜日は省略可能、区切り文字は1文字なら何でもいい。
169: // 入力値の範囲チェックはしない。(出来れば 0A月みたいなのも受け付けたいが)
170: bool
171: RTCDevice::ParseDate(const std::string& str, int *y, int *m, int *d, int *w)
1.1 root 172: {
1.1.1.6 root 173: const char *p = str.data();
174: char *end;
1.1 root 175:
1.1.1.6 root 176: errno = 0;
177: *y = strtoul(p, &end, 10);
178: if (end == p || *end == '\0' || errno != 0) {
179: return false;
180: }
1.1 root 181:
1.1.1.6 root 182: p = end + 1;
183: errno = 0;
184: *m = strtoul(p, &end, 10);
185: if (end == p || *end == '\0' || errno != 0) {
186: return false;
187: }
1.1 root 188:
1.1.1.6 root 189: p = end + 1;
190: errno = 0;
191: *d = strtoul(p, &end, 10);
192: if (end == p || errno != 0) {
193: return false;
1.1 root 194: }
195:
1.1.1.6 root 196: if (*end == 0) {
197: // 曜日はなくても可
198: *w = -1;
199: } else {
200: // あれば取り出す
201: p = end + 1;
202: errno = 0;
203: *w = strtoul(p, &end, 10);
204: if (end == p || errno != 0) {
205: return false;
206: }
1.1 root 207: }
1.1.1.6 root 208:
209: return true;
1.1 root 210: }
211:
1.1.1.6 root 212: // デバッグオプションの時刻文字列を解析する。
213: // 書式は "hh:mm:ss"。区切り文字は1文字なら何でもいい。
214: // 入力値の範囲チェックはしない。
215: bool
216: RTCDevice::ParseTime(const std::string& str, int *h, int *m, int *s)
1.1 root 217: {
1.1.1.6 root 218: const char *p = str.data();
219: char *end;
1.1 root 220:
1.1.1.6 root 221: errno = 0;
222: *h = strtoul(p, &end, 10);
223: if (end == p || *end == '\0' || errno != 0) {
224: return false;
1.1 root 225: }
226:
1.1.1.6 root 227: p = end + 1;
228: errno = 0;
229: *m = strtoul(p, &end, 10);
230: if (end == p || *end == '\0' || errno != 0) {
231: return false;
232: }
1.1 root 233:
1.1.1.6 root 234: p = end + 1;
235: errno = 0;
236: *s = strtoul(p, &end, 10);
237: if (end == p || errno != 0) {
238: return false;
1.1 root 239: }
1.1.1.6 root 240:
241: return true;
1.1 root 242: }
243:
1.1.1.8 root 244: // イベントハンドラ
245: void
246: RTCDevice::Callback(Event& ev)
247: {
248: stime += period;
249: ClockIn();
250: if (sync_rt) {
1.1.1.9 root 251: scheduler->StartRealtimeEvent(ev, stime, period);
1.1.1.8 root 252: } else {
1.1.1.9 root 253: scheduler->StartEvent(ev);
1.1.1.8 root 254: }
255: }
256:
257: uint64
258: RTCDevice::ClockIn_PowerOff()
259: {
260: stime += period;
261:
262: ClockIn();
263:
264: // XXX RealTime ではなく HostTime?
1.1.1.9 root 265: uint64 rt = syncer->GetRealTime();
1.1.1.8 root 266: if (rt > stime) {
267: // RTC が現実より遅れているので早回し
268: return 0;
269: } else {
270: // clock-sync=virtual のときには stime が現実よりも多く進んで
271: // いるため、実時間との差をスケジューラが usleep で待つところで
272: // 長時間スリープしてしまう。
273: // たちまち、period * 2 で制限しておく。
274: uint64 dt = stime - rt;
275: if (dt > period * 2) {
276: dt = period * 2;
277: }
278: return dt;
279: }
280: }
281:
1.1.1.6 root 282: // 32Hz のクロック入力。
1.1.1.8 root 283: // イベントから呼び出される。
1.1.1.6 root 284: // 本来の RTC は 32768Hz が入力クロックだが、
285: // それを 1024 分周したものを基準クロック入力として考える。
286: void
287: RTCDevice::ClockIn()
1.1 root 288: {
1.1.1.6 root 289: cnt++;
1.1 root 290:
1.1.1.6 root 291: Tick32Hz();
292: if ((cnt & 15) == 0) {
293: Tick2Hz();
294: if ((cnt & 31) == 0) {
295: // 秒カウントアップは Tick1Hz が行う
296: // 秒カウントアップ禁止を実装するため、
297: // 基本クラスで直接秒カウントアップはしない。
298: Tick1Hz();
299: }
1.1 root 300: }
301: }
302:
1.1.1.8 root 303: // 32Hz で呼び出される関数。
304: void
305: RTCDevice::Tick32Hz()
306: {
307: }
308:
309: // 2Hz で呼び出される関数。
310: void
311: RTCDevice::Tick2Hz()
312: {
313: }
314:
315: // 1Hz で呼び出される関数。
316: void
317: RTCDevice::Tick1Hz()
318: {
319: }
320:
1.1.1.6 root 321: // 秒をカウントアップし、繰り上がりを処理。
322: void
323: RTCDevice::CountUpSec()
1.1 root 324: {
1.1.1.6 root 325: uint v;
1.1 root 326:
1.1.1.6 root 327: // BCD の桁ごとにキャリー処理をしてるはずだけど妥協。
328: // たぶん内部キャリーはビット途中のキャリーで作られていると
329: // 思うんだけど、ソフトでやるのは大変なので妥協。
330:
331: v = GetSec() + 1;
332: if (v < 60) {
333: SetSec(v);
334: return;
1.1 root 335: }
1.1.1.6 root 336: SetSec(0);
337: CountUpMin();
1.1 root 338: }
339:
1.1.1.6 root 340: // 分をカウントアップし、繰り上がりを処理。
341: void
342: RTCDevice::CountUpMin()
1.1 root 343: {
1.1.1.6 root 344: uint v;
1.1 root 345:
1.1.1.6 root 346: v = GetMin() + 1;
347: if (v < 60) {
348: SetMin(v);
349: return;
1.1 root 350: }
1.1.1.6 root 351: SetMin(0);
1.1 root 352:
1.1.1.6 root 353: v = GetHour() + 1;
354: if (v < 24) {
355: SetHour(v);
356: return;
357: }
358: SetHour(0);
359:
360: // 曜日は単純7進カウンタ
361: SetWday((GetWday() + 1) % 7);
1.1 root 362:
1.1.1.6 root 363: v = GetMday() + 1;
364: if (v <= GetLastMday()) {
365: SetMday(v);
366: return;
367: }
368: SetMday(1);
369:
370: v = GetMon() + 1;
371: if (v <= 12) {
372: SetMon(v);
373: return;
374: }
375: SetMon(1);
1.1 root 376:
1.1.1.6 root 377: SetLeap((GetLeap() + 1) % 4);
378: SetYear(GetYear() + 1);
1.1 root 379: }
380:
1.1.1.6 root 381: // 現在の月の最終日を返す (月の繰り上がり処理用)
1.1.1.10! root 382: uint
1.1.1.6 root 383: RTCDevice::GetLastMday() const
1.1 root 384: {
1.1.1.6 root 385: // 下記の理由により 0 月が必要。値は適当。
1.1.1.10! root 386: static const uint mdays[] = {
1.1.1.6 root 387: 30, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
388: };
389:
390: // GetMon() は不正な範囲を取ると規定していることに注意。
391: // (0月、19月などが許されている)
392: // 範囲外の値に対するロジックは違うだろうが規定されていないので
393: // こっち都合で実装しておく。
394: // m は 0..12
1.1.1.10! root 395: uint m = GetMon() % 13;
! 396: uint lastday = mdays[m];
1.1.1.6 root 397: if (m == 2 && GetLeap() == 0) {
398: lastday++;
1.1 root 399: }
1.1.1.6 root 400: return lastday;
1.1 root 401: }
402:
1.1.1.6 root 403: // 曜日文字列。
404: // RP5C15、MK48T02 いずれも曜日カウンタと曜日の対応はデータシートに規定がなく
405: // 運用依存であり、X680x0 (RP5C15) は 0..6 (0が日曜) としている。
406: // LUNA (MK48T02) は運用上 0..7(0と7が日曜) となってしまっている。
1.1 root 407: /*static*/ const char * const
1.1.1.6 root 408: RTCDevice::wdays[8] = {
1.1 root 409: "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat",
1.1.1.6 root 410: "Sun",
1.1 root 411: };
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.