Aurelien Guillaume | 749f387 | 2012-12-02 21:21:01 +0100 | [diff] [blame] | 1 | /* |
| 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 Ravipati | 7a4c422 | 2016-08-23 06:30:31 -0700 | [diff] [blame] | 15 | #include "sff-common.h" |
Aurelien Guillaume | 749f387 | 2012-12-02 21:21:01 +0100 | [diff] [blame] | 16 | |
| 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 Salim | 99bc878 | 2014-10-15 06:09:00 -0400 | [diff] [blame] | 52 | #define SFF_A2_BIAS 100 |
Aurelien Guillaume | 749f387 | 2012-12-02 21:21:01 +0100 | [diff] [blame] | 53 | #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 Guillaume | 749f387 | 2012-12-02 21:21:01 +0100 | [diff] [blame] | 90 | static struct sff8472_aw_flags { |
| 91 | const char *str; /* Human-readable string, null at the end */ |
Ville Skyttä | 500a9f4 | 2013-12-15 14:19:31 +0200 | [diff] [blame] | 92 | int offset; /* A2-relative address offset */ |
Aurelien Guillaume | 749f387 | 2012-12-02 21:21:01 +0100 | [diff] [blame] | 93 | __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 Guillaume | 749f387 | 2012-12-02 21:21:01 +0100 | [diff] [blame] | 123 | /* 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 Ravipati | 7a4c422 | 2016-08-23 06:30:31 -0700 | [diff] [blame] | 146 | static void sff8472_dom_parse(const __u8 *id, struct sff_diags *sd) |
Aurelien Guillaume | 749f387 | 2012-12-02 21:21:01 +0100 | [diff] [blame] | 147 | { |
Aurelien Guillaume | 749f387 | 2012-12-02 21:21:01 +0100 | [diff] [blame] | 148 | 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 Guillaume | 749f387 | 2012-12-02 21:21:01 +0100 | [diff] [blame] | 177 | } |
| 178 | |
| 179 | /* Converts to a float from a big-endian 4-byte source buffer. */ |
| 180 | static 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 Ravipati | 7a4c422 | 2016-08-23 06:30:31 -0700 | [diff] [blame] | 191 | static void sff8472_calibration(const __u8 *id, struct sff_diags *sd) |
Aurelien Guillaume | 749f387 | 2012-12-02 21:21:01 +0100 | [diff] [blame] | 192 | { |
| 193 | int i; |
| 194 | __u16 rx_reading; |
| 195 | |
| 196 | /* Calibration should occur for all values (threshold and current) */ |
Ben Hutchings | df7e421 | 2013-09-22 02:36:42 +0100 | [diff] [blame] | 197 | for (i = 0; i < ARRAY_SIZE(sd->bias_cur); ++i) { |
Aurelien Guillaume | 749f387 | 2012-12-02 21:21:01 +0100 | [diff] [blame] | 198 | /* |
| 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 Ravipati | 7a4c422 | 2016-08-23 06:30:31 -0700 | [diff] [blame] | 225 | static void sff8472_parse_eeprom(const __u8 *id, struct sff_diags *sd) |
Aurelien Guillaume | 749f387 | 2012-12-02 21:21:01 +0100 | [diff] [blame] | 226 | { |
| 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 | |
| 242 | void sff8472_show_all(const __u8 *id) |
| 243 | { |
Eran Ben Elisha | ecdf295 | 2018-10-02 10:24:19 +0300 | [diff] [blame] | 244 | struct sff_diags sd = {0}; |
Aurelien Guillaume | 749f387 | 2012-12-02 21:21:01 +0100 | [diff] [blame] | 245 | 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 Ravipati | 7a4c422 | 2016-08-23 06:30:31 -0700 | [diff] [blame] | 252 | return; |
Aurelien Guillaume | 749f387 | 2012-12-02 21:21:01 +0100 | [diff] [blame] | 253 | } |
| 254 | printf("\t%-41s : Yes\n", "Optical diagnostics support"); |
| 255 | |
Vidya Sagar Ravipati | 7a4c422 | 2016-08-23 06:30:31 -0700 | [diff] [blame] | 256 | PRINT_BIAS("Laser bias current", sd.bias_cur[MCURR]); |
| 257 | PRINT_xX_PWR("Laser output power", sd.tx_power[MCURR]); |
Aurelien Guillaume | 749f387 | 2012-12-02 21:21:01 +0100 | [diff] [blame] | 258 | |
| 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 Ravipati | 7a4c422 | 2016-08-23 06:30:31 -0700 | [diff] [blame] | 264 | PRINT_xX_PWR(rx_power_string, sd.rx_power[MCURR]); |
Aurelien Guillaume | 749f387 | 2012-12-02 21:21:01 +0100 | [diff] [blame] | 265 | |
Vidya Sagar Ravipati | 7a4c422 | 2016-08-23 06:30:31 -0700 | [diff] [blame] | 266 | PRINT_TEMP("Module temperature", sd.sfp_temp[MCURR]); |
| 267 | PRINT_VCC("Module voltage", sd.sfp_voltage[MCURR]); |
Aurelien Guillaume | 749f387 | 2012-12-02 21:21:01 +0100 | [diff] [blame] | 268 | |
| 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 Ravipati | 7a4c422 | 2016-08-23 06:30:31 -0700 | [diff] [blame] | 278 | sff_show_thresholds(sd); |
Aurelien Guillaume | 749f387 | 2012-12-02 21:21:01 +0100 | [diff] [blame] | 279 | } |
Aurelien Guillaume | 749f387 | 2012-12-02 21:21:01 +0100 | [diff] [blame] | 280 | } |
| 281 | |