/* * sfpdiag.c: Implements SFF-8472 optics diagnostics. * * Aurelien Guillaume (C) 2012 * This implementation is loosely based on DOM patches * from Robert Olsson (C) 2009 * and SFF-8472 specs (ftp://ftp.seagate.com/pub/sff/SFF-8472.PDF) * by SFF Committee. */ #include #include #include #include "internal.h" /* Offsets in decimal, for direct comparison with the SFF specs */ /* A0-based EEPROM offsets for DOM support checks */ #define SFF_A0_DOM 92 #define SFF_A0_OPTIONS 93 #define SFF_A0_COMP 94 /* EEPROM bit values for various registers */ #define SFF_A0_DOM_EXTCAL (1 << 4) #define SFF_A0_DOM_INTCAL (1 << 5) #define SFF_A0_DOM_IMPL (1 << 6) #define SFF_A0_DOM_PWRT (1 << 3) #define SFF_A0_OPTIONS_AW (1 << 7) /* * See ethtool.c comments about SFF-8472, this is the offset * at which the A2 page is in the EEPROM blob returned by the * kernel. */ #define SFF_A2_BASE 0x100 /* A2-based offsets for DOM */ #define SFF_A2_TEMP 96 #define SFF_A2_TEMP_HALRM 0 #define SFF_A2_TEMP_LALRM 2 #define SFF_A2_TEMP_HWARN 4 #define SFF_A2_TEMP_LWARN 6 #define SFF_A2_VCC 98 #define SFF_A2_VCC_HALRM 8 #define SFF_A2_VCC_LALRM 10 #define SFF_A2_VCC_HWARN 12 #define SFF_A2_VCC_LWARN 14 #define SFF_A2_BIAS 96 #define SFF_A2_BIAS_HALRM 16 #define SFF_A2_BIAS_LALRM 18 #define SFF_A2_BIAS_HWARN 20 #define SFF_A2_BIAS_LWARN 22 #define SFF_A2_TX_PWR 102 #define SFF_A2_TX_PWR_HALRM 24 #define SFF_A2_TX_PWR_LALRM 26 #define SFF_A2_TX_PWR_HWARN 28 #define SFF_A2_TX_PWR_LWARN 30 #define SFF_A2_RX_PWR 104 #define SFF_A2_RX_PWR_HALRM 32 #define SFF_A2_RX_PWR_LALRM 34 #define SFF_A2_RX_PWR_HWARN 36 #define SFF_A2_RX_PWR_LWARN 38 #define SFF_A2_ALRM_FLG 112 #define SFF_A2_WARN_FLG 116 /* 32-bit little-endian calibration constants */ #define SFF_A2_CAL_RXPWR4 56 #define SFF_A2_CAL_RXPWR3 60 #define SFF_A2_CAL_RXPWR2 64 #define SFF_A2_CAL_RXPWR1 68 #define SFF_A2_CAL_RXPWR0 72 /* 16-bit little endian calibration constants */ #define SFF_A2_CAL_TXI_SLP 76 #define SFF_A2_CAL_TXI_OFF 78 #define SFF_A2_CAL_TXPWR_SLP 80 #define SFF_A2_CAL_TXPWR_OFF 82 #define SFF_A2_CAL_T_SLP 84 #define SFF_A2_CAL_T_OFF 86 #define SFF_A2_CAL_V_SLP 88 #define SFF_A2_CAL_V_OFF 90 struct sff8472_diags { #define MCURR 0 #define LWARN 1 #define HWARN 2 #define LALRM 3 #define HALRM 4 /* [5] tables are current, low/high warn, low/high alarm */ __u8 supports_dom; /* Supports DOM */ __u8 supports_alarms; /* Supports alarm/warning thold */ __u8 calibrated_ext; /* Is externally calibrated */ __u16 bias_cur[5]; /* Measured bias current in 2uA units */ __u16 tx_power[5]; /* Measured TX Power in 0.1uW units */ __u16 rx_power[5]; /* Measured RX Power */ __u8 rx_power_type; /* 0 = OMA, 1 = Average power */ __s16 sfp_temp[5]; /* SFP Temp in 16-bit signed 1/256 Celcius */ __u16 sfp_voltage[5]; /* SFP voltage in 0.1mV units */ }; static struct sff8472_aw_flags { const char *str; /* Human-readable string, null at the end */ int offset; /* A2-relative adress offset */ __u8 value; /* Alarm is on if (offset & value) != 0. */ } sff8472_aw_flags[] = { { "Laser bias current high alarm", SFF_A2_ALRM_FLG, (1 << 3) }, { "Laser bias current low alarm", SFF_A2_ALRM_FLG, (1 << 2) }, { "Laser bias current high warning", SFF_A2_WARN_FLG, (1 << 3) }, { "Laser bias current low warning", SFF_A2_WARN_FLG, (1 << 2) }, { "Laser output power high alarm", SFF_A2_ALRM_FLG, (1 << 1) }, { "Laser output power low alarm", SFF_A2_ALRM_FLG, (1 << 0) }, { "Laser output power high warning", SFF_A2_WARN_FLG, (1 << 1) }, { "Laser output power low warning", SFF_A2_WARN_FLG, (1 << 0) }, { "Module temperature high alarm", SFF_A2_ALRM_FLG, (1 << 7) }, { "Module temperature low alarm", SFF_A2_ALRM_FLG, (1 << 6) }, { "Module temperature high warning", SFF_A2_WARN_FLG, (1 << 7) }, { "Module temperature low warning", SFF_A2_WARN_FLG, (1 << 6) }, { "Module voltage high alarm", SFF_A2_ALRM_FLG, (1 << 5) }, { "Module voltage low alarm", SFF_A2_ALRM_FLG, (1 << 4) }, { "Module voltage high warning", SFF_A2_WARN_FLG, (1 << 5) }, { "Module voltage low warning", SFF_A2_WARN_FLG, (1 << 4) }, { "Laser rx power high alarm", SFF_A2_ALRM_FLG + 1, (1 << 7) }, { "Laser rx power low alarm", SFF_A2_ALRM_FLG + 1, (1 << 6) }, { "Laser rx power high warning", SFF_A2_WARN_FLG + 1, (1 << 7) }, { "Laser rx power low warning", SFF_A2_WARN_FLG + 1, (1 << 6) }, { NULL, 0, 0 }, }; static double convert_mw_to_dbm(double mw) { return (10. * log10(mw / 1000.)) + 30.; } /* Most common case: 16-bit unsigned integer in a certain unit */ #define A2_OFFSET_TO_U16(offset) \ (id[SFF_A2_BASE + (offset)] << 8 | id[SFF_A2_BASE + (offset) + 1]) /* Calibration slope is a number between 0.0 included and 256.0 excluded. */ #define A2_OFFSET_TO_SLP(offset) \ (id[SFF_A2_BASE + (offset)] + id[SFF_A2_BASE + (offset) + 1] / 256.) /* Calibration offset is an integer from -32768 to 32767 */ #define A2_OFFSET_TO_OFF(offset) \ ((__s16)A2_OFFSET_TO_U16(offset)) /* RXPWR(x) are IEEE-754 floating point numbers in big-endian format */ #define A2_OFFSET_TO_RXPWRx(offset) \ (befloattoh((__u32 *)(id + SFF_A2_BASE + (offset)))) /* * 2-byte internal temperature conversions: * First byte is a signed 8-bit integer, which is the temp decimal part * Second byte are 1/256th of degree, which are added to the dec part. */ #define A2_OFFSET_TO_TEMP(offset) ((__s16)A2_OFFSET_TO_U16(offset)) static void sff8472_dom_parse(const __u8 *id, struct sff8472_diags *sd) { sd->bias_cur[MCURR] = A2_OFFSET_TO_U16(SFF_A2_BIAS); sd->bias_cur[HALRM] = A2_OFFSET_TO_U16(SFF_A2_BIAS_HALRM); sd->bias_cur[LALRM] = A2_OFFSET_TO_U16(SFF_A2_BIAS_LALRM); sd->bias_cur[HWARN] = A2_OFFSET_TO_U16(SFF_A2_BIAS_HWARN); sd->bias_cur[LWARN] = A2_OFFSET_TO_U16(SFF_A2_BIAS_LWARN); sd->sfp_voltage[MCURR] = A2_OFFSET_TO_U16(SFF_A2_VCC); sd->sfp_voltage[HALRM] = A2_OFFSET_TO_U16(SFF_A2_VCC_HALRM); sd->sfp_voltage[LALRM] = A2_OFFSET_TO_U16(SFF_A2_VCC_LALRM); sd->sfp_voltage[HWARN] = A2_OFFSET_TO_U16(SFF_A2_VCC_HWARN); sd->sfp_voltage[LWARN] = A2_OFFSET_TO_U16(SFF_A2_VCC_LWARN); sd->tx_power[MCURR] = A2_OFFSET_TO_U16(SFF_A2_TX_PWR); sd->tx_power[HALRM] = A2_OFFSET_TO_U16(SFF_A2_TX_PWR_HALRM); sd->tx_power[LALRM] = A2_OFFSET_TO_U16(SFF_A2_TX_PWR_LALRM); sd->tx_power[HWARN] = A2_OFFSET_TO_U16(SFF_A2_TX_PWR_HWARN); sd->tx_power[LWARN] = A2_OFFSET_TO_U16(SFF_A2_TX_PWR_LWARN); sd->rx_power[MCURR] = A2_OFFSET_TO_U16(SFF_A2_RX_PWR); sd->rx_power[HALRM] = A2_OFFSET_TO_U16(SFF_A2_RX_PWR_HALRM); sd->rx_power[LALRM] = A2_OFFSET_TO_U16(SFF_A2_RX_PWR_LALRM); sd->rx_power[HWARN] = A2_OFFSET_TO_U16(SFF_A2_RX_PWR_HWARN); sd->rx_power[LWARN] = A2_OFFSET_TO_U16(SFF_A2_RX_PWR_LWARN); sd->sfp_temp[MCURR] = A2_OFFSET_TO_TEMP(SFF_A2_TEMP); sd->sfp_temp[HALRM] = A2_OFFSET_TO_TEMP(SFF_A2_TEMP_HALRM); sd->sfp_temp[LALRM] = A2_OFFSET_TO_TEMP(SFF_A2_TEMP_LALRM); sd->sfp_temp[HWARN] = A2_OFFSET_TO_TEMP(SFF_A2_TEMP_HWARN); sd->sfp_temp[LWARN] = A2_OFFSET_TO_TEMP(SFF_A2_TEMP_LWARN); } /* Converts to a float from a big-endian 4-byte source buffer. */ static float befloattoh(const __u32 *source) { union { __u32 src; float dst; } converter; converter.src = ntohl(*source); return converter.dst; } static void sff8472_calibration(const __u8 *id, struct sff8472_diags *sd) { int i; __u16 rx_reading; /* Calibration should occur for all values (threshold and current) */ for (i = 0; i < sizeof(sd->bias_cur); ++i) { /* * Apply calibration formula 1 (Temp., Voltage, Bias, Tx Power) */ sd->bias_cur[i] *= A2_OFFSET_TO_SLP(SFF_A2_CAL_TXI_SLP); sd->tx_power[i] *= A2_OFFSET_TO_SLP(SFF_A2_CAL_TXPWR_SLP); sd->sfp_voltage[i] *= A2_OFFSET_TO_SLP(SFF_A2_CAL_V_SLP); sd->sfp_temp[i] *= A2_OFFSET_TO_SLP(SFF_A2_CAL_T_SLP); sd->bias_cur[i] += A2_OFFSET_TO_OFF(SFF_A2_CAL_TXI_OFF); sd->tx_power[i] += A2_OFFSET_TO_OFF(SFF_A2_CAL_TXPWR_OFF); sd->sfp_voltage[i] += A2_OFFSET_TO_OFF(SFF_A2_CAL_V_OFF); sd->sfp_temp[i] += A2_OFFSET_TO_OFF(SFF_A2_CAL_T_OFF); /* * Apply calibration formula 2 (Rx Power only) */ rx_reading = sd->rx_power[i]; sd->rx_power[i] = A2_OFFSET_TO_RXPWRx(SFF_A2_CAL_RXPWR0); sd->rx_power[i] += rx_reading * A2_OFFSET_TO_RXPWRx(SFF_A2_CAL_RXPWR1); sd->rx_power[i] += rx_reading * A2_OFFSET_TO_RXPWRx(SFF_A2_CAL_RXPWR2); sd->rx_power[i] += rx_reading * A2_OFFSET_TO_RXPWRx(SFF_A2_CAL_RXPWR3); } } static void sff8472_parse_eeprom(const __u8 *id, struct sff8472_diags *sd) { sd->supports_dom = id[SFF_A0_DOM] & SFF_A0_DOM_IMPL; sd->supports_alarms = id[SFF_A0_OPTIONS] & SFF_A0_OPTIONS_AW; sd->calibrated_ext = id[SFF_A0_DOM] & SFF_A0_DOM_EXTCAL; sd->rx_power_type = id[SFF_A0_DOM] & SFF_A0_DOM_PWRT; sff8472_dom_parse(id, sd); /* * If the SFP is externally calibrated, we need to read calibration data * and compensate the already stored readings. */ if (sd->calibrated_ext) sff8472_calibration(id, sd); } void sff8472_show_all(const __u8 *id) { struct sff8472_diags sd; char *rx_power_string = NULL; int i; sff8472_parse_eeprom(id, &sd); if (!sd.supports_dom) { printf("\t%-41s : No\n", "Optical diagnostics support"); return ; } printf("\t%-41s : Yes\n", "Optical diagnostics support"); #define PRINT_BIAS(string, index) \ printf("\t%-41s : %.3f mA\n", (string), \ (double)(sd.bias_cur[(index)] / 500.)) # define PRINT_xX_PWR(string, var, index) \ printf("\t%-41s : %.4f mW / %.2f dBm\n", (string), \ (double)((var)[(index)] / 10000.), \ convert_mw_to_dbm((double)((var)[(index)] / 10000.))) #define PRINT_TEMP(string, index) \ printf("\t%-41s : %.2f degrees C / %.2f degrees F\n", (string), \ (double)(sd.sfp_temp[(index)] / 256.), \ (double)(sd.sfp_temp[(index)] / 256. * 1.8 + 32.)) #define PRINT_VCC(string, index) \ printf("\t%-41s : %.4f V\n", (string), \ (double)(sd.sfp_voltage[(index)] / 10000.)) PRINT_BIAS("Laser bias current", MCURR); PRINT_xX_PWR("Laser output power", sd.tx_power, MCURR); if (!sd.rx_power_type) rx_power_string = "Receiver signal OMA"; else rx_power_string = "Receiver signal average optical power"; PRINT_xX_PWR(rx_power_string, sd.rx_power, MCURR); PRINT_TEMP("Module temperature", MCURR); PRINT_VCC("Module voltage", MCURR); printf("\t%-41s : %s\n", "Alarm/warning flags implemented", (sd.supports_alarms ? "Yes" : "No")); if (sd.supports_alarms) { for (i = 0; sff8472_aw_flags[i].str; ++i) { printf("\t%-41s : %s\n", sff8472_aw_flags[i].str, id[SFF_A2_BASE + sff8472_aw_flags[i].offset] & sff8472_aw_flags[i].value ? "On" : "Off"); } PRINT_BIAS("Laser bias current high alarm threshold", HALRM); PRINT_BIAS("Laser bias current low alarm threshold", LALRM); PRINT_BIAS("Laser bias current high warning threshold", HWARN); PRINT_BIAS("Laser bias current low warning threshold", LWARN); PRINT_xX_PWR("Laser output power high alarm threshold", sd.tx_power, HALRM); PRINT_xX_PWR("Laser output power low alarm threshold", sd.tx_power, LALRM); PRINT_xX_PWR("Laser output power high warning threshold", sd.tx_power, HWARN); PRINT_xX_PWR("Laser output power low warning threshold", sd.tx_power, LWARN); PRINT_TEMP("Module temperature high alarm threshold", HALRM); PRINT_TEMP("Module temperature low alarm threshold", LALRM); PRINT_TEMP("Module temperature high warning threshold", HWARN); PRINT_TEMP("Module temperature low warning threshold", LWARN); PRINT_VCC("Module voltage high alarm threshold", HALRM); PRINT_VCC("Module voltage low alarm threshold", LALRM); PRINT_VCC("Module voltage high warning threshold", HWARN); PRINT_VCC("Module voltage low warning threshold", LWARN); PRINT_xX_PWR("Laser rx power high alarm threshold", sd.rx_power, HALRM); PRINT_xX_PWR("Laser rx power low alarm threshold", sd.rx_power, LALRM); PRINT_xX_PWR("Laser rx power high warning threshold", sd.rx_power, HWARN); PRINT_xX_PWR("Laser rx power low warning threshold", sd.rx_power, LWARN); } }