681 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			681 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0
 | |
| /*
 | |
|  * drivers/soc/rockchip/rockchip_debug.c
 | |
|  *
 | |
|  * Arm debug driver
 | |
|  *
 | |
|  * Copyright (C) 2019 Rockchip Electronics Co., Ltd.
 | |
|  */
 | |
| 
 | |
| /*	RK3399
 | |
|  *	debug {
 | |
|  *		compatible = "rockchip,debug";
 | |
|  *		reg = <0x0 0xfe430000 0x0 0x1000>,
 | |
|  *		      <0x0 0xfe432000 0x0 0x1000>,
 | |
|  *		      <0x0 0xfe434000 0x0 0x1000>,
 | |
|  *		      <0x0 0xfe436000 0x0 0x1000>,
 | |
|  *		      <0x0 0xfe610000 0x0 0x1000>,
 | |
|  *		      <0x0 0xfe710000 0x0 0x1000>;
 | |
|  *	};
 | |
|  */
 | |
| 
 | |
| /*	RK3326
 | |
|  *	debug {
 | |
|  *		compatible = "rockchip,debug";
 | |
|  *		reg = <0x0 0xff690000 0x0 0x1000>,
 | |
|  *		      <0x0 0xff692000 0x0 0x1000>,
 | |
|  *		      <0x0 0xff694000 0x0 0x1000>,
 | |
|  *		      <0x0 0xff696000 0x0 0x1000>;
 | |
|  *	};
 | |
|  */
 | |
| 
 | |
| /*	RK3308
 | |
|  *	debug {
 | |
|  *		compatible = "rockchip,debug";
 | |
|  *		reg = <0x0 0xff810000 0x0 0x1000>,
 | |
|  *		      <0x0 0xff812000 0x0 0x1000>,
 | |
|  *		      <0x0 0xff814000 0x0 0x1000>,
 | |
|  *		      <0x0 0xff816000 0x0 0x1000>;
 | |
|  *	};
 | |
|  */
 | |
| 
 | |
| /*	RK3288
 | |
|  *	debug {
 | |
|  *		compatible = "rockchip,debug";
 | |
|  *		reg = <0x0 0xffbb0000 0x0 0x1000>,
 | |
|  *		      <0x0 0xffbb2000 0x0 0x1000>,
 | |
|  *		      <0x0 0xffbb4000 0x0 0x1000>,
 | |
|  *		      <0x0 0xffbb6000 0x0 0x1000>;
 | |
|  *	};
 | |
|  */
 | |
| 
 | |
| #include <linux/init.h>
 | |
| #include <linux/io.h>
 | |
| #include <linux/kernel.h>
 | |
| #include <linux/module.h>
 | |
| #include <linux/of.h>
 | |
| #include <linux/of_address.h>
 | |
| #include <linux/panic_notifier.h>
 | |
| #include <linux/kernel_stat.h>
 | |
| #include <linux/irq.h>
 | |
| #include <linux/delay.h>
 | |
| 
 | |
| #if IS_ENABLED(CONFIG_ROCKCHIP_MINIDUMP)
 | |
| #include <soc/rockchip/rk_minidump.h>
 | |
| #endif
 | |
| 
 | |
| #include "fiq_debugger/fiq_debugger_priv.h"
 | |
| #include "rockchip_debug.h"
 | |
| 
 | |
| #define EDPCSR_LO			0x0a0
 | |
| #define EDPCSR_HI			0x0ac
 | |
| #define EDLAR				0xfb0
 | |
| #define EDLAR_UNLOCK			0xc5acce55
 | |
| 
 | |
| #define EDPRSR				0x314
 | |
| #define EDPRSR_PU			0x1
 | |
| #define EDDEVID				0xFC8
 | |
| 
 | |
| #define PMPCSR_LO			0x200
 | |
| #define PMPCSR_HI			0x204
 | |
| 
 | |
| #define NUM_CPU_SAMPLES			100
 | |
| #define NUM_SAMPLES_TO_PRINT		32
 | |
| 
 | |
