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