mirror of
https://github.com/hardkernel/kernel_common_drivers.git
synced 2026-06-25 12:03:48 +09:00
ac5dfe15e4
PD#SWPL-231235 Problem: Reading the register through the function memcpy_fromio will cause an external abort. Solution: Reading the register through the function readl. Verify: T6X Change-Id: Ic3371e92d0bfdd83de8b7fae431609e4259cd005 Signed-off-by: Qiankun Wang <qiankun.wang@amlogic.com>
1249 lines
33 KiB
C
1249 lines
33 KiB
C
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
|
|
/*
|
|
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
|
|
*/
|
|
|
|
#include <linux/init.h>
|
|
#include <linux/module.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/of.h>
|
|
#include <linux/irqreturn.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/io.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/of_reserved_mem.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/sched/clock.h>
|
|
#include <linux/sysrq.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/amlogic/gki_module.h>
|
|
#include <trace/hooks/traps.h>
|
|
#include <linux/amlogic/aml_iotm.h>
|
|
#include <linux/amlogic/arm-smccc.h>
|
|
#include <linux/panic_notifier.h>
|
|
#include <linux/timer.h>
|
|
#include <linux/syscore_ops.h>
|
|
#include <linux/hardirq.h>
|
|
#include <asm/arch_timer.h>
|
|
#include <linux/proc_fs.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/string.h>
|
|
#include <linux/vmalloc.h>
|
|
#include <linux/sort.h>
|
|
#include "iotm_hw.h"
|
|
|
|
#define AML_IOTM_SMC_CMD 0x8200007A
|
|
#define AML_IOTM_INIT_SMC_ARG 0x1
|
|
#define AML_IOTM_RANGE_SMC_ARG 0x2
|
|
#define AML_IOTM_JTAG_SMC_ARG 0x3
|
|
#define AML_IOTM_JTAG_DISABLE_SMC_ARG 0x0
|
|
#define AML_IOTM_JTAG_ENABLE_SMC_ARG 0x1
|
|
#define IOTM_DUMP_TRACE_CNT 3000
|
|
#define ETB_SIZE (8 * 1024)
|
|
#define TRACE_BUF_SIZE 512
|
|
#define IOTM_MONITOR_RANGE_MAX 0xFFFFFFFF
|
|
#define IOTM_MONITOR_RANGE_MIN 0xE0000000
|
|
#define CONTINUOUS_PULSE 20
|
|
#define CONTINUOUS_PULSE_TIME 10
|
|
#define IOTM_V1 0x1
|
|
#define IOTM_V2 0x2
|
|
#define DDR_TRACE_MAGIC "IOTM_trace:"
|
|
#define SIZE_OF_TRACE_MAGIC 48
|
|
#define NUM_OF_IRQ 1000
|
|
#define ETB_BUF_FULL 0x7FF
|
|
#define NUM_OF_UNRELIABLE 10
|
|
|
|
enum iotm_en_mode {
|
|
IOTM_DISABLE,
|
|
IOTM_ENABLE,
|
|
IOTM_ENABLE_NO_TRACE,
|
|
};
|
|
|
|
enum iotm_mode {
|
|
AXI_MODE,
|
|
TPIU_MODE,
|
|
ETB_MODE,
|
|
};
|
|
|
|
enum iotm_type {
|
|
IOTM_TYPE_T6D,
|
|
IOTM_TYPE_T6W,
|
|
IOTM_TYPE_T6X,
|
|
};
|
|
|
|
enum iotm_dump {
|
|
IOTM_DUMP_NONE,
|
|
IOTM_DUMP_WATCHDOG,
|
|
IOTM_DUMP_ALL,
|
|
};
|
|
|
|
struct iotm iotm = {
|
|
.monitor_mode = AXI_MODE,
|
|
.dump_cnt = IOTM_DUMP_TRACE_CNT,
|
|
.dump_mode = IOTM_DUMP_WATCHDOG,
|
|
};
|
|
|
|
static int iotm_en = IOTM_ENABLE;
|
|
|
|
const char *sw_record_name[] = {
|
|
[IOTM_SW_SCHED_BEGIN] = "IOTM_SCHED_SWITCH_BEGIN",
|
|
[IOTM_SW_SCHED_END] = "IOTM_SCHED_SWITCH_END",
|
|
[IOTM_SW_IRQ_IN] = "IOTM_ISR_IN",
|
|
[IOTM_SW_IRQ_OUT] = "IOTM_ISR_OUT",
|
|
[IOTM_SW_SMC_IN] = "IOTM_SMC_IN",
|
|
[IOTM_SW_SMC_OUT] = "IOTM_SMC_OUT",
|
|
[IOTM_SW_SMC_NORET_IN] = "IOTM_SMC_NORET_IN",
|
|
[IOTM_SW_TIME] = "IOTM_SW_TIME",
|
|
};
|
|
|
|
static struct reg_entry reg_table_public[] __initdata = {
|
|
{0xF7000000, 0xF703AFFF, "axi_sram"},
|
|
{0xF7100000, 0xF71FFFFF, "NIC_SYS_GPV"},
|
|
{0xFC000000, 0xFCFFFFFF, "ddrphy_and_pll_0"},
|
|
{0xFE000000, 0xFE001FFF, "clk_ctrl"},
|
|
{0xFE002000, 0xFE003FFF, "reset_ctrl"},
|
|
{0xFE004000, 0xFE005FFF, "pad_ctrl"},
|
|
{0xFE006000, 0xFE007FFF, "mailbox"},
|
|
{0xFE008000, 0xFE009FFF, "analog_ctrl"},
|
|
{0xFE00A000, 0xFE00BFFF, "irq_ctrl"},
|
|
{0xFE00C000, 0xFE00DFFF, "pwr_ctrl"},
|
|
{0xFE00E000, 0xFE00FFFF, "cpu_ctrl"},
|
|
{0xFE010000, 0xFE011FFF, "sys_ctrl"},
|
|
{0xFE012000, 0xFE013FFF, "capu"},
|
|
{0xFE01A000, 0xFE01BFFF, "acodec_ctrl"},
|
|
{0xFE01C000, 0xFE01DFFF, "temp_sensor_top"},
|
|
{0xFE01E000, 0xFE01FFFF, "hdmi20_aes"},
|
|
{0xFE022000, 0xFE023FFF, "temp_sensor_cpu"},
|
|
{0xFE026000, 0xFE027FFF, "sar_adc"},
|
|
{0xFE02C000, 0xFE02DFFF, "i2c_mon"},
|
|
{0xFE02E000, 0xFE02FFFF, "startup"},
|
|
{0xFE038000, 0xFE039FFF, "Smart Card"},
|
|
{0xFE044000, 0xFE045FFF, "cec"},
|
|
{0xFE048000, 0xFE049FFF, "msr_clk"},
|
|
{0xFE050000, 0xFE051FFF, "spicc(spi_sg_comb)"},
|
|
{0xFE058000, 0xFE059FFF, "pwm_(a-j)"},
|
|
{0xFE066000, 0xFE067FFF, "i2c_m(0-4)"},
|
|
{0xFE072000, 0xFE073FFF, "tvfe"},
|
|
{0xFE074000, 0xFE075FFF, "led_ctrl"},
|
|
{0xFE078000, 0xFE079FFF, "uart(a-d)"},
|
|
{0xFE080000, 0xFE081FFF, "ci_plus"},
|
|
{0xFE084000, 0xFE085FFF, "ir_top"},
|
|
{0xFE08C000, 0xFE08DFFF, "nand_emmc_C"},
|
|
{0xFE090000, 0xFE091FFF, "axi_srama"},
|
|
{0xFE092000, 0xFE093FFF, "amfc"},
|
|
{0xFE09C000, 0xFE09DFFF, "aocpu"},
|
|
{0xFE0BC000, 0xFE0BDFFF, "adec"},
|
|
{0xFE0BE000, 0xFE0BFFFF, "atv_demod"},
|
|
{0xFE310000, 0xFE31FFFF, "demod"},
|
|
{0xFE320000, 0xFE32FFFF, "dos"},
|
|
{0xFE330000, 0xFE33FFFF, "audio"},
|
|
{0xFE380000, 0xFE38FFFF, "eth_top"},
|
|
{0xFE390000, 0xFE39FFFF, "hdmirx"},
|
|
{0xFE3A0000, 0xFE3AFFFF, "hdmirx_core"},
|
|
{0xFE3B0000, 0xFE3BFFFF, "tcon"},
|
|
{0xFE400000, 0xFE43FFFF, "mali"},
|
|
{0xFE440000, 0xFE47FFFF, "SECTOP"},
|
|
{0xFE800000, 0xFF7FFFFF, "a53_dbg"},
|
|
{0xFF800000, 0xFF83FFFF, "vpu"},
|
|
{0xFF840000, 0xFF84FFFF, "ge2d"},
|
|
{0xFFF00000, 0xFFF07FFF, "gic"},
|
|
{0xFFFD8000, 0xFFFF1FFF, "ROM_MAIN"},
|
|
{0xFFFF2000, 0xFFFF7FFF, "ROM_DFU"},
|
|
{0xFFFF8000, 0xFFFFFFFF, "ROM_MMU"},
|
|
};
|
|
|
|
static struct reg_entry reg_table_t6d[] __initdata = {
|
|
{0xFE036000, 0xFE037FFF, "dmc0"},
|
|
{0xFE064000, 0xFE065FFF, "i2c_s_a"},
|
|
{0xFE07A000, 0xFE07BFFF, "uart_b"},
|
|
{0xFE07C000, 0xFE07DFFF, "uart_c"},
|
|
{0xFE480000, 0xFE4BFFFF, "usb_wrapper"},
|
|
};
|
|
|
|
static struct reg_entry reg_table_t6w[] __initdata = {
|
|
{0xFE014000, 0xFE015FFF, "bcon"},
|
|
{0xFE016000, 0xFE017FFF, "vx1_lvds"},
|
|
{0xFE018000, 0xFE019FFF, "apb2btc"},
|
|
{0xFE036000, 0xFE037FFF, "dmc0"},
|
|
{0xFE064000, 0xFE065FFF, "i2c_s_a"},
|
|
{0xFE07A000, 0xFE07BFFF, "uart_b"},
|
|
{0xFE07C000, 0xFE07DFFF, "uart_c"},
|
|
{0xFE09E000, 0xFE09FFFF, "aucpu"},
|
|
{0xFE340000, 0xFE34FFFF, "dmc0"},
|
|
{0xFE370000, 0xFE37FFFF, "usb2_2"},
|
|
{0xFE480000, 0xFE4BFFFF, "usb_wrapper"},
|
|
{0xFF850000, 0xFF88FFFF, "dpss"},
|
|
};
|
|
|
|
static struct reg_entry reg_table_t6x[] __initdata = {
|
|
{0xFB000000, 0xFBFFFFFF, "ddrphy_and_pll_1"},
|
|
{0xFE014000, 0xFE015FFF, "bcon"},
|
|
{0xFE016000, 0xFE017FFF, "vx1_lvds"},
|
|
{0xFE030000, 0xFE031FFF, "mdc_vpu1"},
|
|
{0xFE032000, 0xFE033FFF, "mdc_vpu0"},
|
|
{0xFE034000, 0xFE035FFF, "mdc_sys"},
|
|
{0xFE036000, 0xFE037FFF, "mdc_cpu"},
|
|
{0xFE068000, 0xFE069FFF, "i2c_tcon"},
|
|
{0xFE094000, 0xFE095FFF, "ddr_test"},
|
|
{0xFE09E000, 0xFE09FFFF, "aucpu"},
|
|
{0xFE0B8000, 0xFE0B9FFF, "dsc_dec"},
|
|
{0xFE300000, 0xFE30FFFF, "dmc1"},
|
|
{0xFE340000, 0xFE34FFFF, "dmc0"},
|
|
{0xFE350000, 0xFE35FFFF, "usb3_0"},
|
|
{0xFE360000, 0xFE36FFFF, "usb2_1"},
|
|
{0xFE370000, 0xFE37FFFF, "usb2_2"},
|
|
{0xFF850000, 0xFF88FFFF, "dpss"},
|
|
};
|
|
|
|
const char *find_register_node(phys_addr_t addr)
|
|
{
|
|
int left = 0, right = iotm.reg_table_size - 1;
|
|
|
|
while (left <= right) {
|
|
int mid = left + (right - left) / 2;
|
|
|
|
if (addr < iotm.reg_table[mid].reg_start)
|
|
right = mid - 1;
|
|
else if (addr > iotm.reg_table[mid].reg_end)
|
|
left = mid + 1;
|
|
else
|
|
return iotm.reg_table[mid].reg_name;
|
|
}
|
|
|
|
return "Unknown";
|
|
}
|
|
|
|
static int range_cmp(const void *a, const void *b)
|
|
{
|
|
const struct reg_entry *ra = a;
|
|
const struct reg_entry *rb = b;
|
|
|
|
if (ra->reg_start < rb->reg_start)
|
|
return -1;
|
|
else if (ra->reg_start > rb->reg_start)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void confirm_chip_mmap(void)
|
|
{
|
|
iotm.reg_table_size = ARRAY_SIZE(reg_table_public);
|
|
|
|
switch (iotm.cpu_type) {
|
|
case IOTM_TYPE_T6D:
|
|
iotm.reg_table_size += ARRAY_SIZE(reg_table_t6d);
|
|
iotm.reg_table = kmalloc(sizeof(reg_table_public) + sizeof(reg_table_t6d),
|
|
GFP_KERNEL);
|
|
if (iotm.reg_table) {
|
|
memcpy(iotm.reg_table, reg_table_public, sizeof(reg_table_public));
|
|
memcpy((char *)(iotm.reg_table) + sizeof(reg_table_public),
|
|
reg_table_t6d, sizeof(reg_table_t6d));
|
|
}
|
|
break;
|
|
case IOTM_TYPE_T6W:
|
|
iotm.reg_table_size += ARRAY_SIZE(reg_table_t6w);
|
|
iotm.reg_table = kmalloc(sizeof(reg_table_public) + sizeof(reg_table_t6w),
|
|
GFP_KERNEL);
|
|
if (iotm.reg_table) {
|
|
memcpy(iotm.reg_table, reg_table_public, sizeof(reg_table_public));
|
|
memcpy((char *)(iotm.reg_table) + sizeof(reg_table_public),
|
|
reg_table_t6w, sizeof(reg_table_t6w));
|
|
}
|
|
break;
|
|
case IOTM_TYPE_T6X:
|
|
iotm.reg_table_size += ARRAY_SIZE(reg_table_t6x);
|
|
iotm.reg_table = kmalloc(sizeof(reg_table_public) + sizeof(reg_table_t6x),
|
|
GFP_KERNEL);
|
|
if (iotm.reg_table) {
|
|
memcpy(iotm.reg_table, reg_table_public, sizeof(reg_table_public));
|
|
memcpy((char *)(iotm.reg_table) + sizeof(reg_table_public),
|
|
reg_table_t6x, sizeof(reg_table_t6x));
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (iotm.reg_table) {
|
|
sort(iotm.reg_table, iotm.reg_table_size,
|
|
sizeof(struct reg_entry), range_cmp, NULL);
|
|
}
|
|
}
|
|
|
|
/* Obtain the operating mode of the IOTM */
|
|
int iotm_monitor_mode_get(void)
|
|
{
|
|
return iotm.monitor_mode;
|
|
}
|
|
EXPORT_SYMBOL(iotm_monitor_mode_get);
|
|
|
|
/* Obtain the DDR size and status of the IOTM */
|
|
int iotm_en_ddr_size_get(int *iotm_ddr_size)
|
|
{
|
|
*iotm_ddr_size = iotm.buf_end - iotm.buf_start + 1;
|
|
return iotm.supported;
|
|
}
|
|
EXPORT_SYMBOL(iotm_en_ddr_size_get);
|
|
|
|
static int iotm_en_set(const char *val, const struct kernel_param *kp)
|
|
{
|
|
u32 iotm_ctrl_mode_val;
|
|
struct arm_smccc_res res;
|
|
|
|
if (!iotm.supported) {
|
|
pr_err("IOTM:not support in this board\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (kstrtoint(val, 0, &iotm_en)) {
|
|
pr_err("IOTM:iotm_en error: %s\n", val);
|
|
return -EINVAL;
|
|
}
|
|
iotm_ctrl_mode_val = readl(iotm.cssys_base + IOTM_CTRL_MODE);
|
|
|
|
switch (iotm_en) {
|
|
case IOTM_DISABLE:
|
|
iotm_ctrl_mode_val |= IOTM_CTRL_MODE_TRACE_DISABLE;
|
|
|
|
pr_info("IOTM:has been disabled\n");
|
|
break;
|
|
case IOTM_ENABLE:
|
|
/* monitor vapb4 and capu bus then start trace data */
|
|
iotm_smccc_smc(AML_IOTM_SMC_CMD, AML_IOTM_JTAG_SMC_ARG,
|
|
AML_IOTM_JTAG_ENABLE_SMC_ARG, 0, 0, res);
|
|
iotm_ctrl_mode_val |= (IOTM_CTRL_MODE_CAPU_ENABLE |
|
|
IOTM_CTRL_MODE_VAPB4_ENABLE | IOTM_CTRL_MODE_TRACE_ENABLE);
|
|
|
|
pr_info("IOTM:has been enabled and can record trace.\n");
|
|
break;
|
|
case IOTM_ENABLE_NO_TRACE:
|
|
iotm_smccc_smc(AML_IOTM_SMC_CMD, AML_IOTM_JTAG_SMC_ARG,
|
|
AML_IOTM_JTAG_DISABLE_SMC_ARG, 0, 0, res);
|
|
iotm_ctrl_mode_val |= (IOTM_CTRL_MODE_CAPU_ENABLE |
|
|
IOTM_CTRL_MODE_VAPB4_ENABLE | IOTM_CTRL_MODE_TRACE_ENABLE);
|
|
|
|
pr_info("IOTM:has been enabled, but can't record trace, just trigger timeout\n");
|
|
break;
|
|
default:
|
|
pr_err("IOTM:iotm_en error: %s\n", val);
|
|
break;
|
|
}
|
|
|
|
writel(iotm_ctrl_mode_val, iotm.cssys_base + IOTM_CTRL_MODE);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct kernel_param_ops iotm_en_ops = {
|
|
.set = iotm_en_set,
|
|
.get = param_get_int,
|
|
};
|
|
module_param_cb(iotm_en, &iotm_en_ops, &iotm_en, 0644);
|
|
|
|
static int iotm_en_setup(char *buf)
|
|
{
|
|
if (!buf)
|
|
return -EINVAL;
|
|
|
|
if (kstrtoint(buf, 0, &iotm_en)) {
|
|
pr_err("IOTM:iotm_en error: %s\n", buf);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
__setup("iotm_en=", iotm_en_setup);
|
|
|
|
static int iotm_dump_mode_setup(char *buf)
|
|
{
|
|
if (!buf)
|
|
return -EINVAL;
|
|
|
|
if (kstrtouint(buf, 0, &iotm.dump_mode) || iotm.dump_mode > 2) {
|
|
pr_err("IOTM:iotm.dump_mode error: %s\n", buf);
|
|
iotm.dump_mode = IOTM_DUMP_WATCHDOG;
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
__setup("iotm_dump_mode=", iotm_dump_mode_setup);
|
|
|
|
static int iotm_monitor_mode_setup(char *buf)
|
|
{
|
|
if (!buf)
|
|
return -EINVAL;
|
|
|
|
if (kstrtouint(buf, 0, &iotm.monitor_mode) || iotm.monitor_mode > 2) {
|
|
pr_err("IOTM:iotm_monitor_mode error: %s\n", buf);
|
|
iotm.monitor_mode = 0;
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
__setup("iotm_monitor_mode=", iotm_monitor_mode_setup);
|
|
|
|
static int iotm_dump_cnt_setup(char *buf)
|
|
{
|
|
if (!buf)
|
|
return -EINVAL;
|
|
|
|
if (kstrtouint(buf, 0, &iotm.dump_cnt)) {
|
|
pr_err("IOTM:iotm.dump_cnt error: %s\n", buf);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
__setup("iotm_dump_cnt=", iotm_dump_cnt_setup);
|
|
|
|
/* interface for the software to record data */
|
|
void iotm_sw_record_write(u32 sw_type, u32 val1, u32 val2)
|
|
{
|
|
unsigned long flags;
|
|
|
|
if (iotm_en != IOTM_ENABLE || !iotm.supported)
|
|
return;
|
|
|
|
spin_lock_irqsave(&iotm.record_lock, flags);
|
|
iotm.ops->sw_record_write(sw_type, val1, val2);
|
|
spin_unlock_irqrestore(&iotm.record_lock, flags);
|
|
}
|
|
EXPORT_SYMBOL(iotm_sw_record_write);
|
|
|
|
static void print_ddr_buf(int *cnt, int trace_cnt, void *trace_start, void *trace_end)
|
|
{
|
|
char buf[TRACE_BUF_SIZE] = "";
|
|
|
|
while (trace_start < trace_end) {
|
|
iotm.ops->print_single_trace(trace_start, buf);
|
|
if (*cnt > trace_cnt)
|
|
pr_err("%s", buf);
|
|
|
|
(*cnt)++;
|
|
trace_start += iotm.bytes_per_trace;
|
|
}
|
|
}
|
|
|
|
/* Ddr buffer can't be confirmed to have been overwritten.
|
|
* So the data has been overwritten defaultly when data is dumped.
|
|
* ddr size needs to be set to a multiple of 48 to confirm corrected.
|
|
*/
|
|
static void auto_dump_ddr_buf(void)
|
|
{
|
|
/* the first data is DDR_TRACE_MAGIC, need to be excluded */
|
|
u32 trace_buf_start = iotm.buf_start + SIZE_OF_TRACE_MAGIC;
|
|
u32 iotm_ddr_size = iotm.buf_end - trace_buf_start + 1;
|
|
u32 iotm_axi_addr_val = readl(iotm.cssys_base + IOTM_AXI_ADDR);
|
|
u32 last_seg_size = iotm_axi_addr_val - trace_buf_start;
|
|
int trace_cnt = iotm_ddr_size / iotm.bytes_per_trace;
|
|
void *buf_start_vaddr;
|
|
void *trace_start, *trace_end;
|
|
u32 offset = 0;
|
|
int cnt = 0;
|
|
u64 prev_time = 0;
|
|
|
|
buf_start_vaddr = phys_to_virt(trace_buf_start);
|
|
if (!buf_start_vaddr) {
|
|
pr_err("IOTM: NOMEM\n");
|
|
return;
|
|
}
|
|
|
|
offset = last_seg_size % iotm.bytes_per_trace;
|
|
if (offset)
|
|
last_seg_size += iotm.bytes_per_trace - offset;
|
|
|
|
if (iotm_axi_addr_val < trace_buf_start || iotm_axi_addr_val > iotm.buf_end + 1) {
|
|
pr_err("IOTM:dump error!IOTM_AXI_ADDR=0x%x, ddr_buf=[0x%x, 0x%x]\n",
|
|
iotm_axi_addr_val, trace_buf_start, iotm.buf_end);
|
|
return;
|
|
}
|
|
|
|
iotm.ops->clean_buf();
|
|
iotm.ops->trace_time_loop_check(buf_start_vaddr + last_seg_size,
|
|
buf_start_vaddr + iotm_ddr_size, &prev_time);
|
|
/*
|
|
* Due to the offset of IOTM_AXI_ADDR, the last few data entries are unreliable,
|
|
* and when calculating whether the time overflows, these entries need to be filtered out.
|
|
*/
|
|
iotm.ops->trace_time_loop_check(buf_start_vaddr,
|
|
buf_start_vaddr + last_seg_size - NUM_OF_UNRELIABLE * iotm.bytes_per_trace,
|
|
&prev_time);
|
|
|
|
trace_cnt = trace_cnt - iotm.dump_cnt;
|
|
if (iotm.ops->is_trace_loop()) {
|
|
trace_start = buf_start_vaddr + last_seg_size;
|
|
trace_end = buf_start_vaddr + iotm_ddr_size;
|
|
print_ddr_buf(&cnt, trace_cnt, trace_start, trace_end);
|
|
}
|
|
|
|
trace_start = buf_start_vaddr;
|
|
trace_end = buf_start_vaddr + last_seg_size;
|
|
print_ddr_buf(&cnt, trace_cnt, trace_start, trace_end);
|
|
}
|
|
|
|
static bool need_dump(void)
|
|
{
|
|
/* before reboot */
|
|
if (iotm.supported) {
|
|
unsigned int val;
|
|
|
|
val = readl(iotm.cssys_base + IOTM_IRQ_CTRL);
|
|
if (iotm.timeout_irq_handled || (val & (IOTM_IRQ_CTRL_CAPU_TIMEOUT |
|
|
IOTM_IRQ_CTRL_VAPB4_TIMEOUT)))
|
|
return true;
|
|
}
|
|
|
|
/* after reboot */
|
|
switch (iotm.dump_mode) {
|
|
case IOTM_DUMP_NONE:
|
|
return false;
|
|
case IOTM_DUMP_WATCHDOG:
|
|
return iotm.ops->is_watchdog();
|
|
case IOTM_DUMP_ALL:
|
|
return true;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static void auto_dump_etb_buf(void)
|
|
{
|
|
u32 write_ptr = 0;
|
|
u32 depth;
|
|
int i, j;
|
|
int words_per_trace = iotm.bytes_per_trace / sizeof(u32);
|
|
u32 iotm_trace[4];
|
|
char buf[TRACE_BUF_SIZE] = "";
|
|
|
|
// stop etb trace
|
|
writel(0x0, iotm.cssys_base + ETB_CTL);
|
|
|
|
depth = readl(iotm.cssys_base + ETB_DEPTH);
|
|
|
|
/* -4: T6D/T6W chip designs that RWP will take 4 more steps. */
|
|
if (!(readl(iotm.cssys_base + ETB_STATUS) & 0x1)) {
|
|
/* etb buffer is not full, need to read from 0 */
|
|
iotm.etb_buf_words_cnt = readl(iotm.cssys_base + ETB_RWP) - 4;
|
|
writel(0x0, iotm.cssys_base + ETB_RRP);
|
|
} else {
|
|
/*
|
|
* etb buffer is full. need to read from ETB_RWP.
|
|
* (depth % words_per_trace): Loop buffer has been overwritten,
|
|
* and the oldest data needs to be found.
|
|
* +words_per_trace: the lastest data is [1,0,0] and useless.
|
|
*/
|
|
write_ptr = readl(iotm.cssys_base + ETB_RWP);
|
|
write_ptr += (depth % words_per_trace) + words_per_trace - 4;
|
|
iotm.etb_buf_words_cnt = depth;
|
|
writel(write_ptr, iotm.cssys_base + ETB_RRP);
|
|
}
|
|
|
|
/* 8K */
|
|
for (i = 0; i < iotm.etb_buf_words_cnt; i += words_per_trace) {
|
|
for (j = 0; j < words_per_trace; j++) {
|
|
iotm_trace[j] = readl(iotm.cssys_base + ETB_RRD);
|
|
|
|
if (!iotm.timeout_irq_handled)
|
|
iotm.etb_buf[i + j] = iotm_trace[j];
|
|
}
|
|
|
|
iotm.ops->print_single_trace(iotm_trace, buf);
|
|
|
|
if (need_dump() && (i > iotm.etb_buf_words_cnt - iotm.dump_cnt * words_per_trace))
|
|
pr_err("%s", buf);
|
|
}
|
|
}
|
|
|
|
static void dump_register_info(void *buf)
|
|
{
|
|
int i = 0;
|
|
int pos = 0;
|
|
u32 *reg_base = NULL;
|
|
|
|
if (iotm.saved_trace_show)
|
|
/* cat /proc/aml_iotm_trace */
|
|
reg_base = iotm.saved_trace;
|
|
else
|
|
reg_base = iotm.cssys_base + ADDR_RANGE0_BEGIN;
|
|
|
|
//monitor_range
|
|
pos += sprintf(buf + pos, "IOTM:include monitor range:[%x, %x]\n",
|
|
reg_base[(ADDR_RANGE7_BEGIN - ADDR_RANGE0_BEGIN) >> 2],
|
|
reg_base[(ADDR_RANGE7_END - ADDR_RANGE0_BEGIN) >> 2]);
|
|
pos += sprintf(buf + pos, "IOTM:exclude monitor range:");
|
|
|
|
for (i = 0; i < MAX_MONITOR_RANGE * 2; i += 2)
|
|
if (reg_base[i] != 0)
|
|
pos += sprintf(buf + pos, "[%x, %x] ",
|
|
reg_base[i], reg_base[i + 1]);
|
|
else
|
|
break;
|
|
|
|
pos += sprintf(buf + pos, "\n");
|
|
|
|
//ddr range
|
|
if (iotm.monitor_mode == AXI_MODE)
|
|
iotm.ops->ddr_range_get(reg_base, buf, &pos);
|
|
else if (iotm.monitor_mode == ETB_MODE)
|
|
pos += sprintf(buf + pos, "IOTM:ETB\n");
|
|
|
|
//print cnt
|
|
pos += sprintf(buf + pos, "IOTM:trace_print cnt = %d\n", iotm.dump_cnt);
|
|
|
|
//others
|
|
pos += sprintf(buf + pos, "IOTM:MONITOR_STATUS:0x%x IOTM_CTRL_MODE = 0x%x\n",
|
|
reg_base[(MONITOR_STATUS - ADDR_RANGE0_BEGIN) >> 2],
|
|
reg_base[(IOTM_CTRL_MODE - ADDR_RANGE0_BEGIN) >> 2]);
|
|
}
|
|
|
|
static void dump_trace(void)
|
|
{
|
|
char buf[TRACE_BUF_SIZE] = "";
|
|
bool is_need_dump = need_dump();
|
|
|
|
if (is_need_dump) {
|
|
pr_err("IOTM:dump trace start==========\n");
|
|
|
|
dump_register_info(buf);
|
|
pr_err("%s", buf);
|
|
}
|
|
|
|
if (iotm.monitor_mode == AXI_MODE && is_need_dump)
|
|
auto_dump_ddr_buf();
|
|
else if (iotm.monitor_mode == ETB_MODE)
|
|
auto_dump_etb_buf();
|
|
|
|
if (is_need_dump)
|
|
pr_err("IOTM:dump trace end==========\n");
|
|
}
|
|
|
|
static void print_timeout_data(void)
|
|
{
|
|
unsigned int val;
|
|
|
|
val = readl(iotm.cssys_base + IOTM_IRQ_CTRL);
|
|
|
|
/* capu timeout, time is setted in bl31. */
|
|
if (val & IOTM_IRQ_CTRL_CAPU_TIMEOUT) {
|
|
pr_err("IOTM:capu timeout(50ms) addr:%x data:%x monitor_status:%x\n",
|
|
readl(iotm.cssys_base + CAPU_MONITOR_ADDR),
|
|
readl(iotm.cssys_base + CAPU_MONITOR_DATA),
|
|
readl(iotm.cssys_base + MONITOR_STATUS));
|
|
}
|
|
|
|
/* vapb4 timeout, time is setted in bl31. */
|
|
if (val & IOTM_IRQ_CTRL_VAPB4_TIMEOUT) {
|
|
pr_err("IOTM:vapb timeout(50ms) addr:%x data:%x monitor_status:%x\n",
|
|
readl(iotm.cssys_base + VAPB4_MONITOR_ADDR),
|
|
readl(iotm.cssys_base + VAPB4_MONITOR_DATA),
|
|
readl(iotm.cssys_base + MONITOR_STATUS));
|
|
}
|
|
}
|
|
|
|
static int iotm_exception_handler(struct notifier_block *nb,
|
|
unsigned long action, void *data)
|
|
{
|
|
unsigned int val;
|
|
|
|
val = readl(iotm.cssys_base + IOTM_IRQ_CTRL);
|
|
|
|
/* panic has nothing with IOTM */
|
|
if (iotm.timeout_irq_handled == 0 && !(val & (IOTM_IRQ_CTRL_CAPU_TIMEOUT |
|
|
IOTM_IRQ_CTRL_VAPB4_TIMEOUT)))
|
|
return 0;
|
|
|
|
print_timeout_data();
|
|
|
|
dump_trace();
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct notifier_block iotm_panic_notifier = {
|
|
.notifier_call = iotm_exception_handler,
|
|
};
|
|
|
|
static irqreturn_t iotm_irq_handler(int irq, void *data)
|
|
{
|
|
unsigned int val;
|
|
int iotm_en_saved = iotm_en;
|
|
|
|
iotm_en = IOTM_DISABLE;
|
|
|
|
val = readl(iotm.cssys_base + IOTM_IRQ_CTRL);
|
|
|
|
if (val & (IOTM_IRQ_CTRL_CAPU_TIMEOUT | IOTM_IRQ_CTRL_VAPB4_TIMEOUT)) {
|
|
pr_err("IOTM:timeout,IOTM_IRQ_CTRL = 0x%x\n", val);
|
|
print_timeout_data();
|
|
iotm.timeout_irq_handled = 1;
|
|
mb(); /* make sure we only clear irq after iotm.timeout_irq_handled is set */
|
|
writel(val | IOTM_IRQ_CTRL_VAPB4_TIMEOUT_CLEAR | IOTM_IRQ_CTRL_CAPU_TIMEOUT_CLEAR,
|
|
iotm.cssys_base + IOTM_IRQ_CTRL);
|
|
}
|
|
|
|
if (val & (IOTM_IRQ_CTRL_PACK_IRQ | IOTM_IRQ_CTRL_VAPB4_FULL | IOTM_IRQ_CTRL_CAPU_FULL)) {
|
|
pr_err("IOTM:full irq! IOTM_IRQ_CTRL = 0x%x, MONITOR_STATUS = 0x%x, IOTM_CTRL_MODE = 0x%x, IOTM_AXI_ADDR = 0x%x\n",
|
|
val,
|
|
readl(iotm.cssys_base + MONITOR_STATUS),
|
|
readl(iotm.cssys_base + IOTM_CTRL_MODE),
|
|
readl(iotm.cssys_base + IOTM_AXI_ADDR));
|
|
writel(val | IOTM_IRQ_CTRL_PACK_IRQ_CLEAR | IOTM_IRQ_CTRL_VAPB4_FULL_CLEAR |
|
|
IOTM_IRQ_CTRL_CAPU_FULL_CLEAR, iotm.cssys_base + IOTM_IRQ_CTRL);
|
|
|
|
if (iotm.monitor_mode == AXI_MODE) {
|
|
int i = NUM_OF_IRQ;
|
|
|
|
/* read iotm status NUM_OF_IRQ times to wait for trace to be brushed out. */
|
|
while (i-- &&
|
|
(readl(iotm.cssys_base + MONITOR_STATUS) & ETB_BUF_FULL) == ETB_BUF_FULL)
|
|
;
|
|
|
|
if (i < 0) {
|
|
val = readl(iotm.cssys_base + IOTM_IRQ_CTRL);
|
|
val |= (IOTM_IRQ_CTRL_PACK_IRQ_MASK |
|
|
IOTM_IRQ_CTRL_VAPB4_FULL_MASK |
|
|
IOTM_IRQ_CTRL_CAPU_FULL_MASK |
|
|
IOTM_IRQ_CTRL_PACK_IRQ_CLEAR |
|
|
IOTM_IRQ_CTRL_VAPB4_FULL_CLEAR |
|
|
IOTM_IRQ_CTRL_CAPU_FULL_CLEAR);
|
|
writel(val, iotm.cssys_base + IOTM_IRQ_CTRL);
|
|
|
|
/* stop iotm. */
|
|
val = readl(iotm.cssys_base + IOTM_CTRL_MODE);
|
|
val |= IOTM_CTRL_MODE_TRACE_DISABLE;
|
|
writel(val, iotm.cssys_base + IOTM_CTRL_MODE);
|
|
|
|
pr_err("IOTM:clear irq,close iotm!IOTM_IRQ_CTRL = 0x%x, MONITOR_STATUS = 0x%x, IOTM_CTRL_MODE = 0x%x, IOTM_AXI_ADDR = 0x%x\n",
|
|
readl(iotm.cssys_base + IOTM_IRQ_CTRL),
|
|
readl(iotm.cssys_base + MONITOR_STATUS),
|
|
readl(iotm.cssys_base + IOTM_CTRL_MODE),
|
|
readl(iotm.cssys_base + IOTM_AXI_ADDR));
|
|
|
|
del_timer(&iotm.ts_to_kernel_timer);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
}
|
|
|
|
/* restart trace data */
|
|
val = readl(iotm.cssys_base + IOTM_CTRL_MODE);
|
|
val |= (IOTM_CTRL_MODE_CAPU_ENABLE | IOTM_CTRL_MODE_VAPB4_ENABLE |
|
|
IOTM_CTRL_MODE_TRACE_ENABLE);
|
|
writel(val, iotm.cssys_base + IOTM_CTRL_MODE);
|
|
|
|
iotm_en = iotm_en_saved;
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int iotm_dt_init(struct platform_device *pdev)
|
|
{
|
|
struct device_node *node;
|
|
struct reserved_mem *rmem;
|
|
u32 iotm_ddr_size;
|
|
int ret = 0;
|
|
struct resource *res;
|
|
struct device_node *dn = pdev->dev.of_node;
|
|
const __be32 *exclude_reg, *include_reg;
|
|
int len, i;
|
|
|
|
/* get the version of iotm */
|
|
if (of_property_read_u32(dn, "version", &iotm.version)) {
|
|
pr_err("IOTM:Failed to read version property\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
switch (iotm.version) {
|
|
case IOTM_V1:
|
|
iotm.ops = &iotm_v1_ops;
|
|
iotm.bytes_per_trace = sizeof(struct iotm_record_v1);
|
|
break;
|
|
case IOTM_V2:
|
|
iotm.ops = &iotm_v2_ops;
|
|
iotm.bytes_per_trace = sizeof(struct iotm_record_v2);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* get registers of a53_debug */
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
if (!res)
|
|
return -ENODEV;
|
|
|
|
iotm.cssys_base = devm_ioremap(&pdev->dev, res->start, resource_size(res));
|
|
if (IS_ERR(iotm.cssys_base)) {
|
|
pr_err("IOTM:fail to ioremap iotm register\n");
|
|
return PTR_ERR(iotm.cssys_base);
|
|
}
|
|
|
|
/* get registers of monitor_range */
|
|
exclude_reg = of_get_property(dn, "exclude-reg", &len);
|
|
if (exclude_reg && !(len % (sizeof(u32) * 2)))
|
|
for (i = 0; i < len / (sizeof(u32) * 2); i++) {
|
|
iotm.range[i].start = be32_to_cpup(exclude_reg++);
|
|
iotm.range[i].end = be32_to_cpup(exclude_reg++);
|
|
}
|
|
|
|
include_reg = of_get_property(dn, "include-reg", &len);
|
|
if (include_reg && !(len % (sizeof(u32) * 2))) {
|
|
iotm.range[7].start = be32_to_cpup(include_reg++);
|
|
iotm.range[7].end = be32_to_cpup(include_reg++);
|
|
}
|
|
|
|
/* get ddr_range buffer */
|
|
if (iotm.monitor_mode == AXI_MODE) {
|
|
node = of_parse_phandle(pdev->dev.of_node, "memory-region", 0);
|
|
if (!node) {
|
|
pr_err("IOTM:Failed to find memory-region node\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
rmem = of_reserved_mem_lookup(node);
|
|
if (!rmem) {
|
|
dev_warn(&pdev->dev, "no such reserved mem of node name %s\n",
|
|
pdev->dev.of_node->name);
|
|
return -ENODEV;
|
|
}
|
|
|
|
ret = of_property_read_u32(node, "iotm-size", &iotm_ddr_size);
|
|
if (ret) {
|
|
pr_err("IOTM:Failed to read iotm-size property\n");
|
|
return ret;
|
|
}
|
|
|
|
iotm.buf_start = (rmem->base + rmem->size - iotm_ddr_size + 1) & PAGE_MASK;
|
|
iotm.buf_end = iotm.buf_start + iotm_ddr_size - 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void etb_coresight_init(void)
|
|
{
|
|
writel(ETB_LOCK_ACCESS_ALL, iotm.cssys_base + FUNNUL_LOCKACCESS);
|
|
iotm.ops->etb_coresight_clk();
|
|
writel(ETB_LOCK_ACCESS_ALL, iotm.cssys_base + REPLICAPTER_LOCKACCESS);
|
|
writel(ETB_LOCK_ACCESS_ALL, iotm.cssys_base + TPIU_LOCKACCESS);
|
|
writel(ETB_LOCK_ACCESS_ALL, iotm.cssys_base + ETB_LOCKACCESS);
|
|
writel(TPIU_ITCTRL_DISABLE, iotm.cssys_base + TPIU_ITCTRL);
|
|
writel(TPIU_ITATBCTR2_ATREADY, iotm.cssys_base + TPIU_ITATBCTR2);
|
|
writel(ETB_FFCR_DISABLE_FORMATTING, iotm.cssys_base + ETB_FFCR);
|
|
writel(ETB_CTL_ENABLE_CAPTURE, iotm.cssys_base + ETB_CTL);
|
|
}
|
|
|
|
void get_boot_time(struct timer_list *t)
|
|
{
|
|
u64 ns_time, pct;
|
|
unsigned long flags;
|
|
|
|
local_irq_save(flags);
|
|
pct = __arch_counter_get_cntpct();
|
|
ns_time = sched_clock();
|
|
local_irq_restore(flags);
|
|
|
|
iotm.ops->boot_time_record(pct, ns_time);
|
|
|
|
if (t)
|
|
mod_timer(t, jiffies + msecs_to_jiffies(10000));
|
|
}
|
|
|
|
static int ddr_trace_magic_set(void)
|
|
{
|
|
struct page **pages;
|
|
int page_count, i;
|
|
phys_addr_t phys = iotm.buf_start;
|
|
size_t size = iotm.bytes_per_trace;
|
|
void *vaddr;
|
|
|
|
page_count = DIV_ROUND_UP(size, PAGE_SIZE);
|
|
|
|
pages = kmalloc_array(page_count, sizeof(struct page *), GFP_KERNEL);
|
|
if (!pages)
|
|
return -ENOMEM;
|
|
|
|
for (i = 0; i < page_count; i++)
|
|
pages[i] = pfn_to_page((phys + i * PAGE_SIZE) >> PAGE_SHIFT);
|
|
|
|
vaddr = vmap(pages, page_count, VM_MAP, pgprot_writecombine(PAGE_KERNEL));
|
|
kfree(pages);
|
|
|
|
if (!vaddr)
|
|
return -ENOMEM;
|
|
|
|
memset(vaddr, 0, size);
|
|
memcpy(vaddr, DDR_TRACE_MAGIC, strlen(DDR_TRACE_MAGIC) + 1);
|
|
|
|
vunmap(vaddr);
|
|
vaddr = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int coresight_init(void)
|
|
{
|
|
unsigned int val, i;
|
|
struct arm_smccc_res res;
|
|
unsigned long flags;
|
|
|
|
/* stop iotm. */
|
|
val = readl(iotm.cssys_base + IOTM_CTRL_MODE);
|
|
val |= IOTM_CTRL_MODE_TRACE_DISABLE;
|
|
writel(val, iotm.cssys_base + IOTM_CTRL_MODE);
|
|
|
|
/* init iotm and reset coresight */
|
|
iotm_smccc_smc(AML_IOTM_SMC_CMD, AML_IOTM_INIT_SMC_ARG, 0, 0, 0, res);
|
|
|
|
/* Whether the iotm records trace data */
|
|
if (iotm_en == IOTM_ENABLE_NO_TRACE) {
|
|
iotm_smccc_smc(AML_IOTM_SMC_CMD, AML_IOTM_JTAG_SMC_ARG,
|
|
AML_IOTM_JTAG_DISABLE_SMC_ARG, 0, 0, res);
|
|
pr_info("IOTM:can't record trace, just trigger timeout\n");
|
|
}
|
|
|
|
/* do monitor registers */
|
|
for (i = 0; i < MAX_MONITOR_RANGE; i++) {
|
|
if (iotm.range[i].start != 0)
|
|
iotm_smccc_smc(AML_IOTM_SMC_CMD, AML_IOTM_RANGE_SMC_ARG, i,
|
|
iotm.range[i].start, iotm.range[i].end, res);
|
|
}
|
|
|
|
if (iotm.monitor_mode == AXI_MODE) {
|
|
int ret = ddr_trace_magic_set();
|
|
u32 trace_buf_start = iotm.buf_start + SIZE_OF_TRACE_MAGIC;
|
|
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Allocate DDR reserved space to MONITOR_BUF_SIZE_LOW. */
|
|
writel(trace_buf_start,
|
|
iotm.cssys_base + MONITOR_BUF_BASEADDR_LOW);
|
|
|
|
iotm.ops->ddr_range_set(trace_buf_start);
|
|
} else if (iotm.monitor_mode == ETB_MODE) {
|
|
/* init coresight and set iotm_mode*/
|
|
etb_coresight_init();
|
|
|
|
val = readl(iotm.cssys_base + IOTM_CTRL_MODE);
|
|
val |= ETB_MODE;
|
|
writel(val, iotm.cssys_base + IOTM_CTRL_MODE);
|
|
}
|
|
|
|
/* Obtain the TS and kernel timestamps */
|
|
get_boot_time(NULL);
|
|
|
|
/* enable iotm */
|
|
val = readl(iotm.cssys_base + IOTM_CTRL_MODE);
|
|
|
|
/* ts1_accurate_sel=4: TS1 = (soc timestamp)/ (2^ts1_accurate_sel) */
|
|
val &= ~IOTM_CTRL_MODE_ACCURATE;
|
|
val |= IOTM_CTRL_MODE_ACCURATE_16;
|
|
/* crtl_iotm_ts=4: TS1:[31:5],TS0: [4:1] */
|
|
val &= ~IOTM_CTRL_MODE_TS;
|
|
val |= IOTM_CTRL_MODE_TS_27;
|
|
|
|
if (iotm.monitor_mode == AXI_MODE) {
|
|
/* confirm IOTM_AXI_ADDR can point to the correct ddr address */
|
|
for (i = 0; i < CONTINUOUS_PULSE; i++) {
|
|
val |= IOTM_CTRL_MODE_TRACE_ENABLE;
|
|
writel(val, iotm.cssys_base + IOTM_CTRL_MODE);
|
|
udelay(CONTINUOUS_PULSE_TIME);
|
|
|
|
val &= ~IOTM_CTRL_MODE_TRACE_ENABLE;
|
|
writel(val, iotm.cssys_base + IOTM_CTRL_MODE);
|
|
}
|
|
}
|
|
|
|
local_irq_save(flags);
|
|
/* monitor vapb4 and capu bus then start trace data */
|
|
val |= (IOTM_CTRL_MODE_CAPU_ENABLE | IOTM_CTRL_MODE_VAPB4_ENABLE |
|
|
IOTM_CTRL_MODE_TRACE_ENABLE);
|
|
writel(val, iotm.cssys_base + IOTM_CTRL_MODE);
|
|
|
|
/* When disable record trace, the axi_addr can't be changed. */
|
|
if (iotm.monitor_mode == AXI_MODE && iotm_en != IOTM_ENABLE_NO_TRACE) {
|
|
/* Check whether 0x0 is written in the trace */
|
|
val = readl(iotm.cssys_base + IOTM_AXI_ADDR);
|
|
val = readl(iotm.cssys_base + IOTM_AXI_ADDR);
|
|
if (val < iotm.buf_start || val >= iotm.buf_end) {
|
|
pr_err("IOTM:AXI_ADDR = 0x%x is wrong,close iotm\n", val);
|
|
|
|
val = readl(iotm.cssys_base + IOTM_CTRL_MODE);
|
|
val |= IOTM_CTRL_MODE_TRACE_DISABLE;
|
|
writel(val, iotm.cssys_base + IOTM_CTRL_MODE);
|
|
|
|
local_irq_restore(flags);
|
|
return -1;
|
|
}
|
|
}
|
|
local_irq_restore(flags);
|
|
return 0;
|
|
}
|
|
|
|
static int iotm_syscore_suspend(void)
|
|
{
|
|
unsigned int iotm_ctrl_mode_val;
|
|
|
|
if (!iotm.supported || iotm_en == IOTM_DISABLE)
|
|
return 0;
|
|
|
|
iotm.en_saved = iotm_en;
|
|
iotm_en = IOTM_DISABLE;
|
|
|
|
iotm_ctrl_mode_val = readl(iotm.cssys_base + IOTM_CTRL_MODE);
|
|
iotm_ctrl_mode_val |= IOTM_CTRL_MODE_TRACE_DISABLE;
|
|
writel(iotm_ctrl_mode_val, iotm.cssys_base + IOTM_CTRL_MODE);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void iotm_syscore_resume(void)
|
|
{
|
|
int ret;
|
|
|
|
if (!iotm.supported || iotm.en_saved == IOTM_DISABLE)
|
|
return;
|
|
|
|
ret = coresight_init();
|
|
if (!ret)
|
|
iotm_en = iotm.en_saved;
|
|
|
|
iotm.en_saved = IOTM_DISABLE;
|
|
}
|
|
|
|
static void iotm_syscore_shutdown(void)
|
|
{
|
|
iotm_syscore_suspend();
|
|
}
|
|
|
|
static struct syscore_ops iotm_syscore_ops = {
|
|
.suspend = iotm_syscore_suspend,
|
|
.resume = iotm_syscore_resume,
|
|
.shutdown = iotm_syscore_shutdown,
|
|
};
|
|
|
|
static int trace_show(struct seq_file *m, void *v)
|
|
{
|
|
void *ptr, *ptr_end = NULL;
|
|
u8 buf[TRACE_BUF_SIZE];
|
|
u64 prev_time = 0;
|
|
|
|
iotm.saved_trace_show = 1;
|
|
seq_puts(m, "IOTM:dump trace start==========\n");
|
|
|
|
dump_register_info(buf);
|
|
seq_printf(m, "%s", buf);
|
|
|
|
ptr = iotm.saved_trace + NUM_OF_IOTM_REGISTERS * sizeof(int);
|
|
if (iotm.monitor_mode == AXI_MODE)
|
|
ptr_end = ptr + iotm.buf_end - iotm.buf_start - SIZE_OF_TRACE_MAGIC + 1;
|
|
else if (iotm.monitor_mode == ETB_MODE)
|
|
ptr_end = ptr + iotm.etb_buf_words_cnt * sizeof(int);
|
|
|
|
iotm.ops->clean_buf();
|
|
iotm.ops->trace_time_loop_check(ptr,
|
|
ptr_end - NUM_OF_UNRELIABLE * iotm.bytes_per_trace, &prev_time);
|
|
while (ptr < ptr_end) {
|
|
iotm.ops->print_single_trace(ptr, buf);
|
|
seq_printf(m, "%s", buf);
|
|
ptr += iotm.bytes_per_trace;
|
|
}
|
|
|
|
seq_puts(m, "IOTM:dump trace end==========\n");
|
|
iotm.saved_trace_show = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void iotm_proc_init(void)
|
|
{
|
|
struct proc_dir_entry *aml_iotm_trace;
|
|
unsigned int ddr_size = iotm.buf_end - iotm.buf_start + 1;
|
|
u32 saved_trace_size = 0;
|
|
int i;
|
|
|
|
if (iotm.monitor_mode == AXI_MODE)
|
|
saved_trace_size = ddr_size + NUM_OF_IOTM_REGISTERS * sizeof(int);
|
|
else if (iotm.monitor_mode == ETB_MODE)
|
|
saved_trace_size = (iotm.etb_buf_words_cnt + NUM_OF_IOTM_REGISTERS) * sizeof(int);
|
|
|
|
iotm.saved_trace = vmalloc(saved_trace_size);
|
|
if (!iotm.saved_trace)
|
|
return;
|
|
|
|
aml_iotm_trace = proc_create_single_data("aml_iotm_trace",
|
|
0400, NULL, trace_show, NULL);
|
|
if (!aml_iotm_trace) {
|
|
pr_err("IOTM:fail to create /proc/aml_iotm_trace\n");
|
|
vfree(iotm.saved_trace);
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < NUM_OF_IOTM_REGISTERS; i++)
|
|
((u32 *)iotm.saved_trace)[i] =
|
|
readl(iotm.cssys_base + ADDR_RANGE0_BEGIN + (i << 2));
|
|
|
|
if (iotm.monitor_mode == AXI_MODE) {
|
|
u32 iotm_axi_addr_val = readl(iotm.cssys_base + IOTM_AXI_ADDR);
|
|
u32 trace_buf_start = iotm.buf_start + SIZE_OF_TRACE_MAGIC;
|
|
u32 iotm_ddr_size = iotm.buf_end - trace_buf_start + 1;
|
|
u32 offset = 0;
|
|
u32 last_seg_size = iotm_axi_addr_val - trace_buf_start;
|
|
|
|
offset = last_seg_size % iotm.bytes_per_trace;
|
|
if (offset)
|
|
last_seg_size += iotm.bytes_per_trace - offset;
|
|
|
|
if (iotm_axi_addr_val < trace_buf_start || iotm_axi_addr_val > iotm.buf_end + 1) {
|
|
vfree(iotm.saved_trace);
|
|
return;
|
|
}
|
|
|
|
memcpy_fromio(iotm.saved_trace + NUM_OF_IOTM_REGISTERS * sizeof(int),
|
|
phys_to_virt(trace_buf_start + last_seg_size),
|
|
iotm_ddr_size - last_seg_size);
|
|
memcpy_fromio(iotm.saved_trace + NUM_OF_IOTM_REGISTERS * sizeof(int) +
|
|
iotm_ddr_size - last_seg_size, phys_to_virt(trace_buf_start),
|
|
iotm_axi_addr_val - trace_buf_start);
|
|
} else if (iotm.monitor_mode == ETB_MODE) {
|
|
memcpy_fromio(iotm.saved_trace + NUM_OF_IOTM_REGISTERS * sizeof(int),
|
|
(void *)iotm.etb_buf, iotm.etb_buf_words_cnt * sizeof(int));
|
|
}
|
|
}
|
|
|
|
static bool ddr_trace_valid(void)
|
|
{
|
|
void *vaddr = phys_to_virt(iotm.buf_start);
|
|
|
|
if (!vaddr) {
|
|
pr_err("IOTM: NOMEM\n");
|
|
return false;
|
|
}
|
|
|
|
if (!memcmp(vaddr, DDR_TRACE_MAGIC, strlen(DDR_TRACE_MAGIC)))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
static void watchdog_dump_trace(void)
|
|
{
|
|
unsigned int val;
|
|
|
|
// stop iotm, munual flush
|
|
val = readl(iotm.cssys_base + IOTM_CTRL_MODE);
|
|
writel(val | IOTM_CTRL_MODE_TRACE_DISABLE, iotm.cssys_base + IOTM_CTRL_MODE);
|
|
|
|
/* watchdog reset need clear MONITOR_RANGE_INCLUDE_RST_MASK */
|
|
val = readl(iotm.cssys_base + MONITOR_RANGE_INCLUDE);
|
|
val &= ~MONITOR_RANGE_INCLUDE_RST_MASK;
|
|
writel(val, iotm.cssys_base + MONITOR_RANGE_INCLUDE);
|
|
|
|
/* init coresight to dump trace */
|
|
if (iotm.monitor_mode == ETB_MODE) {
|
|
etb_coresight_init();
|
|
iotm.etb_buf = kmalloc(ETB_SIZE, GFP_KERNEL);
|
|
if (!iotm.etb_buf)
|
|
return;
|
|
} else if (iotm.monitor_mode == AXI_MODE) {
|
|
if (!ddr_trace_valid())
|
|
return;
|
|
}
|
|
|
|
dump_trace();
|
|
iotm_proc_init();
|
|
|
|
if (iotm.monitor_mode == ETB_MODE)
|
|
kfree(iotm.etb_buf);
|
|
}
|
|
|
|
static int __ref iotm_probe(struct platform_device *pdev)
|
|
{
|
|
int ret;
|
|
int irq;
|
|
|
|
iotm.cpu_type = (unsigned long)of_device_get_match_data(&pdev->dev);
|
|
confirm_chip_mmap();
|
|
|
|
ret = iotm_dt_init(pdev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
watchdog_dump_trace();
|
|
|
|
spin_lock_init(&iotm.record_lock);
|
|
|
|
ret = coresight_init();
|
|
if (ret)
|
|
return ret;
|
|
|
|
irq = platform_get_irq(pdev, 0);
|
|
if (irq < 0) {
|
|
pr_err("IOTM:fail to get iotm irq\n");
|
|
return irq;
|
|
}
|
|
|
|
ret = devm_request_irq(&pdev->dev, irq, iotm_irq_handler, 0, "iotm", NULL);
|
|
if (ret) {
|
|
pr_err("IOTM:fail to request irq: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
/* print iotm trace */
|
|
atomic_notifier_chain_register(&panic_notifier_list, &iotm_panic_notifier);
|
|
|
|
register_syscore_ops(&iotm_syscore_ops);
|
|
|
|
iotm.ops->boot_timer_setup();
|
|
|
|
iotm.supported = 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_OF
|
|
static const struct of_device_id iotm_match[] = {
|
|
{
|
|
.compatible = "amlogic, iotm-t6d",
|
|
.data = (void *)IOTM_TYPE_T6D,
|
|
},
|
|
{
|
|
.compatible = "amlogic, iotm-t6w",
|
|
.data = (void *)IOTM_TYPE_T6W,
|
|
},
|
|
{
|
|
.compatible = "amlogic, iotm-t6x",
|
|
.data = (void *)IOTM_TYPE_T6X,
|
|
},
|
|
{}
|
|
};
|
|
#endif
|
|
|
|
static struct platform_driver iotm_driver = {
|
|
.driver = {
|
|
.name = "iotm",
|
|
.owner = THIS_MODULE,
|
|
#ifdef CONFIG_OF
|
|
.of_match_table = iotm_match,
|
|
#endif
|
|
},
|
|
.probe = iotm_probe,
|
|
};
|
|
|
|
int iotm_init(void)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (iotm_en != IOTM_DISABLE)
|
|
ret = platform_driver_register(&iotm_driver);
|
|
|
|
return ret;
|
|
}
|