| static void __iomem *rockchip_cpu_debug[16];
 | |
| static void __iomem *rockchip_cs_pmu[16];
 | |
| static bool edpcsr_present;
 | |
| static char log_buf[1024];
 | |
| 
 | |
| extern struct atomic_notifier_head hardlock_notifier_list;
 | |
| extern struct atomic_notifier_head rcu_stall_notifier_list;
 | |
| 
 | |
| static inline void rockchip_debug_serror_enable(void)
 | |
| {
 | |
| #ifdef CONFIG_ARM64
 | |
| 	/* enable SError */
 | |
| 	asm volatile("msr	daifclr, #0x4");
 | |
| #endif
 | |
| }
 | |
| 
 | |
| static inline void rockchip_debug_serror_disable(void)
 | |
| {
 | |
| #ifdef CONFIG_ARM64
 | |
| 	/* disable SError */
 | |
| 	asm volatile("msr	daifset, #0x4");
 | |
| #endif
 | |
| }
 | |
| 
 | |
| #if IS_ENABLED(CONFIG_FIQ_DEBUGGER)
 | |
| static int rockchip_debug_dump_edpcsr(struct fiq_debugger_output *output)
 | |
| {
 | |
| 	unsigned long edpcsr;
 | |
| 	int i = 0, j = 0;
 | |
| 	void *pc = NULL;
 | |
| 	void *prev_pc = NULL;
 | |
| 	int printed = 0;
 | |
| 	void __iomem *base;
 | |
| 	u32 pu = 0, online = 0;
 | |
| 
 | |
| 	rockchip_debug_serror_disable();
 | |
| 
 | |
| 	while (rockchip_cpu_debug[i]) {
 | |
| 		online = cpu_online(i);
 | |
| 		output->printf(output,
 | |
| 				"CPU%d online:%d\n", i, online);
 | |
| 		if (online == 0) {
 | |
| 			i++;
 | |
| 			continue;
 | |
| 		}
 | |
| 
 | |
| 		base = rockchip_cpu_debug[i];
 | |
| 		pu = (u32)readl(base + EDPRSR) & EDPRSR_PU;
 | |
| 		if (pu != EDPRSR_PU) {
 | |
| 			output->printf(output,
 | |
| 					"CPU%d power down\n", i);
 | |
| 			i++;
 | |
| 			continue;
 | |
| 		}
 | |
| 		/* Unlock EDLSR.SLK so that EDPCSRhi gets populated */
 | |
| 		writel(EDLAR_UNLOCK, base + EDLAR);
 | |
| 
 | |
| 		/* Try to read a bunch of times if CPU is actually running */
 | |
| 		for (j = 0; j < NUM_CPU_SAMPLES &&
 | |
| 			    printed < NUM_SAMPLES_TO_PRINT; j++) {
 | |
| 			pu = (u32)readl(base + EDPRSR) & EDPRSR_PU;
 | |
| 			if (pu != EDPRSR_PU) {
 | |
| 				output->printf(output,
 | |
| 						"CPU%d power down\n", i);
 | |
| 				break;
 | |
| 			}
 | |
| 
 | |
| 			if (sizeof(edpcsr) == 8)
 | |
| 				edpcsr = ((u64)readl(base + EDPCSR_LO)) |
 | |
| 				  ((u64)readl(base + EDPCSR_HI) << 32);
 | |
| 			else
 | |
| 				edpcsr = (u32)readl(base + EDPCSR_LO);
 | |
| 
 | |
| 			/* NOTE: no offset on ARMv8; see DBGDEVID1.PCSROffset */
 | |
| 			pc = (void *)(edpcsr & ~1);
 | |
| 
 | |
| 			if (pc != prev_pc) {
 | |
| 				output->printf(output,
 | |
| 					       "\tPC: <0x%px> %pS\n", pc, pc);
 | |
| 				printed++;
 | |
| 			}
 | |
| 			prev_pc = pc;
 | |
| 		}
 | |
| 
 | |
| 		output->printf(output, "\n");
 | |
| 		i++;
 | |
| 		prev_pc = NULL;
 | |
| 		printed = 0;
 | |
| 	}
 | |
| 
 | |
| 	rockchip_debug_serror_enable();
 | |
| 	return NOTIFY_OK;
 | |
| }
 | |
