blob: d6a04c1d22ec0e1567316d9e46e744c015fedd04 [file] [log] [blame]
#include <stdplus/print.hpp>
#include <util/common.hpp>
#include <array>
#include <cstdlib>
#include <format>
#include <memory>
#include <mutex>
#include <optional>
#include <sstream>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
std::mutex mMutex;
std::unordered_map<std::string, std::string> udevIdToPortMap;
/**
* @brief Execute a CLI command and return the command line result
*
* @param[in] cmd - Command to be executed
*
* @return Output of console
*/
std::string exec(const char* cmd)
{
std::array<char, 128> buffer;
std::string result;
std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(cmd, "r"), pclose);
if (!pipe)
{
throw std::runtime_error("popen() failed!");
}
while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr)
{
result += buffer.data();
}
return result;
}
/**
* @brief Gets the already set MCTP links on the system
*
* @param[in] mctpLinkString - Console output for mctp link command
*
* @return list of mctp serial ports
*/
std::optional<std::unordered_set<std::string>>
getActiveMctpPorts(std::string mctpLinkString)
{
size_t pos = 0;
std::string delimiter = "\n";
std::unordered_set<std::string> mctpPorts;
while ((pos = mctpLinkString.find(delimiter)) != std::string::npos)
{
std::string token = mctpLinkString.substr(0, pos);
size_t p = token.find("mctpserial");
if (p != std::string::npos)
{
// Take the first 11 characters from the output string
// That will be of format `mctpserial0` - 11 chars
mctpPorts.emplace(token.substr(p, 11));
}
mctpLinkString = mctpLinkString.substr(pos + delimiter.length());
}
if (!mctpLinkString.empty())
{
size_t p = mctpLinkString.find("mctpserial");
if (p != std::string::npos)
{
// Works only for 9 devices
mctpPorts.emplace(mctpLinkString.substr(p, 11));
}
}
if (mctpPorts.empty())
{
return std::nullopt;
}
return mctpPorts;
}
/**
* @brief Extract the name from the command line result
*
* "dev mctpserial0 index 13 address 0x(no-addr) net 7 mtu 68 up"
*
* To up link mctpserial0 for MCTP we need to extract the added "mctpserial0"
*
* @param[in] preSetupOutput - mctp link output pre setup of new port
* @param[in] postSetupOutput - mctp link output post setup of new port
*
* @return returns the newly added mctp port name
*/
std::optional<std::string> extractNewName(const std::string& preSetupOutput,
const std::string& postSetupOutput)
{
std::optional<std::unordered_set<std::string>> mctpPortsPreOpt =
getActiveMctpPorts(preSetupOutput);
std::unordered_set<std::string> preSetupPorts;
if (mctpPortsPreOpt.has_value())
{
preSetupPorts = mctpPortsPreOpt.value();
}
std::optional<std::unordered_set<std::string>> mctpPortsPostOpt =
getActiveMctpPorts(postSetupOutput);
if (mctpPortsPostOpt.has_value())
{
std::unordered_set<std::string> postSetupPorts =
mctpPortsPostOpt.value();
for (auto port : postSetupPorts)
{
auto it = preSetupPorts.find(port);
if (it == preSetupPorts.end())
{
return port;
}
}
}
stdplus::print(stderr, "Link command failed\n");
return std::nullopt;
}
/**
* @brief Creating a unique network id from the udevId for a particular RDE
* device
*
* Currently this takes the udev id and adds the digits in it to create a net id
* For instance, for udev_id "1_1_4_1" the network id would be "7" (Sum of all
* digits)
*
* @param[in] udevId - Udev id of the device
*
* @return network id
*/
int createNetIdFromUdevId(std::string udevId)
{
// Creates a net id for a udev id by adding all the digits in the path
// 1_1_4_1 = 7
std::string delimiter = "_";
int netId = 0;
size_t pos = 0;
std::string token;
while ((pos = udevId.find(delimiter)) != std::string::npos)
{
token = udevId.substr(0, pos);
netId = netId + stoi(token);
udevId.erase(0, pos + delimiter.length());
}
if (!udevId.empty())
return netId + stoi(udevId);
return netId;
}
/**
* @brief kill all the mctp serial links produced after executing the cmd in
* in param
*
* @param[in] - grepLinkCmd: Command for getting mctp serial links
*/
void killMctpSerialLinks(const std::string& grepLinkCmd)
{
std::string output = exec(grepLinkCmd.c_str());
std::vector<int> pids;
std::istringstream iss(output);
if (DEBUG)
{
stdplus::print(stderr, "Output for {}: {}\n", grepLinkCmd, output);
}
std::string line;
while (std::getline(iss, line))
{
// Create a stringstream from the input line
std::istringstream lineStream(line);
int pid;
if ((lineStream >> pid) && (line.find("grep") == std::string::npos) &&
(line.find("ps") == std::string::npos))
{
pids.emplace_back(pid);
}
}
for (const int& pid : pids)
{
std::string killCmd = std::format("kill -9 {}", pid);
stdplus::print(stderr, "Executing: {}\n", killCmd);
exec(killCmd.c_str());
}
if (!pids.empty())
{
// Sleep since unlinking might take a bit of time
sleep(2);
}
}
/**
* @brief Clean up existing mctp links for a particular device
*
* @param[in] - udevId: device id
*/
void cleanupMctpLink(const std::string& udevId)
{
stdplus::print(stderr,
"Look for exisiting MCTP serial link for udevId: {}...\n",
udevId);
// check for the particular device if any mctp link present
auto it = udevIdToPortMap.find(udevId);
if (it != udevIdToPortMap.end())
{
std::string port = it->second;
if (DEBUG)
{
stdplus::print(stderr,
"Looking at previous dev path: {} for udevId: {}\n",
port, udevId);
}
std::string grepSerialLinks =
std::format("ps | grep \"mctp link serial {}\"", port);
killMctpSerialLinks(grepSerialLinks);
}
else if (DEBUG)
{
stdplus::print(stderr, "No MCTP serial path found for udevId: {}\n",
udevId);
}
}
/**
* @brief Sets up MCTP on on "mctpserial" port
*
* **Currently this has some random sleeps attached as it takes some time to
* bring MCTP up. This function is only executed when a device reboots or the
* system boots**
*
* @param[in] port - the port at which MCTP is to be setup, format- "/dev/tty*"
* @param[in] udevId - udev id of the RDE device, which is a unique identifier
* and remains same for the device even after reboots
*
* @return Returns 0 if success else -1
*/
int setupOnePort(const std::string& port, const std::string& udevId)
{
// TODO(b/284167548): Find an efficient way to set up MCTP without using CLI
// commands This might help us get rid of sleeps
// Wait for some time until the previous MCTP is set up if any
sleep(2);
// Mutex lock so that another device setup doesn't hinder the MCTP setup
mMutex.lock();
bool setupComplete = false;
std::string mctpLinkShow = "mctp link show";
// lock mutex
int networkId;
for (int retryCounter = 0; retryCounter < MAX_RETRIES_MCTP_SOCK_FAILURE;
retryCounter++)
{
stdplus::print(
stderr,
"Triggering MCTP setup on port: {} with retry counter: {}\n", port,
retryCounter);
// Remove the existing mctp links if running for the first time
if (udevIdToPortMap.empty())
{
std::string grepAllMctpLinks = "ps | grep \"mctp link serial\"";
stdplus::print(
stderr,
"Finding any existing MCTP serial links on daemon bootup...\n");
killMctpSerialLinks(grepAllMctpLinks);
}
// Executing link command pre setup to see current active ports
std::string linkResultPreSetup = exec(mctpLinkShow.c_str());
if (DEBUG)
{
stdplus::print(stderr, "Executing linkResult pre setup: {}\n",
linkResultPreSetup);
}
std::string linkCmd = std::format("mctp link serial {} &", port);
if (DEBUG)
{
stdplus::print(stderr, "Executing: {}\n", linkCmd);
}
int sysRc = system(linkCmd.c_str());
if (sysRc)
{
stdplus::print(
stderr, "Error in setting up mctp link with return code: {}\n",
std::to_string(sysRc));
continue;
}
// Update the udevId map with new dev path link
udevIdToPortMap.erase(udevId);
udevIdToPortMap.emplace(udevId, port);
// MCTP link command takes some time to finish - hence a sleep of 5s
sleep(5);
// Extract name:
std::string mctpName;
std::string linkResult = exec(mctpLinkShow.c_str());
if (DEBUG)
{
stdplus::print(stderr, "Executing linkResult: {}\n", linkResult);
}
std::optional<std::string> mctpNameOpt =
extractNewName(linkResultPreSetup, linkResult);
if (mctpNameOpt.has_value())
{
mctpName = mctpNameOpt.value();
}
else
{
stdplus::print(
stderr,
"No mctp serial connection found. MCTP Link failed...\n");
continue;
}
networkId = createNetIdFromUdevId(udevId);
std::string upCmd = std::format("mctp link set {} network {} up",
mctpName, std::to_string(networkId));
if (DEBUG)
{
stdplus::print(stderr, "Executing upcmd: {}\n", upCmd);
}
std::string networkSetResponse = exec(upCmd.c_str());
if (networkSetResponse.find("invalid") != std::string::npos)
{
stdplus::print(stderr, "Network setup failure... retrying\n");
continue;
}
// Setting network id for a port takes some time to setup
sleep(5);
// Setting up local addresses
std::string localAddrCmd =
std::format("mctp addr add 8 dev {}", mctpName);
std::string routeCmd = std::format("mctp route add 9 via {}", mctpName);
if (DEBUG)
{
stdplus::print(stderr, "Executing local addr: {}\n", localAddrCmd);
}
exec(localAddrCmd.c_str());
if (DEBUG)
{
stdplus::print(stderr, "Executing routeCmd: {}\n", routeCmd);
}
exec(routeCmd.c_str());
// Setting up routes and local address takes time
sleep(5);
setupComplete = true;
stdplus::print(stderr, "Setup completed for MCTP port: {}\n", port);
break;
}
// unlock mutex
mMutex.unlock();
if (setupComplete)
{
return networkId;
}
return -1;
}