Commit | Line | Data |
---|---|---|
09c434b8 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
7fe2f639 DB |
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 | |
dec102aa | 22 | * linux-pm@vger.kernel.org |
7fe2f639 DB |
23 | */ |
24 | ||
25 | #include <linux/kernel.h> | |
26 | #include <linux/module.h> | |
27 | #include <linux/init.h> | |
28 | #include <linux/delay.h> | |
8b48463f | 29 | #include <linux/acpi.h> |
7fe2f639 DB |
30 | #include <asm/io.h> |
31 | ||
7fe2f639 DB |
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(pm_tmr_ioport); | |
45 | v2 = inl(pm_tmr_ioport); | |
46 | v3 = inl(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(); | |
4ea1636b | 85 | then_tsc = rdtsc(); |
7fe2f639 DB |
86 | for (i=0;i<20;i++) { |
87 | mdelay(100); | |
88 | now = read_pmtmr(); | |
4ea1636b | 89 | now_tsc = rdtsc(); |
7fe2f639 DB |
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"); |