| 
 | |
| #ifdef CONFIG_ARM64
 | |
| static int rockchip_debug_dump_pmpcsr(struct fiq_debugger_output *output)
 | |
| {
 | |
| 	u64 pmpcsr;
 | |
| 	int i = 0, j = 0, el, ns;
 | |
| 	void *pc = NULL;
 | |
| 	void *prev_pc = NULL;
 | |
| 	int printed = 0;
 | |
| 	void __iomem *base;
 | |
| 	u32 pu = 0, online = 0;
 | |
| 
 | |
| 	rockchip_debug_serror_disable();
 | |
| 
 | |
| 	while (rockchip_cs_pmu[i]) {
 | |
| 		online = cpu_online(i);
 | |
| 		output->printf(output,
 | |
| 				"CPU%d online:%d\n", i, online);
 | |
| 		if (online == 0) {
 | |
| 			i++;
 | |
| 			continue;
 | |
| 		}
 | |
| 
 | |
| 		pu = (u32)readl(rockchip_cpu_debug[i] + EDPRSR) & EDPRSR_PU;
 | |
| 		if (pu != EDPRSR_PU) {
 | |
| 			output->printf(output,
 | |
| 					"CPU%d power down\n", i);
 | |
| 			i++;
 | |
| 			continue;
 | |
| 		}
 | |
| 
 | |
| 		base = rockchip_cs_pmu[i];
 | |
| 		/* Try to read a bunch of times if CPU is actually running */
 | |
| 		for (j = 0; j < NUM_CPU_SAMPLES &&
 | |
| 			    printed < NUM_SAMPLES_TO_PRINT; j++) {
 | |
| 			pu = (u32)readl(rockchip_cpu_debug[i] + EDPRSR) & EDPRSR_PU;
 | |
| 			if (pu != EDPRSR_PU) {
 | |
| 				output->printf(output,
 | |
| 						"CPU%d power down\n", i);
 | |
| 				break;
 | |
| 			}
 | |
| 
 | |
| 			pmpcsr = ((u64)readl(base + PMPCSR_LO)) |
 | |
| 				((u64)readl(base + PMPCSR_HI) << 32);
 | |
| 
 | |
| 			el = (pmpcsr >> 61) & 0x3;
 | |
| 			if (pmpcsr & 0x8000000000000000)
 | |
| 				ns = 1;
 | |
| 			else
 | |
| 				ns = 0;
 | |
| 
 | |
| 			if (el == 2)
 | |
| 				pmpcsr |= 0xff00000000000000;
 | |
| 			else
 | |
| 				pmpcsr &= 0x0fffffffffffffff;
 | |
| 			/* NOTE: no offset on ARMv8; see DBGDEVID1.PCSROffset */
 | |
| 			pc = (void *)(pmpcsr & ~1);
 | |
| 
 | |
| 			if (pc != prev_pc) {
 | |
| 				output->printf(output, "\tEL%d(%s) PC: <0x%px> %pS\n",
 | |
| 						el, ns?"NS":"S", pc, pc);
 | |
| 				printed++;
 | |
| 			}
 | |
| 			prev_pc = pc;
 | |
| 		}
 | |
| 
 | |
| 		output->printf(output, "\n");
 | |
| 		i++;
 | |
| 		prev_pc = NULL;
 | |
| 		printed = 0;
 | |
| 	}
 | |
| 	rockchip_debug_serror_enable();
 | |
| 	return NOTIFY_OK;
 | |
| }
 | |
| #else
 | |
| static int rockchip_debug_dump_pmpcsr(struct fiq_debugger_output *output)
 | |
| {
 | |
| 	return 0;
 | |
| }
 | |
| #endif
 | |
| 
 | |
| int rockchip_debug_dump_pcsr(struct fiq_debugger_output *output)
 | |
