// SPDX-License-Identifier: GPL-2.0-only /* * Mapping of TPMI power domains CPU mapping * * Copyright (c) 2024, Intel Corporation. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "tpmi_power_domains.h" #define MSR_PM_LOGICAL_ID 0x54 /* * Struct of MSR 0x54 * [15:11] PM_DOMAIN_ID * [10:3] MODULE_ID (aka IDI_AGENT_ID) * [2:0] LP_ID * For Atom: * [2] Always 0 * [1:0] core ID within module * For Core * [2:1] Always 0 * [0] thread ID */ #define LP_ID_MASK GENMASK_ULL(2, 0) #define MODULE_ID_MASK GENMASK_ULL(10, 3) #define PM_DOMAIN_ID_MASK GENMASK_ULL(15, 11) /** * struct tpmi_cpu_info - Mapping information for a CPU * @hnode: Used to add mapping information to hash list * @linux_cpu: Linux CPU number * @pkg_id: Package ID of this CPU * @punit_thread_id: Punit thread id of this CPU * @punit_core_id: Punit core id * @punit_domain_id: Power domain id from Punit * * Structure to store mapping information for a Linux CPU * to a Punit core, thread and power domain. */ struct tpmi_cpu_info { struct hlist_node hnode; int linux_cpu; u8 pkg_id; u8 punit_thread_id; u8 punit_core_id; u8 punit_domain_id; }; static DEFINE_PER_CPU(struct tpmi_cpu_info, tpmi_cpu_info); /* The dynamically assigned cpu hotplug state to free later */ static enum cpuhp_state tpmi_hp_state __read_mostly; #define MAX_POWER_DOMAINS 8 static cpumask_t *tpmi_power_domain_mask; /* Lock to protect tpmi_power_domain_mask and tpmi_cpu_hash */ static DEFINE_MUTEX(tpmi_lock); static const struct x86_cpu_id tpmi_cpu_ids[] = { X86_MATCH_VFM(INTEL_GRANITERAPIDS_X, NULL), X86_MATCH_VFM(INTEL_ATOM_CRESTMONT_X, NULL), X86_MATCH_VFM(INTEL_ATOM_CRESTMONT, NULL), X86_MATCH_VFM(INTEL_GRANITERAPIDS_D, NULL), X86_MATCH_VFM(INTEL_PANTHERCOVE_X, NULL), {} }; MODULE_DEVICE_TABLE(x86cpu, tpmi_cpu_ids); static DECLARE_HASHTABLE(tpmi_cpu_hash, 8); static bool tpmi_domain_is_valid(struct tpmi_cpu_info *info) { return info->pkg_id < topology_max_packages() && info->punit_domain_id < MAX_POWER_DOMAINS; } int tpmi_get_linux_cpu_number(int package_id, int domain_id, int punit_core_id) { struct tpmi_cpu_info *info; int ret = -EINVAL; guard(mutex)(&tpmi_lock); hash_for_each_possible(tpmi_cpu_hash, info, hnode, punit_core_id) { if (info->punit_domain_id == domain_id && info->pkg_id == package_id) { ret = info->linux_cpu; break; } } return ret; } EXPORT_SYMBOL_NS_GPL(tpmi_get_linux_cpu_number, INTEL_TPMI_POWER_DOMAIN); int tpmi_get_punit_core_number(int cpu_no) { if (cpu_no >= num_possible_cpus()) return -EINVAL; return per_cpu(tpmi_cpu_info, cpu_no).punit_core_id; } EXPORT_SYMBOL_NS_GPL(tpmi_get_punit_core_number, INTEL_TPMI_POWER_DOMAIN); int tpmi_get_power_domain_id(int cpu_no) { if (cpu_no >= num_possible_cpus()) return -EINVAL; return per_cpu(tpmi_cpu_info, cpu_no).punit_domain_id; } EXPORT_SYMBOL_NS_GPL(tpmi_get_power_domain_id, INTEL_TPMI_POWER_DOMAIN); cpumask_t *tpmi_get_power_domain_mask(int cpu_no) { struct tpmi_cpu_info *info; cpumask_t *mask; int index; if (cpu_no >= num_possible_cpus()) return NULL; info = &per_cpu(tpmi_cpu_info, cpu_no); if (!tpmi_domain_is_valid(info)) return NULL; index = info->pkg_id * MAX_POWER_DOMAINS + info->punit_domain_id; guard(mutex)(&tpmi_lock); mask = &tpmi_power_domain_mask[index]; return mask; } EXPORT_SYMBOL_NS_GPL(tpmi_get_power_domain_mask, INTEL_TPMI_POWER_DOMAIN); static int tpmi_get_logical_id(unsigned int cpu, struct tpmi_cpu_info *info) { u64 data; int ret; ret = rdmsrl_safe(MSR_PM_LOGICAL_ID, &data); if (ret) return ret; info->punit_domain_id = FIELD_GET(PM_DOMAIN_ID_MASK, data); if (info->punit_domain_id >= MAX_POWER_DOMAINS) return -EINVAL; info->punit_thread_id = FIELD_GET(LP_ID_MASK, data); info->punit_core_id = FIELD_GET(MODULE_ID_MASK, data); info->pkg_id = topology_physical_package_id(cpu); info->linux_cpu = cpu; return 0; } static int tpmi_cpu_online(unsigned int cpu) { struct tpmi_cpu_info *info = &per_cpu(tpmi_cpu_info, cpu); int ret, index; /* Don't fail CPU online for some bad mapping of CPUs */ ret = tpmi_get_logical_id(cpu, info); if (ret) return 0; index = info->pkg_id * MAX_POWER_DOMAINS + info->punit_domain_id; guard(mutex)(&tpmi_lock); cpumask_set_cpu(cpu, &tpmi_power_domain_mask[index]); hash_add(tpmi_cpu_hash, &info->hnode, info->punit_core_id); return 0; } static int __init tpmi_init(void) { const struct x86_cpu_id *id; u64 data; int ret; id = x86_match_cpu(tpmi_cpu_ids); if (!id) return -ENODEV; /* Check for MSR 0x54 presence */ ret = rdmsrl_safe(MSR_PM_LOGICAL_ID, &data); if (ret) return ret; tpmi_power_domain_mask = kcalloc(size_mul(topology_max_packages(), MAX_POWER_DOMAINS), sizeof(*tpmi_power_domain_mask), GFP_KERNEL); if (!tpmi_power_domain_mask) return -ENOMEM; ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "platform/x86/tpmi_power_domains:online", tpmi_cpu_online, NULL); if (ret < 0) { kfree(tpmi_power_domain_mask); return ret; } tpmi_hp_state = ret; return 0; } module_init(tpmi_init) static void __exit tpmi_exit(void) { cpuhp_remove_state(tpmi_hp_state); kfree(tpmi_power_domain_mask); } module_exit(tpmi_exit) MODULE_DESCRIPTION("TPMI Power Domains Mapping"); MODULE_LICENSE("GPL");