diff mbox series

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

Message ID 20221102053058.21021-5-lijiaming3@xiaomi.corp-partner.google.com
State New
Headers show
Series Implement File-Based optimization functionality | expand

Commit Message

Jiaming Li Nov. 2, 2022, 5:30 a.m. UTC
From: lijiaming3 <lijiaming3@xiaomi.com>

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@xiaomi.com>
---
 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 mbox series

Patch

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@xiaomi.com>
+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@xiaomi.com>
+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@xiaomi.com>
+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@xiaomi.com>
+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@xiaomi.com>
+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@xiaomi.com>
+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,
 };