| /* |
| // 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 <fcntl.h> |
| #include <linux/i2c.h> |
| #include <sys/ioctl.h> |
| #include <sys/syslog.h> |
| #include <systemd/sd-journal.h> |
| #include <unistd.h> |
| |
| #include <Utils.hpp> |
| #include <boost/asio/error.hpp> |
| #include <boost/asio/io_context.hpp> |
| #include <boost/asio/posix/stream_descriptor.hpp> |
| #include <gpiod.hpp> |
| #include <sdbusplus/asio/object_server.hpp> |
| |
| #include <chrono> |
| #include <cstddef> |
| #include <cstdint> |
| #include <filesystem> |
| #include <fstream> |
| #include <iostream> |
| #include <memory> |
| #include <stdexcept> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| extern "C" |
| { |
| #include <i2c/smbus.h> |
| #include <linux/i2c-dev.h> |
| } |
| |
| 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<ChassisIntrusionPchSensor> 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<ChassisIntrusionPchSensor> 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<ChassisIntrusionHwmonSensor> 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<ChassisIntrusionHwmonSensor> 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<fs::path> 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(); |
| } |