
Add support for power and current sensors, which need to be named differently in sysfs. Power sensors also require values to be scaled between MCDI and sysfs, and have no minimum value. Add definitions of the power, current, fan, and additional temperature and voltage sensors found on SFA6902F, SFN7022F and SFN7122F. (Includes a bug fix from Andrew Jackson.) Signed-off-by: Ben Hutchings <bhutchings@solarflare.com>
531 lines
15 KiB
C
531 lines
15 KiB
C
/****************************************************************************
|
|
* Driver for Solarflare Solarstorm network controllers and boards
|
|
* Copyright 2011 Solarflare Communications Inc.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 as published
|
|
* by the Free Software Foundation, incorporated herein by reference.
|
|
*/
|
|
|
|
#include <linux/bitops.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/hwmon.h>
|
|
#include <linux/stat.h>
|
|
|
|
#include "net_driver.h"
|
|
#include "mcdi.h"
|
|
#include "mcdi_pcol.h"
|
|
#include "nic.h"
|
|
|
|
enum efx_hwmon_type {
|
|
EFX_HWMON_UNKNOWN,
|
|
EFX_HWMON_TEMP, /* temperature */
|
|
EFX_HWMON_COOL, /* cooling device, probably a heatsink */
|
|
EFX_HWMON_IN, /* voltage */
|
|
EFX_HWMON_CURR, /* current */
|
|
EFX_HWMON_POWER, /* power */
|
|
};
|
|
|
|
static const struct {
|
|
const char *label;
|
|
enum efx_hwmon_type hwmon_type;
|
|
int port;
|
|
} efx_mcdi_sensor_type[] = {
|
|
#define SENSOR(name, label, hwmon_type, port) \
|
|
[MC_CMD_SENSOR_##name] = { label, EFX_HWMON_ ## hwmon_type, port }
|
|
SENSOR(CONTROLLER_TEMP, "Controller ext. temp.", TEMP, -1),
|
|
SENSOR(PHY_COMMON_TEMP, "PHY temp.", TEMP, -1),
|
|
SENSOR(CONTROLLER_COOLING, "Controller cooling", COOL, -1),
|
|
SENSOR(PHY0_TEMP, "PHY temp.", TEMP, 0),
|
|
SENSOR(PHY0_COOLING, "PHY cooling", COOL, 0),
|
|
SENSOR(PHY1_TEMP, "PHY temp.", TEMP, 1),
|
|
SENSOR(PHY1_COOLING, "PHY cooling", COOL, 1),
|
|
SENSOR(IN_1V0, "1.0V supply", IN, -1),
|
|
SENSOR(IN_1V2, "1.2V supply", IN, -1),
|
|
SENSOR(IN_1V8, "1.8V supply", IN, -1),
|
|
SENSOR(IN_2V5, "2.5V supply", IN, -1),
|
|
SENSOR(IN_3V3, "3.3V supply", IN, -1),
|
|
SENSOR(IN_12V0, "12.0V supply", IN, -1),
|
|
SENSOR(IN_1V2A, "1.2V analogue supply", IN, -1),
|
|
SENSOR(IN_VREF, "ref. voltage", IN, -1),
|
|
SENSOR(OUT_VAOE, "AOE power supply", IN, -1),
|
|
SENSOR(AOE_TEMP, "AOE temp.", TEMP, -1),
|
|
SENSOR(PSU_AOE_TEMP, "AOE PSU temp.", TEMP, -1),
|
|
SENSOR(PSU_TEMP, "Controller PSU temp.", TEMP, -1),
|
|
SENSOR(FAN_0, NULL, COOL, -1),
|
|
SENSOR(FAN_1, NULL, COOL, -1),
|
|
SENSOR(FAN_2, NULL, COOL, -1),
|
|
SENSOR(FAN_3, NULL, COOL, -1),
|
|
SENSOR(FAN_4, NULL, COOL, -1),
|
|
SENSOR(IN_VAOE, "AOE input supply", IN, -1),
|
|
SENSOR(OUT_IAOE, "AOE output current", CURR, -1),
|
|
SENSOR(IN_IAOE, "AOE input current", CURR, -1),
|
|
SENSOR(NIC_POWER, "Board power use", POWER, -1),
|
|
SENSOR(IN_0V9, "0.9V supply", IN, -1),
|
|
SENSOR(IN_I0V9, "0.9V input current", CURR, -1),
|
|
SENSOR(IN_I1V2, "1.2V input current", CURR, -1),
|
|
SENSOR(IN_0V9_ADC, "0.9V supply (at ADC)", IN, -1),
|
|
SENSOR(CONTROLLER_2_TEMP, "Controller ext. temp. 2", TEMP, -1),
|
|
SENSOR(VREG_INTERNAL_TEMP, "Voltage regulator temp.", TEMP, -1),
|
|
SENSOR(VREG_0V9_TEMP, "0.9V regulator temp.", TEMP, -1),
|
|
SENSOR(VREG_1V2_TEMP, "1.2V regulator temp.", TEMP, -1),
|
|
SENSOR(CONTROLLER_VPTAT, "Controller int. temp. raw", IN, -1),
|
|
SENSOR(CONTROLLER_INTERNAL_TEMP, "Controller int. temp.", TEMP, -1),
|
|
SENSOR(CONTROLLER_VPTAT_EXTADC,
|
|
"Controller int. temp. raw (at ADC)", IN, -1),
|
|
SENSOR(CONTROLLER_INTERNAL_TEMP_EXTADC,
|
|
"Controller int. temp. (via ADC)", TEMP, -1),
|
|
SENSOR(AMBIENT_TEMP, "Ambient temp.", TEMP, -1),
|
|
SENSOR(AIRFLOW, "Air flow raw", IN, -1),
|
|
#undef SENSOR
|
|
};
|
|
|
|
static const char *const sensor_status_names[] = {
|
|
[MC_CMD_SENSOR_STATE_OK] = "OK",
|
|
[MC_CMD_SENSOR_STATE_WARNING] = "Warning",
|
|
[MC_CMD_SENSOR_STATE_FATAL] = "Fatal",
|
|
[MC_CMD_SENSOR_STATE_BROKEN] = "Device failure",
|
|
[MC_CMD_SENSOR_STATE_NO_READING] = "No reading",
|
|
};
|
|
|
|
void efx_mcdi_sensor_event(struct efx_nic *efx, efx_qword_t *ev)
|
|
{
|
|
unsigned int type, state, value;
|
|
const char *name = NULL, *state_txt;
|
|
|
|
type = EFX_QWORD_FIELD(*ev, MCDI_EVENT_SENSOREVT_MONITOR);
|
|
state = EFX_QWORD_FIELD(*ev, MCDI_EVENT_SENSOREVT_STATE);
|
|
value = EFX_QWORD_FIELD(*ev, MCDI_EVENT_SENSOREVT_VALUE);
|
|
|
|
/* Deal gracefully with the board having more drivers than we
|
|
* know about, but do not expect new sensor states. */
|
|
if (type < ARRAY_SIZE(efx_mcdi_sensor_type))
|
|
name = efx_mcdi_sensor_type[type].label;
|
|
if (!name)
|
|
name = "No sensor name available";
|
|
EFX_BUG_ON_PARANOID(state >= ARRAY_SIZE(sensor_status_names));
|
|
state_txt = sensor_status_names[state];
|
|
|
|
netif_err(efx, hw, efx->net_dev,
|
|
"Sensor %d (%s) reports condition '%s' for raw value %d\n",
|
|
type, name, state_txt, value);
|
|
}
|
|
|
|
#ifdef CONFIG_SFC_MCDI_MON
|
|
|
|
struct efx_mcdi_mon_attribute {
|
|
struct device_attribute dev_attr;
|
|
unsigned int index;
|
|
unsigned int type;
|
|
enum efx_hwmon_type hwmon_type;
|
|
unsigned int limit_value;
|
|
char name[12];
|
|
};
|
|
|
|
static int efx_mcdi_mon_update(struct efx_nic *efx)
|
|
{
|
|
struct efx_mcdi_mon *hwmon = efx_mcdi_mon(efx);
|
|
MCDI_DECLARE_BUF(inbuf, MC_CMD_READ_SENSORS_EXT_IN_LEN);
|
|
int rc;
|
|
|
|
MCDI_SET_QWORD(inbuf, READ_SENSORS_EXT_IN_DMA_ADDR,
|
|
hwmon->dma_buf.dma_addr);
|
|
MCDI_SET_DWORD(inbuf, READ_SENSORS_EXT_IN_LENGTH, hwmon->dma_buf.len);
|
|
|
|
rc = efx_mcdi_rpc(efx, MC_CMD_READ_SENSORS,
|
|
inbuf, sizeof(inbuf), NULL, 0, NULL);
|
|
if (rc == 0)
|
|
hwmon->last_update = jiffies;
|
|
return rc;
|
|
}
|
|
|
|
static ssize_t efx_mcdi_mon_show_name(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
return sprintf(buf, "%s\n", KBUILD_MODNAME);
|
|
}
|
|
|
|
static int efx_mcdi_mon_get_entry(struct device *dev, unsigned int index,
|
|
efx_dword_t *entry)
|
|
{
|
|
struct efx_nic *efx = dev_get_drvdata(dev);
|
|
struct efx_mcdi_mon *hwmon = efx_mcdi_mon(efx);
|
|
int rc;
|
|
|
|
BUILD_BUG_ON(MC_CMD_READ_SENSORS_OUT_LEN != 0);
|
|
|
|
mutex_lock(&hwmon->update_lock);
|
|
|
|
/* Use cached value if last update was < 1 s ago */
|
|
if (time_before(jiffies, hwmon->last_update + HZ))
|
|
rc = 0;
|
|
else
|
|
rc = efx_mcdi_mon_update(efx);
|
|
|
|
/* Copy out the requested entry */
|
|
*entry = ((efx_dword_t *)hwmon->dma_buf.addr)[index];
|
|
|
|
mutex_unlock(&hwmon->update_lock);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static ssize_t efx_mcdi_mon_show_value(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct efx_mcdi_mon_attribute *mon_attr =
|
|
container_of(attr, struct efx_mcdi_mon_attribute, dev_attr);
|
|
efx_dword_t entry;
|
|
unsigned int value, state;
|
|
int rc;
|
|
|
|
rc = efx_mcdi_mon_get_entry(dev, mon_attr->index, &entry);
|
|
if (rc)
|
|
return rc;
|
|
|
|
state = EFX_DWORD_FIELD(entry, MC_CMD_SENSOR_VALUE_ENTRY_TYPEDEF_STATE);
|
|
if (state == MC_CMD_SENSOR_STATE_NO_READING)
|
|
return -EBUSY;
|
|
|
|
value = EFX_DWORD_FIELD(entry, MC_CMD_SENSOR_VALUE_ENTRY_TYPEDEF_VALUE);
|
|
|
|
switch (mon_attr->hwmon_type) {
|
|
case EFX_HWMON_TEMP:
|
|
/* Convert temperature from degrees to milli-degrees Celsius */
|
|
value *= 1000;
|
|
break;
|
|
case EFX_HWMON_POWER:
|
|
/* Convert power from watts to microwatts */
|
|
value *= 1000000;
|
|
break;
|
|
default:
|
|
/* No conversion needed */
|
|
break;
|
|
}
|
|
|
|
return sprintf(buf, "%u\n", value);
|
|
}
|
|
|
|
static ssize_t efx_mcdi_mon_show_limit(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct efx_mcdi_mon_attribute *mon_attr =
|
|
container_of(attr, struct efx_mcdi_mon_attribute, dev_attr);
|
|
unsigned int value;
|
|
|
|
value = mon_attr->limit_value;
|
|
|
|
switch (mon_attr->hwmon_type) {
|
|
case EFX_HWMON_TEMP:
|
|
/* Convert temperature from degrees to milli-degrees Celsius */
|
|
value *= 1000;
|
|
break;
|
|
case EFX_HWMON_POWER:
|
|
/* Convert power from watts to microwatts */
|
|
value *= 1000000;
|
|
break;
|
|
default:
|
|
/* No conversion needed */
|
|
break;
|
|
}
|
|
|
|
return sprintf(buf, "%u\n", value);
|
|
}
|
|
|
|
static ssize_t efx_mcdi_mon_show_alarm(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct efx_mcdi_mon_attribute *mon_attr =
|
|
container_of(attr, struct efx_mcdi_mon_attribute, dev_attr);
|
|
efx_dword_t entry;
|
|
int state;
|
|
int rc;
|
|
|
|
rc = efx_mcdi_mon_get_entry(dev, mon_attr->index, &entry);
|
|
if (rc)
|
|
return rc;
|
|
|
|
state = EFX_DWORD_FIELD(entry, MC_CMD_SENSOR_VALUE_ENTRY_TYPEDEF_STATE);
|
|
return sprintf(buf, "%d\n", state != MC_CMD_SENSOR_STATE_OK);
|
|
}
|
|
|
|
static ssize_t efx_mcdi_mon_show_label(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct efx_mcdi_mon_attribute *mon_attr =
|
|
container_of(attr, struct efx_mcdi_mon_attribute, dev_attr);
|
|
return sprintf(buf, "%s\n",
|
|
efx_mcdi_sensor_type[mon_attr->type].label);
|
|
}
|
|
|
|
static int
|
|
efx_mcdi_mon_add_attr(struct efx_nic *efx, const char *name,
|
|
ssize_t (*reader)(struct device *,
|
|
struct device_attribute *, char *),
|
|
unsigned int index, unsigned int type,
|
|
unsigned int limit_value)
|
|
{
|
|
struct efx_mcdi_mon *hwmon = efx_mcdi_mon(efx);
|
|
struct efx_mcdi_mon_attribute *attr = &hwmon->attrs[hwmon->n_attrs];
|
|
int rc;
|
|
|
|
strlcpy(attr->name, name, sizeof(attr->name));
|
|
attr->index = index;
|
|
attr->type = type;
|
|
if (type < ARRAY_SIZE(efx_mcdi_sensor_type))
|
|
attr->hwmon_type = efx_mcdi_sensor_type[type].hwmon_type;
|
|
else
|
|
attr->hwmon_type = EFX_HWMON_UNKNOWN;
|
|
attr->limit_value = limit_value;
|
|
sysfs_attr_init(&attr->dev_attr.attr);
|
|
attr->dev_attr.attr.name = attr->name;
|
|
attr->dev_attr.attr.mode = S_IRUGO;
|
|
attr->dev_attr.show = reader;
|
|
rc = device_create_file(&efx->pci_dev->dev, &attr->dev_attr);
|
|
if (rc == 0)
|
|
++hwmon->n_attrs;
|
|
return rc;
|
|
}
|
|
|
|
int efx_mcdi_mon_probe(struct efx_nic *efx)
|
|
{
|
|
unsigned int n_temp = 0, n_cool = 0, n_in = 0, n_curr = 0, n_power = 0;
|
|
struct efx_mcdi_mon *hwmon = efx_mcdi_mon(efx);
|
|
MCDI_DECLARE_BUF(inbuf, MC_CMD_SENSOR_INFO_EXT_IN_LEN);
|
|
MCDI_DECLARE_BUF(outbuf, MC_CMD_SENSOR_INFO_OUT_LENMAX);
|
|
unsigned int n_pages, n_sensors, n_attrs, page;
|
|
size_t outlen;
|
|
char name[12];
|
|
u32 mask;
|
|
int rc, i, j, type;
|
|
|
|
/* Find out how many sensors are present */
|
|
n_sensors = 0;
|
|
page = 0;
|
|
do {
|
|
MCDI_SET_DWORD(inbuf, SENSOR_INFO_EXT_IN_PAGE, page);
|
|
|
|
rc = efx_mcdi_rpc(efx, MC_CMD_SENSOR_INFO, inbuf, sizeof(inbuf),
|
|
outbuf, sizeof(outbuf), &outlen);
|
|
if (rc)
|
|
return rc;
|
|
if (outlen < MC_CMD_SENSOR_INFO_OUT_LENMIN)
|
|
return -EIO;
|
|
|
|
mask = MCDI_DWORD(outbuf, SENSOR_INFO_OUT_MASK);
|
|
n_sensors += hweight32(mask & ~(1 << MC_CMD_SENSOR_PAGE0_NEXT));
|
|
++page;
|
|
} while (mask & (1 << MC_CMD_SENSOR_PAGE0_NEXT));
|
|
n_pages = page;
|
|
|
|
/* Don't create a device if there are none */
|
|
if (n_sensors == 0)
|
|
return 0;
|
|
|
|
rc = efx_nic_alloc_buffer(
|
|
efx, &hwmon->dma_buf,
|
|
n_sensors * MC_CMD_SENSOR_VALUE_ENTRY_TYPEDEF_LEN,
|
|
GFP_KERNEL);
|
|
if (rc)
|
|
return rc;
|
|
|
|
mutex_init(&hwmon->update_lock);
|
|
efx_mcdi_mon_update(efx);
|
|
|
|
/* Allocate space for the maximum possible number of
|
|
* attributes for this set of sensors: name of the driver plus
|
|
* value, min, max, crit, alarm and label for each sensor.
|
|
*/
|
|
n_attrs = 1 + 6 * n_sensors;
|
|
hwmon->attrs = kcalloc(n_attrs, sizeof(*hwmon->attrs), GFP_KERNEL);
|
|
if (!hwmon->attrs) {
|
|
rc = -ENOMEM;
|
|
goto fail;
|
|
}
|
|
|
|
hwmon->device = hwmon_device_register(&efx->pci_dev->dev);
|
|
if (IS_ERR(hwmon->device)) {
|
|
rc = PTR_ERR(hwmon->device);
|
|
goto fail;
|
|
}
|
|
|
|
rc = efx_mcdi_mon_add_attr(efx, "name", efx_mcdi_mon_show_name, 0, 0, 0);
|
|
if (rc)
|
|
goto fail;
|
|
|
|
for (i = 0, j = -1, type = -1; ; i++) {
|
|
enum efx_hwmon_type hwmon_type;
|
|
const char *hwmon_prefix;
|
|
unsigned hwmon_index;
|
|
u16 min1, max1, min2, max2;
|
|
|
|
/* Find next sensor type or exit if there is none */
|
|
do {
|
|
type++;
|
|
|
|
if ((type % 32) == 0) {
|
|
page = type / 32;
|
|
j = -1;
|
|
if (page == n_pages)
|
|
return 0;
|
|
|
|
MCDI_SET_DWORD(inbuf, SENSOR_INFO_EXT_IN_PAGE,
|
|
page);
|
|
rc = efx_mcdi_rpc(efx, MC_CMD_SENSOR_INFO,
|
|
inbuf, sizeof(inbuf),
|
|
outbuf, sizeof(outbuf),
|
|
&outlen);
|
|
if (rc)
|
|
goto fail;
|
|
if (outlen < MC_CMD_SENSOR_INFO_OUT_LENMIN) {
|
|
rc = -EIO;
|
|
goto fail;
|
|
}
|
|
|
|
mask = (MCDI_DWORD(outbuf,
|
|
SENSOR_INFO_OUT_MASK) &
|
|
~(1 << MC_CMD_SENSOR_PAGE0_NEXT));
|
|
|
|
/* Check again for short response */
|
|
if (outlen <
|
|
MC_CMD_SENSOR_INFO_OUT_LEN(hweight32(mask))) {
|
|
rc = -EIO;
|
|
goto fail;
|
|
}
|
|
}
|
|
} while (!(mask & (1 << type % 32)));
|
|
j++;
|
|
|
|
if (type < ARRAY_SIZE(efx_mcdi_sensor_type)) {
|
|
hwmon_type = efx_mcdi_sensor_type[type].hwmon_type;
|
|
|
|
/* Skip sensors specific to a different port */
|
|
if (hwmon_type != EFX_HWMON_UNKNOWN &&
|
|
efx_mcdi_sensor_type[type].port >= 0 &&
|
|
efx_mcdi_sensor_type[type].port !=
|
|
efx_port_num(efx))
|
|
continue;
|
|
} else {
|
|
hwmon_type = EFX_HWMON_UNKNOWN;
|
|
}
|
|
|
|
switch (hwmon_type) {
|
|
case EFX_HWMON_TEMP:
|
|
hwmon_prefix = "temp";
|
|
hwmon_index = ++n_temp; /* 1-based */
|
|
break;
|
|
case EFX_HWMON_COOL:
|
|
/* This is likely to be a heatsink, but there
|
|
* is no convention for representing cooling
|
|
* devices other than fans.
|
|
*/
|
|
hwmon_prefix = "fan";
|
|
hwmon_index = ++n_cool; /* 1-based */
|
|
break;
|
|
default:
|
|
hwmon_prefix = "in";
|
|
hwmon_index = n_in++; /* 0-based */
|
|
break;
|
|
case EFX_HWMON_CURR:
|
|
hwmon_prefix = "curr";
|
|
hwmon_index = ++n_curr; /* 1-based */
|
|
break;
|
|
case EFX_HWMON_POWER:
|
|
hwmon_prefix = "power";
|
|
hwmon_index = ++n_power; /* 1-based */
|
|
break;
|
|
}
|
|
|
|
min1 = MCDI_ARRAY_FIELD(outbuf, SENSOR_ENTRY,
|
|
SENSOR_INFO_ENTRY, j, MIN1);
|
|
max1 = MCDI_ARRAY_FIELD(outbuf, SENSOR_ENTRY,
|
|
SENSOR_INFO_ENTRY, j, MAX1);
|
|
min2 = MCDI_ARRAY_FIELD(outbuf, SENSOR_ENTRY,
|
|
SENSOR_INFO_ENTRY, j, MIN2);
|
|
max2 = MCDI_ARRAY_FIELD(outbuf, SENSOR_ENTRY,
|
|
SENSOR_INFO_ENTRY, j, MAX2);
|
|
|
|
if (min1 != max1) {
|
|
snprintf(name, sizeof(name), "%s%u_input",
|
|
hwmon_prefix, hwmon_index);
|
|
rc = efx_mcdi_mon_add_attr(
|
|
efx, name, efx_mcdi_mon_show_value, i, type, 0);
|
|
if (rc)
|
|
goto fail;
|
|
|
|
if (hwmon_type != EFX_HWMON_POWER) {
|
|
snprintf(name, sizeof(name), "%s%u_min",
|
|
hwmon_prefix, hwmon_index);
|
|
rc = efx_mcdi_mon_add_attr(
|
|
efx, name, efx_mcdi_mon_show_limit,
|
|
i, type, min1);
|
|
if (rc)
|
|
goto fail;
|
|
}
|
|
|
|
snprintf(name, sizeof(name), "%s%u_max",
|
|
hwmon_prefix, hwmon_index);
|
|
rc = efx_mcdi_mon_add_attr(
|
|
efx, name, efx_mcdi_mon_show_limit,
|
|
i, type, max1);
|
|
if (rc)
|
|
goto fail;
|
|
|
|
if (min2 != max2) {
|
|
/* Assume max2 is critical value.
|
|
* But we have no good way to expose min2.
|
|
*/
|
|
snprintf(name, sizeof(name), "%s%u_crit",
|
|
hwmon_prefix, hwmon_index);
|
|
rc = efx_mcdi_mon_add_attr(
|
|
efx, name, efx_mcdi_mon_show_limit,
|
|
i, type, max2);
|
|
if (rc)
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
snprintf(name, sizeof(name), "%s%u_alarm",
|
|
hwmon_prefix, hwmon_index);
|
|
rc = efx_mcdi_mon_add_attr(
|
|
efx, name, efx_mcdi_mon_show_alarm, i, type, 0);
|
|
if (rc)
|
|
goto fail;
|
|
|
|
if (type < ARRAY_SIZE(efx_mcdi_sensor_type) &&
|
|
efx_mcdi_sensor_type[type].label) {
|
|
snprintf(name, sizeof(name), "%s%u_label",
|
|
hwmon_prefix, hwmon_index);
|
|
rc = efx_mcdi_mon_add_attr(
|
|
efx, name, efx_mcdi_mon_show_label, i, type, 0);
|
|
if (rc)
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
fail:
|
|
efx_mcdi_mon_remove(efx);
|
|
return rc;
|
|
}
|
|
|
|
void efx_mcdi_mon_remove(struct efx_nic *efx)
|
|
{
|
|
struct efx_mcdi_mon *hwmon = efx_mcdi_mon(efx);
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < hwmon->n_attrs; i++)
|
|
device_remove_file(&efx->pci_dev->dev,
|
|
&hwmon->attrs[i].dev_attr);
|
|
kfree(hwmon->attrs);
|
|
if (hwmon->device)
|
|
hwmon_device_unregister(hwmon->device);
|
|
efx_nic_free_buffer(efx, &hwmon->dma_buf);
|
|
}
|
|
|
|
#endif /* CONFIG_SFC_MCDI_MON */
|