summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSrinivas Pandruvada <srinivas.pandruvada@linux.intel.com>2017-10-05 19:24:03 -0400
committerRafael J. Wysocki <rafael.j.wysocki@intel.com>2017-10-11 09:38:10 -0400
commiteeb2d80d502af28e5660ff4bbe00f90ceb82c2db (patch)
tree8d485561b5673a70d876008390dce3cc53e4b63a
parentc2ebf788f927dcca72beead19fab5f5aba79a098 (diff)
ACPI / LPIT: Add Low Power Idle Table (LPIT) support
Add functionality to read LPIT table, which provides: - Sysfs interface to read residency counters via /sys/devices/system/cpu/cpuidle/low_power_idle_cpu_residency_us /sys/devices/system/cpu/cpuidle/low_power_idle_system_residency_us Here the count "low_power_idle_cpu_residency_us" shows the time spent by CPU package in low power state. This is read via MSR interface, which points to MSR for PKG C10. Here the count "low_power_idle_system_residency_us" show the count the system was in low power state. This is read via MMIO interface. This is mapped to SLP_S0 residency on modern Intel systems. This residency is achieved only when CPU is in PKG C10 and all functional blocks are in low power state. It is possible that none of the above counters present or anyone of the counter present or all counters present. For example: On my Kabylake system both of the above counters present. After suspend to idle these counts updated and prints: 6916179 6998564 This counter can be read by tools like turbostat to display. Or it can be used to debug, if modern systems are reaching desired low power state. - Provides an interface to read residency counter memory address This address can be used to get the base address of PMC memory mapped IO. This is utilized by intel_pmc_core driver to print more debug information. In addition, to avoid code duplication to read iomem, removed the read of iomem from acpi_os_read_memory() in osl.c and made a common function acpi_os_read_iomem(). This new function is used for reading iomem in in both osl.c and acpi_lpit.c. Link: http://www.uefi.org/sites/default/files/resources/Intel_ACPI_Low_Power_S0_Idle.pdf Signed-off-by: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com> Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
-rw-r--r--Documentation/acpi/lpit.txt25
-rw-r--r--drivers/acpi/Kconfig5
-rw-r--r--drivers/acpi/Makefile1
-rw-r--r--drivers/acpi/acpi_lpit.c162
-rw-r--r--drivers/acpi/internal.h6
-rw-r--r--drivers/acpi/osl.c42
-rw-r--r--drivers/acpi/scan.c1
-rw-r--r--include/acpi/acpiosxf.h2
-rw-r--r--include/linux/acpi.h9
9 files changed, 237 insertions, 16 deletions
diff --git a/Documentation/acpi/lpit.txt b/Documentation/acpi/lpit.txt
new file mode 100644
index 000000000000..b426398d2e97
--- /dev/null
+++ b/Documentation/acpi/lpit.txt
@@ -0,0 +1,25 @@
1To enumerate platform Low Power Idle states, Intel platforms are using
2“Low Power Idle Table” (LPIT). More details about this table can be
3downloaded from:
4http://www.uefi.org/sites/default/files/resources/Intel_ACPI_Low_Power_S0_Idle.pdf
5
6Residencies for each low power state can be read via FFH
7(Function fixed hardware) or a memory mapped interface.
8
9On platforms supporting S0ix sleep states, there can be two types of
10residencies:
11- CPU PKG C10 (Read via FFH interface)
12- Platform Controller Hub (PCH) SLP_S0 (Read via memory mapped interface)
13
14The following attributes are added dynamically to the cpuidle
15sysfs attribute group:
16 /sys/devices/system/cpu/cpuidle/low_power_idle_cpu_residency_us
17 /sys/devices/system/cpu/cpuidle/low_power_idle_system_residency_us
18
19The "low_power_idle_cpu_residency_us" attribute shows time spent
20by the CPU package in PKG C10
21
22The "low_power_idle_system_residency_us" attribute shows SLP_S0
23residency, or system time spent with the SLP_S0# signal asserted.
24This is the lowest possible system power state, achieved only when CPU is in
25PKG C10 and all functional blocks in PCH are in a low power state.
diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig
index 1ce52f84dc23..4bfef0f78cde 100644
--- a/drivers/acpi/Kconfig
+++ b/drivers/acpi/Kconfig
@@ -80,6 +80,11 @@ endif
80config ACPI_SPCR_TABLE 80config ACPI_SPCR_TABLE
81 bool 81 bool
82 82
83config ACPI_LPIT
84 bool
85 depends on X86_64
86 default y
87
83config ACPI_SLEEP 88config ACPI_SLEEP
84 bool 89 bool
85 depends on SUSPEND || HIBERNATION 90 depends on SUSPEND || HIBERNATION
diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile
index 90265ab4437a..6a19bd7aba21 100644
--- a/drivers/acpi/Makefile
+++ b/drivers/acpi/Makefile
@@ -56,6 +56,7 @@ acpi-$(CONFIG_DEBUG_FS) += debugfs.o
56acpi-$(CONFIG_ACPI_NUMA) += numa.o 56acpi-$(CONFIG_ACPI_NUMA) += numa.o
57acpi-$(CONFIG_ACPI_PROCFS_POWER) += cm_sbs.o 57acpi-$(CONFIG_ACPI_PROCFS_POWER) += cm_sbs.o
58acpi-y += acpi_lpat.o 58acpi-y += acpi_lpat.o
59acpi-$(CONFIG_ACPI_LPIT) += acpi_lpit.o
59acpi-$(CONFIG_ACPI_GENERIC_GSI) += irq.o 60acpi-$(CONFIG_ACPI_GENERIC_GSI) += irq.o
60acpi-$(CONFIG_ACPI_WATCHDOG) += acpi_watchdog.o 61acpi-$(CONFIG_ACPI_WATCHDOG) += acpi_watchdog.o
61 62
diff --git a/drivers/acpi/acpi_lpit.c b/drivers/acpi/acpi_lpit.c
new file mode 100644
index 000000000000..e94e478dd18b
--- /dev/null
+++ b/drivers/acpi/acpi_lpit.c
@@ -0,0 +1,162 @@
1
2/*
3 * acpi_lpit.c - LPIT table processing functions
4 *
5 * Copyright (C) 2017 Intel Corporation. All rights reserved.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License version
9 * 2 as published by the Free Software Foundation.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 */
16
17#include <linux/cpu.h>
18#include <linux/acpi.h>
19#include <asm/msr.h>
20#include <asm/tsc.h>
21
22struct lpit_residency_info {
23 struct acpi_generic_address gaddr;
24 u64 frequency;
25 void __iomem *iomem_addr;
26};
27
28/* Storage for an memory mapped and FFH based entries */
29static struct lpit_residency_info residency_info_mem;
30static struct lpit_residency_info residency_info_ffh;
31
32static int lpit_read_residency_counter_us(u64 *counter, bool io_mem)
33{
34 int err;
35
36 if (io_mem) {
37 u64 count = 0;
38 int error;
39
40 error = acpi_os_read_iomem(residency_info_mem.iomem_addr, &count,
41 residency_info_mem.gaddr.bit_width);
42 if (error)
43 return error;
44
45 *counter = div64_u64(count * 1000000ULL, residency_info_mem.frequency);
46 return 0;
47 }
48
49 err = rdmsrl_safe(residency_info_ffh.gaddr.address, counter);
50 if (!err) {
51 u64 mask = GENMASK_ULL(residency_info_ffh.gaddr.bit_offset +
52 residency_info_ffh.gaddr. bit_width - 1,
53 residency_info_ffh.gaddr.bit_offset);
54
55 *counter &= mask;
56 *counter >>= residency_info_ffh.gaddr.bit_offset;
57 *counter = div64_u64(*counter * 1000000ULL, residency_info_ffh.frequency);
58 return 0;
59 }
60
61 return -ENODATA;
62}
63
64static ssize_t low_power_idle_system_residency_us_show(struct device *dev,
65 struct device_attribute *attr,
66 char *buf)
67{
68 u64 counter;
69 int ret;
70
71 ret = lpit_read_residency_counter_us(&counter, true);
72 if (ret)
73 return ret;
74
75 return sprintf(buf, "%llu\n", counter);
76}
77static DEVICE_ATTR_RO(low_power_idle_system_residency_us);
78
79static ssize_t low_power_idle_cpu_residency_us_show(struct device *dev,
80 struct device_attribute *attr,
81 char *buf)
82{
83 u64 counter;
84 int ret;
85
86 ret = lpit_read_residency_counter_us(&counter, false);
87 if (ret)
88 return ret;
89
90 return sprintf(buf, "%llu\n", counter);
91}
92static DEVICE_ATTR_RO(low_power_idle_cpu_residency_us);
93
94int lpit_read_residency_count_address(u64 *address)
95{
96 if (!residency_info_mem.gaddr.address)
97 return -EINVAL;
98
99 *address = residency_info_mem.gaddr.address;
100
101 return 0;
102}
103
104static void lpit_update_residency(struct lpit_residency_info *info,
105 struct acpi_lpit_native *lpit_native)
106{
107 info->frequency = lpit_native->counter_frequency ?
108 lpit_native->counter_frequency : tsc_khz * 1000;
109 if (!info->frequency)
110 info->frequency = 1;
111
112 info->gaddr = lpit_native->residency_counter;
113 if (info->gaddr.space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) {
114 info->iomem_addr = ioremap_nocache(info->gaddr.address,
115 info->gaddr.bit_width / 8);
116 if (!info->iomem_addr)
117 return;
118
119 /* Silently fail, if cpuidle attribute group is not present */
120 sysfs_add_file_to_group(&cpu_subsys.dev_root->kobj,
121 &dev_attr_low_power_idle_system_residency_us.attr,
122 "cpuidle");
123 } else if (info->gaddr.space_id == ACPI_ADR_SPACE_FIXED_HARDWARE) {
124 /* Silently fail, if cpuidle attribute group is not present */
125 sysfs_add_file_to_group(&cpu_subsys.dev_root->kobj,
126 &dev_attr_low_power_idle_cpu_residency_us.attr,
127 "cpuidle");
128 }
129}
130
131static void lpit_process(u64 begin, u64 end)
132{
133 while (begin + sizeof(struct acpi_lpit_native) < end) {
134 struct acpi_lpit_native *lpit_native = (struct acpi_lpit_native *)begin;
135
136 if (!lpit_native->header.type && !lpit_native->header.flags) {
137 if (lpit_native->residency_counter.space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY &&
138 !residency_info_mem.gaddr.address) {
139 lpit_update_residency(&residency_info_mem, lpit_native);
140 } else if (lpit_native->residency_counter.space_id == ACPI_ADR_SPACE_FIXED_HARDWARE &&
141 !residency_info_ffh.gaddr.address) {
142 lpit_update_residency(&residency_info_ffh, lpit_native);
143 }
144 }
145 begin += lpit_native->header.length;
146 }
147}
148
149void acpi_init_lpit(void)
150{
151 acpi_status status;
152 u64 lpit_begin;
153 struct acpi_table_lpit *lpit;
154
155 status = acpi_get_table(ACPI_SIG_LPIT, 0, (struct acpi_table_header **)&lpit);
156
157 if (ACPI_FAILURE(status))
158 return;
159
160 lpit_begin = (u64)lpit + sizeof(*lpit);
161 lpit_process(lpit_begin, lpit_begin + lpit->header.length);
162}
diff --git a/drivers/acpi/internal.h b/drivers/acpi/internal.h
index 4361c4415b4f..fc8c43e76707 100644
--- a/drivers/acpi/internal.h
+++ b/drivers/acpi/internal.h
@@ -248,4 +248,10 @@ void acpi_watchdog_init(void);
248static inline void acpi_watchdog_init(void) {} 248static inline void acpi_watchdog_init(void) {}
249#endif 249#endif
250 250
251#ifdef CONFIG_ACPI_LPIT
252void acpi_init_lpit(void);
253#else
254static inline void acpi_init_lpit(void) { }
255#endif
256
251#endif /* _ACPI_INTERNAL_H_ */ 257#endif /* _ACPI_INTERNAL_H_ */
diff --git a/drivers/acpi/osl.c b/drivers/acpi/osl.c
index db78d353bab1..3bb46cb24a99 100644
--- a/drivers/acpi/osl.c
+++ b/drivers/acpi/osl.c
@@ -663,6 +663,29 @@ acpi_status acpi_os_write_port(acpi_io_address port, u32 value, u32 width)
663 663
664EXPORT_SYMBOL(acpi_os_write_port); 664EXPORT_SYMBOL(acpi_os_write_port);
665 665
666int acpi_os_read_iomem(void __iomem *virt_addr, u64 *value, u32 width)
667{
668
669 switch (width) {
670 case 8:
671 *(u8 *) value = readb(virt_addr);
672 break;
673 case 16:
674 *(u16 *) value = readw(virt_addr);
675 break;
676 case 32:
677 *(u32 *) value = readl(virt_addr);
678 break;
679 case 64:
680 *(u64 *) value = readq(virt_addr);
681 break;
682 default:
683 return -EINVAL;
684 }
685
686 return 0;
687}
688
666acpi_status 689acpi_status
667acpi_os_read_memory(acpi_physical_address phys_addr, u64 *value, u32 width) 690acpi_os_read_memory(acpi_physical_address phys_addr, u64 *value, u32 width)
668{ 691{
@@ -670,6 +693,7 @@ acpi_os_read_memory(acpi_physical_address phys_addr, u64 *value, u32 width)
670 unsigned int size = width / 8; 693 unsigned int size = width / 8;
671 bool unmap = false; 694 bool unmap = false;
672 u64 dummy; 695 u64 dummy;
696 int error;
673 697
674 rcu_read_lock(); 698 rcu_read_lock();
675 virt_addr = acpi_map_vaddr_lookup(phys_addr, size); 699 virt_addr = acpi_map_vaddr_lookup(phys_addr, size);
@@ -684,22 +708,8 @@ acpi_os_read_memory(acpi_physical_address phys_addr, u64 *value, u32 width)
684 if (!value) 708 if (!value)
685 value = &dummy; 709 value = &dummy;
686 710
687 switch (width) { 711 error = acpi_os_read_iomem(virt_addr, value, width);
688 case 8: 712 BUG_ON(error);
689 *(u8 *) value = readb(virt_addr);
690 break;
691 case 16:
692 *(u16 *) value = readw(virt_addr);
693 break;
694 case 32:
695 *(u32 *) value = readl(virt_addr);
696 break;
697 case 64:
698 *(u64 *) value = readq(virt_addr);
699 break;
700 default:
701 BUG();
702 }
703 713
704 if (unmap) 714 if (unmap)
705 iounmap(virt_addr); 715 iounmap(virt_addr);
diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c
index 602f8ff212f2..81367edc8a10 100644
--- a/drivers/acpi/scan.c
+++ b/drivers/acpi/scan.c
@@ -2122,6 +2122,7 @@ int __init acpi_scan_init(void)
2122 acpi_int340x_thermal_init(); 2122 acpi_int340x_thermal_init();
2123 acpi_amba_init(); 2123 acpi_amba_init();
2124 acpi_watchdog_init(); 2124 acpi_watchdog_init();
2125 acpi_init_lpit();
2125 2126
2126 acpi_scan_add_handler(&generic_device_handler); 2127 acpi_scan_add_handler(&generic_device_handler);
2127 2128
diff --git a/include/acpi/acpiosxf.h b/include/acpi/acpiosxf.h
index c66eb8ffa454..d5c0f5153c4e 100644
--- a/include/acpi/acpiosxf.h
+++ b/include/acpi/acpiosxf.h
@@ -287,6 +287,8 @@ acpi_status acpi_os_write_port(acpi_io_address address, u32 value, u32 width);
287/* 287/*
288 * Platform and hardware-independent physical memory interfaces 288 * Platform and hardware-independent physical memory interfaces
289 */ 289 */
290int acpi_os_read_iomem(void __iomem *virt_addr, u64 *value, u32 width);
291
290#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_acpi_os_read_memory 292#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_acpi_os_read_memory
291acpi_status 293acpi_status
292acpi_os_read_memory(acpi_physical_address address, u64 *value, u32 width); 294acpi_os_read_memory(acpi_physical_address address, u64 *value, u32 width);
diff --git a/include/linux/acpi.h b/include/linux/acpi.h
index d18c92d4ba19..2b1738f840ab 100644
--- a/include/linux/acpi.h
+++ b/include/linux/acpi.h
@@ -1248,4 +1248,13 @@ int acpi_irq_get(acpi_handle handle, unsigned int index, struct resource *res)
1248} 1248}
1249#endif 1249#endif
1250 1250
1251#ifdef CONFIG_ACPI_LPIT
1252int lpit_read_residency_count_address(u64 *address);
1253#else
1254static inline int lpit_read_residency_count_address(u64 *address)
1255{
1256 return -EINVAL;
1257}
1258#endif
1259
1251#endif /*_LINUX_ACPI_H*/ 1260#endif /*_LINUX_ACPI_H*/