1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Mapping of TPMI power domains CPU mapping 4 * 5 * Copyright (c) 2024, Intel Corporation. 6 */ 7 8 #include <linux/bitfield.h> 9 #include <linux/cleanup.h> 10 #include <linux/cpuhotplug.h> 11 #include <linux/cpumask.h> 12 #include <linux/errno.h> 13 #include <linux/export.h> 14 #include <linux/hashtable.h> 15 #include <linux/module.h> 16 #include <linux/mutex.h> 17 #include <linux/overflow.h> 18 #include <linux/slab.h> 19 #include <linux/topology.h> 20 #include <linux/types.h> 21 22 #include <asm/cpu_device_id.h> 23 #include <asm/intel-family.h> 24 #include <asm/msr.h> 25 26 #include "tpmi_power_domains.h" 27 28 #define MSR_PM_LOGICAL_ID 0x54 29 30 /* 31 * Struct of MSR 0x54 32 * [15:11] PM_DOMAIN_ID 33 * [10:3] MODULE_ID (aka IDI_AGENT_ID) 34 * [2:0] LP_ID 35 * For Atom: 36 * [2] Always 0 37 * [1:0] core ID within module 38 * For Core 39 * [2:1] Always 0 40 * [0] thread ID 41 */ 42 43 #define LP_ID_MASK GENMASK_ULL(2, 0) 44 #define MODULE_ID_MASK GENMASK_ULL(10, 3) 45 #define PM_DOMAIN_ID_MASK GENMASK_ULL(15, 11) 46 47 /** 48 * struct tpmi_cpu_info - Mapping information for a CPU 49 * @hnode: Used to add mapping information to hash list 50 * @linux_cpu: Linux CPU number 51 * @pkg_id: Package ID of this CPU 52 * @punit_thread_id: Punit thread id of this CPU 53 * @punit_core_id: Punit core id 54 * @punit_domain_id: Power domain id from Punit 55 * 56 * Structure to store mapping information for a Linux CPU 57 * to a Punit core, thread and power domain. 58 */ 59 struct tpmi_cpu_info { 60 struct hlist_node hnode; 61 int linux_cpu; 62 u8 pkg_id; 63 u8 punit_thread_id; 64 u8 punit_core_id; 65 u8 punit_domain_id; 66 }; 67 68 static DEFINE_PER_CPU(struct tpmi_cpu_info, tpmi_cpu_info); 69 70 /* The dynamically assigned cpu hotplug state to free later */ 71 static enum cpuhp_state tpmi_hp_state __read_mostly; 72 73 #define MAX_POWER_DOMAINS 8 74 75 static cpumask_t *tpmi_power_domain_mask; 76 77 static u16 *domain_die_map; 78 79 /* Lock to protect tpmi_power_domain_mask and tpmi_cpu_hash */ 80 static DEFINE_MUTEX(tpmi_lock); 81 82 static const struct x86_cpu_id tpmi_cpu_ids[] = { 83 X86_MATCH_VFM(INTEL_GRANITERAPIDS_X, NULL), 84 X86_MATCH_VFM(INTEL_ATOM_CRESTMONT_X, NULL), 85 X86_MATCH_VFM(INTEL_ATOM_CRESTMONT, NULL), 86 X86_MATCH_VFM(INTEL_ATOM_DARKMONT_X, NULL), 87 X86_MATCH_VFM(INTEL_GRANITERAPIDS_D, NULL), 88 X86_MATCH_VFM(INTEL_PANTHERCOVE_X, NULL), 89 {} 90 }; 91 MODULE_DEVICE_TABLE(x86cpu, tpmi_cpu_ids); 92 93 static DECLARE_HASHTABLE(tpmi_cpu_hash, 8); 94 95 static bool tpmi_domain_is_valid(struct tpmi_cpu_info *info) 96 { 97 return info->pkg_id < topology_max_packages() && 98 info->punit_domain_id < MAX_POWER_DOMAINS; 99 } 100 101 int tpmi_get_linux_cpu_number(int package_id, int domain_id, int punit_core_id) 102 { 103 struct tpmi_cpu_info *info; 104 int ret = -EINVAL; 105 106 guard(mutex)(&tpmi_lock); 107 hash_for_each_possible(tpmi_cpu_hash, info, hnode, punit_core_id) { 108 if (info->punit_domain_id == domain_id && info->pkg_id == package_id) { 109 ret = info->linux_cpu; 110 break; 111 } 112 } 113 114 return ret; 115 } 116 EXPORT_SYMBOL_NS_GPL(tpmi_get_linux_cpu_number, "INTEL_TPMI_POWER_DOMAIN"); 117 118 int tpmi_get_punit_core_number(int cpu_no) 119 { 120 if (cpu_no >= num_possible_cpus()) 121 return -EINVAL; 122 123 return per_cpu(tpmi_cpu_info, cpu_no).punit_core_id; 124 } 125 EXPORT_SYMBOL_NS_GPL(tpmi_get_punit_core_number, "INTEL_TPMI_POWER_DOMAIN"); 126 127 int tpmi_get_power_domain_id(int cpu_no) 128 { 129 if (cpu_no >= num_possible_cpus()) 130 return -EINVAL; 131 132 return per_cpu(tpmi_cpu_info, cpu_no).punit_domain_id; 133 } 134 EXPORT_SYMBOL_NS_GPL(tpmi_get_power_domain_id, "INTEL_TPMI_POWER_DOMAIN"); 135 136 cpumask_t *tpmi_get_power_domain_mask(int cpu_no) 137 { 138 struct tpmi_cpu_info *info; 139 cpumask_t *mask; 140 int index; 141 142 if (cpu_no >= num_possible_cpus()) 143 return NULL; 144 145 info = &per_cpu(tpmi_cpu_info, cpu_no); 146 if (!tpmi_domain_is_valid(info)) 147 return NULL; 148 149 index = info->pkg_id * MAX_POWER_DOMAINS + info->punit_domain_id; 150 guard(mutex)(&tpmi_lock); 151 mask = &tpmi_power_domain_mask[index]; 152 153 return mask; 154 } 155 EXPORT_SYMBOL_NS_GPL(tpmi_get_power_domain_mask, "INTEL_TPMI_POWER_DOMAIN"); 156 157 int tpmi_get_linux_die_id(int pkg_id, int domain_id) 158 { 159 if (pkg_id >= topology_max_packages() || domain_id >= MAX_POWER_DOMAINS) 160 return -EINVAL; 161 162 return domain_die_map[pkg_id * MAX_POWER_DOMAINS + domain_id]; 163 } 164 EXPORT_SYMBOL_NS_GPL(tpmi_get_linux_die_id, "INTEL_TPMI_POWER_DOMAIN"); 165 166 static int tpmi_get_logical_id(unsigned int cpu, struct tpmi_cpu_info *info) 167 { 168 u64 data; 169 int ret; 170 171 ret = rdmsrq_safe(MSR_PM_LOGICAL_ID, &data); 172 if (ret) 173 return ret; 174 175 info->punit_domain_id = FIELD_GET(PM_DOMAIN_ID_MASK, data); 176 if (info->punit_domain_id >= MAX_POWER_DOMAINS) 177 return -EINVAL; 178 179 info->punit_thread_id = FIELD_GET(LP_ID_MASK, data); 180 info->punit_core_id = FIELD_GET(MODULE_ID_MASK, data); 181 info->pkg_id = topology_physical_package_id(cpu); 182 info->linux_cpu = cpu; 183 184 return 0; 185 } 186 187 static int tpmi_cpu_online(unsigned int cpu) 188 { 189 struct tpmi_cpu_info *info = &per_cpu(tpmi_cpu_info, cpu); 190 int ret, index; 191 192 /* Don't fail CPU online for some bad mapping of CPUs */ 193 ret = tpmi_get_logical_id(cpu, info); 194 if (ret) 195 return 0; 196 197 index = info->pkg_id * MAX_POWER_DOMAINS + info->punit_domain_id; 198 199 guard(mutex)(&tpmi_lock); 200 cpumask_set_cpu(cpu, &tpmi_power_domain_mask[index]); 201 hash_add(tpmi_cpu_hash, &info->hnode, info->punit_core_id); 202 203 domain_die_map[info->pkg_id * MAX_POWER_DOMAINS + info->punit_domain_id] = 204 topology_die_id(cpu); 205 206 return 0; 207 } 208 209 static int __init tpmi_init(void) 210 { 211 const struct x86_cpu_id *id; 212 u64 data; 213 int ret; 214 215 id = x86_match_cpu(tpmi_cpu_ids); 216 if (!id) 217 return -ENODEV; 218 219 /* Check for MSR 0x54 presence */ 220 ret = rdmsrq_safe(MSR_PM_LOGICAL_ID, &data); 221 if (ret) 222 return ret; 223 224 tpmi_power_domain_mask = kcalloc(size_mul(topology_max_packages(), MAX_POWER_DOMAINS), 225 sizeof(*tpmi_power_domain_mask), GFP_KERNEL); 226 if (!tpmi_power_domain_mask) 227 return -ENOMEM; 228 229 domain_die_map = kcalloc(size_mul(topology_max_packages(), MAX_POWER_DOMAINS), 230 sizeof(*domain_die_map), GFP_KERNEL); 231 if (!domain_die_map) { 232 ret = -ENOMEM; 233 goto free_domain_mask; 234 } 235 236 ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, 237 "platform/x86/tpmi_power_domains:online", 238 tpmi_cpu_online, NULL); 239 if (ret < 0) 240 goto free_domain_map; 241 242 tpmi_hp_state = ret; 243 244 return 0; 245 246 free_domain_map: 247 kfree(domain_die_map); 248 249 free_domain_mask: 250 kfree(tpmi_power_domain_mask); 251 252 return ret; 253 } 254 module_init(tpmi_init) 255 256 static void __exit tpmi_exit(void) 257 { 258 cpuhp_remove_state(tpmi_hp_state); 259 kfree(tpmi_power_domain_mask); 260 kfree(domain_die_map); 261 } 262 module_exit(tpmi_exit) 263 264 MODULE_DESCRIPTION("TPMI Power Domains Mapping"); 265 MODULE_LICENSE("GPL"); 266