Skip to main content

atmos/kernel/
timer.rs

1// timer.rs - ARM Generic Timer (Non-Secure Physical Timer CNTP) Driver
2#![allow(dead_code)]
3use alloc::string::String;
4use core::sync::atomic::{AtomicUsize, Ordering};
5
6// `init()`/`init_secondary()` は全4コアそれぞれの CNTP ローカルタイマーを
7// 個別に有効化し、各コアの `rust_irq_handler` が自コアのタイマー割り込み
8// ごとに `handle_timer_irq()` を呼ぶ(`interrupt.rs` の `int_src` チェックに
9// コア限定は無い)。そのため `SYSTEM_TICKS` は最大4コアから並行に
10// インクリメントされ得る。かつては非アトミックな `static mut usize` の
11// `+= 1` だったため、read-modify-write の競合で更新ロストが起こり得た
12// (2026-07-14 発見。`spec/TODO.md`「デバッグ・検証候補」参照。
13// `AtomicUsize` 化はこの更新ロストのみを解消するもので、下記の
14// レート問題とは別)。
15//
16// **レート問題(2026-07-14 に追加修正)**: 4コアそれぞれが独立に約10ms
17// 周期でカウントアップすると、消費側(`layout.rs` の CSS アニメーション
18// 時計・`main.rs` の `ticks==500`/sysmon 間隔判定等)が前提とする
19// 「1 tick = 10ms」という設計が崩れ、実質最大4倍速で進んでしまう。
20// `handle_timer_irq()` 側で `Core 0` のみがインクリメントするよう
21// ガードして解消した。`watchdog::check_from_irq()`
22// (`handle_timer_irq()` から全コアで無条件に呼ばれる)はこの
23// `SYSTEM_TICKS` を一切参照せず `get_system_time_ms()`(コア数に
24// 非依存のカウンタレジスタ直読み)のみを使うため、このガードによる
25// 影響は無いことをコードで確認済み。
26static SYSTEM_TICKS: AtomicUsize = AtomicUsize::new(0);
27static mut TIMER_PERIOD_TICKS: u64 = 0;
28
29// --- 壁時計(NTP で同期した実時刻)---
30// NTP 同期時の Unix 時刻(秒, UTC)と、その時点のシステム稼働ミリ秒を保持する。
31// `set_wall_clock()` の書き手は `net_service_thread`(Core 2 固定、NTP 同期
32// 契機)と Aura の日時設定コマンド(`os_lib/aura/builtins.rs`、Core 0 固定の
33// `gui_shell_process` から)の2箇所存在し、読み手(`get_unix_time()` 等、
34// UI の時計表示等)は Core 0 から呼ばれる。3変数(Unix秒/基準ms/設定済み
35// フラグ)を個別の `static mut` として非アトミックに更新していたため、
36// Core 2 が書き込み中に Core 0 が読むと三つ組が不整合な状態
37// (例: `WALL_SET=true` が先に見えるが `WALL_BASE_MS` はまだ更新前)で
38// 読める可能性があった(2026-07-14 発見。`spec/TODO.md`「デバッグ・
39// 検証候補」節の `static mut` SMP 横断監査で確認)。3値をまとめて
40// `spin::Mutex` で保護し、読み書きの原子性を保証する。
41struct WallClock {
42    base_unix: u64,
43    base_ms: u64,
44    set: bool,
45}
46static WALL_CLOCK: spin::Mutex<WallClock> = spin::Mutex::new(WallClock {
47    base_unix: 0,
48    base_ms: 0,
49    set: false,
50});
51
52/// JST のタイムゾーンオフセット(秒)
53const JST_OFFSET_SECS: u64 = 9 * 3600;
54/// NTP epoch (1900-01-01) と Unix epoch (1970-01-01) の差(秒)
55pub const NTP_UNIX_OFFSET: u64 = 2_208_988_800;
56
57/// ARM Generic Timer を 10ms 間隔で初期化します
58pub fn init() {
59    unsafe {
60        let freq: u64;
61        core::arch::asm!("mrs {}, cntfrq_el0", out(reg) freq);
62        TIMER_PERIOD_TICKS = freq / 100;
63        core::arch::asm!("msr cntp_tval_el0, {}", in(reg) TIMER_PERIOD_TICKS);
64        let ctrl: u64 = 1;
65        core::arch::asm!("msr cntp_ctl_el0, {}", in(reg) ctrl);
66    }
67    SYSTEM_TICKS.store(0, Ordering::Relaxed);
68    crate::debug!("[TIMER] ARM Generic Timer enabled at 10ms intervals");
69}
70
71pub fn init_secondary() {
72    unsafe {
73        core::arch::asm!("msr cntp_tval_el0, {}", in(reg) TIMER_PERIOD_TICKS);
74        let ctrl: u64 = 1;
75        core::arch::asm!("msr cntp_ctl_el0, {}", in(reg) ctrl);
76    }
77}
78
79/// タイマー割り込みハンドラから呼び出され、次の 10ms の割り込みを設定します
80pub fn handle_timer_irq() {
81    unsafe {
82        // 次の 10ms のカウントを設定
83        core::arch::asm!("msr cntp_tval_el0, {}", in(reg) TIMER_PERIOD_TICKS);
84    }
85
86    // `SYSTEM_TICKS` は「1 tick ≈ 10ms」という消費側の前提を保つため、
87    // 常時稼働する Core 0 のローカルタイマー割り込みのみでカウントする
88    // (他コアの割り込みでも進めると最大4倍速になってしまうレート問題の
89    // 対策。上のコメント参照)。
90    if crate::kernel::scheduler::core_id() == 0 {
91        SYSTEM_TICKS.fetch_add(1, Ordering::Relaxed);
92    }
93
94    // アプリ描画ウォッチドッグ: メインスレッドが draw に張り付いていないか確認(ハング検知)
95    // `SYSTEM_TICKS` を参照しないため上記のコアガードとは無関係に全コアで実行する。
96    crate::kernel::watchdog::check_from_irq();
97}
98
99/// 起動時からのシステムティック(10ms単位)を取得します
100pub fn get_ticks() -> usize {
101    SYSTEM_TICKS.load(Ordering::Relaxed)
102}
103
104/// システムカウンターから現在時刻をマイクロ秒単位で取得します (u128でオーバーフロー防止)
105pub fn get_system_time_us() -> u64 {
106    let count: u64;
107    let freq: u64;
108    unsafe {
109        core::arch::asm!("mrs {}, cntpct_el0", out(reg) count);
110        core::arch::asm!("mrs {}, cntfrq_el0", out(reg) freq);
111    }
112    ((count as u128 * 1_000_000) / freq as u128) as u64
113}
114
115/// システムカウンターから現在時刻をミリ秒単位で取得します
116pub fn get_system_time_ms() -> u64 {
117    get_system_time_us() / 1000
118}
119
120// ---------------------------------------------------------------------------
121// 壁時計(NTP 同期した実時刻)
122// ---------------------------------------------------------------------------
123
124/// NTP 等で取得した Unix 時刻(UTC 秒)を基準として登録する。
125pub fn set_wall_clock(unix_secs: u64) {
126    let now_ms = get_system_time_ms();
127    let mut w = WALL_CLOCK.lock();
128    w.base_unix = unix_secs;
129    w.base_ms = now_ms;
130    w.set = true;
131}
132
133/// 壁時計が同期済みかどうか
134pub fn is_wall_clock_set() -> bool {
135    WALL_CLOCK.lock().set
136}
137
138/// 現在の Unix 時刻(UTC 秒)を返す。未同期なら None。
139pub fn get_unix_time() -> Option<u64> {
140    let w = WALL_CLOCK.lock();
141    if !w.set {
142        return None;
143    }
144    let elapsed_ms = get_system_time_ms().wrapping_sub(w.base_ms);
145    Some(w.base_unix + elapsed_ms / 1000)
146}
147
148/// 日数(1970-01-01 からの経過日数)を (年, 月, 日) に変換する。
149/// Howard Hinnant の civil_from_days アルゴリズム。
150fn civil_from_days(z: i64) -> (i64, u32, u32) {
151    let z = z + 719468;
152    let era = (if z >= 0 { z } else { z - 146096 }) / 146097;
153    let doe = z - era * 146097; // [0, 146096]
154    let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; // [0, 399]
155    let y = yoe + era * 400;
156    let doy = doe - (365 * yoe + yoe / 4 - yoe / 100); // [0, 365]
157    let mp = (5 * doy + 2) / 153; // [0, 11]
158    let d = (doy - (153 * mp + 2) / 5 + 1) as u32; // [1, 31]
159    let m = (if mp < 10 { mp + 3 } else { mp - 9 }) as u32; // [1, 12]
160    (if m <= 2 { y + 1 } else { y }, m, d)
161}
162
163/// 現在の JST 日時を (年, 月, 日, 時, 分, 秒) で返す。未同期なら None。
164pub fn get_datetime_jst() -> Option<(i64, u32, u32, u32, u32, u32)> {
165    let unix = get_unix_time()?;
166    let local = unix + JST_OFFSET_SECS;
167    let days = (local / 86400) as i64;
168    let secs_of_day = local % 86400;
169    let (y, mo, d) = civil_from_days(days);
170    let hh = (secs_of_day / 3600) as u32;
171    let mm = ((secs_of_day % 3600) / 60) as u32;
172    let ss = (secs_of_day % 60) as u32;
173    Some((y, mo, d, hh, mm, ss))
174}
175
176/// 現在の JST 日時を "YYYY-MM-DD  HH:mm" 形式で返す。未同期なら None。
177pub fn format_datetime_jst() -> Option<String> {
178    let (y, mo, d, hh, mm, _ss) = get_datetime_jst()?;
179    Some(alloc::format!(
180        "{:04}-{:02}-{:02}  {:02}:{:02}",
181        y,
182        mo,
183        d,
184        hh,
185        mm
186    ))
187}
188
189/// 現在の JST 時刻を "HH:MM:SS" で返す(コマンド実行時刻の記録用)。未同期なら None。
190pub fn format_time_hms_jst() -> Option<String> {
191    let (_y, _mo, _d, hh, mm, ss) = get_datetime_jst()?;
192    Some(alloc::format!("{:02}:{:02}:{:02}", hh, mm, ss))
193}