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