From: Lee Susman <lsusman@xxxxxxxxxxxxxx> Adding debugfs capability for ufshcd. debugfs attributes introduced in this patch: - View driver/controller runtime data - Command tag statistics for performance analisis - Dump device descriptor info - Track recoverable errors statistics during runtime - Change UFS power mode during runtime entry a string in the format 'GGLLMM' where: G - selected gear L - number of lanes M - power mode (1=fast mode, 2=slow mode, 4=fast-auto mode, 5=slow-auto mode) First letter is for RX, second is for TX. - Get/set DME attributes Signed-off-by: Lee Susman <lsusman@xxxxxxxxxxxxxx> Signed-off-by: Dolev Raviv <draviv@xxxxxxxxxxxxxx> Signed-off-by: Yaniv Gardi <ygardi@xxxxxxxxxxxxxx> Signed-off-by: Raviv Shvili <rshvili@xxxxxxxxxxxxxx> Signed-off-by: Gilad Broner <gbroner@xxxxxxxxxxxxxx> --- drivers/scsi/ufs/Makefile | 1 + drivers/scsi/ufs/debugfs.c | 898 +++++++++++++++++++++++++++++++++++++++++++++ drivers/scsi/ufs/debugfs.h | 32 ++ drivers/scsi/ufs/ufshcd.c | 230 +++++++++++- drivers/scsi/ufs/ufshcd.h | 65 ++++ drivers/scsi/ufs/ufshci.h | 2 + 6 files changed, 1216 insertions(+), 12 deletions(-) create mode 100644 drivers/scsi/ufs/debugfs.c create mode 100644 drivers/scsi/ufs/debugfs.h diff --git a/drivers/scsi/ufs/Makefile b/drivers/scsi/ufs/Makefile index 31adca5..f60a4b9 100644 --- a/drivers/scsi/ufs/Makefile +++ b/drivers/scsi/ufs/Makefile @@ -4,3 +4,4 @@ obj-$(CONFIG_SCSI_UFS_QCOM_ICE) += ufs-qcom-ice.o obj-$(CONFIG_SCSI_UFSHCD) += ufshcd.o obj-$(CONFIG_SCSI_UFSHCD_PCI) += ufshcd-pci.o obj-$(CONFIG_SCSI_UFSHCD_PLATFORM) += ufshcd-pltfrm.o +obj-$(CONFIG_DEBUG_FS) += debugfs.o diff --git a/drivers/scsi/ufs/debugfs.c b/drivers/scsi/ufs/debugfs.c new file mode 100644 index 0000000..10d2b50 --- /dev/null +++ b/drivers/scsi/ufs/debugfs.c @@ -0,0 +1,898 @@ +/* Copyright (c) 2013-2015, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * UFS debugfs - add debugfs interface to the ufshcd. + * This is currently used for statistics collection and exporting from the + * UFS driver. + * This infrastructure can be used for debugging or direct tweaking + * of the driver from userspace. + * + */ + +#include "debugfs.h" +#include "unipro.h" + +enum field_width { + BYTE = 1, + WORD = 2, +}; + +struct desc_field_offset { + char *name; + int offset; + enum field_width width_byte; +}; + +#define UFS_ERR_STATS_PRINT(file, error_index, string, error_seen) \ + do { \ + if (err_stats[error_index]) { \ + seq_printf(file, string, \ + err_stats[error_index]); \ + error_seen = true; \ + } \ + } while (0) +#define DOORBELL_CLR_TOUT_US (1000 * 1000) /* 1 sec */ + +#define BUFF_LINE_CAPACITY 16 +#define TAB_CHARS 8 + +static int ufsdbg_tag_stats_show(struct seq_file *file, void *data) +{ + struct ufs_hba *hba = (struct ufs_hba *)file->private; + struct ufs_stats *ufs_stats; + int i, j; + int max_depth; + bool is_tag_empty = true; + unsigned long flags; + char *sep = " | * | "; + + if (!hba) + goto exit; + + ufs_stats = &hba->ufs_stats; + + if (!ufs_stats->enabled) { + pr_debug("%s: ufs statistics are disabled\n", __func__); + seq_puts(file, "ufs statistics are disabled"); + goto exit; + } + + max_depth = hba->nutrs; + + spin_lock_irqsave(hba->host->host_lock, flags); + /* Header */ + seq_printf(file, " Tag Stat\t\t%s Queue Fullness\n", sep); + for (i = 0; i < TAB_CHARS * (TS_NUM_STATS + 4); i++) { + seq_puts(file, "-"); + if (i == (TAB_CHARS * 3 - 1)) + seq_puts(file, sep); + } + seq_printf(file, + "\n #\tnum uses\t%s\t #\tAll\t Read\t Write\t Flush\n", + sep); + + /* values */ + for (i = 0; i < max_depth; i++) { + if (ufs_stats->tag_stats[i][0] <= 0 && + ufs_stats->tag_stats[i][1] <= 0 && + ufs_stats->tag_stats[i][2] <= 0 && + ufs_stats->tag_stats[i][3] <= 0) + continue; + + is_tag_empty = false; + seq_printf(file, " %d\t ", i); + for (j = 0; j < TS_NUM_STATS; j++) { + seq_printf(file, "%llu\t ", ufs_stats->tag_stats[i][j]); + if (j == 0) + seq_printf(file, "\t%s\t %d\t%llu\t ", sep, i, + ufs_stats->tag_stats[i][j+1] + + ufs_stats->tag_stats[i][j+2]); + } + seq_puts(file, "\n"); + } + spin_unlock_irqrestore(hba->host->host_lock, flags); + + if (is_tag_empty) + pr_debug("%s: All tags statistics are empty", __func__); + +exit: + return 0; +} + +static int ufsdbg_tag_stats_open(struct inode *inode, struct file *file) +{ + return single_open(file, ufsdbg_tag_stats_show, inode->i_private); +} + +static ssize_t ufsdbg_tag_stats_write(struct file *filp, + const char __user *ubuf, size_t cnt, + loff_t *ppos) +{ + struct ufs_hba *hba = filp->f_mapping->host->i_private; + struct ufs_stats *ufs_stats; + int val = 0; + int ret, bit = 0; + unsigned long flags; + + ret = kstrtoint_from_user(ubuf, cnt, 0, &val); + if (ret) { + dev_err(hba->dev, "%s: Invalid argument\n", __func__); + return ret; + } + + ufs_stats = &hba->ufs_stats; + spin_lock_irqsave(hba->host->host_lock, flags); + + if (!val) { + ufs_stats->enabled = false; + pr_debug("%s: Disabling UFS tag statistics", __func__); + } else { + ufs_stats->enabled = true; + pr_debug("%s: Enabling & Resetting UFS tag statistics", + __func__); + memset(hba->ufs_stats.tag_stats[0], 0, + sizeof(**hba->ufs_stats.tag_stats) * + TS_NUM_STATS * hba->nutrs); + + /* initialize current queue depth */ + ufs_stats->q_depth = 0; + for_each_set_bit_from(bit, &hba->outstanding_reqs, hba->nutrs) + ufs_stats->q_depth++; + pr_debug("%s: Enabled UFS tag statistics", __func__); + } + + spin_unlock_irqrestore(hba->host->host_lock, flags); + return cnt; +} + +static const struct file_operations ufsdbg_tag_stats_fops = { + .open = ufsdbg_tag_stats_open, + .read = seq_read, + .write = ufsdbg_tag_stats_write, +}; + +static int ufsdbg_err_stats_show(struct seq_file *file, void *data) +{ + struct ufs_hba *hba = (struct ufs_hba *)file->private; + int *err_stats; + unsigned long flags; + bool error_seen = false; + + if (!hba) + goto exit; + + err_stats = hba->ufs_stats.err_stats; + + spin_lock_irqsave(hba->host->host_lock, flags); + + seq_puts(file, "\n==UFS errors that caused controller reset==\n"); + + UFS_ERR_STATS_PRINT(file, UFS_ERR_HIBERN8_EXIT, + "controller reset due to hibern8 exit error:\t %d\n", + error_seen); + + UFS_ERR_STATS_PRINT(file, UFS_ERR_VOPS_SUSPEND, + "controller reset due to vops suspend error:\t\t %d\n", + error_seen); + + UFS_ERR_STATS_PRINT(file, UFS_ERR_EH, + "controller reset due to error handling:\t\t %d\n", + error_seen); + + UFS_ERR_STATS_PRINT(file, UFS_ERR_CLEAR_PEND_XFER_TM, + "controller reset due to clear xfer/tm regs:\t\t %d\n", + error_seen); + + UFS_ERR_STATS_PRINT(file, UFS_ERR_INT_FATAL_ERRORS, + "controller reset due to fatal interrupt:\t %d\n", + error_seen); + + UFS_ERR_STATS_PRINT(file, UFS_ERR_INT_UIC_ERROR, + "controller reset due to uic interrupt error:\t %d\n", + error_seen); + + if (error_seen) + error_seen = false; + else + seq_puts(file, + "so far, no errors that caused controller reset\n\n"); + + seq_puts(file, "\n\n==UFS other errors==\n"); + + UFS_ERR_STATS_PRINT(file, UFS_ERR_HIBERN8_ENTER, + "hibern8 enter:\t\t %d\n", error_seen); + + UFS_ERR_STATS_PRINT(file, UFS_ERR_RESUME, + "resume error:\t\t %d\n", error_seen); + + UFS_ERR_STATS_PRINT(file, UFS_ERR_SUSPEND, + "suspend error:\t\t %d\n", error_seen); + + UFS_ERR_STATS_PRINT(file, UFS_ERR_LINKSTARTUP, + "linkstartup error:\t\t %d\n", error_seen); + + UFS_ERR_STATS_PRINT(file, UFS_ERR_POWER_MODE_CHANGE, + "power change error:\t %d\n", error_seen); + + UFS_ERR_STATS_PRINT(file, UFS_ERR_TASK_ABORT, + "abort callback:\t\t %d\n\n", error_seen); + + if (!error_seen) + seq_puts(file, + "so far, no other UFS related errors\n\n"); + + spin_unlock_irqrestore(hba->host->host_lock, flags); +exit: + return 0; +} + +static int ufsdbg_err_stats_open(struct inode *inode, struct file *file) +{ + return single_open(file, ufsdbg_err_stats_show, inode->i_private); +} + +static ssize_t ufsdbg_err_stats_write(struct file *filp, + const char __user *ubuf, size_t cnt, + loff_t *ppos) +{ + struct ufs_hba *hba = filp->f_mapping->host->i_private; + struct ufs_stats *ufs_stats; + unsigned long flags; + + ufs_stats = &hba->ufs_stats; + spin_lock_irqsave(hba->host->host_lock, flags); + + pr_debug("%s: Resetting UFS error statistics", __func__); + memset(ufs_stats->err_stats, 0, sizeof(hba->ufs_stats.err_stats)); + + spin_unlock_irqrestore(hba->host->host_lock, flags); + return cnt; +} + +static const struct file_operations ufsdbg_err_stats_fops = { + .open = ufsdbg_err_stats_open, + .read = seq_read, + .write = ufsdbg_err_stats_write, +}; + +static int ufshcd_init_statistics(struct ufs_hba *hba) +{ + struct ufs_stats *stats = &hba->ufs_stats; + int ret = 0; + int i; + + stats->enabled = false; + stats->tag_stats = kcalloc(hba->nutrs, sizeof(*stats->tag_stats), + GFP_KERNEL); + if (!hba->ufs_stats.tag_stats) + goto no_mem; + + stats->tag_stats[0] = kzalloc(sizeof(**stats->tag_stats) * + TS_NUM_STATS * hba->nutrs, GFP_KERNEL); + if (!stats->tag_stats[0]) + goto no_mem; + + for (i = 1; i < hba->nutrs; i++) + stats->tag_stats[i] = &stats->tag_stats[0][i * TS_NUM_STATS]; + + memset(stats->err_stats, 0, sizeof(hba->ufs_stats.err_stats)); + + goto exit; + +no_mem: + dev_err(hba->dev, "%s: Unable to allocate UFS tag_stats", __func__); + ret = -ENOMEM; +exit: + return ret; +} + +static void +ufsdbg_pr_buf_to_std(struct seq_file *file, void *buff, int size, char *str) +{ + int i; + char linebuf[38]; + int lines = size/BUFF_LINE_CAPACITY + + (size % BUFF_LINE_CAPACITY ? 1 : 0); + + for (i = 0; i < lines; i++) { + hex_dump_to_buffer(buff + i * BUFF_LINE_CAPACITY, + BUFF_LINE_CAPACITY, BUFF_LINE_CAPACITY, 4, + linebuf, sizeof(linebuf), false); + seq_printf(file, "%s [%x]: %s\n", str, i * BUFF_LINE_CAPACITY, + linebuf); + } +} + +static int ufsdbg_host_regs_show(struct seq_file *file, void *data) +{ + struct ufs_hba *hba = (struct ufs_hba *)file->private; + + ufshcd_hold(hba, false); + pm_runtime_get_sync(hba->dev); + ufsdbg_pr_buf_to_std(file, hba->mmio_base, UFSHCI_REG_SPACE_SIZE, + "host regs"); + pm_runtime_put_sync(hba->dev); + ufshcd_release(hba); + return 0; +} + +static int ufsdbg_host_regs_open(struct inode *inode, struct file *file) +{ + return single_open(file, ufsdbg_host_regs_show, inode->i_private); +} + +static const struct file_operations ufsdbg_host_regs_fops = { + .open = ufsdbg_host_regs_open, + .read = seq_read, +}; + +static int ufsdbg_dump_device_desc_show(struct seq_file *file, void *data) +{ + int err = 0; + int buff_len = QUERY_DESC_DEVICE_MAX_SIZE; + u8 desc_buf[QUERY_DESC_DEVICE_MAX_SIZE]; + struct ufs_hba *hba = (struct ufs_hba *)file->private; + + struct desc_field_offset device_desc_field_name[] = { + {"bLength", 0x00, BYTE}, + {"bDescriptorType", 0x01, BYTE}, + {"bDevice", 0x02, BYTE}, + {"bDeviceClass", 0x03, BYTE}, + {"bDeviceSubClass", 0x04, BYTE}, + {"bProtocol", 0x05, BYTE}, + {"bNumberLU", 0x06, BYTE}, + {"bNumberWLU", 0x07, BYTE}, + {"bBootEnable", 0x08, BYTE}, + {"bDescrAccessEn", 0x09, BYTE}, + {"bInitPowerMode", 0x0A, BYTE}, + {"bHighPriorityLUN", 0x0B, BYTE}, + {"bSecureRemovalType", 0x0C, BYTE}, + {"bSecurityLU", 0x0D, BYTE}, + {"Reserved", 0x0E, BYTE}, + {"bInitActiveICCLevel", 0x0F, BYTE}, + {"wSpecVersion", 0x10, WORD}, + {"wManufactureDate", 0x12, WORD}, + {"iManufactureName", 0x14, BYTE}, + {"iProductName", 0x15, BYTE}, + {"iSerialNumber", 0x16, BYTE}, + {"iOemID", 0x17, BYTE}, + {"wManufactureID", 0x18, WORD}, + {"bUD0BaseOffset", 0x1A, BYTE}, + {"bUDConfigPLength", 0x1B, BYTE}, + {"bDeviceRTTCap", 0x1C, BYTE}, + {"wPeriodicRTCUpdate", 0x1D, WORD} + }; + + pm_runtime_get_sync(hba->dev); + err = ufshcd_read_device_desc(hba, desc_buf, buff_len); + pm_runtime_put_sync(hba->dev); + + if (!err) { + int i; + struct desc_field_offset *tmp; + + for (i = 0; i < ARRAY_SIZE(device_desc_field_name); ++i) { + tmp = &device_desc_field_name[i]; + + if (tmp->width_byte == BYTE) { + seq_printf(file, + "Device Descriptor[Byte offset 0x%x]: %s = 0x%x\n", + tmp->offset, + tmp->name, + (u8)desc_buf[tmp->offset]); + } else if (tmp->width_byte == WORD) { + seq_printf(file, + "Device Descriptor[Byte offset 0x%x]: %s = 0x%x\n", + tmp->offset, + tmp->name, + *(u16 *)&desc_buf[tmp->offset]); + } else { + seq_printf(file, + "Device Descriptor[offset 0x%x]: %s. Wrong Width = %d", + tmp->offset, tmp->name, tmp->width_byte); + } + } + } else { + seq_printf(file, "Reading Device Descriptor failed. err = %d\n", + err); + } + + return err; +} + +static int ufsdbg_show_hba_show(struct seq_file *file, void *data) +{ + struct ufs_hba *hba = (struct ufs_hba *)file->private; + + seq_printf(file, "hba->outstanding_tasks = 0x%x\n", + (u32)hba->outstanding_tasks); + seq_printf(file, "hba->outstanding_reqs = 0x%x\n", + (u32)hba->outstanding_reqs); + + seq_printf(file, "hba->capabilities = 0x%x\n", hba->capabilities); + seq_printf(file, "hba->nutrs = %d\n", hba->nutrs); + seq_printf(file, "hba->nutmrs = %d\n", hba->nutmrs); + seq_printf(file, "hba->ufs_version = 0x%x\n", hba->ufs_version); + seq_printf(file, "hba->irq = 0x%x\n", hba->irq); + seq_printf(file, "hba->auto_bkops_enabled = %d\n", + hba->auto_bkops_enabled); + + seq_printf(file, "hba->ufshcd_state = 0x%x\n", hba->ufshcd_state); + seq_printf(file, "hba->clk_gating.state = 0x%x\n", + hba->clk_gating.state); + seq_printf(file, "hba->eh_flags = 0x%x\n", hba->eh_flags); + seq_printf(file, "hba->intr_mask = 0x%x\n", hba->intr_mask); + seq_printf(file, "hba->ee_ctrl_mask = 0x%x\n", hba->ee_ctrl_mask); + + /* HBA Errors */ + seq_printf(file, "hba->errors = 0x%x\n", hba->errors); + seq_printf(file, "hba->uic_error = 0x%x\n", hba->uic_error); + seq_printf(file, "hba->saved_err = 0x%x\n", hba->saved_err); + seq_printf(file, "hba->saved_uic_err = 0x%x\n", hba->saved_uic_err); + + return 0; +} + +static int ufsdbg_show_hba_open(struct inode *inode, struct file *file) +{ + return single_open(file, ufsdbg_show_hba_show, inode->i_private); +} + +static const struct file_operations ufsdbg_show_hba_fops = { + .open = ufsdbg_show_hba_open, + .read = seq_read, +}; + +static int ufsdbg_dump_device_desc_open(struct inode *inode, struct file *file) +{ + return single_open(file, + ufsdbg_dump_device_desc_show, inode->i_private); +} + +static const struct file_operations ufsdbg_dump_device_desc = { + .open = ufsdbg_dump_device_desc_open, + .read = seq_read, +}; + +static int ufsdbg_power_mode_show(struct seq_file *file, void *data) +{ + struct ufs_hba *hba = (struct ufs_hba *)file->private; + static const char * const names[] = { + "INVALID MODE", + "FAST MODE", + "SLOW MODE", + "INVALID MODE", + "FASTAUTO MODE", + "SLOWAUTO MODE", + "INVALID MODE", + }; + + /* Print current status */ + seq_puts(file, "UFS current power mode [RX, TX]:"); + seq_printf(file, "gear=[%d,%d], lane=[%d,%d], pwr=[%s,%s], rate = %c", + hba->pwr_info.gear_rx, hba->pwr_info.gear_tx, + hba->pwr_info.lane_rx, hba->pwr_info.lane_tx, + names[hba->pwr_info.pwr_rx], + names[hba->pwr_info.pwr_tx], + hba->pwr_info.hs_rate == PA_HS_MODE_B ? 'B' : 'A'); + seq_puts(file, "\n\n"); + + /* Print usage */ + seq_puts(file, + "To change power mode write 'GGLLMM' where:\n" + "G - selected gear\n" + "L - number of lanes\n" + "M - power mode:\n" + "\t1 = fast mode\n" + "\t2 = slow mode\n" + "\t4 = fast-auto mode\n" + "\t5 = slow-auto mode\n" + "first letter is for RX, second letter is for TX.\n\n"); + + return 0; +} + +static bool ufsdbg_power_mode_validate(struct ufs_pa_layer_attr *pwr_mode) +{ + if (pwr_mode->gear_rx < UFS_PWM_G1 || pwr_mode->gear_rx > UFS_PWM_G7 || + pwr_mode->gear_tx < UFS_PWM_G1 || pwr_mode->gear_tx > UFS_PWM_G7 || + pwr_mode->lane_rx < 1 || pwr_mode->lane_rx > 2 || + pwr_mode->lane_tx < 1 || pwr_mode->lane_tx > 2 || + (pwr_mode->pwr_rx != FAST_MODE && pwr_mode->pwr_rx != SLOW_MODE && + pwr_mode->pwr_rx != FASTAUTO_MODE && + pwr_mode->pwr_rx != SLOWAUTO_MODE) || + (pwr_mode->pwr_tx != FAST_MODE && pwr_mode->pwr_tx != SLOW_MODE && + pwr_mode->pwr_tx != FASTAUTO_MODE && + pwr_mode->pwr_tx != SLOWAUTO_MODE)) { + pr_err("%s: power parameters are not valid\n", __func__); + return false; + } + + return true; +} + +static int ufsdbg_cfg_pwr_param(struct ufs_hba *hba, + struct ufs_pa_layer_attr *new_pwr, + struct ufs_pa_layer_attr *final_pwr) +{ + int ret = 0; + bool is_dev_sup_hs = false; + bool is_new_pwr_hs = false; + int dev_pwm_max_rx_gear; + int dev_pwm_max_tx_gear; + + if (!hba->max_pwr_info.is_valid) { + dev_err(hba->dev, "%s: device max power is not valid. can't configure power\n", + __func__); + return -EINVAL; + } + + if (hba->max_pwr_info.info.pwr_rx == FAST_MODE) + is_dev_sup_hs = true; + + if (new_pwr->pwr_rx == FAST_MODE || new_pwr->pwr_rx == FASTAUTO_MODE) + is_new_pwr_hs = true; + + final_pwr->lane_rx = hba->max_pwr_info.info.lane_rx; + final_pwr->lane_tx = hba->max_pwr_info.info.lane_tx; + + /* device doesn't support HS but requested power is HS */ + if (!is_dev_sup_hs && is_new_pwr_hs) { + pr_err("%s: device doesn't support HS. requested power is HS\n", + __func__); + return -ENOTSUPP; + } else if ((is_dev_sup_hs && is_new_pwr_hs) || + (!is_dev_sup_hs && !is_new_pwr_hs)) { + /* + * If device and requested power mode are both HS or both PWM + * then dev_max->gear_xx are the gears to be assign to + * final_pwr->gear_xx + */ + final_pwr->gear_rx = hba->max_pwr_info.info.gear_rx; + final_pwr->gear_tx = hba->max_pwr_info.info.gear_tx; + } else if (is_dev_sup_hs && !is_new_pwr_hs) { + /* + * If device supports HS but requested power is PWM, then we + * need to find out what is the max gear in PWM the device + * supports + */ + + ufshcd_dme_get(hba, UIC_ARG_MIB(PA_MAXRXPWMGEAR), + &dev_pwm_max_rx_gear); + + if (!dev_pwm_max_rx_gear) { + pr_err("%s: couldn't get device max pwm rx gear\n", + __func__); + ret = -EINVAL; + goto out; + } + + ufshcd_dme_peer_get(hba, UIC_ARG_MIB(PA_MAXRXPWMGEAR), + &dev_pwm_max_tx_gear); + + if (!dev_pwm_max_tx_gear) { + pr_err("%s: couldn't get device max pwm tx gear\n", + __func__); + ret = -EINVAL; + goto out; + } + + final_pwr->gear_rx = dev_pwm_max_rx_gear; + final_pwr->gear_tx = dev_pwm_max_tx_gear; + } + + if ((new_pwr->gear_rx > final_pwr->gear_rx) || + (new_pwr->gear_tx > final_pwr->gear_tx) || + (new_pwr->lane_rx > final_pwr->lane_rx) || + (new_pwr->lane_tx > final_pwr->lane_tx)) { + pr_err("%s: (RX,TX) GG,LL: in PWM/HS new pwr [%d%d,%d%d] exceeds device limitation [%d%d,%d%d]\n", + __func__, + new_pwr->gear_rx, new_pwr->gear_tx, + new_pwr->lane_rx, new_pwr->lane_tx, + final_pwr->gear_rx, final_pwr->gear_tx, + final_pwr->lane_rx, final_pwr->lane_tx); + return -ENOTSUPP; + } + + final_pwr->gear_rx = new_pwr->gear_rx; + final_pwr->gear_tx = new_pwr->gear_tx; + final_pwr->lane_rx = new_pwr->lane_rx; + final_pwr->lane_tx = new_pwr->lane_tx; + final_pwr->pwr_rx = new_pwr->pwr_rx; + final_pwr->pwr_tx = new_pwr->pwr_tx; + final_pwr->hs_rate = new_pwr->hs_rate; + +out: + return ret; +} + +static int ufsdbg_config_pwr_mode(struct ufs_hba *hba, + struct ufs_pa_layer_attr *desired_pwr_mode) +{ + int ret; + + pm_runtime_get_sync(hba->dev); + scsi_block_requests(hba->host); + ret = ufshcd_wait_for_doorbell_clr(hba, DOORBELL_CLR_TOUT_US); + if (!ret) + ret = ufshcd_change_power_mode(hba, desired_pwr_mode); + scsi_unblock_requests(hba->host); + pm_runtime_put_sync(hba->dev); + + return ret; +} + +static ssize_t ufsdbg_power_mode_write(struct file *file, + const char __user *ubuf, size_t cnt, + loff_t *ppos) +{ + struct ufs_hba *hba = file->f_mapping->host->i_private; + struct ufs_pa_layer_attr pwr_mode; + struct ufs_pa_layer_attr final_pwr_mode; + char pwr_mode_str[BUFF_LINE_CAPACITY] = {0}; + loff_t buff_pos = 0; + int ret; + int idx = 0; + + ret = simple_write_to_buffer(pwr_mode_str, BUFF_LINE_CAPACITY, + &buff_pos, ubuf, cnt); + + pwr_mode.gear_rx = pwr_mode_str[idx++] - '0'; + pwr_mode.gear_tx = pwr_mode_str[idx++] - '0'; + pwr_mode.lane_rx = pwr_mode_str[idx++] - '0'; + pwr_mode.lane_tx = pwr_mode_str[idx++] - '0'; + pwr_mode.pwr_rx = pwr_mode_str[idx++] - '0'; + pwr_mode.pwr_tx = pwr_mode_str[idx++] - '0'; + + /* + * Switching between rates is not currently supported so use the + * current rate. + * TODO: add rate switching if and when it is supported in the future + */ + pwr_mode.hs_rate = hba->pwr_info.hs_rate; + + /* Validate user input */ + if (!ufsdbg_power_mode_validate(&pwr_mode)) + return -EINVAL; + + pr_debug("%s: new power mode requested [RX,TX]: Gear=[%d,%d], Lane=[%d,%d], Mode=[%d,%d]\n", + __func__, + pwr_mode.gear_rx, pwr_mode.gear_tx, pwr_mode.lane_rx, + pwr_mode.lane_tx, pwr_mode.pwr_rx, pwr_mode.pwr_tx); + + ret = ufsdbg_cfg_pwr_param(hba, &pwr_mode, &final_pwr_mode); + if (ret) { + dev_err(hba->dev, + "%s: failed to configure new power parameters, ret = %d\n", + __func__, ret); + return cnt; + } + + ret = ufsdbg_config_pwr_mode(hba, &final_pwr_mode); + if (ret == -EBUSY) + dev_err(hba->dev, + "%s: ufshcd_config_pwr_mode failed: system is busy, try again\n", + __func__); + else if (ret) + dev_err(hba->dev, + "%s: ufshcd_config_pwr_mode failed, ret=%d\n", + __func__, ret); + + return cnt; +} + +static int ufsdbg_power_mode_open(struct inode *inode, struct file *file) +{ + return single_open(file, ufsdbg_power_mode_show, inode->i_private); +} + +static const struct file_operations ufsdbg_power_mode_desc = { + .open = ufsdbg_power_mode_open, + .read = seq_read, + .write = ufsdbg_power_mode_write, +}; + +static int ufsdbg_dme_read(void *data, u64 *attr_val, bool peer) +{ + int ret; + struct ufs_hba *hba = data; + u32 attr_id, read_val = 0; + int (*read_func)(struct ufs_hba *, u32, u32 *); + + if (!hba) + return -EINVAL; + + read_func = peer ? ufshcd_dme_peer_get : ufshcd_dme_get; + attr_id = peer ? hba->debugfs_files.dme_peer_attr_id : + hba->debugfs_files.dme_local_attr_id; + pm_runtime_get_sync(hba->dev); + scsi_block_requests(hba->host); + ret = ufshcd_wait_for_doorbell_clr(hba, DOORBELL_CLR_TOUT_US); + if (!ret) + ret = read_func(hba, UIC_ARG_MIB(attr_id), &read_val); + scsi_unblock_requests(hba->host); + pm_runtime_put_sync(hba->dev); + + if (!ret) + *attr_val = (u64)read_val; + + return ret; +} + +static int ufsdbg_dme_local_set_attr_id(void *data, u64 attr_id) +{ + struct ufs_hba *hba = data; + + if (!hba) + return -EINVAL; + + hba->debugfs_files.dme_local_attr_id = (u32)attr_id; + + return 0; +} + +static int ufsdbg_dme_local_read(void *data, u64 *attr_val) +{ + return ufsdbg_dme_read(data, attr_val, false); +} + +DEFINE_SIMPLE_ATTRIBUTE(ufsdbg_dme_local_read_ops, + ufsdbg_dme_local_read, + ufsdbg_dme_local_set_attr_id, + "%llu\n"); + +static int ufsdbg_dme_peer_read(void *data, u64 *attr_val) +{ + struct ufs_hba *hba = data; + + if (!hba) + return -EINVAL; + else + return ufsdbg_dme_read(data, attr_val, true); +} + +static int ufsdbg_dme_peer_set_attr_id(void *data, u64 attr_id) +{ + struct ufs_hba *hba = data; + + if (!hba) + return -EINVAL; + + hba->debugfs_files.dme_peer_attr_id = (u32)attr_id; + + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(ufsdbg_dme_peer_read_ops, + ufsdbg_dme_peer_read, + ufsdbg_dme_peer_set_attr_id, + "%llu\n"); + +void ufsdbg_add_debugfs(struct ufs_hba *hba) +{ + if (!hba) { + pr_err("%s: NULL hba, exiting\n", __func__); + return; + } + + hba->debugfs_files.debugfs_root = debugfs_create_dir("ufs", NULL); + if (IS_ERR(hba->debugfs_files.debugfs_root)) + /* Don't complain -- debugfs just isn't enabled */ + goto err_no_root; + if (!hba->debugfs_files.debugfs_root) { + /* + * Complain -- debugfs is enabled, but it failed to + * create the directory + */ + dev_err(hba->dev, + "%s: NULL debugfs root directory, exiting", __func__); + goto err_no_root; + } + + hba->debugfs_files.tag_stats = + debugfs_create_file("tag_stats", S_IRUSR, + hba->debugfs_files.debugfs_root, hba, + &ufsdbg_tag_stats_fops); + if (!hba->debugfs_files.tag_stats) { + dev_err(hba->dev, "%s: NULL tag stats file, exiting", + __func__); + goto err; + } + + hba->debugfs_files.err_stats = + debugfs_create_file("err_stats", S_IRUSR, + hba->debugfs_files.debugfs_root, hba, + &ufsdbg_err_stats_fops); + if (!hba->debugfs_files.err_stats) { + dev_err(hba->dev, "%s: NULL err stats file, exiting", + __func__); + goto err; + } + + if (ufshcd_init_statistics(hba)) { + dev_err(hba->dev, "%s: Error initializing statistics", + __func__); + goto err; + } + + hba->debugfs_files.host_regs = debugfs_create_file("host_regs", S_IRUSR, + hba->debugfs_files.debugfs_root, hba, + &ufsdbg_host_regs_fops); + if (!hba->debugfs_files.host_regs) { + dev_err(hba->dev, "%s: NULL hcd regs file, exiting", __func__); + goto err; + } + + hba->debugfs_files.show_hba = debugfs_create_file("show_hba", S_IRUSR, + hba->debugfs_files.debugfs_root, hba, + &ufsdbg_show_hba_fops); + if (!hba->debugfs_files.show_hba) { + dev_err(hba->dev, "%s: NULL hba file, exiting", __func__); + goto err; + } + + hba->debugfs_files.dump_dev_desc = + debugfs_create_file("dump_device_desc", S_IRUSR, + hba->debugfs_files.debugfs_root, hba, + &ufsdbg_dump_device_desc); + if (!hba->debugfs_files.dump_dev_desc) { + dev_err(hba->dev, + "%s: NULL dump_device_desc file, exiting", __func__); + goto err; + } + + hba->debugfs_files.power_mode = + debugfs_create_file("power_mode", S_IRUSR | S_IWUSR, + hba->debugfs_files.debugfs_root, hba, + &ufsdbg_power_mode_desc); + if (!hba->debugfs_files.power_mode) { + dev_err(hba->dev, + "%s: NULL power_mode_desc file, exiting", __func__); + goto err; + } + + hba->debugfs_files.dme_local_read = + debugfs_create_file("dme_local_read", S_IRUSR | S_IWUSR, + hba->debugfs_files.debugfs_root, hba, + &ufsdbg_dme_local_read_ops); + if (!hba->debugfs_files.dme_local_read) { + dev_err(hba->dev, + "%s: failed create dme_local_read debugfs entry\n", + __func__); + goto err; + } + + hba->debugfs_files.dme_peer_read = + debugfs_create_file("dme_peer_read", S_IRUSR | S_IWUSR, + hba->debugfs_files.debugfs_root, hba, + &ufsdbg_dme_peer_read_ops); + if (!hba->debugfs_files.dme_peer_read) { + dev_err(hba->dev, + "%s: failed create dme_peer_read debugfs entry\n", + __func__); + goto err; + } + + return; + +err: + debugfs_remove_recursive(hba->debugfs_files.debugfs_root); + hba->debugfs_files.debugfs_root = NULL; +err_no_root: + dev_err(hba->dev, "%s: failed to initialize debugfs\n", __func__); +} + +void ufsdbg_remove_debugfs(struct ufs_hba *hba) +{ + debugfs_remove_recursive(hba->debugfs_files.debugfs_root); + kfree(hba->ufs_stats.tag_stats); + +} diff --git a/drivers/scsi/ufs/debugfs.h b/drivers/scsi/ufs/debugfs.h new file mode 100644 index 0000000..de7680a --- /dev/null +++ b/drivers/scsi/ufs/debugfs.h @@ -0,0 +1,32 @@ +/* Copyright (c) 2013-2015, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * UFS debugfs - add debugfs interface to the ufshcd. + * This is currently used for statistics collection and exporting from the + * UFS driver. + * This infrastructure can be used for debugging or direct tweaking + * of the driver from userspace. + * + */ + +#ifndef _UFS_DEBUGFS_H +#define _UFS_DEBUGFS_H + +#include <linux/debugfs.h> +#include "ufshcd.h" + +#ifdef CONFIG_DEBUG_FS +void ufsdbg_add_debugfs(struct ufs_hba *hba); + +void ufsdbg_remove_debugfs(struct ufs_hba *hba); +#endif + +#endif /* End of Header */ diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c index cb357f8..f2e446b 100644 --- a/drivers/scsi/ufs/ufshcd.c +++ b/drivers/scsi/ufs/ufshcd.c @@ -43,6 +43,61 @@ #include "ufshcd.h" #include "unipro.h" +#include "debugfs.h" + +#ifdef CONFIG_DEBUG_FS + +#define UFSHCD_UPDATE_ERROR_STATS(hba, type) \ + do { \ + if (type < UFS_ERR_MAX) \ + hba->ufs_stats.err_stats[type]++; \ + } while (0) + +#define UFSHCD_UPDATE_TAG_STATS(hba, tag) \ + do { \ + struct request *rq = hba->lrb[task_tag].cmd ? \ + hba->lrb[task_tag].cmd->request : NULL; \ + u64 **tag_stats = hba->ufs_stats.tag_stats; \ + int rq_type = -1; \ + if (!hba->ufs_stats.enabled) \ + break; \ + tag_stats[tag][TS_TAG]++; \ + if (!rq) \ + break; \ + WARN_ON(hba->ufs_stats.q_depth > hba->nutrs); \ + if (rq_data_dir(rq) == READ) \ + rq_type = TS_READ; \ + else if (rq_data_dir(rq) == WRITE) \ + rq_type = TS_WRITE; \ + else if (rq->cmd_flags & REQ_FLUSH) \ + rq_type = TS_FLUSH; \ + else \ + break; \ + tag_stats[hba->ufs_stats.q_depth++][rq_type]++; \ + } while (0) + +#define UFSHCD_UPDATE_TAG_STATS_COMPLETION(hba, cmd) \ + do { \ + struct request *rq = cmd ? cmd->request : NULL; \ + if (cmd->request && \ + ((rq_data_dir(rq) == READ) || \ + (rq_data_dir(rq) == WRITE) || \ + (rq->cmd_flags & REQ_FLUSH))) \ + hba->ufs_stats.q_depth--; \ + } while (0) + +#define UFSDBG_ADD_DEBUGFS(hba) ufsdbg_add_debugfs(hba) + +#define UFSDBG_REMOVE_DEBUGFS(hba) ufsdbg_remove_debugfs(hba) + +#else +#define UFSHCD_UPDATE_TAG_STATS(hba, tag) +#define UFSHCD_UPDATE_TAG_STATS_COMPLETION(hba, cmd) +#define UFSDBG_ADD_DEBUGFS(hba) +#define UFSDBG_REMOVE_DEBUGFS(hba) +#define UFSHCD_UPDATE_ERROR_STATS(hba, type) + +#endif #define UFSHCD_ENABLE_INTRS (UTP_TRANSFER_REQ_COMPL |\ UTP_TASK_REQ_COMPL |\ @@ -50,6 +105,9 @@ /* UIC command timeout, unit: ms */ #define UIC_CMD_TIMEOUT 500 +/* Retries waiting for doorbells to clear */ +#define POWER_MODE_RETRIES 10 + /* NOP OUT retries waiting for NOP IN response */ #define NOP_OUT_RETRIES 10 /* Timeout after 30 msecs if NOP OUT hangs without response */ @@ -189,8 +247,6 @@ static int ufshcd_uic_hibern8_exit(struct ufs_hba *hba); static int ufshcd_uic_hibern8_enter(struct ufs_hba *hba); static int ufshcd_host_reset_and_restore(struct ufs_hba *hba); static irqreturn_t ufshcd_intr(int irq, void *__hba); -static int ufshcd_config_pwr_mode(struct ufs_hba *hba, - struct ufs_pa_layer_attr *desired_pwr_mode); static inline int ufshcd_enable_irq(struct ufs_hba *hba) { @@ -789,6 +845,7 @@ void ufshcd_send_command(struct ufs_hba *hba, unsigned int task_tag) ufshcd_clk_scaling_start_busy(hba); __set_bit(task_tag, &hba->outstanding_reqs); ufshcd_writel(hba, 1 << task_tag, REG_UTP_TRANSFER_REQ_DOOR_BELL); + UFSHCD_UPDATE_TAG_STATS(hba, task_tag); } /** @@ -975,6 +1032,7 @@ ufshcd_send_uic_cmd(struct ufs_hba *hba, struct uic_command *uic_cmd) unsigned long flags; ufshcd_hold(hba, false); + pm_runtime_get_sync(hba->dev); mutex_lock(&hba->uic_cmd_mutex); spin_lock_irqsave(hba->host->host_lock, flags); ret = __ufshcd_send_uic_cmd(hba, uic_cmd); @@ -983,7 +1041,7 @@ ufshcd_send_uic_cmd(struct ufs_hba *hba, struct uic_command *uic_cmd) ret = ufshcd_wait_for_uic_cmd(hba, uic_cmd); mutex_unlock(&hba->uic_cmd_mutex); - + pm_runtime_put_sync(hba->dev); ufshcd_release(hba); return ret; } @@ -1866,6 +1924,27 @@ static inline int ufshcd_read_power_desc(struct ufs_hba *hba, return ufshcd_read_desc(hba, QUERY_DESC_IDN_POWER, 0, buf, size); } +int ufshcd_read_device_desc(struct ufs_hba *hba, u8 *buf, u32 size) +{ + int err = 0; + int retries; + + for (retries = QUERY_REQ_RETRIES; retries > 0; retries--) { + /* Read descriptor*/ + err = ufshcd_read_desc(hba, + QUERY_DESC_IDN_DEVICE, 0, buf, size); + if (!err) + break; + dev_dbg(hba->dev, "%s: error %d retrying\n", __func__, err); + } + + if (err) + dev_err(hba->dev, "%s: reading Device Desc failed. err = %d\n", + __func__, err); + + return err; +} + /** * ufshcd_read_unit_desc_param - read the specified unit descriptor parameter * @hba: Pointer to adapter instance @@ -2158,11 +2237,42 @@ static int ufshcd_uic_pwr_ctrl(struct ufs_hba *hba, struct uic_command *cmd) unsigned long flags; u8 status; int ret; + u32 tm_doorbell; + u32 tr_doorbell; + bool uic_ready; + int retries = POWER_MODE_RETRIES; + ufshcd_hold(hba, false); + pm_runtime_get_sync(hba->dev); mutex_lock(&hba->uic_cmd_mutex); init_completion(&uic_async_done); - spin_lock_irqsave(hba->host->host_lock, flags); + /* + * Before changing the power mode there should be no outstanding + * tasks/transfer requests. Verify by checking the doorbell registers + * are clear. + */ + do { + spin_lock_irqsave(hba->host->host_lock, flags); + uic_ready = ufshcd_ready_for_uic_cmd(hba); + tm_doorbell = ufshcd_readl(hba, REG_UTP_TASK_REQ_DOOR_BELL); + tr_doorbell = ufshcd_readl(hba, REG_UTP_TRANSFER_REQ_DOOR_BELL); + if (!tm_doorbell && !tr_doorbell && uic_ready) + break; + + spin_unlock_irqrestore(hba->host->host_lock, flags); + schedule(); + retries--; + } while (retries && (tm_doorbell || tr_doorbell || !uic_ready)); + + if (!retries) { + dev_err(hba->dev, + "%s: too many retries waiting for doorbell to clear (tm=0x%x, tr=0x%x, uicrdy=%d)\n", + __func__, tm_doorbell, tr_doorbell, uic_ready); + ret = -EBUSY; + goto out; + } + hba->uic_async_done = &uic_async_done; ret = __ufshcd_send_uic_cmd(hba, cmd); spin_unlock_irqrestore(hba->host->host_lock, flags); @@ -2201,7 +2311,56 @@ out: hba->uic_async_done = NULL; spin_unlock_irqrestore(hba->host->host_lock, flags); mutex_unlock(&hba->uic_cmd_mutex); + pm_runtime_put_sync(hba->dev); + ufshcd_release(hba); + return ret; +} + +int ufshcd_wait_for_doorbell_clr(struct ufs_hba *hba, u64 wait_timeout_us) +{ + unsigned long flags; + int ret = 0; + u32 tm_doorbell; + u32 tr_doorbell; + bool timeout = false; + ktime_t start = ktime_get(); + + ufshcd_hold(hba, false); + spin_lock_irqsave(hba->host->host_lock, flags); + if (hba->ufshcd_state != UFSHCD_STATE_OPERATIONAL) { + ret = -EBUSY; + goto out; + } + /* + * Wait for all the outstanding tasks/transfer requests. + * Verify by checking the doorbell registers are clear. + */ + do { + tm_doorbell = ufshcd_readl(hba, REG_UTP_TASK_REQ_DOOR_BELL); + tr_doorbell = ufshcd_readl(hba, REG_UTP_TRANSFER_REQ_DOOR_BELL); + if (!tm_doorbell && !tr_doorbell) { + timeout = false; + break; + } + + spin_unlock_irqrestore(hba->host->host_lock, flags); + schedule(); + if (ktime_to_us(ktime_sub(ktime_get(), start)) > + wait_timeout_us) + timeout = true; + spin_lock_irqsave(hba->host->host_lock, flags); + } while (tm_doorbell || tr_doorbell); + + if (timeout) { + dev_err(hba->dev, + "%s: timedout waiting for doorbell to clear (tm=0x%x, tr=0x%x)\n", + __func__, tm_doorbell, tr_doorbell); + ret = -EBUSY; + } +out: + spin_unlock_irqrestore(hba->host->host_lock, flags); + ufshcd_release(hba); return ret; } @@ -2230,11 +2389,20 @@ static int ufshcd_uic_change_pwr_mode(struct ufs_hba *hba, u8 mode) static int ufshcd_uic_hibern8_enter(struct ufs_hba *hba) { + int ret; struct uic_command uic_cmd = {0}; uic_cmd.command = UIC_CMD_DME_HIBER_ENTER; - return ufshcd_uic_pwr_ctrl(hba, &uic_cmd); + ret = ufshcd_uic_pwr_ctrl(hba, &uic_cmd); + + if (ret) { + UFSHCD_UPDATE_ERROR_STATS(hba, UFS_ERR_HIBERN8_ENTER); + dev_err(hba->dev, "%s: hibern8 enter failed. ret = %d", + __func__, ret); + } + + return ret; } static int ufshcd_uic_hibern8_exit(struct ufs_hba *hba) @@ -2246,6 +2414,9 @@ static int ufshcd_uic_hibern8_exit(struct ufs_hba *hba) ret = ufshcd_uic_pwr_ctrl(hba, &uic_cmd); if (ret) { ufshcd_set_link_off(hba); + UFSHCD_UPDATE_ERROR_STATS(hba, UFS_ERR_HIBERN8_EXIT); + dev_err(hba->dev, "%s: hibern8 exit failed. ret = %d", + __func__, ret); ret = ufshcd_host_reset_and_restore(hba); } @@ -2279,8 +2450,8 @@ static int ufshcd_get_max_pwr_mode(struct ufs_hba *hba) if (hba->max_pwr_info.is_valid) return 0; - pwr_info->pwr_tx = FASTAUTO_MODE; - pwr_info->pwr_rx = FASTAUTO_MODE; + pwr_info->pwr_tx = FAST_MODE; + pwr_info->pwr_rx = FAST_MODE; pwr_info->hs_rate = PA_HS_MODE_B; /* Get the connected lane count */ @@ -2311,7 +2482,7 @@ static int ufshcd_get_max_pwr_mode(struct ufs_hba *hba) __func__, pwr_info->gear_rx); return -EINVAL; } - pwr_info->pwr_rx = SLOWAUTO_MODE; + pwr_info->pwr_rx = SLOW_MODE; } ufshcd_dme_peer_get(hba, UIC_ARG_MIB(PA_MAXRXHSGEAR), @@ -2324,14 +2495,14 @@ static int ufshcd_get_max_pwr_mode(struct ufs_hba *hba) __func__, pwr_info->gear_tx); return -EINVAL; } - pwr_info->pwr_tx = SLOWAUTO_MODE; + pwr_info->pwr_tx = SLOW_MODE; } hba->max_pwr_info.is_valid = true; return 0; } -static int ufshcd_change_power_mode(struct ufs_hba *hba, +int ufshcd_change_power_mode(struct ufs_hba *hba, struct ufs_pa_layer_attr *pwr_mode) { int ret; @@ -2383,6 +2554,7 @@ static int ufshcd_change_power_mode(struct ufs_hba *hba, | pwr_mode->pwr_tx); if (ret) { + UFSHCD_UPDATE_ERROR_STATS(hba, UFS_ERR_POWER_MODE_CHANGE); dev_err(hba->dev, "%s: power mode change failed %d\n", __func__, ret); } else { @@ -2613,9 +2785,12 @@ static int ufshcd_link_startup(struct ufs_hba *hba) hba->vops->link_startup_notify(hba, PRE_CHANGE); ret = ufshcd_dme_link_startup(hba); + if (ret) + UFSHCD_UPDATE_ERROR_STATS(hba, UFS_ERR_LINKSTARTUP); /* check if device is detected by inter-connect layer */ if (!ret && !ufshcd_is_device_present(hba)) { + UFSHCD_UPDATE_ERROR_STATS(hba, UFS_ERR_LINKSTARTUP); dev_err(hba->dev, "%s: Device not present\n", __func__); ret = -ENXIO; goto out; @@ -3051,6 +3226,7 @@ static void ufshcd_transfer_req_compl(struct ufs_hba *hba) lrbp = &hba->lrb[index]; cmd = lrbp->cmd; if (cmd) { + UFSHCD_UPDATE_TAG_STATS_COMPLETION(hba, cmd); result = ufshcd_transfer_rsp_status(hba, lrbp); scsi_dma_unmap(cmd); cmd->result = result; @@ -3382,6 +3558,19 @@ static void ufshcd_err_handler(struct work_struct *work) if (err_xfer || err_tm || (hba->saved_err & INT_FATAL_ERRORS) || ((hba->saved_err & UIC_ERROR) && (hba->saved_uic_err & UFSHCD_UIC_DL_PA_INIT_ERROR))) { + + if (hba->saved_err & INT_FATAL_ERRORS) + UFSHCD_UPDATE_ERROR_STATS(hba, + UFS_ERR_INT_FATAL_ERRORS); + + if (hba->saved_err & UIC_ERROR) + UFSHCD_UPDATE_ERROR_STATS(hba, + UFS_ERR_INT_UIC_ERROR); + + if (err_xfer || err_tm) + UFSHCD_UPDATE_ERROR_STATS(hba, + UFS_ERR_CLEAR_PEND_XFER_TM); + err = ufshcd_reset_and_restore(hba); if (err) { dev_err(hba->dev, "%s: reset and restore failed\n", @@ -3719,6 +3908,9 @@ static int ufshcd_abort(struct scsi_cmnd *cmd) hba = shost_priv(host); tag = cmd->request->tag; + UFSHCD_UPDATE_ERROR_STATS(hba, UFS_ERR_TASK_ABORT); + + ufshcd_hold(hba, false); /* If command is already aborted/completed, return SUCCESS */ if (!(test_bit(tag, &hba->outstanding_reqs))) @@ -3903,6 +4095,7 @@ static int ufshcd_eh_host_reset_handler(struct scsi_cmnd *cmd) ufshcd_set_eh_in_progress(hba); spin_unlock_irqrestore(hba->host->host_lock, flags); + UFSHCD_UPDATE_ERROR_STATS(hba, UFS_ERR_EH); err = ufshcd_reset_and_restore(hba); spin_lock_irqsave(hba->host->host_lock, flags); @@ -5188,10 +5381,12 @@ vops_resume: hba->vops->resume(hba, pm_op); set_link_active: ufshcd_vreg_set_hpm(hba); - if (ufshcd_is_link_hibern8(hba) && !ufshcd_uic_hibern8_exit(hba)) + if (ufshcd_is_link_hibern8(hba) && !ufshcd_uic_hibern8_exit(hba)) { ufshcd_set_link_active(hba); - else if (ufshcd_is_link_off(hba)) + } else if (ufshcd_is_link_off(hba)) { + UFSHCD_UPDATE_ERROR_STATS(hba, UFS_ERR_VOPS_SUSPEND); ufshcd_host_reset_and_restore(hba); + } set_dev_active: if (!ufshcd_set_dev_pwr_mode(hba, UFS_ACTIVE_PWR_MODE)) ufshcd_disable_auto_bkops(hba); @@ -5200,6 +5395,10 @@ enable_gating: ufshcd_release(hba); out: hba->pm_op_in_progress = 0; + + if (ret) + UFSHCD_UPDATE_ERROR_STATS(hba, UFS_ERR_SUSPEND); + return ret; } @@ -5295,6 +5494,10 @@ disable_irq_and_vops_clks: ufshcd_setup_clocks(hba, false); out: hba->pm_op_in_progress = 0; + + if (ret) + UFSHCD_UPDATE_ERROR_STATS(hba, UFS_ERR_RESUME); + return ret; } @@ -5760,6 +5963,8 @@ int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq) async_schedule(ufshcd_async_scan, hba); + UFSDBG_ADD_DEBUGFS(hba); + return 0; out_remove_scsi_host: @@ -5769,6 +5974,7 @@ exit_gating: out_disable: hba->is_irq_enabled = false; scsi_host_put(host); + UFSDBG_REMOVE_DEBUGFS(hba); ufshcd_hba_exit(hba); out_error: return err; diff --git a/drivers/scsi/ufs/ufshcd.h b/drivers/scsi/ufs/ufshcd.h index 4a574aa..d9b1251 100644 --- a/drivers/scsi/ufs/ufshcd.h +++ b/drivers/scsi/ufs/ufshcd.h @@ -3,6 +3,7 @@ * * This code is based on drivers/scsi/ufs/ufshcd.h * Copyright (C) 2011-2013 Samsung India Software Operations + * Copyright (c) 2013-2015, The Linux Foundation. All rights reserved. * * Authors: * Santosh Yaraganavi <santosh.sy@xxxxxxxxxxx> @@ -125,6 +126,25 @@ enum uic_link_state { #define ufshcd_set_link_hibern8(hba) ((hba)->uic_link_state = \ UIC_LINK_HIBERN8_STATE) +enum { + /* errors which require the host controller reset for recovery */ + UFS_ERR_HIBERN8_EXIT, + UFS_ERR_VOPS_SUSPEND, + UFS_ERR_EH, + UFS_ERR_CLEAR_PEND_XFER_TM, + UFS_ERR_INT_FATAL_ERRORS, + UFS_ERR_INT_UIC_ERROR, + + /* other errors */ + UFS_ERR_HIBERN8_ENTER, + UFS_ERR_RESUME, + UFS_ERR_SUSPEND, + UFS_ERR_LINKSTARTUP, + UFS_ERR_POWER_MODE_CHANGE, + UFS_ERR_TASK_ABORT, + UFS_ERR_MAX, +}; + /* * UFS Power management levels. * Each level is in increasing order of power savings. @@ -203,6 +223,39 @@ struct ufs_dev_cmd { struct ufs_query query; }; +#ifdef CONFIG_DEBUG_FS +struct ufs_stats { + bool enabled; + u64 **tag_stats; + int q_depth; + int err_stats[UFS_ERR_MAX]; +}; + +struct debugfs_files { + struct dentry *debugfs_root; + struct dentry *tag_stats; + struct dentry *err_stats; + struct dentry *show_hba; + struct dentry *host_regs; + struct dentry *dump_dev_desc; + struct dentry *power_mode; + struct dentry *dme_local_read; + struct dentry *dme_peer_read; + u32 dme_local_attr_id; + u32 dme_peer_attr_id; +}; + +/* tag stats statistics types */ +enum ts_types { + TS_NOT_SUPPORTED = -1, + TS_TAG = 0, + TS_READ = 1, + TS_WRITE = 2, + TS_FLUSH = 3, + TS_NUM_STATS = 4, +}; +#endif + /** * struct ufs_clk_info - UFS clock related info * @list: list headed by hba->clk_list_head @@ -371,6 +424,8 @@ struct ufs_init_prefetch { * @clk_list_head: UFS host controller clocks list node head * @pwr_info: holds current power mode * @max_pwr_info: keeps the device max valid pwm + * @ufs_stats: ufshcd statistics to be used via debugfs + * @debugfs_files: debugfs files associated with the ufs stats */ struct ufs_hba { void __iomem *mmio_base; @@ -473,6 +528,10 @@ struct ufs_hba { struct devfreq *devfreq; struct ufs_clk_scaling clk_scaling; bool is_sys_suspended; +#ifdef CONFIG_DEBUG_FS + struct ufs_stats ufs_stats; + struct debugfs_files debugfs_files; +#endif }; /* Returns true if clocks can be gated. Otherwise false */ @@ -593,4 +652,10 @@ static inline int ufshcd_dme_peer_get(struct ufs_hba *hba, int ufshcd_hold(struct ufs_hba *hba, bool async); void ufshcd_release(struct ufs_hba *hba); +int ufshcd_read_device_desc(struct ufs_hba *hba, u8 *buf, u32 size); + +/* Expose Query-Request API */ +int ufshcd_wait_for_doorbell_clr(struct ufs_hba *hba, u64 wait_timeout_us); +int ufshcd_change_power_mode(struct ufs_hba *hba, + struct ufs_pa_layer_attr *pwr_mode); #endif /* End of Header */ diff --git a/drivers/scsi/ufs/ufshci.h b/drivers/scsi/ufs/ufshci.h index d572119..c8b178f 100644 --- a/drivers/scsi/ufs/ufshci.h +++ b/drivers/scsi/ufs/ufshci.h @@ -72,6 +72,8 @@ enum { REG_UIC_COMMAND_ARG_1 = 0x94, REG_UIC_COMMAND_ARG_2 = 0x98, REG_UIC_COMMAND_ARG_3 = 0x9C, + + UFSHCI_REG_SPACE_SIZE = 0xA0, }; /* Controller capability masks */ -- Qualcomm Israel, on behalf of Qualcomm Innovation Center, Inc. The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum, a Linux Foundation Collaborative Project -- To unsubscribe from this list: send the line "unsubscribe linux-arm-msm" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html