Add chassis intrusion sensor daemon
Chassis intrusion signal is read from PCH via I2C or from GPIO.
Create a new daemon to poll its status and expose to dbus.
Related patches to run test:
- meta-phosphor: dbus-sensors: Enable new service of intrusion sensor
https://gerrit.openbmc-project.xyz/#/c/openbmc/meta-phosphor/+/17063/
- redfish: chassis: add property of physical security
https://gerrit.openbmc-project.xyz/#/c/openbmc/bmcweb/+/17691/
Tested-by: Check intrusion status value by redfish,
verified PCH solution on WFP and GPIO solution on STP.
Change-Id: Id5e67abbd80bbf2ef502db49fa183d92d0d31bda
Signed-off-by: Qiang XU <qiang.xu@linux.intel.com>
diff --git a/src/ChassisIntrusionSensor.cpp b/src/ChassisIntrusionSensor.cpp
new file mode 100644
index 0000000..22c03ab
--- /dev/null
+++ b/src/ChassisIntrusionSensor.cpp
@@ -0,0 +1,373 @@
+/*
+// 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 <errno.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+#include <ChassisIntrusionSensor.hpp>
+#include <boost/asio.hpp>
+#include <boost/bind.hpp>
+#include <chrono>
+#include <iostream>
+#include <sdbusplus/asio/object_server.hpp>
+#include <string>
+#include <thread>
+
+extern "C" {
+#include <i2c/smbus.h>
+#include <linux/i2c-dev.h>
+}
+
+static constexpr bool DEBUG = false;
+
+static constexpr unsigned int intrusionSensorPollSec = 1;
+
+// SMLink Status Register
+const static constexpr size_t pchStatusRegIntrusion = 0x04;
+
+// Status bit field masks
+const static constexpr size_t pchRegMaskIntrusion = 0x01;
+
+// Hold current PCH register values
+static unsigned int intrudeValue;
+
+// gpio sysfs path
+constexpr const char* gpioPath = "/sys/class/gpio/";
+
+void ChassisIntrusionSensor::updateValue(const std::string newValue)
+{
+ // indicate that it is internal set call
+ mInternalSet = true;
+ mIface->set_property("Status", newValue);
+
+ mValue = newValue;
+
+ if (mOldValue == "Normal" && mValue != "Normal")
+ {
+ std::cerr << "save to SEL for intrusion assert event \n";
+ // TODO: call add SEL log API, depends on patch #13956
+ mOldValue = mValue;
+ }
+ else if (mOldValue != "Normal" && mValue == "Normal")
+ {
+ std::cerr << "save to SEL for intrusion de-assert event \n";
+ // TODO: call add SEL log API, depends on patch #13956
+ mOldValue = mValue;
+ }
+}
+
+int ChassisIntrusionSensor::i2cReadFromPch(int busId, int slaveAddr)
+{
+ std::string i2cBus = "/dev/i2c-" + std::to_string(busId);
+
+ int fd = open(i2cBus.c_str(), O_RDWR | O_CLOEXEC);
+ if (fd < 0)
+ {
+ std::cerr << "unable to open i2c device \n";
+ return -1;
+ }
+ if (ioctl(fd, I2C_SLAVE_FORCE, slaveAddr) < 0)
+ {
+ std::cerr << "unable to set device address\n";
+ close(fd);
+ return -1;
+ }
+
+ unsigned long funcs = 0;
+ if (ioctl(fd, I2C_FUNCS, &funcs) < 0)
+ {
+ std::cerr << "not support I2C_FUNCS \n";
+ close(fd);
+ return -1;
+ }
+
+ if (!(funcs & I2C_FUNC_SMBUS_READ_BYTE_DATA))
+ {
+ std::cerr << "not support I2C_FUNC_SMBUS_READ_BYTE_DATA \n";
+ close(fd);
+ return -1;
+ }
+
+ unsigned int statusValue;
+ unsigned int statusMask = pchRegMaskIntrusion;
+ unsigned int statusReg = pchStatusRegIntrusion;
+
+ statusValue = i2c_smbus_read_byte_data(fd, statusReg);
+ if (DEBUG)
+ {
+ std::cout << "\nRead bus " << busId << " addr " << slaveAddr
+ << ", value = " << statusValue << "\n";
+ }
+
+ close(fd);
+
+ if (statusValue < 0)
+ {
+ std::cerr << "i2c_smbus_read_byte_data failed \n";
+ return -1;
+ }
+
+ // Get status value with mask
+ int newValue = statusValue & statusMask;
+
+ if (DEBUG)
+ {
+ std::cout << "statusValue is " << statusValue << "\n";
+ std::cout << "Intrusion sensor value is " << newValue << "\n";
+ }
+
+ return newValue;
+}
+
+void ChassisIntrusionSensor::pollSensorStatusByPch()
+{
+ // setting a new experation implicitly cancels any pending async wait
+ mPollTimer.expires_from_now(
+ boost::posix_time::seconds(intrusionSensorPollSec));
+
+ mPollTimer.async_wait([&](const boost::system::error_code& ec) {
+ // case of timer expired
+ if (!ec)
+ {
+ int statusValue = i2cReadFromPch(mBusId, mSlaveAddr);
+ std::string newValue = statusValue ? "HardwareIntrusion" : "Normal";
+
+ // save value
+ if (mOverridenState)
+ {
+ newValue = mOverriddenValue;
+ }
+
+ if (newValue != "unknown" && mValue != newValue)
+ {
+ std::cout << "update value from " << mValue << " to "
+ << newValue << "\n";
+ updateValue(newValue);
+ }
+
+ // trigger next polling
+ pollSensorStatusByPch();
+ }
+ // case of being canceled
+ else if (ec == boost::asio::error::operation_aborted)
+ {
+ std::cerr << "Timer of intrusion sensor is cancelled. Return \n";
+ return;
+ }
+ });
+}
+
+void ChassisIntrusionSensor::readGpio()
+{
+ constexpr size_t readSize = sizeof("0");
+ std::string readBuf;
+ readBuf.resize(readSize);
+ lseek(mFd, 0, SEEK_SET);
+ size_t r = ::read(mFd, readBuf.data(), readSize);
+ if (r != readSize)
+ {
+ std::cerr << "Error reading gpio\n";
+ }
+ else
+ {
+ bool value = std::stoi(readBuf);
+ if (mGpioInverted)
+ {
+ value = !value;
+ }
+
+ // set string defined in chassis redfish schema
+ std::string newValue = value ? "HardwareIntrusion" : "Normal";
+
+ if (DEBUG)
+ {
+ std::cout << "\nGPIO value is " << value << "\n";
+ std::cout << "Intrusion sensor value is " << newValue << "\n";
+ }
+
+ // save value
+ if (mOverridenState)
+ {
+ newValue = mOverriddenValue;
+ }
+
+ if (newValue != "unknown" && mValue != newValue)
+ {
+ std::cout << "update value from " << mValue << " to " << newValue
+ << "\n";
+ updateValue(newValue);
+ }
+ }
+}
+
+void ChassisIntrusionSensor::pollSensorStatusByGpio(void)
+{
+ mInputDev.async_wait(
+ boost::asio::ip::tcp::socket::wait_error,
+ [this](const boost::system::error_code& ec) {
+ if (ec == boost::system::errc::bad_file_descriptor)
+ {
+ return; // we're being destroyed
+ }
+ else if (ec)
+ {
+ std::cerr << "Error on GPIO based intrusion sensor socket\n";
+ }
+ else
+ {
+ readGpio();
+ }
+ pollSensorStatusByGpio();
+ });
+}
+
+void ChassisIntrusionSensor::initGpioDeviceFile(const int index)
+{
+ std::string device = gpioPath + std::string("gpio") + std::to_string(index);
+ mFd = open((device + "/value").c_str(), O_RDONLY);
+ if (mFd < 0)
+ {
+ std::cerr << "Error opening gpio " << index << "\n";
+ return;
+ }
+ mInputDev.assign(boost::asio::ip::tcp::v4(), mFd);
+}
+
+int ChassisIntrusionSensor::setSensorValue(const std::string& req,
+ std::string& propertyValue)
+{
+ if (mInternalSet)
+ {
+ mInternalSet = false;
+ propertyValue = req;
+ }
+ else
+ {
+ mOverriddenValue = req;
+ mOverridenState = true;
+ }
+ return 1;
+}
+
+void ChassisIntrusionSensor::start(IntrusionSensorType type, int busId,
+ int slaveAddr, int gpioIndex,
+ bool gpioInverted)
+{
+ if (DEBUG)
+ {
+ std::cerr << "enter ChassisIntrusionSensor::start, type = " << type
+ << "\n";
+ if (type == IntrusionSensorType::pch)
+ {
+ std::cerr << "busId = " << busId << ", slaveAddr = " << slaveAddr
+ << "\n";
+ }
+ else if (type == IntrusionSensorType::gpio)
+ {
+ std::cerr << "gpioIndex = " << gpioIndex
+ << ", gpioInverted = " << gpioInverted << "\n";
+ }
+ }
+
+ if ((type == IntrusionSensorType::pch && busId == mBusId &&
+ slaveAddr == mSlaveAddr) ||
+ (type == IntrusionSensorType::gpio && gpioIndex == mGpioIndex &&
+ gpioInverted == mGpioInverted))
+ {
+ return;
+ }
+
+ mType = type;
+ mBusId = busId;
+ mSlaveAddr = slaveAddr;
+ mGpioIndex = gpioIndex;
+ mGpioInverted = gpioInverted;
+
+ if ((mType == IntrusionSensorType::pch && mBusId > 0 && mSlaveAddr > 0) ||
+ (mType == IntrusionSensorType::gpio && mGpioIndex > 0))
+ {
+ // initialize first if not initialized before
+ if (!mInitialized)
+ {
+ mIface->register_property(
+ "Status", mValue,
+ [&](const std::string& req, std::string& propertyValue) {
+ return setSensorValue(req, propertyValue);
+ });
+ mIface->initialize();
+
+ if (mType == IntrusionSensorType::gpio)
+ {
+ initGpioDeviceFile(mGpioIndex);
+ }
+
+ mInitialized = true;
+ }
+
+ // start polling value
+ if (mType == IntrusionSensorType::pch)
+ {
+ pollSensorStatusByPch();
+ }
+ else if (mType == IntrusionSensorType::gpio && mFd > 0)
+ {
+ pollSensorStatusByGpio();
+ }
+ }
+
+ // invalid para, release resource
+ else
+ {
+ if (mInitialized)
+ {
+ if (mType == IntrusionSensorType::pch)
+ {
+ mPollTimer.cancel();
+ }
+ else if (mType == IntrusionSensorType::gpio)
+ {
+ mInputDev.close();
+ close(mFd);
+ }
+ mInitialized = false;
+ }
+ }
+}
+
+ChassisIntrusionSensor::ChassisIntrusionSensor(
+ boost::asio::io_service& io,
+ std::shared_ptr<sdbusplus::asio::dbus_interface> iface) :
+ mPollTimer(io),
+ mIface(iface), mInputDev(io), mType(IntrusionSensorType::gpio), mBusId(-1),
+ mSlaveAddr(-1), mGpioIndex(-1), mGpioInverted(false), mValue("unknown"),
+ mOldValue("unknown")
+{
+}
+
+ChassisIntrusionSensor::~ChassisIntrusionSensor()
+{
+ if (mType == IntrusionSensorType::pch)
+ {
+ mPollTimer.cancel();
+ }
+ else if (mType == IntrusionSensorType::gpio)
+ {
+ mInputDev.close();
+ close(mFd);
+ }
+}