scsi: ufs: core: Add WB buffer resize support

Follow JESD220G, support a WB buffer resize function through sysfs. The
host can obtain resize hint and resize status, and enable the resize
operation. Add three sysfs nodes:

	1. wb_resize_enable

	2. wb_resize_hint

	3. wb_resize_status

The detailed definition of the three nodes can be found in the sysfs
documentation.

Signed-off-by: Huan Tang <tanghuan@vivo.com>
Signed-off-by: Lu Hongfei <luhongfei@vivo.com>
Link: https://lore.kernel.org/r/20250411092924.1116-1-tanghuan@vivo.com
Reviewed-by: Bart Van Assche <bvanassche@acm.org>
Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
pull/982/merge
Huan Tang 2025-04-11 17:29:24 +08:00 committed by Martin K. Petersen
parent ae82eaf4ae
commit 500d4b742e
5 changed files with 234 additions and 1 deletions

View File

@ -1604,3 +1604,52 @@ Description:
prevent the UFS from frequently performing clock gating/ungating.
The attribute is read/write.
What: /sys/bus/platform/drivers/ufshcd/*/wb_resize_enable
What: /sys/bus/platform/devices/*.ufs/wb_resize_enable
Date: April 2025
Contact: Huan Tang <tanghuan@vivo.com>
Description:
The host can enable the WriteBooster buffer resize by setting this
attribute.
======== ======================================
idle There is no resize operation
decrease Decrease WriteBooster buffer size
increase Increase WriteBooster buffer size
======== ======================================
The file is write only.
What: /sys/bus/platform/drivers/ufshcd/*/attributes/wb_resize_hint
What: /sys/bus/platform/devices/*.ufs/attributes/wb_resize_hint
Date: April 2025
Contact: Huan Tang <tanghuan@vivo.com>
Description:
wb_resize_hint indicates hint information about which type of resize
for WriteBooster buffer is recommended by the device.
========= ======================================
keep Recommend keep the buffer size
decrease Recommend to decrease the buffer size
increase Recommend to increase the buffer size
========= ======================================
The file is read only.
What: /sys/bus/platform/drivers/ufshcd/*/attributes/wb_resize_status
What: /sys/bus/platform/devices/*.ufs/attributes/wb_resize_status
Date: April 2025
Contact: Huan Tang <tanghuan@vivo.com>
Description:
The host can check the resize operation status of the WriteBooster
buffer by reading this attribute.
================ ========================================
idle Resize operation is not issued
in_progress Resize operation in progress
complete_success Resize operation completed successfully
general_failure Resize operation general failure
================ ========================================
The file is read only.

View File

@ -57,6 +57,36 @@ static const char *ufs_hs_gear_to_string(enum ufs_hs_gear_tag gear)
}
}
static const char *ufs_wb_resize_hint_to_string(enum wb_resize_hint hint)
{
switch (hint) {
case WB_RESIZE_HINT_KEEP:
return "keep";
case WB_RESIZE_HINT_DECREASE:
return "decrease";
case WB_RESIZE_HINT_INCREASE:
return "increase";
default:
return "unknown";
}
}
static const char *ufs_wb_resize_status_to_string(enum wb_resize_status status)
{
switch (status) {
case WB_RESIZE_STATUS_IDLE:
return "idle";
case WB_RESIZE_STATUS_IN_PROGRESS:
return "in_progress";
case WB_RESIZE_STATUS_COMPLETE_SUCCESS:
return "complete_success";
case WB_RESIZE_STATUS_GENERAL_FAILURE:
return "general_failure";
default:
return "unknown";
}
}
static const char *ufshcd_uic_link_state_to_string(
enum uic_link_state state)
{
@ -411,6 +441,44 @@ static ssize_t wb_flush_threshold_store(struct device *dev,
return count;
}
static const char * const wb_resize_en_mode[] = {
[WB_RESIZE_EN_IDLE] = "idle",
[WB_RESIZE_EN_DECREASE] = "decrease",
[WB_RESIZE_EN_INCREASE] = "increase",
};
static ssize_t wb_resize_enable_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct ufs_hba *hba = dev_get_drvdata(dev);
int mode;
ssize_t res;
if (!ufshcd_is_wb_allowed(hba) || !hba->dev_info.wb_enabled
|| !hba->dev_info.b_presrv_uspc_en
|| !(hba->dev_info.ext_wb_sup & UFS_DEV_WB_BUF_RESIZE))
return -EOPNOTSUPP;
mode = sysfs_match_string(wb_resize_en_mode, buf);
if (mode < 0)
return -EINVAL;
down(&hba->host_sem);
if (!ufshcd_is_user_access_allowed(hba)) {
res = -EBUSY;
goto out;
}
ufshcd_rpm_get_sync(hba);
res = ufshcd_wb_set_resize_en(hba, mode);
ufshcd_rpm_put_sync(hba);
out:
up(&hba->host_sem);
return res < 0 ? res : count;
}
/**
* pm_qos_enable_show - sysfs handler to show pm qos enable value
* @dev: device associated with the UFS controller
@ -476,6 +544,7 @@ static DEVICE_ATTR_RW(auto_hibern8);
static DEVICE_ATTR_RW(wb_on);
static DEVICE_ATTR_RW(enable_wb_buf_flush);
static DEVICE_ATTR_RW(wb_flush_threshold);
static DEVICE_ATTR_WO(wb_resize_enable);
static DEVICE_ATTR_RW(rtc_update_ms);
static DEVICE_ATTR_RW(pm_qos_enable);
static DEVICE_ATTR_RO(critical_health);
@ -491,6 +560,7 @@ static struct attribute *ufs_sysfs_ufshcd_attrs[] = {
&dev_attr_wb_on.attr,
&dev_attr_enable_wb_buf_flush.attr,
&dev_attr_wb_flush_threshold.attr,
&dev_attr_wb_resize_enable.attr,
&dev_attr_rtc_update_ms.attr,
&dev_attr_pm_qos_enable.attr,
&dev_attr_critical_health.attr,
@ -1495,6 +1565,67 @@ static inline bool ufshcd_is_wb_attrs(enum attr_idn idn)
idn <= QUERY_ATTR_IDN_CURR_WB_BUFF_SIZE;
}
static int wb_read_resize_attrs(struct ufs_hba *hba,
enum attr_idn idn, u32 *attr_val)
{
u8 index = 0;
int ret;
if (!ufshcd_is_wb_allowed(hba) || !hba->dev_info.wb_enabled
|| !hba->dev_info.b_presrv_uspc_en
|| !(hba->dev_info.ext_wb_sup & UFS_DEV_WB_BUF_RESIZE))
return -EOPNOTSUPP;
down(&hba->host_sem);
if (!ufshcd_is_user_access_allowed(hba)) {
up(&hba->host_sem);
return -EBUSY;
}
index = ufshcd_wb_get_query_index(hba);
ufshcd_rpm_get_sync(hba);
ret = ufshcd_query_attr(hba, UPIU_QUERY_OPCODE_READ_ATTR,
idn, index, 0, attr_val);
ufshcd_rpm_put_sync(hba);
up(&hba->host_sem);
return ret;
}
static ssize_t wb_resize_hint_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct ufs_hba *hba = dev_get_drvdata(dev);
int ret;
u32 value;
ret = wb_read_resize_attrs(hba,
QUERY_ATTR_IDN_WB_BUF_RESIZE_HINT, &value);
if (ret)
return ret;
return sysfs_emit(buf, "%s\n", ufs_wb_resize_hint_to_string(value));
}
static DEVICE_ATTR_RO(wb_resize_hint);
static ssize_t wb_resize_status_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct ufs_hba *hba = dev_get_drvdata(dev);
int ret;
u32 value;
ret = wb_read_resize_attrs(hba,
QUERY_ATTR_IDN_WB_BUF_RESIZE_STATUS, &value);
if (ret)
return ret;
return sysfs_emit(buf, "%s\n", ufs_wb_resize_status_to_string(value));
}
static DEVICE_ATTR_RO(wb_resize_status);
#define UFS_ATTRIBUTE(_name, _uname) \
static ssize_t _name##_show(struct device *dev, \
struct device_attribute *attr, char *buf) \
@ -1568,6 +1699,8 @@ static struct attribute *ufs_sysfs_attributes[] = {
&dev_attr_wb_avail_buf.attr,
&dev_attr_wb_life_time_est.attr,
&dev_attr_wb_cur_buf.attr,
&dev_attr_wb_resize_hint.attr,
&dev_attr_wb_resize_status.attr,
NULL,
};

View File

@ -6080,6 +6080,21 @@ int ufshcd_wb_toggle_buf_flush(struct ufs_hba *hba, bool enable)
return ret;
}
int ufshcd_wb_set_resize_en(struct ufs_hba *hba, enum wb_resize_en en_mode)
{
int ret;
u8 index;
index = ufshcd_wb_get_query_index(hba);
ret = ufshcd_query_attr_retry(hba, UPIU_QUERY_OPCODE_WRITE_ATTR,
QUERY_ATTR_IDN_WB_BUF_RESIZE_EN, index, 0, &en_mode);
if (ret)
dev_err(hba->dev, "%s: Enable WB buf resize operation failed %d\n",
__func__, ret);
return ret;
}
static bool ufshcd_wb_presrv_usrspc_keep_vcc_on(struct ufs_hba *hba,
u32 avail_buf)
{
@ -8100,6 +8115,9 @@ static void ufshcd_wb_probe(struct ufs_hba *hba, const u8 *desc_buf)
*/
dev_info->wb_buffer_type = desc_buf[DEVICE_DESC_PARAM_WB_TYPE];
dev_info->ext_wb_sup = get_unaligned_be16(desc_buf +
DEVICE_DESC_PARAM_EXT_UFS_FEATURE_SUP);
dev_info->b_presrv_uspc_en =
desc_buf[DEVICE_DESC_PARAM_WB_PRESRV_USRSPC_EN];

