kernel_samsung_a34x-permissive/drivers/misc/mediatek/cmdq/mailbox/cmdq-util.c
2024-04-28 15:49:01 +02:00

556 lines
13 KiB
C
Executable file

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2019 MediaTek Inc.
*/
#include <linux/kernel.h>
#include <linux/io.h>
#include <linux/debugfs.h>
#include <linux/sched/clock.h>
#include <linux/soc/mediatek/mtk-cmdq.h>
#include <linux/arm-smccc.h>
#include <mt-plat/mtk_secure_api.h>
#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 <devapc_public.h>
#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);