| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * NVM Express hardware monitoring support | |
| * Copyright (c) 2019, Guenter Roeck | |
| */ | |
| #include <linux/hwmon.h> | |
| #include <linux/units.h> | |
| #include <linux/unaligned.h> | |
| #include "nvme.h" | |
| struct nvme_hwmon_data { | |
| struct nvme_ctrl *ctrl; | |
| struct nvme_smart_log *log; | |
| struct mutex read_lock; | |
| }; | |
| static int nvme_get_temp_thresh(struct nvme_ctrl *ctrl, int sensor, bool under, | |
| long *temp) | |
| { | |
| unsigned int threshold = sensor << NVME_TEMP_THRESH_SELECT_SHIFT; | |
| u32 status; | |
| int ret; | |
| if (under) | |
| threshold |= NVME_TEMP_THRESH_TYPE_UNDER; | |
| ret = nvme_get_features(ctrl, NVME_FEAT_TEMP_THRESH, threshold, NULL, 0, | |
| &status); | |
| if (ret > 0) | |
| return -EIO; | |
| if (ret < 0) | |
| return ret; | |
| *temp = kelvin_to_millicelsius(status & NVME_TEMP_THRESH_MASK); | |
| return 0; | |
| } | |
| static int nvme_set_temp_thresh(struct nvme_ctrl *ctrl, int sensor, bool under, | |
| long temp) | |
| { | |
| unsigned int threshold = sensor << NVME_TEMP_THRESH_SELECT_SHIFT; | |
| int ret; | |
| temp = millicelsius_to_kelvin(temp); | |
| threshold |= clamp_val(temp, 0, NVME_TEMP_THRESH_MASK); | |
| if (under) | |
| threshold |= NVME_TEMP_THRESH_TYPE_UNDER; | |
| ret = nvme_set_features(ctrl, NVME_FEAT_TEMP_THRESH, threshold, NULL, 0, | |
| NULL); | |
| if (ret > 0) | |
| return -EIO; | |
| return ret; | |
| } | |
| static int nvme_hwmon_get_smart_log(struct nvme_hwmon_data *data) | |
| { | |
| return nvme_get_log(data->ctrl, NVME_NSID_ALL, NVME_LOG_SMART, 0, | |
| NVME_CSI_NVM, data->log, sizeof(*data->log), 0); | |
| } | |
| static int nvme_hwmon_read(struct device *dev, enum hwmon_sensor_types type, | |
| u32 attr, int channel, long *val) | |
| { | |
| struct nvme_hwmon_data *data = dev_get_drvdata(dev); | |
| struct nvme_smart_log *log = data->log; | |
| int temp; | |
| int err; | |
| /* | |
| * First handle attributes which don't require us to read | |
| * the smart log. | |
| */ | |
| switch (attr) { | |
| case hwmon_temp_max: | |
| return nvme_get_temp_thresh(data->ctrl, channel, false, val); | |
| case hwmon_temp_min: | |
| return nvme_get_temp_thresh(data->ctrl, channel, true, val); | |
| case hwmon_temp_crit: | |
| *val = kelvin_to_millicelsius(data->ctrl->cctemp); | |
| return 0; | |
| default: | |
| break; | |
| } | |
| mutex_lock(&data->read_lock); | |
| err = nvme_hwmon_get_smart_log(data); | |
| if (err) | |
| goto unlock; | |
| switch (attr) { | |
| case hwmon_temp_input: | |
| if (!channel) | |
| temp = get_unaligned_le16(log->temperature); | |
| else | |
| temp = le16_to_cpu(log->temp_sensor[channel - 1]); | |
| *val = kelvin_to_millicelsius(temp); | |
| break; | |
| case hwmon_temp_alarm: | |
| *val = !!(log->critical_warning & NVME_SMART_CRIT_TEMPERATURE); | |
| break; | |
| default: | |
| err = -EOPNOTSUPP; | |
| break; | |
| } | |
| unlock: | |
| mutex_unlock(&data->read_lock); | |
| return err; | |
| } | |
| static int nvme_hwmon_write(struct device *dev, enum hwmon_sensor_types type, | |
| u32 attr, int channel, long val) | |
| { | |
| struct nvme_hwmon_data *data = dev_get_drvdata(dev); | |
| switch (attr) { | |
| case hwmon_temp_max: | |
| return nvme_set_temp_thresh(data->ctrl, channel, false, val); | |
| case hwmon_temp_min: | |
| return nvme_set_temp_thresh(data->ctrl, channel, true, val); | |
| default: | |
| break; | |
| } | |
| return -EOPNOTSUPP; | |
| } | |
| static const char * const nvme_hwmon_sensor_names[] = { | |
| "Composite", | |
| "Sensor 1", | |
| "Sensor 2", | |
| "Sensor 3", | |
| "Sensor 4", | |
| "Sensor 5", | |
| "Sensor 6", | |
| "Sensor 7", | |
| "Sensor 8", | |
| }; | |
| static int nvme_hwmon_read_string(struct device *dev, | |
| enum hwmon_sensor_types type, u32 attr, | |
| int channel, const char **str) | |
| { | |
| *str = nvme_hwmon_sensor_names[channel]; | |
| return 0; | |
| } | |
| static umode_t nvme_hwmon_is_visible(const void *_data, | |
| enum hwmon_sensor_types type, | |
| u32 attr, int channel) | |
| { | |
| const struct nvme_hwmon_data *data = _data; | |
| switch (attr) { | |
| case hwmon_temp_crit: | |
| if (!channel && data->ctrl->cctemp) | |
| return 0444; | |
| break; | |
| case hwmon_temp_max: | |
| case hwmon_temp_min: | |
| if ((!channel && data->ctrl->wctemp) || | |
| (channel && data->log->temp_sensor[channel - 1] && | |
| !(data->ctrl->quirks & | |
| NVME_QUIRK_NO_SECONDARY_TEMP_THRESH))) { | |
| if (data->ctrl->quirks & | |
| NVME_QUIRK_NO_TEMP_THRESH_CHANGE) | |
| return 0444; | |
| return 0644; | |
| } | |
| break; | |
| case hwmon_temp_alarm: | |
| if (!channel) | |
| return 0444; | |
| break; | |
| case hwmon_temp_input: | |
| case hwmon_temp_label: | |
| if (!channel || data->log->temp_sensor[channel - 1]) | |
| return 0444; | |
| break; | |
| default: | |
| break; | |
| } | |
| return 0; | |
| } | |
| static const struct hwmon_channel_info *const nvme_hwmon_info[] = { | |
| HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ), | |
| HWMON_CHANNEL_INFO(temp, | |
| HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MIN | | |
| HWMON_T_CRIT | HWMON_T_LABEL | HWMON_T_ALARM, | |
| HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MIN | | |
| HWMON_T_LABEL, | |
| HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MIN | | |
| HWMON_T_LABEL, | |
| HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MIN | | |
| HWMON_T_LABEL, | |
| HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MIN | | |
| HWMON_T_LABEL, | |
| HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MIN | | |
| HWMON_T_LABEL, | |
| HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MIN | | |
| HWMON_T_LABEL, | |
| HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MIN | | |
| HWMON_T_LABEL, | |
| HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MIN | | |
| HWMON_T_LABEL), | |
| NULL | |
| }; | |
| static const struct hwmon_ops nvme_hwmon_ops = { | |
| .is_visible = nvme_hwmon_is_visible, | |
| .read = nvme_hwmon_read, | |
| .read_string = nvme_hwmon_read_string, | |
| .write = nvme_hwmon_write, | |
| }; | |
| static const struct hwmon_chip_info nvme_hwmon_chip_info = { | |
| .ops = &nvme_hwmon_ops, | |
| .info = nvme_hwmon_info, | |
| }; | |
| int nvme_hwmon_init(struct nvme_ctrl *ctrl) | |
| { | |
| struct device *dev = ctrl->device; | |
| struct nvme_hwmon_data *data; | |
| struct device *hwmon; | |
| int err; | |
| data = kzalloc_obj(*data); | |
| if (!data) | |
| return -ENOMEM; | |
| data->log = kzalloc_obj(*data->log); | |
| if (!data->log) { | |
| err = -ENOMEM; | |
| goto err_free_data; | |
| } | |
| data->ctrl = ctrl; | |
| mutex_init(&data->read_lock); | |
| err = nvme_hwmon_get_smart_log(data); | |
| if (err) { | |
| dev_warn(dev, "Failed to read smart log (error %d)\n", err); | |
| goto err_free_log; | |
| } | |
| hwmon = hwmon_device_register_with_info(dev, "nvme", | |
| data, &nvme_hwmon_chip_info, | |
| NULL); | |
| if (IS_ERR(hwmon)) { | |
| dev_warn(dev, "Failed to instantiate hwmon device\n"); | |
| err = PTR_ERR(hwmon); | |
| goto err_free_log; | |
| } | |
| ctrl->hwmon_device = hwmon; | |
| return 0; | |
| err_free_log: | |
| kfree(data->log); | |
| err_free_data: | |
| kfree(data); | |
| return err; | |
| } | |
| void nvme_hwmon_exit(struct nvme_ctrl *ctrl) | |
| { | |
| if (ctrl->hwmon_device) { | |
| struct nvme_hwmon_data *data = | |
| dev_get_drvdata(ctrl->hwmon_device); | |
| hwmon_device_unregister(ctrl->hwmon_device); | |
| ctrl->hwmon_device = NULL; | |
| kfree(data->log); | |
| kfree(data); | |
| } | |
| } |