/* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2020 Oxide Computer Company */ /* * Handle and report sensors found on some igb parts. * * The Intel I350 has a built-in thermal sensor diode and an optional External * Thermal Sensor configuration. This external configuration is provided through * an optional space in the NVM and allows for up to 4 external sensors to be * defined. Currently, the only defined external thermal sensor is the Microchip * EMC 1413. As of this time, we haven't encountered a device that uses the EMC * 1413 in the wild, so while the definitions here are present, that is stubbed * out for the time. * * When accessing the internal sensor, the I350 Datasheet requires that we take * software/firmware semaphore before proceeding. */ #include "igb_sw.h" #include #include /* * Thermal register values. */ #define E1000_THMJT_TEMP(x) BITX(x, 8, 0) #define E1000_THMJT_VALID(x) BITX(x, 31, 31) #define E1000_THMJT_RESOLUTION 1 #define E1000_THMJT_PRECISION 5 /* * Misc. definitions required for accessing the NVM space. */ #define IGB_NVM_ETS_CFG 0x3e #define IGB_NVM_ETS_CFG_NSENSORS(x) BITX(x, 2, 0) #define IGB_NVM_ETS_CFG_TYPE(x) BITX(x, 5, 3) #define IGB_NVM_ETS_CFG_TYPE_EMC1413 0 #define IGB_NVM_ETS_SENSOR_LOC(x) BITX(x, 13, 10) #define IGB_NVM_ETS_SENSOR_INDEX(x) BITX(x, 9, 8) #define IGB_NVM_ETS_SENSOR_THRESH(x) BITX(x, 7, 0) #define IGB_ETS_I2C_ADDRESS 0xf8 /* * These definitions come from the Microchip datasheet for the thermal diode * sensor defined by the external spec. These parts have an accuracy of 1 degree * and a granularity of 1/8th of a degree. */ #define EMC1413_REG_CFG 0x03 #define EMC1413_REG_CFG_RANGE (1 << 2) #define EMC1413_RANGE_ADJ (-64) #define EMC1413_REG_INT_DIODE_HI 0x00 #define EMC1413_REG_INT_DIODE_LO 0x29 #define EMC1413_REG_EXT1_DIODE_HI 0x01 #define EMC1413_REG_EXT1_DIODE_LO 0x10 #define EMC1413_REG_EXT2_DIODE_HI 0x23 #define EMC1413_REG_EXT2_DIODE_LO 0x24 #define EMC1413_REG_EXT3_DIODE_HI 0x2a #define EMC1413_REG_EXT3_DIODE_LO 0x2b static int igb_sensor_reg_temperature(void *arg, sensor_ioctl_scalar_t *scalar) { igb_t *igb = arg; uint32_t reg; if (igb->hw.mac.ops.acquire_swfw_sync(&igb->hw, E1000_SWFW_PWRTS_SM) != E1000_SUCCESS) { return (EIO); } reg = E1000_READ_REG(&igb->hw, E1000_THMJT); igb->hw.mac.ops.release_swfw_sync(&igb->hw, E1000_SWFW_PWRTS_SM); if (E1000_THMJT_VALID(reg) == 0) { return (EIO); } scalar->sis_unit = SENSOR_UNIT_CELSIUS; scalar->sis_gran = E1000_THMJT_RESOLUTION; scalar->sis_prec = E1000_THMJT_PRECISION; scalar->sis_value = E1000_THMJT_TEMP(reg); return (0); } static const ksensor_ops_t igb_sensor_reg_ops = { .kso_kind = ksensor_kind_temperature, .kso_scalar = igb_sensor_reg_temperature }; static boolean_t igb_sensors_create_minors(igb_t *igb) { int ret; igb_sensors_t *sp = &igb->igb_sensors; if ((ret = ksensor_create_scalar_pcidev(igb->dip, SENSOR_KIND_TEMPERATURE, &igb_sensor_reg_ops, igb, "builtin", &sp->isn_reg_ksensor)) != 0) { igb_log(igb, IGB_LOG_ERROR, "failed to create main sensor: %d", ret); return (B_FALSE); } return (B_TRUE); } static boolean_t igb_sensors_init_ets(igb_t *igb, uint_t ets_off, uint_t index) { uint16_t val; int ret; igb_sensors_t *sensors = &igb->igb_sensors; igb_ets_t *etsp = &sensors->isn_ets[sensors->isn_nents]; igb_ets_loc_t loc; if ((ret = e1000_read_nvm(&igb->hw, ets_off, 1, &val)) != E1000_SUCCESS) { igb_log(igb, IGB_LOG_ERROR, "failed to read ETS word " "at offset 0x%x: error %d", ets_off, ret); return (B_FALSE); } /* * The data sheet says that if the location is listed as N/A, then we * should not display this sensor. In this case, we just skip it. */ loc = IGB_NVM_ETS_SENSOR_LOC(val); if (loc == IGB_ETS_LOC_NA) { return (B_TRUE); } etsp->iet_loc = loc; etsp->iet_index = IGB_NVM_ETS_SENSOR_INDEX(val); etsp->iet_thresh = IGB_NVM_ETS_SENSOR_THRESH(val); sensors->isn_nents++; return (B_TRUE); } void igb_init_sensors(igb_t *igb) { struct e1000_hw *hw = &igb->hw; uint16_t ets_off; /* * Only the I350 supports the thermal temperature sensor values. This is * device-wide, so only enumerate on bus zero. */ hw = &igb->hw; if (hw->mac.type != e1000_i350 || hw->bus.func != 0) { return; } ets_off = 0xffff; (void) e1000_read_nvm(hw, IGB_NVM_ETS_CFG, 1, &ets_off); if (ets_off != 0 && ets_off != 0xffff) { int ret; uint_t nents, i; uint16_t val; /* * Swallow the fact that we can't read the ETS config. */ if ((ret = e1000_read_nvm(hw, ets_off, 1, &val)) != E1000_SUCCESS) { igb_log(igb, IGB_LOG_ERROR, "failed to read ETS word " "at offset 0x%x: error %d", ets_off, ret); return; } /* * If we don't find this, assume we can't use the external * sensor either. */ if (IGB_NVM_ETS_CFG_TYPE(val) != IGB_NVM_ETS_CFG_TYPE_EMC1413) { return; } nents = IGB_NVM_ETS_CFG_NSENSORS(val); if (nents > IGB_ETS_MAX) { igb_log(igb, IGB_LOG_ERROR, "firmware NVM ETS " "configuration has more entries (%d) than allowed", nents); nents = IGB_ETS_MAX; } for (i = 0; i < nents; i++) { if (!igb_sensors_init_ets(igb, ets_off, i)) { return; } } } if (!igb_sensors_create_minors(igb)) { (void) ksensor_remove(igb->dip, KSENSOR_ALL_IDS); return; } igb->igb_sensors.isn_valid = B_TRUE; } void igb_fini_sensors(igb_t *igb) { if (igb->igb_sensors.isn_valid) { (void) ksensor_remove(igb->dip, KSENSOR_ALL_IDS); igb->igb_sensors.isn_valid = B_FALSE; } }