Merge tag 'v4.9.214' of git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux...
[platform/kernel/linux-amlogic.git] / drivers / scsi / ufs / ufshcd.c
index 394df57..cca3280 100644 (file)
@@ -41,6 +41,7 @@
 #include <linux/devfreq.h>
 #include <linux/nls.h>
 #include <linux/of.h>
+#include <linux/blkdev.h>
 #include "ufshcd.h"
 #include "ufs_quirks.h"
 #include "unipro.h"
@@ -1491,6 +1492,17 @@ static int ufshcd_queuecommand(struct Scsi_Host *host, struct scsi_cmnd *cmd)
                clear_bit_unlock(tag, &hba->lrb_in_use);
                goto out;
        }
+
+       /* IO svc time latency histogram */
+       if (hba != NULL && cmd->request != NULL) {
+               if (hba->latency_hist_enabled &&
+                   (cmd->request->cmd_type == REQ_TYPE_FS)) {
+                       cmd->request->lat_hist_io_start = ktime_get();
+                       cmd->request->lat_hist_enabled = 1;
+               } else
+                       cmd->request->lat_hist_enabled = 0;
+       }
+
        WARN_ON(hba->clk_gating.state != CLKS_ON);
 
        lrbp = &hba->lrb[tag];
@@ -3699,6 +3711,7 @@ static void __ufshcd_transfer_req_compl(struct ufs_hba *hba,
        struct scsi_cmnd *cmd;
        int result;
        int index;
+       struct request *req;
 
        for_each_set_bit(index, &completed_reqs, hba->nutrs) {
                lrbp = &hba->lrb[index];
@@ -3710,6 +3723,22 @@ static void __ufshcd_transfer_req_compl(struct ufs_hba *hba,
                        /* Mark completed command as NULL in LRB */
                        lrbp->cmd = NULL;
                        clear_bit_unlock(index, &hba->lrb_in_use);
+                       req = cmd->request;
+                       if (req) {
+                               /* Update IO svc time latency histogram */
+                               if (req->lat_hist_enabled) {
+                                       ktime_t completion;
+                                       u_int64_t delta_us;
+
+                                       completion = ktime_get();
+                                       delta_us = ktime_us_delta(completion,
+                                                 req->lat_hist_io_start);
+                                       blk_update_latency_hist(
+                                               (rq_data_dir(req) == READ) ?
+                                               &hba->io_lat_read :
+                                               &hba->io_lat_write, delta_us);
+                               }
+                       }
                        /* Do not touch lrbp after scsi done */
                        cmd->scsi_done(cmd);
                        __ufshcd_release(hba);
@@ -6514,6 +6543,61 @@ out:
 }
 EXPORT_SYMBOL(ufshcd_shutdown);
 
+/*
+ * Values permitted 0, 1, 2.
+ * 0 -> Disable IO latency histograms (default)
+ * 1 -> Enable IO latency histograms
+ * 2 -> Zero out IO latency histograms
+ */
+static ssize_t
+latency_hist_store(struct device *dev, struct device_attribute *attr,
+                  const char *buf, size_t count)
+{
+       struct ufs_hba *hba = dev_get_drvdata(dev);
+       long value;
+
+       if (kstrtol(buf, 0, &value))
+               return -EINVAL;
+       if (value == BLK_IO_LAT_HIST_ZERO) {
+               memset(&hba->io_lat_read, 0, sizeof(hba->io_lat_read));
+               memset(&hba->io_lat_write, 0, sizeof(hba->io_lat_write));
+       } else if (value == BLK_IO_LAT_HIST_ENABLE ||
+                value == BLK_IO_LAT_HIST_DISABLE)
+               hba->latency_hist_enabled = value;
+       return count;
+}
+
+ssize_t
+latency_hist_show(struct device *dev, struct device_attribute *attr,
+                 char *buf)
+{
+       struct ufs_hba *hba = dev_get_drvdata(dev);
+       size_t written_bytes;
+
+       written_bytes = blk_latency_hist_show("Read", &hba->io_lat_read,
+                       buf, PAGE_SIZE);
+       written_bytes += blk_latency_hist_show("Write", &hba->io_lat_write,
+                       buf + written_bytes, PAGE_SIZE - written_bytes);
+
+       return written_bytes;
+}
+
+static DEVICE_ATTR(latency_hist, S_IRUGO | S_IWUSR,
+                  latency_hist_show, latency_hist_store);
+
+static void
+ufshcd_init_latency_hist(struct ufs_hba *hba)
+{
+       if (device_create_file(hba->dev, &dev_attr_latency_hist))
+               dev_err(hba->dev, "Failed to create latency_hist sysfs entry\n");
+}
+
+static void
+ufshcd_exit_latency_hist(struct ufs_hba *hba)
+{
+       device_create_file(hba->dev, &dev_attr_latency_hist);
+}
+
 /**
  * ufshcd_remove - de-allocate SCSI host and host memory space
  *             data structure memory
@@ -6527,6 +6611,7 @@ void ufshcd_remove(struct ufs_hba *hba)
        ufshcd_hba_stop(hba, true);
 
        ufshcd_exit_clk_gating(hba);
+       ufshcd_exit_latency_hist(hba);
        if (ufshcd_is_clkscaling_enabled(hba))
                devfreq_remove_device(hba->devfreq);
        ufshcd_hba_exit(hba);
@@ -6875,6 +6960,8 @@ int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq)
        /* Hold auto suspend until async scan completes */
        pm_runtime_get_sync(dev);
 
+       ufshcd_init_latency_hist(hba);
+
        /*
         * We are assuming that device wasn't put in sleep/power-down
         * state exclusively during the boot stage before kernel.
@@ -6891,6 +6978,7 @@ out_remove_scsi_host:
        scsi_remove_host(hba->host);
 exit_gating:
        ufshcd_exit_clk_gating(hba);
+       ufshcd_exit_latency_hist(hba);
 out_disable:
        hba->is_irq_enabled = false;
        ufshcd_hba_exit(hba);