This is a patch for the HPB feature.
This patch adds HPB function calls to UFS core driver.

Acked-by: Bart Van Assche <bvanass...@acm.org>
Acked-by: Avri Altman <avri.alt...@wdc.com>
Tested-by: Bean Huo <bean...@micron.com>
Signed-off-by: Daejun Park <daejun7.p...@samsung.com>
---
 drivers/scsi/ufs/Kconfig  |   9 +
 drivers/scsi/ufs/Makefile |   1 +
 drivers/scsi/ufs/ufshcd.c |  60 ++++
 drivers/scsi/ufs/ufshcd.h |  23 +-
 drivers/scsi/ufs/ufshpb.c | 621 ++++++++++++++++++++++++++++++++++++++
 drivers/scsi/ufs/ufshpb.h | 174 +++++++++++
 6 files changed, 887 insertions(+), 1 deletion(-)
 create mode 100644 drivers/scsi/ufs/ufshpb.c
 create mode 100644 drivers/scsi/ufs/ufshpb.h

diff --git a/drivers/scsi/ufs/Kconfig b/drivers/scsi/ufs/Kconfig
index f6394999b98c..773fd241ac84 100644
--- a/drivers/scsi/ufs/Kconfig
+++ b/drivers/scsi/ufs/Kconfig
@@ -182,3 +182,12 @@ config SCSI_UFS_CRYPTO
          Enabling this makes it possible for the kernel to use the crypto
          capabilities of the UFS device (if present) to perform crypto
          operations on data being transferred to/from the device.
+
+config SCSI_UFS_HPB
+       bool "Support UFS Host Performance Booster"
+       depends on SCSI_UFSHCD
+       help
+         The UFS HPB feature improves random read performance. It caches
+         L2P (logical to physical) map of UFS to host DRAM. The driver uses HPB
+         read command by piggybacking physical page number for bypassing FTL 
(flash
+         translation layer)'s L2P address translation.
\ No newline at end of file
diff --git a/drivers/scsi/ufs/Makefile b/drivers/scsi/ufs/Makefile
index 4679af1b564e..663e17cee359 100644
--- a/drivers/scsi/ufs/Makefile
+++ b/drivers/scsi/ufs/Makefile
@@ -11,6 +11,7 @@ obj-$(CONFIG_SCSI_UFSHCD) += ufshcd-core.o
 ufshcd-core-y                          += ufshcd.o ufs-sysfs.o
 ufshcd-core-$(CONFIG_SCSI_UFS_BSG)     += ufs_bsg.o
 ufshcd-core-$(CONFIG_SCSI_UFS_CRYPTO) += ufshcd-crypto.o
+ufshcd-core-$(CONFIG_SCSI_UFS_HPB) += ufshpb.o
 obj-$(CONFIG_SCSI_UFSHCD_PCI) += ufshcd-pci.o
 obj-$(CONFIG_SCSI_UFSHCD_PLATFORM) += ufshcd-pltfrm.o
 obj-$(CONFIG_SCSI_UFS_HISI) += ufs-hisi.o
diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c
index 06e2439d523c..40ddce5b36d1 100644
--- a/drivers/scsi/ufs/ufshcd.c
+++ b/drivers/scsi/ufs/ufshcd.c
@@ -22,6 +22,7 @@
 #include "ufs-sysfs.h"
 #include "ufs_bsg.h"
 #include "ufshcd-crypto.h"
+#include "ufshpb.h"
 #include <asm/unaligned.h>
 #include <linux/blkdev.h>
 
@@ -2546,6 +2547,8 @@ static int ufshcd_queuecommand(struct Scsi_Host *host, 
struct scsi_cmnd *cmd)
 
        ufshcd_comp_scsi_upiu(hba, lrbp);
 
+       ufshpb_prep(hba, lrbp);
+
        err = ufshcd_map_sg(hba, lrbp);
        if (err) {
                lrbp->cmd = NULL;
@@ -4691,6 +4694,33 @@ static int ufshcd_change_queue_depth(struct scsi_device 
*sdev, int depth)
        return scsi_change_queue_depth(sdev, depth);
 }
 
+static void ufshcd_hpb_destroy(struct ufs_hba *hba, struct scsi_device *sdev)
+{
+       /* skip well-known LU */
+       if (sdev->lun >= UFS_UPIU_MAX_UNIT_NUM_ID)
+               return;
+
+       if (!ufshpb_is_allowed(hba))
+               return;
+
+       ufshpb_destroy_lu(hba, sdev);
+}
+
+static void ufshcd_hpb_configure(struct ufs_hba *hba, struct scsi_device *sdev)
+{
+       /* skip well-known LU */
+       if (sdev->lun >= UFS_UPIU_MAX_UNIT_NUM_ID)
+               return;
+
+       if (!(hba->dev_info.b_ufs_feature_sup & UFS_DEV_HPB_SUPPORT))
+               return;
+
+       if (!ufshpb_is_allowed(hba))
+               return;
+
+       ufshpb_init_hpb_lu(hba, sdev);
+}
+
 /**
  * ufshcd_slave_configure - adjust SCSI device configurations
  * @sdev: pointer to SCSI device
@@ -4700,6 +4730,8 @@ static int ufshcd_slave_configure(struct scsi_device 
*sdev)
        struct ufs_hba *hba = shost_priv(sdev->host);
        struct request_queue *q = sdev->request_queue;
 
+       ufshcd_hpb_configure(hba, sdev);
+
        blk_queue_update_dma_pad(q, PRDT_DATA_BYTE_COUNT_PAD - 1);
 
        if (ufshcd_is_rpm_autosuspend_allowed(hba))
@@ -4719,6 +4751,9 @@ static void ufshcd_slave_destroy(struct scsi_device *sdev)
        struct ufs_hba *hba;
 
        hba = shost_priv(sdev->host);
+
+       ufshcd_hpb_destroy(hba, sdev);
+
        /* Drop the reference as it won't be needed anymore */
        if (ufshcd_scsi_to_upiu_lun(sdev->lun) == UFS_UPIU_UFS_DEVICE_WLUN) {
                unsigned long flags;
@@ -4828,6 +4863,9 @@ ufshcd_transfer_rsp_status(struct ufs_hba *hba, struct 
ufshcd_lrb *lrbp)
                                 */
                                pm_runtime_get_noresume(hba->dev);
                        }
