/** * @brief SILA sensors tables implementation. * * This file is part of sila-snmp project. * * Copyright (c) 2022 SILA * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ #include "tracing.hpp" #include "data/table.hpp" #include "data/table/item.hpp" #include "sila/sila_oid.hpp" #include "snmptrap.hpp" #include namespace sila { namespace sensors { /** * @brief Sensor implementation. */ struct Sensor : public phosphor::snmp::data::table::Item { /** * @brief State codes like in MIB file. */ enum state_t { E_DISABLED = 0, E_NORMAL, E_WARNING_LOW, E_WARNING_HIGH, E_CRITICAL_LOW, E_CRITICAL_HIGH, }; // SNMP table columns enum Columns { COLUMN_SILASENSOR_NAME = 1, COLUMN_SILASENSOR_VALUE, COLUMN_SILASENSOR_WARNLOW, COLUMN_SILASENSOR_WARNHIGH, COLUMN_SILASENSOR_CRITLOW, COLUMN_SILASENSOR_CRITHIGH, COLUMN_SILASENSOR_STATE, }; // Indexes of fields in tuple enum Fields { FIELD_SENSOR_VALUE = 0, FIELD_SENSOR_WARNLOW, FIELD_SENSOR_WARNLOW_ALARM, FIELD_SENSOR_WARNHI, FIELD_SENSOR_WARNHI_ALARM, FIELD_SENSOR_CRITLOW, FIELD_SENSOR_CRITLOW_ALARM, FIELD_SENSOR_CRITHI, FIELD_SENSOR_CRITHI_ALARM, }; // Sensor types (first letter in sensors folder name) enum Types { ST_TEMPERATURE = 't', // temperature ST_VOLTAGE = 'v', // voltage ST_TACHOMETER = 'f', // fan_tach ST_CURRENT = 'c', // current ST_POWER = 'p', // power }; /** * @brief Object contructor. */ Sensor(const std::string& folder, const std::string& name) : phosphor::snmp::data::table::Item( folder, name, .0, // Value .0, false, // WarningLow .0, false, // WarningHigh .0, false, // CriticalLow .0, false) // CriticalHigh { auto n = folder.rfind('/'); if (n != std::string::npos) { // Prepare for send traps switch (folder[n + 1]) { case ST_TEMPERATURE: _notifyOid.assign(SILA_OID(0, 2)); break; case ST_VOLTAGE: _notifyOid.assign(SILA_OID(0, 3)); break; case ST_TACHOMETER: _notifyOid.assign(SILA_OID(0, 4)); break; case ST_CURRENT: _notifyOid.assign(SILA_OID(0, 5)); break; case ST_POWER: _notifyOid.assign(SILA_OID(0, 6)); break; } // Correct scale power // Required for TEXTUAL-CONVENTION in SILA-MIB.txt switch (folder[n + 1]) { case ST_TACHOMETER: _power = 0; break; } } if (!_notifyOid.empty()) { phosphor::snmp::agent::make_oid( _stateOid, ".1.3.6.1.4.1.49769.1.%lu.1.7.\"%s\"", _notifyOid.back(), name.c_str()); } } /** * @brief Update fields with new values recieved from DBus. */ void setFields(const fields_map_t& fields) override { auto prevValue = getValue(); auto prevState = getState(); setField(fields, "Value"); setField(fields, "WarningLow"); setField(fields, "WarningAlarmLow"); setField(fields, "WarningHigh"); setField(fields, "WarningAlarmHigh"); setField(fields, "CriticalLow"); setField(fields, "CriticalAlarmLow"); setField(fields, "CriticalHigh"); setField(fields, "CriticalAlarmHigh"); auto lastState = getState(); if (prevValue != getValue() || prevState != lastState) { DEBUGMSGTL(("sila:sensors", "Sensor '%s' changed: %d -> %d, state: %d -> %d\n", name.c_str(), prevValue, getValue(), prevState, lastState)); } if (prevState != lastState) { send_notify(lastState); } } /** * @brief Called when sensor added to table. */ void onCreate() override { DEBUGMSGTL(("sila:sensors", "Sensor '%s' added, state=%d\n", name.c_str(), getState())); send_notify(E_NORMAL); } /** * @brief Called when sensor removed from table. */ void onDestroy() override { DEBUGMSGTL(("sila:sensors", "Sensor '%s' removed, state=%d\n", name.c_str(), getState())); send_notify(E_DISABLED); } /** * @brief Send snmptrap about changed state. * * @param state - state value for sent with trap. */ void send_notify(state_t state) { if (!_notifyOid.empty() && !_stateOid.empty()) { phosphor::snmp::agent::Trap trap(_notifyOid); trap.add_field(_stateOid.data(), _stateOid.size(), state); trap.send(); } else { TRACE_ERROR("Notify is unsupported for sensor '%s'\n", name.c_str()); } } /** * @brief Get current state. */ state_t getState() const { if (std::get(data)) { return E_CRITICAL_HIGH; } else if (std::get(data)) { return E_WARNING_HIGH; } else if (std::get(data)) { return E_WARNING_LOW; } else if (std::get(data)) { return E_CRITICAL_LOW; } return E_NORMAL; } /** * @brief snmp request handler. */ void get_snmp_reply(netsnmp_agent_request_info* reqinfo, netsnmp_request_info* request) const override { using namespace phosphor::snmp::agent; netsnmp_table_request_info* tinfo = netsnmp_extract_table_info(request); switch (tinfo->colnum) { case COLUMN_SILASENSOR_NAME: VariableList::set(request->requestvb, name); break; case COLUMN_SILASENSOR_VALUE: VariableList::set(request->requestvb, getValue()); break; case COLUMN_SILASENSOR_WARNLOW: VariableList::set(request->requestvb, getValue()); break; case COLUMN_SILASENSOR_WARNHIGH: VariableList::set(request->requestvb, getValue()); break; case COLUMN_SILASENSOR_CRITLOW: VariableList::set(request->requestvb, getValue()); break; case COLUMN_SILASENSOR_CRITHIGH: VariableList::set(request->requestvb, getValue()); break; case COLUMN_SILASENSOR_STATE: VariableList::set(request->requestvb, getState()); break; default: netsnmp_set_request_error(reqinfo, request, SNMP_NOSUCHOBJECT); break; } } /** * @brief Scale and round sensors value. */ template int getValue() const { return static_cast( std::round(std::get(data) * powf(10.f, _power))); } std::vector _notifyOid; std::vector _stateOid; int _power = 3; }; struct SensorsTable : public phosphor::snmp::data::Table { using OID = std::vector; SensorsTable(const std::string& folder, const std::string& tableName, const OID& tableOID) : phosphor::snmp::data::Table( "/xyz/openbmc_project/sensors/" + folder, { "xyz.openbmc_project.Sensor.Value", "xyz.openbmc_project.Sensor.Threshold.Warning", "xyz.openbmc_project.Sensor.Threshold.Critical", "xyz.openbmc_project.Sensor.Threshold.Fatal", }), tableName(tableName), tableOID(tableOID) { } std::string tableName; OID tableOID; }; static std::array sensors = { SensorsTable{"temperature", "silaTempSensorsTable", SILA_OID(1, 2)}, SensorsTable{"voltage", "silaVoltSensorsTable", SILA_OID(1, 3)}, SensorsTable{"fan_tach", "silaTachSensorsTable", SILA_OID(1, 4)}, SensorsTable{"current", "silaCurrSensorsTable", SILA_OID(1, 5)}, SensorsTable{"power", "silaPowerSensorsTable", SILA_OID(1, 6)}, }; /** * @brief Update all sensors. */ void update() { for (auto& s : sensors) { s.update(); } } /** * @brief Initialize sensors. */ void init() { DEBUGMSGTL(("sila:init", "Initialize silaSensors\n")); for (auto& s : sensors) { s.init_mib(s.tableName.c_str(), s.tableOID.data(), s.tableOID.size(), Sensor::COLUMN_SILASENSOR_NAME, Sensor::COLUMN_SILASENSOR_STATE); s.update(); } } /** * @brief Deinitialize sensors. */ void destroy() { DEBUGMSGTL(("sila:shutdown", "Destroy silaSensors\n")); for (auto& s : sensors) { unregister_mib(s.tableOID.data(), s.tableOID.size()); } } } // namespace sensors } // namespace sila