| #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; |
| } |