// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2019 MediaTek Inc. */ #include #include #include #include #include #include #include #include "cmdq-util.h" #ifdef CMDQ_SECURE_SUPPORT #include "cmdq-sec-mailbox.h" #endif #ifdef CONFIG_MTK_SMI_EXT #include "smi_public.h" #endif #ifdef CONFIG_MTK_DEVAPC #include #endif #define CMDQ_MBOX_NUM 2 #define CMDQ_HW_MAX 2 #define CMDQ_RECORD_NUM 512 #define CMDQ_FIRST_ERR_SIZE 524288 /* 512k */ #define CMDQ_CURR_IRQ_STATUS 0x10 #define CMDQ_CURR_LOADED_THR 0x18 #define CMDQ_THR_EXEC_CYCLES 0x34 #define CMDQ_THR_TIMEOUT_TIMER 0x38 #define GCE_DBG_CTL 0x3000 #define GCE_DBG0 0x3004 #define GCE_DBG2 0x300C #define GCE_DBG3 0x3010 #define util_time_to_us(start, end, duration) \ { \ u64 _duration = end - start; \ do_div(_duration, 1000); \ duration = (s32)_duration; \ } struct cmdq_util_error { spinlock_t lock; bool enable; char *buffer; u32 length; u64 nsec; struct timeval errtm; char caller[TASK_COMM_LEN]; // TODO }; struct cmdq_util_dentry { struct dentry *status; struct dentry *record; struct dentry *log_feature; u8 bit_feature; }; struct cmdq_record { unsigned long pkt; s32 priority; /* task priority (not thread priority) */ s8 id; s32 thread; /* allocated thread */ s32 reorder; u32 size; bool is_secure; /* true for secure task */ u64 submit; /* epoch time of IOCTL/Kernel API call */ u64 trigger; /* epoch time of enable HW thread */ /* epoch time of start waiting for task completion */ u64 wait; u64 irq; /* epoch time of IRQ event */ u64 done; /* epoch time of sw leaving wait and task finish */ unsigned long start; /* buffer start address */ unsigned long end; /* command end address */ u64 last_inst; /* last instruction, jump addr */ u32 exec_begin; /* task execute time in hardware thread */ u32 exec_end; /* task execute time in hardware thread */ }; struct cmdq_util { struct cmdq_util_error err; struct cmdq_util_dentry fs; struct cmdq_record record[CMDQ_RECORD_NUM]; u16 record_idx; void *cmdq_mbox[CMDQ_MBOX_NUM]; void *cmdq_sec_mbox[CMDQ_MBOX_NUM]; u32 mbox_cnt; u32 mbox_sec_cnt; const char *first_err_mod[CMDQ_HW_MAX]; }; static struct cmdq_util util; static DEFINE_MUTEX(cmdq_record_mutex); static DEFINE_MUTEX(cmdq_dump_mutex); u32 cmdq_util_get_bit_feature(void) { return util.fs.bit_feature; } bool cmdq_util_is_feature_en(u8 feature) { return (util.fs.bit_feature & BIT(feature)) != 0; } void cmdq_util_error_enable(void) { if (!util.err.nsec) { util.err.nsec = sched_clock(); do_gettimeofday(&util.err.errtm); } util.err.enable = true; } EXPORT_SYMBOL(cmdq_util_error_enable); void cmdq_util_error_disable(void) { util.err.enable = false; } EXPORT_SYMBOL(cmdq_util_error_disable); void cmdq_util_dump_lock(void) { mutex_lock(&cmdq_dump_mutex); } EXPORT_SYMBOL(cmdq_util_dump_lock); void cmdq_util_dump_unlock(void) { mutex_unlock(&cmdq_dump_mutex); } EXPORT_SYMBOL(cmdq_util_dump_unlock); s32 cmdq_util_error_save_lst(const char *format, va_list args) { unsigned long flags; s32 size; if (!util.err.enable || !util.err.buffer) return -EFAULT; spin_lock_irqsave(&util.err.lock, flags); size = vsnprintf(util.err.buffer + util.err.length, CMDQ_FIRST_ERR_SIZE - util.err.length, format, args); if (size >= CMDQ_FIRST_ERR_SIZE - util.err.length) cmdq_log("size:%d over buf size:%d", size, CMDQ_FIRST_ERR_SIZE - util.err.length); util.err.length += size; spin_unlock_irqrestore(&util.err.lock, flags); if (util.err.length >= CMDQ_FIRST_ERR_SIZE) { cmdq_util_error_disable(); cmdq_err("util.err.length:%u is over CMDQ_FIRST_ERR_SIZE:%u", util.err.length, CMDQ_FIRST_ERR_SIZE); } return 0; } EXPORT_SYMBOL(cmdq_util_error_save_lst); s32 cmdq_util_error_save(const char *format, ...) { va_list args; if (!util.err.enable || !util.err.buffer) return -EFAULT; va_start(args, format); cmdq_util_error_save_lst(format, args); va_end(args); return 0; } EXPORT_SYMBOL(cmdq_util_error_save); static int cmdq_util_status_print(struct seq_file *seq, void *data) { u64 sec = util.err.nsec; unsigned long nsec = do_div(sec, 1000000000); struct tm nowtm; u32 i; if (util.err.length) { time_to_tm(util.err.errtm.tv_sec, sys_tz.tz_minuteswest * 60, &nowtm); seq_printf(seq, "[cmdq] first error kernel time:[%5llu.%06lu] UTC time:[%04ld-%02d-%02d %02d:%02d:%02d.%06ld]\n", sec, nsec, nowtm.tm_year + 1900, nowtm.tm_mon + 1, nowtm.tm_mday, nowtm.tm_hour, nowtm.tm_min, nowtm.tm_sec, util.err.errtm.tv_usec); seq_printf(seq, "%s", util.err.buffer); } seq_puts(seq, "[cmdq] dump all thread current status\n"); for (i = 0; i < util.mbox_cnt; i++) cmdq_thread_dump_all_seq(util.cmdq_mbox[i], seq); return 0; } static int cmdq_util_record_print(struct seq_file *seq, void *data) { struct cmdq_record *rec; u32 acq_time, irq_time, begin_wait, exec_time, total_time; u64 submit_sec, hw_time; unsigned long submit_rem, hw_time_rem; s32 i, idx; mutex_lock(&cmdq_record_mutex); seq_puts(seq, "index,pkt,task priority,sec,size,gce,thread,"); seq_puts(seq, "submit,acq_time(us),irq_time(us),begin_wait(us),exec_time(us),total_time(us),start,end,jump,"); seq_puts(seq, "exec begin,exec end,hw_time(us),\n"); idx = util.record_idx; for (i = 0; i < ARRAY_SIZE(util.record); i++) { idx--; if (idx < 0) idx = ARRAY_SIZE(util.record) - 1; rec = &util.record[idx]; if (!rec->pkt) continue; seq_printf(seq, "%u,%#lx,%d,%d,%u,%hhd,%d,", idx, rec->pkt, rec->priority, (int)rec->is_secure, rec->size, rec->id, rec->thread); submit_sec = rec->submit; submit_rem = do_div(submit_sec, 1000000000); util_time_to_us(rec->submit, rec->trigger, acq_time); util_time_to_us(rec->trigger, rec->irq, irq_time); util_time_to_us(rec->submit, rec->wait, begin_wait); util_time_to_us(rec->trigger, rec->done, exec_time); util_time_to_us(rec->submit, rec->done, total_time); seq_printf(seq, "%llu.%06lu,%u,%u,%u,%u,%u,%#lx,%#lx,%#llx,", submit_sec, submit_rem / 1000, acq_time, irq_time, begin_wait, exec_time, total_time, rec->start, rec->end, rec->last_inst); hw_time = rec->exec_end > rec->exec_begin ? rec->exec_end - rec->exec_begin : ~rec->exec_begin + 1 + rec->exec_end; hw_time_rem = (u32)CMDQ_TICK_TO_US(hw_time); seq_printf(seq, "%u,%u,%llu.%06lu,\n", rec->exec_begin, rec->exec_end, hw_time, hw_time_rem); } mutex_unlock(&cmdq_record_mutex); return 0; } static int cmdq_util_status_open(struct inode *inode, struct file *file) { return single_open(file, cmdq_util_status_print, inode->i_private); } static int cmdq_util_record_open(struct inode *inode, struct file *file) { return single_open(file, cmdq_util_record_print, inode->i_private); } static const struct file_operations cmdq_util_status_fops = { .owner = THIS_MODULE, .open = cmdq_util_status_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static const struct file_operations cmdq_util_record_fops = { .owner = THIS_MODULE, .open = cmdq_util_record_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static int cmdq_util_log_feature_get(void *data, u64 *val) { cmdq_msg("data:%p val:%#llx bit_feature:%#x", data, *val, util.fs.bit_feature); return util.fs.bit_feature; } static int cmdq_util_log_feature_set(void *data, u64 val) { if (val == CMDQ_LOG_FEAT_NUM) { util.fs.bit_feature = 0; cmdq_msg("data:%p val:%#llx bit_feature:%#x reset", data, val, util.fs.bit_feature); return 0; } if (val >= CMDQ_LOG_FEAT_NUM) { cmdq_err("data:%p val:%#llx cannot be over %#x", data, val, CMDQ_LOG_FEAT_NUM); return -EINVAL; } util.fs.bit_feature |= (1 << val); cmdq_msg("data:%p val:%#llx bit_feature:%#x", data, val, util.fs.bit_feature); return 0; } DEFINE_SIMPLE_ATTRIBUTE(cmdq_util_log_feature_fops, cmdq_util_log_feature_get, cmdq_util_log_feature_set, "%llu"); /* sync with request in atf */ enum cmdq_smc_request { CMDQ_ENABLE_DEBUG, }; #if !IS_ENABLED(CONFIG_FPGA_EARLY_PORTING) static atomic_t cmdq_dbg_ctrl = ATOMIC_INIT(0); #endif void cmdq_util_dump_dbg_reg(void *chan) { void *base = cmdq_mbox_get_base(chan); u32 dbg0[3], dbg2[6], dbg3, i; u32 id; if (!base) { cmdq_util_msg("no cmdq dbg since no base"); return; } id = cmdq_util_hw_id((u32)cmdq_mbox_get_base_pa(chan)); #if !IS_ENABLED(CONFIG_FPGA_EARLY_PORTING) if (atomic_cmpxchg(&cmdq_dbg_ctrl, 0, 1) == 0) { struct arm_smccc_res res; arm_smccc_smc(MTK_SIP_CMDQ_CONTROL, CMDQ_ENABLE_DEBUG, id, 0, 0, 0, 0, 0, &res); } #endif /* debug select */ for (i = 0; i < 6; i++) { if (i < 3) { writel((i << 8) | i, base + GCE_DBG_CTL); dbg0[i] = readl(base + GCE_DBG0); } else { /* only other part */ writel(i << 8, base + GCE_DBG_CTL); } dbg2[i] = readl(base + GCE_DBG2); } dbg3 = readl(base + GCE_DBG3); cmdq_util_user_msg(chan, "id:%u dbg0:%#x %#x %#x dbg2:%#x %#x %#x %#x %#x %#x dbg3:%#x", id, dbg0[0], dbg0[1], dbg0[2], dbg2[0], dbg2[1], dbg2[2], dbg2[3], dbg2[4], dbg2[5], dbg3); } void cmdq_util_track(struct cmdq_pkt *pkt) { struct cmdq_record *record; struct cmdq_client *cl = pkt->cl; struct cmdq_pkt_buffer *buf; u64 done = sched_clock(); u32 offset, *perf; mutex_lock(&cmdq_record_mutex); record = &util.record[util.record_idx++]; record->pkt = (unsigned long)pkt; record->priority = pkt->priority; record->size = pkt->cmd_buf_size; record->submit = pkt->rec_submit; record->trigger = pkt->rec_trigger; record->wait = pkt->rec_wait; record->irq = pkt->rec_irq; record->done = done; if (cl && cl->chan) { record->thread = cmdq_mbox_chan_id(cl->chan); record->id = cmdq_util_hw_id((u32)cmdq_mbox_get_base_pa( cl->chan)); } else { record->thread = -1; record->id = -1; } #ifdef CMDQ_SECURE_SUPPORT if (pkt->sec_data) record->is_secure = true; #endif if (util.record_idx >= CMDQ_RECORD_NUM) util.record_idx = 0; if (!list_empty(&pkt->buf)) { buf = list_first_entry(&pkt->buf, typeof(*buf), list_entry); record->start = buf->pa_base; buf = list_last_entry(&pkt->buf, typeof(*buf), list_entry); offset = CMDQ_CMD_BUFFER_SIZE - (pkt->buf_size - pkt->cmd_buf_size); record->end = buf->pa_base + offset; record->last_inst = *(u64 *)(buf->va_base + offset - CMDQ_INST_SIZE); perf = cmdq_pkt_get_perf_ret(pkt); if (perf) { record->exec_begin = perf[0]; record->exec_end = perf[1]; } } mutex_unlock(&cmdq_record_mutex); } void cmdq_util_dump_smi(void) { #if defined(CONFIG_MTK_SMI_EXT) && !defined(CONFIG_FPGA_EARLY_PORTING) && \ !defined(CONFIG_MTK_SMI_VARIANT) int smi_hang; smi_hang = smi_debug_bus_hang_detect(1, "CMDQ"); cmdq_util_err("smi hang:%d", smi_hang); #else cmdq_util_err("[WARNING]not enable SMI dump now"); #endif } #ifdef CONFIG_MTK_DEVAPC static void cmdq_util_handle_devapc_vio(void) { u32 i; cmdq_util_msg("%s mbox cnt:%u", __func__, util.mbox_cnt); for (i = 0; i < util.mbox_cnt; i++) cmdq_thread_dump_all(util.cmdq_mbox[i]); #ifdef CMDQ_SECURE_SUPPORT cmdq_util_msg("%s mbox sec cnt:%u", __func__, util.mbox_sec_cnt); for (i = 0; i < util.mbox_sec_cnt; i++) cmdq_sec_dump_thread_all(util.cmdq_sec_mbox[i]); #endif } static struct devapc_vio_callbacks devapc_vio_handle = { .id = INFRA_SUBSYS_GCE, .debug_dump = cmdq_util_handle_devapc_vio, }; #endif u8 cmdq_util_track_ctrl(void *cmdq, phys_addr_t base, bool sec) { cmdq_msg("%s cmdq:%p sec:%s", __func__, cmdq, sec ? "true" : "false"); if (sec) util.cmdq_sec_mbox[util.mbox_sec_cnt++] = cmdq; else util.cmdq_mbox[util.mbox_cnt++] = cmdq; return (u8)cmdq_util_hw_id((u32)base); } void cmdq_util_set_first_err_mod(void *chan, const char *mod) { u32 hw_id = cmdq_util_hw_id((u32)cmdq_mbox_get_base_pa(chan)); util.first_err_mod[hw_id] = mod; } const char *cmdq_util_get_first_err_mod(void *chan) { u32 hw_id = cmdq_util_hw_id((u32)cmdq_mbox_get_base_pa(chan)); return util.first_err_mod[hw_id]; } static int __init cmdq_util_init(void) { struct dentry *dir; bool exists = false; cmdq_msg("%s begin", __func__); spin_lock_init(&util.err.lock); util.err.buffer = vzalloc(CMDQ_FIRST_ERR_SIZE); if (!util.err.buffer) return -ENOMEM; dir = debugfs_lookup("cmdq", NULL); if (!dir) { dir = debugfs_create_dir("cmdq", NULL); if (!dir) { cmdq_err("debugfs_create_dir cmdq failed"); return -EINVAL; } } else exists = true; util.fs.status = debugfs_create_file( "cmdq-status", 0444, dir, &util, &cmdq_util_status_fops); if (IS_ERR(util.fs.status)) { cmdq_err("debugfs_create_file cmdq-status failed:%ld", PTR_ERR(util.fs.status)); return PTR_ERR(util.fs.status); } util.fs.record = debugfs_create_file( "cmdq-record", 0444, dir, &util, &cmdq_util_record_fops); if (IS_ERR(util.fs.record)) { cmdq_err("debugfs_create_file cmdq-record failed:%ld", PTR_ERR(util.fs.record)); return PTR_ERR(util.fs.record); } util.fs.log_feature = debugfs_create_file("cmdq-log-feature", 0444, dir, &util, &cmdq_util_log_feature_fops); if (IS_ERR(util.fs.log_feature)) { cmdq_err("debugfs_create_file cmdq-log-feature failed:%ld", PTR_ERR(util.fs.log_feature)); return PTR_ERR(util.fs.log_feature); } if (exists) dput(dir); cmdq_util_log_feature_set(NULL, CMDQ_LOG_FEAT_PERF); #ifdef CONFIG_MTK_DEVAPC register_devapc_vio_callback(&devapc_vio_handle); #endif cmdq_msg("%s end", __func__); return 0; } late_initcall(cmdq_util_init);