xref: /linux/drivers/platform/x86/amd/pmf/metrics.c (revision 5b05bb3f6c5716fab6911e12d60dd1f43ad9806a)
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * AMD Platform Management Framework Driver - Metrics Support
4  *
5  * Copyright (c) 2026, Advanced Micro Devices, Inc.
6  * All Rights Reserved.
7  *
8  * Authors: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
9  *	    Patil Rajesh Reddy <Patil.Reddy@amd.com>
10  */
11 
12 #include <linux/array_size.h>
13 #include <linux/cleanup.h>
14 #include <linux/container_of.h>
15 #include <linux/device.h>
16 #include <linux/device/devres.h>
17 #include <linux/io.h>
18 #include <linux/ktime.h>
19 #include <linux/limits.h>
20 #include <linux/math64.h>
21 #include <linux/minmax.h>
22 #include <linux/string.h>
23 #include <linux/units.h>
24 #include <linux/wordpart.h>
25 #include <linux/workqueue.h>
26 
27 #include "pmf.h"
28 
29 static struct device *pmf_device;
30 
amd_pmf_q10_acc_to_mW(u64 avg_acc)31 static u16 amd_pmf_q10_acc_to_mW(u64 avg_acc)
32 {
33 	u64 val = DIV_U64_ROUND_CLOSEST(avg_acc, 1024) * MILLIWATT_PER_WATT;
34 
35 	return min_t(u16, val, U16_MAX);
36 }
37 
amd_pmf_q10_acc_to_int(u64 avg_acc)38 static u16 amd_pmf_q10_acc_to_int(u64 avg_acc)
39 {
40 	u64 val = DIV_U64_ROUND_CLOSEST(avg_acc, 1024);
41 
42 	return min_t(u16, val, U16_MAX);
43 }
44 
amd_pmf_avg_acc_metric(u32 curr_counter,u32 prev_counter,u64 curr_value,u64 prev_value)45 static u64 amd_pmf_avg_acc_metric(u32 curr_counter, u32 prev_counter,
46 				  u64 curr_value, u64 prev_value)
47 {
48 	u32 counter_diff;
49 	u64 val_diff;
50 
51 	/* Check for counter reset */
52 	if (curr_counter <= prev_counter)
53 		return 0;
54 
55 	counter_diff = curr_counter - prev_counter;
56 
57 	if (curr_value < prev_value)
58 		return 0;
59 
60 	val_diff = curr_value - prev_value;
61 
62 	return DIV_U64_ROUND_CLOSEST(val_diff, counter_diff);
63 }
64 
amd_pmf_get_tbl_dram_addr(struct amd_pmf_dev * dev)65 int amd_pmf_get_tbl_dram_addr(struct amd_pmf_dev *dev)
66 {
67 	int ret;
68 
69 	ret = amd_pmf_send_cmd(dev, GET_1AH_M80H_METRICS_TABLE_DRAM_ADDR, GET_CMD,
70 			       ARG_NONE, &dev->dram_addr.lo);
71 	if (ret) {
72 		dev_err(dev->dev, "Failed to get DRAM address: %d\n", ret);
73 		return ret;
74 	}
75 
76 	dev->metrics_table_phys = ((u64)dev->dram_addr.hi << 32) | dev->dram_addr.lo;
77 	dev->mtable_size = dev->dram_addr.size;
78 
79 	if (dev->mtable_size != sizeof(dev->mtable_v3)) {
80 		dev_err(dev->dev, "Metrics table size mismatch: got %u, expected %zu\n",
81 			dev->dram_addr.size, sizeof(dev->mtable_v3));
82 		return -EINVAL;
83 	}
84 
85 	dev->metrics_table_virt = devm_ioremap(dev->dev, dev->metrics_table_phys, dev->mtable_size);
86 	if (!dev->metrics_table_virt) {
87 		dev_err(dev->dev, "Failed to map DRAM address for PMF metrics table\n");
88 		dev->metrics_table_phys = 0;
89 		return -ENOMEM;
90 	}
91 
92 	return 0;
93 }
94 
amd_pmf_get_metrics_table_log_sample(struct amd_pmf_dev * dev)95 static int amd_pmf_get_metrics_table_log_sample(struct amd_pmf_dev *dev)
96 {
97 	int ret;
98 
99 	if (!dev->metrics_table_virt) {
100 		dev_err(dev->dev, "Metrics DRAM not mapped\n");
101 		return -EINVAL;
102 	}
103 
104 	/* Send command to PMFW to update metrics table log sample */
105 	ret = amd_pmf_send_cmd(dev, GET_1AH_M80H_METRICS_TABLE_LOG_SAMPLE, SET_CMD, ARG_NONE, NULL);
106 	if (ret) {
107 		dev_err(dev->dev, "Failed to request metrics log sample: %d\n", ret);
108 		return ret;
109 	}
110 
111 	return 0;
112 }
113 
amd_pmf_get_metrics(struct work_struct * work)114 static void amd_pmf_get_metrics(struct work_struct *work)
115 {
116 	struct amd_pmf_dev *dev = container_of(work, struct amd_pmf_dev, work_buffer.work);
117 	ktime_t time_elapsed_ms;
118 	int socket_power;
119 
120 	guard(mutex)(&dev->update_mutex);
121 
122 	/* Transfer table contents */
123 	memset(dev->buf, 0, sizeof(dev->m_table));
124 	amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, SET_CMD, METRICS_TABLE_ID, NULL);
125 	memcpy(&dev->m_table, dev->buf, sizeof(dev->m_table));
126 
127 	time_elapsed_ms = ktime_to_ms(ktime_get()) - dev->start_time;
128 	/* Calculate the avg SoC power consumption */
129 	socket_power = dev->m_table.apu_power + dev->m_table.dgpu_power;
130 
131 	if (dev->amt_enabled) {
132 		/* Apply the Auto Mode transition */
133 		amd_pmf_trans_automode(dev, socket_power, time_elapsed_ms);
134 	}
135 
136 	if (dev->cnqf_enabled) {
137 		/* Apply the CnQF transition */
138 		amd_pmf_trans_cnqf(dev, socket_power, time_elapsed_ms);
139 	}
140 
141 	dev->start_time = ktime_to_ms(ktime_get());
142 	schedule_delayed_work(&dev->work_buffer, msecs_to_jiffies(metrics_table_loop_ms));
143 }
144 
amd_pmf_set_dram_addr(struct amd_pmf_dev * dev,bool alloc_buffer)145 int amd_pmf_set_dram_addr(struct amd_pmf_dev *dev, bool alloc_buffer)
146 {
147 	u64 phys_addr;
148 	u32 hi, low;
149 
150 	/* Get Metrics Table Address */
151 	if (alloc_buffer) {
152 		switch (dev->cpu_id) {
153 		case AMD_CPU_ID_PS:
154 		case AMD_CPU_ID_RMB:
155 			dev->mtable_size = sizeof(dev->m_table);
156 			break;
157 		case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT:
158 		case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT:
159 			dev->mtable_size = sizeof(dev->m_table_v2);
160 			break;
161 		default:
162 			dev_err(dev->dev, "Invalid CPU id: 0x%x\n", dev->cpu_id);
163 		}
164 
165 		dev->buf = devm_kzalloc(dev->dev, dev->mtable_size, GFP_KERNEL);
166 		if (!dev->buf)
167 			return -ENOMEM;
168 	}
169 
170 	phys_addr = virt_to_phys(dev->buf);
171 	hi = upper_32_bits(phys_addr);
172 	low = lower_32_bits(phys_addr);
173 
174 	amd_pmf_send_cmd(dev, SET_DRAM_ADDR_HIGH, SET_CMD, hi, NULL);
175 	amd_pmf_send_cmd(dev, SET_DRAM_ADDR_LOW, SET_CMD, low, NULL);
176 
177 	return 0;
178 }
179 
amd_pmf_init_metrics_table(struct amd_pmf_dev * dev)180 int amd_pmf_init_metrics_table(struct amd_pmf_dev *dev)
181 {
182 	int ret;
183 
184 	INIT_DELAYED_WORK(&dev->work_buffer, amd_pmf_get_metrics);
185 
186 	ret = amd_pmf_set_dram_addr(dev, true);
187 	if (ret)
188 		return ret;
189 
190 	/*
191 	 * Start collecting the metrics data after a small delay
192 	 * or else, we might end up getting stale values from PMFW.
193 	 */
194 	schedule_delayed_work(&dev->work_buffer, msecs_to_jiffies(metrics_table_loop_ms * 3));
195 
196 	return 0;
197 }
198 
amd_pmf_set_device(struct device * p_device)199 void amd_pmf_set_device(struct device *p_device)
200 {
201 	pmf_device = p_device;
202 }
203 
is_npu_metrics_supported(struct amd_pmf_dev * pdev)204 static int is_npu_metrics_supported(struct amd_pmf_dev *pdev)
205 {
206 	switch (pdev->cpu_id) {
207 	case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT:
208 	case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT:
209 	case PCI_DEVICE_ID_AMD_1AH_M80H_ROOT:
210 		return 0;
211 	default:
212 		return -EOPNOTSUPP;
213 	}
214 }
215 
amd_pmf_calculate_acc_npu_metrics(struct amd_pmf_dev * dev,struct amd_pmf_npu_metrics * data)216 static void amd_pmf_calculate_acc_npu_metrics(struct amd_pmf_dev *dev,
217 					      struct amd_pmf_npu_metrics *data)
218 {
219 	struct amd_pmf_metrics_iod *curr, *prev;
220 	u64 val;
221 	int i;
222 
223 	curr = &dev->mtable_v3.iod;
224 	prev = &dev->prev_metrics.iod;
225 
226 	val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc,
227 				     curr->npu_temp_acc, prev->npu_temp_acc);
228 	data->npu_temp = amd_pmf_q10_acc_to_int(val);
229 	val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc,
230 				     curr->npu_power_acc, prev->npu_power_acc);
231 	data->npu_power = amd_pmf_q10_acc_to_mW(val);
232 	val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc,
233 				     curr->npuhclk_freq_eff_acc,
234 				     prev->npuhclk_freq_eff_acc);
235 	data->mpnpuclk_freq = amd_pmf_q10_acc_to_int(val);
236 	val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc,
237 				     curr->aieclk_freq_eff_acc,
238 				     prev->aieclk_freq_eff_acc);
239 	data->npuclk_freq = amd_pmf_q10_acc_to_int(val);
240 
241 	for (i = 0; i < ARRAY_SIZE(curr->npu_busy_acc); i++) {
242 		val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc,
243 					     curr->npu_busy_acc[i],
244 					     prev->npu_busy_acc[i]);
245 		data->npu_busy[i] = amd_pmf_q10_acc_to_int(val);
246 	}
247 }
248 
amd_pmf_get_smu_metrics(struct amd_pmf_dev * dev,struct amd_pmf_npu_metrics * data)249 static int amd_pmf_get_smu_metrics(struct amd_pmf_dev *dev, struct amd_pmf_npu_metrics *data)
250 {
251 	int ret, i;
252 
253 	guard(mutex)(&dev->metrics_mutex);
254 
255 	ret = is_npu_metrics_supported(dev);
256 	if (ret)
257 		return ret;
258 
259 	memset(data, 0, sizeof(*data));
260 
261 	switch (dev->cpu_id) {
262 	case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT:
263 	case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT:
264 		ret = amd_pmf_set_dram_addr(dev, true);
265 		if (ret)
266 			return ret;
267 
268 		memset(dev->buf, 0, dev->mtable_size);
269 
270 		/* Send SMU command to get NPU metrics */
271 		ret = amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, SET_CMD, METRICS_TABLE_ID, NULL);
272 		if (ret) {
273 			dev_err(dev->dev, "SMU command failed to get NPU metrics: %d\n", ret);
274 			return ret;
275 		}
276 
277 		memcpy(&dev->m_table_v2, dev->buf, dev->mtable_size);
278 
279 		data->npuclk_freq = dev->m_table_v2.npuclk_freq;
280 		for (i = 0; i < ARRAY_SIZE(data->npu_busy); i++)
281 			data->npu_busy[i] = dev->m_table_v2.npu_busy[i];
282 		data->npu_power = dev->m_table_v2.npu_power;
283 		data->mpnpuclk_freq = dev->m_table_v2.mpnpuclk_freq;
284 		data->npu_reads = dev->m_table_v2.npu_reads;
285 		data->npu_writes = dev->m_table_v2.npu_writes;
286 		break;
287 	case PCI_DEVICE_ID_AMD_1AH_M80H_ROOT:
288 		ret = amd_pmf_get_metrics_table_log_sample(dev);
289 		if (ret)
290 			return ret;
291 
292 		memcpy_fromio(&dev->mtable_v3, dev->metrics_table_virt, sizeof(dev->mtable_v3));
293 
294 		/*
295 		 * Ignore the first sample, as previous metrics is uninitialized (zero)
296 		 * Metrics are calculated as:
297 		 *	metrics = current_metrics - previous_metrics
298 		 * Skipping the initial sample ensures accurate delta calculations.
299 		 */
300 		if (!dev->npu_metrics_have_prev) {
301 			memcpy(&dev->prev_metrics, &dev->mtable_v3, sizeof(dev->mtable_v3));
302 			dev->npu_metrics_have_prev = true;
303 			return 0;
304 		}
305 
306 		amd_pmf_calculate_acc_npu_metrics(dev, data);
307 		memcpy(&dev->prev_metrics, &dev->mtable_v3, sizeof(dev->mtable_v3));
308 		break;
309 	}
310 
311 	return 0;
312 }
313 
amd_pmf_get_npu_data(struct amd_pmf_npu_metrics * info)314 int amd_pmf_get_npu_data(struct amd_pmf_npu_metrics *info)
315 {
316 	struct amd_pmf_dev *pdev;
317 
318 	if (!info)
319 		return -EINVAL;
320 
321 	if (!pmf_device)
322 		return -ENODEV;
323 
324 	pdev = dev_get_drvdata(pmf_device);
325 	if (!pdev)
326 		return -ENODEV;
327 
328 	return amd_pmf_get_smu_metrics(pdev, info);
329 }
330 EXPORT_SYMBOL_NS_GPL(amd_pmf_get_npu_data, "AMD_PMF");
331