xref: /openbmc/linux/tools/power/cpupower/debug/kernel/cpufreq-test_tsc.c (revision 58e16d792a6a8c6b750f637a4649967fcac853dc)
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*/
read_pmtmr(void)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  
cpufreq_test_tsc(void)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  
cpufreq_none(void)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