1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * test module to check whether the TSC-based delay routine continues |
4 | * to work properly after cpufreq transitions. Needs ACPI to work |
5 | * properly. |
6 | * |
7 | * Based partly on the Power Management Timer (PMTMR) code to be found |
8 | * in arch/i386/kernel/timers/timer_pm.c on recent 2.6. kernels, especially |
9 | * code written by John Stultz. The read_pmtmr function was copied verbatim |
10 | * from that file. |
11 | * |
12 | * (C) 2004 Dominik Brodowski |
13 | * |
14 | * To use: |
15 | * 1.) pass clock=tsc to the kernel on your bootloader |
16 | * 2.) modprobe this module (it'll fail) |
17 | * 3.) change CPU frequency |
18 | * 4.) modprobe this module again |
19 | * 5.) if the third value, "diff_pmtmr", changes between 2. and 4., the |
20 | * TSC-based delay routine on the Linux kernel does not correctly |
21 | * handle the cpufreq transition. Please report this to |
22 | * linux-pm@vger.kernel.org |
23 | */ |
24 | |
25 | #include <linux/kernel.h> |
26 | #include <linux/module.h> |
27 | #include <linux/init.h> |
28 | #include <linux/delay.h> |
29 | #include <linux/acpi.h> |
30 | #include <asm/io.h> |
31 | |
32 | static int pm_tmr_ioport = 0; |
33 | |
34 | /*helper function to safely read acpi pm timesource*/ |
35 | static u32 read_pmtmr(void) |
36 | { |
37 | u32 v1=0,v2=0,v3=0; |
38 | /* It has been reported that because of various broken |
39 | * chipsets (ICH4, PIIX4 and PIIX4E) where the ACPI PM time |
40 | * source is not latched, so you must read it multiple |
41 | * times to insure a safe value is read. |
42 | */ |
43 | do { |
44 | v1 = inl(port: pm_tmr_ioport); |
45 | v2 = inl(port: pm_tmr_ioport); |
46 | v3 = inl(port: pm_tmr_ioport); |
47 | } while ((v1 > v2 && v1 < v3) || (v2 > v3 && v2 < v1) |
48 | || (v3 > v1 && v3 < v2)); |
49 | |
50 | /* mask the output to 24 bits */ |
51 | return (v2 & 0xFFFFFF); |
52 | } |
53 | |
54 | static int __init cpufreq_test_tsc(void) |
55 | { |
56 | u32 now, then, diff; |
57 | u64 now_tsc, then_tsc, diff_tsc; |
58 | int i; |
59 | |
60 | /* the following code snipped is copied from arch/x86/kernel/acpi/boot.c |
61 | of Linux v2.6.25. */ |
62 | |
63 | /* detect the location of the ACPI PM Timer */ |
64 | if (acpi_gbl_FADT.header.revision >= FADT2_REVISION_ID) { |
65 | /* FADT rev. 2 */ |
66 | if (acpi_gbl_FADT.xpm_timer_block.space_id != |
67 | ACPI_ADR_SPACE_SYSTEM_IO) |
68 | return 0; |
69 | |
70 | pm_tmr_ioport = acpi_gbl_FADT.xpm_timer_block.address; |
71 | /* |
72 | * "X" fields are optional extensions to the original V1.0 |
73 | * fields, so we must selectively expand V1.0 fields if the |
74 | * corresponding X field is zero. |
75 | */ |
76 | if (!pm_tmr_ioport) |
77 | pm_tmr_ioport = acpi_gbl_FADT.pm_timer_block; |
78 | } else { |
79 | /* FADT rev. 1 */ |
80 | pm_tmr_ioport = acpi_gbl_FADT.pm_timer_block; |
81 | } |
82 | |
83 | printk(KERN_DEBUG "start--> \n" ); |
84 | then = read_pmtmr(); |
85 | then_tsc = rdtsc(); |
86 | for (i=0;i<20;i++) { |
87 | mdelay(100); |
88 | now = read_pmtmr(); |
89 | now_tsc = rdtsc(); |
90 | diff = (now - then) & 0xFFFFFF; |
91 | diff_tsc = now_tsc - then_tsc; |
92 | printk(KERN_DEBUG "t1: %08u t2: %08u diff_pmtmr: %08u diff_tsc: %016llu\n" , then, now, diff, diff_tsc); |
93 | then = now; |
94 | then_tsc = now_tsc; |
95 | } |
96 | printk(KERN_DEBUG "<-- end \n" ); |
97 | return -ENODEV; |
98 | } |
99 | |
100 | static void __exit cpufreq_none(void) |
101 | { |
102 | return; |
103 | } |
104 | |
105 | module_init(cpufreq_test_tsc) |
106 | module_exit(cpufreq_none) |
107 | |
108 | |
109 | MODULE_AUTHOR("Dominik Brodowski" ); |
110 | MODULE_DESCRIPTION("Verify the TSC cpufreq notifier working correctly -- needs ACPI-enabled system" ); |
111 | MODULE_LICENSE ("GPL" ); |
112 | |