lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20180717201308.GD15476@roeck-us.net>
Date:   Tue, 17 Jul 2018 13:13:08 -0700
From:   Guenter Roeck <linux@...ck-us.net>
To:     Andrew Lunn <andrew@...n.ch>
Cc:     David Miller <davem@...emloft.net>,
        netdev <netdev@...r.kernel.org>,
        Florian Fainelli <f.fainelli@...il.com>,
        Russell King <rmk+kernel@....linux.org.uk>,
        linux-hwmon@...r.kernel.org
Subject: Re: [PATCH net-next 4/4] net: phy: sfp: Add HWMON support for module
 sensors

On Tue, Jul 17, 2018 at 09:48:13PM +0200, Andrew Lunn wrote:
> SFP modules can contain a number of sensors. The EEPROM also contains
> recommended alarm and critical values for each sensor, and indications
> of if these have been exceeded. Export this information via
> HWMON. Currently temperature, VCC, bias current, transmit power, and
> possibly receiver power is supported.
> 
> The sensors in the modules can either return calibrate or uncalibrated
> values. Uncalibrated values need to be manipulated, using coefficients
> provided in the SFP EEPROM. Uncalibrated receive power values require
> floating point maths in order to calibrate them. Performing this in
> the kernel is hard. So if the SFP module indicates it uses
> uncalibrated values, RX power is not made available.
> 
> With this hwmon device, it is possible to view the sensor values using
> lm-sensors programs:
> 
> in0:          +3.29 V  (crit min =  +2.90 V, min =  +3.00 V)
>                        (max =  +3.60 V, crit max =  +3.70 V)
> temp1:        +33.0°C  (low  =  -5.0°C, high = +80.0°C)
>                        (crit low = -10.0°C, crit = +85.0°C)
> power1:      1000.00 nW (max = 794.00 uW, min =  50.00 uW)  ALARM (LCRIT)
>                        (lcrit =  40.00 uW, crit = 1000.00 uW)
> curr1:        +0.00 A  (crit min =  +0.00 A, min =  +0.00 A)  ALARM (LCRIT, MIN)
>                        (max =  +0.01 A, crit max =  +0.01 A)
> 
> The scaling sensors performs on the bias current is not particularly
> good. The raw values are more useful:
> 
> curr1:
>   curr1_input: 0.000
>   curr1_min: 0.002
>   curr1_max: 0.010
>   curr1_lcrit: 0.000
>   curr1_crit: 0.011
>   curr1_min_alarm: 1.000
>   curr1_max_alarm: 0.000
>   curr1_lcrit_alarm: 1.000
>   curr1_crit_alarm: 0.000
> 
> In order to keep the I2C overhead to a minimum, the constant values,
> such as limits and calibration coefficients are read once at module
> insertion time. Thus only reading *_input and *_alarm properties
> requires i2c read operations.
> 
> Signed-off-by: Andrew Lunn <andrew@...n.ch>

Acked-by: Guenter Roeck <linux@...ck-us.net>

