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(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(); 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