blob: 736d07d87f3ce7fd9ee9c3bc3ff20efe9d798295 [file] [log] [blame]
/*
// 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;
}