kernel_samsung_a53x/drivers/scsi/ufs/ufs-exynos-dbg.c
2024-06-15 16:02:09 -03:00

833 lines
23 KiB
C
Executable file

/*
* UFS debugging functions for Exynos specific extensions
*
* Copyright (C) 2016 Samsung Electronics Co., Ltd.
*
* Authors:
* Kiwoong <kwmad.kim@samsung.com>
*/
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/io.h>
#include <linux/module.h>
#include "ufshcd.h"
#include <soc/samsung/exynos-smc.h>
#include <soc/samsung/memlogger.h>
#include "ufs-vs-mmio.h"
#include "ufs-vs-regs.h"
#include "ufs-dump.h"
struct exynos_ufs_memlog {
struct memlog *desc;
struct memlog_obj *log_obj_file;
struct memlog_obj *log_obj;
unsigned int log_enable;
};
/* Structure for ufs cmd logging */
#define MAX_CMD_LOGS 128
/* Hardware */
#define UIC_ARG_MIB_SEL(attr, sel) ((((attr) & 0xFFFF) << 16) |\
((sel) & 0xFFFF))
#define UIC_ARG_MIB(attr) UIC_ARG_MIB_SEL(attr, 0)
#define PCS_CMN_OFFSET(ofs) (0x2800 + ((ofs) - 0x200) * 4)
#define PCS_TRSV_OFFSET(ofs) (0x2000 + (ofs) * 4)
#define PHY_PMA_COMN_ADDR(reg) (reg)
#define PHY_PMA_TRSV_ADDR(reg, lane) ((reg) + (0x400 * (lane)))
#define UNIP_COMP_AXI_AUX_FIELD 0x040
#define __WSTRB (0xF << 24)
#define __SEL_IDX(L) ((L) & 0xFFFF)
enum {
TX_LANE_0 = 0,
RX_LANE_0 = 4,
};
struct cmd_data {
unsigned int tag;
unsigned int sct;
unsigned long lba;
u64 start_time;
u64 end_time;
u64 outstanding_reqs;
int retries;
unsigned char op;
};
struct ufs_cmd_info {
u32 total;
u32 last;
struct cmd_data data[MAX_CMD_LOGS];
struct cmd_data *pdata[32]; /* Currently, 32 slots */
};
static const char *ufs_sfr_field_name[3] = {
"NAME", "OFFSET", "VALUE",
};
static const char *ufs_attr_field_name[4]= {
"MIB", "OFFSET(SFR)", "VALUE(lane #0)", "VALUE(lane #1)",
};
#define ATTR_LANE_OFFSET 16
#define ATTR_TYPE_MASK(addr) ((addr >> ATTR_LANE_OFFSET) & 0xF)
#define ATTR_SET(addr, type) ((addr) | ((type & 0xF) << ATTR_LANE_OFFSET))
#define ATTR_NUM_MAX_LANES 2
#define CPORT_TX_BUF_SIZE 0x100
#define CPORT_RX_BUF_SIZE 0x100
#define CPORT_LOG_PTR_SIZE 0x100
#define CPORT_UTRL_SIZE 0x400
#define CPORT_UCD_SIZE 0x400
#define CPORT_UTMRL_SIZE 0xc0
#define CPORT_BUF_SIZE (CPORT_TX_BUF_SIZE + CPORT_RX_BUF_SIZE + \
CPORT_LOG_PTR_SIZE + CPORT_UTRL_SIZE + \
CPORT_UCD_SIZE + CPORT_UTMRL_SIZE)
#define CPORT_TX_BUF_PTR 0x0
#define CPORT_RX_BUF_PTR (CPORT_TX_BUF_PTR + CPORT_TX_BUF_SIZE)
#define CPORT_LOG_PTR_OFFSET (CPORT_RX_BUF_PTR + CPORT_RX_BUF_SIZE)
#define CPORT_UTRL_PTR (CPORT_LOG_PTR_OFFSET + CPORT_LOG_PTR_SIZE)
#define CPORT_UCD_PTR (CPORT_UTRL_PTR + CPORT_UTRL_SIZE)
#define CPORT_UTMRL_PTR (CPORT_UCD_PTR + CPORT_UCD_SIZE)
#define pr_memlog(dev, fmt, ...) \
do { \
if (dev->log_enable) \
memlog_write_printf(dev->log_obj, \
MEMLOG_LEVEL_EMERG, \
fmt, ##__VA_ARGS__); \
else \
pr_err(fmt, ##__VA_ARGS__); \
} while (0)
struct ufs_log_cport {
u32 ptr;
u8 buf[CPORT_BUF_SIZE];
} ufs_log_cport;
#define DBG_NUM_OF_HOSTS 1
struct ufs_dbg_mgr {
struct ufs_vs_handle *handle;
int active;
u64 first_time;
u64 time;
u32 lanes;
/* cport */
struct ufs_log_cport log_cport;
/* cmd log */
struct ufs_cmd_info cmd_info;
struct cmd_data cmd_log; /* temp buffer to put */
spinlock_t cmd_lock;
/* mem log*/
struct exynos_ufs_memlog *mem_log;
struct ufs_stats ufs_stats;
};
static struct ufs_dbg_mgr ufs_dbg[DBG_NUM_OF_HOSTS];
static struct exynos_ufs_memlog ufs_memlog[DBG_NUM_OF_HOSTS];
static int ufs_dbg_mgr_idx = 0;
static void __ufs_get_sfr(struct ufs_dbg_mgr *mgr,
struct exynos_ufs_sfr_log* cfg)
{
struct ufs_vs_handle *handle = mgr->handle;
int sel_api = 0;
u32 reg = 0;
u32 *pval;
while(cfg) {
if (!cfg->name)
break;
if (cfg->offset >= LOG_STD_HCI_SFR) {
/* Select an API to get SFRs */
sel_api = cfg->offset;
if (sel_api == LOG_PMA_SFR) {
/* Enable MPHY APB */
reg = hci_readl(handle, HCI_CLKSTOP_CTRL);
hci_writel(handle, reg & ~MPHY_APBCLK_STOP, HCI_CLKSTOP_CTRL);
}
cfg++;
continue;
}
/* Fetch value */
pval = &cfg->val[SFR_VAL_H_0];
if (sel_api == LOG_STD_HCI_SFR)
*pval = std_readl(handle, cfg->offset);
else if (sel_api == LOG_VS_HCI_SFR)
*pval = hci_readl(handle, cfg->offset);
else if (sel_api == LOG_UNIPRO_SFR)
*pval = unipro_readl(handle, cfg->offset);
else if (sel_api == LOG_PMA_SFR)
*pval = pma_readl(handle, cfg->offset);
else
*pval = 0xFFFFFFFF;
/* Keep the first contexts permanently */
if (mgr->first_time == 0ULL)
cfg->val[SFR_VAL_H_0_FIRST] = *pval;
/* Next SFR */
cfg++;
}
/* Disable MPHY APB */
hci_writel(handle, reg | MPHY_APBCLK_STOP, HCI_CLKSTOP_CTRL);
}
static u32 __read_pcs(struct ufs_vs_handle *handle, int lane,
struct exynos_ufs_attr_log* cfg) {
unipro_writel(handle, __WSTRB | __SEL_IDX(lane),
UNIP_COMP_AXI_AUX_FIELD);
return unipro_readl(handle, PCS_TRSV_OFFSET(cfg->mib));
}
static void __ufs_get_attr(struct ufs_dbg_mgr *mgr,
struct exynos_ufs_attr_log* cfg)
{
struct ufs_vs_handle *handle = mgr->handle;
int sel_api = 0;
u32 val;
u32 *pval;
int i;
while(cfg) {
if (cfg->mib == 0)
break;
/* Fetch result and value */
pval = &cfg->val[ATTR_VAL_H_0_L_0];
if (cfg->mib >= DBG_ATTR_UNIPRO) {
/* Select an API to get attributes */
sel_api = cfg->mib;
cfg++;
continue;
}
cfg->val[ATTR_VAL_H_0_L_1] = 0xFFFFFFFF;
if (sel_api == DBG_ATTR_UNIPRO) {
*pval = unipro_readl(handle, cfg->offset);
} else if (sel_api == DBG_ATTR_PCS_CMN) {
*pval = unipro_readl(handle, PCS_CMN_OFFSET(cfg->mib));
} else if (sel_api == DBG_ATTR_PCS_TX) {
for (i = 0 ; i < mgr->lanes; i++) {
val = __read_pcs(handle, TX_LANE_0 + i, cfg);
*(pval + i) = val;
}
} else if (sel_api == DBG_ATTR_PCS_RX) {
for (i = 0 ; i < mgr->lanes; i++) {
val = __read_pcs(handle, RX_LANE_0 + i, cfg);
*(pval + i) = val;
}
} else
// TODO:
;
/* Keep the first contexts permanently */
if (mgr->first_time == 0ULL) {
cfg->val[ATTR_VAL_H_0_L_0_FIRST] = *pval;
cfg->val[ATTR_VAL_H_0_L_1_FIRST] = *(pval + 1);
}
/* Next attribute */
cfg++;
}
}
static void __ufs_print_sfr(struct ufs_vs_handle *handle,
struct device *dev,
struct exynos_ufs_sfr_log* cfg)
{
struct ufs_dbg_mgr *mgr = (struct ufs_dbg_mgr *)handle->private;
pr_memlog(mgr->mem_log, ":---------------------------------------------------\n");
pr_memlog(mgr->mem_log, ":\t\tREGISTER\n");
pr_memlog(mgr->mem_log, ":---------------------------------------------------\n");
pr_memlog(mgr->mem_log, ": %-30s\t%-012s\t%-014s\n",
ufs_sfr_field_name[0],
ufs_sfr_field_name[1],
ufs_sfr_field_name[2]);
#if IS_ENABLED(CONFIG_SAMSUNG_PRODUCT_SHIP)
dev_err(dev, ":---------------------------------------------------\n");
dev_err(dev, ":\t\tREGISTER\n");
dev_err(dev, ":---------------------------------------------------\n");
dev_err(dev, ": %-30s\t%-012s\t%-014s\n",
ufs_sfr_field_name[0],
ufs_sfr_field_name[1],
ufs_sfr_field_name[2]);
#endif
while(cfg) {
if (!cfg->name)
break;
/* show */
if (cfg->offset >= LOG_STD_HCI_SFR)
pr_memlog(mgr->mem_log, "\n");
pr_memlog(mgr->mem_log, ": %-30s\t0x%-012x\t0x%-014x\n",
cfg->name, cfg->offset, cfg->val[SFR_VAL_H_0]);
#if IS_ENABLED(CONFIG_SAMSUNG_PRODUCT_SHIP)
dev_err(dev, ": %-30s\t0x%-012x\t0x%-014x\n",
cfg->name, cfg->offset, cfg->val[SFR_VAL_H_0]);
#endif
if (cfg->offset >= LOG_STD_HCI_SFR)
pr_memlog(mgr->mem_log, "\n");
/* Next SFR */
cfg++;
}
}
static void __ufs_print_attr(struct ufs_vs_handle *handle,
struct device *dev,
struct exynos_ufs_attr_log* cfg)
{
struct ufs_dbg_mgr *mgr = (struct ufs_dbg_mgr *)handle->private;
pr_memlog(mgr->mem_log, ":---------------------------------------------------\n");
pr_memlog(mgr->mem_log, ":\t\tATTRIBUTE\n");
pr_memlog(mgr->mem_log, ":---------------------------------------------------\n");
pr_memlog(mgr->mem_log, ": %-30s\t%-12s%-14s\t%-14s\n",
ufs_attr_field_name[0],
ufs_attr_field_name[1],
ufs_attr_field_name[2],
ufs_attr_field_name[3]);
#if IS_ENABLED(CONFIG_SAMSUNG_PRODUCT_SHIP)
dev_err(dev, ":---------------------------------------------------\n");
dev_err(dev, ":\t\tATTRIBUTE\n");
dev_err(dev, ":---------------------------------------------------\n");
dev_err(dev, ": %-30s\t%-12s%-14s\t%-14s\n",
ufs_attr_field_name[0],
ufs_attr_field_name[1],
ufs_attr_field_name[2],
ufs_attr_field_name[3]);
#endif
while(cfg) {
if (!cfg->mib)
break;
/* show */
if (cfg->offset >= DBG_ATTR_UNIPRO)
pr_memlog(mgr->mem_log, "\n");
pr_memlog(mgr->mem_log, ": 0x%-27x\t0x%-012x\t0x%-014x\t0x%-014x\n",
cfg->mib, cfg->offset,
cfg->val[ATTR_VAL_H_0_L_0], cfg->val[ATTR_VAL_H_0_L_1]);
#if IS_ENABLED(CONFIG_SAMSUNG_PRODUCT_SHIP)
dev_err(dev, ": 0x%-27x\t0x%-012x\t0x%-014x\t0x%-014x\n",
cfg->mib, cfg->offset,
cfg->val[ATTR_VAL_H_0_L_0], cfg->val[ATTR_VAL_H_0_L_1]);
#endif
if (cfg->offset >= DBG_ATTR_UNIPRO)
pr_memlog(mgr->mem_log, "\n");
/* Next SFR */
cfg++;
}
}
static void __print_cport(struct device *dev, int tag_printed,
u32 tag, u32 *ptr,
struct ufs_dbg_mgr *mgr)
{
if (tag_printed)
pr_memlog(mgr->mem_log, "%08x %08x %08x %08x tag# %u\n",
be32_to_cpu(*(ptr + 0)), be32_to_cpu(*(ptr + 1)),
be32_to_cpu(*(ptr + 2)), be32_to_cpu(*(ptr + 3)),
tag);
else
pr_memlog(mgr->mem_log, "%08x %08x %08x %08x\n",
be32_to_cpu(*(ptr + 0)), be32_to_cpu(*(ptr + 1)),
be32_to_cpu(*(ptr + 2)), be32_to_cpu(*(ptr + 3)));
#ifdef CONFIG_SCSI_UFS_EXYNOS_DUMP_TO_CONSOLE
if (tag_printed)
dev_err(dev, "%08x %08x %08x %08x tag# %u\n",
be32_to_cpu(*(ptr + 0)), be32_to_cpu(*(ptr + 1)),
be32_to_cpu(*(ptr + 2)), be32_to_cpu(*(ptr + 3)),
tag);
else
dev_err(dev, "%08x %08x %08x %08x\n",
be32_to_cpu(*(ptr + 0)), be32_to_cpu(*(ptr + 1)),
be32_to_cpu(*(ptr + 2)), be32_to_cpu(*(ptr + 3)));
#endif
}
static void __ufs_print_cport(struct ufs_dbg_mgr *mgr, struct device *dev)
{
struct ufs_vs_handle *handle = mgr->handle;
struct ufs_log_cport *log_cport = &mgr->log_cport;
u32 *buf_ptr;
u8 *buf_ptr_out;
u32 offset;
u32 size = 0;
u32 cur_ptr = 0;
u32 tag = 0;
u32 idx = 0;
int tag_printed;
/*
* CPort logger
*
* [ log type 0 ]
* First 4 double words
*
* [ log type 1 ]
* 6 double words, for Rx
* 8 double words, for Tx
*
* [ log type 2 ]
* 4 double words, for Command UPIU, DATA OUT/IN UPIU and RTT.
* 2 double words, otherwise.
*
*/
log_cport->ptr = cport_readl(handle, CPORT_LOG_PTR_OFFSET);
buf_ptr = (u32 *)&log_cport->buf[0];
size = 0;
offset = 0;
while (size < CPORT_BUF_SIZE) {
*buf_ptr = cport_readl(handle, offset);
size += 4;
buf_ptr += 1;
offset += 4;
}
/* memory barrier for ufs cport dump */
mb();
/* Print data */
buf_ptr_out = &log_cport->buf[0];
cur_ptr = 0;
pr_memlog(mgr->mem_log, "cport logging finished\n");
pr_memlog(mgr->mem_log, ":---------------------------------------------------\n");
pr_memlog(mgr->mem_log, ":\t\tCPORT\n");
pr_memlog(mgr->mem_log, ":---------------------------------------------------\n");
#ifdef CONFIG_SCSI_UFS_EXYNOS_DUMP_TO_CONSOLE
dev_err(dev, "cport logging finished\n");
dev_err(dev, ":---------------------------------------------------\n");
dev_err(dev, ":\t\tCPORT\n");
dev_err(dev, ":---------------------------------------------------\n");
#endif
while (cur_ptr < CPORT_BUF_SIZE) {
switch (cur_ptr) {
case CPORT_TX_BUF_PTR:
pr_memlog(mgr->mem_log, ":---------------------------------------------------\n");
pr_memlog(mgr->mem_log, ": \t\tTX BUF (%d)\n", ((log_cport->ptr >> 0) & 0x3F)/2);
pr_memlog(mgr->mem_log, ":---------------------------------------------------\n");
#ifdef CONFIG_SCSI_UFS_EXYNOS_DUMP_TO_CONSOLE
dev_err(dev, ":---------------------------------------------------\n");
dev_err(dev, ": \t\tTX BUF (%d)\n", ((log_cport->ptr >> 0) & 0x3F)/2);
dev_err(dev, ":---------------------------------------------------\n");
#endif
break;
case CPORT_RX_BUF_PTR:
pr_memlog(mgr->mem_log, ":---------------------------------------------------\n");
pr_memlog(mgr->mem_log, ": \t\tRX BUF (%d)\n", ((log_cport->ptr >> 8) & 0x3F)/2);
pr_memlog(mgr->mem_log, ":---------------------------------------------------\n");
#ifdef CONFIG_SCSI_UFS_EXYNOS_DUMP_TO_CONSOLE
dev_err(dev, ":---------------------------------------------------\n");
dev_err(dev, ": \t\tRX BUF (%d)\n", ((log_cport->ptr >> 8) & 0x3F)/2);
dev_err(dev, ":---------------------------------------------------\n");
#endif
break;
case CPORT_LOG_PTR_OFFSET:
pr_memlog(mgr->mem_log, ":---------------------------------------------------\n");
pr_memlog(mgr->mem_log, ": \t\tCPORT LOG PTR\n");
pr_memlog(mgr->mem_log, ":---------------------------------------------------\n");
#ifdef CONFIG_SCSI_UFS_EXYNOS_DUMP_TO_CONSOLE
dev_err(dev, ":---------------------------------------------------\n");
dev_err(dev, ": \t\tCPORT LOG PTR\n");
dev_err(dev, ":---------------------------------------------------\n");
#endif
break;
case CPORT_UTRL_PTR:
pr_memlog(mgr->mem_log, ":---------------------------------------------------\n");
pr_memlog(mgr->mem_log, ": \t\tUTRL\n");
pr_memlog(mgr->mem_log, ":---------------------------------------------------\n");
#ifdef CONFIG_SCSI_UFS_EXYNOS_DUMP_TO_CONSOLE
dev_err(dev, ":---------------------------------------------------\n");
dev_err(dev, ": \t\tUTRL\n");
dev_err(dev, ":---------------------------------------------------\n");
#endif
tag = -1;
idx = 0;
break;
case CPORT_UCD_PTR:
pr_memlog(mgr->mem_log, ":---------------------------------------------------\n");
pr_memlog(mgr->mem_log, ": \t\tUCD\n");
pr_memlog(mgr->mem_log, ":---------------------------------------------------\n");
#ifdef CONFIG_SCSI_UFS_EXYNOS_DUMP_TO_CONSOLE
dev_err(dev, ":---------------------------------------------------\n");
dev_err(dev, ": \t\tUCD\n");
dev_err(dev, ":---------------------------------------------------\n");
#endif
tag = -1;
idx = 0;
break;
case CPORT_UTMRL_PTR:
pr_memlog(mgr->mem_log, ":---------------------------------------------------\n");
pr_memlog(mgr->mem_log, ": \t\tUTMRL\n");
pr_memlog(mgr->mem_log, ":---------------------------------------------------\n");
#ifdef CONFIG_SCSI_UFS_EXYNOS_DUMP_TO_CONSOLE
dev_err(dev, ":---------------------------------------------------\n");
dev_err(dev, ": \t\tUTMRL\n");
dev_err(dev, ":---------------------------------------------------\n");
#endif
break;
default:
break;
}
if (cur_ptr == CPORT_LOG_PTR_OFFSET) {
pr_memlog(mgr->mem_log, "%02x%02x%02x%02x ",
*(buf_ptr_out+0x3), *(buf_ptr_out+0x2), *(buf_ptr_out+0x1), *(buf_ptr_out+0x0));
#ifdef CONFIG_SCSI_UFS_EXYNOS_DUMP_TO_CONSOLE
dev_err(dev, "%02x%02x%02x%02x ",
*(buf_ptr_out+0x3), *(buf_ptr_out+0x2), *(buf_ptr_out+0x1), *(buf_ptr_out+0x0));
#endif
buf_ptr_out += 0x100;
cur_ptr += 0x100;
} else {
tag_printed = 0;
if (cur_ptr >= CPORT_UTRL_PTR &&
cur_ptr < CPORT_UTMRL_PTR) {
if (idx++ % 2 == 0) {
tag_printed = 1;
tag++;
}
}
__print_cport(dev, tag_printed, tag,
(u32 *)buf_ptr_out, mgr);
buf_ptr_out += 0x10;
cur_ptr += 0x10;
}
}
}
static void __ufs_print_cmd_log(struct ufs_dbg_mgr *mgr, struct device *dev) {
struct ufs_cmd_info *cmd_info = &mgr->cmd_info;
struct cmd_data *data = cmd_info->data;
u32 i;
u32 last;
u32 max = MAX_CMD_LOGS;
unsigned long flags;
u32 total;
spin_lock_irqsave(&mgr->cmd_lock, flags);
total = cmd_info->total;
if (cmd_info->total < max)
max = cmd_info->total;
last = (cmd_info->last + MAX_CMD_LOGS - 1) % MAX_CMD_LOGS;
spin_unlock_irqrestore(&mgr->cmd_lock, flags);
pr_memlog(mgr->mem_log, ":---------------------------------------------------\n");
pr_memlog(mgr->mem_log, ":\t\tSCSI CMD(%u)\n", total - 1);
pr_memlog(mgr->mem_log, ":---------------------------------------------------\n");
pr_memlog(mgr->mem_log, ": OP, TAG, LBA, SCT, RETRIES, STIME, ETIME, REQS\n\n");
#ifdef CONFIG_SCSI_UFS_EXYNOS_DUMP_TO_CONSOLE
dev_err(dev, ":---------------------------------------------------\n");
dev_err(dev, ":\t\tSCSI CMD(%u)\n", total - 1);
dev_err(dev, ":---------------------------------------------------\n");
dev_err(dev, ": OP, TAG, LBA, SCT, RETRIES, STIME, ETIME, REQS\n\n");
#endif
for (i = 0 ; i < max ; i++, data++) {
pr_memlog(mgr->mem_log, ": 0x%02x, %02d, 0x%08lx, 0x%04x, %d, %llu, %llu, 0x%lx %s\n",
data->op,
data->tag,
data->lba,
data->sct,
data->retries,
data->start_time,
data->end_time,
data->outstanding_reqs,
((last == i) ? "<--" : " "));
#ifdef CONFIG_SCSI_UFS_EXYNOS_DUMP_TO_CONSOLE
dev_err(dev, ": 0x%02x, %02d, 0x%08lx, 0x%04x, %d, %llu, %llu, 0x%lx %s\n",
data->op,
data->tag,
data->lba,
data->sct,
data->retries,
data->start_time,
data->end_time,
data->outstanding_reqs,
((last == i) ? "<--" : " "));
#endif
if (last == i)
pr_memlog(mgr->mem_log, "\n");
}
}
static void __ufs_put_cmd_log(struct ufs_dbg_mgr *mgr, struct cmd_data *cmd_data)
{
struct ufs_cmd_info *cmd_info = &mgr->cmd_info;
unsigned long flags;
struct cmd_data *pdata;
spin_lock_irqsave(&mgr->cmd_lock, flags);
pdata = &cmd_info->data[cmd_info->last];
++cmd_info->total;
cmd_info->last = (cmd_info->last + 1) % MAX_CMD_LOGS;
spin_unlock_irqrestore(&mgr->cmd_lock, flags);
pdata->op = cmd_data->op;
pdata->tag = cmd_data->tag;
pdata->lba = cmd_data->lba;
pdata->sct = cmd_data->sct;
pdata->retries = cmd_data->retries;
pdata->start_time = cmd_data->start_time;
pdata->end_time = 0;
pdata->outstanding_reqs = cmd_data->outstanding_reqs;
cmd_info->pdata[cmd_data->tag] = pdata;
}
static void __ufs_print_evt(struct ufs_dbg_mgr *mgr, u32 id,
char *err_name)
{
int i;
bool found = false;
struct ufs_event_hist *e;
if (id >= UFS_EVT_CNT)
return;
e = &mgr->ufs_stats.event[id];
for (i = 0; i < UFS_EVENT_HIST_LENGTH; i++) {
int p = (i + e->pos) % UFS_EVENT_HIST_LENGTH;
if (e->tstamp[p] == 0)
continue;
pr_memlog(mgr->mem_log, "%s[%d] = 0x%x at %lld us\n", err_name, p,
e->val[p], ktime_to_us(e->tstamp[p]));
found = true;
}
if (!found)
pr_memlog(mgr->mem_log, "No record of %s\n", err_name);
}
static void __ufs_print_evt_hist(struct ufs_dbg_mgr *mgr)
{
__ufs_print_evt(mgr, UFS_EVT_PA_ERR, "pa_err");
__ufs_print_evt(mgr, UFS_EVT_DL_ERR, "dl_err");
__ufs_print_evt(mgr, UFS_EVT_NL_ERR, "nl_err");
__ufs_print_evt(mgr, UFS_EVT_TL_ERR, "tl_err");
__ufs_print_evt(mgr, UFS_EVT_DME_ERR, "dme_err");
__ufs_print_evt(mgr, UFS_EVT_AUTO_HIBERN8_ERR,
"auto_hibern8_err");
__ufs_print_evt(mgr, UFS_EVT_FATAL_ERR, "fatal_err");
__ufs_print_evt(mgr, UFS_EVT_LINK_STARTUP_FAIL,
"link_startup_fail");
__ufs_print_evt(mgr, UFS_EVT_RESUME_ERR, "resume_fail");
__ufs_print_evt(mgr, UFS_EVT_SUSPEND_ERR,
"suspend_fail");
__ufs_print_evt(mgr, UFS_EVT_DEV_RESET, "dev_reset");
__ufs_print_evt(mgr, UFS_EVT_HOST_RESET, "host_reset");
__ufs_print_evt(mgr, UFS_EVT_ABORT, "task_abort");
}
/*
* EXTERNAL FUNCTIONS
*
* There are two classes that are to initialize data structures for debug
* and to define actual behavior.
*/
void exynos_ufs_dump_info(struct ufs_hba *hba,
struct ufs_vs_handle *handle, struct device *dev)
{
struct ufs_dbg_mgr *mgr = (struct ufs_dbg_mgr *)handle->private;
if (mgr->active == 0)
goto out;
mgr->time = cpu_clock(raw_smp_processor_id());
/* get context */
__ufs_get_sfr(mgr, ufs_log_sfr);
__ufs_get_attr(mgr, ufs_log_attr);
memcpy(&mgr->ufs_stats, &hba->ufs_stats, sizeof(struct ufs_stats));
/* show context */
__ufs_print_sfr(handle, dev, ufs_log_sfr);
__ufs_print_attr(handle, dev, ufs_log_attr);
__ufs_print_cport(mgr, dev);
__ufs_print_cmd_log(mgr, dev);
__ufs_print_evt_hist(mgr);
if (mgr->first_time == 0ULL)
mgr->first_time = mgr->time;
out:
return;
}
void exynos_ufs_cmd_log_start(struct ufs_vs_handle *handle,
struct ufs_hba *hba, struct scsi_cmnd *cmd)
{
struct ufs_dbg_mgr *mgr = (struct ufs_dbg_mgr *)handle->private;
int cpu = raw_smp_processor_id();
struct cmd_data *cmd_log = &mgr->cmd_log; /* temp buffer to put */
unsigned long lba = (cmd->cmnd[2] << 24) |
(cmd->cmnd[3] << 16) |
(cmd->cmnd[4] << 8) |
(cmd->cmnd[5] << 0);
unsigned int sct = (cmd->cmnd[7] << 8) |
(cmd->cmnd[8] << 0);
if (mgr->active == 0)
return;
cmd_log->start_time = cpu_clock(cpu);
cmd_log->op = cmd->cmnd[0];
cmd_log->tag = cmd->request->tag;
/* This function runtime is protected by spinlock from outside */
cmd_log->outstanding_reqs = hba->outstanding_reqs;
/* unmap */
if(cmd->cmnd[0] != UNMAP) // TODO: for unmap
cmd_log->lba = lba;
cmd_log->sct = sct;
cmd_log->retries = cmd->allowed;
__ufs_put_cmd_log(mgr, cmd_log);
}
void exynos_ufs_cmd_log_end(struct ufs_vs_handle *handle,
struct ufs_hba *hba, int tag)
{
struct ufs_dbg_mgr *mgr = (struct ufs_dbg_mgr *)handle->private;
struct ufs_cmd_info *cmd_info = &mgr->cmd_info;
int cpu = raw_smp_processor_id();
if (mgr->active == 0)
return;
if (!cmd_info->pdata[tag]) {
pr_err("%s: there is no cmd logging inform about tag: %d\n",
__func__, tag);
return;
}
cmd_info->pdata[tag]->end_time = cpu_clock(cpu);
}
int exynos_ufs_dbg_set_lanes(struct ufs_vs_handle *handle,
struct device *dev, u32 lanes)
{
struct ufs_dbg_mgr *mgr = (struct ufs_dbg_mgr *)handle->private;
int ret = 0;
mgr->lanes = lanes;
if (mgr->lanes > ATTR_NUM_MAX_LANES) {
printk("%s: input lanes is too big: %u > %d\n",
__func__, mgr->lanes, ATTR_NUM_MAX_LANES);
ret = -1;
}
return ret;
}
static int ufs_memlog_file_completed(struct memlog_obj *obj, u32 flags)
{
/* NOP */
return 0;
}
static int ufs_memlog_status_notify(struct memlog_obj *obj, u32 flags)
{
/* NOP */
return 0;
}
static int ufs_memlog_level_notify(struct memlog_obj *obj, u32 flags)
{
/* NOP */
return 0;
}
static int ufs_memlog_enable_notify(struct memlog_obj *obj, u32 flags)
{
/* NOP */
return 0;
}
static const struct memlog_ops ufs_memlog_ops = {
.file_ops_completed = ufs_memlog_file_completed,
.log_status_notify = ufs_memlog_status_notify,
.log_level_notify = ufs_memlog_level_notify,
.log_enable_notify = ufs_memlog_enable_notify,
};
int exynos_ufs_init_mem_log(struct platform_device *pdev)
{
struct exynos_ufs_memlog *memlog = &ufs_memlog[ufs_dbg_mgr_idx];
struct memlog *desc;
struct memlog_obj *log_obj;
int ret;
pr_info("%s: +++\n", __func__);
ret = memlog_register("UFS", &pdev->dev, &desc);
if (ret) {
pr_err("failed to register memlog\n");
return -1;
}
memlog->desc = desc;
desc->ops = ufs_memlog_ops;
log_obj = memlog_alloc_printf(desc,
SZ_512K,
NULL,
"log-mem",
0);
if (log_obj) {
memlog->log_obj = log_obj;
memlog->log_enable = 1;
} else {
pr_err("%s: failed to alloc memlog memory for log\n",
__func__);
return -1;
}
pr_info("%s: ---\n", __func__);
return 0;
}
int exynos_ufs_init_dbg(struct ufs_vs_handle *handle)
{
struct ufs_dbg_mgr *mgr;
int ret = -1;
if (ufs_dbg_mgr_idx >= DBG_NUM_OF_HOSTS)
goto out;
mgr = &ufs_dbg[ufs_dbg_mgr_idx];
mgr->mem_log = &ufs_memlog[ufs_dbg_mgr_idx];
ufs_dbg_mgr_idx++;
handle->private = (void *)mgr;
mgr->handle = handle;
mgr->active = 1;
/* cmd log */
spin_lock_init(&mgr->cmd_lock);
ret = 0;
out:
return ret;
}
MODULE_AUTHOR("Kiwoong Kim <kwmad.kim@samsung.com>");
MODULE_DESCRIPTION("Exynos UFS debug information");
MODULE_LICENSE("GPL");
MODULE_VERSION("0.1");