Linux Audio

Check our new training course

Loading...
Note: File does not exist in v4.6.
  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 */
 59struct 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
 68static DEFINE_PER_CPU(struct tpmi_cpu_info, tpmi_cpu_info);
 69
 70/* The dynamically assigned cpu hotplug state to free later */
 71static enum cpuhp_state tpmi_hp_state __read_mostly;
 72
 73#define MAX_POWER_DOMAINS	8
 74
 75static cpumask_t *tpmi_power_domain_mask;
 76
 77/* Lock to protect tpmi_power_domain_mask and tpmi_cpu_hash */
 78static DEFINE_MUTEX(tpmi_lock);
 79
 80static const struct x86_cpu_id tpmi_cpu_ids[] = {
 81	X86_MATCH_VFM(INTEL_GRANITERAPIDS_X,	NULL),
 82	X86_MATCH_VFM(INTEL_ATOM_CRESTMONT_X,	NULL),
 83	X86_MATCH_VFM(INTEL_ATOM_CRESTMONT,	NULL),
 84	X86_MATCH_VFM(INTEL_ATOM_DARKMONT_X,	NULL),
 85	X86_MATCH_VFM(INTEL_GRANITERAPIDS_D,	NULL),
 86	X86_MATCH_VFM(INTEL_PANTHERCOVE_X,	NULL),
 87	{}
 88};
 89MODULE_DEVICE_TABLE(x86cpu, tpmi_cpu_ids);
 90
 91static DECLARE_HASHTABLE(tpmi_cpu_hash, 8);
 92
 93static bool tpmi_domain_is_valid(struct tpmi_cpu_info *info)
 94{
 95	return info->pkg_id < topology_max_packages() &&
 96		info->punit_domain_id < MAX_POWER_DOMAINS;
 97}
 98
 99int tpmi_get_linux_cpu_number(int package_id, int domain_id, int punit_core_id)
100{
101	struct tpmi_cpu_info *info;
102	int ret = -EINVAL;
103
104	guard(mutex)(&tpmi_lock);
105	hash_for_each_possible(tpmi_cpu_hash, info, hnode, punit_core_id) {
106		if (info->punit_domain_id == domain_id && info->pkg_id == package_id) {
107			ret = info->linux_cpu;
108			break;
109		}
110	}
111
112	return ret;
113}
114EXPORT_SYMBOL_NS_GPL(tpmi_get_linux_cpu_number, "INTEL_TPMI_POWER_DOMAIN");
115
116int tpmi_get_punit_core_number(int cpu_no)
117{
118	if (cpu_no >= num_possible_cpus())
119		return -EINVAL;
120
121	return per_cpu(tpmi_cpu_info, cpu_no).punit_core_id;
122}
123EXPORT_SYMBOL_NS_GPL(tpmi_get_punit_core_number, "INTEL_TPMI_POWER_DOMAIN");
124
125int tpmi_get_power_domain_id(int cpu_no)
126{
127	if (cpu_no >= num_possible_cpus())
128		return -EINVAL;
129
130	return per_cpu(tpmi_cpu_info, cpu_no).punit_domain_id;
131}
132EXPORT_SYMBOL_NS_GPL(tpmi_get_power_domain_id, "INTEL_TPMI_POWER_DOMAIN");
133
134cpumask_t *tpmi_get_power_domain_mask(int cpu_no)
135{
136	struct tpmi_cpu_info *info;
137	cpumask_t *mask;
138	int index;
139
140	if (cpu_no >= num_possible_cpus())
141		return NULL;
142
143	info = &per_cpu(tpmi_cpu_info, cpu_no);
144	if (!tpmi_domain_is_valid(info))
145		return NULL;
146
147	index = info->pkg_id * MAX_POWER_DOMAINS + info->punit_domain_id;
148	guard(mutex)(&tpmi_lock);
149	mask = &tpmi_power_domain_mask[index];
150
151	return mask;
152}
153EXPORT_SYMBOL_NS_GPL(tpmi_get_power_domain_mask, "INTEL_TPMI_POWER_DOMAIN");
154
155static int tpmi_get_logical_id(unsigned int cpu, struct tpmi_cpu_info *info)
156{
157	u64 data;
158	int ret;
159
160	ret = rdmsrl_safe(MSR_PM_LOGICAL_ID, &data);
161	if (ret)
162		return ret;
163
164	info->punit_domain_id = FIELD_GET(PM_DOMAIN_ID_MASK, data);
165	if (info->punit_domain_id >= MAX_POWER_DOMAINS)
166		return -EINVAL;
167
168	info->punit_thread_id = FIELD_GET(LP_ID_MASK, data);
169	info->punit_core_id = FIELD_GET(MODULE_ID_MASK, data);
170	info->pkg_id = topology_physical_package_id(cpu);
171	info->linux_cpu = cpu;
172
173	return 0;
174}
175
176static int tpmi_cpu_online(unsigned int cpu)
177{
178	struct tpmi_cpu_info *info = &per_cpu(tpmi_cpu_info, cpu);
179	int ret, index;
180
181	/* Don't fail CPU online for some bad mapping of CPUs */
182	ret = tpmi_get_logical_id(cpu, info);
183	if (ret)
184		return 0;
185
186	index = info->pkg_id * MAX_POWER_DOMAINS + info->punit_domain_id;
187
188	guard(mutex)(&tpmi_lock);
189	cpumask_set_cpu(cpu, &tpmi_power_domain_mask[index]);
190	hash_add(tpmi_cpu_hash, &info->hnode, info->punit_core_id);
191
192	return 0;
193}
194
195static int __init tpmi_init(void)
196{
197	const struct x86_cpu_id *id;
198	u64 data;
199	int ret;
200
201	id = x86_match_cpu(tpmi_cpu_ids);
202	if (!id)
203		return -ENODEV;
204
205	/* Check for MSR 0x54 presence */
206	ret = rdmsrl_safe(MSR_PM_LOGICAL_ID, &data);
207	if (ret)
208		return ret;
209
210	tpmi_power_domain_mask = kcalloc(size_mul(topology_max_packages(), MAX_POWER_DOMAINS),
211					 sizeof(*tpmi_power_domain_mask), GFP_KERNEL);
212	if (!tpmi_power_domain_mask)
213		return -ENOMEM;
214
215	ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN,
216				"platform/x86/tpmi_power_domains:online",
217				tpmi_cpu_online, NULL);
218	if (ret < 0) {
219		kfree(tpmi_power_domain_mask);
220		return ret;
221	}
222
223	tpmi_hp_state = ret;
224
225	return 0;
226}
227module_init(tpmi_init)
228
229static void __exit tpmi_exit(void)
230{
231	cpuhp_remove_state(tpmi_hp_state);
232	kfree(tpmi_power_domain_mask);
233}
234module_exit(tpmi_exit)
235
236MODULE_DESCRIPTION("TPMI Power Domains Mapping");
237MODULE_LICENSE("GPL");