18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci *
48c2ecf20Sopenharmony_ci * Copyright (C) IBM Corporation, 2005
58c2ecf20Sopenharmony_ci *               Jeff Muizelaar, 2006, 2007
68c2ecf20Sopenharmony_ci *               Pekka Paalanen, 2008 <pq@iki.fi>
78c2ecf20Sopenharmony_ci *
88c2ecf20Sopenharmony_ci * Derived from the read-mod example from relay-examples by Tom Zanussi.
98c2ecf20Sopenharmony_ci */
108c2ecf20Sopenharmony_ci
118c2ecf20Sopenharmony_ci#define pr_fmt(fmt) "mmiotrace: " fmt
128c2ecf20Sopenharmony_ci
138c2ecf20Sopenharmony_ci#define DEBUG 1
148c2ecf20Sopenharmony_ci
158c2ecf20Sopenharmony_ci#include <linux/moduleparam.h>
168c2ecf20Sopenharmony_ci#include <linux/debugfs.h>
178c2ecf20Sopenharmony_ci#include <linux/slab.h>
188c2ecf20Sopenharmony_ci#include <linux/uaccess.h>
198c2ecf20Sopenharmony_ci#include <linux/io.h>
208c2ecf20Sopenharmony_ci#include <linux/mmiotrace.h>
218c2ecf20Sopenharmony_ci#include <linux/pgtable.h>
228c2ecf20Sopenharmony_ci#include <asm/e820/api.h> /* for ISA_START_ADDRESS */
238c2ecf20Sopenharmony_ci#include <linux/atomic.h>
248c2ecf20Sopenharmony_ci#include <linux/percpu.h>
258c2ecf20Sopenharmony_ci#include <linux/cpu.h>
268c2ecf20Sopenharmony_ci
278c2ecf20Sopenharmony_ci#include "pf_in.h"
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_cistruct trap_reason {
308c2ecf20Sopenharmony_ci	unsigned long addr;
318c2ecf20Sopenharmony_ci	unsigned long ip;
328c2ecf20Sopenharmony_ci	enum reason_type type;
338c2ecf20Sopenharmony_ci	int active_traces;
348c2ecf20Sopenharmony_ci};
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_cistruct remap_trace {
378c2ecf20Sopenharmony_ci	struct list_head list;
388c2ecf20Sopenharmony_ci	struct kmmio_probe probe;
398c2ecf20Sopenharmony_ci	resource_size_t phys;
408c2ecf20Sopenharmony_ci	unsigned long id;
418c2ecf20Sopenharmony_ci};
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_ci/* Accessed per-cpu. */
448c2ecf20Sopenharmony_cistatic DEFINE_PER_CPU(struct trap_reason, pf_reason);
458c2ecf20Sopenharmony_cistatic DEFINE_PER_CPU(struct mmiotrace_rw, cpu_trace);
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_cistatic DEFINE_MUTEX(mmiotrace_mutex);
488c2ecf20Sopenharmony_cistatic DEFINE_SPINLOCK(trace_lock);
498c2ecf20Sopenharmony_cistatic atomic_t mmiotrace_enabled;
508c2ecf20Sopenharmony_cistatic LIST_HEAD(trace_list);		/* struct remap_trace */
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_ci/*
538c2ecf20Sopenharmony_ci * Locking in this file:
548c2ecf20Sopenharmony_ci * - mmiotrace_mutex enforces enable/disable_mmiotrace() critical sections.
558c2ecf20Sopenharmony_ci * - mmiotrace_enabled may be modified only when holding mmiotrace_mutex
568c2ecf20Sopenharmony_ci *   and trace_lock.
578c2ecf20Sopenharmony_ci * - Routines depending on is_enabled() must take trace_lock.
588c2ecf20Sopenharmony_ci * - trace_list users must hold trace_lock.
598c2ecf20Sopenharmony_ci * - is_enabled() guarantees that mmio_trace_{rw,mapping} are allowed.
608c2ecf20Sopenharmony_ci * - pre/post callbacks assume the effect of is_enabled() being true.
618c2ecf20Sopenharmony_ci */
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_ci/* module parameters */
648c2ecf20Sopenharmony_cistatic unsigned long	filter_offset;
658c2ecf20Sopenharmony_cistatic bool		nommiotrace;
668c2ecf20Sopenharmony_cistatic bool		trace_pc;
678c2ecf20Sopenharmony_ci
688c2ecf20Sopenharmony_cimodule_param(filter_offset, ulong, 0);
698c2ecf20Sopenharmony_cimodule_param(nommiotrace, bool, 0);
708c2ecf20Sopenharmony_cimodule_param(trace_pc, bool, 0);
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_ciMODULE_PARM_DESC(filter_offset, "Start address of traced mappings.");
738c2ecf20Sopenharmony_ciMODULE_PARM_DESC(nommiotrace, "Disable actual MMIO tracing.");
748c2ecf20Sopenharmony_ciMODULE_PARM_DESC(trace_pc, "Record address of faulting instructions.");
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_cistatic bool is_enabled(void)
778c2ecf20Sopenharmony_ci{
788c2ecf20Sopenharmony_ci	return atomic_read(&mmiotrace_enabled);
798c2ecf20Sopenharmony_ci}
808c2ecf20Sopenharmony_ci
818c2ecf20Sopenharmony_cistatic void print_pte(unsigned long address)
828c2ecf20Sopenharmony_ci{
838c2ecf20Sopenharmony_ci	unsigned int level;
848c2ecf20Sopenharmony_ci	pte_t *pte = lookup_address(address, &level);
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_ci	if (!pte) {
878c2ecf20Sopenharmony_ci		pr_err("Error in %s: no pte for page 0x%08lx\n",
888c2ecf20Sopenharmony_ci		       __func__, address);
898c2ecf20Sopenharmony_ci		return;
908c2ecf20Sopenharmony_ci	}
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_ci	if (level == PG_LEVEL_2M) {
938c2ecf20Sopenharmony_ci		pr_emerg("4MB pages are not currently supported: 0x%08lx\n",
948c2ecf20Sopenharmony_ci			 address);
958c2ecf20Sopenharmony_ci		BUG();
968c2ecf20Sopenharmony_ci	}
978c2ecf20Sopenharmony_ci	pr_info("pte for 0x%lx: 0x%llx 0x%llx\n",
988c2ecf20Sopenharmony_ci		address,
998c2ecf20Sopenharmony_ci		(unsigned long long)pte_val(*pte),
1008c2ecf20Sopenharmony_ci		(unsigned long long)pte_val(*pte) & _PAGE_PRESENT);
1018c2ecf20Sopenharmony_ci}
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_ci/*
1048c2ecf20Sopenharmony_ci * For some reason the pre/post pairs have been called in an
1058c2ecf20Sopenharmony_ci * unmatched order. Report and die.
1068c2ecf20Sopenharmony_ci */
1078c2ecf20Sopenharmony_cistatic void die_kmmio_nesting_error(struct pt_regs *regs, unsigned long addr)
1088c2ecf20Sopenharmony_ci{
1098c2ecf20Sopenharmony_ci	const struct trap_reason *my_reason = &get_cpu_var(pf_reason);
1108c2ecf20Sopenharmony_ci	pr_emerg("unexpected fault for address: 0x%08lx, last fault for address: 0x%08lx\n",
1118c2ecf20Sopenharmony_ci		 addr, my_reason->addr);
1128c2ecf20Sopenharmony_ci	print_pte(addr);
1138c2ecf20Sopenharmony_ci	pr_emerg("faulting IP is at %pS\n", (void *)regs->ip);
1148c2ecf20Sopenharmony_ci	pr_emerg("last faulting IP was at %pS\n", (void *)my_reason->ip);
1158c2ecf20Sopenharmony_ci#ifdef __i386__
1168c2ecf20Sopenharmony_ci	pr_emerg("eax: %08lx   ebx: %08lx   ecx: %08lx   edx: %08lx\n",
1178c2ecf20Sopenharmony_ci		 regs->ax, regs->bx, regs->cx, regs->dx);
1188c2ecf20Sopenharmony_ci	pr_emerg("esi: %08lx   edi: %08lx   ebp: %08lx   esp: %08lx\n",
1198c2ecf20Sopenharmony_ci		 regs->si, regs->di, regs->bp, regs->sp);
1208c2ecf20Sopenharmony_ci#else
1218c2ecf20Sopenharmony_ci	pr_emerg("rax: %016lx   rcx: %016lx   rdx: %016lx\n",
1228c2ecf20Sopenharmony_ci		 regs->ax, regs->cx, regs->dx);
1238c2ecf20Sopenharmony_ci	pr_emerg("rsi: %016lx   rdi: %016lx   rbp: %016lx   rsp: %016lx\n",
1248c2ecf20Sopenharmony_ci		 regs->si, regs->di, regs->bp, regs->sp);
1258c2ecf20Sopenharmony_ci#endif
1268c2ecf20Sopenharmony_ci	put_cpu_var(pf_reason);
1278c2ecf20Sopenharmony_ci	BUG();
1288c2ecf20Sopenharmony_ci}
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_cistatic void pre(struct kmmio_probe *p, struct pt_regs *regs,
1318c2ecf20Sopenharmony_ci						unsigned long addr)
1328c2ecf20Sopenharmony_ci{
1338c2ecf20Sopenharmony_ci	struct trap_reason *my_reason = &get_cpu_var(pf_reason);
1348c2ecf20Sopenharmony_ci	struct mmiotrace_rw *my_trace = &get_cpu_var(cpu_trace);
1358c2ecf20Sopenharmony_ci	const unsigned long instptr = instruction_pointer(regs);
1368c2ecf20Sopenharmony_ci	const enum reason_type type = get_ins_type(instptr);
1378c2ecf20Sopenharmony_ci	struct remap_trace *trace = p->private;
1388c2ecf20Sopenharmony_ci
1398c2ecf20Sopenharmony_ci	/* it doesn't make sense to have more than one active trace per cpu */
1408c2ecf20Sopenharmony_ci	if (my_reason->active_traces)
1418c2ecf20Sopenharmony_ci		die_kmmio_nesting_error(regs, addr);
1428c2ecf20Sopenharmony_ci	else
1438c2ecf20Sopenharmony_ci		my_reason->active_traces++;
1448c2ecf20Sopenharmony_ci
1458c2ecf20Sopenharmony_ci	my_reason->type = type;
1468c2ecf20Sopenharmony_ci	my_reason->addr = addr;
1478c2ecf20Sopenharmony_ci	my_reason->ip = instptr;
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_ci	my_trace->phys = addr - trace->probe.addr + trace->phys;
1508c2ecf20Sopenharmony_ci	my_trace->map_id = trace->id;
1518c2ecf20Sopenharmony_ci
1528c2ecf20Sopenharmony_ci	/*
1538c2ecf20Sopenharmony_ci	 * Only record the program counter when requested.
1548c2ecf20Sopenharmony_ci	 * It may taint clean-room reverse engineering.
1558c2ecf20Sopenharmony_ci	 */
1568c2ecf20Sopenharmony_ci	if (trace_pc)
1578c2ecf20Sopenharmony_ci		my_trace->pc = instptr;
1588c2ecf20Sopenharmony_ci	else
1598c2ecf20Sopenharmony_ci		my_trace->pc = 0;
1608c2ecf20Sopenharmony_ci
1618c2ecf20Sopenharmony_ci	/*
1628c2ecf20Sopenharmony_ci	 * XXX: the timestamp recorded will be *after* the tracing has been
1638c2ecf20Sopenharmony_ci	 * done, not at the time we hit the instruction. SMP implications
1648c2ecf20Sopenharmony_ci	 * on event ordering?
1658c2ecf20Sopenharmony_ci	 */
1668c2ecf20Sopenharmony_ci
1678c2ecf20Sopenharmony_ci	switch (type) {
1688c2ecf20Sopenharmony_ci	case REG_READ:
1698c2ecf20Sopenharmony_ci		my_trace->opcode = MMIO_READ;
1708c2ecf20Sopenharmony_ci		my_trace->width = get_ins_mem_width(instptr);
1718c2ecf20Sopenharmony_ci		break;
1728c2ecf20Sopenharmony_ci	case REG_WRITE:
1738c2ecf20Sopenharmony_ci		my_trace->opcode = MMIO_WRITE;
1748c2ecf20Sopenharmony_ci		my_trace->width = get_ins_mem_width(instptr);
1758c2ecf20Sopenharmony_ci		my_trace->value = get_ins_reg_val(instptr, regs);
1768c2ecf20Sopenharmony_ci		break;
1778c2ecf20Sopenharmony_ci	case IMM_WRITE:
1788c2ecf20Sopenharmony_ci		my_trace->opcode = MMIO_WRITE;
1798c2ecf20Sopenharmony_ci		my_trace->width = get_ins_mem_width(instptr);
1808c2ecf20Sopenharmony_ci		my_trace->value = get_ins_imm_val(instptr);
1818c2ecf20Sopenharmony_ci		break;
1828c2ecf20Sopenharmony_ci	default:
1838c2ecf20Sopenharmony_ci		{
1848c2ecf20Sopenharmony_ci			unsigned char *ip = (unsigned char *)instptr;
1858c2ecf20Sopenharmony_ci			my_trace->opcode = MMIO_UNKNOWN_OP;
1868c2ecf20Sopenharmony_ci			my_trace->width = 0;
1878c2ecf20Sopenharmony_ci			my_trace->value = (*ip) << 16 | *(ip + 1) << 8 |
1888c2ecf20Sopenharmony_ci								*(ip + 2);
1898c2ecf20Sopenharmony_ci		}
1908c2ecf20Sopenharmony_ci	}
1918c2ecf20Sopenharmony_ci	put_cpu_var(cpu_trace);
1928c2ecf20Sopenharmony_ci	put_cpu_var(pf_reason);
1938c2ecf20Sopenharmony_ci}
1948c2ecf20Sopenharmony_ci
1958c2ecf20Sopenharmony_cistatic void post(struct kmmio_probe *p, unsigned long condition,
1968c2ecf20Sopenharmony_ci							struct pt_regs *regs)
1978c2ecf20Sopenharmony_ci{
1988c2ecf20Sopenharmony_ci	struct trap_reason *my_reason = &get_cpu_var(pf_reason);
1998c2ecf20Sopenharmony_ci	struct mmiotrace_rw *my_trace = &get_cpu_var(cpu_trace);
2008c2ecf20Sopenharmony_ci
2018c2ecf20Sopenharmony_ci	/* this should always return the active_trace count to 0 */
2028c2ecf20Sopenharmony_ci	my_reason->active_traces--;
2038c2ecf20Sopenharmony_ci	if (my_reason->active_traces) {
2048c2ecf20Sopenharmony_ci		pr_emerg("unexpected post handler");
2058c2ecf20Sopenharmony_ci		BUG();
2068c2ecf20Sopenharmony_ci	}
2078c2ecf20Sopenharmony_ci
2088c2ecf20Sopenharmony_ci	switch (my_reason->type) {
2098c2ecf20Sopenharmony_ci	case REG_READ:
2108c2ecf20Sopenharmony_ci		my_trace->value = get_ins_reg_val(my_reason->ip, regs);
2118c2ecf20Sopenharmony_ci		break;
2128c2ecf20Sopenharmony_ci	default:
2138c2ecf20Sopenharmony_ci		break;
2148c2ecf20Sopenharmony_ci	}
2158c2ecf20Sopenharmony_ci
2168c2ecf20Sopenharmony_ci	mmio_trace_rw(my_trace);
2178c2ecf20Sopenharmony_ci	put_cpu_var(cpu_trace);
2188c2ecf20Sopenharmony_ci	put_cpu_var(pf_reason);
2198c2ecf20Sopenharmony_ci}
2208c2ecf20Sopenharmony_ci
2218c2ecf20Sopenharmony_cistatic void ioremap_trace_core(resource_size_t offset, unsigned long size,
2228c2ecf20Sopenharmony_ci							void __iomem *addr)
2238c2ecf20Sopenharmony_ci{
2248c2ecf20Sopenharmony_ci	static atomic_t next_id;
2258c2ecf20Sopenharmony_ci	struct remap_trace *trace = kmalloc(sizeof(*trace), GFP_KERNEL);
2268c2ecf20Sopenharmony_ci	/* These are page-unaligned. */
2278c2ecf20Sopenharmony_ci	struct mmiotrace_map map = {
2288c2ecf20Sopenharmony_ci		.phys = offset,
2298c2ecf20Sopenharmony_ci		.virt = (unsigned long)addr,
2308c2ecf20Sopenharmony_ci		.len = size,
2318c2ecf20Sopenharmony_ci		.opcode = MMIO_PROBE
2328c2ecf20Sopenharmony_ci	};
2338c2ecf20Sopenharmony_ci
2348c2ecf20Sopenharmony_ci	if (!trace) {
2358c2ecf20Sopenharmony_ci		pr_err("kmalloc failed in ioremap\n");
2368c2ecf20Sopenharmony_ci		return;
2378c2ecf20Sopenharmony_ci	}
2388c2ecf20Sopenharmony_ci
2398c2ecf20Sopenharmony_ci	*trace = (struct remap_trace) {
2408c2ecf20Sopenharmony_ci		.probe = {
2418c2ecf20Sopenharmony_ci			.addr = (unsigned long)addr,
2428c2ecf20Sopenharmony_ci			.len = size,
2438c2ecf20Sopenharmony_ci			.pre_handler = pre,
2448c2ecf20Sopenharmony_ci			.post_handler = post,
2458c2ecf20Sopenharmony_ci			.private = trace
2468c2ecf20Sopenharmony_ci		},
2478c2ecf20Sopenharmony_ci		.phys = offset,
2488c2ecf20Sopenharmony_ci		.id = atomic_inc_return(&next_id)
2498c2ecf20Sopenharmony_ci	};
2508c2ecf20Sopenharmony_ci	map.map_id = trace->id;
2518c2ecf20Sopenharmony_ci
2528c2ecf20Sopenharmony_ci	spin_lock_irq(&trace_lock);
2538c2ecf20Sopenharmony_ci	if (!is_enabled()) {
2548c2ecf20Sopenharmony_ci		kfree(trace);
2558c2ecf20Sopenharmony_ci		goto not_enabled;
2568c2ecf20Sopenharmony_ci	}
2578c2ecf20Sopenharmony_ci
2588c2ecf20Sopenharmony_ci	mmio_trace_mapping(&map);
2598c2ecf20Sopenharmony_ci	list_add_tail(&trace->list, &trace_list);
2608c2ecf20Sopenharmony_ci	if (!nommiotrace)
2618c2ecf20Sopenharmony_ci		register_kmmio_probe(&trace->probe);
2628c2ecf20Sopenharmony_ci
2638c2ecf20Sopenharmony_cinot_enabled:
2648c2ecf20Sopenharmony_ci	spin_unlock_irq(&trace_lock);
2658c2ecf20Sopenharmony_ci}
2668c2ecf20Sopenharmony_ci
2678c2ecf20Sopenharmony_civoid mmiotrace_ioremap(resource_size_t offset, unsigned long size,
2688c2ecf20Sopenharmony_ci						void __iomem *addr)
2698c2ecf20Sopenharmony_ci{
2708c2ecf20Sopenharmony_ci	if (!is_enabled()) /* recheck and proper locking in *_core() */
2718c2ecf20Sopenharmony_ci		return;
2728c2ecf20Sopenharmony_ci
2738c2ecf20Sopenharmony_ci	pr_debug("ioremap_*(0x%llx, 0x%lx) = %p\n",
2748c2ecf20Sopenharmony_ci		 (unsigned long long)offset, size, addr);
2758c2ecf20Sopenharmony_ci	if ((filter_offset) && (offset != filter_offset))
2768c2ecf20Sopenharmony_ci		return;
2778c2ecf20Sopenharmony_ci	ioremap_trace_core(offset, size, addr);
2788c2ecf20Sopenharmony_ci}
2798c2ecf20Sopenharmony_ci
2808c2ecf20Sopenharmony_cistatic void iounmap_trace_core(volatile void __iomem *addr)
2818c2ecf20Sopenharmony_ci{
2828c2ecf20Sopenharmony_ci	struct mmiotrace_map map = {
2838c2ecf20Sopenharmony_ci		.phys = 0,
2848c2ecf20Sopenharmony_ci		.virt = (unsigned long)addr,
2858c2ecf20Sopenharmony_ci		.len = 0,
2868c2ecf20Sopenharmony_ci		.opcode = MMIO_UNPROBE
2878c2ecf20Sopenharmony_ci	};
2888c2ecf20Sopenharmony_ci	struct remap_trace *trace;
2898c2ecf20Sopenharmony_ci	struct remap_trace *tmp;
2908c2ecf20Sopenharmony_ci	struct remap_trace *found_trace = NULL;
2918c2ecf20Sopenharmony_ci
2928c2ecf20Sopenharmony_ci	pr_debug("Unmapping %p.\n", addr);
2938c2ecf20Sopenharmony_ci
2948c2ecf20Sopenharmony_ci	spin_lock_irq(&trace_lock);
2958c2ecf20Sopenharmony_ci	if (!is_enabled())
2968c2ecf20Sopenharmony_ci		goto not_enabled;
2978c2ecf20Sopenharmony_ci
2988c2ecf20Sopenharmony_ci	list_for_each_entry_safe(trace, tmp, &trace_list, list) {
2998c2ecf20Sopenharmony_ci		if ((unsigned long)addr == trace->probe.addr) {
3008c2ecf20Sopenharmony_ci			if (!nommiotrace)
3018c2ecf20Sopenharmony_ci				unregister_kmmio_probe(&trace->probe);
3028c2ecf20Sopenharmony_ci			list_del(&trace->list);
3038c2ecf20Sopenharmony_ci			found_trace = trace;
3048c2ecf20Sopenharmony_ci			break;
3058c2ecf20Sopenharmony_ci		}
3068c2ecf20Sopenharmony_ci	}
3078c2ecf20Sopenharmony_ci	map.map_id = (found_trace) ? found_trace->id : -1;
3088c2ecf20Sopenharmony_ci	mmio_trace_mapping(&map);
3098c2ecf20Sopenharmony_ci
3108c2ecf20Sopenharmony_cinot_enabled:
3118c2ecf20Sopenharmony_ci	spin_unlock_irq(&trace_lock);
3128c2ecf20Sopenharmony_ci	if (found_trace) {
3138c2ecf20Sopenharmony_ci		synchronize_rcu(); /* unregister_kmmio_probe() requirement */
3148c2ecf20Sopenharmony_ci		kfree(found_trace);
3158c2ecf20Sopenharmony_ci	}
3168c2ecf20Sopenharmony_ci}
3178c2ecf20Sopenharmony_ci
3188c2ecf20Sopenharmony_civoid mmiotrace_iounmap(volatile void __iomem *addr)
3198c2ecf20Sopenharmony_ci{
3208c2ecf20Sopenharmony_ci	might_sleep();
3218c2ecf20Sopenharmony_ci	if (is_enabled()) /* recheck and proper locking in *_core() */
3228c2ecf20Sopenharmony_ci		iounmap_trace_core(addr);
3238c2ecf20Sopenharmony_ci}
3248c2ecf20Sopenharmony_ci
3258c2ecf20Sopenharmony_ciint mmiotrace_printk(const char *fmt, ...)
3268c2ecf20Sopenharmony_ci{
3278c2ecf20Sopenharmony_ci	int ret = 0;
3288c2ecf20Sopenharmony_ci	va_list args;
3298c2ecf20Sopenharmony_ci	unsigned long flags;
3308c2ecf20Sopenharmony_ci	va_start(args, fmt);
3318c2ecf20Sopenharmony_ci
3328c2ecf20Sopenharmony_ci	spin_lock_irqsave(&trace_lock, flags);
3338c2ecf20Sopenharmony_ci	if (is_enabled())
3348c2ecf20Sopenharmony_ci		ret = mmio_trace_printk(fmt, args);
3358c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&trace_lock, flags);
3368c2ecf20Sopenharmony_ci
3378c2ecf20Sopenharmony_ci	va_end(args);
3388c2ecf20Sopenharmony_ci	return ret;
3398c2ecf20Sopenharmony_ci}
3408c2ecf20Sopenharmony_ciEXPORT_SYMBOL(mmiotrace_printk);
3418c2ecf20Sopenharmony_ci
3428c2ecf20Sopenharmony_cistatic void clear_trace_list(void)
3438c2ecf20Sopenharmony_ci{
3448c2ecf20Sopenharmony_ci	struct remap_trace *trace;
3458c2ecf20Sopenharmony_ci	struct remap_trace *tmp;
3468c2ecf20Sopenharmony_ci
3478c2ecf20Sopenharmony_ci	/*
3488c2ecf20Sopenharmony_ci	 * No locking required, because the caller ensures we are in a
3498c2ecf20Sopenharmony_ci	 * critical section via mutex, and is_enabled() is false,
3508c2ecf20Sopenharmony_ci	 * i.e. nothing can traverse or modify this list.
3518c2ecf20Sopenharmony_ci	 * Caller also ensures is_enabled() cannot change.
3528c2ecf20Sopenharmony_ci	 */
3538c2ecf20Sopenharmony_ci	list_for_each_entry(trace, &trace_list, list) {
3548c2ecf20Sopenharmony_ci		pr_notice("purging non-iounmapped trace @0x%08lx, size 0x%lx.\n",
3558c2ecf20Sopenharmony_ci			  trace->probe.addr, trace->probe.len);
3568c2ecf20Sopenharmony_ci		if (!nommiotrace)
3578c2ecf20Sopenharmony_ci			unregister_kmmio_probe(&trace->probe);
3588c2ecf20Sopenharmony_ci	}
3598c2ecf20Sopenharmony_ci	synchronize_rcu(); /* unregister_kmmio_probe() requirement */
3608c2ecf20Sopenharmony_ci
3618c2ecf20Sopenharmony_ci	list_for_each_entry_safe(trace, tmp, &trace_list, list) {
3628c2ecf20Sopenharmony_ci		list_del(&trace->list);
3638c2ecf20Sopenharmony_ci		kfree(trace);
3648c2ecf20Sopenharmony_ci	}
3658c2ecf20Sopenharmony_ci}
3668c2ecf20Sopenharmony_ci
3678c2ecf20Sopenharmony_ci#ifdef CONFIG_HOTPLUG_CPU
3688c2ecf20Sopenharmony_cistatic cpumask_var_t downed_cpus;
3698c2ecf20Sopenharmony_ci
3708c2ecf20Sopenharmony_cistatic void enter_uniprocessor(void)
3718c2ecf20Sopenharmony_ci{
3728c2ecf20Sopenharmony_ci	int cpu;
3738c2ecf20Sopenharmony_ci	int err;
3748c2ecf20Sopenharmony_ci
3758c2ecf20Sopenharmony_ci	if (!cpumask_available(downed_cpus) &&
3768c2ecf20Sopenharmony_ci	    !alloc_cpumask_var(&downed_cpus, GFP_KERNEL)) {
3778c2ecf20Sopenharmony_ci		pr_notice("Failed to allocate mask\n");
3788c2ecf20Sopenharmony_ci		goto out;
3798c2ecf20Sopenharmony_ci	}
3808c2ecf20Sopenharmony_ci
3818c2ecf20Sopenharmony_ci	get_online_cpus();
3828c2ecf20Sopenharmony_ci	cpumask_copy(downed_cpus, cpu_online_mask);
3838c2ecf20Sopenharmony_ci	cpumask_clear_cpu(cpumask_first(cpu_online_mask), downed_cpus);
3848c2ecf20Sopenharmony_ci	if (num_online_cpus() > 1)
3858c2ecf20Sopenharmony_ci		pr_notice("Disabling non-boot CPUs...\n");
3868c2ecf20Sopenharmony_ci	put_online_cpus();
3878c2ecf20Sopenharmony_ci
3888c2ecf20Sopenharmony_ci	for_each_cpu(cpu, downed_cpus) {
3898c2ecf20Sopenharmony_ci		err = remove_cpu(cpu);
3908c2ecf20Sopenharmony_ci		if (!err)
3918c2ecf20Sopenharmony_ci			pr_info("CPU%d is down.\n", cpu);
3928c2ecf20Sopenharmony_ci		else
3938c2ecf20Sopenharmony_ci			pr_err("Error taking CPU%d down: %d\n", cpu, err);
3948c2ecf20Sopenharmony_ci	}
3958c2ecf20Sopenharmony_ciout:
3968c2ecf20Sopenharmony_ci	if (num_online_cpus() > 1)
3978c2ecf20Sopenharmony_ci		pr_warn("multiple CPUs still online, may miss events.\n");
3988c2ecf20Sopenharmony_ci}
3998c2ecf20Sopenharmony_ci
4008c2ecf20Sopenharmony_cistatic void leave_uniprocessor(void)
4018c2ecf20Sopenharmony_ci{
4028c2ecf20Sopenharmony_ci	int cpu;
4038c2ecf20Sopenharmony_ci	int err;
4048c2ecf20Sopenharmony_ci
4058c2ecf20Sopenharmony_ci	if (!cpumask_available(downed_cpus) || cpumask_weight(downed_cpus) == 0)
4068c2ecf20Sopenharmony_ci		return;
4078c2ecf20Sopenharmony_ci	pr_notice("Re-enabling CPUs...\n");
4088c2ecf20Sopenharmony_ci	for_each_cpu(cpu, downed_cpus) {
4098c2ecf20Sopenharmony_ci		err = add_cpu(cpu);
4108c2ecf20Sopenharmony_ci		if (!err)
4118c2ecf20Sopenharmony_ci			pr_info("enabled CPU%d.\n", cpu);
4128c2ecf20Sopenharmony_ci		else
4138c2ecf20Sopenharmony_ci			pr_err("cannot re-enable CPU%d: %d\n", cpu, err);
4148c2ecf20Sopenharmony_ci	}
4158c2ecf20Sopenharmony_ci}
4168c2ecf20Sopenharmony_ci
4178c2ecf20Sopenharmony_ci#else /* !CONFIG_HOTPLUG_CPU */
4188c2ecf20Sopenharmony_cistatic void enter_uniprocessor(void)
4198c2ecf20Sopenharmony_ci{
4208c2ecf20Sopenharmony_ci	if (num_online_cpus() > 1)
4218c2ecf20Sopenharmony_ci		pr_warn("multiple CPUs are online, may miss events. "
4228c2ecf20Sopenharmony_ci			"Suggest booting with maxcpus=1 kernel argument.\n");
4238c2ecf20Sopenharmony_ci}
4248c2ecf20Sopenharmony_ci
4258c2ecf20Sopenharmony_cistatic void leave_uniprocessor(void)
4268c2ecf20Sopenharmony_ci{
4278c2ecf20Sopenharmony_ci}
4288c2ecf20Sopenharmony_ci#endif
4298c2ecf20Sopenharmony_ci
4308c2ecf20Sopenharmony_civoid enable_mmiotrace(void)
4318c2ecf20Sopenharmony_ci{
4328c2ecf20Sopenharmony_ci	mutex_lock(&mmiotrace_mutex);
4338c2ecf20Sopenharmony_ci	if (is_enabled())
4348c2ecf20Sopenharmony_ci		goto out;
4358c2ecf20Sopenharmony_ci
4368c2ecf20Sopenharmony_ci	if (nommiotrace)
4378c2ecf20Sopenharmony_ci		pr_info("MMIO tracing disabled.\n");
4388c2ecf20Sopenharmony_ci	kmmio_init();
4398c2ecf20Sopenharmony_ci	enter_uniprocessor();
4408c2ecf20Sopenharmony_ci	spin_lock_irq(&trace_lock);
4418c2ecf20Sopenharmony_ci	atomic_inc(&mmiotrace_enabled);
4428c2ecf20Sopenharmony_ci	spin_unlock_irq(&trace_lock);
4438c2ecf20Sopenharmony_ci	pr_info("enabled.\n");
4448c2ecf20Sopenharmony_ciout:
4458c2ecf20Sopenharmony_ci	mutex_unlock(&mmiotrace_mutex);
4468c2ecf20Sopenharmony_ci}
4478c2ecf20Sopenharmony_ci
4488c2ecf20Sopenharmony_civoid disable_mmiotrace(void)
4498c2ecf20Sopenharmony_ci{
4508c2ecf20Sopenharmony_ci	mutex_lock(&mmiotrace_mutex);
4518c2ecf20Sopenharmony_ci	if (!is_enabled())
4528c2ecf20Sopenharmony_ci		goto out;
4538c2ecf20Sopenharmony_ci
4548c2ecf20Sopenharmony_ci	spin_lock_irq(&trace_lock);
4558c2ecf20Sopenharmony_ci	atomic_dec(&mmiotrace_enabled);
4568c2ecf20Sopenharmony_ci	BUG_ON(is_enabled());
4578c2ecf20Sopenharmony_ci	spin_unlock_irq(&trace_lock);
4588c2ecf20Sopenharmony_ci
4598c2ecf20Sopenharmony_ci	clear_trace_list(); /* guarantees: no more kmmio callbacks */
4608c2ecf20Sopenharmony_ci	leave_uniprocessor();
4618c2ecf20Sopenharmony_ci	kmmio_cleanup();
4628c2ecf20Sopenharmony_ci	pr_info("disabled.\n");
4638c2ecf20Sopenharmony_ciout:
4648c2ecf20Sopenharmony_ci	mutex_unlock(&mmiotrace_mutex);
4658c2ecf20Sopenharmony_ci}
466