]>
Commit | Line | Data |
---|---|---|
400b6a7b GR |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * NVM Express hardware monitoring support | |
4 | * Copyright (c) 2019, Guenter Roeck | |
5 | */ | |
6 | ||
7 | #include <linux/hwmon.h> | |
8 | #include <asm/unaligned.h> | |
9 | ||
10 | #include "nvme.h" | |
11 | ||
52deba0f AM |
12 | /* These macros should be moved to linux/temperature.h */ |
13 | #define MILLICELSIUS_TO_KELVIN(t) DIV_ROUND_CLOSEST((t) + 273150, 1000) | |
14 | #define KELVIN_TO_MILLICELSIUS(t) ((t) * 1000L - 273150) | |
15 | ||
400b6a7b GR |
16 | struct nvme_hwmon_data { |
17 | struct nvme_ctrl *ctrl; | |
18 | struct nvme_smart_log log; | |
19 | struct mutex read_lock; | |
20 | }; | |
21 | ||
52deba0f AM |
22 | static int nvme_get_temp_thresh(struct nvme_ctrl *ctrl, int sensor, bool under, |
23 | long *temp) | |
24 | { | |
25 | unsigned int threshold = sensor << NVME_TEMP_THRESH_SELECT_SHIFT; | |
26 | u32 status; | |
27 | int ret; | |
28 | ||
29 | if (under) | |
30 | threshold |= NVME_TEMP_THRESH_TYPE_UNDER; | |
31 | ||
32 | ret = nvme_get_features(ctrl, NVME_FEAT_TEMP_THRESH, threshold, NULL, 0, | |
33 | &status); | |
34 | if (ret > 0) | |
35 | return -EIO; | |
36 | if (ret < 0) | |
37 | return ret; | |
38 | *temp = KELVIN_TO_MILLICELSIUS(status & NVME_TEMP_THRESH_MASK); | |
39 | ||
40 | return 0; | |
41 | } | |
42 | ||
43 | static int nvme_set_temp_thresh(struct nvme_ctrl *ctrl, int sensor, bool under, | |
44 | long temp) | |
45 | { | |
46 | unsigned int threshold = sensor << NVME_TEMP_THRESH_SELECT_SHIFT; | |
47 | int ret; | |
48 | ||
49 | temp = MILLICELSIUS_TO_KELVIN(temp); | |
50 | threshold |= clamp_val(temp, 0, NVME_TEMP_THRESH_MASK); | |
51 | ||
52 | if (under) | |
53 | threshold |= NVME_TEMP_THRESH_TYPE_UNDER; | |
54 | ||
55 | ret = nvme_set_features(ctrl, NVME_FEAT_TEMP_THRESH, threshold, NULL, 0, | |
56 | NULL); | |
57 | if (ret > 0) | |
58 | return -EIO; | |
59 | ||
60 | return ret; | |
61 | } | |
62 | ||
400b6a7b GR |
63 | static int nvme_hwmon_get_smart_log(struct nvme_hwmon_data *data) |
64 | { | |
65 | int ret; | |
66 | ||
67 | ret = nvme_get_log(data->ctrl, NVME_NSID_ALL, NVME_LOG_SMART, 0, | |
68 | &data->log, sizeof(data->log), 0); | |
69 | ||
70 | return ret <= 0 ? ret : -EIO; | |
71 | } | |
72 | ||
73 | static int nvme_hwmon_read(struct device *dev, enum hwmon_sensor_types type, | |
74 | u32 attr, int channel, long *val) | |
75 | { | |
76 | struct nvme_hwmon_data *data = dev_get_drvdata(dev); | |
77 | struct nvme_smart_log *log = &data->log; | |
78 | int temp; | |
79 | int err; | |
80 | ||
81 | /* | |
82 | * First handle attributes which don't require us to read | |
83 | * the smart log. | |
84 | */ | |
85 | switch (attr) { | |
86 | case hwmon_temp_max: | |
52deba0f AM |
87 | return nvme_get_temp_thresh(data->ctrl, channel, false, val); |
88 | case hwmon_temp_min: | |
89 | return nvme_get_temp_thresh(data->ctrl, channel, true, val); | |
400b6a7b | 90 | case hwmon_temp_crit: |
52deba0f | 91 | *val = KELVIN_TO_MILLICELSIUS(data->ctrl->cctemp); |
400b6a7b GR |
92 | return 0; |
93 | default: | |
94 | break; | |
95 | } | |
96 | ||
97 | mutex_lock(&data->read_lock); | |
98 | err = nvme_hwmon_get_smart_log(data); | |
99 | if (err) | |
100 | goto unlock; | |
101 | ||
102 | switch (attr) { | |
103 | case hwmon_temp_input: | |
104 | if (!channel) | |
105 | temp = get_unaligned_le16(log->temperature); | |
106 | else | |
107 | temp = le16_to_cpu(log->temp_sensor[channel - 1]); | |
52deba0f | 108 | *val = KELVIN_TO_MILLICELSIUS(temp); |
400b6a7b GR |
109 | break; |
110 | case hwmon_temp_alarm: | |
111 | *val = !!(log->critical_warning & NVME_SMART_CRIT_TEMPERATURE); | |
112 | break; | |
113 | default: | |
114 | err = -EOPNOTSUPP; | |
115 | break; | |
116 | } | |
117 | unlock: | |
118 | mutex_unlock(&data->read_lock); | |
119 | return err; | |
120 | } | |
121 | ||
52deba0f AM |
122 | static int nvme_hwmon_write(struct device *dev, enum hwmon_sensor_types type, |
123 | u32 attr, int channel, long val) | |
124 | { | |
125 | struct nvme_hwmon_data *data = dev_get_drvdata(dev); | |
126 | ||
127 | switch (attr) { | |
128 | case hwmon_temp_max: | |
129 | return nvme_set_temp_thresh(data->ctrl, channel, false, val); | |
130 | case hwmon_temp_min: | |
131 | return nvme_set_temp_thresh(data->ctrl, channel, true, val); | |
132 | default: | |
133 | break; | |
134 | } | |
135 | ||
136 | return -EOPNOTSUPP; | |
137 | } | |
138 | ||
400b6a7b GR |
139 | static const char * const nvme_hwmon_sensor_names[] = { |
140 | "Composite", | |
141 | "Sensor 1", | |
142 | "Sensor 2", | |
143 | "Sensor 3", | |
144 | "Sensor 4", | |
145 | "Sensor 5", | |
146 | "Sensor 6", | |
147 | "Sensor 7", | |
148 | "Sensor 8", | |
149 | }; | |
150 | ||
151 | static int nvme_hwmon_read_string(struct device *dev, | |
152 | enum hwmon_sensor_types type, u32 attr, | |
153 | int channel, const char **str) | |
154 | { | |
155 | *str = nvme_hwmon_sensor_names[channel]; | |
156 | return 0; | |
157 | } | |
158 | ||
159 | static umode_t nvme_hwmon_is_visible(const void *_data, | |
160 | enum hwmon_sensor_types type, | |
161 | u32 attr, int channel) | |
162 | { | |
163 | const struct nvme_hwmon_data *data = _data; | |
164 | ||
165 | switch (attr) { | |
166 | case hwmon_temp_crit: | |
167 | if (!channel && data->ctrl->cctemp) | |
168 | return 0444; | |
169 | break; | |
170 | case hwmon_temp_max: | |
52deba0f AM |
171 | case hwmon_temp_min: |
172 | if ((!channel && data->ctrl->wctemp) || | |
6c6aa2f2 AM |
173 | (channel && data->log.temp_sensor[channel - 1])) { |
174 | if (data->ctrl->quirks & | |
175 | NVME_QUIRK_NO_TEMP_THRESH_CHANGE) | |
176 | return 0444; | |
52deba0f | 177 | return 0644; |
6c6aa2f2 | 178 | } |
400b6a7b GR |
179 | break; |
180 | case hwmon_temp_alarm: | |
181 | if (!channel) | |
182 | return 0444; | |
183 | break; | |
184 | case hwmon_temp_input: | |
185 | case hwmon_temp_label: | |
186 | if (!channel || data->log.temp_sensor[channel - 1]) | |
187 | return 0444; | |
188 | break; | |
189 | default: | |
190 | break; | |
191 | } | |
192 | return 0; | |
193 | } | |
194 | ||
195 | static const struct hwmon_channel_info *nvme_hwmon_info[] = { | |
196 | HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ), | |
197 | HWMON_CHANNEL_INFO(temp, | |
52deba0f AM |
198 | HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MIN | |
199 | HWMON_T_CRIT | HWMON_T_LABEL | HWMON_T_ALARM, | |
200 | HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MIN | | |
201 | HWMON_T_LABEL, | |
202 | HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MIN | | |
203 | HWMON_T_LABEL, | |
204 | HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MIN | | |
205 | HWMON_T_LABEL, | |
206 | HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MIN | | |
207 | HWMON_T_LABEL, | |
208 | HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MIN | | |
209 | HWMON_T_LABEL, | |
210 | HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MIN | | |
211 | HWMON_T_LABEL, | |
212 | HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MIN | | |
213 | HWMON_T_LABEL, | |
214 | HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MIN | | |
215 | HWMON_T_LABEL), | |
400b6a7b GR |
216 | NULL |
217 | }; | |
218 | ||
219 | static const struct hwmon_ops nvme_hwmon_ops = { | |
220 | .is_visible = nvme_hwmon_is_visible, | |
221 | .read = nvme_hwmon_read, | |
222 | .read_string = nvme_hwmon_read_string, | |
52deba0f | 223 | .write = nvme_hwmon_write, |
400b6a7b GR |
224 | }; |
225 | ||
226 | static const struct hwmon_chip_info nvme_hwmon_chip_info = { | |
227 | .ops = &nvme_hwmon_ops, | |
228 | .info = nvme_hwmon_info, | |
229 | }; | |
230 | ||
231 | void nvme_hwmon_init(struct nvme_ctrl *ctrl) | |
232 | { | |
233 | struct device *dev = ctrl->dev; | |
234 | struct nvme_hwmon_data *data; | |
235 | struct device *hwmon; | |
236 | int err; | |
237 | ||
238 | data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); | |
239 | if (!data) | |
240 | return; | |
241 | ||
242 | data->ctrl = ctrl; | |
243 | mutex_init(&data->read_lock); | |
244 | ||
245 | err = nvme_hwmon_get_smart_log(data); | |
246 | if (err) { | |
247 | dev_warn(dev, "Failed to read smart log (error %d)\n", err); | |
248 | devm_kfree(dev, data); | |
249 | return; | |
250 | } | |
251 | ||
252 | hwmon = devm_hwmon_device_register_with_info(dev, "nvme", data, | |
253 | &nvme_hwmon_chip_info, | |
254 | NULL); | |
255 | if (IS_ERR(hwmon)) { | |
256 | dev_warn(dev, "Failed to instantiate hwmon device\n"); | |
257 | devm_kfree(dev, data); | |
258 | } | |
259 | } |