| {
 | |
| 	if (edpcsr_present)
 | |
| 		rockchip_debug_dump_edpcsr(output);
 | |
| 	else
 | |
| 		rockchip_debug_dump_pmpcsr(output);
 | |
| 	return 0;
 | |
| }
 | |
| EXPORT_SYMBOL_GPL(rockchip_debug_dump_pcsr);
 | |
| #endif
 | |
| 
 | |
| static int rockchip_panic_notify_edpcsr(struct notifier_block *nb,
 | |
| 					unsigned long event, void *p)
 | |
| {
 | |
| 	unsigned long edpcsr;
 | |
| 	int i = 0, j;
 | |
| 	void *pc = NULL;
 | |
| 	void *prev_pc = NULL;
 | |
| 	int printed = 0;
 | |
| 	void __iomem *base;
 | |
| 	u32 pu = 0;
 | |
| 
 | |
| 	rockchip_debug_serror_disable();
 | |
| 
 | |
| 	/*
 | |
| 	 * The panic handler will try to shut down the other CPUs.
 | |
| 	 * If any of them are still online at this point, this loop attempts
 | |
| 	 * to determine the program counter value.  If there are no wedged
 | |
| 	 * CPUs, this loop will do nothing.
 | |
| 	 */
 | |
| 
 | |
| 	while (rockchip_cpu_debug[i]) {
 | |
| 		base = rockchip_cpu_debug[i];
 | |
| 		pu = (u32)readl(base + EDPRSR) & EDPRSR_PU;
 | |
| 		if (pu != EDPRSR_PU) {
 | |
| 			pr_err("CPU%d power down\n", i);
 | |
| 			i++;
 | |
| 			continue;
 | |
| 		}
 | |
| 
 | |
| 		/* Unlock EDLSR.SLK so that EDPCSRhi gets populated */
 | |
| 		writel(EDLAR_UNLOCK, base + EDLAR);
 | |
| 
 | |
| 		pr_err("CPU%d online:%d\n", i, cpu_online(i));
 | |
| 
 | |
| 		/* Try to read a bunch of times if CPU is actually running */
 | |
| 		for (j = 0; j < NUM_CPU_SAMPLES &&
 | |
| 			    printed < NUM_SAMPLES_TO_PRINT; j++) {
 | |
| 			pu = (u32)readl(base + EDPRSR) & EDPRSR_PU;
 | |
| 			if (pu != EDPRSR_PU) {
 | |
| 				pr_err("CPU%d power down\n", i);
 | |
| 				break;
 | |
| 			}
 | |
| 
 | |
| 			if (sizeof(edpcsr) == 8)
 | |
| 				edpcsr = ((u64)readl(base + EDPCSR_LO)) |
 | |
| 				  ((u64)readl(base + EDPCSR_HI) << 32);
 | |
| 			else
 | |
| 				edpcsr = (u32)readl(base + EDPCSR_LO);
 | |
| 
 | |
| 			/* NOTE: no offset on ARMv8; see DBGDEVID1.PCSROffset */
 | |
| 			pc = (void *)(edpcsr & ~1);
 | |
| 
 | |
| 			if (pc != prev_pc) {
 | |
| 				pr_err("\tPC: <0x%px> %pS\n", pc, pc);
 | |
| 				printed++;
 | |
| 			}
 | |
| 			prev_pc = pc;
 | |
| 		}
 | |
| 
 | |
| 		pr_err("\n");
 | |
| 		i++;
 | |
| 		prev_pc = NULL;
 | |
| 		printed = 0;
 | |
| 	}
 | |
| 
 | |
| 	return NOTIFY_OK;
 | |
| }
 | |
| 
 | |
| #ifdef CONFIG_ARM64
 | |
| static int rockchip_panic_notify_pmpcsr(struct notifier_block *nb,
 | |
| 					unsigned long event, void *p)
 | |
