// SPDX-License-Identifier: (GPL-2.0+ OR MIT) /* * Copyright (c) 2019 Amlogic, Inc. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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; }