Probe the dExtendedUFSFeaturesSupport register for the device's
temperature notification support, and if supported, add a hw monitor
device.
Signed-off-by: Avri Altman <[email protected]>
---
drivers/scsi/ufs/Kconfig | 10 ++
drivers/scsi/ufs/Makefile | 1 +
drivers/scsi/ufs/ufs-hwmon.c | 180 +++++++++++++++++++++++++++++++++++
drivers/scsi/ufs/ufs.h | 6 ++
drivers/scsi/ufs/ufshcd.c | 27 ++++++
drivers/scsi/ufs/ufshcd.h | 18 ++++
6 files changed, 242 insertions(+)
create mode 100644 drivers/scsi/ufs/ufs-hwmon.c
diff --git a/drivers/scsi/ufs/Kconfig b/drivers/scsi/ufs/Kconfig
index 432df76e6318..b930f29fc375 100644
--- a/drivers/scsi/ufs/Kconfig
+++ b/drivers/scsi/ufs/Kconfig
@@ -199,3 +199,13 @@ config SCSI_UFS_FAULT_INJECTION
help
Enable fault injection support in the UFS driver. This makes it easier
to test the UFS error handler and abort handler.
+
+config SCSI_UFS_HWMON
+ bool "UFS Temperature Notification"
+ depends on SCSI_UFSHCD && HWMON
+ help
+ This provides support for UFS hardware monitoring. If enabled,
+ a hardware monitoring device will be created for the UFS device.
+
+ If unsure, say N.
+
diff --git a/drivers/scsi/ufs/Makefile b/drivers/scsi/ufs/Makefile
index c407da9b5171..966048875b50 100644
--- a/drivers/scsi/ufs/Makefile
+++ b/drivers/scsi/ufs/Makefile
@@ -10,6 +10,7 @@ ufshcd-core-$(CONFIG_SCSI_UFS_BSG) += ufs_bsg.o
ufshcd-core-$(CONFIG_SCSI_UFS_CRYPTO) += ufshcd-crypto.o
ufshcd-core-$(CONFIG_SCSI_UFS_HPB) += ufshpb.o
ufshcd-core-$(CONFIG_SCSI_UFS_FAULT_INJECTION) += ufs-fault-injection.o
+ufshcd-core-$(CONFIG_SCSI_UFS_HWMON) += ufs-hwmon.o
obj-$(CONFIG_SCSI_UFS_DWC_TC_PCI) += tc-dwc-g210-pci.o ufshcd-dwc.o tc-dwc-g210.o
obj-$(CONFIG_SCSI_UFS_DWC_TC_PLATFORM) += tc-dwc-g210-pltfrm.o ufshcd-dwc.o tc-dwc-g210.o
diff --git a/drivers/scsi/ufs/ufs-hwmon.c b/drivers/scsi/ufs/ufs-hwmon.c
new file mode 100644
index 000000000000..159678ce89ce
--- /dev/null
+++ b/drivers/scsi/ufs/ufs-hwmon.c
@@ -0,0 +1,180 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * UFS hardware monitoring support
+ * Copyright (c) 2021, Western Digital Corporation
+ */
+
+#include <linux/hwmon.h>
+
+#include "ufshcd.h"
+
+struct ufs_hwmon_data {
+ struct ufs_hba *hba;
+ u8 mask;
+};
+
+static bool ufs_temp_enabled(struct ufs_hba *hba, struct ufs_hwmon_data *data)
+{
+ u32 ee_mask;
+
+ if (ufshcd_query_attr(hba, UPIU_QUERY_OPCODE_READ_ATTR,
+ QUERY_ATTR_IDN_EE_CONTROL, 0, 0, &ee_mask))
+ return false;
+
+ return (data->mask & ee_mask & MASK_EE_TOO_HIGH_TEMP) ||
+ (data->mask & ee_mask & MASK_EE_TOO_LOW_TEMP);
+}
+
+static bool ufs_temp_valid(struct ufs_hba *hba, struct ufs_hwmon_data *data,
+ enum attr_idn idn, u32 value)
+{
+ return (idn == QUERY_ATTR_IDN_CASE_ROUGH_TEMP && value >= 1 &&
+ value <= 250 && ufs_temp_enabled(hba, data)) ||
+ (idn == QUERY_ATTR_IDN_HIGH_TEMP_BOUND && value >= 100 &&
+ value <= 250) ||
+ (idn == QUERY_ATTR_IDN_LOW_TEMP_BOUND && value >= 1 &&
+ value <= 80);
+}
+
+static int ufs_get_temp(struct ufs_hba *hba, struct ufs_hwmon_data *data,
+ enum attr_idn idn)
+{
+ u32 value;
+
+ if (ufshcd_query_attr(hba, UPIU_QUERY_OPCODE_READ_ATTR, idn, 0, 0,
+ &value))
+ return 0;
+
+ if (ufs_temp_valid(hba, data, idn, value))
+ return value - 80;
+
+ return 0;
+}
+
+static int ufs_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ struct ufs_hwmon_data *data = dev_get_drvdata(dev);
+ struct ufs_hba *hba = data->hba;
+ int err = 0;
+ bool get_temp = true;
+
+ if (type != hwmon_temp)
+ return 0;
+
+ down(&hba->host_sem);
+
+ if (!ufshcd_is_user_access_allowed(hba)) {
+ up(&hba->host_sem);
+ return -EBUSY;
+ }
+
+ ufshcd_rpm_get_sync(hba);
+
+ switch (attr) {
+ case hwmon_temp_enable:
+ *val = ufs_temp_enabled(hba, data);
+ get_temp = false;
+
+ break;
+ case hwmon_temp_max_alarm:
+ *val = ufs_get_temp(hba, data, QUERY_ATTR_IDN_HIGH_TEMP_BOUND);
+
+ break;
+ case hwmon_temp_min_alarm:
+ *val = ufs_get_temp(hba, data, QUERY_ATTR_IDN_LOW_TEMP_BOUND);
+
+ break;
+ case hwmon_temp_input:
+ *val = ufs_get_temp(hba, data, QUERY_ATTR_IDN_CASE_ROUGH_TEMP);
+
+ break;
+ default:
+ err = -EOPNOTSUPP;
+
+ break;
+ }
+
+ ufshcd_rpm_put_sync(hba);
+
+ up(&hba->host_sem);
+
+ if (get_temp && !err && *val == 0)
+ err = -EINVAL;
+
+ return err;
+}
+
+static umode_t ufs_hwmon_is_visible(const void *_data,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ if (type != hwmon_temp)
+ return 0;
+
+ switch (attr) {
+ case hwmon_temp_enable:
+ case hwmon_temp_max_alarm:
+ case hwmon_temp_min_alarm:
+ case hwmon_temp_input:
+ return 0444;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static const struct hwmon_channel_info *ufs_hwmon_info[] = {
+ HWMON_CHANNEL_INFO(temp, HWMON_T_ENABLE | HWMON_T_INPUT |
+ HWMON_T_MIN_ALARM | HWMON_T_MAX_ALARM),
+ NULL
+};
+
+static const struct hwmon_ops ufs_hwmon_ops = {
+ .is_visible = ufs_hwmon_is_visible,
+ .read = ufs_hwmon_read,
+};
+
+static const struct hwmon_chip_info ufs_hwmon_hba_info = {
+ .ops = &ufs_hwmon_ops,
+ .info = ufs_hwmon_info,
+};
+
+int ufs_hwmon_probe(struct ufs_hba *hba, u8 mask)
+{
+ struct device *dev = hba->dev;
+ struct ufs_hwmon_data *data;
+ struct device *hwmon;
+
+ data = kzalloc(sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return 0;
+
+ data->hba = hba;
+ data->mask = mask;
+
+ hwmon = hwmon_device_register_with_info(dev, "ufs",
+ data, &ufs_hwmon_hba_info,
+ NULL);
+ if (IS_ERR(hwmon)) {
+ dev_warn(dev, "Failed to instantiate hwmon device\n");
+ kfree(data);
+ return PTR_ERR(hwmon);
+ }
+
+ hba->hwmon_device = hwmon;
+
+ return 0;
+}
+
+void ufs_hwmon_remove(struct ufs_hba *hba)
+{
+ if (hba->hwmon_device) {
+ struct ufs_hwmon_data *data =
+ dev_get_drvdata(hba->hwmon_device);
+
+ hwmon_device_unregister(hba->hwmon_device);
+ hba->hwmon_device = NULL;
+ kfree(data);
+ }
+}
diff --git a/drivers/scsi/ufs/ufs.h b/drivers/scsi/ufs/ufs.h
index 8c6b38b1b142..171b27be7b1d 100644
--- a/drivers/scsi/ufs/ufs.h
+++ b/drivers/scsi/ufs/ufs.h
@@ -152,6 +152,9 @@ enum attr_idn {
QUERY_ATTR_IDN_PSA_STATE = 0x15,
QUERY_ATTR_IDN_PSA_DATA_SIZE = 0x16,
QUERY_ATTR_IDN_REF_CLK_GATING_WAIT_TIME = 0x17,
+ QUERY_ATTR_IDN_CASE_ROUGH_TEMP = 0x18,
+ QUERY_ATTR_IDN_HIGH_TEMP_BOUND = 0x19,
+ QUERY_ATTR_IDN_LOW_TEMP_BOUND = 0x1A,
QUERY_ATTR_IDN_WB_FLUSH_STATUS = 0x1C,
QUERY_ATTR_IDN_AVAIL_WB_BUFF_SIZE = 0x1D,
QUERY_ATTR_IDN_WB_BUFF_LIFE_TIME_EST = 0x1E,
@@ -338,6 +341,9 @@ enum {
/* Possible values for dExtendedUFSFeaturesSupport */
enum {
+ UFS_DEV_LOW_TEMP_NOTIF = BIT(4),
+ UFS_DEV_HIGH_TEMP_NOTIF = BIT(5),
+ UFS_DEV_EXT_TEMP_NOTIF = BIT(6),
UFS_DEV_HPB_SUPPORT = BIT(7),
UFS_DEV_WRITE_BOOSTER_SUP = BIT(8),
};
diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c
index 3841ab49f556..fc995bf1f296 100644
--- a/drivers/scsi/ufs/ufshcd.c
+++ b/drivers/scsi/ufs/ufshcd.c
@@ -7469,6 +7469,31 @@ static void ufshcd_wb_probe(struct ufs_hba *hba, u8 *desc_buf)
hba->caps &= ~UFSHCD_CAP_WB_EN;
}
+static void ufshcd_temp_notif_probe(struct ufs_hba *hba, u8 *desc_buf)
+{
+ struct ufs_dev_info *dev_info = &hba->dev_info;
+ u32 ext_ufs_feature;
+ u8 mask = 0;
+
+ if (!(hba->caps & UFSHCD_CAP_TEMP_NOTIF) ||
+ dev_info->wspecversion < 0x300)
+ return;
+
+ ext_ufs_feature = get_unaligned_be32(desc_buf +
+ DEVICE_DESC_PARAM_EXT_UFS_FEATURE_SUP);
+
+ if (ext_ufs_feature & UFS_DEV_LOW_TEMP_NOTIF)
+ mask |= MASK_EE_TOO_LOW_TEMP;
+
+ if (ext_ufs_feature & UFS_DEV_HIGH_TEMP_NOTIF)
+ mask |= MASK_EE_TOO_HIGH_TEMP;
+
+ if (mask) {
+ ufshcd_enable_ee(hba, mask);
+ ufs_hwmon_probe(hba, mask);
+ }
+}
+
void ufshcd_fixup_dev_quirks(struct ufs_hba *hba, struct ufs_dev_fix *fixups)
{
struct ufs_dev_fix *f;
@@ -7564,6 +7589,8 @@ static int ufs_get_device_desc(struct ufs_hba *hba)
ufshcd_wb_probe(hba, desc_buf);
+ ufshcd_temp_notif_probe(hba, desc_buf);
+
/*
* ufshcd_read_string_desc returns size of the string
* reset the error value
diff --git a/drivers/scsi/ufs/ufshcd.h b/drivers/scsi/ufs/ufshcd.h
index 52ea6f350b18..9bc434c4ae71 100644
--- a/drivers/scsi/ufs/ufshcd.h
+++ b/drivers/scsi/ufs/ufshcd.h
@@ -653,6 +653,12 @@ enum ufshcd_caps {
* in order to exit DeepSleep state.
*/
UFSHCD_CAP_DEEPSLEEP = 1 << 10,
+
+ /*
+ * This capability allows the host controller driver to use temperature
+ * notification if it is supported by the UFS device.
+ */
+ UFSHCD_CAP_TEMP_NOTIF = 1 << 11,
};
struct ufs_hba_variant_params {
@@ -789,6 +795,10 @@ struct ufs_hba {
struct scsi_device *sdev_ufs_device;
struct scsi_device *sdev_rpmb;
+#ifdef CONFIG_SCSI_UFS_HWMON
+ struct device *hwmon_device;
+#endif
+
enum ufs_dev_pwr_mode curr_dev_pwr_mode;
enum uic_link_state uic_link_state;
/* Desired UFS power management level during runtime PM */
@@ -1049,6 +1059,14 @@ static inline u8 ufshcd_wb_get_query_index(struct ufs_hba *hba)
return 0;
}
+#ifdef CONFIG_SCSI_UFS_HWMON
+int ufs_hwmon_probe(struct ufs_hba *hba, u8 mask);
+void ufs_hwmon_remove(struct ufs_hba *hba);
+#else
+static inline int ufs_hwmon_probe(struct ufs_hba *hba, u8 mask) { return 0; }
+static inline void nvme_hwmon_remove(struct ufs_hba *hba) {}
+#endif
+
#ifdef CONFIG_PM
extern int ufshcd_runtime_suspend(struct device *dev);
extern int ufshcd_runtime_resume(struct device *dev);
--
2.17.1
On 9/8/21 11:34 PM, Avri Altman wrote:
> +static bool ufs_temp_enabled(struct ufs_hba *hba, struct ufs_hwmon_data *data)
> +{
> + u32 ee_mask;
> +
> + if (ufshcd_query_attr(hba, UPIU_QUERY_OPCODE_READ_ATTR,
> + QUERY_ATTR_IDN_EE_CONTROL, 0, 0, &ee_mask))
> + return false;
> +
> + return (data->mask & ee_mask & MASK_EE_TOO_HIGH_TEMP) ||
> + (data->mask & ee_mask & MASK_EE_TOO_LOW_TEMP);
> +}
The above function uses data->mask but not data so it's probably better to pass
data->mask directly as the second argument.
> +static bool ufs_temp_valid(struct ufs_hba *hba, struct ufs_hwmon_data *data,
> + enum attr_idn idn, u32 value)
> +{
> + return (idn == QUERY_ATTR_IDN_CASE_ROUGH_TEMP && value >= 1 &&
> + value <= 250 && ufs_temp_enabled(hba, data)) ||
> + (idn == QUERY_ATTR_IDN_HIGH_TEMP_BOUND && value >= 100 &&
> + value <= 250) ||
> + (idn == QUERY_ATTR_IDN_LOW_TEMP_BOUND && value >= 1 &&
> + value <= 80);
> +}
Same comment for this function - if the above comment is addressed 'mask' can be
passed as an argument instead of 'data'.
> +static int ufs_get_temp(struct ufs_hba *hba, struct ufs_hwmon_data *data,
> + enum attr_idn idn)
> +{
> + u32 value;
> +
> + if (ufshcd_query_attr(hba, UPIU_QUERY_OPCODE_READ_ATTR, idn, 0, 0,
> + &value))
> + return 0;
> +
> + if (ufs_temp_valid(hba, data, idn, value))
> + return value - 80;
> +
> + return 0;
> +}
Same comment here.
> +void ufs_hwmon_remove(struct ufs_hba *hba)
> +{
> + if (hba->hwmon_device) {
> + struct ufs_hwmon_data *data =
> + dev_get_drvdata(hba->hwmon_device);
> +
> + hwmon_device_unregister(hba->hwmon_device);
> + hba->hwmon_device = NULL;
> + kfree(data);
> + }
> +}
Please use the "early return" style since that is the most widely used style
in the Linux kernel (if (!hba->hwmon_device) return).
Thanks,
Bart.
On Thu, Sep 09, 2021 at 09:34:43AM +0300, Avri Altman wrote:
> Probe the dExtendedUFSFeaturesSupport register for the device's
> temperature notification support, and if supported, add a hw monitor
> device.
>
> Signed-off-by: Avri Altman <[email protected]>
> ---
> drivers/scsi/ufs/Kconfig | 10 ++
> drivers/scsi/ufs/Makefile | 1 +
> drivers/scsi/ufs/ufs-hwmon.c | 180 +++++++++++++++++++++++++++++++++++
> drivers/scsi/ufs/ufs.h | 6 ++
> drivers/scsi/ufs/ufshcd.c | 27 ++++++
> drivers/scsi/ufs/ufshcd.h | 18 ++++
> 6 files changed, 242 insertions(+)
> create mode 100644 drivers/scsi/ufs/ufs-hwmon.c
>
> diff --git a/drivers/scsi/ufs/Kconfig b/drivers/scsi/ufs/Kconfig
> index 432df76e6318..b930f29fc375 100644
> --- a/drivers/scsi/ufs/Kconfig
> +++ b/drivers/scsi/ufs/Kconfig
> @@ -199,3 +199,13 @@ config SCSI_UFS_FAULT_INJECTION
> help
> Enable fault injection support in the UFS driver. This makes it easier
> to test the UFS error handler and abort handler.
> +
> +config SCSI_UFS_HWMON
> + bool "UFS Temperature Notification"
> + depends on SCSI_UFSHCD && HWMON
> + help
> + This provides support for UFS hardware monitoring. If enabled,
> + a hardware monitoring device will be created for the UFS device.
> +
> + If unsure, say N.
> +
> diff --git a/drivers/scsi/ufs/Makefile b/drivers/scsi/ufs/Makefile
> index c407da9b5171..966048875b50 100644
> --- a/drivers/scsi/ufs/Makefile
> +++ b/drivers/scsi/ufs/Makefile
> @@ -10,6 +10,7 @@ ufshcd-core-$(CONFIG_SCSI_UFS_BSG) += ufs_bsg.o
> ufshcd-core-$(CONFIG_SCSI_UFS_CRYPTO) += ufshcd-crypto.o
> ufshcd-core-$(CONFIG_SCSI_UFS_HPB) += ufshpb.o
> ufshcd-core-$(CONFIG_SCSI_UFS_FAULT_INJECTION) += ufs-fault-injection.o
> +ufshcd-core-$(CONFIG_SCSI_UFS_HWMON) += ufs-hwmon.o
>
> obj-$(CONFIG_SCSI_UFS_DWC_TC_PCI) += tc-dwc-g210-pci.o ufshcd-dwc.o tc-dwc-g210.o
> obj-$(CONFIG_SCSI_UFS_DWC_TC_PLATFORM) += tc-dwc-g210-pltfrm.o ufshcd-dwc.o tc-dwc-g210.o
> diff --git a/drivers/scsi/ufs/ufs-hwmon.c b/drivers/scsi/ufs/ufs-hwmon.c
> new file mode 100644
> index 000000000000..159678ce89ce
> --- /dev/null
> +++ b/drivers/scsi/ufs/ufs-hwmon.c
> @@ -0,0 +1,180 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * UFS hardware monitoring support
> + * Copyright (c) 2021, Western Digital Corporation
> + */
> +
> +#include <linux/hwmon.h>
> +
> +#include "ufshcd.h"
> +
> +struct ufs_hwmon_data {
> + struct ufs_hba *hba;
> + u8 mask;
> +};
> +
> +static bool ufs_temp_enabled(struct ufs_hba *hba, struct ufs_hwmon_data *data)
> +{
> + u32 ee_mask;
> +
> + if (ufshcd_query_attr(hba, UPIU_QUERY_OPCODE_READ_ATTR,
> + QUERY_ATTR_IDN_EE_CONTROL, 0, 0, &ee_mask))
> + return false;
> +
> + return (data->mask & ee_mask & MASK_EE_TOO_HIGH_TEMP) ||
> + (data->mask & ee_mask & MASK_EE_TOO_LOW_TEMP);
> +}
> +
> +static bool ufs_temp_valid(struct ufs_hba *hba, struct ufs_hwmon_data *data,
> + enum attr_idn idn, u32 value)
> +{
> + return (idn == QUERY_ATTR_IDN_CASE_ROUGH_TEMP && value >= 1 &&
> + value <= 250 && ufs_temp_enabled(hba, data)) ||
> + (idn == QUERY_ATTR_IDN_HIGH_TEMP_BOUND && value >= 100 &&
> + value <= 250) ||
> + (idn == QUERY_ATTR_IDN_LOW_TEMP_BOUND && value >= 1 &&
> + value <= 80);
> +}
> +
> +static int ufs_get_temp(struct ufs_hba *hba, struct ufs_hwmon_data *data,
> + enum attr_idn idn)
> +{
> + u32 value;
> +
> + if (ufshcd_query_attr(hba, UPIU_QUERY_OPCODE_READ_ATTR, idn, 0, 0,
> + &value))
> + return 0;
> +
> + if (ufs_temp_valid(hba, data, idn, value))
> + return value - 80;
> +
> + return 0;
> +}
> +
> +static int ufs_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
> + u32 attr, int channel, long *val)
> +{
> + struct ufs_hwmon_data *data = dev_get_drvdata(dev);
> + struct ufs_hba *hba = data->hba;
> + int err = 0;
> + bool get_temp = true;
> +
> + if (type != hwmon_temp)
> + return 0;
> +
> + down(&hba->host_sem);
> +
> + if (!ufshcd_is_user_access_allowed(hba)) {
> + up(&hba->host_sem);
> + return -EBUSY;
> + }
> +
> + ufshcd_rpm_get_sync(hba);
> +
> + switch (attr) {
> + case hwmon_temp_enable:
> + *val = ufs_temp_enabled(hba, data);
> + get_temp = false;
> +
> + break;
> + case hwmon_temp_max_alarm:
> + *val = ufs_get_temp(hba, data, QUERY_ATTR_IDN_HIGH_TEMP_BOUND);
> +
> + break;
> + case hwmon_temp_min_alarm:
> + *val = ufs_get_temp(hba, data, QUERY_ATTR_IDN_LOW_TEMP_BOUND);
> +
> + break;
> + case hwmon_temp_input:
> + *val = ufs_get_temp(hba, data, QUERY_ATTR_IDN_CASE_ROUGH_TEMP);
> +
> + break;
> + default:
> + err = -EOPNOTSUPP;
> +
> + break;
> + }
> +
> + ufshcd_rpm_put_sync(hba);
> +
> + up(&hba->host_sem);
> +
> + if (get_temp && !err && *val == 0)
> + err = -EINVAL;
> +
> + return err;
> +}
> +
> +static umode_t ufs_hwmon_is_visible(const void *_data,
> + enum hwmon_sensor_types type,
> + u32 attr, int channel)
> +{
> + if (type != hwmon_temp)
> + return 0;
> +
> + switch (attr) {
> + case hwmon_temp_enable:
> + case hwmon_temp_max_alarm:
> + case hwmon_temp_min_alarm:
> + case hwmon_temp_input:
> + return 0444;
> + default:
> + break;
> + }
> + return 0;
> +}
> +
> +static const struct hwmon_channel_info *ufs_hwmon_info[] = {
> + HWMON_CHANNEL_INFO(temp, HWMON_T_ENABLE | HWMON_T_INPUT |
> + HWMON_T_MIN_ALARM | HWMON_T_MAX_ALARM),
> + NULL
> +};
> +
> +static const struct hwmon_ops ufs_hwmon_ops = {
> + .is_visible = ufs_hwmon_is_visible,
> + .read = ufs_hwmon_read,
> +};
> +
> +static const struct hwmon_chip_info ufs_hwmon_hba_info = {
> + .ops = &ufs_hwmon_ops,
> + .info = ufs_hwmon_info,
> +};
> +
> +int ufs_hwmon_probe(struct ufs_hba *hba, u8 mask)
> +{
> + struct device *dev = hba->dev;
> + struct ufs_hwmon_data *data;
> + struct device *hwmon;
> +
> + data = kzalloc(sizeof(*data), GFP_KERNEL);
> + if (!data)
> + return 0;
> +
> + data->hba = hba;
> + data->mask = mask;
> +
> + hwmon = hwmon_device_register_with_info(dev, "ufs",
> + data, &ufs_hwmon_hba_info,
> + NULL);
> + if (IS_ERR(hwmon)) {
> + dev_warn(dev, "Failed to instantiate hwmon device\n");
> + kfree(data);
> + return PTR_ERR(hwmon);
If the error is ignored by the caller, it doesn't make sense
to return it.
> + }
> +
> + hba->hwmon_device = hwmon;
> +
> + return 0;
> +}
> +
> +void ufs_hwmon_remove(struct ufs_hba *hba)
> +{
> + if (hba->hwmon_device) {
> + struct ufs_hwmon_data *data =
> + dev_get_drvdata(hba->hwmon_device);
> +
> + hwmon_device_unregister(hba->hwmon_device);
> + hba->hwmon_device = NULL;
> + kfree(data);
> + }
> +}
That function is never called and thus pointless (suggesting that there may be
some structural problem in the code).
> diff --git a/drivers/scsi/ufs/ufs.h b/drivers/scsi/ufs/ufs.h
> index 8c6b38b1b142..171b27be7b1d 100644
> --- a/drivers/scsi/ufs/ufs.h
> +++ b/drivers/scsi/ufs/ufs.h
> @@ -152,6 +152,9 @@ enum attr_idn {
> QUERY_ATTR_IDN_PSA_STATE = 0x15,
> QUERY_ATTR_IDN_PSA_DATA_SIZE = 0x16,
> QUERY_ATTR_IDN_REF_CLK_GATING_WAIT_TIME = 0x17,
> + QUERY_ATTR_IDN_CASE_ROUGH_TEMP = 0x18,
> + QUERY_ATTR_IDN_HIGH_TEMP_BOUND = 0x19,
> + QUERY_ATTR_IDN_LOW_TEMP_BOUND = 0x1A,
> QUERY_ATTR_IDN_WB_FLUSH_STATUS = 0x1C,
> QUERY_ATTR_IDN_AVAIL_WB_BUFF_SIZE = 0x1D,
> QUERY_ATTR_IDN_WB_BUFF_LIFE_TIME_EST = 0x1E,
> @@ -338,6 +341,9 @@ enum {
>
> /* Possible values for dExtendedUFSFeaturesSupport */
> enum {
> + UFS_DEV_LOW_TEMP_NOTIF = BIT(4),
> + UFS_DEV_HIGH_TEMP_NOTIF = BIT(5),
> + UFS_DEV_EXT_TEMP_NOTIF = BIT(6),
> UFS_DEV_HPB_SUPPORT = BIT(7),
> UFS_DEV_WRITE_BOOSTER_SUP = BIT(8),
> };
> diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c
> index 3841ab49f556..fc995bf1f296 100644
> --- a/drivers/scsi/ufs/ufshcd.c
> +++ b/drivers/scsi/ufs/ufshcd.c
> @@ -7469,6 +7469,31 @@ static void ufshcd_wb_probe(struct ufs_hba *hba, u8 *desc_buf)
> hba->caps &= ~UFSHCD_CAP_WB_EN;
> }
>
> +static void ufshcd_temp_notif_probe(struct ufs_hba *hba, u8 *desc_buf)
> +{
> + struct ufs_dev_info *dev_info = &hba->dev_info;
> + u32 ext_ufs_feature;
> + u8 mask = 0;
> +
> + if (!(hba->caps & UFSHCD_CAP_TEMP_NOTIF) ||
> + dev_info->wspecversion < 0x300)
> + return;
> +
> + ext_ufs_feature = get_unaligned_be32(desc_buf +
> + DEVICE_DESC_PARAM_EXT_UFS_FEATURE_SUP);
> +
> + if (ext_ufs_feature & UFS_DEV_LOW_TEMP_NOTIF)
> + mask |= MASK_EE_TOO_LOW_TEMP;
> +
> + if (ext_ufs_feature & UFS_DEV_HIGH_TEMP_NOTIF)
> + mask |= MASK_EE_TOO_HIGH_TEMP;
> +
> + if (mask) {
> + ufshcd_enable_ee(hba, mask);
> + ufs_hwmon_probe(hba, mask);
> + }
> +}
> +
> void ufshcd_fixup_dev_quirks(struct ufs_hba *hba, struct ufs_dev_fix *fixups)
> {
> struct ufs_dev_fix *f;
> @@ -7564,6 +7589,8 @@ static int ufs_get_device_desc(struct ufs_hba *hba)
>
> ufshcd_wb_probe(hba, desc_buf);
>
> + ufshcd_temp_notif_probe(hba, desc_buf);
> +
Is that the appropriate place to make this call ?
> /*
> * ufshcd_read_string_desc returns size of the string
> * reset the error value
> diff --git a/drivers/scsi/ufs/ufshcd.h b/drivers/scsi/ufs/ufshcd.h
> index 52ea6f350b18..9bc434c4ae71 100644
> --- a/drivers/scsi/ufs/ufshcd.h
> +++ b/drivers/scsi/ufs/ufshcd.h
> @@ -653,6 +653,12 @@ enum ufshcd_caps {
> * in order to exit DeepSleep state.
> */
> UFSHCD_CAP_DEEPSLEEP = 1 << 10,
> +
> + /*
> + * This capability allows the host controller driver to use temperature
> + * notification if it is supported by the UFS device.
> + */
> + UFSHCD_CAP_TEMP_NOTIF = 1 << 11,
> };
>
> struct ufs_hba_variant_params {
> @@ -789,6 +795,10 @@ struct ufs_hba {
> struct scsi_device *sdev_ufs_device;
> struct scsi_device *sdev_rpmb;
>
> +#ifdef CONFIG_SCSI_UFS_HWMON
> + struct device *hwmon_device;
> +#endif
> +
> enum ufs_dev_pwr_mode curr_dev_pwr_mode;
> enum uic_link_state uic_link_state;
> /* Desired UFS power management level during runtime PM */
> @@ -1049,6 +1059,14 @@ static inline u8 ufshcd_wb_get_query_index(struct ufs_hba *hba)
> return 0;
> }
>
> +#ifdef CONFIG_SCSI_UFS_HWMON
> +int ufs_hwmon_probe(struct ufs_hba *hba, u8 mask);
> +void ufs_hwmon_remove(struct ufs_hba *hba);
> +#else
> +static inline int ufs_hwmon_probe(struct ufs_hba *hba, u8 mask) { return 0; }
> +static inline void nvme_hwmon_remove(struct ufs_hba *hba) {}
ufs_hwmon_remove
> +#endif
> +
> #ifdef CONFIG_PM
> extern int ufshcd_runtime_suspend(struct device *dev);
> extern int ufshcd_runtime_resume(struct device *dev);
> --
> 2.17.1
>
>
> On 9/8/21 11:34 PM, Avri Altman wrote:
> > +static bool ufs_temp_enabled(struct ufs_hba *hba, struct
> > +ufs_hwmon_data *data) {
> > + u32 ee_mask;
> > +
> > + if (ufshcd_query_attr(hba, UPIU_QUERY_OPCODE_READ_ATTR,
> > + QUERY_ATTR_IDN_EE_CONTROL, 0, 0, &ee_mask))
> > + return false;
> > +
> > + return (data->mask & ee_mask & MASK_EE_TOO_HIGH_TEMP) ||
> > + (data->mask & ee_mask & MASK_EE_TOO_LOW_TEMP); }
>
> The above function uses data->mask but not data so it's probably better to
> pass
> data->mask directly as the second argument.
Done.
>
> > +static bool ufs_temp_valid(struct ufs_hba *hba, struct ufs_hwmon_data
> *data,
> > + enum attr_idn idn, u32 value) {
> > + return (idn == QUERY_ATTR_IDN_CASE_ROUGH_TEMP && value >= 1
> &&
> > + value <= 250 && ufs_temp_enabled(hba, data)) ||
> > + (idn == QUERY_ATTR_IDN_HIGH_TEMP_BOUND && value >= 100
> &&
> > + value <= 250) ||
> > + (idn == QUERY_ATTR_IDN_LOW_TEMP_BOUND && value >= 1 &&
> > + value <= 80);
> > +}
>
> Same comment for this function - if the above comment is addressed 'mask'
> can be passed as an argument instead of 'data'.
Done.
> > +static int ufs_get_temp(struct ufs_hba *hba, struct ufs_hwmon_data
> *data,
> > + enum attr_idn idn) {
> > + u32 value;
> > +
> > + if (ufshcd_query_attr(hba, UPIU_QUERY_OPCODE_READ_ATTR, idn, 0,
> 0,
> > + &value))
> > + return 0;
> > +
> > + if (ufs_temp_valid(hba, data, idn, value))
> > + return value - 80;
> > +
> > + return 0;
> > +}
>
> Same comment here.
Done.
> > +void ufs_hwmon_remove(struct ufs_hba *hba) {
> > + if (hba->hwmon_device) {
> > + struct ufs_hwmon_data *data =
> > + dev_get_drvdata(hba->hwmon_device);
> > +
> > + hwmon_device_unregister(hba->hwmon_device);
> > + hba->hwmon_device = NULL;
> > + kfree(data);
> > + }
> > +}
>
> Please use the "early return" style since that is the most widely used style in
> the Linux kernel (if (!hba->hwmon_device) return).
Done.
>
> Thanks,
>
> Bart.
> > + if (IS_ERR(hwmon)) {
> > + dev_warn(dev, "Failed to instantiate hwmon device\n");
> > + kfree(data);
> > + return PTR_ERR(hwmon);
>
> If the error is ignored by the caller, it doesn't make sense to return it.
Done.
>
> > + }
> > +
> > + hba->hwmon_device = hwmon;
> > +
> > + return 0;
> > +}
> > +
> > +void ufs_hwmon_remove(struct ufs_hba *hba) {
> > + if (hba->hwmon_device) {
> > + struct ufs_hwmon_data *data =
> > + dev_get_drvdata(hba->hwmon_device);
> > +
> > + hwmon_device_unregister(hba->hwmon_device);
> > + hba->hwmon_device = NULL;
> > + kfree(data);
> > + }
> > +}
>
> That function is never called and thus pointless (suggesting that there may
> be some structural problem in the code).
Yayks... Forgot to call it from ufshcd_remve()
Thanks.
> >
> > ufshcd_wb_probe(hba, desc_buf);
> >
> > + ufshcd_temp_notif_probe(hba, desc_buf);
> > +
>
> Is that the appropriate place to make this call ?
Yes I think so.
This path is on link startup when the host reads the device configuration.
> > +#ifdef CONFIG_SCSI_UFS_HWMON
> > +int ufs_hwmon_probe(struct ufs_hba *hba, u8 mask); void
> > +ufs_hwmon_remove(struct ufs_hba *hba); #else static inline int
> > +ufs_hwmon_probe(struct ufs_hba *hba, u8 mask) { return 0; } static
> > +inline void nvme_hwmon_remove(struct ufs_hba *hba) {}
>
> ufs_hwmon_remove
Done. Thanks.