Skip to content

Commit 229557c

Browse files
authored
fix(timekeeping): restore monotonic clock and clocksource semantics (#2145)
* fix(timekeeping): restore monotonic clock and clocksource semantics DragonOS used the realtime epoch as the basis for monotonic clocks and spread clocksource cycle state across multiple owners. The resulting updates could lose elapsed time, recurse through the timekeeper lock, and make timeout consumers depend on wall-clock adjustments. KVM clock CPU bring-up and interrupt-context locking also lacked transactional failure handling. Rework the timekeeping core around independently maintained monotonic and raw read bases, with realtime and boottime represented as offsets. Keep clocksource selection, watchdog state, switching, and rollback under a single control transaction, and preserve elapsed time and fractional state when a source changes. Make KVM clock setup per-CPU and transactional, including AP failure rollback. Add writer-preferred RwLock progress and explicit interrupt context tracking so timekeeper writers cannot deadlock behind continuous readers or acquire blocking paths from hardirq/softirq context. Align POSIX clock reads, nanosleep restart handling, signal waits, and futex timeouts with their Linux clock domains. Add kernel selftests, dunitest coverage, a guest calibration workload, and a fail-closed host calibration harness for KVM and TCG evidence. Validation: - x86_64 kernel build and link - RISC-V Rust build and kernel ELF link - 31 host calibration unit tests - guest calibration workload static build - QEMU runner and Python syntax checks - KVM 2-vCPU timekeeping/restart suite: 19/19 relevant tests passed - fixed-CPU and migrating 10,000,000-read monotonicity tests passed Signed-off-by: longjin <longjin@dragonos.org> * fix(timekeeping): address CI and review regressions Serialize watchdog cleanup with clocksource registration and fully roll back partially published watchdog state. Keep hardirq nesting counters cache-line isolated and avoid the CAS loop on interrupt entry and exit. Match Linux's settimeofday boundary after suspend, expose clocksource selftests through debugfs, and bound normal guest semantic loops while retaining the opt-in ten-million-read stress gates. Make QEMU argv evidence fail closed, release partially initialized serial transport resources, and resolve the formatting regressions reported by CI. Validated with make fmt, make kernel, host calibration tests, the calibration host build, and DragonOS guest timekeeping selftest and semantics suites. Signed-off-by: longjin <longjin@dragonos.org> * fix(timekeeping): preserve selftest execution state Restore preemption when an already-registered rwlock writer acquisition fails, and cover the failure path in the debugfs selftest. Consolidate all selftest-only registered writer attempts behind the balanced helper. Also preserve the underlying serial socket error in calibration diagnostics and verify both the reported detail and resource cleanup. Validated with format and Clippy checks, the kernel build, calibration unit and host tests, and DragonOS guest timekeeping selftests. Signed-off-by: longjin <longjin@dragonos.org> * fix(net): snapshot bound sockets before notification Network polling walked the bound-socket vector by index while dropping the lock around every notification. Concurrent socket teardown could remove an earlier entry, shift the vector, and make the next index skip a poll waiter entirely. Take one coherent Arc snapshot and reuse the shared notification path for regular polling, NAPI polling, and listener teardown. Notifications still run outside the bounds lock, preserving the required lock order, while a single read-side acquisition avoids writer-fair reacquisition stalls under close-heavy workloads. This prevents tcp_close_semantics poll timeouts during concurrent IPv4 and dual-stack reset/close stress. Signed-off-by: longjin <longjin@dragonos.org> * fix(process): serialize kernel stack allocation safely Kernel stack allocation and reclamation used try_lock_irqsave().unwrap() on their shared mapper lock. Concurrent fork and exit activity therefore converted ordinary lock contention into a kernel panic, as observed when tcp_socket_test created processes on both CPUs. Acquire KSTACK_LOCK with the blocking irqsave spin-lock operation in both paths. This preserves the existing mapper serialization and interrupt exclusion while allowing the contending CPU to wait for the current stack operation to finish. The gVisor tcp_socket_test now completes all 184 tests without triggering the allocation race. Signed-off-by: longjin <longjin@dragonos.org> * fix(kernel): avoid poll allocation and TTY lock recursion Make the network interface's bound-socket table copy-on-write. Poll and notification paths now clone only the outer Arc while interrupts are disabled, while the less frequent bind and unbind paths perform any required Vec clone. This preserves a coherent notification snapshot without O(n) IRQ-off allocation or the index-shift race fixed earlier. Use one by-value termios snapshot for each N_TTY receive batch and pass it through the nested input helpers. The previous receive path recursively acquired the same read lock; once a termios writer queued, writer-fair admission blocked the recursive reader and deadlocked both CPUs. A value snapshot preserves consistent per-batch input semantics and removes the recursive locking dependency. Validated with make kernel, all 27 CubeSandbox PTY exec-chain tests, and all 6 TCP close semantics tests. Signed-off-by: longjin <longjin@dragonos.org> * style(kernel): format N_TTY snapshot changes Apply the repository rustfmt output required by the architecture format-check jobs. No behavior changes. Signed-off-by: longjin <longjin@dragonos.org> * fix(x86_64): roll back failed HPET enablement Treat HPET enablement as a hardware transaction. If any validation, counter check, timer lookup, or IRQ registration step fails, restore the firmware general configuration instead of relying on the enabled flag, which is intentionally published only after IRQ setup succeeds. Snapshot and restore each timer comparator together with its configuration. Restore timer state while the counter is disabled, then write the complete firmware general configuration last so a firmware-enabled counter resumes only after all timer registers are coherent. Normal HPET disable now reuses the same restoration path. Validated with the repository format and Clippy checks, make kernel, and a DragonOS x86_64 fallback boot with HPET unavailable. Signed-off-by: longjin <longjin@dragonos.org> * fix(tty,time): eliminate PTY input hangs Use a persistent waiter notification for nanosleep timers, preserve deadline semantics across signal and restart races, and avoid recursive N_TTY termios, flow, and line-discipline wakeup locking during input processing. This fixes the ByteStream PTY deadlocks observed in CI while keeping absolute sleeps from returning before their POSIX clock deadline. Signed-off-by: longjin <longjin@dragonos.org> * fix(tty): satisfy clippy auto-deref lint Signed-off-by: longjin <longjin@dragonos.org> * fix(dunitest): synchronize reparent wait test Signed-off-by: longjin <longjin@dragonos.org> * fix(time): return EOPNOTSUPP for thread CPU nanosleep Signed-off-by: longjin <longjin@dragonos.org> --------- Signed-off-by: longjin <longjin@dragonos.org>
1 parent 45931ee commit 229557c

52 files changed

Lines changed: 7842 additions & 1439 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

kernel/src/arch/riscv64/interrupt/handle.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ fn try_fixup_kernel_user_access(trap_frame: &mut TrapFrame) -> bool {
5757
#[no_mangle]
5858
unsafe extern "C" fn riscv64_do_irq(trap_frame: &mut TrapFrame) {
5959
if trap_frame.cause.is_interrupt() {
60+
let hardirq_guard = crate::exception::enter_hardirq();
6061
crate::rcu::irq_enter();
6162
riscv64_do_interrupt(trap_frame);
6263
let irq_outermost = crate::rcu::irq_is_outermost();
@@ -69,6 +70,7 @@ unsafe extern "C" fn riscv64_do_irq(trap_frame: &mut TrapFrame) {
6970
let resume_idle_eqs = !should_schedule
7071
&& ProcessManager::current_pcb().sched_info().policy() == SchedPolicy::IDLE;
7172
let irq_exited_outermost = crate::rcu::irq_exit(resume_idle_eqs);
73+
drop(hardirq_guard);
7274

7375
if should_schedule && irq_exited_outermost {
7476
__schedule(SchedMode::SM_PREEMPT);

kernel/src/arch/x86_64/driver/hpet.rs

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ struct InnerHpet {
7979
timer_registers_ptr: NonNull<HpetTimerRegisters>,
8080
/// 定时器启动时配置
8181
timer_boot_cfg: Vec<u64>,
82+
/// 定时器启动时比较器值
83+
timer_boot_comparator: Vec<u64>,
8284
}
8385

8486
impl Hpet {
@@ -127,10 +129,12 @@ impl Hpet {
127129
.unwrap();
128130
// 记录hpet定时器启动时配置
129131
let mut timer_boot_cfg = Vec::with_capacity(tm_num as usize);
132+
let mut timer_boot_comparator = Vec::with_capacity(tm_num as usize);
130133
for i in 0..tm_num {
131134
let timer_reg = unsafe { timer_ptr.as_ptr().add(i).as_ref().unwrap() };
132135
let cfg = timer_reg.config();
133136
timer_boot_cfg.push(cfg);
137+
timer_boot_comparator.push(timer_reg.comparator_value());
134138
}
135139

136140
let hpet = Hpet {
@@ -140,6 +144,7 @@ impl Hpet {
140144
registers_ptr: ptr,
141145
timer_registers_ptr: timer_ptr,
142146
timer_boot_cfg,
147+
timer_boot_comparator,
143148
}),
144149
enabled: AtomicBool::new(false),
145150
boot_cfg,
@@ -156,6 +161,21 @@ impl Hpet {
156161
pub fn hpet_enable(&self) -> Result<(), SystemError> {
157162
let irq_guard = unsafe { CurrentIrqArch::save_and_disable_irq() };
158163

164+
let result = self.try_hpet_enable();
165+
if result.is_err() {
166+
// Hpet::new() disables the device, and try_hpet_enable() may also
167+
// restart the counter or program timer 0 before reporting an
168+
// error. Restore the firmware state even though `enabled` was
169+
// never published, so fallback clocks cannot inherit a partially
170+
// configured HPET.
171+
self.restore_boot_configuration();
172+
}
173+
174+
drop(irq_guard);
175+
return result;
176+
}
177+
178+
fn try_hpet_enable(&self) -> Result<(), SystemError> {
159179
// !!!这里是临时糊代码的,需要在apic重构的时候修改!!!
160180
let (inner_guard, regs) = unsafe { self.hpet_regs_mut() };
161181
let freq = regs.frequency();
@@ -204,8 +224,6 @@ impl Hpet {
204224
drop(inner_guard);
205225

206226
info!("HPET enabled");
207-
208-
drop(irq_guard);
209227
return Ok(());
210228
}
211229

@@ -350,13 +368,10 @@ impl Hpet {
350368
unsafe { regs.write_general_config(cfg) };
351369
}
352370

353-
/// 关闭hpet
354-
pub fn hpet_disable(&self) {
355-
debug!("HPET disable");
356-
if !is_hpet_enabled() {
357-
return;
358-
}
359-
371+
/// Restore every HPET register modified during initialization or enable.
372+
/// This intentionally does not consult `enabled`: failed enable attempts
373+
/// must be rolled back before that flag is set.
374+
fn restore_boot_configuration(&self) {
360375
// 恢复启动时的配置
361376
let mut cfg = self.boot_cfg;
362377
cfg &= !HPET_CFG_ENABLE;
@@ -368,18 +383,28 @@ impl Hpet {
368383
let (inner_guard, timer_reg) = unsafe { self.timer_mut(i).unwrap() };
369384
unsafe {
370385
timer_reg.write_config(inner_guard.timer_boot_cfg[i as usize]);
386+
timer_reg.write_comparator_value(inner_guard.timer_boot_comparator[i as usize]);
371387
}
372388
drop(inner_guard);
373389
}
374390

375-
// 如果HPET在启动时已经启用了,重新启用它
376-
if (self.boot_cfg & HPET_CFG_ENABLE) != 0 {
377-
let (_, regs) = unsafe { self.hpet_regs_mut() };
378-
unsafe { regs.write_general_config(self.boot_cfg) };
379-
}
391+
// Restore the complete firmware configuration last. This also
392+
// restarts a counter that firmware left enabled.
393+
let (_, regs) = unsafe { self.hpet_regs_mut() };
394+
unsafe { regs.write_general_config(self.boot_cfg) };
380395

381396
self.enabled.store(false, Ordering::SeqCst);
382397
}
398+
399+
/// 关闭hpet
400+
pub fn hpet_disable(&self) {
401+
debug!("HPET disable");
402+
if !is_hpet_enabled() {
403+
return;
404+
}
405+
406+
self.restore_boot_configuration();
407+
}
383408
}
384409

385410
pub fn hpet_init() -> Result<(), SystemError> {

kernel/src/arch/x86_64/init/mod.rs

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use core::{
33
sync::atomic::{compiler_fence, Ordering},
44
};
55

6-
use log::debug;
6+
use log::{debug, warn};
77
use system_error::SystemError;
88
use x86::dtables::DescriptorTablePointer;
99

@@ -115,13 +115,45 @@ pub fn setup_arch_post() -> Result<(), SystemError> {
115115
// 3.如果 kvm-clock 和 HPET 都不可用,则回退到 ACPI PM Timer
116116
// 4.最后初始化 TSC 管理器 (既可以通过 kvm-clock 提供的 pvclock 确定 TSC 频率,也可以通过其他方法确定 TSC 频率)
117117
let kvmclock_ok = kvm_clock::kvmclock_init();
118-
let ret = hpet_init();
119-
if ret.is_ok() {
120-
hpet_instance().hpet_enable().expect("hpet enable failed");
121-
} else if !kvmclock_ok {
122-
init_acpi_pm_clocksource().expect("acpi_pm_timer inits failed");
118+
let hpet_ok = match hpet_init() {
119+
Ok(()) => match hpet_instance().hpet_enable() {
120+
Ok(_) => true,
121+
Err(error) => {
122+
warn!(
123+
"HPET enable failed, retaining fallback clocksource: {:?}",
124+
error
125+
);
126+
false
127+
}
128+
},
129+
Err(error) => {
130+
warn!(
131+
"HPET init failed, retaining fallback clocksource: {:?}",
132+
error
133+
);
134+
false
135+
}
136+
};
137+
if !kvmclock_ok && !hpet_ok {
138+
if let Err(error) = init_acpi_pm_clocksource() {
139+
// jiffies is already registered and installed by timekeeping_init.
140+
warn!(
141+
"ACPI PM timer unavailable, continuing with jiffies: {:?}",
142+
error
143+
);
144+
}
145+
}
146+
if let Err(error) = TSCManager::init() {
147+
if TSCManager::cpu_khz() == 0 {
148+
// The scheduler clock and APIC timer still require CPU_KHZ even
149+
// when POSIX timekeeping can remain on jiffies.
150+
return Err(error);
151+
}
152+
warn!(
153+
"TSC clocksource unavailable, continuing with registered clocksource: {:?}",
154+
error
155+
);
123156
}
124-
TSCManager::init().expect("tsc init failed");
125157

126158
return Ok(());
127159
}

kernel/src/arch/x86_64/interrupt/handle.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ unsafe extern "C" fn x86_64_do_irq(trap_frame: &mut TrapFrame, vector: u32) {
1717
x86_64::registers::segmentation::GS::swap();
1818
}
1919

20+
let hardirq_guard = crate::exception::enter_hardirq();
2021
crate::rcu::irq_enter();
2122

2223
// 由于x86上面,虚拟中断号与物理中断号是一一对应的,所以这里直接使用vector作为中断号来查询irqdesc
@@ -46,6 +47,7 @@ unsafe extern "C" fn x86_64_do_irq(trap_frame: &mut TrapFrame, vector: u32) {
4647
let resume_idle_eqs = !should_schedule
4748
&& ProcessManager::current_pcb().sched_info().policy() == SchedPolicy::IDLE;
4849
let irq_exited_outermost = crate::rcu::irq_exit(resume_idle_eqs);
50+
drop(hardirq_guard);
4951

5052
if should_schedule && irq_exited_outermost {
5153
__schedule(SchedMode::SM_PREEMPT);

kernel/src/debug/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@ pub mod page_cache;
1010
pub mod panic;
1111
pub mod rcu;
1212
pub mod sysfs;
13+
pub mod timekeeping;
1314
pub mod traceback;
1415
pub mod tracing;

kernel/src/debug/sysfs/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ fn debugfs_init() -> Result<(), SystemError> {
2828
super::fuse::init_debugfs_fuse()?;
2929
super::kthread::init_debugfs_kthread()?;
3030
super::page_cache::init_debugfs_page_cache()?;
31+
super::timekeeping::init_debugfs_timekeeping()?;
3132
return Ok(());
3233
}
3334

kernel/src/debug/timekeeping.rs

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
use alloc::{format, string::String, string::ToString};
2+
3+
use system_error::SystemError;
4+
5+
use crate::{
6+
debug::sysfs::debugfs_kobj,
7+
driver::base::kobject::KObject,
8+
filesystem::{
9+
kernfs::callback::{KernCallbackData, KernFSCallback, KernFilePrivateData},
10+
vfs::{InodeMode, PollStatus},
11+
},
12+
libs::rwlock::run_rwlock_selftests,
13+
time::{clocksource::run_clocksource_selftests, timekeeping::run_timekeeping_selftests},
14+
};
15+
16+
#[cfg(target_arch = "x86_64")]
17+
use crate::driver::clocksource::kvm_clock::run_kvm_clock_allocator_selftests;
18+
19+
#[derive(Debug)]
20+
struct TimekeepingDirCallback;
21+
22+
impl KernFSCallback for TimekeepingDirCallback {
23+
fn open(&self, _data: KernCallbackData) -> Result<(), SystemError> {
24+
Ok(())
25+
}
26+
27+
fn read(
28+
&self,
29+
_data: KernCallbackData,
30+
_buf: &mut [u8],
31+
_offset: usize,
32+
) -> Result<usize, SystemError> {
33+
Err(SystemError::EISDIR)
34+
}
35+
36+
fn write(
37+
&self,
38+
_data: KernCallbackData,
39+
_buf: &[u8],
40+
_offset: usize,
41+
) -> Result<usize, SystemError> {
42+
Err(SystemError::EISDIR)
43+
}
44+
45+
fn poll(&self, _data: KernCallbackData) -> Result<PollStatus, SystemError> {
46+
Err(SystemError::EISDIR)
47+
}
48+
}
49+
50+
#[derive(Debug)]
51+
struct TimekeepingSelftestCallback;
52+
53+
impl KernFSCallback for TimekeepingSelftestCallback {
54+
fn open(&self, mut data: KernCallbackData) -> Result<(), SystemError> {
55+
data.file_private_data_mut()
56+
.replace(KernFilePrivateData::DebugTextSnapshot(run_selftests()));
57+
Ok(())
58+
}
59+
60+
fn read(
61+
&self,
62+
data: KernCallbackData,
63+
buf: &mut [u8],
64+
offset: usize,
65+
) -> Result<usize, SystemError> {
66+
let report = match data.file_private_data() {
67+
Some(KernFilePrivateData::DebugTextSnapshot(report)) => report,
68+
_ => return Err(SystemError::EINVAL),
69+
};
70+
let bytes = report.as_bytes();
71+
if offset >= bytes.len() {
72+
return Ok(0);
73+
}
74+
let len = buf.len().min(bytes.len() - offset);
75+
buf[..len].copy_from_slice(&bytes[offset..offset + len]);
76+
Ok(len)
77+
}
78+
79+
fn write(
80+
&self,
81+
_data: KernCallbackData,
82+
_buf: &[u8],
83+
_offset: usize,
84+
) -> Result<usize, SystemError> {
85+
Err(SystemError::EPERM)
86+
}
87+
88+
fn poll(&self, _data: KernCallbackData) -> Result<PollStatus, SystemError> {
89+
Ok(PollStatus::READ)
90+
}
91+
}
92+
93+
fn run_selftests() -> String {
94+
let (timekeeping_passed, timekeeping_failed, timekeeping_body) = run_timekeeping_selftests();
95+
let (clocksource_passed, clocksource_failed, clocksource_body) = run_clocksource_selftests();
96+
let (rwlock_passed, rwlock_failed, rwlock_body) = run_rwlock_selftests();
97+
#[cfg(target_arch = "x86_64")]
98+
let (kvm_passed, kvm_failed, kvm_body) = run_kvm_clock_allocator_selftests();
99+
#[cfg(not(target_arch = "x86_64"))]
100+
let (kvm_passed, kvm_failed, kvm_body) = (0, 0, String::new());
101+
let passed = timekeeping_passed + clocksource_passed + rwlock_passed + kvm_passed;
102+
let failed = timekeeping_failed + clocksource_failed + rwlock_failed + kvm_failed;
103+
let status = if failed == 0 { "ok" } else { "fail" };
104+
format!(
105+
"status={status}\n{timekeeping_body}{clocksource_body}{rwlock_body}{kvm_body}summary_pass={passed}\nsummary_fail={failed}\n"
106+
)
107+
}
108+
109+
pub fn init_debugfs_timekeeping() -> Result<(), SystemError> {
110+
let debugfs = debugfs_kobj();
111+
let root_dir = debugfs.inode().ok_or(SystemError::ENOENT)?;
112+
let timekeeping_root = root_dir.add_dir(
113+
"timekeeping".to_string(),
114+
InodeMode::from_bits_truncate(0o500),
115+
None,
116+
Some(&TimekeepingDirCallback),
117+
)?;
118+
timekeeping_root.add_file(
119+
"selftest".to_string(),
120+
InodeMode::S_IRUSR,
121+
Some(16 * 1024),
122+
None,
123+
Some(&TimekeepingSelftestCallback),
124+
)?;
125+
Ok(())
126+
}

kernel/src/driver/clocksource/acpi_pm.rs

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ use crate::{
77
},
88
libs::spinlock::SpinLock,
99
time::{
10-
clocksource::{Clocksource, ClocksourceData, ClocksourceFlags, ClocksourceMask, CycleNum},
10+
clocksource::{
11+
Clocksource, ClocksourceData, ClocksourceFlags, ClocksourceMask, ClocksourceUpdate,
12+
CycleNum,
13+
},
1114
PIT_TICK_RATE,
1215
},
1316
};
@@ -85,13 +88,13 @@ impl Acpipm {
8588
mask: ClocksourceMask::new(ACPI_PM_MASK),
8689
mult: 0,
8790
shift: 0,
91+
max_cycles: Default::default(),
8892
max_idle_ns: Default::default(),
8993
flags: ClocksourceFlags::CLOCK_SOURCE_IS_CONTINUOUS,
9094
watchdog_last: CycleNum::new(0),
9195
cs_last: CycleNum::new(0),
9296
uncertainty_margin: 0,
9397
maxadj: 0,
94-
cycle_last: CycleNum::new(0),
9598
};
9699
let acpi_pm = Arc::new(Acpipm(SpinLock::new(InnerAcpipm {
97100
data,
@@ -117,20 +120,8 @@ impl Clocksource for Acpipm {
117120
return self.0.lock_irqsave().self_reaf.upgrade().unwrap();
118121
}
119122

120-
fn update_clocksource_data(&self, data: ClocksourceData) -> Result<(), SystemError> {
121-
let d = &mut self.0.lock_irqsave().data;
122-
d.set_name(data.name);
123-
d.set_rating(data.rating);
124-
d.set_mask(data.mask);
125-
d.set_mult(data.mult);
126-
d.set_shift(data.shift);
127-
d.set_max_idle_ns(data.max_idle_ns);
128-
d.set_flags(data.flags);
129-
d.watchdog_last = data.watchdog_last;
130-
d.cs_last = data.cs_last;
131-
d.set_uncertainty_margin(data.uncertainty_margin);
132-
d.set_maxadj(data.maxadj);
133-
d.cycle_last = data.cycle_last;
123+
fn update_clocksource_data(&self, update: ClocksourceUpdate) -> Result<(), SystemError> {
124+
update.apply(&mut self.0.lock_irqsave().data);
134125
return Ok(());
135126
}
136127
}

0 commit comments

Comments
 (0)