/* // Copyright (c) 2018 Intel Corporation // // 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 "ChassisIntrusionSensor.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern "C" { #include #include } static constexpr bool debug = false; static constexpr unsigned int defaultPollSec = 1; static constexpr unsigned int sensorFailedPollSec = 5; static unsigned int intrusionSensorPollSec = defaultPollSec; static constexpr const char* hwIntrusionValStr = "xyz.openbmc_project.Chassis.Intrusion.Status.HardwareIntrusion"; static constexpr const char* normalValStr = "xyz.openbmc_project.Chassis.Intrusion.Status.Normal"; static constexpr const char* manualRearmStr = "xyz.openbmc_project.Chassis.Intrusion.RearmMode.Manual"; static constexpr const char* autoRearmStr = "xyz.openbmc_project.Chassis.Intrusion.RearmMode.Automatic"; // SMLink Status Register const static constexpr size_t pchStatusRegIntrusion = 0x04; // Status bit field masks const static constexpr size_t pchRegMaskIntrusion = 0x01; // Value to clear intrusion status hwmon file const static constexpr size_t intrusionStatusHwmonClearValue = 0; void ChassisIntrusionSensor::updateValue(const size_t& value) { std::string newValue = value != 0 ? hwIntrusionValStr : normalValStr; // Take no action if the hardware status does not change // Same semantics as Sensor::updateValue(const double&) if (newValue == mValue) { return; } if constexpr (debug) { std::cout << "Update value from " << mValue << " to " << newValue << "\n"; } // Automatic Rearm mode allows direct update // Manual Rearm mode requires a rearm action to clear the intrusion // status if (!mAutoRearm) { if (newValue == normalValStr) { // Chassis is first closed from being open. If it has been // rearmed externally, reset the flag, update mValue and // return, without having to write "Normal" to DBus property // (because the rearm action already did). // Otherwise, return with no more action. if (mRearmFlag) { mRearmFlag = false; mValue = newValue; } return; } } // Flush the rearm flag everytime it allows an update to Dbus mRearmFlag = false; // indicate that it is internal set call mOverridenState = false; mInternalSet = true; mIface->set_property("Status", newValue); mInternalSet = false; mValue = newValue; } int ChassisIntrusionPchSensor::readSensor() { int32_t statusMask = pchRegMaskIntrusion; int32_t statusReg = pchStatusRegIntrusion; int32_t value = i2c_smbus_read_byte_data(mBusFd, statusReg); if constexpr (debug) { std::cout << "Pch type: raw value is " << value << "\n"; } if (value < 0) { std::cerr << "i2c_smbus_read_byte_data failed \n"; return -1; } // Get status value with mask value &= statusMask; if constexpr (debug) { std::cout << "Pch type: masked raw value is " << value << "\n"; } return value; } void ChassisIntrusionPchSensor::pollSensorStatus() { std::weak_ptr weakRef = weak_from_this(); // setting a new experation implicitly cancels any pending async wait mPollTimer.expires_after(std::chrono::seconds(intrusionSensorPollSec)); mPollTimer.async_wait([weakRef](const boost::system::error_code& ec) { // case of being canceled if (ec == boost::asio::error::operation_aborted) { std::cerr << "Timer of intrusion sensor is cancelled\n"; return; } std::shared_ptr self = weakRef.lock(); if (!self) { std::cerr << "ChassisIntrusionSensor no self\n"; return; } int value = self->readSensor(); if (value < 0) { intrusionSensorPollSec = sensorFailedPollSec; } else { intrusionSensorPollSec = defaultPollSec; self->updateValue(value); } // trigger next polling self->pollSensorStatus(); }); } int ChassisIntrusionGpioSensor::readSensor() { mGpioLine.event_read(); auto value = mGpioLine.get_value(); if constexpr (debug) { std::cout << "Gpio type: raw value is " << value << "\n"; } return value; } void ChassisIntrusionGpioSensor::pollSensorStatus() { mGpioFd.async_wait( boost::asio::posix::stream_descriptor::wait_read, [this](const boost::system::error_code& ec) { if (ec == boost::system::errc::bad_file_descriptor) { return; // we're being destroyed } if (ec) { std::cerr << "Error on GPIO based intrusion sensor wait event\n"; } else { int value = readSensor(); if (value >= 0) { updateValue(value); } // trigger next polling pollSensorStatus(); } }); } int ChassisIntrusionHwmonSensor::readSensor() { int value = 0; std::fstream stream(mHwmonPath, std::ios::in | std::ios::out); if (!stream.good()) { std::cerr << "Error reading status at " << mHwmonPath << "\n"; return -1; } std::string line; if (!std::getline(stream, line)) { std::cerr << "Error reading status at " << mHwmonPath << "\n"; return -1; } try { value = std::stoi(line); if constexpr (debug) { std::cout << "Hwmon type: raw value is " << value << "\n"; } } catch (const std::invalid_argument& e) { std::cerr << "Error reading status at " << mHwmonPath << " : " << e.what() << "\n"; return -1; } // Reset chassis intrusion status after every reading stream << intrusionStatusHwmonClearValue; return value; } void ChassisIntrusionHwmonSensor::pollSensorStatus() { std::weak_ptr weakRef = weak_from_this(); // setting a new experation implicitly cancels any pending async wait mPollTimer.expires_after(std::chrono::seconds(intrusionSensorPollSec)); mPollTimer.async_wait([weakRef](const boost::system::error_code& ec) { // case of being canceled if (ec == boost::asio::error::operation_aborted) { std::cerr << "Timer of intrusion sensor is cancelled\n"; return; } std::shared_ptr self = weakRef.lock(); if (!self) { std::cerr << "ChassisIntrusionSensor no self\n"; return; } int value = self->readSensor(); if (value < 0) { intrusionSensorPollSec = sensorFailedPollSec; } else { intrusionSensorPollSec = defaultPollSec; self->updateValue(value); } // trigger next polling self->pollSensorStatus(); }); } int ChassisIntrusionSensor::setSensorValue(const std::string& req, std::string& propertyValue) { if (!mInternalSet) { /* 1. Assuming that setting property in Automatic mode causes no effect but only event logs and propertiesChanged signal (because the property will be updated continuously to the current hardware status anyway), only update Status property and raise rearm flag in Manual rearm mode. 2. Only accept Normal value from an external call. */ if (!mAutoRearm && req == normalValStr) { mRearmFlag = true; propertyValue = req; mOverridenState = true; } } else if (!mOverridenState) { propertyValue = req; } else { return 1; } // Send intrusion event to Redfish if (mValue == normalValStr && propertyValue != normalValStr) { sd_journal_send("MESSAGE=%s", "Chassis intrusion assert event", "PRIORITY=%i", LOG_INFO, "REDFISH_MESSAGE_ID=%s", "OpenBMC.0.1.ChassisIntrusionDetected", NULL); } else if (mValue == hwIntrusionValStr && propertyValue == normalValStr) { sd_journal_send("MESSAGE=%s", "Chassis intrusion de-assert event", "PRIORITY=%i", LOG_INFO, "REDFISH_MESSAGE_ID=%s", "OpenBMC.0.1.ChassisIntrusionReset", NULL); } return 1; } void ChassisIntrusionSensor::start() { mIface->register_property( "Status", mValue, [&](const std::string& req, std::string& propertyValue) { return setSensorValue(req, propertyValue); }); std::string rearmStr = mAutoRearm ? autoRearmStr : manualRearmStr; mIface->register_property("Rearm", rearmStr); mIface->initialize(); pollSensorStatus(); } ChassisIntrusionSensor::ChassisIntrusionSensor( bool autoRearm, sdbusplus::asio::object_server& objServer) : mValue(normalValStr), mAutoRearm(autoRearm), mObjServer(objServer) { mIface = mObjServer.add_interface("/xyz/openbmc_project/Chassis/Intrusion", "xyz.openbmc_project.Chassis.Intrusion"); } ChassisIntrusionPchSensor::ChassisIntrusionPchSensor( bool autoRearm, boost::asio::io_context& io, sdbusplus::asio::object_server& objServer, int busId, int slaveAddr) : ChassisIntrusionSensor(autoRearm, objServer), mPollTimer(io) { if (busId < 0 || slaveAddr <= 0) { throw std::invalid_argument( "Invalid i2c bus " + std::to_string(busId) + " address " + std::to_string(slaveAddr) + "\n"); } mSlaveAddr = slaveAddr; std::string devPath = "/dev/i2c-" + std::to_string(busId); // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) mBusFd = open(devPath.c_str(), O_RDWR | O_CLOEXEC); if (mBusFd < 0) { throw std::invalid_argument("Unable to open " + devPath + "\n"); } // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) if (ioctl(mBusFd, I2C_SLAVE_FORCE, mSlaveAddr) < 0) { throw std::runtime_error("Unable to set device address\n"); } unsigned long funcs = 0; // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) if (ioctl(mBusFd, I2C_FUNCS, &funcs) < 0) { throw std::runtime_error("Don't support I2C_FUNCS\n"); } if ((funcs & I2C_FUNC_SMBUS_READ_BYTE_DATA) == 0U) { throw std::runtime_error( "Do not have I2C_FUNC_SMBUS_READ_BYTE_DATA \n"); } } ChassisIntrusionGpioSensor::ChassisIntrusionGpioSensor( bool autoRearm, boost::asio::io_context& io, sdbusplus::asio::object_server& objServer, bool gpioInverted) : ChassisIntrusionSensor(autoRearm, objServer), mGpioInverted(gpioInverted), mGpioFd(io) { mGpioLine = gpiod::find_line(mPinName); if (!mGpioLine) { throw std::invalid_argument( "Error finding gpio pin name: " + mPinName + "\n"); } mGpioLine.request( {"ChassisIntrusionSensor", gpiod::line_request::EVENT_BOTH_EDGES, mGpioInverted ? gpiod::line_request::FLAG_ACTIVE_LOW : 0}); auto gpioLineFd = mGpioLine.event_get_fd(); if (gpioLineFd < 0) { throw std::invalid_argument("Failed to get " + mPinName + " fd\n"); } mGpioFd.assign(gpioLineFd); } ChassisIntrusionHwmonSensor::ChassisIntrusionHwmonSensor( bool autoRearm, boost::asio::io_context& io, sdbusplus::asio::object_server& objServer, std::string hwmonName) : ChassisIntrusionSensor(autoRearm, objServer), mHwmonName(std::move(hwmonName)), mPollTimer(io) { std::vector paths; if (!findFiles(fs::path("/sys/class/hwmon"), mHwmonName, paths)) { throw std::invalid_argument("Failed to find hwmon path in sysfs\n"); } if (paths.empty()) { throw std::invalid_argument( "Hwmon file " + mHwmonName + " can't be found in sysfs\n"); } if (paths.size() > 1) { std::cerr << "Found more than 1 hwmon file to read chassis intrusion" << " status. Taking the first one. \n"; } // Expecting only one hwmon file for one given chassis mHwmonPath = paths[0].string(); if constexpr (debug) { std::cout << "Found " << paths.size() << " paths for intrusion status \n" << " The first path is: " << mHwmonPath << "\n"; } } ChassisIntrusionSensor::~ChassisIntrusionSensor() { mObjServer.remove_interface(mIface); } ChassisIntrusionPchSensor::~ChassisIntrusionPchSensor() { mPollTimer.cancel(); if (close(mBusFd) < 0) { std::cerr << "Failed to close fd " << std::to_string(mBusFd); } } ChassisIntrusionGpioSensor::~ChassisIntrusionGpioSensor() { mGpioFd.close(); if (mGpioLine) { mGpioLine.release(); } } ChassisIntrusionHwmonSensor::~ChassisIntrusionHwmonSensor() { mPollTimer.cancel(); }