1043 lines
27 KiB
C
1043 lines
27 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (c) 2021 MediaTek Inc.
|
|
*/
|
|
|
|
#include "cqhci.h"
|
|
#include "mtk-mmc.h"
|
|
#include "mtk-mmc-dbg.h"
|
|
#include "../core/queue.h"
|
|
#include <linux/clk-provider.h>
|
|
#include <linux/module.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/sched/clock.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/tracepoint.h>
|
|
#include <linux/proc_fs.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/uidgid.h>
|
|
#include <mt-plat/mrdump.h>
|
|
#include <mt-plat/mtk_blocktag.h>
|
|
|
|
/* For msdc register dump */
|
|
u16 msdc_offsets[] = {
|
|
MSDC_CFG,
|
|
MSDC_IOCON,
|
|
MSDC_PS,
|
|
MSDC_INT,
|
|
MSDC_INTEN,
|
|
MSDC_FIFOCS,
|
|
SDC_CFG,
|
|
SDC_CMD,
|
|
SDC_ARG,
|
|
SDC_STS,
|
|
SDC_RESP0,
|
|
SDC_RESP1,
|
|
SDC_RESP2,
|
|
SDC_RESP3,
|
|
SDC_BLK_NUM,
|
|
SDC_ADV_CFG0,
|
|
MSDC_NEW_RX_CFG,
|
|
EMMC_IOCON,
|
|
SDC_ACMD_RESP,
|
|
DMA_SA_H4BIT,
|
|
MSDC_DMA_SA,
|
|
MSDC_DMA_CTRL,
|
|
MSDC_DMA_CFG,
|
|
MSDC_DBG_SEL,
|
|
MSDC_DBG_OUT,
|
|
MSDC_PATCH_BIT,
|
|
MSDC_PATCH_BIT1,
|
|
MSDC_PATCH_BIT2,
|
|
MSDC_PAD_TUNE,
|
|
MSDC_PAD_TUNE0,
|
|
MSDC_PAD_TUNE1,
|
|
MSDC_DAT_RDDLY0,
|
|
MSDC_DAT_RDDLY1,
|
|
MSDC_PAD_CTL0,
|
|
PAD_DS_TUNE,
|
|
PAD_CMD_TUNE,
|
|
EMMC50_PAD_DAT01_TUNE,
|
|
EMMC50_PAD_DAT23_TUNE,
|
|
EMMC50_PAD_DAT45_TUNE,
|
|
EMMC50_PAD_DAT67_TUNE,
|
|
EMMC50_CFG0,
|
|
EMMC50_CFG1,
|
|
EMMC50_CFG3,
|
|
EMMC50_CFG4,
|
|
SDC_FIFO_CFG,
|
|
|
|
0xFFFF /*as mark of end */
|
|
};
|
|
|
|
u16 msdc_offsets_top[] = {
|
|
EMMC_TOP_CONTROL,
|
|
EMMC_TOP_CMD,
|
|
EMMC50_PAD_CTL0,
|
|
EMMC50_PAD_DS_TUNE,
|
|
EMMC50_PAD_DAT0_TUNE,
|
|
EMMC50_PAD_DAT1_TUNE,
|
|
EMMC50_PAD_DAT2_TUNE,
|
|
EMMC50_PAD_DAT3_TUNE,
|
|
EMMC50_PAD_DAT4_TUNE,
|
|
EMMC50_PAD_DAT5_TUNE,
|
|
EMMC50_PAD_DAT6_TUNE,
|
|
EMMC50_PAD_DAT7_TUNE,
|
|
LOOP_TEST_CONTROL,
|
|
MSDC_TOP_NEW_RX_CFG,
|
|
|
|
0xFFFF /*as mark of end */
|
|
};
|
|
|
|
/*---------------------------------------------------------------------*/
|
|
/* Command dump */
|
|
/*---------------------------------------------------------------------*/
|
|
struct mmc_host *mtk_mmc_host[] = {NULL, NULL};
|
|
/*#define MTK_MSDC_ERROR_TUNE_DEBUG*/
|
|
|
|
#define dbg_max_cnt (4000)
|
|
#define sd_dbg_max_cnt (500)
|
|
#define MMC_AEE_BUFFER_SIZE (300 * 1024)
|
|
|
|
struct dbg_run_host_log {
|
|
unsigned long long time_sec;
|
|
unsigned long long time_usec;
|
|
int type;
|
|
int cmd;
|
|
int arg;
|
|
int skip;
|
|
};
|
|
|
|
struct dbg_task_log {
|
|
u32 address;
|
|
unsigned long long size;
|
|
};
|
|
struct dbg_dma_cmd_log {
|
|
unsigned long long time;
|
|
int cmd;
|
|
int arg;
|
|
};
|
|
|
|
static unsigned int dbg_host_cnt;
|
|
static unsigned int dbg_sd_cnt;
|
|
|
|
static struct dbg_run_host_log dbg_run_host_log_dat[dbg_max_cnt];
|
|
static struct dbg_run_host_log dbg_run_sd_log_dat[sd_dbg_max_cnt];
|
|
|
|
static unsigned int print_cpu_test = UINT_MAX;
|
|
|
|
static spinlock_t cmd_hist_lock;
|
|
static bool cmd_hist_init;
|
|
static bool cmd_hist_enabled;
|
|
static char *mmc_aee_buffer;
|
|
|
|
/**
|
|
* Data structures to store tracepoints information
|
|
*/
|
|
struct tracepoints_table {
|
|
const char *name;
|
|
void *func;
|
|
struct tracepoint *tp;
|
|
bool init;
|
|
};
|
|
/***********************************************************************/
|
|
static void msdc_dump_clock_sts_core(char **buff, unsigned long *size,
|
|
struct seq_file *m, struct msdc_host *host)
|
|
{
|
|
char buffer[512] = {0};
|
|
char *buf_ptr = buffer;
|
|
int n = 0;
|
|
|
|
if (host->bulk_clks[0].clk)
|
|
n += scnprintf(&buf_ptr[n], sizeof(buffer) - n,
|
|
"[%s]enable:%d freq:%lu,",
|
|
host->bulk_clks[0].id,
|
|
__clk_is_enabled(host->bulk_clks[0].clk),
|
|
clk_get_rate(host->bulk_clks[0].clk));
|
|
if (host->bulk_clks[1].clk)
|
|
n += scnprintf(&buf_ptr[n], sizeof(buffer) - n,
|
|
"[%s]enable:%d freq:%lu,",
|
|
host->bulk_clks[1].id,
|
|
__clk_is_enabled(host->bulk_clks[1].clk),
|
|
clk_get_rate(host->bulk_clks[1].clk));
|
|
if (host->bulk_clks[2].clk)
|
|
n += scnprintf(&buf_ptr[n], sizeof(buffer) - n,
|
|
"[%s]enable:%d freq:%lu,",
|
|
host->bulk_clks[2].id,
|
|
__clk_is_enabled(host->bulk_clks[2].clk),
|
|
clk_get_rate(host->bulk_clks[2].clk));
|
|
if (host->src_clk)
|
|
n += scnprintf(&buf_ptr[n], sizeof(buffer) - n,
|
|
"[src_clk]enable:%d freq:%lu,",
|
|
__clk_is_enabled(host->src_clk), clk_get_rate(host->src_clk));
|
|
if (host->macro_clk)
|
|
n += scnprintf(&buf_ptr[n], sizeof(buffer) - n,
|
|
"[macro_clk]enable:%d freq:%lu,",
|
|
__clk_is_enabled(host->macro_clk), clk_get_rate(host->macro_clk));
|
|
if (host->h_clk)
|
|
n += scnprintf(&buf_ptr[n], sizeof(buffer) - n,
|
|
"[h_clk]enable:%d freq:%lu,",
|
|
__clk_is_enabled(host->h_clk), clk_get_rate(host->h_clk));
|
|
if (host->bus_clk)
|
|
n += scnprintf(&buf_ptr[n], sizeof(buffer) - n,
|
|
"[bus_clk]enable:%d freq:%lu,",
|
|
__clk_is_enabled(host->bus_clk), clk_get_rate(host->bus_clk));
|
|
if (host->new_rx_clk)
|
|
n += scnprintf(&buf_ptr[n], sizeof(buffer) - n,
|
|
"[new_rx_clk]enable:%d freq:%lu,",
|
|
__clk_is_enabled(host->new_rx_clk), clk_get_rate(host->new_rx_clk));
|
|
if (host->src_clk_cg)
|
|
n += scnprintf(&buf_ptr[n], sizeof(buffer) - n,
|
|
"[src_clk_cg]enable:%d freq:%lu\n",
|
|
__clk_is_enabled(host->src_clk_cg), clk_get_rate(host->src_clk_cg));
|
|
|
|
if (host->crypto_clk)
|
|
n += scnprintf(&buf_ptr[n], sizeof(buffer) - n,
|
|
"[crypto_clk]enable:%d freq:%lu\n",
|
|
__clk_is_enabled(host->crypto_clk), clk_get_rate(host->crypto_clk));
|
|
|
|
if (host->crypto_cg)
|
|
n += scnprintf(&buf_ptr[n], sizeof(buffer) - n,
|
|
"[crypto_cg]enable:%d freq:%lu\n",
|
|
__clk_is_enabled(host->crypto_cg), clk_get_rate(host->crypto_cg));
|
|
|
|
SPREAD_PRINTF(buff, size, m, "%s", buffer);
|
|
}
|
|
|
|
void msdc_dump_clock_sts(char **buff, unsigned long *size,
|
|
struct seq_file *m, struct msdc_host *host)
|
|
{
|
|
msdc_dump_clock_sts_core(buff, size, m, host);
|
|
}
|
|
|
|
void msdc_dump_ldo_sts(char **buff, unsigned long *size,
|
|
struct seq_file *m, struct msdc_host *host)
|
|
{
|
|
u32 id = host->id;
|
|
struct mmc_host *mmc = mmc_from_priv(host);
|
|
|
|
switch (id) {
|
|
/*
|
|
* PMIC only provide regulator APIs with mutex protection.
|
|
* Therefore, can not dump msdc ldo status in IRQ context.
|
|
* Enable dump msdc ldo if you make sure the dump context
|
|
* is correct.
|
|
*/
|
|
case MSDC_EMMC:
|
|
SPREAD_PRINTF(buff, size, m,
|
|
" VEMC_EN=0x%x, VEMC_VOL=%duV\n",
|
|
regulator_is_enabled(mmc->supply.vmmc),
|
|
regulator_get_voltage(mmc->supply.vmmc));
|
|
break;
|
|
case MSDC_SD:
|
|
SPREAD_PRINTF(buff, size, m,
|
|
" VMCH_EN=0x%x, VMCH_VOL=%duV\n",
|
|
regulator_is_enabled(mmc->supply.vmmc),
|
|
regulator_get_voltage(mmc->supply.vmmc));
|
|
SPREAD_PRINTF(buff, size, m,
|
|
" VMC_EN=0x%x, VMC_VOL=%duV\n",
|
|
regulator_is_enabled(mmc->supply.vqmmc),
|
|
regulator_get_voltage(mmc->supply.vqmmc));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
EXPORT_SYMBOL(msdc_dump_ldo_sts);
|
|
|
|
void msdc_dump_register_core(char **buff, unsigned long *size,
|
|
struct seq_file *m, struct msdc_host *host)
|
|
{
|
|
u32 id = host->id;
|
|
u32 msg_size = 0;
|
|
u32 val;
|
|
u16 offset, i;
|
|
char buffer[PRINTF_REGISTER_BUFFER_SIZE + 1];
|
|
char *buffer_cur_ptr = buffer;
|
|
|
|
memset(buffer, 0, PRINTF_REGISTER_BUFFER_SIZE);
|
|
SPREAD_PRINTF(buff, size, m, "MSDC%d normal register\n", id);
|
|
for (i = 0; msdc_offsets[i] != (u16)0xFFFF; i++) {
|
|
offset = msdc_offsets[i];
|
|
val = readl(host->base + offset);
|
|
MSDC_REG_PRINT(offset, val, ONE_REGISTER_STRING_SIZE, msg_size,
|
|
PRINTF_REGISTER_BUFFER_SIZE, buffer, buffer_cur_ptr, m);
|
|
}
|
|
SPREAD_PRINTF(buff, size, m, "%s\n", buffer);
|
|
|
|
if (!host->top_base)
|
|
return;
|
|
|
|
MSDC_RST_REG_PRINT_BUF(msg_size,
|
|
PRINTF_REGISTER_BUFFER_SIZE, buffer, buffer_cur_ptr);
|
|
|
|
SPREAD_PRINTF(buff, size, m, "MSDC%d top register\n", id);
|
|
|
|
for (i = 0; msdc_offsets_top[i] != (u16)0xFFFF; i++) {
|
|
offset = msdc_offsets_top[i];
|
|
val = readl(host->top_base + offset);
|
|
MSDC_REG_PRINT(offset, val, ONE_REGISTER_STRING_SIZE, msg_size,
|
|
PRINTF_REGISTER_BUFFER_SIZE, buffer, buffer_cur_ptr, m);
|
|
}
|
|
SPREAD_PRINTF(buff, size, m, "%s\n", buffer);
|
|
}
|
|
|
|
void msdc_dump_register(char **buff, unsigned long *size,
|
|
struct seq_file *m, struct msdc_host *host)
|
|
{
|
|
msdc_dump_register_core(buff, size, m, host);
|
|
}
|
|
|
|
void msdc_dump_dbg_register(char **buff, unsigned long *size,
|
|
struct seq_file *m, struct msdc_host *host)
|
|
{
|
|
u32 msg_size = 0;
|
|
u16 i;
|
|
char buffer[PRINTF_REGISTER_BUFFER_SIZE + 1];
|
|
char *buffer_cur_ptr = buffer;
|
|
|
|
memset(buffer, 0, PRINTF_REGISTER_BUFFER_SIZE);
|
|
SPREAD_PRINTF(buff, size, m, "MSDC debug register [set:out]\n");
|
|
for (i = 0; i < MSDC_DEBUG_REGISTER_COUNT + 1; i++) {
|
|
msg_size += ONE_REGISTER_STRING_SIZE;
|
|
if (msg_size >= PRINTF_REGISTER_BUFFER_SIZE) {
|
|
SPREAD_PRINTF(buff, size, m, "%s", buffer);
|
|
memset(buffer, 0, PRINTF_REGISTER_BUFFER_SIZE);
|
|
msg_size = ONE_REGISTER_STRING_SIZE;
|
|
buffer_cur_ptr = buffer;
|
|
}
|
|
writel(i, host->base + MSDC_DBG_SEL);
|
|
snprintf(buffer_cur_ptr, ONE_REGISTER_STRING_SIZE + 1,
|
|
"[%.3hx:%.8x]", i, readl(host->base + MSDC_DBG_OUT));
|
|
buffer_cur_ptr += ONE_REGISTER_STRING_SIZE;
|
|
}
|
|
SPREAD_PRINTF(buff, size, m, "%s\n", buffer);
|
|
|
|
writel(0x27, host->base + MSDC_DBG_SEL);
|
|
msg_size = 0;
|
|
memset(buffer, 0, PRINTF_REGISTER_BUFFER_SIZE);
|
|
buffer_cur_ptr = buffer;
|
|
SPREAD_PRINTF(buff, size, m, "MSDC debug 0x224 register [set:out]\n");
|
|
for (i = 0; i < 12; i++) {
|
|
msg_size += ONE_REGISTER_STRING_SIZE;
|
|
if (msg_size >= PRINTF_REGISTER_BUFFER_SIZE) {
|
|
SPREAD_PRINTF(buff, size, m, "%s", buffer);
|
|
memset(buffer, 0, PRINTF_REGISTER_BUFFER_SIZE);
|
|
msg_size = ONE_REGISTER_STRING_SIZE;
|
|
buffer_cur_ptr = buffer;
|
|
}
|
|
writel(i, host->base + EMMC50_CFG4);
|
|
snprintf(buffer_cur_ptr, ONE_REGISTER_STRING_SIZE + 1,
|
|
"[%.3hx:%.8x]", i, readl(host->base + MSDC_DBG_OUT));
|
|
buffer_cur_ptr += ONE_REGISTER_STRING_SIZE;
|
|
}
|
|
SPREAD_PRINTF(buff, size, m, "%s\n", buffer);
|
|
|
|
writel(0, host->base + MSDC_DBG_SEL);
|
|
}
|
|
|
|
void msdc_dump_info(char **buff, unsigned long *size, struct seq_file *m,
|
|
struct msdc_host *host)
|
|
{
|
|
if (host == NULL) {
|
|
SPREAD_PRINTF(buff, size, m, "msdc host null\n");
|
|
return;
|
|
}
|
|
|
|
msdc_dump_register(buff, size, m, host);
|
|
|
|
if (!buff)
|
|
mdelay(10);
|
|
|
|
msdc_dump_clock_sts(buff, size, m, host);
|
|
|
|
msdc_dump_ldo_sts(buff, size, m, host);
|
|
|
|
if (!buff)
|
|
mdelay(10);
|
|
|
|
msdc_dump_dbg_register(buff, size, m, host);
|
|
}
|
|
EXPORT_SYMBOL(msdc_dump_info);
|
|
|
|
static inline u8 *dbg_get_desc(struct cqhci_host *cq_host, u8 tag)
|
|
{
|
|
return cq_host->desc_base + (tag * cq_host->slot_sz);
|
|
}
|
|
|
|
static void cqhci_prep_task_desc_dbg(struct mmc_request *mrq,
|
|
u64 *data, bool intr)
|
|
{
|
|
u32 req_flags = mrq->data->flags;
|
|
|
|
*data = CQHCI_VALID(1) |
|
|
CQHCI_END(1) |
|
|
CQHCI_INT(intr) |
|
|
CQHCI_ACT(0x5) |
|
|
CQHCI_FORCED_PROG(!!(req_flags & MMC_DATA_FORCED_PRG)) |
|
|
CQHCI_DATA_TAG(!!(req_flags & MMC_DATA_DAT_TAG)) |
|
|
CQHCI_DATA_DIR(!!(req_flags & MMC_DATA_READ)) |
|
|
CQHCI_PRIORITY(!!(req_flags & MMC_DATA_PRIO)) |
|
|
CQHCI_QBAR(!!(req_flags & MMC_DATA_QBR)) |
|
|
CQHCI_REL_WRITE(!!(req_flags & MMC_DATA_REL_WR)) |
|
|
CQHCI_BLK_COUNT(mrq->data->blocks) |
|
|
CQHCI_BLK_ADDR((u64)mrq->data->blk_addr);
|
|
|
|
}
|
|
|
|
static bool is_dcmd_request(struct mmc_request *mrq)
|
|
{
|
|
struct mmc_queue_req *mqrq;
|
|
struct request *req;
|
|
struct mmc_queue *mq;
|
|
struct mmc_host *host;
|
|
|
|
/* skip non-cqe cmd */
|
|
if (PTR_ERR(mrq->completion.wait.task_list.next)
|
|
&& PTR_ERR(mrq->completion.wait.task_list.prev))
|
|
return false;
|
|
|
|
mqrq = container_of(mrq, struct mmc_queue_req, brq.mrq);
|
|
req = blk_mq_rq_from_pdu(mqrq);
|
|
|
|
if (!req || !req->q || !req->q->queuedata)
|
|
return false;
|
|
|
|
mq = (struct mmc_queue *)(req->q->queuedata);
|
|
|
|
if (IS_ERR_OR_NULL(mq->card) || !mq->card->host)
|
|
return false;
|
|
|
|
host = mq->card->host;
|
|
|
|
if (host->cqe_enabled && !host->hsq_enabled) {
|
|
if (req_op(req) == REQ_OP_FLUSH)
|
|
return (host->caps2 & MMC_CAP2_CQE_DCMD) ? true : false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void __emmc_store_buf_start(void *__data, struct mmc_host *mmc,
|
|
struct mmc_request *mrq)
|
|
{
|
|
unsigned long long t, tn;
|
|
unsigned long long nanosec_rem = 0;
|
|
static int last_cmd, last_arg, skip;
|
|
int l_skip = 0;
|
|
u64 *task_desc = NULL;
|
|
u64 data;
|
|
struct cqhci_host *cq_host = NULL;
|
|
unsigned long flags;
|
|
struct msdc_host *host = NULL;
|
|
|
|
if (!cmd_hist_enabled)
|
|
return;
|
|
|
|
if (!mmc)
|
|
return;
|
|
|
|
host = mmc_priv(mmc);
|
|
cq_host = mmc->cqe_private;
|
|
t = cpu_clock(print_cpu_test);
|
|
tn = t;
|
|
nanosec_rem = do_div(t, 1000000000)/1000;
|
|
|
|
spin_lock_irqsave(&host->log_lock, flags);
|
|
|
|
if (!(mrq->cmd) && cq_host && cq_host->desc_base) { /* CQE */
|
|
task_desc = (__le64 __force *)dbg_get_desc(cq_host, mrq->tag);
|
|
cqhci_prep_task_desc_dbg(mrq, &data, 1);
|
|
*task_desc = cpu_to_le64(data);
|
|
|
|
dbg_run_host_log_dat[dbg_host_cnt].time_sec = t;
|
|
dbg_run_host_log_dat[dbg_host_cnt].time_usec = nanosec_rem;
|
|
dbg_run_host_log_dat[dbg_host_cnt].type = 5;
|
|
dbg_run_host_log_dat[dbg_host_cnt].cmd = MAGIC_CQHCI_DBG_NUM_L + mrq->tag;
|
|
dbg_run_host_log_dat[dbg_host_cnt].arg = lower_32_bits(*task_desc);
|
|
dbg_run_host_log_dat[dbg_host_cnt].skip = l_skip;
|
|
dbg_host_cnt++;
|
|
if (dbg_host_cnt >= dbg_max_cnt)
|
|
dbg_host_cnt = 0;
|
|
dbg_run_host_log_dat[dbg_host_cnt].time_sec = t;
|
|
dbg_run_host_log_dat[dbg_host_cnt].time_usec = nanosec_rem;
|
|
dbg_run_host_log_dat[dbg_host_cnt].type = 5;
|
|
dbg_run_host_log_dat[dbg_host_cnt].cmd = MAGIC_CQHCI_DBG_NUM_U + mrq->tag;
|
|
dbg_run_host_log_dat[dbg_host_cnt].arg = upper_32_bits(*task_desc);
|
|
dbg_run_host_log_dat[dbg_host_cnt].skip = l_skip;
|
|
dbg_host_cnt++;
|
|
} else if (mrq->cmd) { /* non-CQE */
|
|
/* skip log if last cmd rsp are the same */
|
|
if (last_cmd == mrq->cmd->opcode &&
|
|
last_arg == mrq->cmd->arg && mrq->cmd->opcode == 13) {
|
|
skip++;
|
|
if (dbg_host_cnt == 0)
|
|
dbg_host_cnt = dbg_max_cnt;
|
|
/* remove type = 0, command */
|
|
dbg_host_cnt--;
|
|
spin_unlock_irqrestore(&host->log_lock, flags);
|
|
return;
|
|
}
|
|
last_cmd = mrq->cmd->opcode;
|
|
last_arg = mrq->cmd->arg;
|
|
l_skip = skip;
|
|
skip = 0;
|
|
|
|
if (is_dcmd_request(mrq)) {
|
|
dbg_run_host_log_dat[dbg_host_cnt].time_sec = t;
|
|
dbg_run_host_log_dat[dbg_host_cnt].time_usec = nanosec_rem;
|
|
dbg_run_host_log_dat[dbg_host_cnt].type = 60;
|
|
dbg_run_host_log_dat[dbg_host_cnt].cmd = mrq->cmd->opcode;
|
|
dbg_run_host_log_dat[dbg_host_cnt].arg = mrq->cmd->arg;
|
|
dbg_run_host_log_dat[dbg_host_cnt].skip = l_skip;
|
|
dbg_host_cnt++;
|
|
} else {
|
|
dbg_run_host_log_dat[dbg_host_cnt].time_sec = t;
|
|
dbg_run_host_log_dat[dbg_host_cnt].time_usec = nanosec_rem;
|
|
dbg_run_host_log_dat[dbg_host_cnt].type = 0;
|
|
dbg_run_host_log_dat[dbg_host_cnt].cmd = mrq->cmd->opcode;
|
|
dbg_run_host_log_dat[dbg_host_cnt].arg = mrq->cmd->arg;
|
|
dbg_run_host_log_dat[dbg_host_cnt].skip = l_skip;
|
|
dbg_host_cnt++;
|
|
}
|
|
}
|
|
|
|
if (dbg_host_cnt >= dbg_max_cnt)
|
|
dbg_host_cnt = 0;
|
|
|
|
spin_unlock_irqrestore(&host->log_lock, flags);
|
|
}
|
|
|
|
static void __emmc_store_buf_end(void *__data, struct mmc_host *mmc,
|
|
struct mmc_request *mrq)
|
|
{
|
|
unsigned long long t;
|
|
unsigned long long nanosec_rem = 0;
|
|
static int last_cmd, last_arg, skip;
|
|
int l_skip = 0;
|
|
struct cqhci_host *cq_host = NULL;
|
|
unsigned long flags;
|
|
struct msdc_host *host = NULL;
|
|
|
|
if (!cmd_hist_enabled)
|
|
return;
|
|
|
|
if (!mmc)
|
|
return;
|
|
|
|
host = mmc_priv(mmc);
|
|
cq_host = mmc->cqe_private;
|
|
t = cpu_clock(print_cpu_test);
|
|
|
|
nanosec_rem = do_div(t, 1000000000)/1000;
|
|
|
|
spin_lock_irqsave(&host->log_lock, flags);
|
|
|
|
if (!(mrq->cmd)) { /* CQE */
|
|
dbg_run_host_log_dat[dbg_host_cnt].time_sec = t;
|
|
dbg_run_host_log_dat[dbg_host_cnt].time_usec = nanosec_rem;
|
|
dbg_run_host_log_dat[dbg_host_cnt].type = 5;
|
|
dbg_run_host_log_dat[dbg_host_cnt].cmd = MAGIC_CQHCI_DBG_NUM_RI + mrq->tag;
|
|
dbg_run_host_log_dat[dbg_host_cnt].arg = cqhci_readl(cq_host, CQHCI_CRA);
|
|
dbg_run_host_log_dat[dbg_host_cnt].skip = l_skip;
|
|
dbg_host_cnt++;
|
|
} else if (mrq->cmd) { /* non-CQE */
|
|
/* skip log if last cmd rsp are the same */
|
|
if (last_cmd == mrq->cmd->opcode &&
|
|
last_arg == mrq->cmd->arg && mrq->cmd->opcode == 13) {
|
|
skip++;
|
|
if (dbg_host_cnt == 0)
|
|
dbg_host_cnt = dbg_max_cnt;
|
|
/* remove type = 0, command */
|
|
dbg_host_cnt--;
|
|
spin_unlock_irqrestore(&host->log_lock, flags);
|
|
return;
|
|
}
|
|
last_cmd = mrq->cmd->opcode;
|
|
last_arg = mrq->cmd->resp[0];
|
|
l_skip = skip;
|
|
skip = 0;
|
|
|
|
if (is_dcmd_request(mrq)) {
|
|
dbg_run_host_log_dat[dbg_host_cnt].time_sec = t;
|
|
dbg_run_host_log_dat[dbg_host_cnt].time_usec = nanosec_rem;
|
|
dbg_run_host_log_dat[dbg_host_cnt].type = 61;
|
|
dbg_run_host_log_dat[dbg_host_cnt].cmd = mrq->cmd->opcode;
|
|
dbg_run_host_log_dat[dbg_host_cnt].arg = cqhci_readl(cq_host, CQHCI_CRDCT);
|
|
dbg_run_host_log_dat[dbg_host_cnt].skip = l_skip;
|
|
dbg_host_cnt++;
|
|
} else {
|
|
dbg_run_host_log_dat[dbg_host_cnt].time_sec = t;
|
|
dbg_run_host_log_dat[dbg_host_cnt].time_usec = nanosec_rem;
|
|
dbg_run_host_log_dat[dbg_host_cnt].type = 1;
|
|
dbg_run_host_log_dat[dbg_host_cnt].cmd = mrq->cmd->opcode;
|
|
dbg_run_host_log_dat[dbg_host_cnt].arg = mrq->cmd->resp[0];
|
|
dbg_run_host_log_dat[dbg_host_cnt].skip = l_skip;
|
|
dbg_host_cnt++;
|
|
}
|
|
}
|
|
if (dbg_host_cnt >= dbg_max_cnt)
|
|
dbg_host_cnt = 0;
|
|
|
|
spin_unlock_irqrestore(&host->log_lock, flags);
|
|
}
|
|
|
|
static void __sd_store_buf_start(void *__data, struct mmc_host *mmc,
|
|
struct mmc_request *mrq)
|
|
{
|
|
unsigned long long t, tn;
|
|
unsigned long long nanosec_rem;
|
|
unsigned long flags;
|
|
struct msdc_host *host = NULL;
|
|
|
|
if (!cmd_hist_enabled)
|
|
return;
|
|
|
|
if (!mmc)
|
|
return;
|
|
|
|
host = mmc_priv(mmc);
|
|
t = cpu_clock(print_cpu_test);
|
|
tn = t;
|
|
nanosec_rem = do_div(t, 1000000000)/1000;
|
|
|
|
spin_lock_irqsave(&host->log_lock, flags);
|
|
|
|
dbg_run_sd_log_dat[dbg_sd_cnt].time_sec = t;
|
|
dbg_run_sd_log_dat[dbg_sd_cnt].time_usec = nanosec_rem;
|
|
dbg_run_sd_log_dat[dbg_sd_cnt].type = 0;
|
|
dbg_run_sd_log_dat[dbg_sd_cnt].cmd = mrq->cmd->opcode;
|
|
dbg_run_sd_log_dat[dbg_sd_cnt].arg = mrq->cmd->arg;
|
|
dbg_run_sd_log_dat[dbg_sd_cnt].skip = 0;
|
|
dbg_sd_cnt++;
|
|
|
|
if (dbg_sd_cnt >= sd_dbg_max_cnt)
|
|
dbg_sd_cnt = 0;
|
|
|
|
spin_unlock_irqrestore(&host->log_lock, flags);
|
|
}
|
|
|
|
static void __sd_store_buf_end(void *__data, struct mmc_host *mmc,
|
|
struct mmc_request *mrq)
|
|
{
|
|
unsigned long long t, tn;
|
|
unsigned long long nanosec_rem;
|
|
static int last_cmd, last_arg, skip;
|
|
int l_skip = 0;
|
|
unsigned long flags;
|
|
struct msdc_host *host = NULL;
|
|
|
|
if (!cmd_hist_enabled)
|
|
return;
|
|
|
|
if (!mmc)
|
|
return;
|
|
|
|
host = mmc_priv(mmc);
|
|
t = cpu_clock(print_cpu_test);
|
|
tn = t;
|
|
nanosec_rem = do_div(t, 1000000000)/1000;
|
|
|
|
/* skip log if last cmd rsp are the same */
|
|
if (last_cmd == mrq->cmd->opcode &&
|
|
last_arg == mrq->cmd->arg && mrq->cmd->opcode == 13) {
|
|
skip++;
|
|
spin_lock_irqsave(&host->log_lock, flags);
|
|
if (dbg_sd_cnt == 0)
|
|
dbg_sd_cnt = sd_dbg_max_cnt;
|
|
/* remove type = 0, command */
|
|
dbg_sd_cnt--;
|
|
spin_unlock_irqrestore(&host->log_lock, flags);
|
|
return;
|
|
}
|
|
last_cmd = mrq->cmd->opcode;
|
|
last_arg = mrq->cmd->arg;
|
|
l_skip = skip;
|
|
skip = 0;
|
|
|
|
spin_lock_irqsave(&host->log_lock, flags);
|
|
|
|
dbg_run_sd_log_dat[dbg_sd_cnt].time_sec = t;
|
|
dbg_run_sd_log_dat[dbg_sd_cnt].time_usec = nanosec_rem;
|
|
dbg_run_sd_log_dat[dbg_sd_cnt].type = 1;
|
|
dbg_run_sd_log_dat[dbg_sd_cnt].cmd = mrq->cmd->opcode;
|
|
dbg_run_sd_log_dat[dbg_sd_cnt].arg = mrq->cmd->resp[0];
|
|
dbg_run_sd_log_dat[dbg_sd_cnt].skip = l_skip;
|
|
dbg_sd_cnt++;
|
|
|
|
if (dbg_sd_cnt >= sd_dbg_max_cnt)
|
|
dbg_sd_cnt = 0;
|
|
|
|
spin_unlock_irqrestore(&host->log_lock, flags);
|
|
}
|
|
|
|
/* all cases which except softirq of IO */
|
|
static void record_mmc_send_command(void *__data,
|
|
struct mmc_host *mmc, struct mmc_request *mrq)
|
|
{
|
|
if (!(mmc->caps2 & MMC_CAP2_NO_MMC))
|
|
__emmc_store_buf_start(__data, mmc, mrq);
|
|
else if (!(mmc->caps2 & MMC_CAP2_NO_SD))
|
|
__sd_store_buf_start(__data, mmc, mrq);
|
|
}
|
|
|
|
static void record_mmc_receive_command(void *__data,
|
|
struct mmc_host *mmc, struct mmc_request *mrq)
|
|
{
|
|
if (!(mmc->caps2 & MMC_CAP2_NO_MMC))
|
|
__emmc_store_buf_end(__data, mmc, mrq);
|
|
else if (!(mmc->caps2 & MMC_CAP2_NO_SD))
|
|
__sd_store_buf_end(__data, mmc, mrq);
|
|
}
|
|
|
|
void mmc_cmd_dump(char **buff, unsigned long *size, struct seq_file *m,
|
|
struct mmc_host *mmc, u32 latest_cnt)
|
|
{
|
|
int i, j;
|
|
unsigned long long time_sec, time_usec;
|
|
int type, cmd, arg, skip;
|
|
u32 dump_cnt;
|
|
|
|
if (!mmc || !mmc->card)
|
|
return;
|
|
|
|
dump_cnt = min_t(u32, latest_cnt, dbg_max_cnt);
|
|
|
|
i = dbg_host_cnt - 1;
|
|
if (i < 0)
|
|
i = dbg_max_cnt - 1;
|
|
|
|
for (j = 0; j < dump_cnt; j++) {
|
|
time_sec = dbg_run_host_log_dat[i].time_sec;
|
|
time_usec = dbg_run_host_log_dat[i].time_usec;
|
|
type = dbg_run_host_log_dat[i].type;
|
|
cmd = dbg_run_host_log_dat[i].cmd;
|
|
arg = dbg_run_host_log_dat[i].arg;
|
|
skip = dbg_run_host_log_dat[i].skip;
|
|
|
|
SPREAD_PRINTF(buff, size, m,
|
|
"%03d [%5llu.%06llu]%2d %3d %08x (%d)\n",
|
|
j, time_sec, time_usec,
|
|
type, cmd, arg, skip);
|
|
i--;
|
|
if (i < 0)
|
|
i = dbg_max_cnt - 1;
|
|
}
|
|
|
|
SPREAD_PRINTF(buff, size, m,
|
|
"eMMC claimed(%d), claim_cnt(%d), claimer pid(%d), comm %s\n",
|
|
mmc->claimed, mmc->claim_cnt,
|
|
mmc->claimer && mmc->claimer->task ?
|
|
mmc->claimer->task->pid : 0,
|
|
mmc->claimer && mmc->claimer->task ?
|
|
mmc->claimer->task->comm : "NULL");
|
|
}
|
|
|
|
void sd_cmd_dump(char **buff, unsigned long *size, struct seq_file *m,
|
|
struct mmc_host *mmc, u32 latest_cnt)
|
|
{
|
|
int i, j;
|
|
unsigned long long time_sec, time_usec;
|
|
int type, cmd, arg, skip;
|
|
u32 dump_cnt;
|
|
|
|
if (!mmc)
|
|
return;
|
|
|
|
dump_cnt = min_t(u32, latest_cnt, sd_dbg_max_cnt);
|
|
|
|
i = dbg_sd_cnt - 1;
|
|
if (i < 0)
|
|
i = sd_dbg_max_cnt - 1;
|
|
|
|
for (j = 0; j < dump_cnt; j++) {
|
|
time_sec = dbg_run_sd_log_dat[i].time_sec;
|
|
time_usec = dbg_run_sd_log_dat[i].time_usec;
|
|
type = dbg_run_sd_log_dat[i].type;
|
|
cmd = dbg_run_sd_log_dat[i].cmd;
|
|
arg = dbg_run_sd_log_dat[i].arg;
|
|
skip = dbg_run_sd_log_dat[i].skip;
|
|
|
|
SPREAD_PRINTF(buff, size, m,
|
|
"%03d [%5llu.%06llu]%2d %3d %08x (%d)\n",
|
|
j, time_sec, time_usec,
|
|
type, cmd, arg, skip);
|
|
i--;
|
|
if (i < 0)
|
|
i = sd_dbg_max_cnt - 1;
|
|
}
|
|
|
|
SPREAD_PRINTF(buff, size, m,
|
|
"SD claimed(%d), claim_cnt(%d), claimer pid(%d), comm %s\n",
|
|
mmc->claimed, mmc->claim_cnt,
|
|
mmc->claimer && mmc->claimer->task ? mmc->claimer->task->pid : 0,
|
|
mmc->claimer && mmc->claimer->task ? mmc->claimer->task->comm : "NULL");
|
|
}
|
|
|
|
|
|
void msdc_dump_host_state(char **buff, unsigned long *size,
|
|
struct seq_file *m)
|
|
{
|
|
/* add log description*/
|
|
SPREAD_PRINTF(buff, size, m,
|
|
"column 1 : log number(Reverse order);\n");
|
|
SPREAD_PRINTF(buff, size, m,
|
|
"column 2 : kernel time\n");
|
|
SPREAD_PRINTF(buff, size, m,
|
|
"column 3 : type(0-cmd, 1-resp, 5-cqhci cmd, 60-cqhci dcmd doorbell,");
|
|
SPREAD_PRINTF(buff, size, m,
|
|
"61-cqhci dcmd complete(irq in));\n");
|
|
SPREAD_PRINTF(buff, size, m,
|
|
"column 4&5 : cmd index&arg(1XX-task XX's task descriptor low 32bit, ");
|
|
SPREAD_PRINTF(buff, size, m,
|
|
"2XX-task XX's task descriptor high 32bit, ");
|
|
SPREAD_PRINTF(buff, size, m,
|
|
"5XX-task XX's task completion(irq in), ");
|
|
SPREAD_PRINTF(buff, size, m,
|
|
"others index-command index) ");
|
|
SPREAD_PRINTF(buff, size, m,
|
|
"others arg-command arg);\n");
|
|
}
|
|
|
|
static int cmd_hist_on(void)
|
|
{
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&cmd_hist_lock, flags);
|
|
cmd_hist_enabled = true;
|
|
spin_unlock_irqrestore(&cmd_hist_lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_hist_off(void)
|
|
{
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&cmd_hist_lock, flags);
|
|
cmd_hist_enabled = false;
|
|
spin_unlock_irqrestore(&cmd_hist_lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct tracepoints_table interests[] = {
|
|
{.name = "mmc_request_start", .func = record_mmc_send_command},
|
|
{.name = "mmc_request_done", .func = record_mmc_receive_command},
|
|
};
|
|
|
|
#define FOR_EACH_INTEREST(i) \
|
|
for (i = 0; i < sizeof(interests) / sizeof(struct tracepoints_table); \
|
|
i++)
|
|
|
|
/**
|
|
* Find the struct tracepoint* associated with a given tracepoint
|
|
* name.
|
|
*/
|
|
static void lookup_tracepoints(struct tracepoint *tp, void *ignore)
|
|
{
|
|
int i;
|
|
|
|
FOR_EACH_INTEREST(i) {
|
|
if (strcmp(interests[i].name, tp->name) == 0)
|
|
interests[i].tp = tp;
|
|
}
|
|
}
|
|
|
|
#ifndef USER_BUILD_KERNEL
|
|
#define PROC_PERM 0660
|
|
#else
|
|
#define PROC_PERM 0440
|
|
#endif
|
|
|
|
static ssize_t mmc_debug_proc_write(struct file *file, const char *buf,
|
|
size_t count, loff_t *data)
|
|
{
|
|
unsigned long op = MMCDBG_UNKNOWN;
|
|
char cmd_buf[16];
|
|
|
|
if (count == 0 || count > 15)
|
|
return -EINVAL;
|
|
|
|
if (copy_from_user(cmd_buf, buf, count))
|
|
return -EINVAL;
|
|
|
|
cmd_buf[count] = '\0';
|
|
if (kstrtoul(cmd_buf, 15, &op))
|
|
return -EINVAL;
|
|
|
|
if (op == MMCDBG_CMD_LIST_ENABLE) {
|
|
cmd_hist_on();
|
|
pr_info("mmc_mtk_dbg: cmd history on\n");
|
|
} else if (op == MMCDBG_CMD_LIST_DISABLE) {
|
|
cmd_hist_off();
|
|
pr_info("mmc_mtk_dbg: cmd history off\n");
|
|
} else
|
|
return -EINVAL;
|
|
|
|
return count;
|
|
}
|
|
|
|
static int mmc_debug_proc_show(struct seq_file *m, void *v)
|
|
{
|
|
msdc_dump_host_state(NULL, NULL, m);
|
|
mmc_cmd_dump(NULL, NULL, m, mtk_mmc_host[0], dbg_max_cnt);
|
|
sd_cmd_dump(NULL, NULL, m, mtk_mmc_host[1], sd_dbg_max_cnt);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mmc_debug_proc_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, mmc_debug_proc_show, inode->i_private);
|
|
}
|
|
|
|
static const struct proc_ops mmc_debug_proc_fops = {
|
|
.proc_open = mmc_debug_proc_open,
|
|
.proc_write = mmc_debug_proc_write,
|
|
.proc_read = seq_read,
|
|
.proc_lseek = seq_lseek,
|
|
.proc_release = single_release,
|
|
};
|
|
|
|
int mmc_debug_init_procfs(void)
|
|
{
|
|
struct proc_dir_entry *prEntry;
|
|
kuid_t uid;
|
|
kgid_t gid;
|
|
|
|
uid = make_kuid(&init_user_ns, 0);
|
|
gid = make_kgid(&init_user_ns, 1001);
|
|
|
|
/* Create "mmc_debug" node */
|
|
prEntry = proc_create("mmc_debug", PROC_PERM, NULL,
|
|
&mmc_debug_proc_fops);
|
|
|
|
if (!prEntry)
|
|
return -ENOENT;
|
|
|
|
proc_set_user(prEntry, uid, gid);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void cmd_hist_cleanup(void)
|
|
{
|
|
memset(dbg_run_host_log_dat, 0, sizeof(dbg_run_host_log_dat));
|
|
}
|
|
|
|
static void mmc_dbg_cleanup(void)
|
|
{
|
|
int i;
|
|
|
|
FOR_EACH_INTEREST(i) {
|
|
if (interests[i].init) {
|
|
tracepoint_probe_unregister(interests[i].tp,
|
|
interests[i].func,
|
|
NULL);
|
|
}
|
|
}
|
|
|
|
cmd_hist_cleanup();
|
|
}
|
|
|
|
int mmc_dbg_register(struct mmc_host *mmc)
|
|
{
|
|
int i, ret;
|
|
|
|
if (!(mmc->caps2 & MMC_CAP2_NO_MMC)) {
|
|
mtk_mmc_host[0] = mmc;
|
|
} else if (!(mmc->caps2 & MMC_CAP2_NO_SD)) {
|
|
mtk_mmc_host[1] = mmc;
|
|
} else /* SDIO no debug */
|
|
return -EINVAL;
|
|
|
|
/* avoid init repeatedly */
|
|
if (cmd_hist_init == true)
|
|
return 0;
|
|
|
|
/*
|
|
* Ignore any failure of AEE buffer allocation to still allow
|
|
* command history dump in procfs.
|
|
*/
|
|
mmc_aee_buffer = kzalloc(MMC_AEE_BUFFER_SIZE, GFP_NOFS);
|
|
|
|
/* Blocktag */
|
|
#if IS_ENABLED(CONFIG_MTK_BLOCK_IO_TRACER)
|
|
ret = mmc_mtk_biolog_init(mmc);
|
|
if (ret)
|
|
return ret;
|
|
#endif
|
|
|
|
spin_lock_init(&cmd_hist_lock);
|
|
|
|
/* Install the tracepoints */
|
|
for_each_kernel_tracepoint(lookup_tracepoints, NULL);
|
|
|
|
FOR_EACH_INTEREST(i) {
|
|
if (interests[i].tp == NULL) {
|
|
pr_info("Error: %s not found\n",
|
|
interests[i].name);
|
|
/* Unload previously loaded */
|
|
mmc_dbg_cleanup();
|
|
return -EINVAL;
|
|
}
|
|
|
|
tracepoint_probe_register(interests[i].tp,
|
|
interests[i].func,
|
|
NULL);
|
|
interests[i].init = true;
|
|
}
|
|
|
|
/* Create control nodes in procfs */
|
|
ret = mmc_debug_init_procfs();
|
|
|
|
cmd_hist_init = true;
|
|
cmd_hist_enabled = true;
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(mmc_dbg_register);
|
|
|
|
void mmc_mtk_dbg_get_aee_buffer(unsigned long *vaddr, unsigned long *size)
|
|
{
|
|
unsigned long free_size = MMC_AEE_BUFFER_SIZE;
|
|
char *buff;
|
|
|
|
if (!mmc_aee_buffer) {
|
|
pr_info("failed to dump MMC: null AEE buffer");
|
|
return;
|
|
}
|
|
|
|
buff = mmc_aee_buffer;
|
|
msdc_dump_host_state(&buff, &free_size, NULL);
|
|
mmc_cmd_dump(&buff, &free_size, NULL, mtk_mmc_host[0], dbg_max_cnt);
|
|
sd_cmd_dump(&buff, &free_size, NULL, mtk_mmc_host[1], sd_dbg_max_cnt);
|
|
|
|
/* return start location */
|
|
*vaddr = (unsigned long)mmc_aee_buffer;
|
|
*size = MMC_AEE_BUFFER_SIZE - free_size;
|
|
}
|
|
EXPORT_SYMBOL_GPL(mmc_mtk_dbg_get_aee_buffer);
|
|
|
|
static int __init mmc_mtk_dbg_init(void)
|
|
{
|
|
dbg_host_cnt = 0;
|
|
dbg_sd_cnt = 0;
|
|
cmd_hist_init = false;
|
|
cmd_hist_enabled = false;
|
|
|
|
mrdump_set_extra_dump(AEE_EXTRA_FILE_MMC, mmc_mtk_dbg_get_aee_buffer);
|
|
return 0;
|
|
}
|
|
|
|
static void __exit mmc_mtk_dbg_exit(void)
|
|
{
|
|
mrdump_set_extra_dump(AEE_EXTRA_FILE_MMC, NULL);
|
|
return;
|
|
}
|
|
|
|
module_init(mmc_mtk_dbg_init);
|
|
module_exit(mmc_mtk_dbg_exit);
|
|
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_DESCRIPTION("MediaTek SD/MMC Debug Driver");
|