| {
 | |
| 	u64 pmpcsr;
 | |
| 	int i = 0, j, el, ns;
 | |
| 	void *pc = NULL;
 | |
| 	void *prev_pc = NULL;
 | |
| 	int printed = 0;
 | |
| 	void __iomem *base;
 | |
| 	u32 pu = 0;
 | |
| 
 | |
| 	rockchip_debug_serror_disable();
 | |
| 
 | |
| 	/*
 | |
| 	 * The panic handler will try to shut down the other CPUs.
 | |
| 	 * If any of them are still online at this point, this loop attempts
 | |
| 	 * to determine the program counter value.  If there are no wedged
 | |
| 	 * CPUs, this loop will do nothing.
 | |
| 	 */
 | |
| 
 | |
| 	while (rockchip_cs_pmu[i]) {
 | |
| 		base = rockchip_cs_pmu[i];
 | |
| 
 | |
| 		pr_err("CPU%d online:%d\n", i, cpu_online(i));
 | |
| 
 | |
| 		pu = (u32)readl(rockchip_cpu_debug[i] + EDPRSR) & EDPRSR_PU;
 | |
| 		if (pu != EDPRSR_PU) {
 | |
| 			pr_err("CPU%d power down\n", i);
 | |
| 			i++;
 | |
| 			continue;
 | |
| 		}
 | |
| 
 | |
| 		/* Try to read a bunch of times if CPU is actually running */
 | |
| 		for (j = 0; j < NUM_CPU_SAMPLES &&
 | |
| 			    printed < NUM_SAMPLES_TO_PRINT; j++) {
 | |
| 			pu = (u32)readl(rockchip_cpu_debug[i] + EDPRSR) & EDPRSR_PU;
 | |
| 			if (pu != EDPRSR_PU) {
 | |
| 				pr_err("CPU%d power down\n", i);
 | |
| 				break;
 | |
| 			}
 | |
| 			pmpcsr = ((u64)readl(base + PMPCSR_LO)) |
 | |
| 				((u64)readl(base + PMPCSR_HI) << 32);
 | |
| 
 | |
| 			el = (pmpcsr >> 61) & 0x3;
 | |
| 			if (pmpcsr & 0x8000000000000000)
 | |
| 				ns = 1;
 | |
| 			else
 | |
| 				ns = 0;
 | |
| 
 | |
| 			if (el == 2)
 | |
| 				pmpcsr |= 0xff00000000000000;
 | |
| 			else
 | |
| 				pmpcsr &= 0x0fffffffffffffff;
 | |
| 			/* NOTE: no offset on ARMv8; see DBGDEVID1.PCSROffset */
 | |
| 			pc = (void *)(pmpcsr & ~1);
 | |
| 
 | |
| 			if (pc != prev_pc) {
 | |
| 				pr_err("\tEL%d(%s) PC: <0x%px> %pS\n",
 | |
| 					el, ns?"NS":"S", pc, pc);
 | |
| 				printed++;
 | |
| 			}
 | |
| 			prev_pc = pc;
 | |
| 		}
 | |
| 
 | |
| 		pr_err("\n");
 | |
| 		i++;
 | |
| 		prev_pc = NULL;
 | |
| 		printed = 0;
 | |
| 	}
 | |
| 
 | |
| 	return NOTIFY_OK;
 | |
| }
 | |
| #else
 | |
| static int rockchip_panic_notify_pmpcsr(struct notifier_block *nb,
 | |
| 					unsigned long event, void *p)
 | |
| {
 | |
| 	return NOTIFY_OK;
 | |
| }
 | |
| #endif
 | |
| 
 | |
| static int rockchip_show_interrupts(char *p, int irq)
 | |