+
+                       if (scsi_status == SAM_STAT_GOOD)
+                               ufshpb_rsp_upiu(hba, lrbp);
                        break;
                case UPIU_TRANSACTION_REJECT_UPIU:
                        /* TODO: handle Reject UPIU Response */
@@ -6681,6 +6719,8 @@ static int ufshcd_host_reset_and_restore(struct ufs_hba 
*hba)
         * Stop the host controller and complete the requests
         * cleared by h/w
         */
+       ufshpb_reset_host(hba);
+
        ufshcd_hba_stop(hba);
 
        spin_lock_irqsave(hba->host->host_lock, flags);
@@ -7116,9 +7156,14 @@ static int ufs_get_device_desc(struct ufs_hba *hba)
        /* getting Specification Version in big endian format */
        dev_info->wspecversion = desc_buf[DEVICE_DESC_PARAM_SPEC_VER] << 8 |
                                      desc_buf[DEVICE_DESC_PARAM_SPEC_VER + 1];
+       dev_info->b_ufs_feature_sup = desc_buf[DEVICE_DESC_PARAM_UFS_FEAT];
 
        model_index = desc_buf[DEVICE_DESC_PARAM_PRDCT_NAME];
 
+       if (dev_info->wspecversion >= UFS_DEV_HPB_SUPPORT_VERSION &&
+           (dev_info->b_ufs_feature_sup & UFS_DEV_HPB_SUPPORT))
+               ufshpb_get_dev_info(hba, desc_buf);
+
        err = ufshcd_read_string_desc(hba, model_index,
                                      &dev_info->model, SD_ASCII_STD);
        if (err < 0) {
@@ -7347,6 +7392,10 @@ static int ufshcd_device_geo_params_init(struct ufs_hba 
*hba)
        else if (desc_buf[GEOMETRY_DESC_PARAM_MAX_NUM_LUN] == 0)
                hba->dev_info.max_lu_supported = 8;
 
+       if (hba->desc_size[QUERY_DESC_IDN_GEOMETRY] >=
+               GEOMETRY_DESC_PARAM_HPB_MAX_ACTIVE_REGS)
+               ufshpb_get_geo_info(hba, desc_buf);
+
 out:
        kfree(desc_buf);
        return err;
@@ -7486,6 +7535,7 @@ static int ufshcd_add_lus(struct ufs_hba *hba)
        }
 
        ufs_bsg_probe(hba);
+       ufshpb_init(hba);
        scsi_scan_host(hba->host);
        pm_runtime_put_sync(hba->dev);
 
@@ -7572,6 +7622,7 @@ static int ufshcd_probe_hba(struct ufs_hba *hba, bool 
async)
        /* Enable Auto-Hibernate if configured */
        ufshcd_auto_hibern8_enable(hba);
 
+       ufshpb_reset(hba);
 out:
        spin_lock_irqsave(hba->host->host_lock, flags);
        if (ret)
@@ -7618,6 +7669,9 @@ static void ufshcd_async_scan(void *data, async_cookie_t 
cookie)
 static const struct attribute_group *ufshcd_driver_groups[] = {
        &ufs_sysfs_unit_descriptor_group,
        &ufs_sysfs_lun_attributes_group,
+#ifdef CONFIG_SCSI_UFS_HPB
+       &ufs_sysfs_hpb_stat_group,
+#endif
        NULL,
 };
 
@@ -8341,6 +8395,8 @@ static int ufshcd_suspend(struct ufs_hba *hba, enum 
ufs_pm_op pm_op)
                req_link_state = UIC_LINK_OFF_STATE;
        }
 
+       ufshpb_suspend(hba);
+
        /*
         * If we can't transition into any of the low power modes
         * just gate the clocks.
@@ -8465,6 +8521,7 @@ static int ufshcd_suspend(struct ufs_hba *hba, enum 
ufs_pm_op pm_op)
        hba->clk_gating.is_suspended = false;
        hba->dev_info.b_rpm_dev_flush_capable = false;
        ufshcd_release(hba);
+       ufshpb_resume(hba);
 out:
        if (hba->dev_info.b_rpm_dev_flush_capable) {
                schedule_delayed_work(&hba->rpm_dev_flush_recheck_work,
@@ -8564,6 +8621,8 @@ static int ufshcd_resume(struct ufs_hba *hba, enum 
ufs_pm_op pm_op)
        /* Enable Auto-Hibernate if configured */
        ufshcd_auto_hibern8_enable(hba);
 
