Move source files into application-specific sub-directories
Currently, dbus-sensors implement multiple applications:
- psusensor
- adcsensor
- intelcpusensor
- hwmontempsensor
- ipmbsensor
- nvmesensor
- externalsensor
- mcutempsensor
- intrusionsensor
- fansensor
- exitairtempsensor
This commit is to create separate directories for each application so
that things can be separated more easily and the files are smaller,
instead of creating one huge file for the sensor implementation.
There was some discussion in discord on this. [1][2]
[1]: https://discord.com/channels/775381525260664832/1187158775438778408/1284106093756289067
[2]: https://discord.com/channels/775381525260664832/867820390406422538/1303217796821553214
Signed-off-by: George Liu <liuxiwei@ieisystem.com>
Change-Id: I258fc2ee7d8f939c7b83a07350395e78775b2b8d
diff --git a/src/external/ExternalSensor.cpp b/src/external/ExternalSensor.cpp
new file mode 100644
index 0000000..506823a
--- /dev/null
+++ b/src/external/ExternalSensor.cpp
@@ -0,0 +1,202 @@
+#include "ExternalSensor.hpp"
+
+#include "SensorPaths.hpp"
+#include "Thresholds.hpp"
+#include "Utils.hpp"
+#include "sensor.hpp"
+
+#include <sdbusplus/asio/connection.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+
+#include <chrono>
+#include <cstddef>
+#include <functional>
+#include <iostream>
+#include <limits>
+#include <memory>
+#include <stdexcept>
+#include <string>
+#include <utility>
+#include <vector>
+
+static constexpr bool debug = false;
+
+ExternalSensor::ExternalSensor(
+ const std::string& objectType, sdbusplus::asio::object_server& objectServer,
+ std::shared_ptr<sdbusplus::asio::connection>& conn,
+ const std::string& sensorName, const std::string& sensorUnits,
+ std::vector<thresholds::Threshold>&& thresholdsIn,
+ const std::string& sensorConfiguration, double maxReading,
+ double minReading, double timeoutSecs, const PowerState& powerState) :
+ Sensor(escapeName(sensorName), std::move(thresholdsIn), sensorConfiguration,
+ objectType, true, true, maxReading, minReading, conn, powerState),
+ objServer(objectServer), writeLast(std::chrono::steady_clock::now()),
+ writeTimeout(
+ std::chrono::duration_cast<std::chrono::steady_clock::duration>(
+ std::chrono::duration<double>(timeoutSecs))),
+ writePerishable(timeoutSecs > 0.0)
+{
+ // The caller must specify what physical characteristic
+ // an external sensor is expected to be measuring, such as temperature,
+ // as, unlike others, this is not implied by device type name.
+ std::string dbusPath = sensor_paths::getPathForUnits(sensorUnits);
+ if (dbusPath.empty())
+ {
+ throw std::runtime_error("Units not in allow list");
+ }
+ std::string objectPath = "/xyz/openbmc_project/sensors/";
+ objectPath += dbusPath;
+ objectPath += '/';
+ objectPath += sensorName;
+
+ sensorInterface = objectServer.add_interface(
+ objectPath, "xyz.openbmc_project.Sensor.Value");
+
+ for (const auto& threshold : thresholds)
+ {
+ std::string interface = thresholds::getInterface(threshold.level);
+ thresholdInterfaces[static_cast<size_t>(threshold.level)] =
+ objectServer.add_interface(objectPath, interface);
+ }
+
+ association =
+ objectServer.add_interface(objectPath, association::interface);
+ setInitialProperties(sensorUnits);
+
+ if constexpr (debug)
+ {
+ std::cerr << "ExternalSensor " << name << " constructed: path "
+ << configurationPath << ", type " << objectType << ", min "
+ << minReading << ", max " << maxReading << ", timeout "
+ << std::chrono::duration_cast<std::chrono::microseconds>(
+ writeTimeout)
+ .count()
+ << " us\n";
+ }
+}
+
+// Separate function from constructor, because of a gotcha: can't use the
+// enable_shared_from_this() API until after the constructor has completed.
+void ExternalSensor::initWriteHook(
+ std::function<void(std::chrono::steady_clock::time_point now)>&&
+ writeHookIn)
+{
+ // Connect ExternalSensorMain with ExternalSensor
+ writeHook = std::move(writeHookIn);
+
+ // Connect ExternalSensor with Sensor
+ auto weakThis = weak_from_this();
+ externalSetHook = [weakThis]() {
+ auto lockThis = weakThis.lock();
+ if (lockThis)
+ {
+ lockThis->externalSetTrigger();
+ return;
+ }
+ if constexpr (debug)
+ {
+ std::cerr << "ExternalSensor receive ignored, sensor gone\n";
+ }
+ };
+}
+
+ExternalSensor::~ExternalSensor()
+{
+ // Make sure the write hook does not reference this object anymore
+ externalSetHook = nullptr;
+
+ objServer.remove_interface(association);
+ for (const auto& iface : thresholdInterfaces)
+ {
+ objServer.remove_interface(iface);
+ }
+ objServer.remove_interface(sensorInterface);
+
+ if constexpr (debug)
+ {
+ std::cerr << "ExternalSensor " << name << " destructed\n";
+ }
+}
+
+void ExternalSensor::checkThresholds()
+{
+ thresholds::checkThresholds(this);
+}
+
+bool ExternalSensor::isAliveAndPerishable() const
+{
+ return (writeAlive && writePerishable);
+}
+
+bool ExternalSensor::isAliveAndFresh(
+ const std::chrono::steady_clock::time_point& now) const
+{
+ // Must be alive and perishable, to have possibility of being fresh
+ if (!isAliveAndPerishable())
+ {
+ return false;
+ }
+
+ // If age, as of now, is less than timeout, it is deemed fresh
+ // NOLINTNEXTLINE
+ return (ageElapsed(now) < writeTimeout);
+}
+
+void ExternalSensor::writeBegin(
+ const std::chrono::steady_clock::time_point& now)
+{
+ if (!writeAlive)
+ {
+ std::cerr << "ExternalSensor " << name
+ << " online, receiving first value " << value << "\n";
+ }
+
+ writeLast = now;
+ writeAlive = true;
+}
+
+void ExternalSensor::writeInvalidate()
+{
+ writeAlive = false;
+
+ std::cerr << "ExternalSensor " << name << " offline, timed out\n";
+
+ // Take back control of this sensor from the external override,
+ // as the external source has timed out.
+ // This allows sensor::updateValue() to work normally,
+ // as it would do for internal sensors with values from hardware.
+ overriddenState = false;
+
+ // Invalidate the existing Value, similar to what internal sensors do,
+ // when they encounter errors trying to read from hardware.
+ updateValue(std::numeric_limits<double>::quiet_NaN());
+}
+
+std::chrono::steady_clock::duration ExternalSensor::ageElapsed(
+ const std::chrono::steady_clock::time_point& now) const
+{
+ // Comparing 2 time_point will return duration
+ return (now - writeLast);
+}
+
+std::chrono::steady_clock::duration ExternalSensor::ageRemaining(
+ const std::chrono::steady_clock::time_point& now) const
+{
+ // Comparing duration will return another duration
+ return (writeTimeout - ageElapsed(now));
+}
+
+void ExternalSensor::externalSetTrigger()
+{
+ if constexpr (debug)
+ {
+ std::cerr << "ExternalSensor " << name << " received " << value << "\n";
+ }
+
+ auto now = std::chrono::steady_clock::now();
+
+ writeBegin(now);
+
+ // Tell the owner to recalculate the expiration timer
+ writeHook(now);
+}
diff --git a/src/external/ExternalSensor.hpp b/src/external/ExternalSensor.hpp
new file mode 100644
index 0000000..c3a7103
--- /dev/null
+++ b/src/external/ExternalSensor.hpp
@@ -0,0 +1,65 @@
+#pragma once
+
+#include "Thresholds.hpp"
+#include "sensor.hpp"
+
+#include <sdbusplus/asio/object_server.hpp>
+
+#include <chrono>
+#include <string>
+#include <vector>
+
+class ExternalSensor :
+ public Sensor,
+ public std::enable_shared_from_this<ExternalSensor>
+{
+ public:
+ ExternalSensor(
+ const std::string& objectType,
+ sdbusplus::asio::object_server& objectServer,
+ std::shared_ptr<sdbusplus::asio::connection>& conn,
+ const std::string& sensorName, const std::string& sensorUnits,
+ std::vector<thresholds::Threshold>&& thresholdsIn,
+ const std::string& sensorConfiguration, double maxReading,
+ double minReading, double timeoutSecs, const PowerState& powerState);
+ ~ExternalSensor() override;
+
+ // Call this immediately after calling the constructor
+ void initWriteHook(
+ std::function<void(std::chrono::steady_clock::time_point now)>&&
+ writeHookIn);
+
+ // Returns true if sensor has external Value that is subject to timeout
+ bool isAliveAndPerishable() const;
+
+ // Returns true if AliveAndPerishable and timeout has not yet happened
+ bool
+ isAliveAndFresh(const std::chrono::steady_clock::time_point& now) const;
+
+ // Marks the time when Value successfully received from external source
+ void writeBegin(const std::chrono::steady_clock::time_point& now);
+
+ // Marks sensor as timed out, replacing Value with floating-point "NaN"
+ void writeInvalidate();
+
+ // Returns amount of time elapsed since last writeBegin() happened
+ std::chrono::steady_clock::duration
+ ageElapsed(const std::chrono::steady_clock::time_point& now) const;
+
+ // Returns amount of time remaining until sensor timeout will happen
+ std::chrono::steady_clock::duration
+ ageRemaining(const std::chrono::steady_clock::time_point& now) const;
+
+ private:
+ sdbusplus::asio::object_server& objServer;
+
+ std::chrono::steady_clock::time_point writeLast;
+ std::chrono::steady_clock::duration writeTimeout;
+ bool writeAlive{false};
+ bool writePerishable;
+ std::function<void(const std::chrono::steady_clock::time_point& now)>
+ writeHook;
+
+ void checkThresholds() override;
+ void externalSetTrigger();
+};
diff --git a/src/external/ExternalSensorMain.cpp b/src/external/ExternalSensorMain.cpp
new file mode 100644
index 0000000..9bd7a4d
--- /dev/null
+++ b/src/external/ExternalSensorMain.cpp
@@ -0,0 +1,417 @@
+#include "ExternalSensor.hpp"
+#include "Thresholds.hpp"
+#include "Utils.hpp"
+#include "VariantVisitors.hpp"
+
+#include <boost/asio/error.hpp>
+#include <boost/asio/io_context.hpp>
+#include <boost/asio/post.hpp>
+#include <boost/asio/steady_timer.hpp>
+#include <boost/container/flat_map.hpp>
+#include <boost/container/flat_set.hpp>
+#include <sdbusplus/asio/connection.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+#include <sdbusplus/bus/match.hpp>
+#include <sdbusplus/message.hpp>
+#include <sdbusplus/message/native_types.hpp>
+
+#include <algorithm>
+#include <array>
+#include <chrono>
+#include <cmath>
+#include <functional>
+#include <iostream>
+#include <memory>
+#include <string>
+#include <utility>
+#include <variant>
+#include <vector>
+
+// Copied from HwmonTempSensor and inspired by
+// https://gerrit.openbmc-project.xyz/c/openbmc/dbus-sensors/+/35476
+
+// The ExternalSensor is a sensor whose value is intended to be writable
+// by something external to the BMC, so that the host (or something else)
+// can write to it, perhaps by using an IPMI or Redfish connection.
+
+// Unlike most other sensors, an external sensor does not correspond
+// to a hwmon file or any other kernel/hardware interface,
+// so, after initialization, this module does not have much to do,
+// but it handles reinitialization and thresholds, similar to the others.
+// The main work of this module is to provide backing storage for a
+// sensor that exists only virtually, and to provide an optional
+// timeout service for detecting loss of timely updates.
+
+// As there is no corresponding driver or hardware to support,
+// all configuration of this sensor comes from the JSON parameters:
+// MinValue, MaxValue, Timeout, PowerState, Units, Name
+
+// The purpose of "Units" is to specify the physical characteristic
+// the external sensor is measuring, because with an external sensor
+// there is no other way to tell, and it will be used for the object path
+// here: /xyz/openbmc_project/sensors/<Units>/<Name>
+
+// For more information, see external-sensor.md design document:
+// https://gerrit.openbmc-project.xyz/c/openbmc/docs/+/41452
+// https://github.com/openbmc/docs/tree/master/designs/
+
+static constexpr bool debug = false;
+
+static const char* sensorType = "ExternalSensor";
+
+void updateReaper(
+ boost::container::flat_map<std::string, std::shared_ptr<ExternalSensor>>&
+ sensors,
+ boost::asio::steady_timer& timer,
+ const std::chrono::steady_clock::time_point& now)
+{
+ // First pass, reap all stale sensors
+ for (const auto& [name, sensor] : sensors)
+ {
+ if (!sensor)
+ {
+ continue;
+ }
+
+ if (!sensor->isAliveAndPerishable())
+ {
+ continue;
+ }
+
+ if (!sensor->isAliveAndFresh(now))
+ {
+ // Mark sensor as dead, no longer alive
+ sensor->writeInvalidate();
+ }
+ }
+
+ std::chrono::steady_clock::duration nextCheck;
+ bool needCheck = false;
+
+ // Second pass, determine timer interval to next check
+ for (const auto& [name, sensor] : sensors)
+ {
+ if (!sensor)
+ {
+ continue;
+ }
+
+ if (!sensor->isAliveAndPerishable())
+ {
+ continue;
+ }
+
+ auto expiration = sensor->ageRemaining(now);
+
+ if (needCheck)
+ {
+ nextCheck = std::min(nextCheck, expiration);
+ }
+ else
+ {
+ // Initialization
+ nextCheck = expiration;
+ needCheck = true;
+ }
+ }
+
+ if (!needCheck)
+ {
+ if constexpr (debug)
+ {
+ std::cerr << "Next ExternalSensor timer idle\n";
+ }
+
+ return;
+ }
+
+ timer.expires_at(now + nextCheck);
+
+ timer.async_wait([&sensors, &timer](const boost::system::error_code& err) {
+ if (err != boost::system::errc::success)
+ {
+ // Cancellation is normal, as timer is dynamically rescheduled
+ if (err != boost::asio::error::operation_aborted)
+ {
+ std::cerr << "ExternalSensor timer scheduling problem: "
+ << err.message() << "\n";
+ }
+ return;
+ }
+
+ updateReaper(sensors, timer, std::chrono::steady_clock::now());
+ });
+
+ if constexpr (debug)
+ {
+ std::cerr << "Next ExternalSensor timer "
+ << std::chrono::duration_cast<std::chrono::microseconds>(
+ nextCheck)
+ .count()
+ << " us\n";
+ }
+}
+
+void createSensors(
+ sdbusplus::asio::object_server& objectServer,
+ boost::container::flat_map<std::string, std::shared_ptr<ExternalSensor>>&
+ sensors,
+ std::shared_ptr<sdbusplus::asio::connection>& dbusConnection,
+ const std::shared_ptr<boost::container::flat_set<std::string>>&
+ sensorsChanged,
+ boost::asio::steady_timer& reaperTimer)
+{
+ if constexpr (debug)
+ {
+ std::cerr << "ExternalSensor considering creating sensors\n";
+ }
+
+ auto getter = std::make_shared<GetSensorConfiguration>(
+ dbusConnection,
+ [&objectServer, &sensors, &dbusConnection, sensorsChanged,
+ &reaperTimer](const ManagedObjectType& sensorConfigurations) {
+ bool firstScan = (sensorsChanged == nullptr);
+
+ for (const std::pair<sdbusplus::message::object_path, SensorData>&
+ sensor : sensorConfigurations)
+ {
+ const std::string& interfacePath = sensor.first.str;
+ const SensorData& sensorData = sensor.second;
+
+ auto sensorBase =
+ sensorData.find(configInterfaceName(sensorType));
+ if (sensorBase == sensorData.end())
+ {
+ std::cerr << "Base configuration not found for "
+ << interfacePath << "\n";
+ continue;
+ }
+
+ const SensorBaseConfiguration& baseConfiguration = *sensorBase;
+ const SensorBaseConfigMap& baseConfigMap =
+ baseConfiguration.second;
+
+ // MinValue and MinValue are mandatory numeric parameters
+ auto minFound = baseConfigMap.find("MinValue");
+ if (minFound == baseConfigMap.end())
+ {
+ std::cerr << "MinValue parameter not found for "
+ << interfacePath << "\n";
+ continue;
+ }
+ double minValue =
+ std::visit(VariantToDoubleVisitor(), minFound->second);
+ if (!std::isfinite(minValue))
+ {
+ std::cerr << "MinValue parameter not parsed for "
+ << interfacePath << "\n";
+ continue;
+ }
+
+ auto maxFound = baseConfigMap.find("MaxValue");
+ if (maxFound == baseConfigMap.end())
+ {
+ std::cerr << "MaxValue parameter not found for "
+ << interfacePath << "\n";
+ continue;
+ }
+ double maxValue =
+ std::visit(VariantToDoubleVisitor(), maxFound->second);
+ if (!std::isfinite(maxValue))
+ {
+ std::cerr << "MaxValue parameter not parsed for "
+ << interfacePath << "\n";
+ continue;
+ }
+
+ double timeoutSecs = 0.0;
+
+ // Timeout is an optional numeric parameter
+ auto timeoutFound = baseConfigMap.find("Timeout");
+ if (timeoutFound != baseConfigMap.end())
+ {
+ timeoutSecs = std::visit(VariantToDoubleVisitor(),
+ timeoutFound->second);
+ }
+ if (!std::isfinite(timeoutSecs) || (timeoutSecs < 0.0))
+ {
+ std::cerr << "Timeout parameter not parsed for "
+ << interfacePath << "\n";
+ continue;
+ }
+
+ std::string sensorName;
+ std::string sensorUnits;
+
+ // Name and Units are mandatory string parameters
+ auto nameFound = baseConfigMap.find("Name");
+ if (nameFound == baseConfigMap.end())
+ {
+ std::cerr << "Name parameter not found for "
+ << interfacePath << "\n";
+ continue;
+ }
+ sensorName =
+ std::visit(VariantToStringVisitor(), nameFound->second);
+ if (sensorName.empty())
+ {
+ std::cerr << "Name parameter not parsed for "
+ << interfacePath << "\n";
+ continue;
+ }
+
+ auto unitsFound = baseConfigMap.find("Units");
+ if (unitsFound == baseConfigMap.end())
+ {
+ std::cerr << "Units parameter not found for "
+ << interfacePath << "\n";
+ continue;
+ }
+ sensorUnits =
+ std::visit(VariantToStringVisitor(), unitsFound->second);
+ if (sensorUnits.empty())
+ {
+ std::cerr << "Units parameter not parsed for "
+ << interfacePath << "\n";
+ continue;
+ }
+
+ // on rescans, only update sensors we were signaled by
+ auto findSensor = sensors.find(sensorName);
+ if (!firstScan && (findSensor != sensors.end()))
+ {
+ std::string suffixName = "/";
+ suffixName += findSensor->second->name;
+ bool found = false;
+ for (auto it = sensorsChanged->begin();
+ it != sensorsChanged->end(); it++)
+ {
+ std::string suffixIt = "/";
+ suffixIt += *it;
+ if (suffixIt.ends_with(suffixName))
+ {
+ sensorsChanged->erase(it);
+ findSensor->second = nullptr;
+ found = true;
+ if constexpr (debug)
+ {
+ std::cerr << "ExternalSensor " << sensorName
+ << " change found\n";
+ }
+ break;
+ }
+ }
+ if (!found)
+ {
+ continue;
+ }
+ }
+
+ std::vector<thresholds::Threshold> sensorThresholds;
+ if (!parseThresholdsFromConfig(sensorData, sensorThresholds))
+ {
+ std::cerr << "error populating thresholds for "
+ << sensorName << "\n";
+ }
+
+ PowerState readState = getPowerState(baseConfigMap);
+
+ auto& sensorEntry = sensors[sensorName];
+ sensorEntry = nullptr;
+
+ sensorEntry = std::make_shared<ExternalSensor>(
+ sensorType, objectServer, dbusConnection, sensorName,
+ sensorUnits, std::move(sensorThresholds), interfacePath,
+ maxValue, minValue, timeoutSecs, readState);
+ sensorEntry->initWriteHook(
+ [&sensors, &reaperTimer](
+ const std::chrono::steady_clock::time_point& now) {
+ updateReaper(sensors, reaperTimer, now);
+ });
+
+ if constexpr (debug)
+ {
+ std::cerr
+ << "ExternalSensor " << sensorName << " created\n";
+ }
+ }
+ });
+
+ getter->getConfiguration(std::vector<std::string>{sensorType});
+}
+
+int main()
+{
+ if constexpr (debug)
+ {
+ std::cerr << "ExternalSensor service starting up\n";
+ }
+
+ boost::asio::io_context io;
+ auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
+ sdbusplus::asio::object_server objectServer(systemBus, true);
+
+ objectServer.add_manager("/xyz/openbmc_project/sensors");
+ systemBus->request_name("xyz.openbmc_project.ExternalSensor");
+
+ boost::container::flat_map<std::string, std::shared_ptr<ExternalSensor>>
+ sensors;
+ auto sensorsChanged =
+ std::make_shared<boost::container::flat_set<std::string>>();
+ boost::asio::steady_timer reaperTimer(io);
+
+ boost::asio::post(io, [&objectServer, &sensors, &systemBus,
+ &reaperTimer]() {
+ createSensors(objectServer, sensors, systemBus, nullptr, reaperTimer);
+ });
+
+ boost::asio::steady_timer filterTimer(io);
+ std::function<void(sdbusplus::message_t&)> eventHandler =
+ [&objectServer, &sensors, &systemBus, &sensorsChanged, &filterTimer,
+ &reaperTimer](sdbusplus::message_t& message) mutable {
+ if (message.is_method_error())
+ {
+ std::cerr << "callback method error\n";
+ return;
+ }
+
+ const auto* messagePath = message.get_path();
+ sensorsChanged->insert(messagePath);
+ if constexpr (debug)
+ {
+ std::cerr << "ExternalSensor change event received: "
+ << messagePath << "\n";
+ }
+
+ // this implicitly cancels the timer
+ filterTimer.expires_after(std::chrono::seconds(1));
+
+ filterTimer.async_wait(
+ [&objectServer, &sensors, &systemBus, &sensorsChanged,
+ &reaperTimer](const boost::system::error_code& ec) mutable {
+ if (ec != boost::system::errc::success)
+ {
+ if (ec != boost::asio::error::operation_aborted)
+ {
+ std::cerr
+ << "callback error: " << ec.message() << "\n";
+ }
+ return;
+ }
+
+ createSensors(objectServer, sensors, systemBus,
+ sensorsChanged, reaperTimer);
+ });
+ };
+
+ std::vector<std::unique_ptr<sdbusplus::bus::match_t>> matches =
+ setupPropertiesChangedMatches(
+ *systemBus, std::to_array<const char*>({sensorType}), eventHandler);
+
+ if constexpr (debug)
+ {
+ std::cerr << "ExternalSensor service entering main loop\n";
+ }
+
+ io.run();
+}
diff --git a/src/external/meson.build b/src/external/meson.build
new file mode 100644
index 0000000..babde51
--- /dev/null
+++ b/src/external/meson.build
@@ -0,0 +1,14 @@
+src_inc = include_directories('..')
+
+executable(
+ 'externalsensor',
+ 'ExternalSensor.cpp',
+ 'ExternalSensorMain.cpp',
+ dependencies: [
+ default_deps,
+ thresholds_dep,
+ utils_dep,
+ ],
+ include_directories: src_inc,
+ install: true,
+)
\ No newline at end of file