xref: /DragonOS/kernel/src/arch/x86_64/driver/tsc.rs (revision 1df85daf8f1b4426fe09d489d815997cdf989a87)
1 use crate::{
2     arch::{io::PortIOArch, CurrentIrqArch, CurrentPortIOArch, CurrentTimeArch},
3     driver::acpi::pmtmr::{acpi_pm_read_early, ACPI_PM_OVERRUN, PMTMR_TICKS_PER_SEC},
4     exception::InterruptArch,
5     kdebug, kerror, kinfo, kwarn,
6     time::{TimeArch, PIT_TICK_RATE},
7 };
8 use core::{
9     cmp::{max, min},
10     intrinsics::unlikely,
11 };
12 use system_error::SystemError;
13 
14 use super::hpet::{hpet_instance, is_hpet_enabled};
15 
16 #[derive(Debug)]
17 pub struct TSCManager;
18 
19 static mut TSC_KHZ: u64 = 0;
20 static mut CPU_KHZ: u64 = 0;
21 
22 impl TSCManager {
23     const DEFAULT_THRESHOLD: u64 = 0x20000;
24 
25     /// 初始化TSC
26     ///
27     /// 目前由于未支持acpi pm timer, 因此调用该函数时,HPET应当完成初始化,否则将无法校准TSC
28     ///
29     /// 参考 https://code.dragonos.org.cn/xref/linux-6.1.9/arch/x86/kernel/tsc.c#1511
30     pub fn init() -> Result<(), SystemError> {
31         let cpuid = x86::cpuid::CpuId::new();
32         let feat = cpuid.get_feature_info().ok_or(SystemError::ENODEV)?;
33         if !feat.has_tsc() {
34             kerror!("TSC is not available");
35             return Err(SystemError::ENODEV);
36         }
37 
38         if unsafe { TSC_KHZ == 0 } {
39             if let Err(e) = Self::determine_cpu_tsc_frequency(false) {
40                 kerror!("Failed to determine CPU TSC frequency: {:?}", e);
41                 // todo: mark TSC as unstable clock source
42                 return Err(e);
43             }
44         }
45 
46         // todo: register TSC as clock source and deal with unstable clock source
47 
48         return Ok(());
49     }
50 
51     /// 获取TSC和CPU总线的频率
52     ///
53     /// ## 参数
54     ///
55     /// - `early`:是否在早期初始化
56     ///
57     /// 参考 https://code.dragonos.org.cn/xref/linux-6.1.9/arch/x86/kernel/tsc.c#1438
58     fn determine_cpu_tsc_frequency(early: bool) -> Result<(), SystemError> {
59         if unlikely(Self::cpu_khz() != 0 || Self::tsc_khz() != 0) {
60             kwarn!("TSC and CPU frequency already determined");
61         }
62 
63         if early {
64             // todo: 先根据cpuid或者读取msr或者pit来测量TSC和CPU总线的频率
65             todo!("detect TSC and CPU frequency by cpuid or msr or pit");
66         } else {
67             // 使用pit来测量TSC和CPU总线的频率
68             Self::set_cpu_khz(Self::calibrate_cpu_by_pit_hpet_ptimer()?);
69         }
70 
71         // 认为非0的TSC频率是可靠的,并且使用它来检查CPU总线的频率
72         if Self::tsc_khz() == 0 {
73             Self::set_tsc_khz(Self::cpu_khz());
74         } else if (Self::cpu_khz() as i64 - Self::tsc_khz() as i64).abs() * 10
75             > Self::cpu_khz() as i64
76         {
77             // 如果TSC和CPU总线的频率相差太大,那么认为CPU总线的频率是不可靠的,使用TSC的频率
78             Self::set_cpu_khz(Self::tsc_khz());
79         }
80 
81         if Self::cpu_khz() == 0 {
82             kerror!("Failed to determine CPU frequency");
83             return Err(SystemError::ENODEV);
84         }
85 
86         kinfo!(
87             "Detected {}.{} MHz processor",
88             Self::cpu_khz() / 1000,
89             Self::cpu_khz() % 1000
90         );
91         kinfo!(
92             "Detected {}.{} MHz TSC",
93             Self::tsc_khz() / 1000,
94             Self::tsc_khz() % 1000
95         );
96 
97         return Ok(());
98     }
99 
100     /// 测量CPU总线的频率
101     ///
102     /// 使用pit、hpet、ptimer来测量CPU总线的频率
103     fn calibrate_cpu_by_pit_hpet_ptimer() -> Result<u64, SystemError> {
104         let hpet = is_hpet_enabled();
105         kdebug!(
106             "Calibrating TSC with {}",
107             if hpet { "HPET" } else { "PMTIMER" }
108         );
109 
110         let mut tsc_pit_min = u64::MAX;
111         let mut tsc_ref_min = u64::MAX;
112 
113         // 默认的校准参数
114         let cal_ms = 10;
115         let cal_latch = PIT_TICK_RATE / (1000 / cal_ms);
116         let cal_pit_loops = 1000;
117 
118         // 如果第一轮校准失败,那么使用这些参数(因为虚拟化平台的问题,第一轮校准可能失败)
119         let cal2_ms = 50;
120         let cal2_latch = PIT_TICK_RATE / (1000 / cal2_ms);
121         let cal2_pit_loops = 5000;
122 
123         let mut latch = cal_latch;
124         let mut loopmin = cal_pit_loops;
125         let mut ms = cal_ms;
126 
127         let mut global_ref1 = 0;
128         let mut global_ref2 = 0;
129 
130         for i in 0..3 {
131             let irq_guard = unsafe { CurrentIrqArch::save_and_disable_irq() };
132 
133             let (tsc1, ref1) = Self::read_refs(hpet);
134             let tsc_pit_khz = Self::pit_calibrate_tsc(latch, ms, loopmin).unwrap_or(u64::MAX);
135             let (tsc2, ref2) = Self::read_refs(hpet);
136             drop(irq_guard);
137 
138             global_ref1 = ref1;
139             global_ref2 = ref2;
140 
141             // 选用最小的tsc_pit_khz
142             tsc_pit_min = min(tsc_pit_min, tsc_pit_khz);
143 
144             // HPET或者PTIMER可能是不可用的
145             if ref1 == ref2 {
146                 kdebug!("HPET/PMTIMER not available");
147                 continue;
148             }
149 
150             // 检查采样是否被打断
151             if tsc1 == u64::MAX || tsc2 == u64::MAX {
152                 continue;
153             }
154 
155             let mut tsc2 = (tsc2 - tsc1) * 1000000;
156 
157             if hpet {
158                 tsc2 = Self::calc_hpet_ref(tsc2, ref1, ref2);
159             } else {
160                 tsc2 = Self::calc_pmtimer_ref(tsc2, ref1, ref2);
161             }
162 
163             tsc_ref_min = min(tsc_ref_min, tsc2);
164 
165             // 检查与参考值的误差
166             let mut delta = tsc_pit_min * 100;
167             delta /= tsc_ref_min;
168 
169             // 如果误差在10%以内,那么认为测量成功
170             // 返回参考值,因为它是更精确的
171             if (90..=110).contains(&delta) {
172                 kinfo!(
173                     "PIT calibration matches {}. {} loops",
174                     if hpet { "HPET" } else { "PMTIMER" },
175                     i + 1
176                 );
177                 return Ok(tsc_ref_min);
178             }
179 
180             if i == 1 && tsc_pit_min == u64::MAX {
181                 latch = cal2_latch;
182                 ms = cal2_ms;
183                 loopmin = cal2_pit_loops;
184             }
185         }
186 
187         if tsc_pit_min == u64::MAX {
188             kwarn!("Unable to calibrate against PIT");
189 
190             // 如果没有参考值,那么禁用tsc
191             if (!hpet) && (global_ref1 == 0) && (global_ref2 == 0) {
192                 kwarn!("No reference (HPET/PMTIMER) available");
193                 return Err(SystemError::ENODEV);
194             }
195 
196             if tsc_ref_min == u64::MAX {
197                 kwarn!("Unable to calibrate against HPET/PMTIMER");
198                 return Err(SystemError::ENODEV);
199             }
200 
201             kinfo!(
202                 "Using {} reference calibration",
203                 if hpet { "HPET" } else { "PMTIMER" }
204             );
205             return Ok(tsc_ref_min);
206         }
207 
208         // We don't have an alternative source, use the PIT calibration value
209         if (!hpet) && (global_ref1 == 0) && (global_ref2 == 0) {
210             kinfo!("Using PIT calibration value");
211             return Ok(tsc_pit_min);
212         }
213 
214         // The alternative source failed, use the PIT calibration value
215         if tsc_ref_min == u64::MAX {
216             kwarn!("Unable to calibrate against HPET/PMTIMER, using PIT calibration value");
217             return Ok(tsc_pit_min);
218         }
219 
220         // The calibration values differ too much. In doubt, we use
221         // the PIT value as we know that there are PMTIMERs around
222         // running at double speed. At least we let the user know:
223         kwarn!(
224             "PIT calibration deviates from {}: tsc_pit_min={}, tsc_ref_min={}",
225             if hpet { "HPET" } else { "PMTIMER" },
226             tsc_pit_min,
227             tsc_ref_min
228         );
229 
230         kinfo!("Using PIT calibration value");
231         return Ok(tsc_pit_min);
232     }
233 
234     /// 尝试使用PIT来校准tsc时间,并且返回tsc的频率(khz)。
235     /// 如果失败,那么返回None
236     ///
237     /// 参考 https://code.dragonos.org.cn/xref/linux-6.1.9/arch/x86/kernel/tsc.c#389
238     fn pit_calibrate_tsc(latch: u64, ms: u64, loopmin: u64) -> Option<u64> {
239         // 当前暂时没写legacy pic的驱动,因此这里直接返回
240         let has_legacy_pic = false;
241         if !has_legacy_pic {
242             let mut cnt = 10000;
243             while cnt > 0 {
244                 cnt -= 1;
245             }
246 
247             return None;
248         }
249 
250         unsafe {
251             // Set the Gate high, disable speaker
252             let d = (CurrentPortIOArch::in8(0x61) & (!0x02)) | 0x01;
253             CurrentPortIOArch::out8(0x61, d);
254 
255             // Setup CTC channel 2* for mode 0, (interrupt on terminal
256             // count mode), binary count. Set the latch register to 50ms
257             // (LSB then MSB) to begin countdown.
258             CurrentPortIOArch::out8(0x43, 0xb0);
259             CurrentPortIOArch::out8(0x42, (latch & 0xff) as u8);
260             CurrentPortIOArch::out8(0x42, ((latch >> 8) & 0xff) as u8);
261         }
262 
263         let mut tsc = CurrentTimeArch::get_cycles() as u64;
264         let t1 = tsc;
265         let mut t2 = tsc;
266         let mut pitcnt = 0u64;
267         let mut tscmax = 0u64;
268         let mut tscmin = u64::MAX;
269         while unsafe { (CurrentPortIOArch::in8(0x61) & 0x20) == 0 } {
270             t2 = CurrentTimeArch::get_cycles() as u64;
271             let delta = t2 - tsc;
272             tsc = t2;
273 
274             tscmin = min(tscmin, delta);
275             tscmax = max(tscmax, delta);
276 
277             pitcnt += 1;
278         }
279 
280         // Sanity checks:
281         //
282         // If we were not able to read the PIT more than loopmin
283         // times, then we have been hit by a massive SMI
284         //
285         // If the maximum is 10 times larger than the minimum,
286         // then we got hit by an SMI as well.
287         if pitcnt < loopmin || tscmax > 10 * tscmin {
288             return None;
289         }
290 
291         let mut delta = t2 - t1;
292         delta /= ms;
293 
294         return Some(delta);
295     }
296 
297     /// 读取tsc和参考值
298     ///
299     /// ## 参数
300     ///
301     /// - `hpet_enabled`:是否启用hpet
302     ///
303     /// ## 返回
304     ///
305     /// - `Ok((tsc, ref))`:tsc和参考值
306     ///
307     /// 参考 https://code.dragonos.org.cn/xref/linux-6.1.9/arch/x86/kernel/tsc.c#317
308     fn read_refs(hpet_enabled: bool) -> (u64, u64) {
309         let thresh = if Self::tsc_khz() == 0 {
310             Self::DEFAULT_THRESHOLD
311         } else {
312             Self::tsc_khz() >> 5
313         };
314 
315         let mut ref_ret = 0;
316         for _ in 0..5 {
317             let t1 = CurrentTimeArch::get_cycles() as u64;
318             if hpet_enabled {
319                 ref_ret = hpet_instance().main_counter_value();
320             } else {
321                 ref_ret = acpi_pm_read_early() as u64;
322             }
323             let t2 = CurrentTimeArch::get_cycles() as u64;
324             if (t2 - t1) < thresh {
325                 return (t2, ref_ret);
326             }
327         }
328 
329         kwarn!("TSCManager: Failed to read reference value, tsc delta too high");
330         return (u64::MAX, ref_ret);
331     }
332 
333     /// 根据HPET的参考值计算tsc的频率
334     ///
335     /// https://code.dragonos.org.cn/xref/linux-6.1.9/arch/x86/kernel/tsc.c#339
336     fn calc_hpet_ref(mut deltatsc: u64, ref1: u64, mut ref2: u64) -> u64 {
337         if ref2 <= ref1 {
338             ref2 += 0x100000000;
339         }
340 
341         ref2 -= ref1;
342         let mut tmp = ref2 * hpet_instance().period();
343 
344         tmp /= 1000000;
345 
346         deltatsc /= tmp;
347 
348         return deltatsc;
349     }
350 
351     /// 根据PMtimer的参考值计算tsc的频率
352     fn calc_pmtimer_ref(mut deltatsc: u64, ref1: u64, mut ref2: u64) -> u64 {
353         if unlikely(ref1 == 0 && ref2 == 0) {
354             return u64::MAX;
355         }
356 
357         if ref2 < ref1 {
358             ref2 += ACPI_PM_OVERRUN;
359         }
360 
361         ref2 -= ref1;
362 
363         let mut tmp = ref2 * 1000000000;
364 
365         tmp /= PMTMR_TICKS_PER_SEC;
366 
367         deltatsc /= tmp;
368 
369         return deltatsc;
370     }
371 
372     pub fn tsc_khz() -> u64 {
373         unsafe { TSC_KHZ }
374     }
375 
376     pub fn cpu_khz() -> u64 {
377         unsafe { CPU_KHZ }
378     }
379 
380     fn set_cpu_khz(khz: u64) {
381         unsafe {
382             CPU_KHZ = khz;
383         }
384     }
385 
386     fn set_tsc_khz(khz: u64) {
387         unsafe {
388             TSC_KHZ = khz;
389         }
390     }
391 }
392