diff --git a/drivers/scsi/ufs/ufs.h b/drivers/scsi/ufs/ufs.h index e4b2c95350ef..7fe25d526dbe 100644 --- a/drivers/scsi/ufs/ufs.h +++ b/drivers/scsi/ufs/ufs.h @@ -156,6 +156,7 @@ enum ufs_desc_max_size { QUERY_DESC_STRING_MAX_SIZE = 0xFE, QUERY_DESC_GEOMETRY_MAZ_SIZE = 0x44, QUERY_DESC_POWER_MAX_SIZE = 0x62, + QUERY_DESC_HEALTH_MAX_SIZE = 0x25, QUERY_DESC_RFU_MAX_SIZE = 0x00, }; @@ -209,6 +210,16 @@ enum device_desc_param { DEVICE_DESC_PARAM_RTT_CAP = 0x1C, DEVICE_DESC_PARAM_FRQ_RTC = 0x1D, }; + +/* Health descriptor parameters offsets in bytes*/ +enum health_desc_param { + HEALTH_DESC_PARAM_LEN = 0x0, + HEALTH_DESC_PARAM_TYPE = 0x1, + HEALTH_DESC_PARAM_EOL_INFO = 0x2, + HEALTH_DESC_PARAM_LIFE_TIME_EST_A = 0x3, + HEALTH_DESC_PARAM_LIFE_TIME_EST_B = 0x4, +}; + /* * Logical Unit Write Protect * 00h: LU not write protected diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c index 433e93f08956..94edba9de9fc 100644 --- a/drivers/scsi/ufs/ufshcd.c +++ b/drivers/scsi/ufs/ufshcd.c @@ -43,6 +43,7 @@ #include #include #include +#include #include "ufshcd.h" #include "ufshci.h" @@ -249,6 +250,7 @@ static u32 ufs_query_desc_max_size[] = { QUERY_DESC_RFU_MAX_SIZE, QUERY_DESC_GEOMETRY_MAZ_SIZE, QUERY_DESC_POWER_MAX_SIZE, + QUERY_DESC_HEALTH_MAX_SIZE, QUERY_DESC_RFU_MAX_SIZE, }; @@ -9234,10 +9236,99 @@ static void ufshcd_add_spm_lvl_sysfs_nodes(struct ufs_hba *hba) dev_err(hba->dev, "Failed to create sysfs for spm_lvl\n"); } +static ssize_t ufs_sysfs_read_desc_param(struct ufs_hba *hba, + enum desc_idn desc_id, + u8 desc_index, + u8 param_offset, + u8 *sysfs_buf, + u8 param_size) +{ + u8 desc_buf[8] = {0}; + int ret; + + if (param_size > 8) + return -EINVAL; + + pm_runtime_get_sync(hba->dev); + ret = ufshcd_read_desc_param(hba, desc_id, desc_index, + param_offset, desc_buf, param_size); + pm_runtime_put_sync(hba->dev); + + if (ret) + return -EINVAL; + switch (param_size) { + case 1: + ret = snprintf(sysfs_buf, PAGE_SIZE, "0x%02X\n", *desc_buf); + break; + case 2: + ret = snprintf(sysfs_buf, PAGE_SIZE, "0x%04X\n", + get_unaligned_be16(desc_buf)); + break; + case 4: + ret = snprintf(sysfs_buf, PAGE_SIZE, "0x%08X\n", + get_unaligned_be32(desc_buf)); + break; + case 8: + ret = snprintf(sysfs_buf, PAGE_SIZE, "0x%016llX\n", + get_unaligned_be64(desc_buf)); + break; + } + + return ret; +} + + +#define UFS_DESC_PARAM(_name, _puname, _duname, _size) \ + static ssize_t _name##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + struct ufs_hba *hba = dev_get_drvdata(dev); \ + return ufs_sysfs_read_desc_param(hba, QUERY_DESC_IDN_##_duname, \ + 0, _duname##_DESC_PARAM##_puname, buf, _size); \ +} \ +static DEVICE_ATTR_RO(_name) + +#define UFS_HEALTH_DESC_PARAM(_name, _uname, _size) \ + UFS_DESC_PARAM(_name, _uname, HEALTH, _size) + +UFS_HEALTH_DESC_PARAM(eol_info, _EOL_INFO, 1); +UFS_HEALTH_DESC_PARAM(life_time_estimation_a, _LIFE_TIME_EST_A, 1); +UFS_HEALTH_DESC_PARAM(life_time_estimation_b, _LIFE_TIME_EST_B, 1); + +static struct attribute *ufs_sysfs_health_descriptor[] = { + &dev_attr_eol_info.attr, + &dev_attr_life_time_estimation_a.attr, + &dev_attr_life_time_estimation_b.attr, + NULL, +}; + +static const struct attribute_group ufs_sysfs_health_descriptor_group = { + .name = "health_descriptor", + .attrs = ufs_sysfs_health_descriptor, +}; + +static const struct attribute_group *ufs_sysfs_groups[] = { + &ufs_sysfs_health_descriptor_group, + NULL, +}; + + +static void ufshcd_add_desc_sysfs_nodes(struct device *dev) +{ + int ret; + + ret = sysfs_create_groups(&dev->kobj, ufs_sysfs_groups); + if (ret) + dev_err(dev, + "%s: sysfs groups creation failed (err = %d)\n", + __func__, ret); +} + static inline void ufshcd_add_sysfs_nodes(struct ufs_hba *hba) { ufshcd_add_rpm_lvl_sysfs_nodes(hba); ufshcd_add_spm_lvl_sysfs_nodes(hba); + ufshcd_add_desc_sysfs_nodes(hba->dev); } static void ufshcd_shutdown_clkscaling(struct ufs_hba *hba) diff --git a/include/uapi/scsi/ufs/ufs.h b/include/uapi/scsi/ufs/ufs.h index cd82b760bd92..9da634db9e9f 100644 --- a/include/uapi/scsi/ufs/ufs.h +++ b/include/uapi/scsi/ufs/ufs.h @@ -51,7 +51,8 @@ enum desc_idn { QUERY_DESC_IDN_RFU_1 = 0x6, QUERY_DESC_IDN_GEOMETRY = 0x7, QUERY_DESC_IDN_POWER = 0x8, - QUERY_DESC_IDN_RFU_2 = 0x9, + QUERY_DESC_IDN_HEALTH = 0x9, + QUERY_DESC_IDN_RFU_2 = 0x0A, QUERY_DESC_IDN_MAX, };