Skip to content

Commit

Permalink
scsi: ufs: core: Add device level exception support
Browse files Browse the repository at this point in the history
The ufs device JEDEC specification version 4.1 adds support for the
device level exception events. To support this new device level
exception feature, expose two new sysfs nodes below to provide the user
space access to the device level exception information.

/sys/bus/platform/drivers/ufshcd/*/device_lvl_exception_count
/sys/bus/platform/drivers/ufshcd/*/device_lvl_exception_id

The device_lvl_exception_count sysfs node reports the number of device
level exceptions that have occurred since the last time this variable is
reset. Writing a value of 0 will reset it.  The device_lvl_exception_id
reports the exception ID which is the qDeviceLevelExceptionID attribute
of the device JEDEC specifications version 4.1 and later. The user space
application can query these sysfs nodes to get more information about
the device level exception.

Signed-off-by: Bao D. Nguyen <quic_nguyenb@quicinc.com>
Link: https://lore.kernel.org/r/6278d7c125b2f0cf5056f4a647a4b9c1fdd24fc7.1743198325.git.quic_nguyenb@quicinc.com
Reviewed-by: Peter Wang <peter.wang@mediatek.com>
Reviewed-by: Bart Van Assche <bvanassche@acm.org>
Reviewed-by: Arthur Simchaev <arthur.simchaev@sandisk.com>
Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
  • Loading branch information
Bao D. Nguyen authored and Martin K. Petersen committed Apr 3, 2025
1 parent bdab404 commit 1fd2e77
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 1 deletion.
32 changes: 32 additions & 0 deletions Documentation/ABI/testing/sysfs-driver-ufs
Original file line number Diff line number Diff line change
Expand Up @@ -1604,3 +1604,35 @@ Description:
prevent the UFS from frequently performing clock gating/ungating.

The attribute is read/write.

What: /sys/bus/platform/drivers/ufshcd/*/device_lvl_exception_count
What: /sys/bus/platform/devices/*.ufs/device_lvl_exception_count
Date: March 2025
Contact: Bao D. Nguyen <quic_nguyenb@quicinc.com>
Description:
This attribute is applicable to ufs devices compliant to the
JEDEC specifications version 4.1 or later. The
device_lvl_exception_count is a counter indicating the number of
times the device level exceptions have occurred since the last
time this variable is reset. Writing a 0 value to this
attribute will reset the device_lvl_exception_count. If the
device_lvl_exception_count reads a positive value, the user
application should read the device_lvl_exception_id attribute to
know more information about the exception.

The attribute is read/write.

What: /sys/bus/platform/drivers/ufshcd/*/device_lvl_exception_id
What: /sys/bus/platform/devices/*.ufs/device_lvl_exception_id
Date: March 2025
Contact: Bao D. Nguyen <quic_nguyenb@quicinc.com>
Description:
Reading the device_lvl_exception_id returns the
qDeviceLevelExceptionID attribute of the ufs device JEDEC
specification version 4.1. The definition of the
qDeviceLevelExceptionID is the ufs device vendor specific
implementation. Refer to the device manufacturer datasheet for
more information on the meaning of the qDeviceLevelExceptionID
attribute value.

The attribute is read only.
54 changes: 54 additions & 0 deletions drivers/ufs/core/ufs-sysfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,56 @@ static ssize_t critical_health_show(struct device *dev,
return sysfs_emit(buf, "%d\n", hba->critical_health_count);
}

static ssize_t device_lvl_exception_count_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct ufs_hba *hba = dev_get_drvdata(dev);

if (hba->dev_info.wspecversion < 0x410)
return -EOPNOTSUPP;

return sysfs_emit(buf, "%u\n", atomic_read(&hba->dev_lvl_exception_count));
}

static ssize_t device_lvl_exception_count_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct ufs_hba *hba = dev_get_drvdata(dev);
unsigned int value;

if (kstrtouint(buf, 0, &value))
return -EINVAL;

/* the only supported usecase is to reset the dev_lvl_exception_count */
if (value)
return -EINVAL;

