blob: 2464862656eb5d94f2e579f69f34dab4be745434 [file] [edit]
/**
* Copyright 2023 Google Inc.
*
* 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 "RdePwmSensor.hpp"
#include "RdeTachSensor.hpp"
#include "RdeUtils.hpp"
#include "nlohmann/json.hpp"
#include <boost/algorithm/string/replace.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/container/flat_map.hpp>
#include <boost/container/flat_set.hpp>
#include <boost/filesystem.hpp>
#include <boost/url/url.hpp>
#include <sdbusplus/asio/connection.hpp>
#include <sdbusplus/asio/object_server.hpp>
#include <sdbusplus/bus/match.hpp>
#include <array>
#include <cstdio>
#include <filesystem>
#include <fstream>
#include <functional>
#include <map>
#include <memory>
#include <optional>
#include <regex>
#include <string>
#include <utility>
#include <variant>
#include <vector>
/**
* @brief Determine the tray id of the tray associated with a Rde device
*
* @param[in] trayType The type of the tray
* @param[in,out] trayCountMap A map that stores tray count against tray type
* @param[in] rdeDeviceId Rde device id
* @param[in,out] rdeIdToTrayIdMap A map for storing rde device id against tray
* id
*
* @return std::string (Tray id)
*/
static std::string getTrayId(
const std::string& trayType,
boost::container::flat_map<std::string, int>& trayCountMap,
const std::string& rdeDeviceId,
boost::container::flat_map<std::string, std::string>& rdeIdToTrayIdMap)
{
std::string trayId = "";
int count = 0;
if (rdeIdToTrayIdMap.find(rdeDeviceId) == rdeIdToTrayIdMap.end())
{
if (trayCountMap.find(trayType) == trayCountMap.end())
{
trayCountMap.emplace(trayType, count);
}
else
{
count = trayCountMap[trayType];
trayCountMap[trayType] = count + 1;
}
trayId = trayType + std::to_string(count);
// Once a trayId is mapped with rdeDeviceId, the mapping will stay
// forever even if the rde device goes down
rdeIdToTrayIdMap.emplace(rdeDeviceId, trayId);
}
else
{
trayId = rdeIdToTrayIdMap[rdeDeviceId];
}
return trayId;
}
/**
* @brief Determine the Rde pwm sensors on the Rde device
*
* @param[in] dbusConnection A D-bus bus object to send D-bus requests
* @param[in] sensorCollectionRespJson A json object storing the sensors
collection response from Rde device
* @param[in] trayId The tray id
* @param[in] rdeDeviceId Rde device id
* @param[in] rdeDevService Name of the service that handles D-bus requests
for Rde device
* @param[in] rdeDevObjPath Rde device D-bus object path
*
* @return std::map<std::string, std::string> (Map that stores sensor Uri
against unique sensor id)
*/
static std::map<std::string, std::string>
findRdePwmSensors(sdbusplus::bus_t& dbusConnection,
const nlohmann::json& sensorCollectionRespJson,
const std::string& trayId, const std::string& rdeDeviceId,
std::string& rdeDevService,
const std::string& rdeDevObjPath)
{
std::map<std::string, std::string> rdePwmSensors;
int sensorsCount = sensorCollectionRespJson["Members@odata.count"];
std::vector<nlohmann::json> members = sensorCollectionRespJson["Members"];
for (int i = 0; i < sensorsCount; i++)
{
nlohmann::json member = members[i];
std::string sensorUri = member["@odata.id"];
std::string sensorResp = sendRdeDbusRequest(
dbusConnection, sensorUri, rdeDeviceId, rdeDevService,
rdeDevObjPath, PLDM_RDE_OPERATION_READ, std::string(""));
if (isResponseError(sensorResp))
{
std::cerr << "Unable to get sensor response" << std::endl;
continue;
}
nlohmann::json sensorRespJson;
try
{
sensorRespJson = nlohmann::json::parse(sensorResp);
}
catch (const nlohmann::json::parse_error& e)
{
std::cerr << "JSON parsing error: " << e.what()
<< " while parsing sensor response " << sensorResp
<< std::endl;
continue;
}
catch (const std::exception& e)
{
std::cerr << "Error: " << e.what()
<< " while parsing sensor response " << sensorResp
<< std::endl;
continue;
}
std::string sensorId = sensorRespJson["Id"];
if (regex_match(sensorId, std::regex(R"(host_fan(\d+)_pwm)")))
{
rdePwmSensors[sensorId + "_" + trayId] = sensorUri;
std::cerr << "Found regex match" << std::endl;
std::cerr << "Found : " << sensorId + "_" + trayId << std::endl;
}
}
return rdePwmSensors;
}
/**
* @brief Create fan pwm and tach sensors for each pwm sensor present on the Rde
* devices
*
* @param[in,out] trayCountMap A map that stores tray count against tray type
* @param[in,out] rdeIdToTrayIdMap A map for storing rde device id against tray
* id
* @param[in] io An asio io context
* @param[in] objectServer An asio object server
* @param[in,out] pwmSensors A map that stores pwm sensor objects against pwm
* sensor names
* @param[in,out] tachSensors A map that stores tach sensor objects against tach
* sensor names
* @param[in] asioConnection An asio connection
* @param[in] dbusConnection A D-bus object to send D-bus requests
* @param[in,out] rdeDevicesChanged A shared pointer to a set that stores the
* Rde object paths for the Rde devices that were newly added or removed
* @param[in] retries The retry count for running D-bus query to get the Rde
* device
*
* @return void
*/
void createSensors(
boost::container::flat_map<std::string, int>& trayCountMap,
boost::container::flat_map<std::string, std::string>& rdeIdToTrayIdMap,
boost::asio::io_context& io, sdbusplus::asio::object_server& objectServer,
boost::container::flat_map<std::string, std::unique_ptr<PwmSensor>>&
pwmSensors,
boost::container::flat_map<std::string, std::unique_ptr<TachSensor>>&
tachSensors,
std::shared_ptr<sdbusplus::asio::connection>& asioConnection,
sdbusplus::bus_t& dbusConnection,
const std::shared_ptr<boost::container::flat_set<std::string>>&
rdeDevicesChanged,
size_t retries = 0)
{
auto getter = std::make_shared<GetRdeDevices>(
asioConnection,
[&trayCountMap, &rdeIdToTrayIdMap, &io, &objectServer, &pwmSensors,
&tachSensors, &asioConnection, &dbusConnection,
rdeDevicesChanged](const std::map<std::string, std::string>
rdeDevObjPathsToServiceMap) {
bool firstScan = (rdeDevicesChanged == nullptr);
for (auto it1 = rdeDevObjPathsToServiceMap.begin();
it1 != rdeDevObjPathsToServiceMap.end(); ++it1)
{
std::string rdeDevObjPath = it1->first;
std::string rdeDevService = it1->second;
sdbusplus::message::object_path path(rdeDevObjPath);
std::string rdeDeviceId = path.filename();
// On re-scans only update sensors for newly discovered Rde
// devices
if (!firstScan && (rdeDevicesChanged->find(rdeDevObjPath) ==
rdeDevicesChanged->end()))
{
continue;
}
std::cerr << "rdeDeviceId : " << rdeDeviceId
<< " was changed/updated" << std::endl;
std::string trayResp =
sendRdeDbusRequest(dbusConnection, "/redfish/v1/Chassis/Tray",
rdeDeviceId, rdeDevService, rdeDevObjPath,
PLDM_RDE_OPERATION_READ, std::string(""));
if (isResponseError(trayResp))
{
std::cerr << "Unable to get tray collection for " << rdeDeviceId
<< "." << std::endl;
std::cerr << "Rde device is probably busy with rded "
<< "daemon discovery requests. Retrying."
<< std::endl;
boost::asio::post(io, [&]() {
createSensors(trayCountMap, rdeIdToTrayIdMap, io,
objectServer, pwmSensors, tachSensors,
asioConnection, dbusConnection,
rdeDevicesChanged);
});
return;
}
nlohmann::json trayRespJson;
try
{
trayRespJson = nlohmann::json::parse(trayResp);
}
catch (const nlohmann::json::parse_error& e)
{
std::cerr << "JSON parsing error: " << e.what()
<< " while parsing tray response " << trayResp
<< std::endl;
continue;
}
catch (const std::exception& e)
{
std::cerr << "Error: " << e.what()
<< " while parsing tray response " << trayResp
<< std::endl;
continue;
}
std::string trayId = getTrayId(trayRespJson["Model"], trayCountMap,
rdeDeviceId, rdeIdToTrayIdMap);
// Get Sensor Collection
std::string sensorCollectionResp = sendRdeDbusRequest(
dbusConnection, "/redfish/v1/Chassis/Tray/Sensors", rdeDeviceId,
rdeDevService, rdeDevObjPath, PLDM_RDE_OPERATION_READ,
std::string(""));
if (isResponseError(sensorCollectionResp))
{
std::cerr << "Unable to get sensor collection for "
<< rdeDeviceId << "." << std::endl;
std::cerr << "Rde device is probably busy with rde daemon "
<< "discovery requests. Retrying." << std::endl;
boost::asio::post(io, [&]() {
createSensors(trayCountMap, rdeIdToTrayIdMap, io,
objectServer, pwmSensors, tachSensors,
asioConnection, dbusConnection,
rdeDevicesChanged);
});
return;
}
nlohmann::json sensorCollectionRespJson;
try
{
sensorCollectionRespJson =
nlohmann::json::parse(sensorCollectionResp);
}
catch (const nlohmann::json::parse_error& e)
{
std::cerr << "JSON parsing error: " << e.what()
<< " while parsing sensor collection response "
<< sensorCollectionResp << std::endl;
continue;
}
catch (const std::exception& e)
{
std::cerr << "Error: " << e.what()
<< " while parsing sensor collection response "
<< sensorCollectionResp << std::endl;
continue;
}
std::map<std::string, std::string> rdePwmSensors =
findRdePwmSensors(dbusConnection, sensorCollectionRespJson,
trayId, rdeDeviceId, rdeDevService,
rdeDevObjPath);
for (auto it2 = rdePwmSensors.begin(); it2 != rdePwmSensors.end();
++it2)
{
std::string fanPwmName = it2->first;
if ((pwmSensors.count(fanPwmName) == 0))
{
std::cerr << "Rde sensor found in config : " << fanPwmName
<< std::endl;
pwmSensors[fanPwmName] = std::make_unique<PwmSensor>(
fanPwmName, rdePwmSensors[fanPwmName], rdeDevObjPath,
rdeDevService, rdeDeviceId, io, asioConnection,
dbusConnection, objectServer, true);
// Create dummy tach sensors with default 0 RPM (one for
// each pwm sensor) to make phosphor-pid-control to skip
// fan loop (b/278600144)
std::string fanTachName = fanPwmName;
int position = fanTachName.find("pwm");
fanTachName.replace(position, 3, "tach");
tachSensors[fanTachName] = std::make_unique<TachSensor>(
fanTachName, rdeDevObjPath, asioConnection,
objectServer, true);
}
}
}
// Clear rdeDevicesChanged container since all the changed rde
// devices have been processed
if (!firstScan)
{
rdeDevicesChanged->clear();
}
});
getter->getRdeDevices(retries);
}
int main()
{
boost::asio::io_context io;
auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
sdbusplus::asio::object_server objectServer(systemBus, true);
sdbusplus::bus_t& dbusConnection =
static_cast<sdbusplus::bus_t&>(*systemBus);
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.RdeFanSensor");
boost::container::flat_map<std::string, int> trayCountMap;
// Once a trayId is mapped with rdeDeviceId, the mapping will stay forever
// even if the rde device goes down. This is because rdeDeviceId for a given
// RDE device will stay the same at runtime.
boost::container::flat_map<std::string, std::string> rdeIdToTrayIdMap;
boost::container::flat_map<std::string, std::unique_ptr<PwmSensor>>
pwmSensors;
boost::container::flat_map<std::string, std::unique_ptr<TachSensor>>
tachSensors;
auto rdeDevicesChanged =
std::make_shared<boost::container::flat_set<std::string>>();
boost::asio::post(io, [&]() {
createSensors(trayCountMap, rdeIdToTrayIdMap, io, objectServer,
pwmSensors, tachSensors, systemBus, dbusConnection,
nullptr);
});
boost::asio::steady_timer filterTimer(io);
std::function<void(sdbusplus::message_t&)> rdeDeviceDetectionHandler =
[&](sdbusplus::message_t& message) {
if (message.is_method_error())
{
std::cerr << "callback method error\n";
return;
}
rdeDevicesChanged->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(trayCountMap, rdeIdToTrayIdMap, io, objectServer,
pwmSensors, tachSensors, systemBus, dbusConnection,
rdeDevicesChanged, 5);
});
};
std::function<void(sdbusplus::message_t&)> rdeDeviceRemovalHandler =
[&](sdbusplus::message_t& message) {
if (message.is_method_error())
{
std::cerr << "callback method error\n";
return;
}
sdbusplus::message::object_path changedObject;
std::vector<std::string> interfacesRemoved;
message.read(changedObject, interfacesRemoved);
std::string removedObjectPath = std::string(changedObject);
auto intfIt = std::find(interfacesRemoved.begin(),
interfacesRemoved.end(), rdeDeviceInterface);
if (intfIt != interfacesRemoved.end())
{
// When RDE device is removed, delete the corresponding pwm and
// tach sensors created for the device
auto it1 = pwmSensors.begin();
while (it1 != pwmSensors.end())
{
std::string& pwmObjPath = it1->second->getRdeDeviceObjPath();
if (pwmObjPath == removedObjectPath)
{
std::cerr << "Removed " << pwmObjPath << std::endl;
it1 = pwmSensors.erase(it1);
break;
}
it1++;
}
auto it2 = tachSensors.begin();
while (it2 != tachSensors.end())
{
std::string& tachObjPath = it2->second->getRdeDeviceObjPath();
if (tachObjPath == removedObjectPath)
{
std::cerr << "Removed " << tachObjPath << std::endl;
it2 = tachSensors.erase(it2);
break;
}
it2++;
}
}
};
std::vector<std::unique_ptr<sdbusplus::bus::match_t>>
rdeDeviceDetectionMatches = setupRdeDeviceDetectionMatches(
dbusConnection, rdeDeviceDetectionHandler);
std::unique_ptr<sdbusplus::bus::match_t> rdeDeviceRemovalMatche =
setupRdeDeviceRemovalMatches(dbusConnection, rdeDeviceRemovalHandler);
io.run();
return 0;
}