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