View File

@ -180,7 +180,10 @@ enum attr_idn {
QUERY_ATTR_IDN_AVAIL_WB_BUFF_SIZE = 0x1D,
QUERY_ATTR_IDN_WB_BUFF_LIFE_TIME_EST = 0x1E,
QUERY_ATTR_IDN_CURR_WB_BUFF_SIZE = 0x1F,
QUERY_ATTR_IDN_TIMESTAMP = 0x30
QUERY_ATTR_IDN_TIMESTAMP = 0x30,
QUERY_ATTR_IDN_WB_BUF_RESIZE_HINT = 0x3C,
QUERY_ATTR_IDN_WB_BUF_RESIZE_EN = 0x3D,
QUERY_ATTR_IDN_WB_BUF_RESIZE_STATUS = 0x3E,
};
/* Descriptor idn for Query requests */
@ -289,6 +292,7 @@ enum device_desc_param {
DEVICE_DESC_PARAM_PRDCT_REV = 0x2A,
DEVICE_DESC_PARAM_HPB_VER = 0x40,
DEVICE_DESC_PARAM_HPB_CONTROL = 0x42,
DEVICE_DESC_PARAM_EXT_WB_SUP = 0x4D,
DEVICE_DESC_PARAM_EXT_UFS_FEATURE_SUP = 0x4F,
DEVICE_DESC_PARAM_WB_PRESRV_USRSPC_EN = 0x53,
DEVICE_DESC_PARAM_WB_TYPE = 0x54,
@ -383,6 +387,11 @@ enum {
UFSHCD_AMP = 3,
};
/* Possible values for wExtendedWriteBoosterSupport */
enum {
UFS_DEV_WB_BUF_RESIZE = BIT(0),
};
/* Possible values for dExtendedUFSFeaturesSupport */
enum {
UFS_DEV_HIGH_TEMP_NOTIF = BIT(4),
@ -454,6 +463,28 @@ enum ufs_ref_clk_freq {
REF_CLK_FREQ_INVAL = -1,
};
/* bWriteBoosterBufferResizeEn attribute */
enum wb_resize_en {
WB_RESIZE_EN_IDLE = 0,
WB_RESIZE_EN_DECREASE = 1,
WB_RESIZE_EN_INCREASE = 2,
};
/* bWriteBoosterBufferResizeHint attribute */
enum wb_resize_hint {
WB_RESIZE_HINT_KEEP = 0,
WB_RESIZE_HINT_DECREASE = 1,
WB_RESIZE_HINT_INCREASE = 2,
};
/* bWriteBoosterBufferResizeStatus attribute */
enum wb_resize_status {
WB_RESIZE_STATUS_IDLE = 0,
WB_RESIZE_STATUS_IN_PROGRESS = 1,
WB_RESIZE_STATUS_COMPLETE_SUCCESS = 2,
WB_RESIZE_STATUS_GENERAL_FAILURE = 3,
};
/* Query response result code */
enum {
QUERY_RESULT_SUCCESS = 0x00,
@ -578,6 +609,7 @@ struct ufs_dev_info {
bool wb_buf_flush_enabled;
u8 wb_dedicated_lu;
u8 wb_buffer_type;
u16 ext_wb_sup;
bool b_rpm_dev_flush_capable;
u8 b_presrv_uspc_en;

View File

@ -1468,6 +1468,7 @@ int ufshcd_advanced_rpmb_req_handler(struct ufs_hba *hba, struct utp_upiu_req *r
struct scatterlist *sg_list, enum dma_data_direction dir);
int ufshcd_wb_toggle(struct ufs_hba *hba, bool enable);
int ufshcd_wb_toggle_buf_flush(struct ufs_hba *hba, bool enable);
int ufshcd_wb_set_resize_en(struct ufs_hba *hba, enum wb_resize_en en_mode);
int ufshcd_suspend_prepare(struct device *dev);
int __ufshcd_suspend_prepare(struct device *dev, bool rpm_ok_for_spm);
void ufshcd_resume_complete(struct device *dev);