xref: /linux/arch/powerpc/platforms/pseries/htmdump.c (revision bf0e022821fa516cd6eb0292fcb4ccdd4e201c9f)
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Copyright (C) IBM Corporation, 2024
4  */
5 
6 #define pr_fmt(fmt) "htmdump: " fmt
7 
8 #include <linux/debugfs.h>
9 #include <linux/module.h>
10 #include <asm/io.h>
11 #include <asm/machdep.h>
12 #include <asm/plpar_wrappers.h>
13 #include <asm/kvm_guest.h>
14 
15 static void *htm_buf;
16 static void *htm_status_buf;
17 static void *htm_info_buf;
18 static void *htm_caps_buf;
19 static void *htm_mem_buf;
20 static u32 nodeindex;
21 static u32 nodalchipindex;
22 static u32 coreindexonchip;
23 static u32 htmtype;
24 static u32 htmconfigure;
25 static u32 htmstart;
26 static u32 htmsetup;
27 static u64 htmflags;
28 
29 static struct dentry *htmdump_debugfs_dir;
30 #define	HTM_ENABLE	1
31 #define	HTM_DISABLE	0
32 #define	HTM_NOWRAP	1
33 #define	HTM_WRAP	0
34 
35 /*
36  * Check the return code for H_HTM hcall.
37  * Return non-zero value (1) if either H_PARTIAL or H_SUCCESS
38  * is returned. For other return codes:
39  * Return zero if H_NOT_AVAILABLE.
40  * Return -EBUSY if hcall return busy.
41  * Return -EINVAL if any parameter or operation is not valid.
42  * Return -EPERM if HTM Virtualization Engine Technology code
43  * is not applied.
44  * Return -EIO if the HTM state is not valid.
45  */
htm_return_check(long rc)46 static ssize_t htm_return_check(long rc)
47 {
48 	switch (rc) {
49 	case H_SUCCESS:
50 	/* H_PARTIAL for the case where all available data can't be
51 	 * returned due to buffer size constraint.
52 	 */
53 	case H_PARTIAL:
54 		break;
55 	/* H_NOT_AVAILABLE indicates reading from an offset outside the range,
56 	 * i.e. past end of file.
57 	 */
58 	case H_NOT_AVAILABLE:
59 		return 0;
60 	case H_BUSY:
61 	case H_LONG_BUSY_ORDER_1_MSEC:
62 	case H_LONG_BUSY_ORDER_10_MSEC:
63 	case H_LONG_BUSY_ORDER_100_MSEC:
64 	case H_LONG_BUSY_ORDER_1_SEC:
65 	case H_LONG_BUSY_ORDER_10_SEC:
66 	case H_LONG_BUSY_ORDER_100_SEC:
67 		return -EBUSY;
68 	case H_PARAMETER:
69 	case H_P2:
70 	case H_P3:
71 	case H_P4:
72 	case H_P5:
73 	case H_P6:
74 		return -EINVAL;
75 	case H_STATE:
76 		return -EIO;
77 	case H_AUTHORITY:
78 		return -EPERM;
79 	}
80 
81 	/*
82 	 * Return 1 for H_SUCCESS/H_PARTIAL
83 	 */
84 	return 1;
85 }
86 
htmdump_read(struct file * filp,char __user * ubuf,size_t count,loff_t * ppos)87 static ssize_t htmdump_read(struct file *filp, char __user *ubuf,
88 			     size_t count, loff_t *ppos)
89 {
90 	void *htm_buf_data = filp->private_data;
91 	unsigned long page, read_size, available;
92 	loff_t offset;
93 	long rc, ret;
94 
95 	page = ALIGN_DOWN(*ppos, PAGE_SIZE);
96 	offset = (*ppos) % PAGE_SIZE;
97 
98 	/*
99 	 * Invoke H_HTM call with:
100 	 * - operation as htm dump (H_HTM_OP_DUMP_DATA)
101 	 * - last three values are address, size and offset
102 	 */
103 	rc = htm_hcall_wrapper(htmflags, nodeindex, nodalchipindex, coreindexonchip,
104 				   htmtype, H_HTM_OP_DUMP_DATA, virt_to_phys(htm_buf_data),
105 				   PAGE_SIZE, page);
106 
107 	ret = htm_return_check(rc);
108 	if (ret <= 0) {
109 		pr_debug("H_HTM hcall failed for op: H_HTM_OP_DUMP_DATA, returning %ld\n", ret);
110 		return ret;
111 	}
112 
113 	available = PAGE_SIZE;
114 	read_size = min(count, available);
115 	*ppos += read_size;
116 	return simple_read_from_buffer(ubuf, count, &offset, htm_buf_data, available);
117 }
118 
htmsystem_mem_read(struct file * filp,char __user * ubuf,size_t count,loff_t * ppos)119 static ssize_t htmsystem_mem_read(struct file *filp, char __user *ubuf,
120 		size_t count, loff_t *ppos)
121 {
122 	void *htm_mem_data = filp->private_data;
123 	long rc, ret;
124 	u64 *num_entries;
125 	u64 to_copy = 0;
126 	loff_t offset = 0;
127 	u64 mem_offset = 0;
128 
129 	/*
130 	 * Invoke H_HTM call with:
131 	 * - operation as htm status (H_HTM_OP_STATUS)
132 	 * - last three values as addr, size and offset. "offset"
133 	 *   is value from output buffer header that points to next
134 	 *   entry to dump. 0 is the first entry to dump. next entry
135 	 *   is read from the output bufferbyte offset 0x8.
136 	 *
137 	 *   When first time hcall is invoked, mem_offset should be
138 	 *   zero because zero is the first entry.
139 	 *   In the next hcall, offset of next entry to read from is
140 	 *   picked from output buffer header itself. So don't fill
141 	 *   mem_offset for first read.
142 	 *
143 	 *  If there is no further data to read in next iteration,
144 	 *  offset value from output buffer header will point to -1.
145 	 */
146 	if (*ppos) {
147 		mem_offset = *(u64 *)(htm_mem_data  + 0x8);
148 		if (mem_offset == -1)
149 			return 0;
150 	}
151 	rc = htm_hcall_wrapper(htmflags, nodeindex, nodalchipindex, coreindexonchip,
152 			htmtype, H_HTM_OP_DUMP_SYSMEM_CONF, virt_to_phys(htm_mem_data),
153 			PAGE_SIZE, be64_to_cpu(mem_offset));
154 	ret = htm_return_check(rc);
155 	if (ret <= 0) {
156 		pr_debug("H_HTM hcall returned for op: H_HTM_OP_DUMP_SYSMEM_CONF with hcall returning  %ld\n", ret);
157 		return ret;
158 	}
159 
160 	/*
161 	 * HTM system mem buffer, start of buffer + 0x10 gives the
162 	 * number of HTM entries in the buffer.
163 	 * So total count to copy is:
164 	 * 32 bytes (for first 5 fields) + (number of HTM entries * entry size)
165 	 */
166 	num_entries = htm_mem_data + 0x10;
167 	to_copy = 32 + (be64_to_cpu(*num_entries) * 32);
168 
169 	*ppos += to_copy;
170 	return simple_read_from_buffer(ubuf, count, &offset, htm_mem_data, to_copy);
171 }
172 
173 static const struct file_operations htmdump_fops = {
174 	.llseek = NULL,
175 	.read	= htmdump_read,
176 	.open	= simple_open,
177 };
178 
179 static const struct file_operations htmsystem_mem_fops = {
180 	.llseek = NULL,
181 	.read   = htmsystem_mem_read,
182 	.open   = simple_open,
183 };
184 
htmconfigure_set(void * data,u64 val)185 static int  htmconfigure_set(void *data, u64 val)
186 {
187 	long rc, ret;
188 	unsigned long param1 = -1, param2 = -1;
189 
190 	/*
191 	 * value as 1 : configure HTM.
192 	 * value as 0 : deconfigure HTM. Return -EINVAL for
193 	 * other values.
194 	 */
195 	if (val == HTM_ENABLE) {
196 		/*
197 		 * Invoke H_HTM call with:
198 		 * - operation as htm configure (H_HTM_OP_CONFIGURE)
199 		 * - If htmflags is set, param1 and param2 will be -1
200 		 *   which is an indicator to use default htm mode reg mask
201 		 *   and htm mode reg value.
202 		 * - last three values are unused, hence set to zero
203 		 */
204 		if (!htmflags) {
205 			param1 = 0;
206 			param2 = 0;
207 		}
208 
209 		rc = htm_hcall_wrapper(htmflags, nodeindex, nodalchipindex, coreindexonchip,
210 			   htmtype, H_HTM_OP_CONFIGURE, param1, param2, 0);
211 	} else if (val == HTM_DISABLE) {
212 		/*
213 		 * Invoke H_HTM call with:
214 		 * - operation as htm deconfigure (H_HTM_OP_DECONFIGURE)
215 		 * - last three values are unused, hence set to zero
216 		 */
217 		rc = htm_hcall_wrapper(htmflags, nodeindex, nodalchipindex, coreindexonchip,
218 				htmtype, H_HTM_OP_DECONFIGURE, 0, 0, 0);
219 	} else
220 		return -EINVAL;
221 
222 	ret = htm_return_check(rc);
223 	if (ret <= 0) {
224 		pr_debug("H_HTM hcall failed, returning %ld\n", ret);
225 		return ret;
226 	}
227 
228 	/* Set htmconfigure if operation succeeds */
229 	htmconfigure = val;
230 
231 	return 0;
232 }
233 
htmconfigure_get(void * data,u64 * val)234 static int htmconfigure_get(void *data, u64 *val)
235 {
236 	*val = htmconfigure;
237 	return 0;
238 }
239 
htmstart_set(void * data,u64 val)240 static int  htmstart_set(void *data, u64 val)
241 {
242 	long rc, ret;
243 
244 	/*
245 	 * value as 1: start HTM
246 	 * value as 0: stop HTM
247 	 * Return -EINVAL for other values.
248 	 */
249 	if (val == HTM_ENABLE) {
250 		/*
251 		 * Invoke H_HTM call with:
252 		 * - operation as htm start (H_HTM_OP_START)
253 		 * - last three values are unused, hence set to zero
254 		 */
255 		rc = htm_hcall_wrapper(htmflags, nodeindex, nodalchipindex, coreindexonchip,
256 			   htmtype, H_HTM_OP_START, 0, 0, 0);
257 
258 	} else if (val == HTM_DISABLE) {
259 		/*
260 		 * Invoke H_HTM call with:
261 		 * - operation as htm stop (H_HTM_OP_STOP)
262 		 * - last three values are unused, hence set to zero
263 		 */
264 		rc = htm_hcall_wrapper(htmflags, nodeindex, nodalchipindex, coreindexonchip,
265 				htmtype, H_HTM_OP_STOP, 0, 0, 0);
266 	} else
267 		return -EINVAL;
268 
269 	ret = htm_return_check(rc);
270 	if (ret <= 0) {
271 		pr_debug("H_HTM hcall failed, returning %ld\n", ret);
272 		return ret;
273 	}
274 
275 	/* Set htmstart if H_HTM_OP_START/H_HTM_OP_STOP operation succeeds */
276 	htmstart = val;
277 
278 	return 0;
279 }
280 
htmstart_get(void * data,u64 * val)281 static int htmstart_get(void *data, u64 *val)
282 {
283 	*val = htmstart;
284 	return 0;
285 }
286 
htmstatus_read(struct file * filp,char __user * ubuf,size_t count,loff_t * ppos)287 static ssize_t htmstatus_read(struct file *filp, char __user *ubuf,
288 			     size_t count, loff_t *ppos)
289 {
290 	void *htm_status_data = filp->private_data;
291 	long rc, ret;
292 	u64 *num_entries;
293 	u64 to_copy;
294 	int htmstatus_flag;
295 	loff_t offset = 0;
296 	u64 status_offset = 0;
297 
298 	/*
299 	 * Invoke H_HTM call with:
300 	 * - operation as htm status (H_HTM_OP_STATUS)
301 	 * - last three values as addr, size and offset.
302 	 *   "offset" is value from output buffer header
303 	 *   that points to next entry to dump. 0 is the first
304 	 *   entry to dump. next entry is read from the output
305 	 *   bufferbyte offset 0x8.
306 	 */
307 	if (*ppos) {
308 		status_offset = *(u64 *)(htm_status_data + 0x8);
309 		if (status_offset == -1)
310 			return 0;
311 	}
312 	rc = htm_hcall_wrapper(htmflags, nodeindex, nodalchipindex, coreindexonchip,
313 				   htmtype, H_HTM_OP_STATUS, virt_to_phys(htm_status_data),
314 				   PAGE_SIZE, be64_to_cpu(status_offset));
315 
316 	ret = htm_return_check(rc);
317 	if (ret <= 0) {
318 		pr_debug("H_HTM hcall failed for op: H_HTM_OP_STATUS, returning %ld\n", ret);
319 		return ret;
320 	}
321 
322 	/*
323 	 * HTM status buffer, start of buffer + 0x10 gives the
324 	 * number of HTM entries in the buffer. Each nest htm status
325 	 * entry is 0x6 bytes where each core htm status entry is
326 	 * 0x8 bytes.
327 	 * So total count to copy is:
328 	 * 32 bytes (for first 7 fields) + (number of HTM entries * entry size)
329 	 */
330 	num_entries = htm_status_data + 0x10;
331 	if (htmtype == 0x2)
332 		htmstatus_flag = 0x8;
333 	else
334 		htmstatus_flag = 0x6;
335 	to_copy = 32 + (be64_to_cpu(*num_entries) * htmstatus_flag);
336 	*ppos += to_copy;
337 
338 	return simple_read_from_buffer(ubuf, count, &offset, htm_status_data, to_copy);
339 }
340 
341 static const struct file_operations htmstatus_fops = {
342 	.llseek = NULL,
343 	.read	= htmstatus_read,
344 	.open	= simple_open,
345 };
346 
htminfo_read(struct file * filp,char __user * ubuf,size_t count,loff_t * ppos)347 static ssize_t htminfo_read(struct file *filp, char __user *ubuf,
348 			     size_t count, loff_t *ppos)
349 {
350 	void *htm_info_data = filp->private_data;
351 	long rc, ret;
352 	u64 *num_entries;
353 	u64 to_copy;
354 	loff_t offset = 0;
355 	u64 info_offset = 0;
356 
357 	/*
358 	 * Invoke H_HTM call with:
359 	 * - operation as htm status (H_HTM_OP_STATUS)
360 	 * - last three values as addr, size and offset
361 	 *   "offset" is value from output buffer header
362 	 *   that points to next entry to dump. 0 is the first
363 	 *   entry to dump. next entry is read from the output
364 	 *   bufferbyte offset 0x8.
365 	 */
366 	if (*ppos) {
367 		info_offset = *(u64 *)(htm_info_data + 0x8);
368 		if (info_offset == -1)
369 			return 0;
370 	}
371 	rc = htm_hcall_wrapper(htmflags, nodeindex, nodalchipindex, coreindexonchip,
372 				   htmtype, H_HTM_OP_DUMP_SYSPROC_CONF, virt_to_phys(htm_info_data),
373 				   PAGE_SIZE, be64_to_cpu(info_offset));
374 
375 	ret = htm_return_check(rc);
376 	if (ret <= 0) {
377 		pr_debug("H_HTM hcall failed for op: H_HTM_OP_DUMP_SYSPROC_CONF, returning %ld\n", ret);
378 		return ret;
379 	}
380 
381 	/*
382 	 * HTM status buffer, start of buffer + 0x10 gives the
383 	 * number of HTM entries in the buffer. Each entry of processor
384 	 * is 16 bytes.
385 	 *
386 	 * So total count to copy is:
387 	 * 32 bytes (for first 5 fields) + (number of HTM entries * entry size)
388 	 */
389 	num_entries = htm_info_data + 0x10;
390 	to_copy = 32 + (be64_to_cpu(*num_entries) * 16);
391 
392 	*ppos += to_copy;
393 	return simple_read_from_buffer(ubuf, count, &offset, htm_info_data, to_copy);
394 }
395 
htmcaps_read(struct file * filp,char __user * ubuf,size_t count,loff_t * ppos)396 static ssize_t htmcaps_read(struct file *filp, char __user *ubuf,
397 			     size_t count, loff_t *ppos)
398 {
399 	void *htm_caps_data = filp->private_data;
400 	long rc, ret;
401 
402 	/*
403 	 * Invoke H_HTM call with:
404 	 * - operation as htm capabilities (H_HTM_OP_CAPABILITIES)
405 	 * - last three values as addr, size (0x80 for Capabilities Output Buffer
406 	 *   and zero
407 	 */
408 	rc = htm_hcall_wrapper(htmflags, nodeindex, nodalchipindex, coreindexonchip,
409 				   htmtype, H_HTM_OP_CAPABILITIES, virt_to_phys(htm_caps_data),
410 				   0x80, 0);
411 
412 	ret = htm_return_check(rc);
413 	if (ret <= 0) {
414 		pr_debug("H_HTM hcall failed for op: H_HTM_OP_CAPABILITIES, returning %ld\n", ret);
415 		return ret;
416 	}
417 
418 	return simple_read_from_buffer(ubuf, count, ppos, htm_caps_data, 0x80);
419 }
420 
421 static const struct file_operations htminfo_fops = {
422 	.llseek = NULL,
423 	.read   = htminfo_read,
424 	.open   = simple_open,
425 };
426 
427 static const struct file_operations htmcaps_fops = {
428 	.llseek = NULL,
429 	.read   = htmcaps_read,
430 	.open   = simple_open,
431 };
432 
htmsetup_set(void * data,u64 val)433 static int  htmsetup_set(void *data, u64 val)
434 {
435 	long rc, ret;
436 
437 	/*
438 	 * Input value: HTM buffer size in the power of 2
439 	 * example: hex value 0x21 ( decimal: 33 ) is for
440 	 * 8GB
441 	 * Invoke H_HTM call with:
442 	 * - operation as htm start (H_HTM_OP_SETUP)
443 	 * - parameter 1 set to input value.
444 	 * - last two values are unused, hence set to zero
445 	 */
446 	rc = htm_hcall_wrapper(htmflags, nodeindex, nodalchipindex, coreindexonchip,
447 			htmtype, H_HTM_OP_SETUP, val, 0, 0);
448 
449 	ret = htm_return_check(rc);
450 	if (ret <= 0) {
451 		pr_debug("H_HTM hcall failed for op: H_HTM_OP_SETUP, returning %ld\n", ret);
452 		return ret;
453 	}
454 
455 	/* Set htmsetup if H_HTM_OP_SETUP operation succeeds */
456 	htmsetup = val;
457 
458 	return 0;
459 }
460 
htmsetup_get(void * data,u64 * val)461 static int htmsetup_get(void *data, u64 *val)
462 {
463 	*val = htmsetup;
464 	return 0;
465 }
466 
htmflags_set(void * data,u64 val)467 static int  htmflags_set(void *data, u64 val)
468 {
469 	/*
470 	 * Input value:
471 	 * Currently supported flag value is to enable/disable
472 	 * HTM buffer wrap. wrap is used along with "configure"
473 	 * to prevent HTM buffer from wrapping.
474 	 * Writing 1 will set noWrap while configuring HTM
475 	 */
476 	if (val == HTM_NOWRAP)
477 		htmflags = H_HTM_FLAGS_NOWRAP;
478 	else if (val == HTM_WRAP)
479 		htmflags = 0;
480 	else
481 		return -EINVAL;
482 
483 	return 0;
484 }
485 
htmflags_get(void * data,u64 * val)486 static int htmflags_get(void *data, u64 *val)
487 {
488 	*val = htmflags;
489 	return 0;
490 }
491 
492 DEFINE_SIMPLE_ATTRIBUTE(htmconfigure_fops, htmconfigure_get, htmconfigure_set, "%llu\n");
493 DEFINE_SIMPLE_ATTRIBUTE(htmstart_fops, htmstart_get, htmstart_set, "%llu\n");
494 DEFINE_SIMPLE_ATTRIBUTE(htmsetup_fops, htmsetup_get, htmsetup_set, "%llu\n");
495 DEFINE_SIMPLE_ATTRIBUTE(htmflags_fops, htmflags_get, htmflags_set, "%llu\n");
496 
htmdump_init_debugfs(void)497 static int htmdump_init_debugfs(void)
498 {
499 	htm_buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
500 	if (!htm_buf) {
501 		pr_err("Failed to allocate htmdump buf\n");
502 		return -ENOMEM;
503 	}
504 
505 	htmdump_debugfs_dir = debugfs_create_dir("htmdump",
506 						  arch_debugfs_dir);
507 
508 	debugfs_create_u32("nodeindex", 0600,
509 			htmdump_debugfs_dir, &nodeindex);
510 	debugfs_create_u32("nodalchipindex", 0600,
511 			htmdump_debugfs_dir, &nodalchipindex);
512 	debugfs_create_u32("coreindexonchip", 0600,
513 			htmdump_debugfs_dir, &coreindexonchip);
514 	debugfs_create_u32("htmtype", 0600,
515 			htmdump_debugfs_dir, &htmtype);
516 	debugfs_create_file("trace", 0400, htmdump_debugfs_dir, htm_buf, &htmdump_fops);
517 
518 	/*
519 	 * Debugfs interface files to control HTM operations:
520 	 */
521 	debugfs_create_file("htmconfigure", 0600, htmdump_debugfs_dir, NULL, &htmconfigure_fops);
522 	debugfs_create_file("htmstart", 0600, htmdump_debugfs_dir, NULL, &htmstart_fops);
523 	debugfs_create_file("htmsetup", 0600, htmdump_debugfs_dir, NULL, &htmsetup_fops);
524 	debugfs_create_file("htmflags", 0600, htmdump_debugfs_dir, NULL, &htmflags_fops);
525 
526 	/* Debugfs interface file to present status of HTM */
527 	htm_status_buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
528 	if (!htm_status_buf) {
529 		pr_err("Failed to allocate htmstatus buf\n");
530 		return -ENOMEM;
531 	}
532 
533 	/* Debugfs interface file to present System Processor Configuration */
534 	htm_info_buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
535 	if (!htm_info_buf) {
536 		pr_err("Failed to allocate htm info buf\n");
537 		return -ENOMEM;
538 	}
539 
540 	/* Debugfs interface file to present HTM capabilities */
541 	htm_caps_buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
542 	if (!htm_caps_buf) {
543 		pr_err("Failed to allocate htm caps buf\n");
544 		return -ENOMEM;
545 	}
546 
547 	/* Memory to present HTM system memory configuration */
548 	htm_mem_buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
549 	if (!htm_mem_buf) {
550 		pr_err("Failed to allocate htm mem buf\n");
551 		return -ENOMEM;
552 	}
553 
554 	debugfs_create_file("htmstatus", 0400, htmdump_debugfs_dir, htm_status_buf, &htmstatus_fops);
555 	debugfs_create_file("htminfo", 0400, htmdump_debugfs_dir, htm_info_buf, &htminfo_fops);
556 	debugfs_create_file("htmcaps", 0400, htmdump_debugfs_dir, htm_caps_buf, &htmcaps_fops);
557 	debugfs_create_file("htmsystem_mem", 0400, htmdump_debugfs_dir, htm_mem_buf, &htmsystem_mem_fops);
558 
559 	return 0;
560 }
561 
htmdump_init(void)562 static int __init htmdump_init(void)
563 {
564 	/* Disable on kvm guest */
565 	if (is_kvm_guest()) {
566 		pr_info("htmdump not supported inside KVM guest\n");
567 		return -EOPNOTSUPP;
568 	}
569 
570 	if (htmdump_init_debugfs())
571 		return -ENOMEM;
572 
573 	return 0;
574 }
575 
htmdump_exit(void)576 static void __exit htmdump_exit(void)
577 {
578 	debugfs_remove_recursive(htmdump_debugfs_dir);
579 	kfree(htm_buf);
580 	kfree(htm_status_buf);
581 	kfree(htm_info_buf);
582 	kfree(htm_caps_buf);
583 	kfree(htm_mem_buf);
584 }
585 
586 module_init(htmdump_init);
587 module_exit(htmdump_exit);
588 MODULE_DESCRIPTION("PHYP Hardware Trace Macro (HTM) data dumper");
589 MODULE_LICENSE("GPL");
590