Branch data Line data Source code
1 : : /* SPDX-License-Identifier: BSD-3-Clause
2 : : * Copyright(c) 2022 Intel Corporation
3 : : * Implements SFF-8472 optics diagnostics.
4 : : */
5 : :
6 : : #include <stdio.h>
7 : :
8 : : #include "sff_common.h"
9 : :
10 : : /* Offsets in decimal, for direct comparison with the SFF specs */
11 : :
12 : : /* A0-based EEPROM offsets for DOM support checks */
13 : : #define SFF_A0_DOM 92
14 : : #define SFF_A0_OPTIONS 93
15 : : #define SFF_A0_COMP 94
16 : :
17 : : /* EEPROM bit values for various registers */
18 : : #define SFF_A0_DOM_EXTCAL RTE_BIT32(4)
19 : : #define SFF_A0_DOM_INTCAL RTE_BIT32(5)
20 : : #define SFF_A0_DOM_IMPL RTE_BIT32(6)
21 : : #define SFF_A0_DOM_PWRT RTE_BIT32(3)
22 : :
23 : : #define SFF_A0_OPTIONS_AW RTE_BIT32(7)
24 : :
25 : : /*
26 : : * This is the offset at which the A2 page is in the EEPROM
27 : : * blob returned by the kernel.
28 : : */
29 : : #define SFF_A2_BASE 0x100
30 : :
31 : : /* A2-based offsets for DOM */
32 : : #define SFF_A2_TEMP 96
33 : : #define SFF_A2_TEMP_HALRM 0
34 : : #define SFF_A2_TEMP_LALRM 2
35 : : #define SFF_A2_TEMP_HWARN 4
36 : : #define SFF_A2_TEMP_LWARN 6
37 : :
38 : : #define SFF_A2_VCC 98
39 : : #define SFF_A2_VCC_HALRM 8
40 : : #define SFF_A2_VCC_LALRM 10
41 : : #define SFF_A2_VCC_HWARN 12
42 : : #define SFF_A2_VCC_LWARN 14
43 : :
44 : : #define SFF_A2_BIAS 100
45 : : #define SFF_A2_BIAS_HALRM 16
46 : : #define SFF_A2_BIAS_LALRM 18
47 : : #define SFF_A2_BIAS_HWARN 20
48 : : #define SFF_A2_BIAS_LWARN 22
49 : :
50 : : #define SFF_A2_TX_PWR 102
51 : : #define SFF_A2_TX_PWR_HALRM 24
52 : : #define SFF_A2_TX_PWR_LALRM 26
53 : : #define SFF_A2_TX_PWR_HWARN 28
54 : : #define SFF_A2_TX_PWR_LWARN 30
55 : :
56 : : #define SFF_A2_RX_PWR 104
57 : : #define SFF_A2_RX_PWR_HALRM 32
58 : : #define SFF_A2_RX_PWR_LALRM 34
59 : : #define SFF_A2_RX_PWR_HWARN 36
60 : : #define SFF_A2_RX_PWR_LWARN 38
61 : :
62 : : #define SFF_A2_ALRM_FLG 112
63 : : #define SFF_A2_WARN_FLG 116
64 : :
65 : : /* 32-bit little-endian calibration constants */
66 : : #define SFF_A2_CAL_RXPWR4 56
67 : : #define SFF_A2_CAL_RXPWR3 60
68 : : #define SFF_A2_CAL_RXPWR2 64
69 : : #define SFF_A2_CAL_RXPWR1 68
70 : : #define SFF_A2_CAL_RXPWR0 72
71 : :
72 : : /* 16-bit little endian calibration constants */
73 : : #define SFF_A2_CAL_TXI_SLP 76
74 : : #define SFF_A2_CAL_TXI_OFF 78
75 : : #define SFF_A2_CAL_TXPWR_SLP 80
76 : : #define SFF_A2_CAL_TXPWR_OFF 82
77 : : #define SFF_A2_CAL_T_SLP 84
78 : : #define SFF_A2_CAL_T_OFF 86
79 : : #define SFF_A2_CAL_V_SLP 88
80 : : #define SFF_A2_CAL_V_OFF 90
81 : :
82 : : static struct sff_8472_aw_flags {
83 : : const char *str; /* Human-readable string, null at the end */
84 : : int offset; /* A2-relative address offset */
85 : : uint8_t value; /* Alarm is on if (offset & value) != 0. */
86 : : } sff_8472_aw_flags[] = {
87 : : { "Laser bias current high alarm", SFF_A2_ALRM_FLG, RTE_BIT32(3) },
88 : : { "Laser bias current low alarm", SFF_A2_ALRM_FLG, RTE_BIT32(2) },
89 : : { "Laser bias current high warning", SFF_A2_WARN_FLG, RTE_BIT32(3) },
90 : : { "Laser bias current low warning", SFF_A2_WARN_FLG, RTE_BIT32(2) },
91 : :
92 : : { "Laser output power high alarm", SFF_A2_ALRM_FLG, RTE_BIT32(1) },
93 : : { "Laser output power low alarm", SFF_A2_ALRM_FLG, RTE_BIT32(0) },
94 : : { "Laser output power high warning", SFF_A2_WARN_FLG, RTE_BIT32(1) },
95 : : { "Laser output power low warning", SFF_A2_WARN_FLG, RTE_BIT32(0) },
96 : :
97 : : { "Module temperature high alarm", SFF_A2_ALRM_FLG, RTE_BIT32(7) },
98 : : { "Module temperature low alarm", SFF_A2_ALRM_FLG, RTE_BIT32(6) },
99 : : { "Module temperature high warning", SFF_A2_WARN_FLG, RTE_BIT32(7) },
100 : : { "Module temperature low warning", SFF_A2_WARN_FLG, RTE_BIT32(6) },
101 : :
102 : : { "Module voltage high alarm", SFF_A2_ALRM_FLG, RTE_BIT32(5) },
103 : : { "Module voltage low alarm", SFF_A2_ALRM_FLG, RTE_BIT32(4) },
104 : : { "Module voltage high warning", SFF_A2_WARN_FLG, RTE_BIT32(5) },
105 : : { "Module voltage low warning", SFF_A2_WARN_FLG, RTE_BIT32(4) },
106 : :
107 : : { "Laser rx power high alarm", SFF_A2_ALRM_FLG + 1, RTE_BIT32(7) },
108 : : { "Laser rx power low alarm", SFF_A2_ALRM_FLG + 1, RTE_BIT32(6) },
109 : : { "Laser rx power high warning", SFF_A2_WARN_FLG + 1, RTE_BIT32(7) },
110 : : { "Laser rx power low warning", SFF_A2_WARN_FLG + 1, RTE_BIT32(6) },
111 : :
112 : : { NULL, 0, 0 },
113 : : };
114 : :
115 : : /* Most common case: 16-bit unsigned integer in a certain unit */
116 : : #define A2_OFFSET_TO_U16(offset) \
117 : : (data[SFF_A2_BASE + (offset)] << 8 | data[SFF_A2_BASE + (offset) + 1])
118 : :
119 : : /* Calibration slope is a number between 0.0 included and 256.0 excluded. */
120 : : #define A2_OFFSET_TO_SLP(offset) \
121 : : (data[SFF_A2_BASE + (offset)] + data[SFF_A2_BASE + (offset) + 1] / 256.)
122 : :
123 : : /* Calibration offset is an integer from -32768 to 32767 */
124 : : #define A2_OFFSET_TO_OFF(offset) \
125 : : ((int16_t)A2_OFFSET_TO_U16(offset))
126 : :
127 : : /* RXPWR(x) are IEEE-754 floating point numbers in big-endian format */
128 : : #define A2_OFFSET_TO_RXPWRx(offset) \
129 : : (befloattoh((const uint32_t *)(data + SFF_A2_BASE + (offset))))
130 : :
131 : : /*
132 : : * 2-byte internal temperature conversions:
133 : : * First byte is a signed 8-bit integer, which is the temp decimal part
134 : : * Second byte are 1/256th of degree, which are added to the dec part.
135 : : */
136 : : #define A2_OFFSET_TO_TEMP(offset) ((int16_t)A2_OFFSET_TO_U16(offset))
137 : :
138 : 0 : static void sff_8472_dom_parse(const uint8_t *data, struct sff_diags *sd)
139 : : {
140 : 0 : sd->bias_cur[SFF_MCURR] = A2_OFFSET_TO_U16(SFF_A2_BIAS);
141 : 0 : sd->bias_cur[SFF_HALRM] = A2_OFFSET_TO_U16(SFF_A2_BIAS_HALRM);
142 : 0 : sd->bias_cur[SFF_LALRM] = A2_OFFSET_TO_U16(SFF_A2_BIAS_LALRM);
143 : 0 : sd->bias_cur[SFF_HWARN] = A2_OFFSET_TO_U16(SFF_A2_BIAS_HWARN);
144 : 0 : sd->bias_cur[SFF_LWARN] = A2_OFFSET_TO_U16(SFF_A2_BIAS_LWARN);
145 : :
146 : 0 : sd->sfp_voltage[SFF_MCURR] = A2_OFFSET_TO_U16(SFF_A2_VCC);
147 : 0 : sd->sfp_voltage[SFF_HALRM] = A2_OFFSET_TO_U16(SFF_A2_VCC_HALRM);
148 : 0 : sd->sfp_voltage[SFF_LALRM] = A2_OFFSET_TO_U16(SFF_A2_VCC_LALRM);
149 : 0 : sd->sfp_voltage[SFF_HWARN] = A2_OFFSET_TO_U16(SFF_A2_VCC_HWARN);
150 : 0 : sd->sfp_voltage[SFF_LWARN] = A2_OFFSET_TO_U16(SFF_A2_VCC_LWARN);
151 : :
152 : 0 : sd->tx_power[SFF_MCURR] = A2_OFFSET_TO_U16(SFF_A2_TX_PWR);
153 : 0 : sd->tx_power[SFF_HALRM] = A2_OFFSET_TO_U16(SFF_A2_TX_PWR_HALRM);
154 : 0 : sd->tx_power[SFF_LALRM] = A2_OFFSET_TO_U16(SFF_A2_TX_PWR_LALRM);
155 : 0 : sd->tx_power[SFF_HWARN] = A2_OFFSET_TO_U16(SFF_A2_TX_PWR_HWARN);
156 : 0 : sd->tx_power[SFF_LWARN] = A2_OFFSET_TO_U16(SFF_A2_TX_PWR_LWARN);
157 : :
158 : 0 : sd->rx_power[SFF_MCURR] = A2_OFFSET_TO_U16(SFF_A2_RX_PWR);
159 : 0 : sd->rx_power[SFF_HALRM] = A2_OFFSET_TO_U16(SFF_A2_RX_PWR_HALRM);
160 : 0 : sd->rx_power[SFF_LALRM] = A2_OFFSET_TO_U16(SFF_A2_RX_PWR_LALRM);
161 : 0 : sd->rx_power[SFF_HWARN] = A2_OFFSET_TO_U16(SFF_A2_RX_PWR_HWARN);
162 : 0 : sd->rx_power[SFF_LWARN] = A2_OFFSET_TO_U16(SFF_A2_RX_PWR_LWARN);
163 : :
164 : 0 : sd->sfp_temp[SFF_MCURR] = A2_OFFSET_TO_TEMP(SFF_A2_TEMP);
165 : 0 : sd->sfp_temp[SFF_HALRM] = A2_OFFSET_TO_TEMP(SFF_A2_TEMP_HALRM);
166 : 0 : sd->sfp_temp[SFF_LALRM] = A2_OFFSET_TO_TEMP(SFF_A2_TEMP_LALRM);
167 : 0 : sd->sfp_temp[SFF_HWARN] = A2_OFFSET_TO_TEMP(SFF_A2_TEMP_HWARN);
168 : 0 : sd->sfp_temp[SFF_LWARN] = A2_OFFSET_TO_TEMP(SFF_A2_TEMP_LWARN);
169 : 0 : }
170 : :
171 : : /* Converts to a float from a big-endian 4-byte source buffer. */
172 : : static float befloattoh(const uint32_t *source)
173 : : {
174 : : union {
175 : : uint32_t src;
176 : : float dst;
177 : : } converter;
178 : :
179 : 0 : converter.src = ntohl(*source);
180 : 0 : return converter.dst;
181 : : }
182 : :
183 : 0 : static void sff_8472_calibration(const uint8_t *data, struct sff_diags *sd)
184 : : {
185 : : unsigned long i;
186 : : uint16_t rx_reading;
187 : :
188 : : /* Calibration should occur for all values (threshold and current) */
189 [ # # ]: 0 : for (i = 0; i < RTE_DIM(sd->bias_cur); ++i) {
190 : : /*
191 : : * Apply calibration formula 1 (Temp., Voltage, Bias, Tx Power)
192 : : */
193 : 0 : sd->bias_cur[i] *= A2_OFFSET_TO_SLP(SFF_A2_CAL_TXI_SLP);
194 : 0 : sd->tx_power[i] *= A2_OFFSET_TO_SLP(SFF_A2_CAL_TXPWR_SLP);
195 : 0 : sd->sfp_voltage[i] *= A2_OFFSET_TO_SLP(SFF_A2_CAL_V_SLP);
196 : 0 : sd->sfp_temp[i] *= A2_OFFSET_TO_SLP(SFF_A2_CAL_T_SLP);
197 : :
198 : 0 : sd->bias_cur[i] += A2_OFFSET_TO_OFF(SFF_A2_CAL_TXI_OFF);
199 : 0 : sd->tx_power[i] += A2_OFFSET_TO_OFF(SFF_A2_CAL_TXPWR_OFF);
200 : 0 : sd->sfp_voltage[i] += A2_OFFSET_TO_OFF(SFF_A2_CAL_V_OFF);
201 : 0 : sd->sfp_temp[i] += A2_OFFSET_TO_OFF(SFF_A2_CAL_T_OFF);
202 : :
203 : : /*
204 : : * Apply calibration formula 2 (Rx Power only)
205 : : */
206 : 0 : rx_reading = sd->rx_power[i];
207 : 0 : sd->rx_power[i] = A2_OFFSET_TO_RXPWRx(SFF_A2_CAL_RXPWR0);
208 : 0 : sd->rx_power[i] += rx_reading *
209 : : A2_OFFSET_TO_RXPWRx(SFF_A2_CAL_RXPWR1);
210 : 0 : sd->rx_power[i] += rx_reading *
211 : : A2_OFFSET_TO_RXPWRx(SFF_A2_CAL_RXPWR2);
212 : 0 : sd->rx_power[i] += rx_reading *
213 : : A2_OFFSET_TO_RXPWRx(SFF_A2_CAL_RXPWR3);
214 : : }
215 : 0 : }
216 : :
217 : 0 : static void sff_8472_parse_eeprom(const uint8_t *data, struct sff_diags *sd)
218 : : {
219 : 0 : sd->supports_dom = data[SFF_A0_DOM] & SFF_A0_DOM_IMPL;
220 : 0 : sd->supports_alarms = data[SFF_A0_OPTIONS] & SFF_A0_OPTIONS_AW;
221 : 0 : sd->calibrated_ext = data[SFF_A0_DOM] & SFF_A0_DOM_EXTCAL;
222 : 0 : sd->rx_power_type = data[SFF_A0_DOM] & SFF_A0_DOM_PWRT;
223 : :
224 : 0 : sff_8472_dom_parse(data, sd);
225 : :
226 : : /*
227 : : * If the SFP is externally calibrated, we need to read calibration data
228 : : * and compensate the already stored readings.
229 : : */
230 [ # # ]: 0 : if (sd->calibrated_ext)
231 : 0 : sff_8472_calibration(data, sd);
232 : 0 : }
233 : :
234 : 0 : void sff_8472_show_all(const uint8_t *data, struct rte_tel_data *d)
235 : : {
236 : 0 : struct sff_diags sd = {0};
237 : : const char *rx_power_string = NULL;
238 : : char val_string[SFF_ITEM_VAL_COMPOSE_SIZE];
239 : : int i;
240 : :
241 : 0 : sff_8472_parse_eeprom(data, &sd);
242 : :
243 [ # # ]: 0 : if (!sd.supports_dom) {
244 : 0 : ssf_add_dict_string(d, "Optical diagnostics support", "No");
245 : 0 : return;
246 : : }
247 : 0 : ssf_add_dict_string(d, "Optical diagnostics support", "Yes");
248 : :
249 : 0 : SFF_SPRINT_BIAS(val_string, sd.bias_cur[SFF_MCURR]);
250 : 0 : ssf_add_dict_string(d, "Laser bias current", val_string);
251 : :
252 : 0 : SFF_SPRINT_xX_PWR(val_string, sd.tx_power[SFF_MCURR]);
253 : 0 : ssf_add_dict_string(d, "Laser output power", val_string);
254 : :
255 [ # # ]: 0 : if (!sd.rx_power_type)
256 : : rx_power_string = "Receiver signal OMA";
257 : : else
258 : : rx_power_string = "Receiver signal average optical power";
259 : :
260 : 0 : SFF_SPRINT_xX_PWR(val_string, sd.rx_power[SFF_MCURR]);
261 : 0 : ssf_add_dict_string(d, rx_power_string, val_string);
262 : :
263 : 0 : SFF_SPRINT_TEMP(val_string, sd.sfp_temp[SFF_MCURR]);
264 : 0 : ssf_add_dict_string(d, "Module temperature", val_string);
265 : :
266 : 0 : SFF_SPRINT_VCC(val_string, sd.sfp_voltage[SFF_MCURR]);
267 : 0 : ssf_add_dict_string(d, "Module voltage", val_string);
268 : :
269 : 0 : ssf_add_dict_string(d, "Alarm/warning flags implemented",
270 [ # # ]: 0 : (sd.supports_alarms ? "Yes" : "No"));
271 : :
272 [ # # ]: 0 : if (sd.supports_alarms) {
273 [ # # ]: 0 : for (i = 0; sff_8472_aw_flags[i].str; ++i) {
274 : 0 : ssf_add_dict_string(d, sff_8472_aw_flags[i].str,
275 : 0 : data[SFF_A2_BASE + sff_8472_aw_flags[i].offset]
276 [ # # ]: 0 : & sff_8472_aw_flags[i].value ? "On" : "Off");
277 : : }
278 : 0 : sff_show_thresholds(sd, d);
279 : : }
280 : : }
|