atomic_set(&hba->dev_lvl_exception_count, 0);

return count;
}

static ssize_t device_lvl_exception_id_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct ufs_hba *hba = dev_get_drvdata(dev);
u64 exception_id;
int err;

ufshcd_rpm_get_sync(hba);
err = ufshcd_read_device_lvl_exception_id(hba, &exception_id);
ufshcd_rpm_put_sync(hba);

if (err)
return err;

hba->dev_lvl_exception_id = exception_id;
return sysfs_emit(buf, "%llu\n", exception_id);
}

static DEVICE_ATTR_RW(rpm_lvl);
static DEVICE_ATTR_RO(rpm_target_dev_state);
static DEVICE_ATTR_RO(rpm_target_link_state);
Expand All @@ -479,6 +529,8 @@ static DEVICE_ATTR_RW(wb_flush_threshold);
static DEVICE_ATTR_RW(rtc_update_ms);
static DEVICE_ATTR_RW(pm_qos_enable);
static DEVICE_ATTR_RO(critical_health);
static DEVICE_ATTR_RW(device_lvl_exception_count);
static DEVICE_ATTR_RO(device_lvl_exception_id);

static struct attribute *ufs_sysfs_ufshcd_attrs[] = {
&dev_attr_rpm_lvl.attr,
Expand All @@ -494,6 +546,8 @@ static struct attribute *ufs_sysfs_ufshcd_attrs[] = {
&dev_attr_rtc_update_ms.attr,
&dev_attr_pm_qos_enable.attr,
&dev_attr_critical_health.attr,
&dev_attr_device_lvl_exception_count.attr,
&dev_attr_device_lvl_exception_id.attr,
NULL
};

Expand Down
1 change: 1 addition & 0 deletions drivers/ufs/core/ufshcd-priv.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ int ufshcd_exec_raw_upiu_cmd(struct ufs_hba *hba,
enum query_opcode desc_op);

int ufshcd_wb_toggle(struct ufs_hba *hba, bool enable);
int ufshcd_read_device_lvl_exception_id(struct ufs_hba *hba, u64 *exception_id);

/* Wrapper functions for safely calling variant operations */
static inline const char *ufshcd_get_var_name(struct ufs_hba *hba)
Expand Down
59 changes: 59 additions & 0 deletions drivers/ufs/core/ufshcd.c
Original file line number Diff line number Diff line change
Expand Up @@ -5998,6 +5998,42 @@ static void ufshcd_bkops_exception_event_handler(struct ufs_hba *hba)
__func__, err);
}

int ufshcd_read_device_lvl_exception_id(struct ufs_hba *hba, u64 *exception_id)
{
struct utp_upiu_query_v4_0 *upiu_resp;
struct ufs_query_req *request = NULL;
struct ufs_query_res *response = NULL;
int err;

if (hba->dev_info.wspecversion < 0x410)
return -EOPNOTSUPP;

ufshcd_hold(hba);
mutex_lock(&hba->dev_cmd.lock);

ufshcd_init_query(hba, &request, &response,
UPIU_QUERY_OPCODE_READ_ATTR,
QUERY_ATTR_IDN_DEV_LVL_EXCEPTION_ID, 0, 0);

request->query_func = UPIU_QUERY_FUNC_STANDARD_READ_REQUEST;

err = ufshcd_exec_dev_cmd(hba, DEV_CMD_TYPE_QUERY, QUERY_REQ_TIMEOUT);

if (err) {
dev_err(hba->dev, "%s: failed to read device level exception %d\n",
__func__, err);
goto out;
}

upiu_resp = (struct utp_upiu_query_v4_0 *)response;
*exception_id = get_unaligned_be64(&upiu_resp->osf3);
out:
mutex_unlock(&hba->dev_cmd.lock);
ufshcd_release(hba);

return err;
}

