pldm: Add RDE reactor to perform PLDM-RDE discovery

The Entity Manager service tree contains an object path for each RDE
device it detects. RDE Reactor code looks for those object paths and
creates new object paths under 'rdeoperation' service and provides the
properties and methods required to perform RDE operations

Google-Bug-Id: 280967985
Change-Id: Ic7f9e7783c1f78558c21f27b47ecdda406100034
Signed-off-by: Harsh Tyagi <harshtya@google.com>
diff --git a/recipes-phosphor/pldm/pldm/0003-Add-RDE-Reactor-code-with-MCTP-setup.patch b/recipes-phosphor/pldm/pldm/0003-Add-RDE-Reactor-code-with-MCTP-setup.patch
new file mode 100644
index 0000000..639b8b3
--- /dev/null
+++ b/recipes-phosphor/pldm/pldm/0003-Add-RDE-Reactor-code-with-MCTP-setup.patch
@@ -0,0 +1,435 @@
+From 7d752ab98fb0b1ed52e3789992218115274e3241 Mon Sep 17 00:00:00 2001
+From: Harsh Tyagi <harshtya@google.com>
+Date: Fri, 5 May 2023 00:58:56 -0700
+Subject: [PATCH] Add RDE Reactor code with MCTP setup
+
+Patch Tracking Bug: b/280967985
+Upstream-Status: Pending
+Upstream Info / review: https://gerrit.openbmc.org/c/openbmc/pldm/+/64459
+Justification: (Design under review)
+https://gerrit.openbmc.org/c/openbmc/docs/+/61256
+---
+ rded/helper/mctpsetup.hpp | 185 ++++++++++++++++++++++++++++++++++++++
+ rded/rded.cpp             | 183 ++++++++++++++++++++++++++++++++++---
+ 2 files changed, 355 insertions(+), 13 deletions(-)
+ create mode 100644 rded/helper/mctpsetup.hpp
+
+diff --git a/rded/helper/mctpsetup.hpp b/rded/helper/mctpsetup.hpp
+new file mode 100644
+index 0000000..454baac
+--- /dev/null
++++ b/rded/helper/mctpsetup.hpp
+@@ -0,0 +1,185 @@
++#include <array>
++#include <cstdlib>
++#include <iostream>
++#include <map>
++#include <memory>
++#include <mutex>
++#include <string>
++#include <vector>
++
++std::mutex m_mutex;
++/**
++ * @brief Execute a CLI command and return the command line result
++ */
++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 Extract the name from the command line result
++ * The result returns a string of the format
++ * "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] mctpLinkString - command line string returned after executing
++ * "mctp link show"
++ * @param[out] name - Name on which MCTP is to be set. e.g. "mctpserial0"
++ * @param[out] count - this is the index that is appended to the name, i.e "0"
++ * in "mctpserial0"
++ *
++ */
++int extractNewName(std::string mctpLinkString, std::string* name,
++                     std::string* count)
++{
++    size_t pos = 0;
++    std::string token;
++    std::string delimiter = "\n";
++    std::vector<std::string> mctpPorts;
++    while ((pos = mctpLinkString.find(delimiter)) != std::string::npos)
++    {
++        token = mctpLinkString.substr(0, pos);
++        mctpPorts.push_back(token);
++        mctpLinkString.erase(0, pos + delimiter.length());
++    }
++    if (!mctpLinkString.empty())
++    {
++        mctpPorts.push_back(mctpLinkString);
++    }
++    std::string lastEntry = mctpPorts.back();
++    if ((pos = lastEntry.find("mctpserial")) != std::string::npos)
++    {
++        *name = lastEntry.substr(pos, 11);
++        *count = lastEntry.substr(pos + 10, 1);
++        return 0;
++    }
++    return -1;
++}
++/**
++ * @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
++ */
++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())
++        netId = netId + stoi(udevid);
++    return netId;
++}
++/**
++ * @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
++ */
++int setupOnePort(std::string port, 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
++    m_mutex.lock();
++    int retryCounter = 0;
++    bool setupComplete = false;
++    // lock mutex
++    int networkId;
++    while (retryCounter < 4)
++    {
++        retryCounter++;
++        std::cerr << "Triggering MCTP setup on port: " << port
++                  << " with retry counter: " << retryCounter << "\n";
++        // Executing link command
++        std::string linkCmd = "mctp link serial " + port + " &";
++        std::cerr << "Executing: " << linkCmd << "\n";
++        int sysRc = system(linkCmd.c_str());
++        if (sysRc)
++        {
++            std::cerr << "Error in setting up mctp link with return code: "
++                      << std::to_string(sysRc) << "\n";
++            return -1;
++        }
++        // MCTP link command takes some time to finish - hence a sleep of 5s
++        sleep(5);
++        // Extract name:
++        std::string mctpName;
++        std::string count;
++        std::string mctpLinkShow = "mctp link show";
++        std::string linkResult = exec(mctpLinkShow.c_str());
++        std::cerr << "linkResult: " << linkResult << "\n";
++        sysRc = extractNewName(linkResult, &mctpName, &count);
++        if (sysRc)
++        {
++            std::cerr
++                << "No mctp serial connection found. MCTP Link failed...\n";
++            continue;
++        }
++
++        networkId = createNetIdFromUdevId(udevid);
++
++        std::string upCmd = "mctp link set " + mctpName + " network " +
++                             std::to_string(networkId) + " up";
++        std::cerr << "Executing upcmd: " << upCmd << "\n";
++        std::string networkSetResponse = exec(upCmd.c_str());
++        if (networkSetResponse.find("invalid") != std::string::npos)
++        {
++            std::cerr << "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 = "mctp addr add 8 dev " + mctpName;
++        std::string routeCmd = "mctp route add 9 via " + mctpName;
++        std::cerr << "Executing local addr: " << localAddrCmd << "\n";
++        exec(localAddrCmd.c_str());
++        std::cerr << "Executing routeCmd: " << routeCmd << "\n";
++        exec(routeCmd.c_str());
++        // Setting up routes and local address takes time
++        sleep(5);
++        setupComplete = true;
++        break;
++    }
++    // unlock mutex
++    m_mutex.unlock();
++    if (setupComplete)
++    {
++        return networkId;
++    }
++    return -1;
++}
+diff --git a/rded/rded.cpp b/rded/rded.cpp
+index 3f06924..7462d87 100644
+--- a/rded/rded.cpp
++++ b/rded/rded.cpp
+@@ -1,22 +1,173 @@
+-#include <locale.h>
+-#include <unistd.h>
+-#include <cstddef>
++#include "common/utils.hpp"
+ 
++#include <locale.h>
+ #include <signal.h>
+ #include <string.h>
+ #include <sys/socket.h>
++#include <unistd.h>
++
++#include <boost/asio.hpp>
++#include <boost/asio/io_context.hpp>
++#include <helper/mctpsetup.hpp>
++#include <sdbusplus/asio/connection.hpp>
++#include <sdbusplus/asio/object_server.hpp>
++#include <sdbusplus/asio/property.hpp>
++#include <sdbusplus/bus.hpp>
++#include <sdbusplus/server.hpp>
++#include <sdbusplus/unpack_properties.hpp>
++#include <sdeventplus/event.hpp>
++
++#include <cstddef>
+ #include <iostream>
++#include <map>
+ #include <vector>
+ 
+-#include <sdeventplus/event.hpp>
+-#include "common/utils.hpp"
+ using namespace sdeventplus;
+ using namespace pldm::utils;
+ 
+-// MCTP Socket for RDE communication
+-int fd;
++using DbusVariant = std::variant<std::string, uint64_t, uint32_t, uint16_t,
++                                 uint8_t, sdbusplus::message::object_path>;
++using ChangedPropertiesType = std::vector<std::pair<std::string, DbusVariant>>;
++
++constexpr bool DEBUG_ENABLED = false;
++
++int fd; // MCTP Socket for RDE communication
++
++std::map<std::string, int> deviceNetIdMap;
++std::map<std::string, std::shared_ptr<sdbusplus::asio::dbus_interface>>
++    dbusIntfMap;
+ 
+-void set_socket_timeout(int fd, int seconds, int milliseconds)
++int triggerRdeReactor(int fd)
++{
++    /**
++     * RDE Reactor is responsible to react to a new RDE device whenever Entity
++     * Manager adds a new object path in its service tree that has the interface
++     * "xyz.openbmc_project.Configuration.RdeSatelliteController"
++     *
++     * The detector code for detecting a RDE Device resides in Entity Manager
++     */
++    boost::asio::io_context io;
++    std::shared_ptr<sdbusplus::asio::connection> systemBus =
++        std::make_shared<sdbusplus::asio::connection>(io);
++    sdbusplus::asio::object_server objectServer(systemBus, true);
++    objectServer.add_manager("/xyz/openbmc_project/rde_devices");
++    std::string prefixPath = "/xyz/openbmc_project/rde_devices/";
++
++    std::cerr << "Beginning RDE reactor...\n";
++
++    // TODO(@kkachana,@harshtya): Add the enumeration code for already existing
++    // RDE devices in the Entity Manager service tree
++
++    std::make_unique<sdbusplus::bus::match_t>(
++        *systemBus, sdbusplus::bus::match::rules::interfacesAdded(),
++        [&objectServer, &prefixPath, &systemBus,
++         &fd](sdbusplus::message_t& reply) {
++            sdbusplus::message::object_path changedObject;
++            reply.read(changedObject);
++            std::vector<std::pair<std::string, ChangedPropertiesType>>
++                changedInterfaces;
++            reply.read(changedInterfaces);
++
++            std::string vendorId;
++            std::string udevid;
++            std::string port;
++            for (const auto& [changedInterface, changedProps] :
++                 changedInterfaces)
++            {
++                if (changedInterface !=
++                    "xyz.openbmc_project.Configuration.RdeSatelliteController")
++                {
++                    continue;
++                }
++
++                if (DEBUG_ENABLED)
++                {
++                    std::cerr << "DEBUG: Changed object path: "
++                              << std::string(changedObject) << "\n";
++                }
++
++                for (auto& [key, value] : changedProps)
++                {
++                    if (key == "VID")
++                    {
++                        vendorId = std::get<std::string>(value);
++                    }
++                    else if (key == "USBPORT")
++                    {
++                        port = std::get<std::string>(value);
++                    }
++                    else if (key == "UDEVID")
++                    {
++                        udevid = std::get<std::string>(value);
++                    }
++                }
++                if (vendorId.empty() || port.empty() || udevid.empty())
++                {
++                    std::cerr << "Matcher does not have required properties\n";
++                    return;
++                }
++
++                int netId = setupOnePort(port, udevid); // MCTP Setup
++                deviceNetIdMap.insert({udevid, netId});
++
++                // TODO(@harshtya): Initiate PLDM and discovery
++
++                std::string objectPath = prefixPath + udevid;
++                if (DEBUG_ENABLED)
++                {
++                    std::cerr << "New object path created: " << objectPath
++                              << "\n";
++                }
++
++                std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
++                    objectServer.add_interface(objectPath,
++                                               "xyz.openbmc_project.RdeDevice");
++                iface->register_property(
++                    "VID", vendorId,
++                    sdbusplus::asio::PropertyPermission::readOnly);
++                iface->register_property(
++                    "USBPORT", port,
++                    sdbusplus::asio::PropertyPermission::readOnly);
++                iface->register_property(
++                    "UDEVID", udevid,
++                    sdbusplus::asio::PropertyPermission::readOnly);
++
++                // TODO(@harshtya): Add method to handle RDE operation requests
++
++                iface->initialize();
++                dbusIntfMap.insert({std::string(changedObject), iface});
++            }
++        });
++
++    // Remove object path if the device is removed
++    std::make_unique<sdbusplus::bus::match_t>(
++        *systemBus, sdbusplus::bus::match::rules::interfacesRemoved(),
++        [&objectServer](sdbusplus::message_t& reply) {
++            sdbusplus::message::object_path changedObject;
++            std::vector<std::string> interfacesRemoved;
++            reply.read(changedObject, interfacesRemoved);
++
++            auto it = dbusIntfMap.find(std::string(changedObject));
++            if (it != dbusIntfMap.end())
++            {
++                std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
++                    it->second;
++                auto removed = objectServer.remove_interface(iface);
++                std::cout << "Removed RDE Device from tree: " << removed
++                          << "\n";
++                dbusIntfMap.erase(std::string(changedObject));
++                // TODO (@harshtya): clean up the manager for this object path
++                // TODO (@harshtya): clean up the dictionaries
++            }
++            return;
++        });
++
++    systemBus->request_name("xyz.openbmc_project.rdeoperation");
++    io.run();
++    return 0;
++}
++
++void setSocketTimeout(int fd, int seconds, int milliseconds)
+ {
+     struct timeval tv;
+     tv.tv_sec = seconds;
+@@ -36,12 +187,13 @@ int main()
+     }
+ 
+     std::cerr << "Socket Created with ID: " << fd << ". Setting timeouts...\n";
+-    set_socket_timeout(fd, /*seconds=*/5, /*milliseconds=*/0);
++    setSocketTimeout(fd, /*seconds=*/5, /*milliseconds=*/0);
+     socklen_t optlen;
+     int currentSendbuffSize;
+     optlen = sizeof(currentSendbuffSize);
+ 
+-    int res = getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &currentSendbuffSize, &optlen);
++    int res =
++        getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &currentSendbuffSize, &optlen);
+     if (res == -1)
+     {
+         std::cerr << "Error in obtaining the default send buffer size, Error: "
+@@ -51,9 +203,15 @@ int main()
+ 
+     // TODO: Use PDR P&M PLDM Type to create Resource id mapping
+ 
+-    // TODO (@harstya): Create a RDE Reactor to react to RDE Devices detected
++    rc = triggerRdeReactor(fd);
++    if (rc)
++    {
++        std::cerr << "Error in RDE reactor \n";
++        return rc;
++    }
+ 
+-    // Daemon loop
++    std::cerr << "RDE Reactor stopped...\n";
++    // Daemon loop -- Code never reaches here if the reactor is running fine--
+     auto event = Event::get_default();
+     rc = event.loop();
+     if (rc)
+@@ -62,4 +220,3 @@ int main()
+     }
+     exit(EXIT_SUCCESS);
+ }
+-
+-- 
+2.41.0.255.g8b1d071c50-goog
+
diff --git a/recipes-phosphor/pldm/pldm_%.bbappend b/recipes-phosphor/pldm/pldm_%.bbappend
index 5041522..657767e 100644
--- a/recipes-phosphor/pldm/pldm_%.bbappend
+++ b/recipes-phosphor/pldm/pldm_%.bbappend
@@ -7,4 +7,5 @@
 SRC_URI:append:gbmc = " \
     file://0001-Add-MCTP-Kernel-support.patch \
     file://0002-Add-RDE-Daemon-support-to-commuicate-over-MCTP.patch \
+    file://0003-Add-RDE-Reactor-code-with-MCTP-setup.patch \
 "