linux/drivers/net/ethernet/sfc/mcdi_mon.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/****************************************************************************
   3 * Driver for Solarflare network controllers and boards
   4 * Copyright 2011-2013 Solarflare Communications Inc.
   5 */
   6
   7#include <linux/bitops.h>
   8#include <linux/slab.h>
   9#include <linux/hwmon.h>
  10#include <linux/stat.h>
  11
  12#include "net_driver.h"
  13#include "mcdi.h"
  14#include "mcdi_pcol.h"
  15#include "nic.h"
  16
  17enum efx_hwmon_type {
  18        EFX_HWMON_UNKNOWN,
  19        EFX_HWMON_TEMP,         /* temperature */
  20        EFX_HWMON_COOL,         /* cooling device, probably a heatsink */
  21        EFX_HWMON_IN,           /* voltage */
  22        EFX_HWMON_CURR,         /* current */
  23        EFX_HWMON_POWER,        /* power */
  24        EFX_HWMON_TYPES_COUNT
  25};
  26
  27static const char *const efx_hwmon_unit[EFX_HWMON_TYPES_COUNT] = {
  28        [EFX_HWMON_TEMP]  = " degC",
  29        [EFX_HWMON_COOL]  = " rpm", /* though nonsense for a heatsink */
  30        [EFX_HWMON_IN]    = " mV",
  31        [EFX_HWMON_CURR]  = " mA",
  32        [EFX_HWMON_POWER] = " W",
  33};
  34
  35static const struct {
  36        const char *label;
  37        enum efx_hwmon_type hwmon_type;
  38        int port;
  39} efx_mcdi_sensor_type[] = {
  40#define SENSOR(name, label, hwmon_type, port)                           \
  41        [MC_CMD_SENSOR_##name] = { label, EFX_HWMON_ ## hwmon_type, port }
  42        SENSOR(CONTROLLER_TEMP,         "Controller board temp.",   TEMP,  -1),
  43        SENSOR(PHY_COMMON_TEMP,         "PHY temp.",                TEMP,  -1),
  44        SENSOR(CONTROLLER_COOLING,      "Controller heat sink",     COOL,  -1),
  45        SENSOR(PHY0_TEMP,               "PHY temp.",                TEMP,  0),
  46        SENSOR(PHY0_COOLING,            "PHY heat sink",            COOL,  0),
  47        SENSOR(PHY1_TEMP,               "PHY temp.",                TEMP,  1),
  48        SENSOR(PHY1_COOLING,            "PHY heat sink",            COOL,  1),
  49        SENSOR(IN_1V0,                  "1.0V supply",              IN,    -1),
  50        SENSOR(IN_1V2,                  "1.2V supply",              IN,    -1),
  51        SENSOR(IN_1V8,                  "1.8V supply",              IN,    -1),
  52        SENSOR(IN_2V5,                  "2.5V supply",              IN,    -1),
  53        SENSOR(IN_3V3,                  "3.3V supply",              IN,    -1),
  54        SENSOR(IN_12V0,                 "12.0V supply",             IN,    -1),
  55        SENSOR(IN_1V2A,                 "1.2V analogue supply",     IN,    -1),
  56        SENSOR(IN_VREF,                 "Ref. voltage",             IN,    -1),
  57        SENSOR(OUT_VAOE,                "AOE FPGA supply",          IN,    -1),
  58        SENSOR(AOE_TEMP,                "AOE FPGA temp.",           TEMP,  -1),
  59        SENSOR(PSU_AOE_TEMP,            "AOE regulator temp.",      TEMP,  -1),
  60        SENSOR(PSU_TEMP,                "Controller regulator temp.",
  61                                                                    TEMP,  -1),
  62        SENSOR(FAN_0,                   "Fan 0",                    COOL,  -1),
  63        SENSOR(FAN_1,                   "Fan 1",                    COOL,  -1),
  64        SENSOR(FAN_2,                   "Fan 2",                    COOL,  -1),
  65        SENSOR(FAN_3,                   "Fan 3",                    COOL,  -1),
  66        SENSOR(FAN_4,                   "Fan 4",                    COOL,  -1),
  67        SENSOR(IN_VAOE,                 "AOE input supply",         IN,    -1),
  68        SENSOR(OUT_IAOE,                "AOE output current",       CURR,  -1),
  69        SENSOR(IN_IAOE,                 "AOE input current",        CURR,  -1),
  70        SENSOR(NIC_POWER,               "Board power use",          POWER, -1),
  71        SENSOR(IN_0V9,                  "0.9V supply",              IN,    -1),
  72        SENSOR(IN_I0V9,                 "0.9V supply current",      CURR,  -1),
  73        SENSOR(IN_I1V2,                 "1.2V supply current",      CURR,  -1),
  74        SENSOR(IN_0V9_ADC,              "0.9V supply (ext. ADC)",   IN,    -1),
  75        SENSOR(CONTROLLER_2_TEMP,       "Controller board temp. 2", TEMP,  -1),
  76        SENSOR(VREG_INTERNAL_TEMP,      "Regulator die temp.",      TEMP,  -1),
  77        SENSOR(VREG_0V9_TEMP,           "0.9V regulator temp.",     TEMP,  -1),
  78        SENSOR(VREG_1V2_TEMP,           "1.2V regulator temp.",     TEMP,  -1),
  79        SENSOR(CONTROLLER_VPTAT,
  80                              "Controller PTAT voltage (int. ADC)", IN,    -1),
  81        SENSOR(CONTROLLER_INTERNAL_TEMP,
  82                                 "Controller die temp. (int. ADC)", TEMP,  -1),
  83        SENSOR(CONTROLLER_VPTAT_EXTADC,
  84                              "Controller PTAT voltage (ext. ADC)", IN,    -1),
  85        SENSOR(CONTROLLER_INTERNAL_TEMP_EXTADC,
  86                                 "Controller die temp. (ext. ADC)", TEMP,  -1),
  87        SENSOR(AMBIENT_TEMP,            "Ambient temp.",            TEMP,  -1),
  88        SENSOR(AIRFLOW,                 "Air flow raw",             IN,    -1),
  89        SENSOR(VDD08D_VSS08D_CSR,       "0.9V die (int. ADC)",      IN,    -1),
  90        SENSOR(VDD08D_VSS08D_CSR_EXTADC, "0.9V die (ext. ADC)",     IN,    -1),
  91        SENSOR(HOTPOINT_TEMP,  "Controller board temp. (hotpoint)", TEMP,  -1),
  92#undef SENSOR
  93};
  94
  95static const char *const sensor_status_names[] = {
  96        [MC_CMD_SENSOR_STATE_OK] = "OK",
  97        [MC_CMD_SENSOR_STATE_WARNING] = "Warning",
  98        [MC_CMD_SENSOR_STATE_FATAL] = "Fatal",
  99        [MC_CMD_SENSOR_STATE_BROKEN] = "Device failure",
 100        [MC_CMD_SENSOR_STATE_NO_READING] = "No reading",
 101};
 102
 103void efx_mcdi_sensor_event(struct efx_nic *efx, efx_qword_t *ev)
 104{
 105        unsigned int type, state, value;
 106        enum efx_hwmon_type hwmon_type = EFX_HWMON_UNKNOWN;
 107        const char *name = NULL, *state_txt, *unit;
 108
 109        type = EFX_QWORD_FIELD(*ev, MCDI_EVENT_SENSOREVT_MONITOR);
 110        state = EFX_QWORD_FIELD(*ev, MCDI_EVENT_SENSOREVT_STATE);
 111        value = EFX_QWORD_FIELD(*ev, MCDI_EVENT_SENSOREVT_VALUE);
 112
 113        /* Deal gracefully with the board having more drivers than we
 114         * know about, but do not expect new sensor states. */
 115        if (type < ARRAY_SIZE(efx_mcdi_sensor_type)) {
 116                name = efx_mcdi_sensor_type[type].label;
 117                hwmon_type = efx_mcdi_sensor_type[type].hwmon_type;
 118        }
 119        if (!name)
 120                name = "No sensor name available";
 121        EFX_WARN_ON_PARANOID(state >= ARRAY_SIZE(sensor_status_names));
 122        state_txt = sensor_status_names[state];
 123        EFX_WARN_ON_PARANOID(hwmon_type >= EFX_HWMON_TYPES_COUNT);
 124        unit = efx_hwmon_unit[hwmon_type];
 125        if (!unit)
 126                unit = "";
 127
 128        netif_err(efx, hw, efx->net_dev,
 129                  "Sensor %d (%s) reports condition '%s' for value %d%s\n",
 130                  type, name, state_txt, value, unit);
 131}
 132
 133#ifdef CONFIG_SFC_MCDI_MON
 134
 135struct efx_mcdi_mon_attribute {
 136        struct device_attribute dev_attr;
 137        unsigned int index;
 138        unsigned int type;
 139        enum efx_hwmon_type hwmon_type;
 140        unsigned int limit_value;
 141        char name[12];
 142};
 143
 144static int efx_mcdi_mon_update(struct efx_nic *efx)
 145{
 146        struct efx_mcdi_mon *hwmon = efx_mcdi_mon(efx);
 147        MCDI_DECLARE_BUF(inbuf, MC_CMD_READ_SENSORS_EXT_IN_LEN);
 148        int rc;
 149
 150        MCDI_SET_QWORD(inbuf, READ_SENSORS_EXT_IN_DMA_ADDR,
 151                       hwmon->dma_buf.dma_addr);
 152        MCDI_SET_DWORD(inbuf, READ_SENSORS_EXT_IN_LENGTH, hwmon->dma_buf.len);
 153
 154        rc = efx_mcdi_rpc(efx, MC_CMD_READ_SENSORS,
 155                          inbuf, sizeof(inbuf), NULL, 0, NULL);
 156        if (rc == 0)
 157                hwmon->last_update = jiffies;
 158        return rc;
 159}
 160
 161static int efx_mcdi_mon_get_entry(struct device *dev, unsigned int index,
 162                                  efx_dword_t *entry)
 163{
 164        struct efx_nic *efx = dev_get_drvdata(dev->parent);
 165        struct efx_mcdi_mon *hwmon = efx_mcdi_mon(efx);
 166        int rc;
 167
 168        BUILD_BUG_ON(MC_CMD_READ_SENSORS_OUT_LEN != 0);
 169
 170        mutex_lock(&hwmon->update_lock);
 171
 172        /* Use cached value if last update was < 1 s ago */
 173        if (time_before(jiffies, hwmon->last_update + HZ))
 174                rc = 0;
 175        else
 176                rc = efx_mcdi_mon_update(efx);
 177
 178        /* Copy out the requested entry */
 179        *entry = ((efx_dword_t *)hwmon->dma_buf.addr)[index];
 180
 181        mutex_unlock(&hwmon->update_lock);
 182
 183        return rc;
 184}
 185
 186static ssize_t efx_mcdi_mon_show_value(struct device *dev,
 187                                       struct device_attribute *attr,
 188                                       char *buf)
 189{
 190        struct efx_mcdi_mon_attribute *mon_attr =
 191                container_of(attr, struct efx_mcdi_mon_attribute, dev_attr);
 192        efx_dword_t entry;
 193        unsigned int value, state;
 194        int rc;
 195
 196        rc = efx_mcdi_mon_get_entry(dev, mon_attr->index, &entry);
 197        if (rc)
 198                return rc;
 199
 200        state = EFX_DWORD_FIELD(entry, MC_CMD_SENSOR_VALUE_ENTRY_TYPEDEF_STATE);
 201        if (state == MC_CMD_SENSOR_STATE_NO_READING)
 202                return -EBUSY;
 203
 204        value = EFX_DWORD_FIELD(entry, MC_CMD_SENSOR_VALUE_ENTRY_TYPEDEF_VALUE);
 205
 206        switch (mon_attr->hwmon_type) {
 207        case EFX_HWMON_TEMP:
 208                /* Convert temperature from degrees to milli-degrees Celsius */
 209                value *= 1000;
 210                break;
 211        case EFX_HWMON_POWER:
 212                /* Convert power from watts to microwatts */
 213                value *= 1000000;
 214                break;
 215        default:
 216                /* No conversion needed */
 217                break;
 218        }
 219
 220        return sprintf(buf, "%u\n", value);
 221}
 222
 223static ssize_t efx_mcdi_mon_show_limit(struct device *dev,
 224                                       struct device_attribute *attr,
 225                                       char *buf)
 226{
 227        struct efx_mcdi_mon_attribute *mon_attr =
 228                container_of(attr, struct efx_mcdi_mon_attribute, dev_attr);
 229        unsigned int value;
 230
 231        value = mon_attr->limit_value;
 232
 233        switch (mon_attr->hwmon_type) {
 234        case EFX_HWMON_TEMP:
 235                /* Convert temperature from degrees to milli-degrees Celsius */
 236                value *= 1000;
 237                break;
 238        case EFX_HWMON_POWER:
 239                /* Convert power from watts to microwatts */
 240                value *= 1000000;
 241                break;
 242        default:
 243                /* No conversion needed */
 244                break;
 245        }
 246
 247        return sprintf(buf, "%u\n", value);
 248}
 249
 250static ssize_t efx_mcdi_mon_show_alarm(struct device *dev,
 251                                       struct device_attribute *attr,
 252                                       char *buf)
 253{
 254        struct efx_mcdi_mon_attribute *mon_attr =
 255                container_of(attr, struct efx_mcdi_mon_attribute, dev_attr);
 256        efx_dword_t entry;
 257        int state;
 258        int rc;
 259
 260        rc = efx_mcdi_mon_get_entry(dev, mon_attr->index, &entry);
 261        if (rc)
 262                return rc;
 263
 264        state = EFX_DWORD_FIELD(entry, MC_CMD_SENSOR_VALUE_ENTRY_TYPEDEF_STATE);
 265        return sprintf(buf, "%d\n", state != MC_CMD_SENSOR_STATE_OK);
 266}
 267
 268static ssize_t efx_mcdi_mon_show_label(struct device *dev,
 269                                       struct device_attribute *attr,
 270                                       char *buf)
 271{
 272        struct efx_mcdi_mon_attribute *mon_attr =
 273                container_of(attr, struct efx_mcdi_mon_attribute, dev_attr);
 274        return sprintf(buf, "%s\n",
 275                       efx_mcdi_sensor_type[mon_attr->type].label);
 276}
 277
 278static void
 279efx_mcdi_mon_add_attr(struct efx_nic *efx, const char *name,
 280                      ssize_t (*reader)(struct device *,
 281                                        struct device_attribute *, char *),
 282                      unsigned int index, unsigned int type,
 283                      unsigned int limit_value)
 284{
 285        struct efx_mcdi_mon *hwmon = efx_mcdi_mon(efx);
 286        struct efx_mcdi_mon_attribute *attr = &hwmon->attrs[hwmon->n_attrs];
 287
 288        strlcpy(attr->name, name, sizeof(attr->name));
 289        attr->index = index;
 290        attr->type = type;
 291        if (type < ARRAY_SIZE(efx_mcdi_sensor_type))
 292                attr->hwmon_type = efx_mcdi_sensor_type[type].hwmon_type;
 293        else
 294                attr->hwmon_type = EFX_HWMON_UNKNOWN;
 295        attr->limit_value = limit_value;
 296        sysfs_attr_init(&attr->dev_attr.attr);
 297        attr->dev_attr.attr.name = attr->name;
 298        attr->dev_attr.attr.mode = 0444;
 299        attr->dev_attr.show = reader;
 300        hwmon->group.attrs[hwmon->n_attrs++] = &attr->dev_attr.attr;
 301}
 302
 303int efx_mcdi_mon_probe(struct efx_nic *efx)
 304{
 305        unsigned int n_temp = 0, n_cool = 0, n_in = 0, n_curr = 0, n_power = 0;
 306        struct efx_mcdi_mon *hwmon = efx_mcdi_mon(efx);
 307        MCDI_DECLARE_BUF(inbuf, MC_CMD_SENSOR_INFO_EXT_IN_LEN);
 308        MCDI_DECLARE_BUF(outbuf, MC_CMD_SENSOR_INFO_OUT_LENMAX);
 309        unsigned int n_pages, n_sensors, n_attrs, page;
 310        size_t outlen;
 311        char name[12];
 312        u32 mask;
 313        int rc, i, j, type;
 314
 315        /* Find out how many sensors are present */
 316        n_sensors = 0;
 317        page = 0;
 318        do {
 319                MCDI_SET_DWORD(inbuf, SENSOR_INFO_EXT_IN_PAGE, page);
 320
 321                rc = efx_mcdi_rpc(efx, MC_CMD_SENSOR_INFO, inbuf, sizeof(inbuf),
 322                                  outbuf, sizeof(outbuf), &outlen);
 323                if (rc)
 324                        return rc;
 325                if (outlen < MC_CMD_SENSOR_INFO_OUT_LENMIN)
 326                        return -EIO;
 327
 328                mask = MCDI_DWORD(outbuf, SENSOR_INFO_OUT_MASK);
 329                n_sensors += hweight32(mask & ~(1 << MC_CMD_SENSOR_PAGE0_NEXT));
 330                ++page;
 331        } while (mask & (1 << MC_CMD_SENSOR_PAGE0_NEXT));
 332        n_pages = page;
 333
 334        /* Don't create a device if there are none */
 335        if (n_sensors == 0)
 336                return 0;
 337
 338        rc = efx_nic_alloc_buffer(
 339                efx, &hwmon->dma_buf,
 340                n_sensors * MC_CMD_SENSOR_VALUE_ENTRY_TYPEDEF_LEN,
 341                GFP_KERNEL);
 342        if (rc)
 343                return rc;
 344
 345        mutex_init(&hwmon->update_lock);
 346        efx_mcdi_mon_update(efx);
 347
 348        /* Allocate space for the maximum possible number of
 349         * attributes for this set of sensors:
 350         * value, min, max, crit, alarm and label for each sensor.
 351         */
 352        n_attrs = 6 * n_sensors;
 353        hwmon->attrs = kcalloc(n_attrs, sizeof(*hwmon->attrs), GFP_KERNEL);
 354        if (!hwmon->attrs) {
 355                rc = -ENOMEM;
 356                goto fail;
 357        }
 358        hwmon->group.attrs = kcalloc(n_attrs + 1, sizeof(struct attribute *),
 359                                     GFP_KERNEL);
 360        if (!hwmon->group.attrs) {
 361                rc = -ENOMEM;
 362                goto fail;
 363        }
 364
 365        for (i = 0, j = -1, type = -1; ; i++) {
 366                enum efx_hwmon_type hwmon_type;
 367                const char *hwmon_prefix;
 368                unsigned hwmon_index;
 369                u16 min1, max1, min2, max2;
 370
 371                /* Find next sensor type or exit if there is none */
 372                do {
 373                        type++;
 374
 375                        if ((type % 32) == 0) {
 376                                page = type / 32;
 377                                j = -1;
 378                                if (page == n_pages)
 379                                        goto hwmon_register;
 380
 381                                MCDI_SET_DWORD(inbuf, SENSOR_INFO_EXT_IN_PAGE,
 382                                               page);
 383                                rc = efx_mcdi_rpc(efx, MC_CMD_SENSOR_INFO,
 384                                                  inbuf, sizeof(inbuf),
 385                                                  outbuf, sizeof(outbuf),
 386                                                  &outlen);
 387                                if (rc)
 388                                        goto fail;
 389                                if (outlen < MC_CMD_SENSOR_INFO_OUT_LENMIN) {
 390                                        rc = -EIO;
 391                                        goto fail;
 392                                }
 393
 394                                mask = (MCDI_DWORD(outbuf,
 395                                                   SENSOR_INFO_OUT_MASK) &
 396                                        ~(1 << MC_CMD_SENSOR_PAGE0_NEXT));
 397
 398                                /* Check again for short response */
 399                                if (outlen <
 400                                    MC_CMD_SENSOR_INFO_OUT_LEN(hweight32(mask))) {
 401                                        rc = -EIO;
 402                                        goto fail;
 403                                }
 404                        }
 405                } while (!(mask & (1 << type % 32)));
 406                j++;
 407
 408                if (type < ARRAY_SIZE(efx_mcdi_sensor_type)) {
 409                        hwmon_type = efx_mcdi_sensor_type[type].hwmon_type;
 410
 411                        /* Skip sensors specific to a different port */
 412                        if (hwmon_type != EFX_HWMON_UNKNOWN &&
 413                            efx_mcdi_sensor_type[type].port >= 0 &&
 414                            efx_mcdi_sensor_type[type].port !=
 415                            efx_port_num(efx))
 416                                continue;
 417                } else {
 418                        hwmon_type = EFX_HWMON_UNKNOWN;
 419                }
 420
 421                switch (hwmon_type) {
 422                case EFX_HWMON_TEMP:
 423                        hwmon_prefix = "temp";
 424                        hwmon_index = ++n_temp; /* 1-based */
 425                        break;
 426                case EFX_HWMON_COOL:
 427                        /* This is likely to be a heatsink, but there
 428                         * is no convention for representing cooling
 429                         * devices other than fans.
 430                         */
 431                        hwmon_prefix = "fan";
 432                        hwmon_index = ++n_cool; /* 1-based */
 433                        break;
 434                default:
 435                        hwmon_prefix = "in";
 436                        hwmon_index = n_in++; /* 0-based */
 437                        break;
 438                case EFX_HWMON_CURR:
 439                        hwmon_prefix = "curr";
 440                        hwmon_index = ++n_curr; /* 1-based */
 441                        break;
 442                case EFX_HWMON_POWER:
 443                        hwmon_prefix = "power";
 444                        hwmon_index = ++n_power; /* 1-based */
 445                        break;
 446                }
 447
 448                min1 = MCDI_ARRAY_FIELD(outbuf, SENSOR_ENTRY,
 449                                        SENSOR_INFO_ENTRY, j, MIN1);
 450                max1 = MCDI_ARRAY_FIELD(outbuf, SENSOR_ENTRY,
 451                                        SENSOR_INFO_ENTRY, j, MAX1);
 452                min2 = MCDI_ARRAY_FIELD(outbuf, SENSOR_ENTRY,
 453                                        SENSOR_INFO_ENTRY, j, MIN2);
 454                max2 = MCDI_ARRAY_FIELD(outbuf, SENSOR_ENTRY,
 455                                        SENSOR_INFO_ENTRY, j, MAX2);
 456
 457                if (min1 != max1) {
 458                        snprintf(name, sizeof(name), "%s%u_input",
 459                                 hwmon_prefix, hwmon_index);
 460                        efx_mcdi_mon_add_attr(
 461                                efx, name, efx_mcdi_mon_show_value, i, type, 0);
 462
 463                        if (hwmon_type != EFX_HWMON_POWER) {
 464                                snprintf(name, sizeof(name), "%s%u_min",
 465                                         hwmon_prefix, hwmon_index);
 466                                efx_mcdi_mon_add_attr(
 467                                        efx, name, efx_mcdi_mon_show_limit,
 468                                        i, type, min1);
 469                        }
 470
 471                        snprintf(name, sizeof(name), "%s%u_max",
 472                                 hwmon_prefix, hwmon_index);
 473                        efx_mcdi_mon_add_attr(
 474                                efx, name, efx_mcdi_mon_show_limit,
 475                                i, type, max1);
 476
 477                        if (min2 != max2) {
 478                                /* Assume max2 is critical value.
 479                                 * But we have no good way to expose min2.
 480                                 */
 481                                snprintf(name, sizeof(name), "%s%u_crit",
 482                                         hwmon_prefix, hwmon_index);
 483                                efx_mcdi_mon_add_attr(
 484                                        efx, name, efx_mcdi_mon_show_limit,
 485                                        i, type, max2);
 486                        }
 487                }
 488
 489                snprintf(name, sizeof(name), "%s%u_alarm",
 490                         hwmon_prefix, hwmon_index);
 491                efx_mcdi_mon_add_attr(
 492                        efx, name, efx_mcdi_mon_show_alarm, i, type, 0);
 493
 494                if (type < ARRAY_SIZE(efx_mcdi_sensor_type) &&
 495                    efx_mcdi_sensor_type[type].label) {
 496                        snprintf(name, sizeof(name), "%s%u_label",
 497                                 hwmon_prefix, hwmon_index);
 498                        efx_mcdi_mon_add_attr(
 499                                efx, name, efx_mcdi_mon_show_label, i, type, 0);
 500                }
 501        }
 502
 503hwmon_register:
 504        hwmon->groups[0] = &hwmon->group;
 505        hwmon->device = hwmon_device_register_with_groups(&efx->pci_dev->dev,
 506                                                          KBUILD_MODNAME, NULL,
 507                                                          hwmon->groups);
 508        if (IS_ERR(hwmon->device)) {
 509                rc = PTR_ERR(hwmon->device);
 510                goto fail;
 511        }
 512
 513        return 0;
 514
 515fail:
 516        efx_mcdi_mon_remove(efx);
 517        return rc;
 518}
 519
 520void efx_mcdi_mon_remove(struct efx_nic *efx)
 521{
 522        struct efx_mcdi_mon *hwmon = efx_mcdi_mon(efx);
 523
 524        if (hwmon->device)
 525                hwmon_device_unregister(hwmon->device);
 526        kfree(hwmon->attrs);
 527        kfree(hwmon->group.attrs);
 528        efx_nic_free_buffer(efx, &hwmon->dma_buf);
 529}
 530
 531#endif /* CONFIG_SFC_MCDI_MON */
 532