[RESEND PATCH 4/4] scsi:ufs:add fbo functionality

From: Jiaming Li
Date: Wed Nov 02 2022 - 01:31:55 EST


From: lijiaming3 <lijiaming3@xxxxxxxxxx>

add fbo analysis and defrag function

We can send LBA info to the device as a comma separated string. Each
adjacent pair represents a range:<open-lba>,<close-lba>.
e.g. The LBA range of the file is 0x1234,0x3456;0x4567,0x5678
echo 0x1234,0x3456,0x4567,0x5678 > fbo_send_lba

Then you can instrcut the device to analyzes the file info use following
command:
echo 1 > fbo_operation_ctrl

Use the following cmd to view the fragmentation progress status
cat fbo_prog_state

After the value of "fbo_prog_state" changes from "1" to "2", it means
the fragment analyzes completed, ust the following cmd to check file's
LBA fragment state
cat fbo_lba_frag_state

The data format follows the structure specified by spec
===============================
00 02 00 00 00 00 00 00
00 00 12 34 00 22 23 07
00 00 45 67 00 22 23 00
===============================

The host then may instruct the device to execute optimization procedure to
improve the regression level
echo 2 > fbo_operation_ctrl

Use the following cmd to view the fragment optimization status, After the
value of "fbo_prog_state" changes to "3", it means the fragment
optimization completed
cat fbo_prog_state

Signed-off-by: lijiaming3 <lijiaming3@xxxxxxxxxx>
---
Documentation/ABI/testing/sysfs-driver-ufs | 64 ++++
drivers/ufs/core/ufsfbo.c | 399 +++++++++++++++++++++
drivers/ufs/core/ufshcd.c | 3 +
3 files changed, 466 insertions(+)

diff --git a/Documentation/ABI/testing/sysfs-driver-ufs b/Documentation/ABI/testing/sysfs-driver-ufs
index 63daccbf7a8d..3792a444d0e2 100644
--- a/Documentation/ABI/testing/sysfs-driver-ufs
+++ b/Documentation/ABI/testing/sysfs-driver-ufs
@@ -1775,3 +1775,67 @@ Description: This file shows the alignment requirement of UFS file-based
specifications - FBO extension.

The file is read only.
+
+What: /sys/class/scsi_device/*/device/fbo_dev_ctrl/fbo_support
+Date: November 2022
+Contact: li jiaming <lijiaming3@xxxxxxxxxx>
+Description: This file shows the support state of UFS file-based optimization.
+
+ The file is read only.
+
+What: /sys/class/scsi_device/*/device/fbo_dev_ctrl/fbo_prog_state
+Date: November 2022
+Contact: li jiaming <lijiaming3@xxxxxxxxxx>
+Description: This file shows the state of UFS file-based optimization. The
+ current driver implementation supports 5 states:
+
+ == ====================================================
+ 0h UFS FBO progress state is idle
+ 1h UFS FBO progress state is on-going
+ 2h UFS FBO progress state is complete analysis
+ 3h UFS FBO progress state is complete optimization
+ FF UFS FBO progress state is general error
+ == ====================================================
+ The file is read only.
+
+What: /sys/class/scsi_device/*/device/fbo_dev_ctrl/fbo_operation_ctrl
+Date: November 2022
+Contact: li jiaming <lijiaming3@xxxxxxxxxx>
+Description: This file controls the operation of UFS file-based optimization.
+ Echo different value to this file to make device perform different funcs.
+ The value are as follows
+ == ==============================================================
+ 0h Device shall stop FBO analysis and FBO optimization operation
+ 1h Start FBO analysis based on the current LBA range
+ 2h Start FBO optimization based on the current LBA range
+ == ==============================================================
+ The file is write only.
+
+What: /sys/class/scsi_device/*/device/fbo_dev_ctrl/fbo_exe_threshold
+Date: November 2022
+Contact: li jiaming <lijiaming3@xxxxxxxxxx>
+Description: This file shows and sets the execute level of UFS file-based
+ optimization. It means the device will do optimization operation for
+ the ranges which fragment level equal or greater than this value .The
+ value ranges from 0x0 to 0xA.
+
+What: /sys/class/scsi_device/*/device/fbo_dev_ctrl/fbo_send_lba
+Date: November 2022
+Contact: li jiaming <lijiaming3@xxxxxxxxxx>
+Description: This file provides an interface for host to send LBA ranges to
+ the device for UFS file-based optimization. First, we obtain the iNode
+ info of the file, which can be used to find out the corresponding block
+ address of the file, then add the offset of each partition to obtain the
+ LBA of the file. We can send LBA info to the device as a comma separated
+ string. Each adjacent pair represents a range:<open-lba>,<close-lba>.
+ e.g. The LBA range of the file is 0x1234,0x3456;0x4567,0x5678
+ echo 0x1234,0x3456,0x4567,0x5678 > fbo_send_lba
+
+ The file is write only.
+
+What: /sys/class/scsi_device/*/device/fbo_dev_ctrl/fbo_lba_frag_state
+Date: November 2022
+Contact: li jiaming <lijiaming3@xxxxxxxxxx>
+Description: This file provides an interface for host to obtain the LBA
+ ranges fragment state sent by read buffer command of UFS file-based
+ optimization. We should read this info when the FBO analysis is completed
diff --git a/drivers/ufs/core/ufsfbo.c b/drivers/ufs/core/ufsfbo.c
index 81326fd2fb3d..303b7b7f1a2c 100644
--- a/drivers/ufs/core/ufsfbo.c
+++ b/drivers/ufs/core/ufsfbo.c
@@ -14,6 +14,18 @@
#include <scsi/scsi_device.h>
#include <asm/unaligned.h>