static int __ufshcd_wb_toggle(struct ufs_hba *hba, bool set, enum flag_idn idn)
{
u8 index;
Expand Down Expand Up @@ -6223,6 +6259,11 @@ static void ufshcd_exception_event_handler(struct work_struct *work)
sysfs_notify(&hba->dev->kobj, NULL, "critical_health");
}

if (status & hba->ee_drv_mask & MASK_EE_DEV_LVL_EXCEPTION) {
atomic_inc(&hba->dev_lvl_exception_count);
sysfs_notify(&hba->dev->kobj, NULL, "device_lvl_exception_count");
}

ufs_debugfs_exception_event(hba, status);
}

Expand Down Expand Up @@ -8122,6 +8163,22 @@ static void ufshcd_temp_notif_probe(struct ufs_hba *hba, const u8 *desc_buf)
}
}

static void ufshcd_device_lvl_exception_probe(struct ufs_hba *hba, u8 *desc_buf)
{
u32 ext_ufs_feature;

if (hba->dev_info.wspecversion < 0x410)
return;

ext_ufs_feature = get_unaligned_be32(desc_buf +
DEVICE_DESC_PARAM_EXT_UFS_FEATURE_SUP);
if (!(ext_ufs_feature & UFS_DEV_LVL_EXCEPTION_SUP))
return;

atomic_set(&hba->dev_lvl_exception_count, 0);
ufshcd_enable_ee(hba, MASK_EE_DEV_LVL_EXCEPTION);
}

static void ufshcd_set_rtt(struct ufs_hba *hba)
{
struct ufs_dev_info *dev_info = &hba->dev_info;
Expand Down Expand Up @@ -8322,6 +8379,8 @@ static int ufs_get_device_desc(struct ufs_hba *hba)

ufs_init_rtc(hba, desc_buf);

ufshcd_device_lvl_exception_probe(hba, desc_buf);

/*
* ufshcd_read_string_desc returns size of the string
* reset the error value
Expand Down
5 changes: 4 additions & 1 deletion include/ufs/ufs.h
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,8 @@ 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_DEV_LVL_EXCEPTION_ID = 0x34,
};

/* Descriptor idn for Query requests */
Expand Down Expand Up @@ -390,6 +391,7 @@ enum {
UFS_DEV_EXT_TEMP_NOTIF = BIT(6),
UFS_DEV_HPB_SUPPORT = BIT(7),
UFS_DEV_WRITE_BOOSTER_SUP = BIT(8),
UFS_DEV_LVL_EXCEPTION_SUP = BIT(12),
};
#define UFS_DEV_HPB_SUPPORT_VERSION 0x310

Expand Down Expand Up @@ -419,6 +421,7 @@ enum {
MASK_EE_TOO_LOW_TEMP = BIT(4),
MASK_EE_WRITEBOOSTER_EVENT = BIT(5),
MASK_EE_PERFORMANCE_THROTTLING = BIT(6),
MASK_EE_DEV_LVL_EXCEPTION = BIT(7),
MASK_EE_HEALTH_CRITICAL = BIT(9),
};
#define MASK_EE_URGENT_TEMP (MASK_EE_TOO_HIGH_TEMP | MASK_EE_TOO_LOW_TEMP)
Expand Down
5 changes: 5 additions & 0 deletions include/ufs/ufshcd.h
Original file line number Diff line number Diff line change
Expand Up @@ -968,6 +968,9 @@ enum ufshcd_mcq_opr {
* @pm_qos_req: PM QoS request handle
* @pm_qos_enabled: flag to check if pm qos is enabled
* @critical_health_count: count of critical health exceptions
* @dev_lvl_exception_count: count of device level exceptions since last reset
* @dev_lvl_exception_id: vendor specific information about the
* device level exception event.
*/
struct ufs_hba {
void __iomem *mmio_base;
Expand Down Expand Up @@ -1138,6 +1141,8 @@ struct ufs_hba {
bool pm_qos_enabled;

int critical_health_count;
atomic_t dev_lvl_exception_count;
u64 dev_lvl_exception_id;
};

/**
Expand Down

0 comments on commit 1fd2e77

Please sign in to comment.