| {
 | |
| 	static int prec;
 | |
| 	char *buf = p;
 | |
| 	unsigned long any_count = 0;
 | |
| 	int i = irq, j;
 | |
| 	struct irqaction *action;
 | |
| 	struct irq_desc *desc;
 | |
| 
 | |
| 	if (i > nr_irqs)
 | |
| 		return -1;
 | |
| 
 | |
| 	/* print header and calculate the width of the first column */
 | |
| 	if (i == 0) {
 | |
| 		for (prec = 3, j = 1000; prec < 10 && j <= nr_irqs; ++prec)
 | |
| 			j *= 10;
 | |
| 
 | |
| 		buf += sprintf(buf, "%*s", prec + 8, "");
 | |
| 		for_each_possible_cpu(j)
 | |
| 			buf += sprintf(buf, "CPU%-8d", j);
 | |
| 		buf += sprintf(buf, "\n");
 | |
| 	}
 | |
| 
 | |
| 	desc = irq_to_desc(i);
 | |
| 	if (!desc || (desc->status_use_accessors & IRQ_HIDDEN))
 | |
| 		goto outsparse;
 | |
| 
 | |
| 	if (desc->kstat_irqs)
 | |
| 		for_each_possible_cpu(j)
 | |
| 			any_count |= *per_cpu_ptr(desc->kstat_irqs, j);
 | |
| 
 | |
| 	if ((!desc->action) && !any_count)
 | |
| 		goto outsparse;
 | |
| 
 | |
| 	buf += sprintf(buf, "%*d: ", prec, i);
 | |
| 	for_each_possible_cpu(j)
 | |
| 		buf += sprintf(buf, "%10u ", desc->kstat_irqs ?
 | |
| 					*per_cpu_ptr(desc->kstat_irqs, j) : 0);
 | |
| 
 | |
| 	if (desc->irq_data.chip) {
 | |
| 		if (desc->irq_data.chip->name)
 | |
| 			buf += sprintf(buf, " %8s", desc->irq_data.chip->name);
 | |
| 		else
 | |
| 			buf += sprintf(buf, " %8s", "-");
 | |
| 	} else {
 | |
| 		buf += sprintf(buf, " %8s", "None");
 | |
| 	}
 | |
| 	if (desc->irq_data.domain)
 | |
| 		buf += sprintf(buf, " %*lu", prec, desc->irq_data.hwirq);
 | |
| 	else
 | |
| 		buf += sprintf(buf, " %*s", prec, "");
 | |
| #ifdef CONFIG_GENERIC_IRQ_SHOW_LEVEL
 | |
| 	buf += sprintf(buf, " %-8s", irqd_is_level_type(&desc->irq_data) ? "Level" : "Edge");
 | |
| #endif
 | |
| 	if (desc->name)
 | |
| 		buf += sprintf(buf, "-%-8s", desc->name);
 | |
| 
 | |
| 	action = desc->action;
 | |
| 	if (action) {
 | |
| 		buf += sprintf(buf, "  %s", action->name);
 | |
| 		while ((action = action->next) != NULL)
 | |
| 			buf += sprintf(buf, ", %s", action->name);
 | |
| 	}
 | |
| 
 | |
| 	sprintf(buf, "\n");
 | |
| 	return 0;
 | |
| outsparse:
 | |
| 	return -1;
 | |
| }
 | |
| 
 | |
| static void rockchip_panic_notify_dump_irqs(void)
 | |