> ---
> RFC->V1
> Use __be16 when reading sensor
> DIV_ROUND_CLOSEST()
> Don't use devm_hwmon_* to fix lifetime issues
> Replace invalid chars in hwmon name with _
> ---
>  drivers/net/phy/Kconfig |   1 +
>  drivers/net/phy/sfp.c   | 727 ++++++++++++++++++++++++++++++++++++++++
>  include/linux/sfp.h     |  72 +++-
>  3 files changed, 799 insertions(+), 1 deletion(-)
> 
> diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
> index 7761536974bf..f31ae4faf4e5 100644
> --- a/drivers/net/phy/Kconfig
> +++ b/drivers/net/phy/Kconfig
> @@ -215,6 +215,7 @@ config SFP
>  	tristate "SFP cage support"
>  	depends on I2C
>  	select MDIO_I2C
> +	imply HWMON
>  
>  config AMD_PHY
>  	tristate "AMD PHYs"
> diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c
> index c4c92db86dfa..5661226cf75b 100644
> --- a/drivers/net/phy/sfp.c
> +++ b/drivers/net/phy/sfp.c
> @@ -1,5 +1,7 @@
> +#include <linux/ctype.h>
>  #include <linux/delay.h>
>  #include <linux/gpio/consumer.h>
> +#include <linux/hwmon.h>
>  #include <linux/i2c.h>
>  #include <linux/interrupt.h>
>  #include <linux/jiffies.h>
> @@ -131,6 +133,12 @@ struct sfp {
>  	unsigned int sm_retries;
>  
>  	struct sfp_eeprom_id id;
> +#if IS_ENABLED(CONFIG_HWMON)
> +	struct sfp_diag diag;
> +	struct device *hwmon_dev;
> +	char *hwmon_name;
> +#endif
> +
>  };
>  
>  static bool sff_module_supported(const struct sfp_eeprom_id *id)
> @@ -316,6 +324,719 @@ static unsigned int sfp_check(void *buf, size_t len)
>  	return check;
>  }
>  
> +/* hwmon */
> +#if IS_ENABLED(CONFIG_HWMON)
> +static umode_t sfp_hwmon_is_visible(const void *data,
> +				    enum hwmon_sensor_types type,
> +				    u32 attr, int channel)
> +{
> +	const struct sfp *sfp = data;
> +
> +	switch (type) {
> +	case hwmon_temp:
> +		switch (attr) {
> +		case hwmon_temp_input:
> +		case hwmon_temp_min_alarm:
> +		case hwmon_temp_max_alarm:
> +		case hwmon_temp_lcrit_alarm:
> +		case hwmon_temp_crit_alarm:
> +		case hwmon_temp_min:
> +		case hwmon_temp_max:
> +		case hwmon_temp_lcrit:
> +		case hwmon_temp_crit:
> +			return 0444;
> +		default:
> +			return 0;
> +		}
> +	case hwmon_in:
> +		switch (attr) {
> +		case hwmon_in_input:
> +		case hwmon_in_min_alarm:
> +		case hwmon_in_max_alarm:
> +		case hwmon_in_lcrit_alarm:
> +		case hwmon_in_crit_alarm:
> +		case hwmon_in_min:
> +		case hwmon_in_max:
> +		case hwmon_in_lcrit:
> +		case hwmon_in_crit:
> +			return 0444;
> +		default:
> +			return 0;
> +		}
> +	case hwmon_curr:
> +		switch (attr) {
> +		case hwmon_curr_input:
> +		case hwmon_curr_min_alarm:
> +		case hwmon_curr_max_alarm:
> +		case hwmon_curr_lcrit_alarm:
> +		case hwmon_curr_crit_alarm:
> +		case hwmon_curr_min:
> +		case hwmon_curr_max:
> +		case hwmon_curr_lcrit:
> +		case hwmon_curr_crit:
> +			return 0444;
> +		default:
> +			return 0;
> +		}
> +	case hwmon_power:
> +		/* External calibration of receive power requires
> +		 * floating point arithmetic. Doing that in the kernel
> +		 * is not easy, so just skip it. If the module does
> +		 * not require external calibration, we can however
> +		 * show receiver power, since FP is then not needed.
> +		 */
> +		if (sfp->id.ext.diagmon & SFP_DIAGMON_EXT_CAL &&
> +		    channel == 1)
> +			return 0;
> +		switch (attr) {
> +		case hwmon_power_input:
> +		case hwmon_power_min_alarm:
> +		case hwmon_power_max_alarm:
> +		case hwmon_power_lcrit_alarm:
> +		case hwmon_power_crit_alarm:
> +		case hwmon_power_min:
> +		case hwmon_power_max:
> +		case hwmon_power_lcrit:
> +		case hwmon_power_crit:
> +			return 0444;
> +		default:
> +			return 0;
> +		}
> +	default:
> +		return 0;
> +	}
> +}
> +
> +static int sfp_hwmon_read_sensor(struct sfp *sfp, int reg, long *value)
> +{
> +	__be16 val;
> +	int err;
> +
> +	err = sfp_read(sfp, true, reg, &val, sizeof(val));
> +	if (err < 0)
> +		return err;
> +
> +	*value = be16_to_cpu(val);
> +
> +	return 0;
> +}
> +
> +static void sfp_hwmon_to_rx_power(long *value)
> +{
> +	*value = DIV_ROUND_CLOSEST(*value, 100);
> +}
> +
> +static void sfp_hwmon_calibrate(struct sfp *sfp, unsigned int slope, int offset,
> +				long *value)
> +{
> +	if (sfp->id.ext.diagmon & SFP_DIAGMON_EXT_CAL)
> +		*value = DIV_ROUND_CLOSEST(*value * slope, 256) + offset;
> +}
> +
> +static void sfp_hwmon_calibrate_temp(struct sfp *sfp, long *value)
> +{
> +	sfp_hwmon_calibrate(sfp, be16_to_cpu(sfp->diag.cal_t_slope),
> +			    be16_to_cpu(sfp->diag.cal_t_offset), value);
> +
> +	if (*value >= 0x8000)
> +		*value -= 0x10000;
> +
> +	*value = DIV_ROUND_CLOSEST(*value * 1000, 256);
> +}
> +
> +static void sfp_hwmon_calibrate_vcc(struct sfp *sfp, long *value)
> +{
> +	sfp_hwmon_calibrate(sfp, be16_to_cpu(sfp->diag.cal_v_slope),
> +			    be16_to_cpu(sfp->diag.cal_v_offset), value);
> +
> +	*value = DIV_ROUND_CLOSEST(*value, 10);
> +}
> +
> +static void sfp_hwmon_calibrate_bias(struct sfp *sfp, long *value)
> +{
> +	sfp_hwmon_calibrate(sfp, be16_to_cpu(sfp->diag.cal_txi_slope),
> +			    be16_to_cpu(sfp->diag.cal_txi_offset), value);
> +
> +	*value = DIV_ROUND_CLOSEST(*value, 500);
> +}
> +
> +static void sfp_hwmon_calibrate_tx_power(struct sfp *sfp, long *value)
> +{
> +	sfp_hwmon_calibrate(sfp, be16_to_cpu(sfp->diag.cal_txpwr_slope),
> +			    be16_to_cpu(sfp->diag.cal_txpwr_offset), value);
> +
> +	*value = DIV_ROUND_CLOSEST(*value, 10);
> +}
> +
> +static int sfp_hwmon_read_temp(struct sfp *sfp, int reg, long *value)
> +{
> +	int err;
> +
> +	err = sfp_hwmon_read_sensor(sfp, reg, value);
> +	if (err < 0)
> +		return err;
> +
> +	sfp_hwmon_calibrate_temp(sfp, value);
> +
> +	return 0;
> +}
> +
> +static int sfp_hwmon_read_vcc(struct sfp *sfp, int reg, long *value)
> +{
> +	int err;
> +
> +	err = sfp_hwmon_read_sensor(sfp, reg, value);
> +	if (err < 0)
> +		return err;
> +
> +	sfp_hwmon_calibrate_vcc(sfp, value);
> +
> +	return 0;
> +}
> +
> +static int sfp_hwmon_read_bias(struct sfp *sfp, int reg, long *value)
> +{
> +	int err;
> +
> +	err = sfp_hwmon_read_sensor(sfp, reg, value);
> +	if (err < 0)
> +		return err;
> +
> +	sfp_hwmon_calibrate_bias(sfp, value);
> +
> +	return 0;
> +}
> +
> +static int sfp_hwmon_read_tx_power(struct sfp *sfp, int reg, long *value)
> +{
> +	int err;
> +
> +	err = sfp_hwmon_read_sensor(sfp, reg, value);
> +	if (err < 0)
> +		return err;
> +
> +	sfp_hwmon_calibrate_tx_power(sfp, value);
> +
> +	return 0;
> +}
> +
> +static int sfp_hwmon_read_rx_power(struct sfp *sfp, int reg, long *value)
> +{
> +	int err;
> +
> +	err = sfp_hwmon_read_sensor(sfp, reg, value);
> +	if (err < 0)
> +		return err;
> +
> +	sfp_hwmon_to_rx_power(value);
> +
> +	return 0;
> +}
> +
> +static int sfp_hwmon_temp(struct sfp *sfp, u32 attr, long *value)
> +{
> +	u8 status;
> +	int err;
> +
> +	switch (attr) {
> +	case hwmon_temp_input:
> +		return sfp_hwmon_read_temp(sfp, SFP_TEMP, value);
> +
> +	case hwmon_temp_lcrit:
> +		*value = be16_to_cpu(sfp->diag.temp_low_alarm);
> +		sfp_hwmon_calibrate_temp(sfp, value);
> +		return 0;
> +
> +	case hwmon_temp_min:
> +		*value = be16_to_cpu(sfp->diag.temp_low_warn);
> +		sfp_hwmon_calibrate_temp(sfp, value);
> +		return 0;
> +	case hwmon_temp_max:
> +		*value = be16_to_cpu(sfp->diag.temp_high_warn);
> +		sfp_hwmon_calibrate_temp(sfp, value);
> +		return 0;
> +
> +	case hwmon_temp_crit:
> +		*value = be16_to_cpu(sfp->diag.temp_high_alarm);
> +		sfp_hwmon_calibrate_temp(sfp, value);
> +		return 0;
> +
> +	case hwmon_temp_lcrit_alarm:
> +		err = sfp_read(sfp, true, SFP_ALARM0, &status, sizeof(status));
> +		if (err < 0)
> +			return err;
> +
> +		*value = !!(status & SFP_ALARM0_TEMP_LOW);
> +		return 0;
> +
> +	case hwmon_temp_min_alarm:
> +		err = sfp_read(sfp, true, SFP_WARN0, &status, sizeof(status));
> +		if (err < 0)
> +			return err;
> +
> +		*value = !!(status & SFP_WARN0_TEMP_LOW);
> +		return 0;
> +
> +	case hwmon_temp_max_alarm:
> +		err = sfp_read(sfp, true, SFP_WARN0, &status, sizeof(status));
> +		if (err < 0)
> +			return err;
> +
> +		*value = !!(status & SFP_WARN0_TEMP_HIGH);
> +		return 0;
> +
> +	case hwmon_temp_crit_alarm:
> +		err = sfp_read(sfp, true, SFP_ALARM0, &status, sizeof(status));
> +		if (err < 0)
> +			return err;
> +
> +		*value = !!(status & SFP_ALARM0_TEMP_HIGH);
> +		return 0;
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +
> +	return -EOPNOTSUPP;
> +}
> +
> +static int sfp_hwmon_vcc(struct sfp *sfp, u32 attr, long *value)
> +{
> +	u8 status;
> +	int err;
> +
> +	switch (attr) {
> +	case hwmon_in_input:
> +		return sfp_hwmon_read_vcc(sfp, SFP_VCC, value);
> +
> +	case hwmon_in_lcrit:
> +		*value = be16_to_cpu(sfp->diag.volt_low_alarm);
> +		sfp_hwmon_calibrate_vcc(sfp, value);
> +		return 0;
> +
> +	case hwmon_in_min:
> +		*value = be16_to_cpu(sfp->diag.volt_low_warn);
> +		sfp_hwmon_calibrate_vcc(sfp, value);
> +		return 0;
> +
> +	case hwmon_in_max:
> +		*value = be16_to_cpu(sfp->diag.volt_high_warn);
> +		sfp_hwmon_calibrate_vcc(sfp, value);
> +		return 0;
> +
> +	case hwmon_in_crit:
> +		*value = be16_to_cpu(sfp->diag.volt_high_alarm);
> +		sfp_hwmon_calibrate_vcc(sfp, value);
> +		return 0;
> +
> +	case hwmon_in_lcrit_alarm:
> +		err = sfp_read(sfp, true, SFP_ALARM0, &status, sizeof(status));
> +		if (err < 0)
> +			return err;
> +
> +		*value = !!(status & SFP_ALARM0_VCC_LOW);
> +		return 0;
> +
> +	case hwmon_in_min_alarm:
> +		err = sfp_read(sfp, true, SFP_WARN0, &status, sizeof(status));
> +		if (err < 0)
> +			return err;
> +
> +		*value = !!(status & SFP_WARN0_VCC_LOW);
> +		return 0;
> +
> +	case hwmon_in_max_alarm:
> +		err = sfp_read(sfp, true, SFP_WARN0, &status, sizeof(status));
> +		if (err < 0)
> +			return err;
> +
> +		*value = !!(status & SFP_WARN0_VCC_HIGH);
> +		return 0;
> +
> +	case hwmon_in_crit_alarm:
> +		err = sfp_read(sfp, true, SFP_ALARM0, &status, sizeof(status));
> +		if (err < 0)
> +			return err;
> +
> +		*value = !!(status & SFP_ALARM0_VCC_HIGH);
> +		return 0;
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +
> +	return -EOPNOTSUPP;
> +}
> +
> +static int sfp_hwmon_bias(struct sfp *sfp, u32 attr, long *value)
> +{
> +	u8 status;
> +	int err;
> +
> +	switch (attr) {
> +	case hwmon_curr_input:
> +		return sfp_hwmon_read_bias(sfp, SFP_TX_BIAS, value);
> +
> +	case hwmon_curr_lcrit:
> +		*value = be16_to_cpu(sfp->diag.bias_low_alarm);
> +		sfp_hwmon_calibrate_bias(sfp, value);
> +		return 0;
> +
> +	case hwmon_curr_min:
> +		*value = be16_to_cpu(sfp->diag.bias_low_warn);
> +		sfp_hwmon_calibrate_bias(sfp, value);
> +		return 0;
> +
> +	case hwmon_curr_max:
> +		*value = be16_to_cpu(sfp->diag.bias_high_warn);
> +		sfp_hwmon_calibrate_bias(sfp, value);
> +		return 0;
> +
> +	case hwmon_curr_crit:
> +		*value = be16_to_cpu(sfp->diag.bias_high_alarm);
> +		sfp_hwmon_calibrate_bias(sfp, value);
> +		return 0;
> +
> +	case hwmon_curr_lcrit_alarm:
> +		err = sfp_read(sfp, true, SFP_ALARM0, &status, sizeof(status));
> +		if (err < 0)
> +			return err;
> +
> +		*value = !!(status & SFP_ALARM0_TX_BIAS_LOW);
> +		return 0;
> +
> +	case hwmon_curr_min_alarm:
> +		err = sfp_read(sfp, true, SFP_WARN0, &status, sizeof(status));
> +		if (err < 0)
> +			return err;
> +
> +		*value = !!(status & SFP_WARN0_TX_BIAS_LOW);
> +		return 0;
> +
> +	case hwmon_curr_max_alarm:
> +		err = sfp_read(sfp, true, SFP_WARN0, &status, sizeof(status));
> +		if (err < 0)
> +			return err;
> +
> +		*value = !!(status & SFP_WARN0_TX_BIAS_HIGH);
> +		return 0;
> +
> +	case hwmon_curr_crit_alarm:
> +		err = sfp_read(sfp, true, SFP_ALARM0, &status, sizeof(status));
> +		if (err < 0)
> +			return err;
> +
> +		*value = !!(status & SFP_ALARM0_TX_BIAS_HIGH);
> +		return 0;
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +
> +	return -EOPNOTSUPP;
> +}
> +
> +static int sfp_hwmon_tx_power(struct sfp *sfp, u32 attr, long *value)
> +{
> +	u8 status;
> +	int err;
> +
> +	switch (attr) {
> +	case hwmon_power_input:
> +		return sfp_hwmon_read_tx_power(sfp, SFP_TX_POWER, value);
> +
> +	case hwmon_power_lcrit:
> +		*value = be16_to_cpu(sfp->diag.txpwr_low_alarm);
> +		sfp_hwmon_calibrate_tx_power(sfp, value);
> +		return 0;
> +
> +	case hwmon_power_min:
> +		*value = be16_to_cpu(sfp->diag.txpwr_low_warn);
> +		sfp_hwmon_calibrate_tx_power(sfp, value);
> +		return 0;
> +
> +	case hwmon_power_max:
> +		*value = be16_to_cpu(sfp->diag.txpwr_high_warn);
> +		sfp_hwmon_calibrate_tx_power(sfp, value);
> +		return 0;
> +
> +	case hwmon_power_crit:
> +		*value = be16_to_cpu(sfp->diag.txpwr_high_alarm);
> +		sfp_hwmon_calibrate_tx_power(sfp, value);
> +		return 0;
> +
> +	case hwmon_power_lcrit_alarm:
> +		err = sfp_read(sfp, true, SFP_ALARM0, &status, sizeof(status));
> +		if (err < 0)
> +			return err;
> +
> +		*value = !!(status & SFP_ALARM0_TXPWR_LOW);
> +		return 0;
> +
> +	case hwmon_power_min_alarm:
> +		err = sfp_read(sfp, true, SFP_WARN0, &status, sizeof(status));
> +		if (err < 0)
> +			return err;
> +
> +		*value = !!(status & SFP_WARN0_TXPWR_LOW);
> +		return 0;
> +
> +	case hwmon_power_max_alarm:
> +		err = sfp_read(sfp, true, SFP_WARN0, &status, sizeof(status));
> +		if (err < 0)
> +			return err;
> +
> +		*value = !!(status & SFP_WARN0_TXPWR_HIGH);
> +		return 0;
> +
> +	case hwmon_power_crit_alarm:
> +		err = sfp_read(sfp, true, SFP_ALARM0, &status, sizeof(status));
> +		if (err < 0)
> +			return err;
> +
> +		*value = !!(status & SFP_ALARM0_TXPWR_HIGH);
> +		return 0;
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +
> +	return -EOPNOTSUPP;
> +}
> +
> +static int sfp_hwmon_rx_power(struct sfp *sfp, u32 attr, long *value)
> +{
> +	u8 status;
> +	int err;
> +
> +	switch (attr) {
> +	case hwmon_power_input:
> +		return sfp_hwmon_read_rx_power(sfp, SFP_RX_POWER, value);
> +
> +	case hwmon_power_lcrit:
> +		*value = be16_to_cpu(sfp->diag.rxpwr_low_alarm);
> +		sfp_hwmon_to_rx_power(value);
> +		return 0;
> +
> +	case hwmon_power_min:
> +		*value = be16_to_cpu(sfp->diag.rxpwr_low_warn);
> +		sfp_hwmon_to_rx_power(value);
> +		return 0;
> +
> +	case hwmon_power_max:
> +		*value = be16_to_cpu(sfp->diag.rxpwr_high_warn);
> +		sfp_hwmon_to_rx_power(value);
> +		return 0;
> +
> +	case hwmon_power_crit:
> +		*value = be16_to_cpu(sfp->diag.rxpwr_high_alarm);
> +		sfp_hwmon_to_rx_power(value);
> +		return 0;
> +
> +	case hwmon_power_lcrit_alarm:
> +		err = sfp_read(sfp, true, SFP_ALARM1, &status, sizeof(status));
> +		if (err < 0)
> +			return err;
> +
> +		*value = !!(status & SFP_ALARM1_RXPWR_LOW);
> +		return 0;
> +
> +	case hwmon_power_min_alarm:
> +		err = sfp_read(sfp, true, SFP_WARN1, &status, sizeof(status));
> +		if (err < 0)
> +			return err;
> +
> +		*value = !!(status & SFP_WARN1_RXPWR_LOW);
> +		return 0;
> +
> +	case hwmon_power_max_alarm:
> +		err = sfp_read(sfp, true, SFP_WARN1, &status, sizeof(status));
> +		if (err < 0)
> +			return err;
> +
> +		*value = !!(status & SFP_WARN1_RXPWR_HIGH);
> +		return 0;
> +
> +	case hwmon_power_crit_alarm:
> +		err = sfp_read(sfp, true, SFP_ALARM1, &status, sizeof(status));
> +		if (err < 0)
> +			return err;
> +
> +		*value = !!(status & SFP_ALARM1_RXPWR_HIGH);
> +		return 0;
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +
> +	return -EOPNOTSUPP;
> +}
> +
> +static int sfp_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
> +			  u32 attr, int channel, long *value)
> +{
> +	struct sfp *sfp = dev_get_drvdata(dev);
> +
> +	switch (type) {
> +	case hwmon_temp:
> +		return sfp_hwmon_temp(sfp, attr, value);
> +	case hwmon_in:
> +		return sfp_hwmon_vcc(sfp, attr, value);
> +	case hwmon_curr:
> +		return sfp_hwmon_bias(sfp, attr, value);
> +	case hwmon_power:
> +		switch (channel) {
> +		case 0:
> +			return sfp_hwmon_tx_power(sfp, attr, value);
> +		case 1:
> +			return sfp_hwmon_rx_power(sfp, attr, value);
> +		default:
> +			return -EOPNOTSUPP;
> +		}
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +}
> +
> +static const struct hwmon_ops sfp_hwmon_ops = {
> +	.is_visible = sfp_hwmon_is_visible,
> +	.read = sfp_hwmon_read,
> +};
> +
> +static u32 sfp_hwmon_chip_config[] = {
> +	HWMON_C_REGISTER_TZ,
> +	0,
> +};
> +
> +static const struct hwmon_channel_info sfp_hwmon_chip = {
> +	.type = hwmon_chip,
> +	.config = sfp_hwmon_chip_config,
> +};
> +
> +static u32 sfp_hwmon_temp_config[] = {
> +	HWMON_T_INPUT |
> +	HWMON_T_MAX | HWMON_T_MIN |
> +	HWMON_T_MAX_ALARM | HWMON_T_MIN_ALARM |
> +	HWMON_T_CRIT | HWMON_T_LCRIT |
> +	HWMON_T_CRIT_ALARM | HWMON_T_LCRIT_ALARM,
> +	0,
> +};
> +
> +static const struct hwmon_channel_info sfp_hwmon_temp_channel_info = {
> +	.type = hwmon_temp,
> +	.config = sfp_hwmon_temp_config,
> +};
> +
> +static u32 sfp_hwmon_vcc_config[] = {
> +	HWMON_I_INPUT |
> +	HWMON_I_MAX | HWMON_I_MIN |
> +	HWMON_I_MAX_ALARM | HWMON_I_MIN_ALARM |
> +	HWMON_I_CRIT | HWMON_I_LCRIT |
> +	HWMON_I_CRIT_ALARM | HWMON_I_LCRIT_ALARM,
> +	0,
> +};
> +
> +static const struct hwmon_channel_info sfp_hwmon_vcc_channel_info = {
> +	.type = hwmon_in,
> +	.config = sfp_hwmon_vcc_config,
> +};
> +
> +static u32 sfp_hwmon_bias_config[] = {
> +	HWMON_C_INPUT |
> +	HWMON_C_MAX | HWMON_C_MIN |
> +	HWMON_C_MAX_ALARM | HWMON_C_MIN_ALARM |
> +	HWMON_C_CRIT | HWMON_C_LCRIT |
> +	HWMON_C_CRIT_ALARM | HWMON_C_LCRIT_ALARM,
> +	0,
> +};
> +
> +static const struct hwmon_channel_info sfp_hwmon_bias_channel_info = {
> +	.type = hwmon_curr,
> +	.config = sfp_hwmon_bias_config,
> +};
> +
> +static u32 sfp_hwmon_power_config[] = {
> +	/* Transmit power */
> +	HWMON_P_INPUT |
> +	HWMON_P_MAX | HWMON_P_MIN |
> +	HWMON_P_MAX_ALARM | HWMON_P_MIN_ALARM |
> +	HWMON_P_CRIT | HWMON_P_LCRIT |
> +	HWMON_P_CRIT_ALARM | HWMON_P_LCRIT_ALARM,
> +	/* Receive power */
> +	HWMON_P_INPUT |
> +	HWMON_P_MAX | HWMON_P_MIN |
> +	HWMON_P_MAX_ALARM | HWMON_P_MIN_ALARM |
> +	HWMON_P_CRIT | HWMON_P_LCRIT |
> +	HWMON_P_CRIT_ALARM | HWMON_P_LCRIT_ALARM,
> +	0,
> +};
> +
> +static const struct hwmon_channel_info sfp_hwmon_power_channel_info = {
> +	.type = hwmon_power,
> +	.config = sfp_hwmon_power_config,
> +};
> +
> +static const struct hwmon_channel_info *sfp_hwmon_info[] = {
> +	&sfp_hwmon_chip,
> +	&sfp_hwmon_vcc_channel_info,
> +	&sfp_hwmon_temp_channel_info,
> +	&sfp_hwmon_bias_channel_info,
> +	&sfp_hwmon_power_channel_info,
> +	NULL,
> +};
> +
> +static const struct hwmon_chip_info sfp_hwmon_chip_info = {
> +	.ops = &sfp_hwmon_ops,
> +	.info = sfp_hwmon_info,
> +};
> +
> +static int sfp_hwmon_insert(struct sfp *sfp)
> +{
> +	int err, i;
> +
> +	if (sfp->id.ext.sff8472_compliance == SFP_SFF8472_COMPLIANCE_NONE)
> +		return 0;
> +
> +	if (!(sfp->id.ext.diagmon & SFP_DIAGMON_DDM))
> +		return 0;
> +
> +	if (sfp->id.ext.diagmon & SFP_DIAGMON_ADDRMODE)
> +		/* This driver in general does not support address
> +		 * change.
> +		 */
> +		return 0;
> +
> +	err = sfp_read(sfp, true, 0, &sfp->diag, sizeof(sfp->diag));
> +	if (err < 0)
> +		return err;
> +
> +	sfp->hwmon_name = kstrdup(dev_name(sfp->dev), GFP_KERNEL);
> +	if (!sfp->hwmon_name)
> +		return -ENODEV;
> +
> +	for (i = 0; sfp->hwmon_name[i]; i++)
> +		if (hwmon_is_bad_char(sfp->hwmon_name[i]))
> +			sfp->hwmon_name[i] = '_';
> +
> +	sfp->hwmon_dev = hwmon_device_register_with_info(sfp->dev,
> +							 sfp->hwmon_name, sfp,
> +							 &sfp_hwmon_chip_info,
> +							 NULL);
> +
> +	return PTR_ERR_OR_ZERO(sfp->hwmon_dev);
> +}
> +
> +static void sfp_hwmon_remove(struct sfp *sfp)
> +{
> +	hwmon_device_unregister(sfp->hwmon_dev);
> +	kfree(sfp->hwmon_name);
> +}
> +#else
> +static int sfp_hwmon_insert(struct sfp *sfp)
> +{
> +	return 0;
> +}
> +
> +static void sfp_hwmon_remove(struct sfp *sfp)
> +{
> +}
> +#endif
> +
>  /* Helpers */
>  static void sfp_module_tx_disable(struct sfp *sfp)
>  {
> @@ -636,6 +1357,10 @@ static int sfp_sm_mod_probe(struct sfp *sfp)
>  		dev_warn(sfp->dev,
>  			 "module address swap to access page 0xA2 is not supported.\n");
>  
> +	ret = sfp_hwmon_insert(sfp);
> +	if (ret < 0)
> +		return ret;
> +
>  	ret = sfp_module_insert(sfp->sfp_bus, &sfp->id);
>  	if (ret < 0)
>  		return ret;
> @@ -647,6 +1372,8 @@ static void sfp_sm_mod_remove(struct sfp *sfp)
>  {
>  	sfp_module_remove(sfp->sfp_bus);
>  
> +	sfp_hwmon_remove(sfp);
> +
>  	if (sfp->mod_phy)
>  		sfp_sm_phy_detach(sfp);
>  
> diff --git a/include/linux/sfp.h b/include/linux/sfp.h
> index ebce9e24906a..d37518e89db2 100644
> --- a/include/linux/sfp.h
> +++ b/include/linux/sfp.h
> @@ -231,6 +231,50 @@ struct sfp_eeprom_id {
>  	struct sfp_eeprom_ext ext;
>  } __packed;
>  
> +struct sfp_diag {
> +	__be16 temp_high_alarm;
> +	__be16 temp_low_alarm;
> +	__be16 temp_high_warn;
> +	__be16 temp_low_warn;
> +	__be16 volt_high_alarm;
> +	__be16 volt_low_alarm;
> +	__be16 volt_high_warn;
> +	__be16 volt_low_warn;
> +	__be16 bias_high_alarm;
> +	__be16 bias_low_alarm;
> +	__be16 bias_high_warn;
> +	__be16 bias_low_warn;
> +	__be16 txpwr_high_alarm;
> +	__be16 txpwr_low_alarm;
> +	__be16 txpwr_high_warn;
> +	__be16 txpwr_low_warn;
> +	__be16 rxpwr_high_alarm;
> +	__be16 rxpwr_low_alarm;
> +	__be16 rxpwr_high_warn;
> +	__be16 rxpwr_low_warn;
> +	__be16 laser_temp_high_alarm;
> +	__be16 laser_temp_low_alarm;
> +	__be16 laser_temp_high_warn;
> +	__be16 laser_temp_low_warn;
> +	__be16 tec_cur_high_alarm;
> +	__be16 tec_cur_low_alarm;
> +	__be16 tec_cur_high_warn;
> +	__be16 tec_cur_low_warn;
> +	__be32 cal_rxpwr4;
> +	__be32 cal_rxpwr3;
> +	__be32 cal_rxpwr2;
> +	__be32 cal_rxpwr1;
> +	__be32 cal_rxpwr0;
> +	__be16 cal_txi_slope;
> +	__be16 cal_txi_offset;
> +	__be16 cal_txpwr_slope;
> +	__be16 cal_txpwr_offset;
> +	__be16 cal_t_slope;
> +	__be16 cal_t_offset;
> +	__be16 cal_v_slope;
> +	__be16 cal_v_offset;
> +} __packed;
> +
>  /* SFP EEPROM registers */
>  enum {
>  	SFP_PHYS_ID			= 0x00,
> @@ -384,7 +428,33 @@ enum {
>  	SFP_TEC_CUR			= 0x6c,
>  
>  	SFP_STATUS			= 0x6e,
> -	SFP_ALARM			= 0x70,
> +	SFP_ALARM0			= 0x70,
> +	SFP_ALARM0_TEMP_HIGH		= BIT(7),
> +	SFP_ALARM0_TEMP_LOW		= BIT(6),
> +	SFP_ALARM0_VCC_HIGH		= BIT(5),
> +	SFP_ALARM0_VCC_LOW		= BIT(4),
> +	SFP_ALARM0_TX_BIAS_HIGH		= BIT(3),
> +	SFP_ALARM0_TX_BIAS_LOW		= BIT(2),
> +	SFP_ALARM0_TXPWR_HIGH		= BIT(1),
> +	SFP_ALARM0_TXPWR_LOW		= BIT(0),
> +
> +	SFP_ALARM1			= 0x71,
> +	SFP_ALARM1_RXPWR_HIGH		= BIT(7),
> +	SFP_ALARM1_RXPWR_LOW		= BIT(6),
> +
> +	SFP_WARN0			= 0x74,
> +	SFP_WARN0_TEMP_HIGH		= BIT(7),
> +	SFP_WARN0_TEMP_LOW		= BIT(6),
> +	SFP_WARN0_VCC_HIGH		= BIT(5),
> +	SFP_WARN0_VCC_LOW		= BIT(4),
> +	SFP_WARN0_TX_BIAS_HIGH		= BIT(3),
> +	SFP_WARN0_TX_BIAS_LOW		= BIT(2),
> +	SFP_WARN0_TXPWR_HIGH		= BIT(1),
> +	SFP_WARN0_TXPWR_LOW		= BIT(0),
> +
> +	SFP_WARN1			= 0x75,
> +	SFP_WARN1_RXPWR_HIGH		= BIT(7),
> +	SFP_WARN1_RXPWR_LOW		= BIT(6),
>  
>  	SFP_EXT_STATUS			= 0x76,
>  	SFP_VSL				= 0x78,
> -- 
> 2.18.0
> 

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