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/fan/FanMain.cpp b/src/fan/FanMain.cpp
new file mode 100644
index 0000000..736d07d
--- /dev/null
+++ b/src/fan/FanMain.cpp
@@ -0,0 +1,679 @@
+/*
+// Copyright (c) 2017 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 "PresenceGpio.hpp"
+#include "PwmSensor.hpp"
+#include "TachSensor.hpp"
+#include "Thresholds.hpp"
+#include "Utils.hpp"
+#include "VariantVisitors.hpp"
+
+#include <boost/algorithm/string/replace.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.hpp>
+#include <sdbusplus/bus/match.hpp>
+#include <sdbusplus/message.hpp>
+
+#include <array>
+#include <chrono>
+#include <cstddef>
+#include <cstdint>
+#include <filesystem>
+#include <fstream>
+#include <functional>
+#include <ios>
+#include <iostream>
+#include <map>
+#include <memory>
+#include <optional>
+#include <regex>
+#include <string>
+#include <system_error>
+#include <utility>
+#include <variant>
+#include <vector>
+
+namespace fs = std::filesystem;
+
+// The following two structures need to be consistent
+static auto sensorTypes{std::to_array<const char*>(
+ {"AspeedFan", "I2CFan", "NuvotonFan", "HPEFan"})};
+
+enum FanTypes
+{
+ aspeed = 0,
+ i2c,
+ nuvoton,
+ hpe,
+ max,
+};
+
+static_assert(std::tuple_size<decltype(sensorTypes)>::value == FanTypes::max,
+ "sensorTypes element number is not equal to FanTypes number");
+
+constexpr const char* redundancyConfiguration =
+ "xyz.openbmc_project.Configuration.FanRedundancy";
+static std::regex inputRegex(R"(fan(\d+)_input)");
+
+// todo: power supply fan redundancy
+std::optional<RedundancySensor> systemRedundancy;
+
+static const std::map<std::string, FanTypes> compatibleFanTypes = {
+ {"aspeed,ast2400-pwm-tacho", FanTypes::aspeed},
+ {"aspeed,ast2500-pwm-tacho", FanTypes::aspeed},
+ {"aspeed,ast2600-pwm-tach", FanTypes::aspeed},
+ {"nuvoton,npcm750-pwm-fan", FanTypes::nuvoton},
+ {"nuvoton,npcm845-pwm-fan", FanTypes::nuvoton},
+ {"hpe,gxp-fan-ctrl", FanTypes::hpe}
+ // add compatible string here for new fan type
+};
+
+FanTypes getFanType(const fs::path& parentPath)
+{
+ fs::path linkPath = parentPath / "of_node";
+ if (!fs::exists(linkPath))
+ {
+ return FanTypes::i2c;
+ }
+
+ std::string canonical = fs::canonical(linkPath);
+ std::string compatiblePath = canonical + "/compatible";
+ std::ifstream compatibleStream(compatiblePath);
+
+ if (!compatibleStream)
+ {
+ std::cerr << "Error opening " << compatiblePath << "\n";
+ return FanTypes::i2c;
+ }
+
+ std::string compatibleString;
+ while (std::getline(compatibleStream, compatibleString))
+ {
+ compatibleString.pop_back(); // trim EOL before comparisons
+
+ std::map<std::string, FanTypes>::const_iterator compatibleIterator =
+ compatibleFanTypes.find(compatibleString);
+
+ if (compatibleIterator != compatibleFanTypes.end())
+ {
+ return compatibleIterator->second;
+ }
+ }
+
+ return FanTypes::i2c;
+}
+void enablePwm(const fs::path& filePath)
+{
+ std::fstream enableFile(filePath, std::ios::in | std::ios::out);
+ if (!enableFile.good())
+ {
+ std::cerr << "Error read/write " << filePath << "\n";
+ return;
+ }
+
+ std::string regulateMode;
+ std::getline(enableFile, regulateMode);
+ if (regulateMode == "0")
+ {
+ enableFile << 1;
+ }
+}
+bool findPwmfanPath(unsigned int configPwmfanIndex, fs::path& pwmPath)
+{
+ /* Search PWM since pwm-fan had separated
+ * PWM from tach directory and 1 channel only*/
+ std::vector<fs::path> pwmfanPaths;
+ std::string pwnfanDevName("pwm-fan");
+
+ pwnfanDevName += std::to_string(configPwmfanIndex);
+
+ if (!findFiles(fs::path("/sys/class/hwmon"), R"(pwm\d+)", pwmfanPaths))
+ {
+ std::cerr << "No PWMs are found!\n";
+ return false;
+ }
+ for (const auto& path : pwmfanPaths)
+ {
+ std::error_code ec;
+ fs::path link = fs::read_symlink(path.parent_path() / "device", ec);
+
+ if (ec)
+ {
+ std::cerr << "read_symlink() failed: " << ec.message() << " ("
+ << ec.value() << ")\n";
+ continue;
+ }
+
+ if (link.filename().string() == pwnfanDevName)
+ {
+ pwmPath = path;
+ return true;
+ }
+ }
+ return false;
+}
+bool findPwmPath(const fs::path& directory, unsigned int pwm, fs::path& pwmPath)
+{
+ std::error_code ec;
+
+ /* Assuming PWM file is appeared in the same directory as fanX_input */
+ auto path = directory / ("pwm" + std::to_string(pwm + 1));
+ bool exists = fs::exists(path, ec);
+
+ if (ec || !exists)
+ {
+ /* PWM file not exist or error happened */
+ if (ec)
+ {
+ std::cerr << "exists() failed: " << ec.message() << " ("
+ << ec.value() << ")\n";
+ }
+ /* try search form pwm-fanX directory */
+ return findPwmfanPath(pwm, pwmPath);
+ }
+
+ pwmPath = path;
+ return true;
+}
+
+// The argument to this function should be the fanN_input file that we want to
+// enable. The function will locate the corresponding fanN_enable file if it
+// exists. Note that some drivers don't provide this file if the sensors are
+// always enabled.
+void enableFanInput(const fs::path& fanInputPath)
+{
+ std::error_code ec;
+ std::string path(fanInputPath.string());
+ boost::replace_last(path, "input", "enable");
+
+ bool exists = fs::exists(path, ec);
+ if (ec || !exists)
+ {
+ return;
+ }
+
+ std::fstream enableFile(path, std::ios::out);
+ if (!enableFile.good())
+ {
+ return;
+ }
+ enableFile << 1;
+}
+
+void createRedundancySensor(
+ const boost::container::flat_map<std::string, std::shared_ptr<TachSensor>>&
+ sensors,
+ const std::shared_ptr<sdbusplus::asio::connection>& conn,
+ sdbusplus::asio::object_server& objectServer)
+{
+ conn->async_method_call(
+ [&objectServer, &sensors](boost::system::error_code& ec,
+ const ManagedObjectType& managedObj) {
+ if (ec)
+ {
+ std::cerr << "Error calling entity manager \n";
+ return;
+ }
+ for (const auto& [path, interfaces] : managedObj)
+ {
+ for (const auto& [intf, cfg] : interfaces)
+ {
+ if (intf == redundancyConfiguration)
+ {
+ // currently only support one
+ auto findCount = cfg.find("AllowedFailures");
+ if (findCount == cfg.end())
+ {
+ std::cerr << "Malformed redundancy record \n";
+ return;
+ }
+ std::vector<std::string> sensorList;
+
+ for (const auto& [name, sensor] : sensors)
+ {
+ sensorList.push_back(
+ "/xyz/openbmc_project/sensors/fan_tach/" +
+ sensor->name);
+ }
+ systemRedundancy.reset();
+ systemRedundancy.emplace(RedundancySensor(
+ std::get<uint64_t>(findCount->second), sensorList,
+ objectServer, path));
+
+ return;
+ }
+ }
+ }
+ },
+ "xyz.openbmc_project.EntityManager", "/xyz/openbmc_project/inventory",
+ "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
+}
+
+void createSensors(
+ boost::asio::io_context& io, sdbusplus::asio::object_server& objectServer,
+ boost::container::flat_map<std::string, std::shared_ptr<TachSensor>>&
+ tachSensors,
+ boost::container::flat_map<std::string, std::unique_ptr<PwmSensor>>&
+ pwmSensors,
+ boost::container::flat_map<std::string, std::weak_ptr<PresenceGpio>>&
+ presenceGpios,
+ std::shared_ptr<sdbusplus::asio::connection>& dbusConnection,
+ const std::shared_ptr<boost::container::flat_set<std::string>>&
+ sensorsChanged,
+ size_t retries = 0)
+{
+ auto getter = std::make_shared<GetSensorConfiguration>(
+ dbusConnection,
+ [&io, &objectServer, &tachSensors, &pwmSensors, &presenceGpios,
+ &dbusConnection,
+ sensorsChanged](const ManagedObjectType& sensorConfigurations) {
+ bool firstScan = sensorsChanged == nullptr;
+ std::vector<fs::path> paths;
+ if (!findFiles(fs::path("/sys/class/hwmon"), R"(fan\d+_input)",
+ paths))
+ {
+ std::cerr << "No fan sensors in system\n";
+ return;
+ }
+
+ // iterate through all found fan sensors, and try to match them with
+ // configuration
+ for (const auto& path : paths)
+ {
+ std::smatch match;
+ std::string pathStr = path.string();
+
+ std::regex_search(pathStr, match, inputRegex);
+ std::string indexStr = *(match.begin() + 1);
+
+ fs::path directory = path.parent_path();
+ FanTypes fanType = getFanType(directory);
+ std::string cfgIntf = configInterfaceName(sensorTypes[fanType]);
+
+ // convert to 0 based
+ size_t index = std::stoul(indexStr) - 1;
+
+ const char* baseType = nullptr;
+ const SensorData* sensorData = nullptr;
+ const std::string* interfacePath = nullptr;
+ const SensorBaseConfiguration* baseConfiguration = nullptr;
+ for (const auto& [path, cfgData] : sensorConfigurations)
+ {
+ // find the base of the configuration to see if indexes
+ // match
+ auto sensorBaseFind = cfgData.find(cfgIntf);
+ if (sensorBaseFind == cfgData.end())
+ {
+ continue;
+ }
+
+ baseConfiguration = &(*sensorBaseFind);
+ interfacePath = &path.str;
+ baseType = sensorTypes[fanType];
+
+ auto findIndex = baseConfiguration->second.find("Index");
+ if (findIndex == baseConfiguration->second.end())
+ {
+ std::cerr
+ << baseConfiguration->first << " missing index\n";
+ continue;
+ }
+ unsigned int configIndex = std::visit(
+ VariantToUnsignedIntVisitor(), findIndex->second);
+ if (configIndex != index)
+ {
+ continue;
+ }
+ if (fanType == FanTypes::aspeed ||
+ fanType == FanTypes::nuvoton ||
+ fanType == FanTypes::hpe)
+ {
+ // there will be only 1 aspeed or nuvoton or hpe sensor
+ // object in sysfs, we found the fan
+ sensorData = &cfgData;
+ break;
+ }
+ if (fanType == FanTypes::i2c)
+ {
+ std::string deviceName =
+ fs::read_symlink(directory / "device").filename();
+
+ size_t bus = 0;
+ size_t addr = 0;
+ if (!getDeviceBusAddr(deviceName, bus, addr))
+ {
+ continue;
+ }
+
+ auto findBus = baseConfiguration->second.find("Bus");
+ auto findAddress =
+ baseConfiguration->second.find("Address");
+ if (findBus == baseConfiguration->second.end() ||
+ findAddress == baseConfiguration->second.end())
+ {
+ std::cerr << baseConfiguration->first
+ << " missing bus or address\n";
+ continue;
+ }
+ unsigned int configBus = std::visit(
+ VariantToUnsignedIntVisitor(), findBus->second);
+ unsigned int configAddress = std::visit(
+ VariantToUnsignedIntVisitor(), findAddress->second);
+
+ if (configBus == bus && configAddress == addr)
+ {
+ sensorData = &cfgData;
+ break;
+ }
+ }
+ }
+ if (sensorData == nullptr)
+ {
+ std::cerr
+ << "failed to find match for " << path.string() << "\n";
+ continue;
+ }
+
+ auto findSensorName = baseConfiguration->second.find("Name");
+
+ if (findSensorName == baseConfiguration->second.end())
+ {
+ std::cerr << "could not determine configuration name for "
+ << path.string() << "\n";
+ continue;
+ }
+ std::string sensorName =
+ std::get<std::string>(findSensorName->second);
+
+ // on rescans, only update sensors we were signaled by
+ auto findSensor = tachSensors.find(sensorName);
+ if (!firstScan && findSensor != tachSensors.end())
+ {
+ bool found = false;
+ for (auto it = sensorsChanged->begin();
+ it != sensorsChanged->end(); it++)
+ {
+ if (it->ends_with(findSensor->second->name))
+ {
+ sensorsChanged->erase(it);
+ findSensor->second = nullptr;
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ {
+ continue;
+ }
+ }
+ std::vector<thresholds::Threshold> sensorThresholds;
+ if (!parseThresholdsFromConfig(*sensorData, sensorThresholds))
+ {
+ std::cerr << "error populating thresholds for "
+ << sensorName << "\n";
+ }
+
+ auto presenceConfig =
+ sensorData->find(cfgIntf + std::string(".Presence"));
+
+ std::shared_ptr<PresenceGpio> presenceGpio(nullptr);
+
+ // presence sensors are optional
+ if (presenceConfig != sensorData->end())
+ {
+ auto findPolarity = presenceConfig->second.find("Polarity");
+ auto findPinName = presenceConfig->second.find("PinName");
+
+ if (findPinName == presenceConfig->second.end() ||
+ findPolarity == presenceConfig->second.end())
+ {
+ std::cerr << "Malformed Presence Configuration\n";
+ }
+ else
+ {
+ bool inverted = std::get<std::string>(
+ findPolarity->second) == "Low";
+ const auto* pinName =
+ std::get_if<std::string>(&findPinName->second);
+
+ if (pinName != nullptr)
+ {
+ auto findPresenceGpio =
+ presenceGpios.find(*pinName);
+ if (findPresenceGpio != presenceGpios.end())
+ {
+ auto p = findPresenceGpio->second.lock();
+ if (p)
+ {
+ presenceGpio = p;
+ }
+ }
+ if (!presenceGpio)
+ {
+ presenceGpio =
+ std::make_shared<EventPresenceGpio>(
+ "Fan", sensorName, *pinName, inverted,
+ io);
+ presenceGpios[*pinName] = presenceGpio;
+ }
+ }
+ else
+ {
+ std::cerr
+ << "Malformed Presence pinName for sensor "
+ << sensorName << " \n";
+ }
+ }
+ }
+ std::optional<RedundancySensor>* redundancy = nullptr;
+ if (fanType == FanTypes::aspeed)
+ {
+ redundancy = &systemRedundancy;
+ }
+
+ PowerState powerState =
+ getPowerState(baseConfiguration->second);
+
+ constexpr double defaultMaxReading = 25000;
+ constexpr double defaultMinReading = 0;
+ std::pair<double, double> limits =
+ std::make_pair(defaultMinReading, defaultMaxReading);
+
+ auto connector =
+ sensorData->find(cfgIntf + std::string(".Connector"));
+
+ std::optional<std::string> led;
+ std::string pwmName;
+ fs::path pwmPath;
+
+ // The Mutable parameter is optional, defaulting to false
+ bool isValueMutable = false;
+ if (connector != sensorData->end())
+ {
+ auto findPwm = connector->second.find("Pwm");
+ if (findPwm != connector->second.end())
+ {
+ size_t pwm = std::visit(VariantToUnsignedIntVisitor(),
+ findPwm->second);
+ if (!findPwmPath(directory, pwm, pwmPath))
+ {
+ std::cerr << "Connector for " << sensorName
+ << " no pwm channel found!\n";
+ continue;
+ }
+
+ fs::path pwmEnableFile =
+ "pwm" + std::to_string(pwm + 1) + "_enable";
+ fs::path enablePath =
+ pwmPath.parent_path() / pwmEnableFile;
+ enablePwm(enablePath);
+
+ /* use pwm name override if found in configuration else
+ * use default */
+ auto findOverride = connector->second.find("PwmName");
+ if (findOverride != connector->second.end())
+ {
+ pwmName = std::visit(VariantToStringVisitor(),
+ findOverride->second);
+ }
+ else
+ {
+ pwmName = "Pwm_" + std::to_string(pwm + 1);
+ }
+
+ // Check PWM sensor mutability
+ auto findMutable = connector->second.find("Mutable");
+ if (findMutable != connector->second.end())
+ {
+ const auto* ptrMutable =
+ std::get_if<bool>(&(findMutable->second));
+ if (ptrMutable != nullptr)
+ {
+ isValueMutable = *ptrMutable;
+ }
+ }
+ }
+ else
+ {
+ std::cerr << "Connector for " << sensorName
+ << " missing pwm!\n";
+ }
+
+ auto findLED = connector->second.find("LED");
+ if (findLED != connector->second.end())
+ {
+ const auto* ledName =
+ std::get_if<std::string>(&(findLED->second));
+ if (ledName == nullptr)
+ {
+ std::cerr << "Wrong format for LED of "
+ << sensorName << "\n";
+ }
+ else
+ {
+ led = *ledName;
+ }
+ }
+ }
+
+ findLimits(limits, baseConfiguration);
+
+ enableFanInput(path);
+
+ auto& tachSensor = tachSensors[sensorName];
+ tachSensor = nullptr;
+ tachSensor = std::make_shared<TachSensor>(
+ path.string(), baseType, objectServer, dbusConnection,
+ presenceGpio, redundancy, io, sensorName,
+ std::move(sensorThresholds), *interfacePath, limits,
+ powerState, led);
+ tachSensor->setupRead();
+
+ if (!pwmPath.empty() && fs::exists(pwmPath) &&
+ (pwmSensors.count(pwmPath) == 0U))
+ {
+ pwmSensors[pwmPath] = std::make_unique<PwmSensor>(
+ pwmName, pwmPath, dbusConnection, objectServer,
+ *interfacePath, "Fan", isValueMutable);
+ }
+ }
+
+ createRedundancySensor(tachSensors, dbusConnection, objectServer);
+ });
+ getter->getConfiguration(
+ std::vector<std::string>{sensorTypes.begin(), sensorTypes.end()},
+ retries);
+}
+
+int main()
+{
+ 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");
+ objectServer.add_manager("/xyz/openbmc_project/control");
+ objectServer.add_manager("/xyz/openbmc_project/inventory");
+ systemBus->request_name("xyz.openbmc_project.FanSensor");
+ boost::container::flat_map<std::string, std::shared_ptr<TachSensor>>
+ tachSensors;
+ boost::container::flat_map<std::string, std::unique_ptr<PwmSensor>>
+ pwmSensors;
+ boost::container::flat_map<std::string, std::weak_ptr<PresenceGpio>>
+ presenceGpios;
+ auto sensorsChanged =
+ std::make_shared<boost::container::flat_set<std::string>>();
+
+ boost::asio::post(io, [&]() {
+ createSensors(io, objectServer, tachSensors, pwmSensors, presenceGpios,
+ systemBus, nullptr);
+ });
+
+ boost::asio::steady_timer filterTimer(io);
+ std::function<void(sdbusplus::message_t&)> eventHandler =
+ [&](sdbusplus::message_t& message) {
+ if (message.is_method_error())
+ {
+ std::cerr << "callback method error\n";
+ return;
+ }
+ sensorsChanged->insert(message.get_path());
+ // this implicitly cancels the timer
+ filterTimer.expires_after(std::chrono::seconds(1));
+
+ filterTimer.async_wait([&](const boost::system::error_code& ec) {
+ if (ec == boost::asio::error::operation_aborted)
+ {
+ /* we were canceled*/
+ return;
+ }
+ if (ec)
+ {
+ std::cerr << "timer error\n";
+ return;
+ }
+ createSensors(io, objectServer, tachSensors, pwmSensors,
+ presenceGpios, systemBus, sensorsChanged, 5);
+ });
+ };
+
+ std::vector<std::unique_ptr<sdbusplus::bus::match_t>> matches =
+ setupPropertiesChangedMatches(*systemBus, sensorTypes, eventHandler);
+
+ // redundancy sensor
+ std::function<void(sdbusplus::message_t&)> redundancyHandler =
+ [&tachSensors, &systemBus, &objectServer](sdbusplus::message_t&) {
+ createRedundancySensor(tachSensors, systemBus, objectServer);
+ };
+ auto match = std::make_unique<sdbusplus::bus::match_t>(
+ static_cast<sdbusplus::bus_t&>(*systemBus),
+ "type='signal',member='PropertiesChanged',path_namespace='" +
+ std::string(inventoryPath) + "',arg0namespace='" +
+ redundancyConfiguration + "'",
+ std::move(redundancyHandler));
+ matches.emplace_back(std::move(match));
+
+ setupManufacturingModeMatch(*systemBus);
+ io.run();
+ return 0;
+}
diff --git a/src/fan/PresenceGpio.cpp b/src/fan/PresenceGpio.cpp
new file mode 100644
index 0000000..6c528d7
--- /dev/null
+++ b/src/fan/PresenceGpio.cpp
@@ -0,0 +1,118 @@
+/*
+// 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 "PresenceGpio.hpp"
+
+#include <boost/asio/io_context.hpp>
+#include <boost/asio/posix/stream_descriptor.hpp>
+#include <gpiod.hpp>
+
+#include <iostream>
+#include <memory>
+#include <stdexcept>
+#include <string>
+#include <system_error>
+
+PresenceGpio::~PresenceGpio()
+{
+ gpioLine.release();
+}
+
+void PresenceGpio::updateAndTracePresence()
+{
+ status = (gpioLine.get_value() != 0);
+ if (status)
+ {
+ logPresent(deviceName);
+ }
+ else
+ {
+ logRemoved(deviceName);
+ }
+}
+
+EventPresenceGpio::EventPresenceGpio(
+ const std::string& iDeviceType, const std::string& iDeviceName,
+ const std::string& gpioName, bool inverted, boost::asio::io_context& io) :
+ PresenceGpio(iDeviceType, iDeviceName), gpioFd(io)
+{
+ gpioLine = gpiod::find_line(gpioName);
+ if (!gpioLine)
+ {
+ std::cerr << "Error requesting gpio: " << gpioName << "\n";
+ return;
+ }
+
+ try
+ {
+ gpioLine.request(
+ {deviceType + "Sensor", gpiod::line_request::EVENT_BOTH_EDGES,
+ inverted ? gpiod::line_request::FLAG_ACTIVE_LOW : 0});
+ updateAndTracePresence();
+
+ int gpioLineFd = gpioLine.event_get_fd();
+ if (gpioLineFd < 0)
+ {
+ std::cerr << "Failed to get " << gpioName << " fd\n";
+ throw std::runtime_error("Failed to get GPIO fd " + gpioName);
+ }
+
+ gpioFd.assign(gpioLineFd);
+ }
+ catch (const std::system_error& e)
+ {
+ std::cerr << "Error reading gpio " << gpioName << ": " << e.what()
+ << "\n";
+ return;
+ }
+
+ monitorPresence();
+}
+
+void EventPresenceGpio::monitorPresence()
+{
+ std::weak_ptr<EventPresenceGpio> weakRef = weak_from_this();
+ gpioFd.async_wait(
+ boost::asio::posix::stream_descriptor::wait_read,
+ [weakRef](const boost::system::error_code& ec) {
+ std::shared_ptr<EventPresenceGpio> self = weakRef.lock();
+ if (!self)
+ {
+ std::cerr << "Failed to get lock for eventPresenceGpio: "
+ << ec.message() << "\n";
+ return;
+ }
+ if (ec)
+ {
+ if (ec != boost::system::errc::bad_file_descriptor)
+ {
+ std::cerr
+ << "Error on event presence device " << self->deviceName
+ << ": " << ec.message() << "\n";
+ }
+ return;
+ }
+ self->read();
+ self->monitorPresence();
+ });
+}
+
+void EventPresenceGpio::read()
+{
+ // Read is invoked when an edge event is detected by monitorPresence
+ gpioLine.event_read();
+ updateAndTracePresence();
+}
diff --git a/src/fan/PresenceGpio.hpp b/src/fan/PresenceGpio.hpp
new file mode 100644
index 0000000..54805cf
--- /dev/null
+++ b/src/fan/PresenceGpio.hpp
@@ -0,0 +1,64 @@
+#pragma once
+
+#include "sensor.hpp"
+
+#include <gpiod.hpp>
+#include <phosphor-logging/lg2.hpp>
+
+class PresenceGpio
+{
+ public:
+ PresenceGpio(const std::string& type, const std::string& name) :
+ deviceType(type), deviceName(name) {};
+ PresenceGpio(const PresenceGpio&) = delete;
+ PresenceGpio& operator=(const PresenceGpio&) = delete;
+ virtual ~PresenceGpio() = 0;
+
+ bool isPresent() const
+ {
+ return status;
+ }
+
+ protected:
+ gpiod::line gpioLine;
+ bool status = false;
+ std::string deviceType;
+ std::string deviceName;
+
+ virtual void monitorPresence() = 0;
+
+ void logPresent(const std::string& device)
+ {
+ std::string summary = deviceType + " " + deviceName + " Inserted";
+ std::string msg = "OpenBMC.0.1." + deviceType + "Inserted";
+ lg2::info(summary.c_str(), "REDFISH_MESSAGE_ID", msg.c_str(),
+ "REDFISH_MESSAGE_ARGS", device);
+ }
+
+ void logRemoved(const std::string& device)
+ {
+ std::string summary = deviceType + " " + deviceName + " Removed";
+ std::string msg = "OpenBMC.0.1." + deviceType + "Removed";
+ lg2::error(summary.c_str(), "REDFISH_MESSAGE_ID", msg.c_str(),
+ "REDFISH_MESSAGE_ARGS", device);
+ }
+
+ void updateAndTracePresence();
+};
+
+class EventPresenceGpio :
+ public PresenceGpio,
+ public std::enable_shared_from_this<EventPresenceGpio>
+{
+ public:
+ EventPresenceGpio(const std::string& iDeviceType,
+ const std::string& iDeviceName,
+ const std::string& gpioName, bool inverted,
+ boost::asio::io_context& io);
+
+ private:
+ boost::asio::posix::stream_descriptor gpioFd;
+
+ void monitorPresence() override;
+ void read();
+};
diff --git a/src/fan/TachSensor.cpp b/src/fan/TachSensor.cpp
new file mode 100644
index 0000000..7908488
--- /dev/null
+++ b/src/fan/TachSensor.cpp
@@ -0,0 +1,274 @@
+/*
+// 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 "TachSensor.hpp"
+
+#include "PresenceGpio.hpp"
+#include "SensorPaths.hpp"
+#include "Thresholds.hpp"
+#include "Utils.hpp"
+#include "sensor.hpp"
+
+#include <boost/asio/buffer.hpp>
+#include <boost/asio/error.hpp>
+#include <boost/asio/io_context.hpp>
+#include <boost/asio/random_access_file.hpp>
+#include <sdbusplus/asio/connection.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+
+#include <charconv>
+#include <chrono>
+#include <cstddef>
+#include <cstdint>
+#include <iostream>
+#include <memory>
+#include <optional>
+#include <string>
+#include <system_error>
+#include <utility>
+#include <vector>
+
+static constexpr unsigned int pwmPollMs = 500;
+
+TachSensor::TachSensor(
+ const std::string& path, const std::string& objectType,
+ sdbusplus::asio::object_server& objectServer,
+ std::shared_ptr<sdbusplus::asio::connection>& conn,
+ std::shared_ptr<PresenceGpio>& presenceGpio,
+ std::optional<RedundancySensor>* redundancy, boost::asio::io_context& io,
+ const std::string& fanName,
+ std::vector<thresholds::Threshold>&& thresholdsIn,
+ const std::string& sensorConfiguration,
+ const std::pair<double, double>& limits, const PowerState& powerState,
+ const std::optional<std::string>& ledIn) :
+ Sensor(escapeName(fanName), std::move(thresholdsIn), sensorConfiguration,
+ objectType, false, false, limits.second, limits.first, conn,
+ powerState),
+ objServer(objectServer), redundancy(redundancy), presence(presenceGpio),
+ inputDev(io, path, boost::asio::random_access_file::read_only),
+ waitTimer(io), path(path), led(ledIn)
+{
+ sensorInterface = objectServer.add_interface(
+ "/xyz/openbmc_project/sensors/fan_tach/" + name,
+ "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(
+ "/xyz/openbmc_project/sensors/fan_tach/" + name, interface);
+ }
+ association = objectServer.add_interface(
+ "/xyz/openbmc_project/sensors/fan_tach/" + name,
+ association::interface);
+
+ if (presence)
+ {
+ itemIface =
+ objectServer.add_interface("/xyz/openbmc_project/inventory/" + name,
+ "xyz.openbmc_project.Inventory.Item");
+ itemIface->register_property("PrettyName",
+ std::string()); // unused property
+ itemIface->register_property("Present", true);
+ itemIface->initialize();
+ itemAssoc = objectServer.add_interface(
+ "/xyz/openbmc_project/inventory/" + name, association::interface);
+ itemAssoc->register_property(
+ "Associations",
+ std::vector<Association>{
+ {"sensors", "inventory",
+ "/xyz/openbmc_project/sensors/fan_tach/" + name}});
+ itemAssoc->initialize();
+ }
+ setInitialProperties(sensor_paths::unitRPMs);
+}
+
+TachSensor::~TachSensor()
+{
+ // close the input dev to cancel async operations
+ inputDev.close();
+ waitTimer.cancel();
+ for (const auto& iface : thresholdInterfaces)
+ {
+ objServer.remove_interface(iface);
+ }
+ objServer.remove_interface(sensorInterface);
+ objServer.remove_interface(association);
+ objServer.remove_interface(itemIface);
+ objServer.remove_interface(itemAssoc);
+}
+
+void TachSensor::setupRead()
+{
+ std::weak_ptr<TachSensor> weakRef = weak_from_this();
+ inputDev.async_read_some_at(
+ 0, boost::asio::buffer(readBuf),
+ [weakRef](const boost::system::error_code& ec, std::size_t bytesRead) {
+ std::shared_ptr<TachSensor> self = weakRef.lock();
+ if (self)
+ {
+ self->handleResponse(ec, bytesRead);
+ }
+ });
+}
+
+void TachSensor::restartRead(size_t pollTime)
+{
+ std::weak_ptr<TachSensor> weakRef = weak_from_this();
+ waitTimer.expires_after(std::chrono::milliseconds(pollTime));
+ waitTimer.async_wait([weakRef](const boost::system::error_code& ec) {
+ if (ec == boost::asio::error::operation_aborted)
+ {
+ return; // we're being canceled
+ }
+ std::shared_ptr<TachSensor> self = weakRef.lock();
+ if (!self)
+ {
+ return;
+ }
+ self->setupRead();
+ });
+}
+
+void TachSensor::handleResponse(const boost::system::error_code& err,
+ size_t bytesRead)
+{
+ if ((err == boost::system::errc::bad_file_descriptor) ||
+ (err == boost::asio::error::misc_errors::not_found))
+ {
+ std::cerr << "TachSensor " << name << " removed " << path << "\n";
+ return; // we're being destroyed
+ }
+ bool missing = false;
+ size_t pollTime = pwmPollMs;
+ if (presence)
+ {
+ if (!presence->isPresent())
+ {
+ markAvailable(false);
+ missing = true;
+ pollTime = sensorFailedPollTimeMs;
+ }
+ itemIface->set_property("Present", !missing);
+ }
+
+ if (!missing)
+ {
+ if (!err)
+ {
+ const char* bufEnd = readBuf.data() + bytesRead;
+ int nvalue = 0;
+ std::from_chars_result ret =
+ std::from_chars(readBuf.data(), bufEnd, nvalue);
+ if (ret.ec != std::errc())
+ {
+ incrementError();
+ pollTime = sensorFailedPollTimeMs;
+ }
+ else
+ {
+ updateValue(nvalue);
+ }
+ }
+ else
+ {
+ incrementError();
+ pollTime = sensorFailedPollTimeMs;
+ }
+ }
+
+ restartRead(pollTime);
+}
+
+void TachSensor::checkThresholds()
+{
+ bool status = thresholds::checkThresholds(this);
+
+ if ((redundancy != nullptr) && *redundancy)
+ {
+ (*redundancy)
+ ->update("/xyz/openbmc_project/sensors/fan_tach/" + name, !status);
+ }
+
+ bool curLed = !status;
+ if (led && ledState != curLed)
+ {
+ ledState = curLed;
+ setLed(dbusConnection, *led, curLed);
+ }
+}
+
+RedundancySensor::RedundancySensor(size_t count,
+ const std::vector<std::string>& children,
+ sdbusplus::asio::object_server& objectServer,
+ const std::string& sensorConfiguration) :
+ count(count),
+ iface(objectServer.add_interface(
+ "/xyz/openbmc_project/control/FanRedundancy/Tach",
+ "xyz.openbmc_project.Control.FanRedundancy")),
+ association(objectServer.add_interface(
+ "/xyz/openbmc_project/control/FanRedundancy/Tach",
+ association::interface)),
+ objectServer(objectServer)
+{
+ createAssociation(association, sensorConfiguration);
+ iface->register_property("Collection", children);
+ iface->register_property("Status", std::string("Full"));
+ iface->register_property("AllowedFailures", static_cast<uint8_t>(count));
+ iface->initialize();
+}
+RedundancySensor::~RedundancySensor()
+{
+ objectServer.remove_interface(association);
+ objectServer.remove_interface(iface);
+}
+void RedundancySensor::update(const std::string& name, bool failed)
+{
+ statuses[name] = failed;
+ size_t failedCount = 0;
+
+ std::string newState = redundancy::full;
+ for (const auto& [name, status] : statuses)
+ {
+ if (status)
+ {
+ failedCount++;
+ }
+ if (failedCount > count)
+ {
+ newState = redundancy::failed;
+ break;
+ }
+ if (failedCount != 0U)
+ {
+ newState = redundancy::degraded;
+ }
+ }
+ if (state != newState)
+ {
+ if (state == redundancy::full)
+ {
+ logFanRedundancyLost();
+ }
+ else if (newState == redundancy::full)
+ {
+ logFanRedundancyRestored();
+ }
+ state = newState;
+ iface->set_property("Status", state);
+ }
+}
diff --git a/src/fan/TachSensor.hpp b/src/fan/TachSensor.hpp
new file mode 100644
index 0000000..d2ca101
--- /dev/null
+++ b/src/fan/TachSensor.hpp
@@ -0,0 +1,95 @@
+#pragma once
+
+#include "PresenceGpio.hpp"
+#include "Thresholds.hpp"
+#include "sensor.hpp"
+
+#include <boost/asio/random_access_file.hpp>
+#include <boost/container/flat_map.hpp>
+#include <boost/container/flat_set.hpp>
+#include <gpiod.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+
+#include <memory>
+#include <optional>
+#include <string>
+#include <utility>
+#include <vector>
+
+namespace redundancy
+{
+constexpr const char* full = "Full";
+constexpr const char* degraded = "Degraded";
+constexpr const char* failed = "Failed";
+} // namespace redundancy
+
+class RedundancySensor
+{
+ public:
+ RedundancySensor(size_t count, const std::vector<std::string>& children,
+ sdbusplus::asio::object_server& objectServer,
+ const std::string& sensorConfiguration);
+ ~RedundancySensor();
+
+ void update(const std::string& name, bool failed);
+
+ private:
+ size_t count;
+ std::string state = redundancy::full;
+ std::shared_ptr<sdbusplus::asio::dbus_interface> iface;
+ std::shared_ptr<sdbusplus::asio::dbus_interface> association;
+ sdbusplus::asio::object_server& objectServer;
+ boost::container::flat_map<std::string, bool> statuses;
+
+ static void logFanRedundancyLost()
+ {
+ const auto* msg = "OpenBMC.0.1.FanRedundancyLost";
+ lg2::error("Fan Inserted", "REDFISH_MESSAGE_ID", msg);
+ }
+
+ static void logFanRedundancyRestored()
+ {
+ const auto* msg = "OpenBMC.0.1.FanRedundancyRegained";
+ lg2::error("Fan Removed", "REDFISH_MESSAGE_ID", msg);
+ }
+};
+
+class TachSensor :
+ public Sensor,
+ public std::enable_shared_from_this<TachSensor>
+{
+ public:
+ TachSensor(const std::string& path, const std::string& objectType,
+ sdbusplus::asio::object_server& objectServer,
+ std::shared_ptr<sdbusplus::asio::connection>& conn,
+ std::shared_ptr<PresenceGpio>& presence,
+ std::optional<RedundancySensor>* redundancy,
+ boost::asio::io_context& io, const std::string& fanName,
+ std::vector<thresholds::Threshold>&& thresholds,
+ const std::string& sensorConfiguration,
+ const std::pair<double, double>& limits,
+ const PowerState& powerState,
+ const std::optional<std::string>& led);
+ ~TachSensor() override;
+ void setupRead();
+
+ private:
+ // Ordering is important here; readBuf is first so that it's not destroyed
+ // while async operations from other member fields might still be using it.
+ std::array<char, 128> readBuf{};
+ sdbusplus::asio::object_server& objServer;
+ std::optional<RedundancySensor>* redundancy;
+ std::shared_ptr<PresenceGpio> presence;
+ std::shared_ptr<sdbusplus::asio::dbus_interface> itemIface;
+ std::shared_ptr<sdbusplus::asio::dbus_interface> itemAssoc;
+ boost::asio::random_access_file inputDev;
+ boost::asio::steady_timer waitTimer;
+ std::string path;
+ std::optional<std::string> led;
+ bool ledState = false;
+
+ void handleResponse(const boost::system::error_code& err, size_t bytesRead);
+ void restartRead(size_t pollTime);
+ void checkThresholds() override;
+};
diff --git a/src/fan/meson.build b/src/fan/meson.build
new file mode 100644
index 0000000..93f8ae6
--- /dev/null
+++ b/src/fan/meson.build
@@ -0,0 +1,17 @@
+src_inc = include_directories('..')
+
+executable(
+ 'fansensor',
+ 'FanMain.cpp',
+ 'PresenceGpio.cpp',
+ 'TachSensor.cpp',
+ '../PwmSensor.cpp',
+ dependencies: [
+ default_deps,
+ gpiodcxx,
+ thresholds_dep,
+ utils_dep,
+ ],
+ include_directories: src_inc,
+ install: true,
+)
\ No newline at end of file