summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/scsi/ufs/Kconfig12
-rw-r--r--drivers/scsi/ufs/ufshcd.c179
-rw-r--r--drivers/scsi/ufs/ufshcd.h24
3 files changed, 197 insertions, 18 deletions
diff --git a/drivers/scsi/ufs/Kconfig b/drivers/scsi/ufs/Kconfig
index b3ed361eff26..fb2f2159c0e1 100644
--- a/drivers/scsi/ufs/Kconfig
+++ b/drivers/scsi/ufs/Kconfig
@@ -106,3 +106,15 @@ config SCSI_UFS_TEST
The UFS unit-tests register as a block device test utility to
the test-iosched and will be initiated when the test-iosched will
be chosen to be the active I/O scheduler.
+
+config SCSI_UFSHCD_CMD_LOGGING
+ bool "Universal Flash Storage host controller driver layer command logging support"
+ depends on SCSI_UFSHCD
+ help
+ This selects the UFS host controller driver layer command logging.
+ UFS host controller driver layer command logging records all the
+ command information sent from UFS host controller for debugging
+ purpose.
+
+ Select this if you want above mentioned debug information captured.
+ If unsure, say N.
diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c
index c5393d517432..7e2f5e06c036 100644
--- a/drivers/scsi/ufs/ufshcd.c
+++ b/drivers/scsi/ufs/ufshcd.c
@@ -516,14 +516,145 @@ static inline void ufshcd_remove_non_printable(char *val)
*val = ' ';
}
+#define UFSHCD_MAX_CMD_LOGGING 100
+
#ifdef CONFIG_TRACEPOINTS
-static void ufshcd_add_command_trace(struct ufs_hba *hba,
- unsigned int tag, const char *str)
+static inline void ufshcd_add_command_trace(struct ufs_hba *hba,
+ struct ufshcd_cmd_log_entry *entry, u8 opcode)
+{
+ if (trace_ufshcd_command_enabled()) {
+ u32 intr = ufshcd_readl(hba, REG_INTERRUPT_STATUS);
+
+ trace_ufshcd_command(dev_name(hba->dev), entry->str, entry->tag,
+ entry->doorbell, entry->transfer_len, intr,
+ entry->lba, opcode);
+ }
+}
+#else
+static inline void ufshcd_add_command_trace(struct ufs_hba *hba,
+ struct ufshcd_cmd_log_entry *entry, u8 opcode)
+{
+}
+#endif
+
+#ifdef CONFIG_SCSI_UFSHCD_CMD_LOGGING
+static void ufshcd_cmd_log_init(struct ufs_hba *hba)
+{
+ /* Allocate log entries */
+ if (!hba->cmd_log.entries) {
+ hba->cmd_log.entries = kzalloc(UFSHCD_MAX_CMD_LOGGING *
+ sizeof(struct ufshcd_cmd_log_entry), GFP_KERNEL);
+ if (!hba->cmd_log.entries)
+ return;
+ dev_dbg(hba->dev, "%s: cmd_log.entries initialized\n",
+ __func__);
+ }
+}
+
+static void __ufshcd_cmd_log(struct ufs_hba *hba, char *str, char *cmd_type,
+ unsigned int tag, u8 cmd_id, u8 idn, u8 lun,
+ sector_t lba, int transfer_len, u8 opcode)
+{
+ struct ufshcd_cmd_log_entry *entry;
+
+ if (!hba->cmd_log.entries)
+ return;
+
+ entry = &hba->cmd_log.entries[hba->cmd_log.pos];
+ entry->lun = lun;
+ entry->str = str;
+ entry->cmd_type = cmd_type;
+ entry->cmd_id = cmd_id;
+ entry->lba = lba;
+ entry->transfer_len = transfer_len;
+ entry->idn = idn;
+ entry->doorbell = ufshcd_readl(hba, REG_UTP_TRANSFER_REQ_DOOR_BELL);
+ entry->tag = tag;
+ entry->tstamp = ktime_get();
+ entry->outstanding_reqs = hba->outstanding_reqs;
+ entry->seq_num = hba->cmd_log.seq_num;
+ hba->cmd_log.seq_num++;
+ hba->cmd_log.pos =
+ (hba->cmd_log.pos + 1) % UFSHCD_MAX_CMD_LOGGING;
+
+ ufshcd_add_command_trace(hba, entry, opcode);
+}
+
+static void ufshcd_cmd_log(struct ufs_hba *hba, char *str, char *cmd_type,
+ unsigned int tag, u8 cmd_id, u8 idn)
+{
+ __ufshcd_cmd_log(hba, str, cmd_type, tag, cmd_id, idn,
+ 0xff, (sector_t)-1, -1, -1);
+}
+
+static void ufshcd_dme_cmd_log(struct ufs_hba *hba, char *str, u8 cmd_id)
+{
+ ufshcd_cmd_log(hba, str, "dme", 0xff, cmd_id, 0xff);
+}
+
+static void ufshcd_cmd_log_print(struct ufs_hba *hba)
+{
+ int i;
+ int pos;
+ struct ufshcd_cmd_log_entry *p;
+
+ if (!hba->cmd_log.entries)
+ return;
+
+ pos = hba->cmd_log.pos;
+ for (i = 0; i < UFSHCD_MAX_CMD_LOGGING; i++) {
+ p = &hba->cmd_log.entries[pos];
+ pos = (pos + 1) % UFSHCD_MAX_CMD_LOGGING;
+
+ if (ktime_to_us(p->tstamp)) {
+ pr_err("%s: %s: seq_no=%u lun=0x%x cmd_id=0x%02x lba=0x%llx txfer_len=%d tag=%u, doorbell=0x%x outstanding=0x%x idn=%d time=%lld us\n",
+ p->cmd_type, p->str, p->seq_num,
+ p->lun, p->cmd_id, (unsigned long long)p->lba,
+ p->transfer_len, p->tag, p->doorbell,
+ p->outstanding_reqs, p->idn,
+ ktime_to_us(p->tstamp));
+ usleep_range(1000, 1100);
+ }
+ }
+}
+#else
+static void ufshcd_cmd_log_init(struct ufs_hba *hba)
+{
+}
+
+static void __ufshcd_cmd_log(struct ufs_hba *hba, char *str, char *cmd_type,
+ unsigned int tag, u8 cmd_id, u8 idn, u8 lun,
+ sector_t lba, int transfer_len, u8 opcode)
+{
+ struct ufshcd_cmd_log_entry entry;
+
+ entry.str = str;
+ entry.lba = lba;
+ entry.transfer_len = transfer_len;
+ entry.doorbell = ufshcd_readl(hba, REG_UTP_TRANSFER_REQ_DOOR_BELL);
+ entry.tag = tag;
+
+ ufshcd_add_command_trace(hba, &entry, opcode);
+}
+
+static void ufshcd_dme_cmd_log(struct ufs_hba *hba, char *str, u8 cmd_id)
+{
+}
+
+static void ufshcd_cmd_log_print(struct ufs_hba *hba)
+{
+}
+#endif
+
+#ifdef CONFIG_TRACEPOINTS
+static inline void ufshcd_cond_add_cmd_trace(struct ufs_hba *hba,
+ unsigned int tag, const char *str)
{
- sector_t lba = -1;
- u8 opcode = 0;
- u32 intr, doorbell;
struct ufshcd_lrb *lrbp;
+ char *cmd_type;
+ u8 opcode = 0;
+ u8 cmd_id, idn = 0;
+ sector_t lba = -1;
int transfer_len = -1;
lrbp = &hba->lrb[tag];
@@ -537,23 +668,28 @@ static void ufshcd_add_command_trace(struct ufs_hba *hba,
*/
if (lrbp->cmd->request && lrbp->cmd->request->bio)
lba =
- lrbp->cmd->request->bio->bi_iter.bi_sector;
+ lrbp->cmd->request->bio->bi_iter.bi_sector;
transfer_len = be32_to_cpu(
lrbp->ucd_req_ptr->sc.exp_data_transfer_len);
}
}
- intr = ufshcd_readl(hba, REG_INTERRUPT_STATUS);
- doorbell = ufshcd_readl(hba, REG_UTP_TRANSFER_REQ_DOOR_BELL);
- trace_ufshcd_command(dev_name(hba->dev), str, tag,
- doorbell, transfer_len, intr, lba, opcode);
-}
+ if (lrbp->command_type == UTP_CMD_TYPE_SCSI) {
+ cmd_type = "scsi";
+ cmd_id = (u8)(*lrbp->cmd->cmnd);
+ } else if (lrbp->command_type == UTP_CMD_TYPE_DEV_MANAGE) {
+ if (hba->dev_cmd.type == DEV_CMD_TYPE_NOP) {
+ cmd_type = "nop";
+ cmd_id = 0;
+ } else if (hba->dev_cmd.type == DEV_CMD_TYPE_QUERY) {
+ cmd_type = "query";
+ cmd_id = hba->dev_cmd.query.request.upiu_req.opcode;
+ idn = hba->dev_cmd.query.request.upiu_req.idn;
+ }
+ }
-static inline void ufshcd_cond_add_cmd_trace(struct ufs_hba *hba,
- unsigned int tag, const char *str)
-{
- if (trace_ufshcd_command_enabled())
- ufshcd_add_command_trace(hba, tag, str);
+ __ufshcd_cmd_log(hba, (char *) str, cmd_type, tag, cmd_id, idn,
+ lrbp->lun, lba, transfer_len, opcode);
}
#else
static inline void ufshcd_cond_add_cmd_trace(struct ufs_hba *hba,
@@ -2263,6 +2399,7 @@ ufshcd_dispatch_uic_cmd(struct ufs_hba *hba, struct uic_command *uic_cmd)
hba->active_uic_cmd = uic_cmd;
+ ufshcd_dme_cmd_log(hba, "send", hba->active_uic_cmd->command);
/* Write Args */
ufshcd_writel(hba, uic_cmd->argument1, REG_UIC_COMMAND_ARG_1);
ufshcd_writel(hba, uic_cmd->argument2, REG_UIC_COMMAND_ARG_2);
@@ -2296,6 +2433,8 @@ ufshcd_wait_for_uic_cmd(struct ufs_hba *hba, struct uic_command *uic_cmd)
if (ret)
ufsdbg_set_err_state(hba);
+ ufshcd_dme_cmd_log(hba, "cmp1", hba->active_uic_cmd->command);
+
spin_lock_irqsave(hba->host->host_lock, flags);
hba->active_uic_cmd = NULL;
spin_unlock_irqrestore(hba->host->host_lock, flags);
@@ -4113,6 +4252,8 @@ static int ufshcd_uic_pwr_ctrl(struct ufs_hba *hba, struct uic_command *cmd)
cmd->command, status);
ret = (status != PWR_OK) ? status : -1;
}
+ ufshcd_dme_cmd_log(hba, "cmp2", hba->active_uic_cmd->command);
+
out:
if (ret) {
ufsdbg_set_err_state(hba);
@@ -5421,7 +5562,7 @@ static void __ufshcd_transfer_req_compl(struct ufs_hba *hba,
} else if (lrbp->command_type == UTP_CMD_TYPE_DEV_MANAGE) {
if (hba->dev_cmd.complete) {
ufshcd_cond_add_cmd_trace(hba, index,
- "dev_complete");
+ "dcmp");
complete(hba->dev_cmd.complete);
}
}
@@ -5943,6 +6084,7 @@ static void ufshcd_err_handler(struct work_struct *work)
ufshcd_print_host_state(hba);
ufshcd_print_pwr_info(hba);
ufshcd_print_tmrs(hba, hba->outstanding_tasks);
+ ufshcd_cmd_log_print(hba);
spin_lock_irqsave(hba->host->host_lock, flags);
}
}
@@ -6449,6 +6591,7 @@ static int ufshcd_eh_device_reset_handler(struct scsi_cmnd *cmd)
hba = shost_priv(host);
tag = cmd->request->tag;
+ ufshcd_cmd_log_print(hba);
lrbp = &hba->lrb[tag];
err = ufshcd_issue_tm_cmd(hba, lrbp->lun, 0, UFS_LOGICAL_RESET, &resp);
if (err || resp != UPIU_TASK_MANAGEMENT_FUNC_COMPL) {
@@ -9809,6 +9952,8 @@ int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq)
*/
ufshcd_set_ufs_dev_active(hba);
+ ufshcd_cmd_log_init(hba);
+
async_schedule(ufshcd_async_scan, hba);
ufsdbg_add_debugfs(hba);
diff --git a/drivers/scsi/ufs/ufshcd.h b/drivers/scsi/ufs/ufshcd.h
index c34a998aac17..12d8f7ff53ed 100644
--- a/drivers/scsi/ufs/ufshcd.h
+++ b/drivers/scsi/ufs/ufshcd.h
@@ -519,7 +519,7 @@ struct ufs_init_prefetch {
u32 icc_level;
};
-#define UIC_ERR_REG_HIST_LENGTH 8
+#define UIC_ERR_REG_HIST_LENGTH 20
/**
* struct ufs_uic_err_reg_hist - keeps history of uic errors
* @pos: index to indicate cyclic buffer position
@@ -641,6 +641,27 @@ struct ufs_stats {
UFSHCD_DBG_PRINT_TMRS_EN | UFSHCD_DBG_PRINT_PWR_EN | \
UFSHCD_DBG_PRINT_HOST_STATE_EN)
+struct ufshcd_cmd_log_entry {
+ char *str; /* context like "send", "complete" */
+ char *cmd_type; /* "scsi", "query", "nop", "dme" */
+ u8 lun;
+ u8 cmd_id;
+ sector_t lba;
+ int transfer_len;
+ u8 idn; /* used only for query idn */
+ u32 doorbell;
+ u32 outstanding_reqs;
+ u32 seq_num;
+ unsigned int tag;
+ ktime_t tstamp;
+};
+
+struct ufshcd_cmd_log {
+ struct ufshcd_cmd_log_entry *entries;
+ int pos;
+ u32 seq_num;
+};
+
/**
* struct ufs_hba - per adapter private structure
* @mmio_base: UFSHCI base register address
@@ -856,6 +877,7 @@ struct ufs_hba {
struct ufs_clk_gating clk_gating;
struct ufs_hibern8_on_idle hibern8_on_idle;
+ struct ufshcd_cmd_log cmd_log;
/* Control to enable/disable host capabilities */
u32 caps;