| {
 | |
| 	int i = 0;
 | |
| 
 | |
| 	for (i = 0; i < nr_irqs; i++) {
 | |
| 		if (!rockchip_show_interrupts(log_buf, i) || i == 0)
 | |
| 			printk("%s", log_buf);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static int rockchip_panic_notify(struct notifier_block *nb, unsigned long event,
 | |
| 				 void *p)
 | |
| {
 | |
| 	if (edpcsr_present)
 | |
| 		rockchip_panic_notify_edpcsr(nb, event, p);
 | |
| 	else
 | |
| 		rockchip_panic_notify_pmpcsr(nb, event, p);
 | |
| 
 | |
| 	rockchip_panic_notify_dump_irqs();
 | |
| 	mdelay(1000);
 | |
| 	rockchip_panic_notify_dump_irqs();
 | |
| 	return NOTIFY_OK;
 | |
| }
 | |
| 
 | |
| static int rockchip_hardlock_notify(struct notifier_block *nb,
 | |
| 				    unsigned long event, void *p)
 | |
| {
 | |
| 	u64 pmpcsr;
 | |
| 	int el;
 | |
| 	u32 pu = 0;
 | |
| 	void *pc = NULL;
 | |
| 	void __iomem *base;
 | |
| 	unsigned long edpcsr;
 | |
| 	unsigned long cpu = event;
 | |
| 
 | |
| 	rockchip_debug_serror_disable();
 | |
| 
 | |
| 	pu = (u32)readl(rockchip_cpu_debug[cpu] + EDPRSR) & EDPRSR_PU;
 | |
| 	if (pu != EDPRSR_PU) {
 | |
| 		pr_err("CPU%ld power down\n", cpu);
 | |
| 		return NOTIFY_OK;
 | |
| 	}
 | |
| 
 | |
| 	if (edpcsr_present) {
 | |
| 		base = rockchip_cpu_debug[cpu];
 | |
| 		/* Unlock EDLSR.SLK so that EDPCSRhi gets populated */
 | |
| 		writel(EDLAR_UNLOCK, base + EDLAR);
 | |
| 		if (sizeof(edpcsr) == 8)
 | |
| 			edpcsr = ((u64)readl(base + EDPCSR_LO)) |
 | |
| 				 ((u64)readl(base + EDPCSR_HI) << 32);
 | |
| 		else
 | |
| 			edpcsr = (u32)readl(base + EDPCSR_LO);
 | |
| 
 | |
| 		/* NOTE: no offset on ARMv8; see DBGDEVID1.PCSROffset */
 | |
| 		pc = (void *)(edpcsr & ~1);
 | |
| 	} else {
 | |
| 		base = rockchip_cs_pmu[cpu];
 | |
| 		pmpcsr = ((u64)readl(base + PMPCSR_LO)) |
 | |
| 			 ((u64)readl(base + PMPCSR_HI) << 32);
 | |
| 		el = (pmpcsr >> 61) & 0x3;
 | |
| 		if (el == 2)
 | |
| 			pmpcsr |= 0xff00000000000000;
 | |
| 		else
 | |
| 			pmpcsr &= 0x0fffffffffffffff;
 | |
| 		/* NOTE: no offset on ARMv8; see DBGDEVID1.PCSROffset */
 | |
| 		pc = (void *)(uintptr_t)(pmpcsr & ~1);
 | |
| 	}
 | |
| 
 | |
| 	rockchip_debug_serror_enable();
 | |
| 
 | |
| #if IS_ENABLED(CONFIG_ROCKCHIP_MINIDUMP)
 | |
| 	rk_minidump_hardlock_notify(nb, event, pc);
 | |
| #endif
 | |
| 
 | |
| #if !IS_ENABLED(CONFIG_BOOTPARAM_HARDLOCKUP_PANIC)
 | |
| 	rockchip_panic_notify(nb, event, p);
 | |
| #endif
 | |
| 	return NOTIFY_OK;
 | |
| }
 | |
| 
 | |
| static struct notifier_block rockchip_panic_nb = {
 | |
| 	.notifier_call = rockchip_panic_notify,
 | |
| };
 | |
| 
 | |
| static struct notifier_block rockchip_rcu_stall_nb = {
 | |
| 	.notifier_call = rockchip_panic_notify,
 | |
| };
 | |
| 
 | |
| static struct notifier_block rockchip_hardlock_nb = {
 | |
| 	.notifier_call = rockchip_hardlock_notify,
 | |
| };
 | |
| 
 | |
| static const struct of_device_id rockchip_debug_dt_match[] __initconst = {
 | |
| 	/* external debug */
 | |
| 	{
 | |
| 		.compatible = "rockchip,debug",
 | |
| 	},
 | |
| 	{ /* sentinel */ },
 | |
| };
 | |
| 
 | |
| static const struct of_device_id rockchip_cspmu_dt_match[] __initconst = {
 | |
| 	/* coresight pmu */
 | |
| 	{
 | |
| 		.compatible = "rockchip,cspmu",
 | |
| 	},
 | |
| 	{ /* sentinel */ },
 | |
| };
 | |
| 
 | |
| static int __init rockchip_debug_init(void)
 | |
| {
 | |
| 	int i;
 | |
| 	u32 pcs;
 | |
| 	struct device_node *debug_np = NULL, *cspmu_np = NULL;
 | |
| 
 | |
| 	debug_np = of_find_matching_node_and_match(NULL,
 | |
| 				rockchip_debug_dt_match, NULL);
 | |
| 
 | |
| 	if (debug_np) {
 | |
| 		i = -1;
 | |
| 		do {
 | |
| 			i++;
 | |
| 			rockchip_cpu_debug[i] = of_iomap(debug_np, i);
 | |
| 		} while (rockchip_cpu_debug[i]);
 | |
| 		of_node_put(debug_np);
 | |
| 	}
 | |
| 
 | |
| 	cspmu_np = of_find_matching_node_and_match(NULL,
 | |
| 				rockchip_cspmu_dt_match, NULL);
 | |
| 
 | |
| 	if (cspmu_np) {
 | |
| 		i = -1;
 | |
| 		do {
 | |
| 			i++;
 | |
| 			rockchip_cs_pmu[i] = of_iomap(cspmu_np, i);
 | |
| 		} while (rockchip_cs_pmu[i]);
 | |
| 		of_node_put(cspmu_np);
 | |
| 	}
 | |
| 
 | |
| 	if (!debug_np)
 | |
| 		return -ENODEV;
 | |
| 
 | |
| 	pcs = readl(rockchip_cpu_debug[0] + EDDEVID) & 0xf;
 | |
| 	/* 0x3 EDPCSR, EDCIDSR, and EDVIDSR are implemented */
 | |
| 	if (pcs == 0x3)
 | |
| 		edpcsr_present = true;
 | |
| 
 | |
| 	if (!edpcsr_present && !cspmu_np)
 | |
| 		return -ENODEV;
 | |
| 
 | |
| 	atomic_notifier_chain_register(&panic_notifier_list,
 | |
| 				       &rockchip_panic_nb);
 | |
| 	if (IS_ENABLED(CONFIG_NO_GKI)) {
 | |
| 		if (IS_ENABLED(CONFIG_HARDLOCKUP_DETECTOR))
 | |
| 			atomic_notifier_chain_register(&hardlock_notifier_list,
 | |
| 						       &rockchip_hardlock_nb);
 | |
| 
 | |
| 		atomic_notifier_chain_register(&rcu_stall_notifier_list,
 | |
| 					       &rockchip_rcu_stall_nb);
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| arch_initcall(rockchip_debug_init);
 | |
| 
 | |
| static void __exit rockchip_debug_exit(void)
 | |
| {
 | |
| 	int i = 0;
 | |
| 
 | |
| 	atomic_notifier_chain_unregister(&panic_notifier_list,
 | |
| 					 &rockchip_panic_nb);
 | |
| 	if (IS_ENABLED(CONFIG_NO_GKI)) {
 | |
| 		if (IS_ENABLED(CONFIG_HARDLOCKUP_DETECTOR))
 | |
| 			atomic_notifier_chain_unregister(&hardlock_notifier_list,
 | |
| 							 &rockchip_hardlock_nb);
 | |
| 
 | |
| 		atomic_notifier_chain_unregister(&rcu_stall_notifier_list,
 | |
| 						 &rockchip_rcu_stall_nb);
 | |
| 	}
 | |
| 
 | |
| 	while (rockchip_cpu_debug[i])
 | |
| 		iounmap(rockchip_cpu_debug[i++]);
 | |
| 
 | |
| 	i = 0;
 | |
| 	while (rockchip_cs_pmu[i])
 | |
| 		iounmap(rockchip_cs_pmu[i++]);
 | |
| }
 | |
| module_exit(rockchip_debug_exit);
 | |
| 
 | |
| MODULE_AUTHOR("Huibin Hong <huibin.hong@rock-chips.com>");
 | |
| MODULE_DESCRIPTION("Rockchip Debugger");
 | |
| MODULE_LICENSE("GPL");
 | |
| MODULE_ALIAS("platform:rockchip-debugger");
 |