blob: fa41651422ea88f013bd280aef57ed3d61e2e01c [file] [log] [blame]
Aurelien Guillaume749f3872012-12-02 21:21:01 +01001/*
2 * sfpdiag.c: Implements SFF-8472 optics diagnostics.
3 *
4 * Aurelien Guillaume <aurelien@iwi.me> (C) 2012
5 * This implementation is loosely based on DOM patches
6 * from Robert Olsson <robert@herjulf.se> (C) 2009
7 * and SFF-8472 specs (ftp://ftp.seagate.com/pub/sff/SFF-8472.PDF)
8 * by SFF Committee.
9 */
10
11#include <stdio.h>
12#include <math.h>
13#include <arpa/inet.h>
14#include "internal.h"
Vidya Sagar Ravipati7a4c4222016-08-23 06:30:31 -070015#include "sff-common.h"
Aurelien Guillaume749f3872012-12-02 21:21:01 +010016
17/* Offsets in decimal, for direct comparison with the SFF specs */
18
19/* A0-based EEPROM offsets for DOM support checks */
20#define SFF_A0_DOM 92
21#define SFF_A0_OPTIONS 93
22#define SFF_A0_COMP 94
23
24/* EEPROM bit values for various registers */
25#define SFF_A0_DOM_EXTCAL (1 << 4)
26#define SFF_A0_DOM_INTCAL (1 << 5)
27#define SFF_A0_DOM_IMPL (1 << 6)
28#define SFF_A0_DOM_PWRT (1 << 3)
29
30#define SFF_A0_OPTIONS_AW (1 << 7)
31
32/*
33 * See ethtool.c comments about SFF-8472, this is the offset
34 * at which the A2 page is in the EEPROM blob returned by the
35 * kernel.
36 */
37#define SFF_A2_BASE 0x100
38
39/* A2-based offsets for DOM */
40#define SFF_A2_TEMP 96
41#define SFF_A2_TEMP_HALRM 0
42#define SFF_A2_TEMP_LALRM 2
43#define SFF_A2_TEMP_HWARN 4
44#define SFF_A2_TEMP_LWARN 6
45
46#define SFF_A2_VCC 98
47#define SFF_A2_VCC_HALRM 8
48#define SFF_A2_VCC_LALRM 10
49#define SFF_A2_VCC_HWARN 12
50#define SFF_A2_VCC_LWARN 14
51
Jamal Hadi Salim99bc8782014-10-15 06:09:00 -040052#define SFF_A2_BIAS 100
Aurelien Guillaume749f3872012-12-02 21:21:01 +010053#define SFF_A2_BIAS_HALRM 16
54#define SFF_A2_BIAS_LALRM 18
55#define SFF_A2_BIAS_HWARN 20
56#define SFF_A2_BIAS_LWARN 22
57
58#define SFF_A2_TX_PWR 102
59#define SFF_A2_TX_PWR_HALRM 24
60#define SFF_A2_TX_PWR_LALRM 26
61#define SFF_A2_TX_PWR_HWARN 28
62#define SFF_A2_TX_PWR_LWARN 30
63
64#define SFF_A2_RX_PWR 104
65#define SFF_A2_RX_PWR_HALRM 32
66#define SFF_A2_RX_PWR_LALRM 34
67#define SFF_A2_RX_PWR_HWARN 36
68#define SFF_A2_RX_PWR_LWARN 38
69
70#define SFF_A2_ALRM_FLG 112
71#define SFF_A2_WARN_FLG 116
72
73/* 32-bit little-endian calibration constants */
74#define SFF_A2_CAL_RXPWR4 56
75#define SFF_A2_CAL_RXPWR3 60
76#define SFF_A2_CAL_RXPWR2 64
77#define SFF_A2_CAL_RXPWR1 68
78#define SFF_A2_CAL_RXPWR0 72
79
80/* 16-bit little endian calibration constants */
81#define SFF_A2_CAL_TXI_SLP 76
82#define SFF_A2_CAL_TXI_OFF 78
83#define SFF_A2_CAL_TXPWR_SLP 80
84#define SFF_A2_CAL_TXPWR_OFF 82
85#define SFF_A2_CAL_T_SLP 84
86#define SFF_A2_CAL_T_OFF 86
87#define SFF_A2_CAL_V_SLP 88
88#define SFF_A2_CAL_V_OFF 90
89
Aurelien Guillaume749f3872012-12-02 21:21:01 +010090static struct sff8472_aw_flags {
91 const char *str; /* Human-readable string, null at the end */
Ville Skyttä500a9f42013-12-15 14:19:31 +020092 int offset; /* A2-relative address offset */
Aurelien Guillaume749f3872012-12-02 21:21:01 +010093 __u8 value; /* Alarm is on if (offset & value) != 0. */
94} sff8472_aw_flags[] = {
95 { "Laser bias current high alarm", SFF_A2_ALRM_FLG, (1 << 3) },
96 { "Laser bias current low alarm", SFF_A2_ALRM_FLG, (1 << 2) },
97 { "Laser bias current high warning", SFF_A2_WARN_FLG, (1 << 3) },
98 { "Laser bias current low warning", SFF_A2_WARN_FLG, (1 << 2) },
99
100 { "Laser output power high alarm", SFF_A2_ALRM_FLG, (1 << 1) },
101 { "Laser output power low alarm", SFF_A2_ALRM_FLG, (1 << 0) },
102 { "Laser output power high warning", SFF_A2_WARN_FLG, (1 << 1) },
103 { "Laser output power low warning", SFF_A2_WARN_FLG, (1 << 0) },
104
105 { "Module temperature high alarm", SFF_A2_ALRM_FLG, (1 << 7) },
106 { "Module temperature low alarm", SFF_A2_ALRM_FLG, (1 << 6) },
107 { "Module temperature high warning", SFF_A2_WARN_FLG, (1 << 7) },
108 { "Module temperature low warning", SFF_A2_WARN_FLG, (1 << 6) },
109
110 { "Module voltage high alarm", SFF_A2_ALRM_FLG, (1 << 5) },
111 { "Module voltage low alarm", SFF_A2_ALRM_FLG, (1 << 4) },
112 { "Module voltage high warning", SFF_A2_WARN_FLG, (1 << 5) },
113 { "Module voltage low warning", SFF_A2_WARN_FLG, (1 << 4) },
114
115 { "Laser rx power high alarm", SFF_A2_ALRM_FLG + 1, (1 << 7) },
116 { "Laser rx power low alarm", SFF_A2_ALRM_FLG + 1, (1 << 6) },
117 { "Laser rx power high warning", SFF_A2_WARN_FLG + 1, (1 << 7) },
118 { "Laser rx power low warning", SFF_A2_WARN_FLG + 1, (1 << 6) },
119
120 { NULL, 0, 0 },
121};
122
Aurelien Guillaume749f3872012-12-02 21:21:01 +0100123/* Most common case: 16-bit unsigned integer in a certain unit */
124#define A2_OFFSET_TO_U16(offset) \
125 (id[SFF_A2_BASE + (offset)] << 8 | id[SFF_A2_BASE + (offset) + 1])
126
127/* Calibration slope is a number between 0.0 included and 256.0 excluded. */
128#define A2_OFFSET_TO_SLP(offset) \
129 (id[SFF_A2_BASE + (offset)] + id[SFF_A2_BASE + (offset) + 1] / 256.)
130
131/* Calibration offset is an integer from -32768 to 32767 */
132#define A2_OFFSET_TO_OFF(offset) \
133 ((__s16)A2_OFFSET_TO_U16(offset))
134
135/* RXPWR(x) are IEEE-754 floating point numbers in big-endian format */
136#define A2_OFFSET_TO_RXPWRx(offset) \
137 (befloattoh((__u32 *)(id + SFF_A2_BASE + (offset))))
138
139/*
140 * 2-byte internal temperature conversions:
141 * First byte is a signed 8-bit integer, which is the temp decimal part
142 * Second byte are 1/256th of degree, which are added to the dec part.
143 */
144#define A2_OFFSET_TO_TEMP(offset) ((__s16)A2_OFFSET_TO_U16(offset))
145
Vidya Sagar Ravipati7a4c4222016-08-23 06:30:31 -0700146static void sff8472_dom_parse(const __u8 *id, struct sff_diags *sd)
Aurelien Guillaume749f3872012-12-02 21:21:01 +0100147{
Aurelien Guillaume749f3872012-12-02 21:21:01 +0100148 sd->bias_cur[MCURR] = A2_OFFSET_TO_U16(SFF_A2_BIAS);
149 sd->bias_cur[HALRM] = A2_OFFSET_TO_U16(SFF_A2_BIAS_HALRM);
150 sd->bias_cur[LALRM] = A2_OFFSET_TO_U16(SFF_A2_BIAS_LALRM);
151 sd->bias_cur[HWARN] = A2_OFFSET_TO_U16(SFF_A2_BIAS_HWARN);
152 sd->bias_cur[LWARN] = A2_OFFSET_TO_U16(SFF_A2_BIAS_LWARN);
153
154 sd->sfp_voltage[MCURR] = A2_OFFSET_TO_U16(SFF_A2_VCC);
155 sd->sfp_voltage[HALRM] = A2_OFFSET_TO_U16(SFF_A2_VCC_HALRM);
156 sd->sfp_voltage[LALRM] = A2_OFFSET_TO_U16(SFF_A2_VCC_LALRM);
157 sd->sfp_voltage[HWARN] = A2_OFFSET_TO_U16(SFF_A2_VCC_HWARN);
158 sd->sfp_voltage[LWARN] = A2_OFFSET_TO_U16(SFF_A2_VCC_LWARN);
159
160 sd->tx_power[MCURR] = A2_OFFSET_TO_U16(SFF_A2_TX_PWR);
161 sd->tx_power[HALRM] = A2_OFFSET_TO_U16(SFF_A2_TX_PWR_HALRM);
162 sd->tx_power[LALRM] = A2_OFFSET_TO_U16(SFF_A2_TX_PWR_LALRM);
163 sd->tx_power[HWARN] = A2_OFFSET_TO_U16(SFF_A2_TX_PWR_HWARN);
164 sd->tx_power[LWARN] = A2_OFFSET_TO_U16(SFF_A2_TX_PWR_LWARN);
165
166 sd->rx_power[MCURR] = A2_OFFSET_TO_U16(SFF_A2_RX_PWR);
167 sd->rx_power[HALRM] = A2_OFFSET_TO_U16(SFF_A2_RX_PWR_HALRM);
168 sd->rx_power[LALRM] = A2_OFFSET_TO_U16(SFF_A2_RX_PWR_LALRM);
169 sd->rx_power[HWARN] = A2_OFFSET_TO_U16(SFF_A2_RX_PWR_HWARN);
170 sd->rx_power[LWARN] = A2_OFFSET_TO_U16(SFF_A2_RX_PWR_LWARN);
171
172 sd->sfp_temp[MCURR] = A2_OFFSET_TO_TEMP(SFF_A2_TEMP);
173 sd->sfp_temp[HALRM] = A2_OFFSET_TO_TEMP(SFF_A2_TEMP_HALRM);
174 sd->sfp_temp[LALRM] = A2_OFFSET_TO_TEMP(SFF_A2_TEMP_LALRM);
175 sd->sfp_temp[HWARN] = A2_OFFSET_TO_TEMP(SFF_A2_TEMP_HWARN);
176 sd->sfp_temp[LWARN] = A2_OFFSET_TO_TEMP(SFF_A2_TEMP_LWARN);
Aurelien Guillaume749f3872012-12-02 21:21:01 +0100177}
178
179/* Converts to a float from a big-endian 4-byte source buffer. */
180static float befloattoh(const __u32 *source)
181{
182 union {
183 __u32 src;
184 float dst;
185 } converter;
186
187 converter.src = ntohl(*source);
188 return converter.dst;
189}
190
Vidya Sagar Ravipati7a4c4222016-08-23 06:30:31 -0700191static void sff8472_calibration(const __u8 *id, struct sff_diags *sd)
Aurelien Guillaume749f3872012-12-02 21:21:01 +0100192{
193 int i;
194 __u16 rx_reading;
195
196 /* Calibration should occur for all values (threshold and current) */
Ben Hutchingsdf7e4212013-09-22 02:36:42 +0100197 for (i = 0; i < ARRAY_SIZE(sd->bias_cur); ++i) {
Aurelien Guillaume749f3872012-12-02 21:21:01 +0100198 /*
199 * Apply calibration formula 1 (Temp., Voltage, Bias, Tx Power)
200 */
201 sd->bias_cur[i] *= A2_OFFSET_TO_SLP(SFF_A2_CAL_TXI_SLP);
202 sd->tx_power[i] *= A2_OFFSET_TO_SLP(SFF_A2_CAL_TXPWR_SLP);
203 sd->sfp_voltage[i] *= A2_OFFSET_TO_SLP(SFF_A2_CAL_V_SLP);
204 sd->sfp_temp[i] *= A2_OFFSET_TO_SLP(SFF_A2_CAL_T_SLP);
205
206 sd->bias_cur[i] += A2_OFFSET_TO_OFF(SFF_A2_CAL_TXI_OFF);
207 sd->tx_power[i] += A2_OFFSET_TO_OFF(SFF_A2_CAL_TXPWR_OFF);
208 sd->sfp_voltage[i] += A2_OFFSET_TO_OFF(SFF_A2_CAL_V_OFF);
209 sd->sfp_temp[i] += A2_OFFSET_TO_OFF(SFF_A2_CAL_T_OFF);
210
211 /*
212 * Apply calibration formula 2 (Rx Power only)
213 */
214 rx_reading = sd->rx_power[i];
215 sd->rx_power[i] = A2_OFFSET_TO_RXPWRx(SFF_A2_CAL_RXPWR0);
216 sd->rx_power[i] += rx_reading *
217 A2_OFFSET_TO_RXPWRx(SFF_A2_CAL_RXPWR1);
218 sd->rx_power[i] += rx_reading *
219 A2_OFFSET_TO_RXPWRx(SFF_A2_CAL_RXPWR2);
220 sd->rx_power[i] += rx_reading *
221 A2_OFFSET_TO_RXPWRx(SFF_A2_CAL_RXPWR3);
222 }
223}
224
Vidya Sagar Ravipati7a4c4222016-08-23 06:30:31 -0700225static void sff8472_parse_eeprom(const __u8 *id, struct sff_diags *sd)
Aurelien Guillaume749f3872012-12-02 21:21:01 +0100226{
227 sd->supports_dom = id[SFF_A0_DOM] & SFF_A0_DOM_IMPL;
228 sd->supports_alarms = id[SFF_A0_OPTIONS] & SFF_A0_OPTIONS_AW;
229 sd->calibrated_ext = id[SFF_A0_DOM] & SFF_A0_DOM_EXTCAL;
230 sd->rx_power_type = id[SFF_A0_DOM] & SFF_A0_DOM_PWRT;
231
232 sff8472_dom_parse(id, sd);
233
234 /*
235 * If the SFP is externally calibrated, we need to read calibration data
236 * and compensate the already stored readings.
237 */
238 if (sd->calibrated_ext)
239 sff8472_calibration(id, sd);
240}
241
242void sff8472_show_all(const __u8 *id)
243{
Eran Ben Elishaecdf2952018-10-02 10:24:19 +0300244 struct sff_diags sd = {0};
Aurelien Guillaume749f3872012-12-02 21:21:01 +0100245 char *rx_power_string = NULL;
246 int i;
247
248 sff8472_parse_eeprom(id, &sd);
249
250 if (!sd.supports_dom) {
251 printf("\t%-41s : No\n", "Optical diagnostics support");
Vidya Sagar Ravipati7a4c4222016-08-23 06:30:31 -0700252 return;
Aurelien Guillaume749f3872012-12-02 21:21:01 +0100253 }
254 printf("\t%-41s : Yes\n", "Optical diagnostics support");
255
Vidya Sagar Ravipati7a4c4222016-08-23 06:30:31 -0700256 PRINT_BIAS("Laser bias current", sd.bias_cur[MCURR]);
257 PRINT_xX_PWR("Laser output power", sd.tx_power[MCURR]);
Aurelien Guillaume749f3872012-12-02 21:21:01 +0100258
259 if (!sd.rx_power_type)
260 rx_power_string = "Receiver signal OMA";
261 else
262 rx_power_string = "Receiver signal average optical power";
263
Vidya Sagar Ravipati7a4c4222016-08-23 06:30:31 -0700264 PRINT_xX_PWR(rx_power_string, sd.rx_power[MCURR]);
Aurelien Guillaume749f3872012-12-02 21:21:01 +0100265
Vidya Sagar Ravipati7a4c4222016-08-23 06:30:31 -0700266 PRINT_TEMP("Module temperature", sd.sfp_temp[MCURR]);
267 PRINT_VCC("Module voltage", sd.sfp_voltage[MCURR]);
Aurelien Guillaume749f3872012-12-02 21:21:01 +0100268
269 printf("\t%-41s : %s\n", "Alarm/warning flags implemented",
270 (sd.supports_alarms ? "Yes" : "No"));
271 if (sd.supports_alarms) {
272
273 for (i = 0; sff8472_aw_flags[i].str; ++i) {
274 printf("\t%-41s : %s\n", sff8472_aw_flags[i].str,
275 id[SFF_A2_BASE + sff8472_aw_flags[i].offset]
276 & sff8472_aw_flags[i].value ? "On" : "Off");
277 }
Vidya Sagar Ravipati7a4c4222016-08-23 06:30:31 -0700278 sff_show_thresholds(sd);
Aurelien Guillaume749f3872012-12-02 21:21:01 +0100279 }
Aurelien Guillaume749f3872012-12-02 21:21:01 +0100280}
281