+       ufshpb_resume(hba);
+
        if (hba->dev_info.b_rpm_dev_flush_capable) {
                hba->dev_info.b_rpm_dev_flush_capable = false;
                cancel_delayed_work(&hba->rpm_dev_flush_recheck_work);
@@ -8793,6 +8852,7 @@ EXPORT_SYMBOL(ufshcd_shutdown);
 void ufshcd_remove(struct ufs_hba *hba)
 {
        ufs_bsg_remove(hba);
+       ufshpb_remove(hba);
        ufs_sysfs_remove_nodes(hba->dev);
        blk_cleanup_queue(hba->tmf_queue);
        blk_mq_free_tag_set(&hba->tmf_tag_set);
diff --git a/drivers/scsi/ufs/ufshcd.h b/drivers/scsi/ufs/ufshcd.h
index 618b253e5ec8..428ae2d599bb 100644
--- a/drivers/scsi/ufs/ufshcd.h
+++ b/drivers/scsi/ufs/ufshcd.h
@@ -589,6 +589,25 @@ struct ufs_hba_variant_params {
        u32 wb_flush_threshold;
 };
 
+#ifdef CONFIG_SCSI_UFS_HPB
+/**
+ * struct ufshpb_dev_info - UFSHPB device related info
+ * @num_lu: the number of user logical unit to check whether all lu finished
+ *          initialization
+ * @rgn_size: device reported HPB region size
+ * @srgn_size: device reported HPB sub-region size
+ * @slave_conf_cnt: counter to check all lu finished initialization
+ * @hpb_disabled: flag to check if HPB is disabled
+ */
+struct ufshpb_dev_info {
+       int num_lu;
+       int rgn_size;
+       int srgn_size;
+       atomic_t slave_conf_cnt;
+       bool hpb_disabled;
+};
+#endif
+
 /**
  * struct ufs_hba - per adapter private structure
  * @mmio_base: UFSHCI base register address
@@ -770,7 +789,9 @@ struct ufs_hba {
        bool wb_buf_flush_enabled;
        bool wb_enabled;
        struct delayed_work rpm_dev_flush_recheck_work;
-
+#ifdef CONFIG_SCSI_UFS_HPB
+       struct ufshpb_dev_info ufshpb_dev;
+#endif
 #ifdef CONFIG_SCSI_UFS_CRYPTO
        union ufs_crypto_capabilities crypto_capabilities;
        union ufs_crypto_cap_entry *crypto_cap_array;
diff --git a/drivers/scsi/ufs/ufshpb.c b/drivers/scsi/ufs/ufshpb.c
new file mode 100644
index 000000000000..45eb80116bfd
--- /dev/null
+++ b/drivers/scsi/ufs/ufshpb.c
@@ -0,0 +1,621 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Universal Flash Storage Host Performance Booster
+ *
+ * Copyright (C) 2017-2018 Samsung Electronics Co., Ltd.
+ *
+ * Authors:
+ *     Yongmyung Lee <ymhungry....@samsung.com>
+ *     Jinyoung Choi <j-young.c...@samsung.com>
+ */
+
+#include <asm/unaligned.h>
+#include <linux/async.h>
+
+#include "ufshcd.h"
+#include "ufshpb.h"
+#include "../sd.h"
+
+bool ufshpb_is_allowed(struct ufs_hba *hba)
+{
+       return !(hba->ufshpb_dev.hpb_disabled);
+}
+
+static int ufshpb_get_state(struct ufshpb_lu *hpb)
+{
+       return atomic_read(&hpb->hpb_state);
+}
+
+static void ufshpb_set_state(struct ufshpb_lu *hpb, int state)
+{
+       atomic_set(&hpb->hpb_state, state);
+}
+
+void ufshpb_prep(struct ufs_hba *hba, struct ufshcd_lrb *lrbp)
+{
+}
+
+void ufshpb_rsp_upiu(struct ufs_hba *hba, struct ufshcd_lrb *lrbp)
+{
+}
+
+static void ufshpb_init_subregion_tbl(struct ufshpb_lu *hpb,
+                                     struct ufshpb_region *rgn)
+{
+       int srgn_idx;
+
+       for (srgn_idx = 0; srgn_idx < rgn->srgn_cnt; srgn_idx++) {
+               struct ufshpb_subregion *srgn = rgn->srgn_tbl + srgn_idx;
+
+               srgn->rgn_idx = rgn->rgn_idx;
+               srgn->srgn_idx = srgn_idx;
+               srgn->srgn_state = HPB_SRGN_UNUSED;
+       }
+}
+
+static int ufshpb_alloc_subregion_tbl(struct ufshpb_lu *hpb,
+                                            struct ufshpb_region *rgn,
+                                            int srgn_cnt)
+{
+       rgn->srgn_tbl = kvcalloc(srgn_cnt, sizeof(struct ufshpb_subregion),
+                                GFP_KERNEL);
+       if (!rgn->srgn_tbl)
+               return -ENOMEM;
+
+       rgn->srgn_cnt = srgn_cnt;
+       return 0;
+}
+
+static void ufshpb_init_lu_parameter(struct ufs_hba *hba,
+                                    struct ufshpb_lu *hpb,
+                                    struct ufshpb_dev_info *hpb_dev_info,
+                                    struct ufshpb_lu_info *hpb_lu_info)
+{
+       u32 entries_per_rgn;
+       u64 rgn_mem_size, tmp;
+
+       hpb->lu_pinned_start = hpb_lu_info->pinned_start;
+       hpb->lu_pinned_end = hpb_lu_info->num_pinned ?
+               (hpb_lu_info->pinned_start + hpb_lu_info->num_pinned - 1)
+               : PINNED_NOT_SET;
+
+       rgn_mem_size = (1ULL << hpb_dev_info->rgn_size) * HPB_RGN_SIZE_UNIT
+                       * HPB_ENTRY_SIZE;
+       do_div(rgn_mem_size, HPB_ENTRY_BLOCK_SIZE);
+       hpb->srgn_mem_size = (1ULL << hpb_dev_info->srgn_size)
+               * HPB_RGN_SIZE_UNIT / HPB_ENTRY_BLOCK_SIZE * HPB_ENTRY_SIZE;
+
+       tmp = rgn_mem_size;
+       do_div(tmp, HPB_ENTRY_SIZE);
+       entries_per_rgn = (u32)tmp;
+       hpb->entries_per_rgn_shift = ilog2(entries_per_rgn);
+       hpb->entries_per_rgn_mask = entries_per_rgn - 1;
+
+       hpb->entries_per_srgn = hpb->srgn_mem_size / HPB_ENTRY_SIZE;
+       hpb->entries_per_srgn_shift = ilog2(hpb->entries_per_srgn);
+       hpb->entries_per_srgn_mask = hpb->entries_per_srgn - 1;
+
+       tmp = rgn_mem_size;
+       do_div(tmp, hpb->srgn_mem_size);
+       hpb->srgns_per_rgn = (int)tmp;
+
+       hpb->rgns_per_lu = DIV_ROUND_UP(hpb_lu_info->num_blocks,
+                               entries_per_rgn);
+       hpb->srgns_per_lu = DIV_ROUND_UP(hpb_lu_info->num_blocks,
+                               (hpb->srgn_mem_size / HPB_ENTRY_SIZE));
+
+       hpb->pages_per_srgn = hpb->srgn_mem_size / PAGE_SIZE;
+
+       dev_info(hba->dev, "ufshpb(%d): region memory size - %llu (bytes)\n",
+                hpb->lun, rgn_mem_size);
+       dev_info(hba->dev, "ufshpb(%d): subregion memory size - %u (bytes)\n",
+                hpb->lun, hpb->srgn_mem_size);
+       dev_info(hba->dev, "ufshpb(%d): total blocks per lu - %d\n",
+                hpb->lun, hpb_lu_info->num_blocks);
+       dev_info(hba->dev, "ufshpb(%d): subregions per region - %d, regions per 
lu - %u",
+                hpb->lun, hpb->srgns_per_rgn, hpb->rgns_per_lu);
+}
+
+static int ufshpb_alloc_region_tbl(struct ufs_hba *hba, struct ufshpb_lu *hpb)
+{
+       struct ufshpb_region *rgn_table, *rgn;
+       int rgn_idx, i;
+       int ret = 0;
+
+       rgn_table = kvcalloc(hpb->rgns_per_lu, sizeof(struct ufshpb_region),
+                           GFP_KERNEL);
+       if (!rgn_table)
+               return -ENOMEM;
+
+       hpb->rgn_tbl = rgn_table;
+
+       for (rgn_idx = 0; rgn_idx < hpb->rgns_per_lu; rgn_idx++) {
+               int srgn_cnt = hpb->srgns_per_rgn;
+
+               rgn = rgn_table + rgn_idx;
+               rgn->rgn_idx = rgn_idx;
+
+               if (rgn_idx == hpb->rgns_per_lu - 1)
+                       srgn_cnt = ((hpb->srgns_per_lu - 1) %
+                                   hpb->srgns_per_rgn) + 1;
+
+               ret = ufshpb_alloc_subregion_tbl(hpb, rgn, srgn_cnt);
+               if (ret)
+                       goto release_srgn_table;
+               ufshpb_init_subregion_tbl(hpb, rgn);
+
+               rgn->rgn_state = HPB_RGN_INACTIVE;
+       }
+
+       return 0;
+
+release_srgn_table:
+       for (i = 0; i < rgn_idx; i++) {
+               rgn = rgn_table + i;
+               if (rgn->srgn_tbl)
+                       kvfree(rgn->srgn_tbl);
+       }
+       kvfree(rgn_table);
+       return ret;
+}
+
+static void ufshpb_destroy_subregion_tbl(struct ufshpb_lu *hpb,
+                                        struct ufshpb_region *rgn)
+{
+       int srgn_idx;
+
+       for (srgn_idx = 0; srgn_idx < rgn->srgn_cnt; srgn_idx++) {
+               struct ufshpb_subregion *srgn;
+
+               srgn = rgn->srgn_tbl + srgn_idx;
+               srgn->srgn_state = HPB_SRGN_UNUSED;
+       }
+}
+
+static void ufshpb_destroy_region_tbl(struct ufshpb_lu *hpb)
+{
+       int rgn_idx;
+
+       for (rgn_idx = 0; rgn_idx < hpb->rgns_per_lu; rgn_idx++) {
+               struct ufshpb_region *rgn;
+
+               rgn = hpb->rgn_tbl + rgn_idx;
+               if (rgn->rgn_state != HPB_RGN_INACTIVE) {
+                       rgn->rgn_state = HPB_RGN_INACTIVE;
+
+                       ufshpb_destroy_subregion_tbl(hpb, rgn);
+               }
+
+               kvfree(rgn->srgn_tbl);
+       }
+
+       kvfree(hpb->rgn_tbl);
+}
+
+/* SYSFS functions */
+#define ufshpb_sysfs_attr_show_func(__name)                            \
+static ssize_t __name##_show(struct device *dev,                       \
+       struct device_attribute *attr, char *buf)                       \
+{                                                                      \
+       struct scsi_device *sdev = to_scsi_device(dev);                 \
+       struct ufshpb_lu *hpb = sdev->hostdata;                         \
+                                                                       \
+       if (!hpb)                                                       \
+               return -ENOENT;                                         \
+       return snprintf(buf, PAGE_SIZE, "%d\n",                         \
+                       atomic_read(&hpb->stats.__name));               \
+}                                                                      \
+\
+static DEVICE_ATTR_RO(__name)
+
+ufshpb_sysfs_attr_show_func(hit_cnt);
+ufshpb_sysfs_attr_show_func(miss_cnt);
+ufshpb_sysfs_attr_show_func(rb_noti_cnt);
+ufshpb_sysfs_attr_show_func(rb_active_cnt);
+ufshpb_sysfs_attr_show_func(rb_inactive_cnt);
+ufshpb_sysfs_attr_show_func(map_req_cnt);
+
+static struct attribute *hpb_dev_attrs[] = {
+       &dev_attr_hit_cnt.attr,
+       &dev_attr_miss_cnt.attr,
+       &dev_attr_rb_noti_cnt.attr,
+       &dev_attr_rb_active_cnt.attr,
+       &dev_attr_rb_inactive_cnt.attr,
+       &dev_attr_map_req_cnt.attr,
+       NULL,
+};
+
+struct attribute_group ufs_sysfs_hpb_stat_group = {
+       .name = "hpb_sysfs",
+       .attrs = hpb_dev_attrs,
+};
+
+static void ufshpb_stat_init(struct ufshpb_lu *hpb)
+{
+       atomic_set(&hpb->stats.hit_cnt, 0);
+       atomic_set(&hpb->stats.miss_cnt, 0);
+       atomic_set(&hpb->stats.rb_noti_cnt, 0);
+       atomic_set(&hpb->stats.rb_active_cnt, 0);
+       atomic_set(&hpb->stats.rb_inactive_cnt, 0);
+       atomic_set(&hpb->stats.map_req_cnt, 0);
+}
+
+static int ufshpb_lu_hpb_init(struct ufs_hba *hba, struct ufshpb_lu *hpb,
+                             struct ufshpb_dev_info *hpb_dev_info)
+{
+       int ret;
+
+       spin_lock_init(&hpb->hpb_state_lock);
+
+       ret = ufshpb_alloc_region_tbl(hba, hpb);
+
+       ufshpb_stat_init(hpb);
+
+       return 0;
+}
+
+static struct ufshpb_lu *ufshpb_alloc_hpb_lu(struct ufs_hba *hba, int lun,
+                                    struct ufshpb_dev_info *hpb_dev_info,
+                                    struct ufshpb_lu_info *hpb_lu_info)
+{
+       struct ufshpb_lu *hpb;
+       int ret;
+
+       hpb = kzalloc(sizeof(struct ufshpb_lu), GFP_KERNEL);
+       if (!hpb)
+               return NULL;
+
+       hpb->lun = lun;
+
+       ufshpb_init_lu_parameter(hba, hpb, hpb_dev_info, hpb_lu_info);
+
+       ret = ufshpb_lu_hpb_init(hba, hpb, hpb_dev_info);
+       if (ret) {
+               dev_err(hba->dev, "hpb lu init failed. ret %d", ret);
+               goto release_hpb;
+       }
+
+       return hpb;
+
+release_hpb:
+       kfree(hpb);
+       return NULL;
+}
+
+static void ufshpb_issue_hpb_reset_query(struct ufs_hba *hba)
+{
+       int err = 0;
+       int retries;
+
+       for (retries = 0; retries < HPB_RESET_REQ_RETRIES; retries++) {
+               err = ufshcd_query_flag(hba, UPIU_QUERY_OPCODE_SET_FLAG,
+                               QUERY_FLAG_IDN_HPB_RESET, 0, NULL);
+               if (err)
+                       dev_dbg(hba->dev,
+                               "%s: failed with error %d, retries %d\n",
+                               __func__, err, retries);
+               else
+                       return;
+       }
+
+       if (err)
+               dev_err(hba->dev,
+                       "%s setting fHpbReset flag failed with error %d\n",
+                       __func__, err);
+}
+
+static void ufshpb_check_hpb_reset_query(struct ufs_hba *hba)
+{
+       int err = 0;
+       bool flag_res = true;
+       int try;
+
+       /* wait for the device to complete HPB reset query */
+       for (try = 0; try < HPB_RESET_REQ_RETRIES; try++) {
+               dev_info(hba->dev,
+                       "%s start flag reset polling %d times\n",
+                       __func__, try);
+
+               /* Poll fHpbReset flag to be cleared */
+               err = ufshcd_query_flag(hba, UPIU_QUERY_OPCODE_READ_FLAG,
+                               QUERY_FLAG_IDN_HPB_RESET, 0, &flag_res);
+
+               if (err) {
+                       dev_err(hba->dev,
+                               "%s reading fHpbReset flag failed with error 
%d\n",
+                               __func__, err);
+                       return;
+               }
+
+               usleep_range(1000, 1100);
+
+               if (!flag_res)
+                       return;
+       }
+       if (flag_res) {
+               dev_err(hba->dev,
+                       "%s fHpbReset was not cleared by the device\n",
+                       __func__);
+       }
+}
+
+void ufshpb_reset(struct ufs_hba *hba)
+{
+       struct ufshpb_lu *hpb;
+       struct scsi_device *sdev;
+
+       shost_for_each_device(sdev, hba->host) {
+               hpb = sdev->hostdata;
+               if (!hpb)
+                       continue;
+
+               if (ufshpb_get_state(hpb) != HPB_RESET)
+                       continue;
+
+               ufshpb_set_state(hpb, HPB_PRESENT);
+       }
+}
+
+void ufshpb_reset_host(struct ufs_hba *hba)
+{
+       struct ufshpb_lu *hpb;
+       struct scsi_device *sdev;
+
+       dev_info(hba->dev, "ufshpb run reset_host");
+       shost_for_each_device(sdev, hba->host) {
+               hpb = sdev->hostdata;
+               if (!hpb)
+                       continue;
+
+               if (ufshpb_get_state(hpb) != HPB_PRESENT)
+                       continue;
+               ufshpb_set_state(hpb, HPB_RESET);
+       }
+}
+
+void ufshpb_suspend(struct ufs_hba *hba)
+{
+       struct ufshpb_lu *hpb;
+       struct scsi_device *sdev;
+
+       dev_info(hba->dev, "ufshpb goto suspend");
+
+       shost_for_each_device(sdev, hba->host) {
+               hpb = sdev->hostdata;
+               if (!hpb)
+                       continue;
+
+               if (ufshpb_get_state(hpb) != HPB_PRESENT)
+                       continue;
+               ufshpb_set_state(hpb, HPB_SUSPEND);
+       }
+}
+
+void ufshpb_resume(struct ufs_hba *hba)
+{
+       struct ufshpb_lu *hpb;
+       struct scsi_device *sdev;
+
+       dev_info(hba->dev, "ufshpb resume");
+
+       shost_for_each_device(sdev, hba->host) {
+               hpb = sdev->hostdata;
+               if (!hpb)
+                       continue;
+
+               if ((ufshpb_get_state(hpb) != HPB_PRESENT) &&
+                   (ufshpb_get_state(hpb) != HPB_SUSPEND))
+                       continue;
+               ufshpb_set_state(hpb, HPB_PRESENT);
+       }
+}
+
+static int ufshpb_read_desc(struct ufs_hba *hba, u8 desc_id, u8 desc_index,
+                           u8 selector, u8 *desc_buf)
+{
+       int err = 0;
+       int size;
+
+       ufshcd_map_desc_id_to_length(hba, desc_id, &size);
+
+       pm_runtime_get_sync(hba->dev);
+
+       err = ufshcd_query_descriptor_retry(hba, UPIU_QUERY_OPCODE_READ_DESC,
+                                           desc_id, desc_index,
+                                           selector,
+                                           desc_buf, &size);
+       if (err)
+               dev_err(hba->dev, "read desc failed: %d, id %d, idx %d\n",
+                       err, desc_id, desc_index);
+
+       pm_runtime_put_sync(hba->dev);
+
+       return err;
+}
+
+static int ufshpb_get_lu_info(struct ufs_hba *hba, int lun,
+                                   struct ufshpb_lu_info *hpb_lu_info,
+                                   u8 *desc_buf)
+{
+       u16 max_active_rgns;
+       u8 lu_enable;
+       int ret;
+
+       ret = ufshpb_read_desc(hba, QUERY_DESC_IDN_UNIT, lun, 0, desc_buf);
+       if (ret) {
+               dev_err(hba->dev,
+                       "%s: idn: %d lun: %d  query request failed",
+                       __func__, QUERY_DESC_IDN_UNIT, lun);
+               return ret;
+       }
+
+       lu_enable = desc_buf[UNIT_DESC_PARAM_LU_ENABLE];
+       if (lu_enable != LU_ENABLED_HPB_FUNC)
+               return -ENODEV;
+
+       max_active_rgns = get_unaligned_be16(
+                       desc_buf + UNIT_DESC_HPB_LU_MAX_ACTIVE_REGIONS);
+       if (!max_active_rgns) {
+               dev_err(hba->dev,
+                       "lun %d wrong number of max active regions\n", lun);
+               return -ENODEV;
+       }
+
+       hpb_lu_info->num_blocks = get_unaligned_be64(
+                       desc_buf + UNIT_DESC_PARAM_LOGICAL_BLK_COUNT);
+       hpb_lu_info->pinned_start = get_unaligned_be16(
+                       desc_buf + UNIT_DESC_HPB_LU_PIN_REGION_START_OFFSET);
+       hpb_lu_info->num_pinned = get_unaligned_be16(
+                       desc_buf + UNIT_DESC_HPB_LU_NUM_PIN_REGIONS);
+       hpb_lu_info->max_active_rgns = max_active_rgns;
+
+       return 0;
+}
+
+static void ufshpb_hpb_lu_prepared(struct ufs_hba *hba)
+{
+       struct ufshpb_lu *hpb;
+       struct scsi_device *sdev;
+
+       ufshpb_check_hpb_reset_query(hba);
+
+       shost_for_each_device(sdev, hba->host) {
+               hpb = sdev->hostdata;
+               if (!hpb)
+                       continue;
+
+               dev_info(hba->dev, "set state to present\n");
+               ufshpb_set_state(hpb, HPB_PRESENT);
+       }
+}
+
+void ufshpb_init_hpb_lu(struct ufs_hba *hba, struct scsi_device *sdev)
+{
+       struct ufshpb_lu *hpb;
+       int ret;
+       struct ufshpb_lu_info hpb_lu_info = { 0 };
+       int lun = sdev->lun;
+       char *desc_buf;
+
+       desc_buf = kzalloc(QUERY_DESC_MAX_SIZE, GFP_KERNEL);
+       if (!desc_buf)
+               return;
+
+       if (lun >= hba->dev_info.max_lu_supported)
+               goto release_desc_buf;
+
+       ret = ufshpb_get_lu_info(hba, lun, &hpb_lu_info, desc_buf);
+       if (ret)
+               goto release_desc_buf;
+
+       hpb = ufshpb_alloc_hpb_lu(hba, lun, &hba->ufshpb_dev,
+                                 &hpb_lu_info);
+       if (!hpb)
+               goto release_desc_buf;
+
+       hpb->sdev_ufs_lu = sdev;
+       sdev->hostdata = hpb;
+
+release_desc_buf:
+       kfree(desc_buf);
+
+       /* All LUs are initialized */
+       if (atomic_dec_and_test(&hba->ufshpb_dev.slave_conf_cnt))
+               ufshpb_hpb_lu_prepared(hba);
+}
+
+void ufshpb_get_geo_info(struct ufs_hba *hba, u8 *geo_buf)
+{
+       struct ufshpb_dev_info *hpb_dev_info = &hba->ufshpb_dev;
+       int hpb_device_max_active_rgns = 0;
+       int hpb_num_lu;
+
+       hpb_dev_info->hpb_disabled = false;
+
+       hpb_num_lu = geo_buf[GEOMETRY_DESC_PARAM_HPB_NUMBER_LU];
+       if (hpb_num_lu == 0) {
+               dev_err(hba->dev, "No HPB LU supported\n");
+               hpb_dev_info->hpb_disabled = true;
+               return;
+       }
+
+       hpb_dev_info->rgn_size = geo_buf[GEOMETRY_DESC_PARAM_HPB_REGION_SIZE];
+       hpb_dev_info->srgn_size = 
geo_buf[GEOMETRY_DESC_PARAM_HPB_SUBREGION_SIZE];
+       hpb_device_max_active_rgns =
+               get_unaligned_be16(geo_buf +
+                       GEOMETRY_DESC_PARAM_HPB_MAX_ACTIVE_REGS);
+
+       if (hpb_dev_info->rgn_size == 0 || hpb_dev_info->srgn_size == 0 ||
+           hpb_device_max_active_rgns == 0) {
+               dev_err(hba->dev, "No HPB supported device\n");
+               hpb_dev_info->hpb_disabled = true;
+               return;
+       }
+}
+
+void ufshpb_get_dev_info(struct ufs_hba *hba, u8 *desc_buf)
+{
+       struct ufshpb_dev_info *hpb_dev_info = &hba->ufshpb_dev;
+       int version;
+       u8 hpb_mode;
+
+       hpb_mode = desc_buf[DEVICE_DESC_PARAM_HPB_CONTROL];
+       if (hpb_mode == HPB_HOST_CONTROL) {
+               dev_err(hba->dev, "%s: host control mode is not supported.\n",
+                       __func__);
+               hpb_dev_info->hpb_disabled = true;
+               return;
+       }
+
+       version = get_unaligned_be16(desc_buf + DEVICE_DESC_PARAM_HPB_VER);
+       if (version != HPB_SUPPORT_VERSION) {
+               dev_err(hba->dev, "%s: HPB %x version is not supported.\n",
+                       __func__, version);
+               hpb_dev_info->hpb_disabled = true;
+               return;
+       }
+
+       /*
+        * Get the number of user logical unit to check whether all
+        * scsi_device finish initialization
+        */
+       hpb_dev_info->num_lu = desc_buf[DEVICE_DESC_PARAM_NUM_LU];
+}
+
+void ufshpb_init(struct ufs_hba *hba)
+{
+       struct ufshpb_dev_info *hpb_dev_info = &hba->ufshpb_dev;
+
+       if (!ufshpb_is_allowed(hba))
+               return;
+
+       atomic_set(&hpb_dev_info->slave_conf_cnt, hpb_dev_info->num_lu);
+       ufshpb_issue_hpb_reset_query(hba);
+}
+
+void ufshpb_destroy_lu(struct ufs_hba *hba, struct scsi_device *sdev)
+{
+       struct ufshpb_lu *hpb = sdev->hostdata;
+
+       if (!hpb)
+               return;
+
+       ufshpb_set_state(hpb, HPB_FAILED);
+
+       sdev = hpb->sdev_ufs_lu;
+       sdev->hostdata = NULL;
+
+       ufshpb_destroy_region_tbl(hpb);
+
+       list_del_init(&hpb->list_hpb_lu);
+
+       kfree(hpb);
+}
+
+void ufshpb_remove(struct ufs_hba *hba)
+{
+}
+
+MODULE_AUTHOR("Yongmyong Lee <ymhungry....@samsung.com>");
+MODULE_AUTHOR("Jinyoung Choi <j-young.c...@samsung.com>");
+MODULE_DESCRIPTION("UFS Host Performance Booster Driver");
diff --git a/drivers/scsi/ufs/ufshpb.h b/drivers/scsi/ufs/ufshpb.h
new file mode 100644
index 000000000000..b6a26db6e797
--- /dev/null
+++ b/drivers/scsi/ufs/ufshpb.h
@@ -0,0 +1,174 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Universal Flash Storage Host Performance Booster
+ *
+ * Copyright (C) 2017-2018 Samsung Electronics Co., Ltd.
+ *
+ * Authors:
+ *     Yongmyung Lee <ymhungry....@samsung.com>
+ *     Jinyoung Choi <j-young.c...@samsung.com>
+ */
+
+#ifndef _UFSHPB_H_
+#define _UFSHPB_H_
+
+/* hpb response UPIU macro */
+#define MAX_ACTIVE_NUM                         2
+#define MAX_INACTIVE_NUM                       2
+#define HPB_RSP_NONE                           0x00
+#define HPB_RSP_REQ_REGION_UPDATE              0x01
+#define HPB_RSP_DEV_RESET                      0x02
+#define DEV_DATA_SEG_LEN                       0x14
+#define DEV_SENSE_SEG_LEN                      0x12
+#define DEV_DES_TYPE                           0x80
+#define DEV_ADDITIONAL_LEN                     0x10
+
+/* hpb map & entries macro */
+#define HPB_RGN_SIZE_UNIT                      512
+#define HPB_ENTRY_BLOCK_SIZE                   4096
+#define HPB_ENTRY_SIZE                         0x8
+#define PINNED_NOT_SET                         U32_MAX
+
+/* hpb support chunk size */
+#define HPB_MULTI_CHUNK_HIGH                   1
+
+/* hpb vender defined opcode */
+#define UFSHPB_READ                            0xF8
+#define UFSHPB_READ_BUFFER                     0xF9
+#define UFSHPB_READ_BUFFER_ID                  0x01
+#define HPB_READ_BUFFER_CMD_LENGTH             10
+#define LU_ENABLED_HPB_FUNC                    0x02
+
+#define SDEV_WAIT_TIMEOUT                      (10 * HZ)
+#define MAP_REQ_TIMEOUT                        (30 * HZ)
+#define HPB_RESET_REQ_RETRIES                  10
+#define HPB_RESET_REQ_MSLEEP                   2
+
+#define HPB_SUPPORT_VERSION                    0x100
+
+enum UFSHPB_MODE {
+       HPB_HOST_CONTROL,
+       HPB_DEVICE_CONTROL,
+};
+
+enum UFSHPB_STATE {
+       HPB_PRESENT = 1,
+       HPB_SUSPEND,
+       HPB_FAILED,
+       HPB_RESET,
+};
+
+enum HPB_RGN_STATE {
+       HPB_RGN_INACTIVE,
+       HPB_RGN_ACTIVE,
+       /* pinned regions are always active */
+       HPB_RGN_PINNED,
+};
+
+enum HPB_SRGN_STATE {
+       HPB_SRGN_UNUSED,
+       HPB_SRGN_INVALID,
+       HPB_SRGN_VALID,
+       HPB_SRGN_ISSUED,
+};
+
+/**
+ * struct ufshpb_lu_info - UFSHPB logical unit related info
+ * @num_blocks: the number of logical block
+ * @pinned_start: the start region number of pinned region
+ * @num_pinned: the number of pinned regions
+ * @max_active_rgns: maximum number of active regions
+ */
+struct ufshpb_lu_info {
+       int num_blocks;
+       int pinned_start;
+       int num_pinned;
+       int max_active_rgns;
+};
+
+struct ufshpb_subregion {
+       enum HPB_SRGN_STATE srgn_state;
+       int rgn_idx;
+       int srgn_idx;
+};
+
+struct ufshpb_region {
+       struct ufshpb_subregion *srgn_tbl;
+       enum HPB_RGN_STATE rgn_state;
+       int rgn_idx;
+       int srgn_cnt;
+};
+
+struct ufshpb_stats {
+       atomic_t hit_cnt;
+       atomic_t miss_cnt;
+       atomic_t rb_noti_cnt;
+       atomic_t rb_active_cnt;
+       atomic_t rb_inactive_cnt;
+       atomic_t map_req_cnt;
+};
+
+struct ufshpb_lu {
+       int lun;
+       struct scsi_device *sdev_ufs_lu;
+       struct ufshpb_region *rgn_tbl;
+
+       spinlock_t hpb_state_lock;
+       atomic_t hpb_state; /* hpb_state_lock */
+
+       /* pinned region information */
+       u32 lu_pinned_start;
+       u32 lu_pinned_end;
+
+       /* HPB related configuration */
+       u32 rgns_per_lu;
+       u32 srgns_per_lu;
+       int srgns_per_rgn;
+       u32 srgn_mem_size;
+       u32 entries_per_rgn_mask;
+       u32 entries_per_rgn_shift;
+       u32 entries_per_srgn;
+       u32 entries_per_srgn_mask;
+       u32 entries_per_srgn_shift;
+       u32 pages_per_srgn;
+
+       struct ufshpb_stats stats;
+
+       struct list_head list_hpb_lu;
+};
+
+struct ufs_hba;
+struct ufshcd_lrb;
+
+#ifndef CONFIG_SCSI_UFS_HPB
+static void ufshpb_resume(struct ufs_hba *hba) {}
+static void ufshpb_suspend(struct ufs_hba *hba) {}
+static void ufshpb_reset(struct ufs_hba *hba) {}
+static void ufshpb_reset_host(struct ufs_hba *hba) {}
+static void ufshpb_rsp_upiu(struct ufs_hba *hba, struct ufshcd_lrb *lrbp) {}
+static void ufshpb_prep(struct ufs_hba *hba, struct ufshcd_lrb *lrbp) {}
+static void ufshpb_remove(struct ufs_hba *hba) {}
+static void ufshpb_init(struct ufs_hba *hba) {}
+static void ufshpb_init_hpb_lu(struct ufs_hba *hba, struct scsi_device *sdev) 
{}
+static void ufshpb_destroy_lu(struct ufs_hba *hba, struct scsi_device *sdev) {}
+static bool ufshpb_is_allowed(struct ufs_hba *hba) { return false; }
+static void ufshpb_get_geo_info(struct ufs_hba *hba, u8 *geo_buf) {}
+static void ufshpb_get_dev_info(struct ufs_hba *hba, u8 *desc_buf) {}
+#else
+void ufshpb_resume(struct ufs_hba *hba);
+void ufshpb_suspend(struct ufs_hba *hba);
+void ufshpb_reset(struct ufs_hba *hba);
+void ufshpb_reset_host(struct ufs_hba *hba);
+void ufshpb_rsp_upiu(struct ufs_hba *hba, struct ufshcd_lrb *lrbp);
+void ufshpb_prep(struct ufs_hba *hba, struct ufshcd_lrb *lrbp);
+void ufshpb_init(struct ufs_hba *hba);
+void ufshpb_init_hpb_lu(struct ufs_hba *hba, struct scsi_device *sdev);
+void ufshpb_destroy_lu(struct ufs_hba *hba, struct scsi_device *sdev);
+void ufshpb_remove(struct ufs_hba *hba);
+bool ufshpb_is_allowed(struct ufs_hba *hba);
+void ufshpb_get_geo_info(struct ufs_hba *hba, u8 *geo_buf);
+void ufshpb_get_dev_info(struct ufs_hba *hba, u8 *desc_buf);
+extern struct attribute_group ufs_sysfs_hpb_stat_group;
+#endif
+
+#endif /* End of Header */
-- 
2.17.1


Reply via email to