1 | // SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 |
2 | /* Copyright (c) 2018 Mellanox Technologies. All rights reserved */ |
3 | |
4 | #include <linux/kernel.h> |
5 | #include <linux/err.h> |
6 | #include <linux/ethtool.h> |
7 | #include <linux/sfp.h> |
8 | #include <linux/mutex.h> |
9 | |
10 | #include "core.h" |
11 | #include "core_env.h" |
12 | #include "item.h" |
13 | #include "reg.h" |
14 | |
15 | struct mlxsw_env_module_info { |
16 | u64 module_overheat_counter; |
17 | bool is_overheat; |
18 | int num_ports_mapped; |
19 | int num_ports_up; |
20 | enum ethtool_module_power_mode_policy power_mode_policy; |
21 | enum mlxsw_reg_pmtm_module_type type; |
22 | }; |
23 | |
24 | struct mlxsw_env_line_card { |
25 | u8 module_count; |
26 | bool active; |
27 | struct mlxsw_env_module_info module_info[]; |
28 | }; |
29 | |
30 | struct mlxsw_env { |
31 | struct mlxsw_core *core; |
32 | const struct mlxsw_bus_info *bus_info; |
33 | u8 max_module_count; /* Maximum number of modules per-slot. */ |
34 | u8 num_of_slots; /* Including the main board. */ |
35 | u8 max_eeprom_len; /* Maximum module EEPROM transaction length. */ |
36 | struct mutex line_cards_lock; /* Protects line cards. */ |
37 | struct mlxsw_env_line_card *line_cards[] __counted_by(num_of_slots); |
38 | }; |
39 | |
40 | static bool __mlxsw_env_linecard_is_active(struct mlxsw_env *mlxsw_env, |
41 | u8 slot_index) |
42 | { |
43 | return mlxsw_env->line_cards[slot_index]->active; |
44 | } |
45 | |
46 | static bool mlxsw_env_linecard_is_active(struct mlxsw_env *mlxsw_env, |
47 | u8 slot_index) |
48 | { |
49 | bool active; |
50 | |
51 | mutex_lock(&mlxsw_env->line_cards_lock); |
52 | active = __mlxsw_env_linecard_is_active(mlxsw_env, slot_index); |
53 | mutex_unlock(lock: &mlxsw_env->line_cards_lock); |
54 | |
55 | return active; |
56 | } |
57 | |
58 | static struct |
59 | mlxsw_env_module_info *mlxsw_env_module_info_get(struct mlxsw_core *mlxsw_core, |
60 | u8 slot_index, u8 module) |
61 | { |
62 | struct mlxsw_env *mlxsw_env = mlxsw_core_env(mlxsw_core); |
63 | |
64 | return &mlxsw_env->line_cards[slot_index]->module_info[module]; |
65 | } |
66 | |
67 | static int __mlxsw_env_validate_module_type(struct mlxsw_core *core, |
68 | u8 slot_index, u8 module) |
69 | { |
70 | struct mlxsw_env *mlxsw_env = mlxsw_core_env(mlxsw_core: core); |
71 | struct mlxsw_env_module_info *module_info; |
72 | int err; |
73 | |
74 | if (!__mlxsw_env_linecard_is_active(mlxsw_env, slot_index)) |
75 | return 0; |
76 | |
77 | module_info = mlxsw_env_module_info_get(mlxsw_core: core, slot_index, module); |
78 | switch (module_info->type) { |
79 | case MLXSW_REG_PMTM_MODULE_TYPE_TWISTED_PAIR: |
80 | err = -EINVAL; |
81 | break; |
82 | default: |
83 | err = 0; |
84 | } |
85 | |
86 | return err; |
87 | } |
88 | |
89 | static int mlxsw_env_validate_module_type(struct mlxsw_core *core, |
90 | u8 slot_index, u8 module) |
91 | { |
92 | struct mlxsw_env *mlxsw_env = mlxsw_core_env(mlxsw_core: core); |
93 | int err; |
94 | |
95 | mutex_lock(&mlxsw_env->line_cards_lock); |
96 | err = __mlxsw_env_validate_module_type(core, slot_index, module); |
97 | mutex_unlock(lock: &mlxsw_env->line_cards_lock); |
98 | |
99 | return err; |
100 | } |
101 | |
102 | static int |
103 | mlxsw_env_validate_cable_ident(struct mlxsw_core *core, u8 slot_index, int id, |
104 | bool *qsfp, bool *cmis) |
105 | { |
106 | char mcia_pl[MLXSW_REG_MCIA_LEN]; |
107 | char *eeprom_tmp; |
108 | u8 ident; |
109 | int err; |
110 | |
111 | err = mlxsw_env_validate_module_type(core, slot_index, module: id); |
112 | if (err) |
113 | return err; |
114 | |
115 | mlxsw_reg_mcia_pack(payload: mcia_pl, slot_index, module: id, |
116 | MLXSW_REG_MCIA_PAGE0_LO_OFF, device_addr: 0, size: 1, |
117 | MLXSW_REG_MCIA_I2C_ADDR_LOW); |
118 | err = mlxsw_reg_query(mlxsw_core: core, MLXSW_REG(mcia), payload: mcia_pl); |
119 | if (err) |
120 | return err; |
121 | eeprom_tmp = mlxsw_reg_mcia_eeprom_data(buf: mcia_pl); |
122 | ident = eeprom_tmp[0]; |
123 | *cmis = false; |
124 | switch (ident) { |
125 | case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_SFP: |
126 | *qsfp = false; |
127 | break; |
128 | case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_QSFP: |
129 | case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_QSFP_PLUS: |
130 | case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_QSFP28: |
131 | *qsfp = true; |
132 | break; |
133 | case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_QSFP_DD: |
134 | case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_OSFP: |
135 | *qsfp = true; |
136 | *cmis = true; |
137 | break; |
138 | default: |
139 | return -EINVAL; |
140 | } |
141 | |
142 | return 0; |
143 | } |
144 | |
145 | static int |
146 | mlxsw_env_query_module_eeprom(struct mlxsw_core *mlxsw_core, u8 slot_index, |
147 | int module, u16 offset, u16 size, void *data, |
148 | bool qsfp, unsigned int *p_read_size) |
149 | { |
150 | struct mlxsw_env *mlxsw_env = mlxsw_core_env(mlxsw_core); |
151 | char mcia_pl[MLXSW_REG_MCIA_LEN]; |
152 | char *eeprom_tmp; |
153 | u16 i2c_addr; |
154 | u8 page = 0; |
155 | int status; |
156 | int err; |
157 | |
158 | size = min_t(u16, size, mlxsw_env->max_eeprom_len); |
159 | |
160 | if (offset < MLXSW_REG_MCIA_EEPROM_PAGE_LENGTH && |
161 | offset + size > MLXSW_REG_MCIA_EEPROM_PAGE_LENGTH) |
162 | /* Cross pages read, read until offset 256 in low page */ |
163 | size = MLXSW_REG_MCIA_EEPROM_PAGE_LENGTH - offset; |
164 | |
165 | i2c_addr = MLXSW_REG_MCIA_I2C_ADDR_LOW; |
166 | if (offset >= MLXSW_REG_MCIA_EEPROM_PAGE_LENGTH) { |
167 | if (qsfp) { |
168 | /* When reading upper pages 1, 2 and 3 the offset |
169 | * starts at 128. Please refer to "QSFP+ Memory Map" |
170 | * figure in SFF-8436 specification and to "CMIS Module |
171 | * Memory Map" figure in CMIS specification for |
172 | * graphical depiction. |
173 | */ |
174 | page = MLXSW_REG_MCIA_PAGE_GET(offset); |
175 | offset -= MLXSW_REG_MCIA_EEPROM_UP_PAGE_LENGTH * page; |
176 | if (offset + size > MLXSW_REG_MCIA_EEPROM_PAGE_LENGTH) |
177 | size = MLXSW_REG_MCIA_EEPROM_PAGE_LENGTH - offset; |
178 | } else { |
179 | /* When reading upper pages 1, 2 and 3 the offset |
180 | * starts at 0 and I2C high address is used. Please refer |
181 | * to "Memory Organization" figure in SFF-8472 |
182 | * specification for graphical depiction. |
183 | */ |
184 | i2c_addr = MLXSW_REG_MCIA_I2C_ADDR_HIGH; |
185 | offset -= MLXSW_REG_MCIA_EEPROM_PAGE_LENGTH; |
186 | } |
187 | } |
188 | |
189 | mlxsw_reg_mcia_pack(payload: mcia_pl, slot_index, module, page_number: page, device_addr: offset, size, |
190 | i2c_device_addr: i2c_addr); |
191 | |
192 | err = mlxsw_reg_query(mlxsw_core, MLXSW_REG(mcia), payload: mcia_pl); |
193 | if (err) |
194 | return err; |
195 | |
196 | status = mlxsw_reg_mcia_status_get(buf: mcia_pl); |
197 | if (status) |
198 | return -EIO; |
199 | |
200 | eeprom_tmp = mlxsw_reg_mcia_eeprom_data(buf: mcia_pl); |
201 | memcpy(data, eeprom_tmp, size); |
202 | *p_read_size = size; |
203 | |
204 | return 0; |
205 | } |
206 | |
207 | int |
208 | mlxsw_env_module_temp_thresholds_get(struct mlxsw_core *core, u8 slot_index, |
209 | int module, int off, int *temp) |
210 | { |
211 | unsigned int module_temp, module_crit, module_emerg; |
212 | union { |
213 | u8 buf[MLXSW_REG_MCIA_TH_ITEM_SIZE]; |
214 | u16 temp; |
215 | } temp_thresh; |
216 | char mcia_pl[MLXSW_REG_MCIA_LEN] = {0}; |
217 | char mtmp_pl[MLXSW_REG_MTMP_LEN]; |
218 | char *eeprom_tmp; |
219 | bool qsfp, cmis; |
220 | int page; |
221 | int err; |
222 | |
223 | mlxsw_reg_mtmp_pack(payload: mtmp_pl, slot_index, |
224 | MLXSW_REG_MTMP_MODULE_INDEX_MIN + module, max_temp_enable: false, |
225 | max_temp_reset: false); |
226 | err = mlxsw_reg_query(mlxsw_core: core, MLXSW_REG(mtmp), payload: mtmp_pl); |
227 | if (err) |
228 | return err; |
229 | mlxsw_reg_mtmp_unpack(payload: mtmp_pl, p_temp: &module_temp, NULL, p_temp_hi: &module_crit, |
230 | p_max_oper_temp: &module_emerg, NULL); |
231 | if (!module_temp) { |
232 | *temp = 0; |
233 | return 0; |
234 | } |
235 | |
236 | /* Validate if threshold reading is available through MTMP register, |
237 | * otherwise fallback to read through MCIA. |
238 | */ |
239 | if (module_emerg) { |
240 | *temp = off == SFP_TEMP_HIGH_WARN ? module_crit : module_emerg; |
241 | return 0; |
242 | } |
243 | |
244 | /* Read Free Side Device Temperature Thresholds from page 03h |
245 | * (MSB at lower byte address). |
246 | * Bytes: |
247 | * 128-129 - Temp High Alarm (SFP_TEMP_HIGH_ALARM); |
248 | * 130-131 - Temp Low Alarm (SFP_TEMP_LOW_ALARM); |
249 | * 132-133 - Temp High Warning (SFP_TEMP_HIGH_WARN); |
250 | * 134-135 - Temp Low Warning (SFP_TEMP_LOW_WARN); |
251 | */ |
252 | |
253 | /* Validate module identifier value. */ |
254 | err = mlxsw_env_validate_cable_ident(core, slot_index, id: module, qsfp: &qsfp, |
255 | cmis: &cmis); |
256 | if (err) |
257 | return err; |
258 | |
259 | if (qsfp) { |
260 | /* For QSFP/CMIS module-defined thresholds are located in page |
261 | * 02h, otherwise in page 03h. |
262 | */ |
263 | if (cmis) |
264 | page = MLXSW_REG_MCIA_TH_PAGE_CMIS_NUM; |
265 | else |
266 | page = MLXSW_REG_MCIA_TH_PAGE_NUM; |
267 | mlxsw_reg_mcia_pack(payload: mcia_pl, slot_index, module, page_number: page, |
268 | MLXSW_REG_MCIA_TH_PAGE_OFF + off, |
269 | MLXSW_REG_MCIA_TH_ITEM_SIZE, |
270 | MLXSW_REG_MCIA_I2C_ADDR_LOW); |
271 | } else { |
272 | mlxsw_reg_mcia_pack(payload: mcia_pl, slot_index, module, |
273 | MLXSW_REG_MCIA_PAGE0_LO, |
274 | device_addr: off, MLXSW_REG_MCIA_TH_ITEM_SIZE, |
275 | MLXSW_REG_MCIA_I2C_ADDR_HIGH); |
276 | } |
277 | |
278 | err = mlxsw_reg_query(mlxsw_core: core, MLXSW_REG(mcia), payload: mcia_pl); |
279 | if (err) |
280 | return err; |
281 | |
282 | eeprom_tmp = mlxsw_reg_mcia_eeprom_data(buf: mcia_pl); |
283 | memcpy(temp_thresh.buf, eeprom_tmp, MLXSW_REG_MCIA_TH_ITEM_SIZE); |
284 | *temp = temp_thresh.temp * 1000; |
285 | |
286 | return 0; |
287 | } |
288 | |
289 | int mlxsw_env_get_module_info(struct net_device *netdev, |
290 | struct mlxsw_core *mlxsw_core, u8 slot_index, |
291 | int module, struct ethtool_modinfo *modinfo) |
292 | { |
293 | struct mlxsw_env *mlxsw_env = mlxsw_core_env(mlxsw_core); |
294 | u8 module_info[MLXSW_REG_MCIA_EEPROM_MODULE_INFO_SIZE]; |
295 | u16 offset = MLXSW_REG_MCIA_EEPROM_MODULE_INFO_SIZE; |
296 | u8 module_rev_id, module_id, diag_mon; |
297 | unsigned int read_size; |
298 | int err; |
299 | |
300 | if (!mlxsw_env_linecard_is_active(mlxsw_env, slot_index)) { |
301 | netdev_err(dev: netdev, format: "Cannot read EEPROM of module on an inactive line card\n" ); |
302 | return -EIO; |
303 | } |
304 | |
305 | err = mlxsw_env_validate_module_type(core: mlxsw_core, slot_index, module); |
306 | if (err) { |
307 | netdev_err(dev: netdev, |
308 | format: "EEPROM is not equipped on port module type" ); |
309 | return err; |
310 | } |
311 | |
312 | err = mlxsw_env_query_module_eeprom(mlxsw_core, slot_index, module, offset: 0, |
313 | size: offset, data: module_info, qsfp: false, |
314 | p_read_size: &read_size); |
315 | if (err) |
316 | return err; |
317 | |
318 | if (read_size < offset) |
319 | return -EIO; |
320 | |
321 | module_rev_id = module_info[MLXSW_REG_MCIA_EEPROM_MODULE_INFO_REV_ID]; |
322 | module_id = module_info[MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID]; |
323 | |
324 | switch (module_id) { |
325 | case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_QSFP: |
326 | modinfo->type = ETH_MODULE_SFF_8436; |
327 | modinfo->eeprom_len = ETH_MODULE_SFF_8436_MAX_LEN; |
328 | break; |
329 | case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_QSFP_PLUS: |
330 | case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_QSFP28: |
331 | if (module_id == MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_QSFP28 || |
332 | module_rev_id >= |
333 | MLXSW_REG_MCIA_EEPROM_MODULE_INFO_REV_ID_8636) { |
334 | modinfo->type = ETH_MODULE_SFF_8636; |
335 | modinfo->eeprom_len = ETH_MODULE_SFF_8636_MAX_LEN; |
336 | } else { |
337 | modinfo->type = ETH_MODULE_SFF_8436; |
338 | modinfo->eeprom_len = ETH_MODULE_SFF_8436_MAX_LEN; |
339 | } |
340 | break; |
341 | case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_SFP: |
342 | /* Verify if transceiver provides diagnostic monitoring page */ |
343 | err = mlxsw_env_query_module_eeprom(mlxsw_core, slot_index, |
344 | module, offset: SFP_DIAGMON, size: 1, |
345 | data: &diag_mon, qsfp: false, |
346 | p_read_size: &read_size); |
347 | if (err) |
348 | return err; |
349 | |
350 | if (read_size < 1) |
351 | return -EIO; |
352 | |
353 | modinfo->type = ETH_MODULE_SFF_8472; |
354 | if (diag_mon) |
355 | modinfo->eeprom_len = ETH_MODULE_SFF_8472_LEN; |
356 | else |
357 | modinfo->eeprom_len = ETH_MODULE_SFF_8472_LEN / 2; |
358 | break; |
359 | case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_QSFP_DD: |
360 | case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_OSFP: |
361 | /* Use SFF_8636 as base type. ethtool should recognize specific |
362 | * type through the identifier value. |
363 | */ |
364 | modinfo->type = ETH_MODULE_SFF_8636; |
365 | /* Verify if module EEPROM is a flat memory. In case of flat |
366 | * memory only page 00h (0-255 bytes) can be read. Otherwise |
367 | * upper pages 01h and 02h can also be read. Upper pages 10h |
368 | * and 11h are currently not supported by the driver. |
369 | */ |
370 | if (module_info[MLXSW_REG_MCIA_EEPROM_MODULE_INFO_TYPE_ID] & |
371 | MLXSW_REG_MCIA_EEPROM_CMIS_FLAT_MEMORY) |
372 | modinfo->eeprom_len = ETH_MODULE_SFF_8636_LEN; |
373 | else |
374 | modinfo->eeprom_len = ETH_MODULE_SFF_8472_LEN; |
375 | break; |
376 | default: |
377 | return -EINVAL; |
378 | } |
379 | |
380 | return 0; |
381 | } |
382 | EXPORT_SYMBOL(mlxsw_env_get_module_info); |
383 | |
384 | int mlxsw_env_get_module_eeprom(struct net_device *netdev, |
385 | struct mlxsw_core *mlxsw_core, u8 slot_index, |
386 | int module, struct ethtool_eeprom *ee, |
387 | u8 *data) |
388 | { |
389 | struct mlxsw_env *mlxsw_env = mlxsw_core_env(mlxsw_core); |
390 | int offset = ee->offset; |
391 | unsigned int read_size; |
392 | bool qsfp, cmis; |
393 | int i = 0; |
394 | int err; |
395 | |
396 | if (!ee->len) |
397 | return -EINVAL; |
398 | |
399 | if (!mlxsw_env_linecard_is_active(mlxsw_env, slot_index)) { |
400 | netdev_err(dev: netdev, format: "Cannot read EEPROM of module on an inactive line card\n" ); |
401 | return -EIO; |
402 | } |
403 | |
404 | memset(data, 0, ee->len); |
405 | /* Validate module identifier value. */ |
406 | err = mlxsw_env_validate_cable_ident(core: mlxsw_core, slot_index, id: module, |
407 | qsfp: &qsfp, cmis: &cmis); |
408 | if (err) |
409 | return err; |
410 | |
411 | while (i < ee->len) { |
412 | err = mlxsw_env_query_module_eeprom(mlxsw_core, slot_index, |
413 | module, offset, |
414 | size: ee->len - i, data: data + i, |
415 | qsfp, p_read_size: &read_size); |
416 | if (err) { |
417 | netdev_err(dev: netdev, format: "Eeprom query failed\n" ); |
418 | return err; |
419 | } |
420 | |
421 | i += read_size; |
422 | offset += read_size; |
423 | } |
424 | |
425 | return 0; |
426 | } |
427 | EXPORT_SYMBOL(mlxsw_env_get_module_eeprom); |
428 | |
429 | static int mlxsw_env_mcia_status_process(const char *mcia_pl, |
430 | struct netlink_ext_ack *extack) |
431 | { |
432 | u8 status = mlxsw_reg_mcia_status_get(buf: mcia_pl); |
433 | |
434 | switch (status) { |
435 | case MLXSW_REG_MCIA_STATUS_GOOD: |
436 | return 0; |
437 | case MLXSW_REG_MCIA_STATUS_NO_EEPROM_MODULE: |
438 | NL_SET_ERR_MSG_MOD(extack, "No response from module's EEPROM" ); |
439 | return -EIO; |
440 | case MLXSW_REG_MCIA_STATUS_MODULE_NOT_SUPPORTED: |
441 | NL_SET_ERR_MSG_MOD(extack, "Module type not supported by the device" ); |
442 | return -EOPNOTSUPP; |
443 | case MLXSW_REG_MCIA_STATUS_MODULE_NOT_CONNECTED: |
444 | NL_SET_ERR_MSG_MOD(extack, "No module present indication" ); |
445 | return -EIO; |
446 | case MLXSW_REG_MCIA_STATUS_I2C_ERROR: |
447 | NL_SET_ERR_MSG_MOD(extack, "Error occurred while trying to access module's EEPROM using I2C" ); |
448 | return -EIO; |
449 | case MLXSW_REG_MCIA_STATUS_MODULE_DISABLED: |
450 | NL_SET_ERR_MSG_MOD(extack, "Module is disabled" ); |
451 | return -EIO; |
452 | default: |
453 | NL_SET_ERR_MSG_MOD(extack, "Unknown error" ); |
454 | return -EIO; |
455 | } |
456 | } |
457 | |
458 | int |
459 | mlxsw_env_get_module_eeprom_by_page(struct mlxsw_core *mlxsw_core, |
460 | u8 slot_index, u8 module, |
461 | const struct ethtool_module_eeprom *page, |
462 | struct netlink_ext_ack *extack) |
463 | { |
464 | struct mlxsw_env *mlxsw_env = mlxsw_core_env(mlxsw_core); |
465 | u32 bytes_read = 0; |
466 | u16 device_addr; |
467 | int err; |
468 | |
469 | if (!mlxsw_env_linecard_is_active(mlxsw_env, slot_index)) { |
470 | NL_SET_ERR_MSG_MOD(extack, |
471 | "Cannot read EEPROM of module on an inactive line card" ); |
472 | return -EIO; |
473 | } |
474 | |
475 | err = mlxsw_env_validate_module_type(core: mlxsw_core, slot_index, module); |
476 | if (err) { |
477 | NL_SET_ERR_MSG_MOD(extack, "EEPROM is not equipped on port module type" ); |
478 | return err; |
479 | } |
480 | |
481 | /* Offset cannot be larger than 2 * ETH_MODULE_EEPROM_PAGE_LEN */ |
482 | device_addr = page->offset; |
483 | |
484 | while (bytes_read < page->length) { |
485 | char mcia_pl[MLXSW_REG_MCIA_LEN]; |
486 | char *eeprom_tmp; |
487 | u8 size; |
488 | |
489 | size = min_t(u8, page->length - bytes_read, |
490 | mlxsw_env->max_eeprom_len); |
491 | |
492 | mlxsw_reg_mcia_pack(payload: mcia_pl, slot_index, module, page_number: page->page, |
493 | device_addr: device_addr + bytes_read, size, |
494 | i2c_device_addr: page->i2c_address); |
495 | mlxsw_reg_mcia_bank_number_set(buf: mcia_pl, val: page->bank); |
496 | |
497 | err = mlxsw_reg_query(mlxsw_core, MLXSW_REG(mcia), payload: mcia_pl); |
498 | if (err) { |
499 | NL_SET_ERR_MSG_MOD(extack, "Failed to access module's EEPROM" ); |
500 | return err; |
501 | } |
502 | |
503 | err = mlxsw_env_mcia_status_process(mcia_pl, extack); |
504 | if (err) |
505 | return err; |
506 | |
507 | eeprom_tmp = mlxsw_reg_mcia_eeprom_data(buf: mcia_pl); |
508 | memcpy(page->data + bytes_read, eeprom_tmp, size); |
509 | bytes_read += size; |
510 | } |
511 | |
512 | return bytes_read; |
513 | } |
514 | EXPORT_SYMBOL(mlxsw_env_get_module_eeprom_by_page); |
515 | |
516 | static int mlxsw_env_module_reset(struct mlxsw_core *mlxsw_core, u8 slot_index, |
517 | u8 module) |
518 | { |
519 | char pmaos_pl[MLXSW_REG_PMAOS_LEN]; |
520 | |
521 | mlxsw_reg_pmaos_pack(payload: pmaos_pl, slot_index, module); |
522 | mlxsw_reg_pmaos_rst_set(buf: pmaos_pl, val: true); |
523 | |
524 | return mlxsw_reg_write(mlxsw_core, MLXSW_REG(pmaos), payload: pmaos_pl); |
525 | } |
526 | |
527 | int mlxsw_env_reset_module(struct net_device *netdev, |
528 | struct mlxsw_core *mlxsw_core, u8 slot_index, |
529 | u8 module, u32 *flags) |
530 | { |
531 | struct mlxsw_env *mlxsw_env = mlxsw_core_env(mlxsw_core); |
532 | struct mlxsw_env_module_info *module_info; |
533 | u32 req = *flags; |
534 | int err; |
535 | |
536 | if (!(req & ETH_RESET_PHY) && |
537 | !(req & (ETH_RESET_PHY << ETH_RESET_SHARED_SHIFT))) |
538 | return 0; |
539 | |
540 | if (!mlxsw_env_linecard_is_active(mlxsw_env, slot_index)) { |
541 | netdev_err(dev: netdev, format: "Cannot reset module on an inactive line card\n" ); |
542 | return -EIO; |
543 | } |
544 | |
545 | mutex_lock(&mlxsw_env->line_cards_lock); |
546 | |
547 | err = __mlxsw_env_validate_module_type(core: mlxsw_core, slot_index, module); |
548 | if (err) { |
549 | netdev_err(dev: netdev, format: "Reset module is not supported on port module type\n" ); |
550 | goto out; |
551 | } |
552 | |
553 | module_info = mlxsw_env_module_info_get(mlxsw_core, slot_index, module); |
554 | if (module_info->num_ports_up) { |
555 | netdev_err(dev: netdev, format: "Cannot reset module when ports using it are administratively up\n" ); |
556 | err = -EINVAL; |
557 | goto out; |
558 | } |
559 | |
560 | if (module_info->num_ports_mapped > 1 && |
561 | !(req & (ETH_RESET_PHY << ETH_RESET_SHARED_SHIFT))) { |
562 | netdev_err(dev: netdev, format: "Cannot reset module without \"phy-shared\" flag when shared by multiple ports\n" ); |
563 | err = -EINVAL; |
564 | goto out; |
565 | } |
566 | |
567 | err = mlxsw_env_module_reset(mlxsw_core, slot_index, module); |
568 | if (err) { |
569 | netdev_err(dev: netdev, format: "Failed to reset module\n" ); |
570 | goto out; |
571 | } |
572 | |
573 | *flags &= ~(ETH_RESET_PHY | (ETH_RESET_PHY << ETH_RESET_SHARED_SHIFT)); |
574 | |
575 | out: |
576 | mutex_unlock(lock: &mlxsw_env->line_cards_lock); |
577 | return err; |
578 | } |
579 | EXPORT_SYMBOL(mlxsw_env_reset_module); |
580 | |
581 | int |
582 | mlxsw_env_get_module_power_mode(struct mlxsw_core *mlxsw_core, u8 slot_index, |
583 | u8 module, |
584 | struct ethtool_module_power_mode_params *params, |
585 | struct netlink_ext_ack *extack) |
586 | { |
587 | struct mlxsw_env *mlxsw_env = mlxsw_core_env(mlxsw_core); |
588 | struct mlxsw_env_module_info *module_info; |
589 | char mcion_pl[MLXSW_REG_MCION_LEN]; |
590 | u32 status_bits; |
591 | int err = 0; |
592 | |
593 | mutex_lock(&mlxsw_env->line_cards_lock); |
594 | |
595 | err = __mlxsw_env_validate_module_type(core: mlxsw_core, slot_index, module); |
596 | if (err) { |
597 | NL_SET_ERR_MSG_MOD(extack, "Power mode is not supported on port module type" ); |
598 | goto out; |
599 | } |
600 | |
601 | module_info = mlxsw_env_module_info_get(mlxsw_core, slot_index, module); |
602 | params->policy = module_info->power_mode_policy; |
603 | |
604 | /* Avoid accessing an inactive line card, as it will result in an error. */ |
605 | if (!__mlxsw_env_linecard_is_active(mlxsw_env, slot_index)) |
606 | goto out; |
607 | |
608 | mlxsw_reg_mcion_pack(payload: mcion_pl, slot_index, module); |
609 | err = mlxsw_reg_query(mlxsw_core, MLXSW_REG(mcion), payload: mcion_pl); |
610 | if (err) { |
611 | NL_SET_ERR_MSG_MOD(extack, "Failed to retrieve module's power mode" ); |
612 | goto out; |
613 | } |
614 | |
615 | status_bits = mlxsw_reg_mcion_module_status_bits_get(buf: mcion_pl); |
616 | if (!(status_bits & MLXSW_REG_MCION_MODULE_STATUS_BITS_PRESENT_MASK)) |
617 | goto out; |
618 | |
619 | if (status_bits & MLXSW_REG_MCION_MODULE_STATUS_BITS_LOW_POWER_MASK) |
620 | params->mode = ETHTOOL_MODULE_POWER_MODE_LOW; |
621 | else |
622 | params->mode = ETHTOOL_MODULE_POWER_MODE_HIGH; |
623 | |
624 | out: |
625 | mutex_unlock(lock: &mlxsw_env->line_cards_lock); |
626 | return err; |
627 | } |
628 | EXPORT_SYMBOL(mlxsw_env_get_module_power_mode); |
629 | |
630 | static int mlxsw_env_module_enable_set(struct mlxsw_core *mlxsw_core, |
631 | u8 slot_index, u8 module, bool enable) |
632 | { |
633 | enum mlxsw_reg_pmaos_admin_status admin_status; |
634 | char pmaos_pl[MLXSW_REG_PMAOS_LEN]; |
635 | |
636 | mlxsw_reg_pmaos_pack(payload: pmaos_pl, slot_index, module); |
637 | admin_status = enable ? MLXSW_REG_PMAOS_ADMIN_STATUS_ENABLED : |
638 | MLXSW_REG_PMAOS_ADMIN_STATUS_DISABLED; |
639 | mlxsw_reg_pmaos_admin_status_set(buf: pmaos_pl, val: admin_status); |
640 | mlxsw_reg_pmaos_ase_set(buf: pmaos_pl, val: true); |
641 | |
642 | return mlxsw_reg_write(mlxsw_core, MLXSW_REG(pmaos), payload: pmaos_pl); |
643 | } |
644 | |
645 | static int mlxsw_env_module_low_power_set(struct mlxsw_core *mlxsw_core, |
646 | u8 slot_index, u8 module, |
647 | bool low_power) |
648 | { |
649 | u16 eeprom_override_mask, eeprom_override; |
650 | char pmmp_pl[MLXSW_REG_PMMP_LEN]; |
651 | |
652 | mlxsw_reg_pmmp_pack(payload: pmmp_pl, slot_index, module); |
653 | mlxsw_reg_pmmp_sticky_set(buf: pmmp_pl, val: true); |
654 | /* Mask all the bits except low power mode. */ |
655 | eeprom_override_mask = ~MLXSW_REG_PMMP_EEPROM_OVERRIDE_LOW_POWER_MASK; |
656 | mlxsw_reg_pmmp_eeprom_override_mask_set(buf: pmmp_pl, val: eeprom_override_mask); |
657 | eeprom_override = low_power ? MLXSW_REG_PMMP_EEPROM_OVERRIDE_LOW_POWER_MASK : |
658 | 0; |
659 | mlxsw_reg_pmmp_eeprom_override_set(buf: pmmp_pl, val: eeprom_override); |
660 | |
661 | return mlxsw_reg_write(mlxsw_core, MLXSW_REG(pmmp), payload: pmmp_pl); |
662 | } |
663 | |
664 | static int __mlxsw_env_set_module_power_mode(struct mlxsw_core *mlxsw_core, |
665 | u8 slot_index, u8 module, |
666 | bool low_power, |
667 | struct netlink_ext_ack *extack) |
668 | { |
669 | struct mlxsw_env *mlxsw_env = mlxsw_core_env(mlxsw_core); |
670 | int err; |
671 | |
672 | /* Avoid accessing an inactive line card, as it will result in an error. |
673 | * Cached configuration will be applied by mlxsw_env_got_active() when |
674 | * line card becomes active. |
675 | */ |
676 | if (!__mlxsw_env_linecard_is_active(mlxsw_env, slot_index)) |
677 | return 0; |
678 | |
679 | err = mlxsw_env_module_enable_set(mlxsw_core, slot_index, module, enable: false); |
680 | if (err) { |
681 | NL_SET_ERR_MSG_MOD(extack, "Failed to disable module" ); |
682 | return err; |
683 | } |
684 | |
685 | err = mlxsw_env_module_low_power_set(mlxsw_core, slot_index, module, |
686 | low_power); |
687 | if (err) { |
688 | NL_SET_ERR_MSG_MOD(extack, "Failed to set module's power mode" ); |
689 | goto err_module_low_power_set; |
690 | } |
691 | |
692 | err = mlxsw_env_module_enable_set(mlxsw_core, slot_index, module, enable: true); |
693 | if (err) { |
694 | NL_SET_ERR_MSG_MOD(extack, "Failed to enable module" ); |
695 | goto err_module_enable_set; |
696 | } |
697 | |
698 | return 0; |
699 | |
700 | err_module_enable_set: |
701 | mlxsw_env_module_low_power_set(mlxsw_core, slot_index, module, |
702 | low_power: !low_power); |
703 | err_module_low_power_set: |
704 | mlxsw_env_module_enable_set(mlxsw_core, slot_index, module, enable: true); |
705 | return err; |
706 | } |
707 | |
708 | static int |
709 | mlxsw_env_set_module_power_mode_apply(struct mlxsw_core *mlxsw_core, |
710 | u8 slot_index, u8 module, |
711 | enum ethtool_module_power_mode_policy policy, |
712 | struct netlink_ext_ack *extack) |
713 | { |
714 | struct mlxsw_env_module_info *module_info; |
715 | bool low_power; |
716 | int err = 0; |
717 | |
718 | err = __mlxsw_env_validate_module_type(core: mlxsw_core, slot_index, module); |
719 | if (err) { |
720 | NL_SET_ERR_MSG_MOD(extack, |
721 | "Power mode set is not supported on port module type" ); |
722 | goto out; |
723 | } |
724 | |
725 | module_info = mlxsw_env_module_info_get(mlxsw_core, slot_index, module); |
726 | if (module_info->power_mode_policy == policy) |
727 | goto out; |
728 | |
729 | /* If any ports are up, we are already in high power mode. */ |
730 | if (module_info->num_ports_up) |
731 | goto out_set_policy; |
732 | |
733 | low_power = policy == ETHTOOL_MODULE_POWER_MODE_POLICY_AUTO; |
734 | err = __mlxsw_env_set_module_power_mode(mlxsw_core, slot_index, module, |
735 | low_power, extack); |
736 | if (err) |
737 | goto out; |
738 | |
739 | out_set_policy: |
740 | module_info->power_mode_policy = policy; |
741 | out: |
742 | return err; |
743 | } |
744 | |
745 | int |
746 | mlxsw_env_set_module_power_mode(struct mlxsw_core *mlxsw_core, u8 slot_index, |
747 | u8 module, |
748 | enum ethtool_module_power_mode_policy policy, |
749 | struct netlink_ext_ack *extack) |
750 | { |
751 | struct mlxsw_env *mlxsw_env = mlxsw_core_env(mlxsw_core); |
752 | int err; |
753 | |
754 | if (policy != ETHTOOL_MODULE_POWER_MODE_POLICY_HIGH && |
755 | policy != ETHTOOL_MODULE_POWER_MODE_POLICY_AUTO) { |
756 | NL_SET_ERR_MSG_MOD(extack, "Unsupported power mode policy" ); |
757 | return -EOPNOTSUPP; |
758 | } |
759 | |
760 | mutex_lock(&mlxsw_env->line_cards_lock); |
761 | err = mlxsw_env_set_module_power_mode_apply(mlxsw_core, slot_index, |
762 | module, policy, extack); |
763 | mutex_unlock(lock: &mlxsw_env->line_cards_lock); |
764 | |
765 | return err; |
766 | } |
767 | EXPORT_SYMBOL(mlxsw_env_set_module_power_mode); |
768 | |
769 | static int mlxsw_env_module_has_temp_sensor(struct mlxsw_core *mlxsw_core, |
770 | u8 slot_index, u8 module, |
771 | bool *p_has_temp_sensor) |
772 | { |
773 | char mtbr_pl[MLXSW_REG_MTBR_LEN]; |
774 | u16 temp; |
775 | int err; |
776 | |
777 | mlxsw_reg_mtbr_pack(payload: mtbr_pl, slot_index, |
778 | MLXSW_REG_MTBR_BASE_MODULE_INDEX + module); |
779 | err = mlxsw_reg_query(mlxsw_core, MLXSW_REG(mtbr), payload: mtbr_pl); |
780 | if (err) |
781 | return err; |
782 | |
783 | mlxsw_reg_mtbr_temp_unpack(payload: mtbr_pl, rec_ind: 0, p_temp: &temp, NULL); |
784 | |
785 | switch (temp) { |
786 | case MLXSW_REG_MTBR_BAD_SENS_INFO: |
787 | case MLXSW_REG_MTBR_NO_CONN: |
788 | case MLXSW_REG_MTBR_NO_TEMP_SENS: |
789 | case MLXSW_REG_MTBR_INDEX_NA: |
790 | *p_has_temp_sensor = false; |
791 | break; |
792 | default: |
793 | *p_has_temp_sensor = temp ? true : false; |
794 | } |
795 | return 0; |
796 | } |
797 | |
798 | static int |
799 | mlxsw_env_temp_event_set(struct mlxsw_core *mlxsw_core, u8 slot_index, |
800 | u16 sensor_index, bool enable) |
801 | { |
802 | char mtmp_pl[MLXSW_REG_MTMP_LEN] = {0}; |
803 | enum mlxsw_reg_mtmp_tee tee; |
804 | int err, threshold_hi; |
805 | |
806 | mlxsw_reg_mtmp_slot_index_set(buf: mtmp_pl, val: slot_index); |
807 | mlxsw_reg_mtmp_sensor_index_set(buf: mtmp_pl, val: sensor_index); |
808 | err = mlxsw_reg_query(mlxsw_core, MLXSW_REG(mtmp), payload: mtmp_pl); |
809 | if (err) |
810 | return err; |
811 | |
812 | if (enable) { |
813 | err = mlxsw_env_module_temp_thresholds_get(core: mlxsw_core, |
814 | slot_index, |
815 | module: sensor_index - |
816 | MLXSW_REG_MTMP_MODULE_INDEX_MIN, |
817 | off: SFP_TEMP_HIGH_WARN, |
818 | temp: &threshold_hi); |
819 | /* In case it is not possible to query the module's threshold, |
820 | * use the default value. |
821 | */ |
822 | if (err) |
823 | threshold_hi = MLXSW_REG_MTMP_THRESH_HI; |
824 | else |
825 | /* mlxsw_env_module_temp_thresholds_get() multiplies |
826 | * Celsius degrees by 1000 whereas MTMP expects |
827 | * temperature in 0.125 Celsius degrees units. |
828 | * Convert threshold_hi to correct units. |
829 | */ |
830 | threshold_hi = threshold_hi / 1000 * 8; |
831 | |
832 | mlxsw_reg_mtmp_temperature_threshold_hi_set(buf: mtmp_pl, val: threshold_hi); |
833 | mlxsw_reg_mtmp_temperature_threshold_lo_set(buf: mtmp_pl, val: threshold_hi - |
834 | MLXSW_REG_MTMP_HYSTERESIS_TEMP); |
835 | } |
836 | tee = enable ? MLXSW_REG_MTMP_TEE_GENERATE_EVENT : MLXSW_REG_MTMP_TEE_NO_EVENT; |
837 | mlxsw_reg_mtmp_tee_set(buf: mtmp_pl, val: tee); |
838 | return mlxsw_reg_write(mlxsw_core, MLXSW_REG(mtmp), payload: mtmp_pl); |
839 | } |
840 | |
841 | static int mlxsw_env_module_temp_event_enable(struct mlxsw_core *mlxsw_core, |
842 | u8 slot_index) |
843 | { |
844 | struct mlxsw_env *mlxsw_env = mlxsw_core_env(mlxsw_core); |
845 | int i, err, sensor_index; |
846 | bool has_temp_sensor; |
847 | |
848 | for (i = 0; i < mlxsw_env->line_cards[slot_index]->module_count; i++) { |
849 | err = mlxsw_env_module_has_temp_sensor(mlxsw_core, slot_index, |
850 | module: i, p_has_temp_sensor: &has_temp_sensor); |
851 | if (err) |
852 | return err; |
853 | |
854 | if (!has_temp_sensor) |
855 | continue; |
856 | |
857 | sensor_index = i + MLXSW_REG_MTMP_MODULE_INDEX_MIN; |
858 | err = mlxsw_env_temp_event_set(mlxsw_core, slot_index, |
859 | sensor_index, enable: true); |
860 | if (err) |
861 | return err; |
862 | } |
863 | |
864 | return 0; |
865 | } |
866 | |
867 | struct mlxsw_env_module_temp_warn_event { |
868 | struct mlxsw_env *mlxsw_env; |
869 | char mtwe_pl[MLXSW_REG_MTWE_LEN]; |
870 | struct work_struct work; |
871 | }; |
872 | |
873 | static void mlxsw_env_mtwe_event_work(struct work_struct *work) |
874 | { |
875 | struct mlxsw_env_module_temp_warn_event *event; |
876 | struct mlxsw_env_module_info *module_info; |
877 | struct mlxsw_env *mlxsw_env; |
878 | int i, sensor_warning; |
879 | bool is_overheat; |
880 | |
881 | event = container_of(work, struct mlxsw_env_module_temp_warn_event, |
882 | work); |
883 | mlxsw_env = event->mlxsw_env; |
884 | |
885 | for (i = 0; i < mlxsw_env->max_module_count; i++) { |
886 | /* 64-127 of sensor_index are mapped to the port modules |
887 | * sequentially (module 0 is mapped to sensor_index 64, |
888 | * module 1 to sensor_index 65 and so on) |
889 | */ |
890 | sensor_warning = |
891 | mlxsw_reg_mtwe_sensor_warning_get(buf: event->mtwe_pl, |
892 | index: i + MLXSW_REG_MTMP_MODULE_INDEX_MIN); |
893 | mutex_lock(&mlxsw_env->line_cards_lock); |
894 | /* MTWE only supports main board. */ |
895 | module_info = mlxsw_env_module_info_get(mlxsw_core: mlxsw_env->core, slot_index: 0, module: i); |
896 | is_overheat = module_info->is_overheat; |
897 | |
898 | if ((is_overheat && sensor_warning) || |
899 | (!is_overheat && !sensor_warning)) { |
900 | /* Current state is "warning" and MTWE still reports |
901 | * warning OR current state in "no warning" and MTWE |
902 | * does not report warning. |
903 | */ |
904 | mutex_unlock(lock: &mlxsw_env->line_cards_lock); |
905 | continue; |
906 | } else if (is_overheat && !sensor_warning) { |
907 | /* MTWE reports "no warning", turn is_overheat off. |
908 | */ |
909 | module_info->is_overheat = false; |
910 | mutex_unlock(lock: &mlxsw_env->line_cards_lock); |
911 | } else { |
912 | /* Current state is "no warning" and MTWE reports |
913 | * "warning", increase the counter and turn is_overheat |
914 | * on. |
915 | */ |
916 | module_info->is_overheat = true; |
917 | module_info->module_overheat_counter++; |
918 | mutex_unlock(lock: &mlxsw_env->line_cards_lock); |
919 | } |
920 | } |
921 | |
922 | kfree(objp: event); |
923 | } |
924 | |
925 | static void |
926 | mlxsw_env_mtwe_listener_func(const struct mlxsw_reg_info *reg, char *mtwe_pl, |
927 | void *priv) |
928 | { |
929 | struct mlxsw_env_module_temp_warn_event *event; |
930 | struct mlxsw_env *mlxsw_env = priv; |
931 | |
932 | event = kmalloc(size: sizeof(*event), GFP_ATOMIC); |
933 | if (!event) |
934 | return; |
935 | |
936 | event->mlxsw_env = mlxsw_env; |
937 | memcpy(event->mtwe_pl, mtwe_pl, MLXSW_REG_MTWE_LEN); |
938 | INIT_WORK(&event->work, mlxsw_env_mtwe_event_work); |
939 | mlxsw_core_schedule_work(work: &event->work); |
940 | } |
941 | |
942 | static const struct mlxsw_listener mlxsw_env_temp_warn_listener = |
943 | MLXSW_CORE_EVENTL(mlxsw_env_mtwe_listener_func, MTWE); |
944 | |
945 | static int mlxsw_env_temp_warn_event_register(struct mlxsw_core *mlxsw_core) |
946 | { |
947 | struct mlxsw_env *mlxsw_env = mlxsw_core_env(mlxsw_core); |
948 | |
949 | return mlxsw_core_trap_register(mlxsw_core, |
950 | listener: &mlxsw_env_temp_warn_listener, |
951 | priv: mlxsw_env); |
952 | } |
953 | |
954 | static void mlxsw_env_temp_warn_event_unregister(struct mlxsw_env *mlxsw_env) |
955 | { |
956 | mlxsw_core_trap_unregister(mlxsw_core: mlxsw_env->core, |
957 | listener: &mlxsw_env_temp_warn_listener, priv: mlxsw_env); |
958 | } |
959 | |
960 | struct mlxsw_env_module_plug_unplug_event { |
961 | struct mlxsw_env *mlxsw_env; |
962 | u8 slot_index; |
963 | u8 module; |
964 | struct work_struct work; |
965 | }; |
966 | |
967 | static void mlxsw_env_pmpe_event_work(struct work_struct *work) |
968 | { |
969 | struct mlxsw_env_module_plug_unplug_event *event; |
970 | struct mlxsw_env_module_info *module_info; |
971 | struct mlxsw_env *mlxsw_env; |
972 | bool has_temp_sensor; |
973 | u16 sensor_index; |
974 | int err; |
975 | |
976 | event = container_of(work, struct mlxsw_env_module_plug_unplug_event, |
977 | work); |
978 | mlxsw_env = event->mlxsw_env; |
979 | |
980 | mutex_lock(&mlxsw_env->line_cards_lock); |
981 | module_info = mlxsw_env_module_info_get(mlxsw_core: mlxsw_env->core, |
982 | slot_index: event->slot_index, |
983 | module: event->module); |
984 | module_info->is_overheat = false; |
985 | mutex_unlock(lock: &mlxsw_env->line_cards_lock); |
986 | |
987 | err = mlxsw_env_module_has_temp_sensor(mlxsw_core: mlxsw_env->core, |
988 | slot_index: event->slot_index, |
989 | module: event->module, |
990 | p_has_temp_sensor: &has_temp_sensor); |
991 | /* Do not disable events on modules without sensors or faulty sensors |
992 | * because FW returns errors. |
993 | */ |
994 | if (err) |
995 | goto out; |
996 | |
997 | if (!has_temp_sensor) |
998 | goto out; |
999 | |
1000 | sensor_index = event->module + MLXSW_REG_MTMP_MODULE_INDEX_MIN; |
1001 | mlxsw_env_temp_event_set(mlxsw_core: mlxsw_env->core, slot_index: event->slot_index, |
1002 | sensor_index, enable: true); |
1003 | |
1004 | out: |
1005 | kfree(objp: event); |
1006 | } |
1007 | |
1008 | static void |
1009 | mlxsw_env_pmpe_listener_func(const struct mlxsw_reg_info *reg, char *pmpe_pl, |
1010 | void *priv) |
1011 | { |
1012 | u8 slot_index = mlxsw_reg_pmpe_slot_index_get(buf: pmpe_pl); |
1013 | struct mlxsw_env_module_plug_unplug_event *event; |
1014 | enum mlxsw_reg_pmpe_module_status module_status; |
1015 | u8 module = mlxsw_reg_pmpe_module_get(buf: pmpe_pl); |
1016 | struct mlxsw_env *mlxsw_env = priv; |
1017 | |
1018 | if (WARN_ON_ONCE(module >= mlxsw_env->max_module_count || |
1019 | slot_index >= mlxsw_env->num_of_slots)) |
1020 | return; |
1021 | |
1022 | module_status = mlxsw_reg_pmpe_module_status_get(buf: pmpe_pl); |
1023 | if (module_status != MLXSW_REG_PMPE_MODULE_STATUS_PLUGGED_ENABLED) |
1024 | return; |
1025 | |
1026 | event = kmalloc(size: sizeof(*event), GFP_ATOMIC); |
1027 | if (!event) |
1028 | return; |
1029 | |
1030 | event->mlxsw_env = mlxsw_env; |
1031 | event->slot_index = slot_index; |
1032 | event->module = module; |
1033 | INIT_WORK(&event->work, mlxsw_env_pmpe_event_work); |
1034 | mlxsw_core_schedule_work(work: &event->work); |
1035 | } |
1036 | |
1037 | static const struct mlxsw_listener mlxsw_env_module_plug_listener = |
1038 | MLXSW_CORE_EVENTL(mlxsw_env_pmpe_listener_func, PMPE); |
1039 | |
1040 | static int |
1041 | mlxsw_env_module_plug_event_register(struct mlxsw_core *mlxsw_core) |
1042 | { |
1043 | struct mlxsw_env *mlxsw_env = mlxsw_core_env(mlxsw_core); |
1044 | |
1045 | return mlxsw_core_trap_register(mlxsw_core, |
1046 | listener: &mlxsw_env_module_plug_listener, |
1047 | priv: mlxsw_env); |
1048 | } |
1049 | |
1050 | static void |
1051 | mlxsw_env_module_plug_event_unregister(struct mlxsw_env *mlxsw_env) |
1052 | { |
1053 | mlxsw_core_trap_unregister(mlxsw_core: mlxsw_env->core, |
1054 | listener: &mlxsw_env_module_plug_listener, |
1055 | priv: mlxsw_env); |
1056 | } |
1057 | |
1058 | static int |
1059 | mlxsw_env_module_oper_state_event_enable(struct mlxsw_core *mlxsw_core, |
1060 | u8 slot_index) |
1061 | { |
1062 | struct mlxsw_env *mlxsw_env = mlxsw_core_env(mlxsw_core); |
1063 | int i, err; |
1064 | |
1065 | for (i = 0; i < mlxsw_env->line_cards[slot_index]->module_count; i++) { |
1066 | char pmaos_pl[MLXSW_REG_PMAOS_LEN]; |
1067 | |
1068 | mlxsw_reg_pmaos_pack(payload: pmaos_pl, slot_index, module: i); |
1069 | mlxsw_reg_pmaos_e_set(buf: pmaos_pl, |
1070 | val: MLXSW_REG_PMAOS_E_GENERATE_EVENT); |
1071 | mlxsw_reg_pmaos_ee_set(buf: pmaos_pl, val: true); |
1072 | err = mlxsw_reg_write(mlxsw_core, MLXSW_REG(pmaos), payload: pmaos_pl); |
1073 | if (err) |
1074 | return err; |
1075 | } |
1076 | return 0; |
1077 | } |
1078 | |
1079 | int |
1080 | mlxsw_env_module_overheat_counter_get(struct mlxsw_core *mlxsw_core, u8 slot_index, |
1081 | u8 module, u64 *p_counter) |
1082 | { |
1083 | struct mlxsw_env *mlxsw_env = mlxsw_core_env(mlxsw_core); |
1084 | struct mlxsw_env_module_info *module_info; |
1085 | |
1086 | mutex_lock(&mlxsw_env->line_cards_lock); |
1087 | module_info = mlxsw_env_module_info_get(mlxsw_core, slot_index, module); |
1088 | *p_counter = module_info->module_overheat_counter; |
1089 | mutex_unlock(lock: &mlxsw_env->line_cards_lock); |
1090 | |
1091 | return 0; |
1092 | } |
1093 | EXPORT_SYMBOL(mlxsw_env_module_overheat_counter_get); |
1094 | |
1095 | void mlxsw_env_module_port_map(struct mlxsw_core *mlxsw_core, u8 slot_index, |
1096 | u8 module) |
1097 | { |
1098 | struct mlxsw_env *mlxsw_env = mlxsw_core_env(mlxsw_core); |
1099 | struct mlxsw_env_module_info *module_info; |
1100 | |
1101 | mutex_lock(&mlxsw_env->line_cards_lock); |
1102 | module_info = mlxsw_env_module_info_get(mlxsw_core, slot_index, module); |
1103 | module_info->num_ports_mapped++; |
1104 | mutex_unlock(lock: &mlxsw_env->line_cards_lock); |
1105 | } |
1106 | EXPORT_SYMBOL(mlxsw_env_module_port_map); |
1107 | |
1108 | void mlxsw_env_module_port_unmap(struct mlxsw_core *mlxsw_core, u8 slot_index, |
1109 | u8 module) |
1110 | { |
1111 | struct mlxsw_env *mlxsw_env = mlxsw_core_env(mlxsw_core); |
1112 | struct mlxsw_env_module_info *module_info; |
1113 | |
1114 | mutex_lock(&mlxsw_env->line_cards_lock); |
1115 | module_info = mlxsw_env_module_info_get(mlxsw_core, slot_index, module); |
1116 | module_info->num_ports_mapped--; |
1117 | mutex_unlock(lock: &mlxsw_env->line_cards_lock); |
1118 | } |
1119 | EXPORT_SYMBOL(mlxsw_env_module_port_unmap); |
1120 | |
1121 | int mlxsw_env_module_port_up(struct mlxsw_core *mlxsw_core, u8 slot_index, |
1122 | u8 module) |
1123 | { |
1124 | struct mlxsw_env *mlxsw_env = mlxsw_core_env(mlxsw_core); |
1125 | struct mlxsw_env_module_info *module_info; |
1126 | int err = 0; |
1127 | |
1128 | mutex_lock(&mlxsw_env->line_cards_lock); |
1129 | |
1130 | module_info = mlxsw_env_module_info_get(mlxsw_core, slot_index, module); |
1131 | if (module_info->power_mode_policy != |
1132 | ETHTOOL_MODULE_POWER_MODE_POLICY_AUTO) |
1133 | goto out_inc; |
1134 | |
1135 | if (module_info->num_ports_up != 0) |
1136 | goto out_inc; |
1137 | |
1138 | /* Transition to high power mode following first port using the module |
1139 | * being put administratively up. |
1140 | */ |
1141 | err = __mlxsw_env_set_module_power_mode(mlxsw_core, slot_index, module, |
1142 | low_power: false, NULL); |
1143 | if (err) |
1144 | goto out_unlock; |
1145 | |
1146 | out_inc: |
1147 | module_info->num_ports_up++; |
1148 | out_unlock: |
1149 | mutex_unlock(lock: &mlxsw_env->line_cards_lock); |
1150 | return err; |
1151 | } |
1152 | EXPORT_SYMBOL(mlxsw_env_module_port_up); |
1153 | |
1154 | void mlxsw_env_module_port_down(struct mlxsw_core *mlxsw_core, u8 slot_index, |
1155 | u8 module) |
1156 | { |
1157 | struct mlxsw_env *mlxsw_env = mlxsw_core_env(mlxsw_core); |
1158 | struct mlxsw_env_module_info *module_info; |
1159 | |
1160 | mutex_lock(&mlxsw_env->line_cards_lock); |
1161 | |
1162 | module_info = mlxsw_env_module_info_get(mlxsw_core, slot_index, module); |
1163 | module_info->num_ports_up--; |
1164 | |
1165 | if (module_info->power_mode_policy != |
1166 | ETHTOOL_MODULE_POWER_MODE_POLICY_AUTO) |
1167 | goto out_unlock; |
1168 | |
1169 | if (module_info->num_ports_up != 0) |
1170 | goto out_unlock; |
1171 | |
1172 | /* Transition to low power mode following last port using the module |
1173 | * being put administratively down. |
1174 | */ |
1175 | __mlxsw_env_set_module_power_mode(mlxsw_core, slot_index, module, low_power: true, |
1176 | NULL); |
1177 | |
1178 | out_unlock: |
1179 | mutex_unlock(lock: &mlxsw_env->line_cards_lock); |
1180 | } |
1181 | EXPORT_SYMBOL(mlxsw_env_module_port_down); |
1182 | |
1183 | static int mlxsw_env_line_cards_alloc(struct mlxsw_env *env) |
1184 | { |
1185 | struct mlxsw_env_module_info *module_info; |
1186 | int i, j; |
1187 | |
1188 | for (i = 0; i < env->num_of_slots; i++) { |
1189 | env->line_cards[i] = kzalloc(struct_size(env->line_cards[i], |
1190 | module_info, |
1191 | env->max_module_count), |
1192 | GFP_KERNEL); |
1193 | if (!env->line_cards[i]) |
1194 | goto kzalloc_err; |
1195 | |
1196 | /* Firmware defaults to high power mode policy where modules |
1197 | * are transitioned to high power mode following plug-in. |
1198 | */ |
1199 | for (j = 0; j < env->max_module_count; j++) { |
1200 | module_info = &env->line_cards[i]->module_info[j]; |
1201 | module_info->power_mode_policy = |
1202 | ETHTOOL_MODULE_POWER_MODE_POLICY_HIGH; |
1203 | } |
1204 | } |
1205 | |
1206 | return 0; |
1207 | |
1208 | kzalloc_err: |
1209 | for (i--; i >= 0; i--) |
1210 | kfree(objp: env->line_cards[i]); |
1211 | return -ENOMEM; |
1212 | } |
1213 | |
1214 | static void mlxsw_env_line_cards_free(struct mlxsw_env *env) |
1215 | { |
1216 | int i = env->num_of_slots; |
1217 | |
1218 | for (i--; i >= 0; i--) |
1219 | kfree(objp: env->line_cards[i]); |
1220 | } |
1221 | |
1222 | static int |
1223 | mlxsw_env_module_event_enable(struct mlxsw_env *mlxsw_env, u8 slot_index) |
1224 | { |
1225 | int err; |
1226 | |
1227 | err = mlxsw_env_module_oper_state_event_enable(mlxsw_core: mlxsw_env->core, |
1228 | slot_index); |
1229 | if (err) |
1230 | return err; |
1231 | |
1232 | err = mlxsw_env_module_temp_event_enable(mlxsw_core: mlxsw_env->core, slot_index); |
1233 | if (err) |
1234 | return err; |
1235 | |
1236 | return 0; |
1237 | } |
1238 | |
1239 | static void |
1240 | mlxsw_env_module_event_disable(struct mlxsw_env *mlxsw_env, u8 slot_index) |
1241 | { |
1242 | } |
1243 | |
1244 | static int |
1245 | mlxsw_env_module_type_set(struct mlxsw_core *mlxsw_core, u8 slot_index) |
1246 | { |
1247 | struct mlxsw_env *mlxsw_env = mlxsw_core_env(mlxsw_core); |
1248 | int i; |
1249 | |
1250 | for (i = 0; i < mlxsw_env->line_cards[slot_index]->module_count; i++) { |
1251 | struct mlxsw_env_module_info *module_info; |
1252 | char pmtm_pl[MLXSW_REG_PMTM_LEN]; |
1253 | int err; |
1254 | |
1255 | mlxsw_reg_pmtm_pack(payload: pmtm_pl, slot_index, module: i); |
1256 | err = mlxsw_reg_query(mlxsw_core, MLXSW_REG(pmtm), payload: pmtm_pl); |
1257 | if (err) |
1258 | return err; |
1259 | |
1260 | module_info = mlxsw_env_module_info_get(mlxsw_core, slot_index, |
1261 | module: i); |
1262 | module_info->type = mlxsw_reg_pmtm_module_type_get(buf: pmtm_pl); |
1263 | } |
1264 | |
1265 | return 0; |
1266 | } |
1267 | |
1268 | static void |
1269 | mlxsw_env_linecard_modules_power_mode_apply(struct mlxsw_core *mlxsw_core, |
1270 | struct mlxsw_env *env, |
1271 | u8 slot_index) |
1272 | { |
1273 | int i; |
1274 | |
1275 | for (i = 0; i < env->line_cards[slot_index]->module_count; i++) { |
1276 | enum ethtool_module_power_mode_policy policy; |
1277 | struct mlxsw_env_module_info *module_info; |
1278 | struct netlink_ext_ack extack; |
1279 | int err; |
1280 | |
1281 | module_info = &env->line_cards[slot_index]->module_info[i]; |
1282 | policy = module_info->power_mode_policy; |
1283 | err = mlxsw_env_set_module_power_mode_apply(mlxsw_core, |
1284 | slot_index, module: i, |
1285 | policy, extack: &extack); |
1286 | if (err) |
1287 | dev_err(env->bus_info->dev, "%s\n" , extack._msg); |
1288 | } |
1289 | } |
1290 | |
1291 | static void |
1292 | mlxsw_env_got_active(struct mlxsw_core *mlxsw_core, u8 slot_index, void *priv) |
1293 | { |
1294 | struct mlxsw_env *mlxsw_env = priv; |
1295 | char mgpir_pl[MLXSW_REG_MGPIR_LEN]; |
1296 | int err; |
1297 | |
1298 | mutex_lock(&mlxsw_env->line_cards_lock); |
1299 | if (__mlxsw_env_linecard_is_active(mlxsw_env, slot_index)) |
1300 | goto out_unlock; |
1301 | |
1302 | mlxsw_reg_mgpir_pack(payload: mgpir_pl, slot_index); |
1303 | err = mlxsw_reg_query(mlxsw_core: mlxsw_env->core, MLXSW_REG(mgpir), payload: mgpir_pl); |
1304 | if (err) |
1305 | goto out_unlock; |
1306 | |
1307 | mlxsw_reg_mgpir_unpack(payload: mgpir_pl, NULL, NULL, NULL, |
1308 | num_of_modules: &mlxsw_env->line_cards[slot_index]->module_count, |
1309 | NULL); |
1310 | |
1311 | err = mlxsw_env_module_event_enable(mlxsw_env, slot_index); |
1312 | if (err) { |
1313 | dev_err(mlxsw_env->bus_info->dev, "Failed to enable port module events for line card in slot %d\n" , |
1314 | slot_index); |
1315 | goto err_mlxsw_env_module_event_enable; |
1316 | } |
1317 | err = mlxsw_env_module_type_set(mlxsw_core: mlxsw_env->core, slot_index); |
1318 | if (err) { |
1319 | dev_err(mlxsw_env->bus_info->dev, "Failed to set modules' type for line card in slot %d\n" , |
1320 | slot_index); |
1321 | goto err_type_set; |
1322 | } |
1323 | |
1324 | mlxsw_env->line_cards[slot_index]->active = true; |
1325 | /* Apply power mode policy. */ |
1326 | mlxsw_env_linecard_modules_power_mode_apply(mlxsw_core, env: mlxsw_env, |
1327 | slot_index); |
1328 | mutex_unlock(lock: &mlxsw_env->line_cards_lock); |
1329 | |
1330 | return; |
1331 | |
1332 | err_type_set: |
1333 | mlxsw_env_module_event_disable(mlxsw_env, slot_index); |
1334 | err_mlxsw_env_module_event_enable: |
1335 | out_unlock: |
1336 | mutex_unlock(lock: &mlxsw_env->line_cards_lock); |
1337 | } |
1338 | |
1339 | static void |
1340 | mlxsw_env_got_inactive(struct mlxsw_core *mlxsw_core, u8 slot_index, |
1341 | void *priv) |
1342 | { |
1343 | struct mlxsw_env *mlxsw_env = priv; |
1344 | |
1345 | mutex_lock(&mlxsw_env->line_cards_lock); |
1346 | if (!__mlxsw_env_linecard_is_active(mlxsw_env, slot_index)) |
1347 | goto out_unlock; |
1348 | mlxsw_env->line_cards[slot_index]->active = false; |
1349 | mlxsw_env_module_event_disable(mlxsw_env, slot_index); |
1350 | mlxsw_env->line_cards[slot_index]->module_count = 0; |
1351 | out_unlock: |
1352 | mutex_unlock(lock: &mlxsw_env->line_cards_lock); |
1353 | } |
1354 | |
1355 | static struct mlxsw_linecards_event_ops mlxsw_env_event_ops = { |
1356 | .got_active = mlxsw_env_got_active, |
1357 | .got_inactive = mlxsw_env_got_inactive, |
1358 | }; |
1359 | |
1360 | static int mlxsw_env_max_module_eeprom_len_query(struct mlxsw_env *mlxsw_env) |
1361 | { |
1362 | char mcam_pl[MLXSW_REG_MCAM_LEN]; |
1363 | bool mcia_128b_supported; |
1364 | int err; |
1365 | |
1366 | mlxsw_reg_mcam_pack(payload: mcam_pl, |
1367 | feat_group: MLXSW_REG_MCAM_FEATURE_GROUP_ENHANCED_FEATURES); |
1368 | err = mlxsw_reg_query(mlxsw_core: mlxsw_env->core, MLXSW_REG(mcam), payload: mcam_pl); |
1369 | if (err) |
1370 | return err; |
1371 | |
1372 | mlxsw_reg_mcam_unpack(payload: mcam_pl, bit: MLXSW_REG_MCAM_MCIA_128B, |
1373 | p_mng_feature_cap_val: &mcia_128b_supported); |
1374 | |
1375 | mlxsw_env->max_eeprom_len = mcia_128b_supported ? 128 : 48; |
1376 | |
1377 | return 0; |
1378 | } |
1379 | |
1380 | int mlxsw_env_init(struct mlxsw_core *mlxsw_core, |
1381 | const struct mlxsw_bus_info *bus_info, |
1382 | struct mlxsw_env **p_env) |
1383 | { |
1384 | u8 module_count, num_of_slots, max_module_count; |
1385 | char mgpir_pl[MLXSW_REG_MGPIR_LEN]; |
1386 | struct mlxsw_env *env; |
1387 | int err; |
1388 | |
1389 | mlxsw_reg_mgpir_pack(payload: mgpir_pl, slot_index: 0); |
1390 | err = mlxsw_reg_query(mlxsw_core, MLXSW_REG(mgpir), payload: mgpir_pl); |
1391 | if (err) |
1392 | return err; |
1393 | |
1394 | mlxsw_reg_mgpir_unpack(payload: mgpir_pl, NULL, NULL, NULL, num_of_modules: &module_count, |
1395 | num_of_slots: &num_of_slots); |
1396 | /* If the system is modular, get the maximum number of modules per-slot. |
1397 | * Otherwise, get the maximum number of modules on the main board. |
1398 | */ |
1399 | max_module_count = num_of_slots ? |
1400 | mlxsw_reg_mgpir_max_modules_per_slot_get(buf: mgpir_pl) : |
1401 | module_count; |
1402 | |
1403 | env = kzalloc(struct_size(env, line_cards, num_of_slots + 1), |
1404 | GFP_KERNEL); |
1405 | if (!env) |
1406 | return -ENOMEM; |
1407 | |
1408 | env->core = mlxsw_core; |
1409 | env->bus_info = bus_info; |
1410 | env->num_of_slots = num_of_slots + 1; |
1411 | env->max_module_count = max_module_count; |
1412 | err = mlxsw_env_line_cards_alloc(env); |
1413 | if (err) |
1414 | goto err_mlxsw_env_line_cards_alloc; |
1415 | |
1416 | mutex_init(&env->line_cards_lock); |
1417 | *p_env = env; |
1418 | |
1419 | err = mlxsw_linecards_event_ops_register(mlxsw_core: env->core, |
1420 | ops: &mlxsw_env_event_ops, priv: env); |
1421 | if (err) |
1422 | goto err_linecards_event_ops_register; |
1423 | |
1424 | err = mlxsw_env_temp_warn_event_register(mlxsw_core); |
1425 | if (err) |
1426 | goto err_temp_warn_event_register; |
1427 | |
1428 | err = mlxsw_env_module_plug_event_register(mlxsw_core); |
1429 | if (err) |
1430 | goto err_module_plug_event_register; |
1431 | |
1432 | /* Set 'module_count' only for main board. Actual count for line card |
1433 | * is to be set after line card is activated. |
1434 | */ |
1435 | env->line_cards[0]->module_count = num_of_slots ? 0 : module_count; |
1436 | /* Enable events only for main board. Line card events are to be |
1437 | * configured only after line card is activated. Before that, access to |
1438 | * modules on line cards is not allowed. |
1439 | */ |
1440 | err = mlxsw_env_module_event_enable(mlxsw_env: env, slot_index: 0); |
1441 | if (err) |
1442 | goto err_mlxsw_env_module_event_enable; |
1443 | |
1444 | err = mlxsw_env_module_type_set(mlxsw_core, slot_index: 0); |
1445 | if (err) |
1446 | goto err_type_set; |
1447 | |
1448 | err = mlxsw_env_max_module_eeprom_len_query(mlxsw_env: env); |
1449 | if (err) |
1450 | goto err_eeprom_len_query; |
1451 | |
1452 | env->line_cards[0]->active = true; |
1453 | |
1454 | return 0; |
1455 | |
1456 | err_eeprom_len_query: |
1457 | err_type_set: |
1458 | mlxsw_env_module_event_disable(mlxsw_env: env, slot_index: 0); |
1459 | err_mlxsw_env_module_event_enable: |
1460 | mlxsw_env_module_plug_event_unregister(mlxsw_env: env); |
1461 | err_module_plug_event_register: |
1462 | mlxsw_env_temp_warn_event_unregister(mlxsw_env: env); |
1463 | err_temp_warn_event_register: |
1464 | mlxsw_linecards_event_ops_unregister(mlxsw_core: env->core, |
1465 | ops: &mlxsw_env_event_ops, priv: env); |
1466 | err_linecards_event_ops_register: |
1467 | mutex_destroy(lock: &env->line_cards_lock); |
1468 | mlxsw_env_line_cards_free(env); |
1469 | err_mlxsw_env_line_cards_alloc: |
1470 | kfree(objp: env); |
1471 | return err; |
1472 | } |
1473 | |
1474 | void mlxsw_env_fini(struct mlxsw_env *env) |
1475 | { |
1476 | env->line_cards[0]->active = false; |
1477 | mlxsw_env_module_event_disable(mlxsw_env: env, slot_index: 0); |
1478 | mlxsw_env_module_plug_event_unregister(mlxsw_env: env); |
1479 | /* Make sure there is no more event work scheduled. */ |
1480 | mlxsw_core_flush_owq(); |
1481 | mlxsw_env_temp_warn_event_unregister(mlxsw_env: env); |
1482 | mlxsw_linecards_event_ops_unregister(mlxsw_core: env->core, |
1483 | ops: &mlxsw_env_event_ops, priv: env); |
1484 | mutex_destroy(lock: &env->line_cards_lock); |
1485 | mlxsw_env_line_cards_free(env); |
1486 | kfree(objp: env); |
1487 | } |
1488 | |