@@ -172,3 +172,17 @@ config SCSI_UFS_EXYNOS
Select this if you have UFS host controller on EXYNOS chipset.
If unsure, say N.
+
+config SCSI_UFS_EXYNOS_DBG
+ bool "EXYNOS specific debug functions"
+ default n
+ depends on SCSI_UFS_EXYNOS
+ help
+ This selects EXYNOS specific functions to get and even print
+ some information to see what's happening at both command
+ issue time completion time.
+ The information may contain gerernal things as well as
+ EXYNOS specific, such as vendor specific hardware contexts.
+
+ Select this if you want to get and print the information.
+ If unsure, say N.
@@ -5,6 +5,7 @@ obj-$(CONFIG_SCSI_UFS_DWC_TC_PLATFORM) += tc-dwc-g210-pltfrm.o ufshcd-dwc.o tc-d
obj-$(CONFIG_SCSI_UFS_CDNS_PLATFORM) += cdns-pltfrm.o
obj-$(CONFIG_SCSI_UFS_QCOM) += ufs-qcom.o
obj-$(CONFIG_SCSI_UFS_EXYNOS) += ufs-exynos.o
+obj-$(CONFIG_SCSI_UFS_EXYNOS_DBG) += ufs-exynos-dbg.o
obj-$(CONFIG_SCSI_UFSHCD) += ufshcd-core.o
ufshcd-core-y += ufshcd.o ufs-sysfs.o
ufshcd-core-$(CONFIG_SCSI_UFS_BSG) += ufs_bsg.o
new file mode 100644
@@ -0,0 +1,199 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * UFS Exynos debugging functions
+ *
+ * Copyright (C) 2020 Samsung Electronics Co., Ltd.
+ * Author: Kiwoong Kim <kwmad.kim@samsung.com>
+ *
+ */
+#include <linux/platform_device.h>
+#include <linux/module.h>
+#include <asm-generic/unaligned.h>
+#include "ufshcd.h"
+#include "ufs-exynos-if.h"
+
+#define MAX_CMD_LOGS 32
+
+struct cmd_data {
+ unsigned int tag;
+ u32 sct;
+ u64 lba;
+ u64 start_time;
+ u64 end_time;
+ u64 outstanding_reqs;
+ int retries;
+ u8 op;
+};
+
+struct ufs_cmd_info {
+ u32 total;
+ u32 last;
+ struct cmd_data data[MAX_CMD_LOGS];
+ struct cmd_data *pdata[MAX_CMD_LOGS];
+};
+
+/*
+ * This structure points out several contexts on debugging
+ * per one host instant.
+ * Now command history exists in here but later handle may
+ * contains some mmio base addresses including vendor specific
+ * regions to get hardware contexts.
+ */
+struct ufs_s_dbg_mgr {
+ struct ufs_exynos_handle *handle;
+ int active;
+ u64 first_time;
+ u64 time;
+
+ /* cmd log */
+ struct ufs_cmd_info cmd_info;
+ struct cmd_data cmd_log; /* temp buffer to put */
+ spinlock_t cmd_lock;
+};
+
+static void ufs_s_print_cmd_log(struct ufs_s_dbg_mgr *mgr, struct device *dev)
+{
+ struct ufs_cmd_info *cmd_info = &mgr->cmd_info;
+ struct cmd_data *data;
+ u32 i, idx;
+ 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);
+
+ 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");
+
+ idx = (last == max - 1) ? 0 : last + 1;
+ for (i = 0 ; i < max ; i++, data = &cmd_info->data[idx]) {
+ dev_err(dev, ": 0x%02x, %02d, 0x%08llx, 0x%04x, %d, %llu, %llu, 0x%llx",
+ data->op, data->tag, data->lba, data->sct, data->retries,
+ data->start_time, data->end_time, data->outstanding_reqs);
+ idx = (idx == max - 1) ? 0 : idx + 1;
+ }
+}
+
+static void ufs_s_put_cmd_log(struct ufs_s_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 = cmd_info->last % 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;
+}
+
+/*
+ * 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_exynos_handle *handle, struct device *dev)
+{
+ struct ufs_s_dbg_mgr *mgr = (struct ufs_s_dbg_mgr *)handle->private;
+
+ if (mgr->active == 0)
+ goto out;
+
+ mgr->time = cpu_clock(raw_smp_processor_id());
+
+#ifdef CONFIG_SCSI_UFS_EXYNOS_CMD_LOG
+ ufs_s_print_cmd_log(mgr, dev);
+#endif
+
+ if (mgr->first_time == 0ULL)
+ mgr->first_time = mgr->time;
+out:
+ return;
+}
+
+void exynos_ufs_cmd_log_start(struct ufs_exynos_handle *handle,
+ struct ufs_hba *hba, int tag)
+{
+ struct ufs_s_dbg_mgr *mgr = (struct ufs_s_dbg_mgr *)handle->private;
+ struct scsi_cmnd *cmd = hba->lrb[tag].cmd;
+ int cpu = raw_smp_processor_id();
+ struct cmd_data *cmd_log = &mgr->cmd_log; /* temp buffer to put */
+ u64 lba = 0;
+ u32 sct = 0;
+
+ if (mgr->active == 0)
+ return;
+
+ cmd_log->start_time = cpu_clock(cpu);
+ cmd_log->op = cmd->cmnd[0];
+ cmd_log->tag = tag;
+
+ /* This function runtime is protected by spinlock from outside */
+ cmd_log->outstanding_reqs = hba->outstanding_reqs;
+
+ /* Now assume using WRITE_10 and READ_10 */
+ put_unaligned(cpu_to_le32(*(u32 *)cmd->cmnd[2]), &lba);
+ put_unaligned(cpu_to_le16(*(u16 *)cmd->cmnd[7]), &sct);
+ if (cmd->cmnd[0] != UNMAP)
+ cmd_log->lba = lba;
+
+ cmd_log->sct = sct;
+ cmd_log->retries = cmd->allowed;
+
+ ufs_s_put_cmd_log(mgr, cmd_log);
+}
+
+void exynos_ufs_cmd_log_end(struct ufs_exynos_handle *handle,
+ struct ufs_hba *hba, int tag)
+{
+ struct ufs_s_dbg_mgr *mgr = (struct ufs_s_dbg_mgr *)handle->private;
+ struct ufs_cmd_info *cmd_info = &mgr->cmd_info;
+ int cpu = raw_smp_processor_id();
+
+ if (mgr->active == 0)
+ return;
+
+ cmd_info->pdata[tag]->end_time = cpu_clock(cpu);
+}
+
+int exynos_ufs_init_dbg(struct ufs_exynos_handle *handle, struct device *dev)
+{
+ struct ufs_s_dbg_mgr *mgr;
+
+ mgr = devm_kzalloc(dev, sizeof(struct ufs_s_dbg_mgr), GFP_KERNEL);
+ if (!mgr)
+ return -ENOMEM;
+ handle->private = (void *)mgr;
+ mgr->handle = handle;
+ mgr->active = 1;
+
+ /* cmd log */
+ spin_lock_init(&mgr->cmd_lock);
+
+ return 0;
+}
+MODULE_AUTHOR("Kiwoong Kim <kwmad.kim@samsung.com>");
+MODULE_DESCRIPTION("Exynos UFS debug information");
+MODULE_LICENSE("GPL v2");
+MODULE_VERSION("0.1");
new file mode 100644
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * UFS Exynos debugging functions
+ *
+ * Copyright (C) 2020 Samsung Electronics Co., Ltd.
+ * Author: Kiwoong Kim <kwmad.kim@samsung.com>
+ *
+ */
+#ifndef _UFS_EXYNOS_IF_H_
+#define _UFS_EXYNOS_IF_H_
+
+/* more members would be added in the future */
+struct ufs_exynos_handle {
+ void *private;
+};
+
+#endif /* _UFS_EXYNOS_IF_H_ */
@@ -700,12 +700,26 @@ static int exynos_ufs_post_pwr_mode(struct ufs_hba *hba,
return 0;
}
+static void exynos_ufs_cmd_log(struct ufs_hba *hba, int tag, int start)
+{
+ struct exynos_ufs *ufs = ufshcd_get_variant(hba);
+ struct ufs_exynos_handle *handle = &ufs->handle;
+
+ if (start == 1)
+ exynos_ufs_cmd_log_start(handle, hba, tag);
+ else if (start == 2)
+ exynos_ufs_cmd_log_end(handle, hba, tag);
+}
+
static void exynos_ufs_specify_nexus_t_xfer_req(struct ufs_hba *hba,
int tag, bool op)
{
struct exynos_ufs *ufs = ufshcd_get_variant(hba);
u32 type;
+ if (op)
+ exynos_ufs_cmd_log(hba, tag, 1);
+
type = hci_readl(ufs, HCI_UTRL_NEXUS_TYPE);
if (op)
@@ -714,6 +728,12 @@ static void exynos_ufs_specify_nexus_t_xfer_req(struct ufs_hba *hba,
hci_writel(ufs, type & ~(1 << tag), HCI_UTRL_NEXUS_TYPE);
}
+static void exynos_ufs_compl_xfer_req(struct ufs_hba *hba, int tag, bool op)
+{
+ if (op)
+ exynos_ufs_cmd_log(hba, tag, 0);
+}
+
static void exynos_ufs_specify_nexus_t_tm_req(struct ufs_hba *hba,
int tag, u8 func)
{
@@ -1008,6 +1028,12 @@ static int exynos_ufs_init(struct ufs_hba *hba)
goto out;
exynos_ufs_specify_phy_time_attr(ufs);
exynos_ufs_config_smu(ufs);
+
+ /* init dbg */
+ ret = exynos_ufs_init_dbg(&ufs->handle, dev);
+ if (ret)
+ return ret;
+ spin_lock_init(&ufs->dbg_lock);
return 0;
phy_off:
@@ -1217,6 +1243,7 @@ static struct ufs_hba_variant_ops ufs_hba_exynos_ops = {
.link_startup_notify = exynos_ufs_link_startup_notify,
.pwr_change_notify = exynos_ufs_pwr_change_notify,
.setup_xfer_req = exynos_ufs_specify_nexus_t_xfer_req,
+ .compl_xfer_req = exynos_ufs_compl_xfer_req,
.setup_task_mgmt = exynos_ufs_specify_nexus_t_tm_req,
.hibern8_notify = exynos_ufs_hibern8_notify,
.suspend = exynos_ufs_suspend,
@@ -8,6 +8,7 @@
#ifndef _UFS_EXYNOS_H_
#define _UFS_EXYNOS_H_
+#include "ufs-exynos-if.h"
/*
* UNIPRO registers
@@ -212,6 +213,10 @@ struct exynos_ufs {
#define EXYNOS_UFS_OPT_BROKEN_AUTO_CLK_CTRL BIT(2)
#define EXYNOS_UFS_OPT_BROKEN_RX_SEL_IDX BIT(3)
#define EXYNOS_UFS_OPT_USE_SW_HIBERN8_TIMER BIT(4)
+
+ struct ufs_exynos_handle handle;
+ spinlock_t dbg_lock;
+ int under_dump;
};
#define for_each_ufs_rx_lane(ufs, i) \
@@ -284,4 +289,28 @@ struct exynos_ufs_uic_attr exynos7_uic_attr = {
.rx_hs_g3_prep_sync_len_cap = PREP_LEN(0xf),
.pa_dbg_option_suite = 0x30103,
};
+
+/* public function declarations */
+#ifdef CONFIG_SCSI_UFS_EXYNOS_DBG
+void exynos_ufs_cmd_log_start(struct ufs_exynos_handle *handle,
+ struct ufs_hba *hba, int tag);
+void exynos_ufs_cmd_log_end(struct ufs_exynos_handle *handle,
+ struct ufs_hba *hba, int tag);
+int exynos_ufs_init_dbg(struct ufs_exynos_handle *handle, struct device *dev);
+#else
+void exynos_ufs_cmd_log_start(struct ufs_exynos_handle *handle,
+ struct ufs_hba *hba, int tag)
+{
+}
+
+void exynos_ufs_cmd_log_end(struct ufs_exynos_handle *handle,
+ struct ufs_hba *hba, int tag)
+{
+}
+
+int exynos_ufs_init_dbg(struct ufs_exynos_handle *handle, struct device *dev)
+{
+ return 0;
+}
+#endif
#endif /* _UFS_EXYNOS_H_ */