+#define FBO_RW_BUF_HDR_SIZE 4
+#define FBO_RW_ENTRY_SIZE 8
+#define FBO_LBA_RANGE_LENGTH 4096
+
+enum UFSFBO_PROG_STATE {
+ FBO_PROG_IDLE = 0x0,
+ FBO_PROG_ON_GOING = 0x1,
+ FBO_PROG_ANALYSIS_COMPLETE = 0x2,
+ FBO_PROG_OPTIMIZATION_COMPLETE = 0x3,
+ FBO_PROG_INTERNAL_ERR = 0xff,
+};
+
/**
* struct ufsfbo_dev_info - FBO device related info
* @fbo_version: UFS file-based optimization Version
@@ -45,6 +57,393 @@ struct ufsfbo_ctrl {
int fbo_lba_cnt;
};

+static void ufsfbo_fill_rw_buffer(unsigned char *cdb, int size, u8 opcode)
+{
+ cdb[0] = opcode;
+ cdb[1] = 0x2;
+ cdb[2] = opcode == WRITE_BUFFER ? 0x1 : 0x2;
+ put_unaligned_be24(size, &cdb[6]);
+}
+
+static ssize_t fbo_support_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct scsi_device *sdev = to_scsi_device(dev);
+ struct ufs_hba *hba = shost_priv(sdev->host);
+ u8 val = 0;
+
+ if (hba->fbo_ctrl)
+ val = 1;
+
+ return sysfs_emit(buf, "%d\n", val);
+}
+static DEVICE_ATTR_RO(fbo_support);
+
+static int ufsfbo_get_fbo_prog_state(struct ufs_hba *hba, int *prog_state)
+{
+ int ret = 0, attr = -1;
+
+ down(&hba->host_sem);
+ if (!ufshcd_is_user_access_allowed(hba)) {
+ ret = -EBUSY;
+ goto out;
+ }
+ ufshcd_rpm_get_sync(hba);
+ ret = ufshcd_query_attr_retry(hba, UPIU_QUERY_OPCODE_READ_ATTR,
+ QUERY_ATTR_IDN_FBO_PROG_STATE, 0, 0, &attr);
+ ufshcd_rpm_put_sync(hba);
+ if (ret) {
+ pr_err("Query attr fbo prog state failed.");
+ goto out;
+ }
+
+ switch (attr) {
+ case 0x0:
+ case 0x1:
+ case 0x2:
+ case 0x3:
+ case 0xff:
+ *prog_state = attr;
+ break;
+ default:
+ pr_info("Unknown fbo prog state attr(%d)", attr);
+ ret = -EINVAL;
+ break;
+ }
+
+out:
+ up(&hba->host_sem);
+ return ret;
+}
+
+static ssize_t fbo_prog_state_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int fbo_prog_state;
+ struct scsi_device *sdev = to_scsi_device(dev);
+ struct ufs_hba *hba = shost_priv(sdev->host);
+
+ if (ufsfbo_get_fbo_prog_state(hba, &fbo_prog_state)) {
+ pr_err("Get fbo prog state failed.");
+ return -EINVAL;
+ }
+
+ return sysfs_emit(buf, "%d\n", fbo_prog_state);
+}
+static DEVICE_ATTR_RO(fbo_prog_state);
+
+static ssize_t fbo_operation_ctrl_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret = 0;
+ u32 val;
+ struct scsi_device *sdev = to_scsi_device(dev);
+ struct ufs_hba *hba = shost_priv(sdev->host);
+
+ if (kstrtouint(buf, 0, &val))
+ return -EINVAL;
+
+ down(&hba->host_sem);
+ if (!ufshcd_is_user_access_allowed(hba)) {
+ ret = -EBUSY;
+ goto out;
+ }
+
+ ufshcd_rpm_get_sync(hba);
+ ret = ufshcd_query_attr_retry(hba, UPIU_QUERY_OPCODE_WRITE_ATTR,
+ QUERY_ATTR_IDN_FBO_CONTROL, 0, 0, &val);
+ ufshcd_rpm_put_sync(hba);
+
+out:
+ up(&hba->host_sem);
+ return ret ? ret : count;
+}
+
+static DEVICE_ATTR_WO(fbo_operation_ctrl);
+
+static ssize_t fbo_exe_threshold_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct scsi_device *sdev = to_scsi_device(dev);
+ struct ufs_hba *hba = shost_priv(sdev->host);
+ struct ufsfbo_ctrl *fbo_ctrl = hba->fbo_ctrl;
+
+ return sysfs_emit(buf, "%d\n", fbo_ctrl->fbo_dev_info.fbo_exec_threshold);
+}
+
+static int ufsfbo_set_exe_level(struct ufs_hba *hba, u32 val)
+{
+ int ret = 0, fbo_prog_state = 0;
+
+ ret = ufsfbo_get_fbo_prog_state(hba, &fbo_prog_state);
+ if (ret) {
+ pr_err("Get fbo prog state failed.");
+ return -EINVAL;
+ }
+
+ if (fbo_prog_state == FBO_PROG_IDLE || fbo_prog_state == FBO_PROG_ANALYSIS_COMPLETE ||
+ fbo_prog_state == FBO_PROG_OPTIMIZATION_COMPLETE) {
+ down(&hba->host_sem);
+ if (!ufshcd_is_user_access_allowed(hba)) {
+ ret = -EBUSY;
+ goto out;
+ }
+ ufshcd_rpm_get_sync(hba);
+ ret = ufshcd_query_attr_retry(hba, UPIU_QUERY_OPCODE_WRITE_ATTR,
+ QUERY_ATTR_IDN_FBO_LEVEL_EXE, 0, 0, &val);
+ ufshcd_rpm_put_sync(hba);
+ } else {
+ pr_err("Illegal fbo prog state");
+ return -EINVAL;
+ }
+
+out:
+ up(&hba->host_sem);
+ return ret;
+}
+
+static ssize_t fbo_exe_threshold_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ u32 val;
+ int ret = 0;
+ struct scsi_device *sdev = to_scsi_device(dev);
+ struct ufs_hba *hba = shost_priv(sdev->host);
+ struct ufsfbo_ctrl *fbo_ctrl = hba->fbo_ctrl;
+
+ if (kstrtouint(buf, 0, &val))
+ return -EINVAL;
+
+ if (val < 0 || val > 10)
+ return -EINVAL;
+
+ ret = ufsfbo_set_exe_level(hba, val);
+ if (ret) {
+ pr_err("Set exec threshold failed.");
+ return -EINVAL;
+ }
+
+ fbo_ctrl->fbo_dev_info.fbo_exec_threshold = val;
+
+ return ret ? ret : count;
+}
+
+static DEVICE_ATTR_RW(fbo_exe_threshold);
+
+static int ufsfbo_issue_read_frag_level(struct scsi_device *sdev, char *buf, int para_len)
+{
+ int ret = 0;
+ unsigned char cdb[10] = {};
+ struct scsi_sense_hdr sshdr = {};
+
+ ufsfbo_fill_rw_buffer(cdb, para_len, READ_BUFFER);
+
+ ret = scsi_execute_req(sdev, cdb, DMA_FROM_DEVICE, buf, para_len,
+ &sshdr, msecs_to_jiffies(15000), 0, NULL);
+ if (ret)
+ pr_err("Read Buffer failed,sense key:0x%x;asc:0x%x;ascq:0x%x",
+ (int)sshdr.sense_key, (int)sshdr.asc, (int)sshdr.ascq);
+
+ return ret;
+}
+
+static ssize_t fbo_lba_frag_state_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int i, ret, count = 0;
+ int para_len = 0;
+ int vaild_body_size = 0;
+ char *fbo_read_buffer;
+ struct scsi_device *sdev = to_scsi_device(dev);
+ struct ufs_hba *hba = shost_priv(sdev->host);
+ struct ufsfbo_ctrl *fbo_ctrl = hba->fbo_ctrl;
+
+ fbo_read_buffer = kzalloc(FBO_LBA_RANGE_LENGTH, GFP_KERNEL);
+ if (!fbo_read_buffer)
+ return -ENOMEM;
+
+ para_len = FBO_RW_BUF_HDR_SIZE + FBO_RW_ENTRY_SIZE +
+ fbo_ctrl->fbo_lba_cnt * FBO_RW_ENTRY_SIZE;
+
+ ret = ufsfbo_issue_read_frag_level(sdev, fbo_read_buffer, para_len);
+ if (ret) {
+ pr_err("Get lba range level failed");
+ goto out;
+ }
+
+ /* we allocated 4k, but reading only the relevant ReadBuffer size */
+ vaild_body_size = FBO_RW_ENTRY_SIZE + (fbo_ctrl->fbo_lba_cnt * FBO_RW_ENTRY_SIZE);
+ for (i = 0; i < vaild_body_size; i++) {
+ count += snprintf(buf + count, PAGE_SIZE - count,
+ "%02x ", fbo_read_buffer[i + FBO_RW_BUF_HDR_SIZE]);
+ if (!((i + 1) % 8))
+ count += snprintf(buf + count, PAGE_SIZE - count, "\n");
+ }
+out:
+ kfree(fbo_read_buffer);
+ return ret ? ret : count;
+}
+
+static DEVICE_ATTR_RO(fbo_lba_frag_state);
+
+static int ufsfbo_check_lba_range_format(struct ufs_hba *hba, char *buf)
+{
+ char *p;
+ int lba_pairs = 0;
+ struct ufsfbo_ctrl *fbo_ctrl = hba->fbo_ctrl;
+
+ p = strstr(buf, ",");
+ if (!p || buf[strlen(buf) - 1] == ',') {
+ pr_err("Invalid lba range format, input lba range separated by ','");
+ return -EINVAL;
+ }
+
+ while (p) {
+ lba_pairs++;
+ p += 1;
+ p = strstr(p, ",");
+ }
+ /*
+ * The input buffer is a comma delimited pairs of LBAs: open,close,
+ * and so on. So there should be an even number of LBAs, and odd
+ * number of commas.
+ */
+ if (lba_pairs % 2)
+ lba_pairs++;
+ else
+ return -EINVAL;
+
+ if (lba_pairs / 2 > fbo_ctrl->fbo_dev_info.fbo_max_lrc)
+ return -EINVAL;
+
+ fbo_ctrl->fbo_lba_cnt = lba_pairs / 2;
+ return 0;
+}
+
+static int ufsfbo_parse_lba_list(struct ufs_hba *hba, char *buf, char *lba_buf)
+{
+ char *lba_ptr;
+ struct ufsfbo_ctrl *fbo_ctrl = hba->fbo_ctrl;
+ struct ufsfbo_dev_info *fbo_dev_info = &fbo_ctrl->fbo_dev_info;
+ u64 lba_range_tmp, start_lba, lba_len;
+ int len_index = 1, lba_info_offset = FBO_RW_BUF_HDR_SIZE + FBO_RW_ENTRY_SIZE;
+
+ lba_buf[5] = fbo_ctrl->fbo_lba_cnt;
+
+ while ((lba_ptr = strsep(&buf, ",")) != NULL) {
+ if (kstrtou64(lba_ptr, 16, &lba_range_tmp))
+ return -EINVAL;
+
+ if (len_index % 2) {
+ start_lba = lba_range_tmp;
+ put_unaligned_be32(start_lba, lba_buf + lba_info_offset);
+ } else {
+ if (lba_range_tmp < start_lba)
+ return -EINVAL;
+
+ lba_len = lba_range_tmp - start_lba + 1;
+ if (lba_len < fbo_dev_info->fbo_min_lrs ||
+ lba_len > fbo_dev_info->fbo_max_lrs)
+ return -EINVAL;
+
+ put_unaligned_be24(lba_len, lba_buf + lba_info_offset + 4);
+ lba_info_offset += FBO_RW_ENTRY_SIZE;
+ }
+ len_index++;
+ }
+
+ return 0;
+}
+
+static int ufsfbo_issue_lba_list_write(struct scsi_device *sdev, char *buf)
+{
+ int ret = 0;
+ struct ufs_hba *hba = shost_priv(sdev->host);
+ struct ufsfbo_ctrl *fbo_ctrl = hba->fbo_ctrl;
+ int fbo_lba_cnt = fbo_ctrl->fbo_lba_cnt;
+ struct scsi_sense_hdr sshdr = {};
+ char *buf_lba;
+ unsigned char cdb[10] = {};
+ int para_len = FBO_RW_BUF_HDR_SIZE + FBO_RW_ENTRY_SIZE + fbo_lba_cnt * FBO_RW_ENTRY_SIZE;
+
+ buf_lba = kzalloc(FBO_LBA_RANGE_LENGTH, GFP_KERNEL);
+ if (!buf_lba) {
+ ret = -ENOMEM;
+ return ret;
+ }
+
+ ret = ufsfbo_parse_lba_list(hba, buf, buf_lba);
+ if (ret) {
+ pr_err("Init buf_lba fail");
+ goto out;
+ }
+
+ ufsfbo_fill_rw_buffer(cdb, para_len, WRITE_BUFFER);
+
+ ret = scsi_execute_req(sdev, cdb, DMA_TO_DEVICE, buf_lba, para_len,
+ &sshdr, msecs_to_jiffies(15000), 0, NULL);
+ if (ret)
+ pr_err("Write Buffer failed,sense key:0x%x;asc:0x%x;ascq:0x%x",
+ (int)sshdr.sense_key, (int)sshdr.asc, (int)sshdr.ascq);
+
+out:
+ kfree(buf_lba);
+ return ret;
+}
+
+static ssize_t fbo_send_lba_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret = 0, fbo_prog_state = 0;
+ char *buf_ptr;
+ struct scsi_device *sdev = to_scsi_device(dev);
+ struct ufs_hba *hba = shost_priv(sdev->host);
+
+ if (!buf)
+ return -EINVAL;
+
+ buf_ptr = kstrdup(buf, GFP_KERNEL);
+ if (unlikely(!buf_ptr))
+ return -ENOMEM;
+
+ if (ufsfbo_check_lba_range_format(hba, buf_ptr))
+ goto out;
+
+ if (ufsfbo_get_fbo_prog_state(hba, &fbo_prog_state))
+ goto out;
+
+ if (fbo_prog_state == FBO_PROG_IDLE) {
+ ret = ufsfbo_issue_lba_list_write(sdev, buf_ptr);
+ } else {
+ ret = -EINVAL;
+ pr_err("Invalid fbo state");
+ }
+
+out:
+ kfree(buf_ptr);
+ return ret ? ret : count;
+}
+
+static DEVICE_ATTR_WO(fbo_send_lba);
+
+static struct attribute *fbo_dev_ctrl_attrs[] = {
+ &dev_attr_fbo_support.attr,
+ &dev_attr_fbo_prog_state.attr,
+ &dev_attr_fbo_operation_ctrl.attr,
+ &dev_attr_fbo_exe_threshold.attr,
+ &dev_attr_fbo_send_lba.attr,
+ &dev_attr_fbo_lba_frag_state.attr,
+ NULL,
+};
+
+const struct attribute_group ufs_sysfs_fbo_param_group = {
+ .name = "fbo_dev_ctrl",
+ .attrs = fbo_dev_ctrl_attrs,
+};
+
static int ufsfbo_get_dev_info(struct ufs_hba *hba, struct ufsfbo_ctrl *fbo_ctrl)
{
int ret;
diff --git a/drivers/ufs/core/ufshcd.c b/drivers/ufs/core/ufshcd.c
index f769fcb72392..410263e2fada 100644
--- a/drivers/ufs/core/ufshcd.c
+++ b/drivers/ufs/core/ufshcd.c
@@ -8294,6 +8294,9 @@ static const struct attribute_group *ufshcd_driver_groups[] = {
#ifdef CONFIG_SCSI_UFS_HPB
&ufs_sysfs_hpb_stat_group,
&ufs_sysfs_hpb_param_group,
+#endif
+#ifdef CONFIG_SCSI_UFS_FBO
+ &ufs_sysfs_fbo_param_group,
#endif
NULL,
};
--
2.38.1