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 22 struct 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 */ 29 static struct lpit_residency_info residency_info_mem; 30 static struct lpit_residency_info residency_info_ffh; 31 32 static 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 64 static 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 } 77 static DEVICE_ATTR_RO(low_power_idle_system_residency_us); 78 79 static 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 } 92 static DEVICE_ATTR_RO(low_power_idle_cpu_residency_us); 93 94 int 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 104 static 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 131 static 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 149 void 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 } 163