Support dynamically update BMC slave address

On multi-node platform, the BMC slave address is determined by node ID,
so the BMC slave address needs to be updated after node ID detection.
This patch makes slave-mqueue configurable at run time using 'new_device'
and 'delete-device' sysfs interfaces.
The sample to change BMC slave addr:
(Assume original slave addr is 0x20, but it should be 0x22)
echo "0x1010" > /sys/bus/i2c/devices/i2c-0/delete_device
echo "slave-mqueue 0x1011" > /sys/bus/i2c/devices/i2c-0/new_device

Dependency:
[DTS] remove ipmb0 node to support dynamically update BMC slave address

Tested:
1. The I2C slave message can be received correctly after CMC sends signal
of "updateBmcSlaveAddr" (BNP)
2. Check sys file of slave device exists by command like
"ls -l /sys/bus/i2c/devices/0-1011/slave-mqueue" (BNP)
3. IPMI command injected to bus0 via IPMB header can return correct value (WFP)

Change-Id: Ie55715cf940f19e10d265ae7efec4d4bf55744a2
Signed-off-by: Qiang XU <qiang.xu@linux.intel.com>
diff --git a/ipmbbridged.cpp b/ipmbbridged.cpp
index 2018e28..8104043 100644
--- a/ipmbbridged.cpp
+++ b/ipmbbridged.cpp
@@ -18,6 +18,8 @@
 #include "ipmbdefines.hpp"
 #include "ipmbutils.hpp"
 
+#include <boost/algorithm/string/replace.hpp>
+#include <filesystem>
 #include <fstream>
 #include <nlohmann/json.hpp>
 #include <phosphor-logging/log.hpp>
@@ -501,6 +503,41 @@
 int IpmbChannel::ipmbChannelInit(const char *ipmbI2cSlave,
                                  const char *ipmbI2cMaster)
 {
+    // extract bus id from master path and save
+    std::string ipmbI2cMasterStr(ipmbI2cMaster);
+    auto findHyphen = ipmbI2cMasterStr.find("-");
+    std::string busStr = ipmbI2cMasterStr.substr(findHyphen + 1);
+    try
+    {
+        ipmbBusId = std::stoi(busStr);
+    }
+    catch (std::invalid_argument)
+    {
+        phosphor::logging::log<phosphor::logging::level::ERR>(
+            "ipmbChannelInit: invalid bus id in master-path config");
+        return -1;
+    }
+
+    // Check if sysfs has device. If not, enable I2C slave driver by command
+    // echo "slave-mqueue 0x1010" > /sys/bus/i2c/devices/i2c-0/new_device
+    bool hasSysfs = std::filesystem::exists(ipmbI2cSlave);
+    if (!hasSysfs)
+    {
+        std::string deviceFileName =
+            "/sys/bus/i2c/devices/i2c-" + busStr + "/new_device";
+        std::string para = "slave-mqueue 0x1010"; // init with BMC addr 0x20
+        std::fstream deviceFile;
+        deviceFile.open(deviceFileName, std::ios::out);
+        if (!deviceFile.good())
+        {
+            phosphor::logging::log<phosphor::logging::level::ERR>(
+                "ipmbChannelInit: error opening deviceFile");
+            return -1;
+        }
+        deviceFile << para;
+        deviceFile.close();
+    }
+
     // open fd to i2c slave device
     ipmbi2cSlaveFd = open(ipmbI2cSlave, O_RDONLY | O_NONBLOCK | O_CLOEXEC);
     if (ipmbi2cSlaveFd < 0)
@@ -517,6 +554,7 @@
         phosphor::logging::log<phosphor::logging::level::ERR>(
             "ipmbChannelInit: error opening ipmbI2cMaster");
         close(ipmbi2cSlaveFd);
+        ipmbi2cSlaveFd = 0;
         return -1;
     }
 
@@ -528,6 +566,8 @@
             "ipmbChannelInit: error setting ipmbi2cMasterFd slave address");
         close(ipmbi2cSlaveFd);
         close(ipmbi2cMasterFd);
+        ipmbi2cSlaveFd = 0;
+        ipmbi2cMasterFd = 0;
         return -1;
     }
 
@@ -549,6 +589,90 @@
     return 0;
 }
 
+int IpmbChannel::ipmbChannelUpdateSlaveAddress(const uint8_t newBmcSlaveAddr)
+{
+    if (ipmbi2cSlaveFd > 0)
+    {
+        i2cSlaveSocket.close();
+        close(ipmbi2cSlaveFd);
+        ipmbi2cSlaveFd = 0;
+    }
+
+    // disable old I2C slave driver by command:
+    //     echo "0x1010" > /sys/bus/i2c/devices/i2c-0/delete_device
+    std::string deviceFileName;
+    std::string para;
+    std::fstream deviceFile;
+    deviceFileName = "/sys/bus/i2c/devices/i2c-" + std::to_string(ipmbBusId) +
+                     "/delete_device";
+    para = "0x1010"; // align with removed ipmb0 definition in dts file
+    deviceFile.open(deviceFileName, std::ios::out);
+    if (!deviceFile.good())
+    {
+        phosphor::logging::log<phosphor::logging::level::ERR>(
+            "ipmbChannelUpdateSlaveAddress: error opening deviceFile to delete "
+            "sysfs");
+        return -1;
+    }
+    deviceFile << para;
+    deviceFile.close();
+
+    // enable new I2C slave driver by command:
+    //      echo "slave-mqueue 0x1012" > /sys/bus/i2c/devices/i2c-0/new_device
+    deviceFileName =
+        "/sys/bus/i2c/devices/i2c-" + std::to_string(ipmbBusId) + "/new_device";
+    std::ostringstream hex;
+    uint16_t addr = 0x1000 + (newBmcSlaveAddr >> 1);
+    hex << std::hex << static_cast<uint16_t>(addr);
+    const std::string &addressHexStr = hex.str();
+    para = "slave-mqueue 0x" + addressHexStr;
+    deviceFile.open(deviceFileName, std::ios::out);
+    if (!deviceFile.good())
+    {
+        phosphor::logging::log<phosphor::logging::level::ERR>(
+            "ipmbChannelUpdateSlaveAddress: error opening deviceFile to create "
+            "sysfs");
+        return -1;
+    }
+    deviceFile << para;
+    deviceFile.close();
+
+    // open fd to i2c slave device
+    std::string ipmbI2cSlaveStr = "/sys/bus/i2c/devices/" +
+                                  std::to_string(ipmbBusId) + "-" +
+                                  addressHexStr + "/slave-mqueue";
+    ipmbi2cSlaveFd =
+        open(ipmbI2cSlaveStr.c_str(), O_RDONLY | O_NONBLOCK | O_CLOEXEC);
+    if (ipmbi2cSlaveFd < 0)
+    {
+        phosphor::logging::log<phosphor::logging::level::ERR>(
+            "ipmbChannelInit: error opening ipmbI2cSlave");
+        return -1;
+    }
+
+    // start to receive i2c data as slave
+    i2cSlaveSocket.assign(boost::asio::ip::tcp::v4(), ipmbi2cSlaveFd);
+    i2cSlaveSocket.async_wait(
+        boost::asio::ip::tcp::socket::wait_error,
+        [this](const boost::system::error_code &ec) {
+            if (ec)
+            {
+                phosphor::logging::log<phosphor::logging::level::ERR>(
+                    "Error: processI2cEvent()");
+                return;
+            }
+
+            processI2cEvent();
+        });
+
+    return 0;
+}
+
+uint8_t IpmbChannel::getBusId()
+{
+    return ipmbBusId;
+}
+
 uint8_t IpmbChannel::getBmcSlaveAddress()
 {
     return ipmbBmcSlaveAddress;
@@ -741,6 +865,55 @@
     return channel->requestAdd(yield, request);
 };
 
+void addUpdateSlaveAddrHandler()
+{
+    // callback to handle dbus signal of updating slave addr
+    std::function<void(sdbusplus::message::message &)> updateSlaveAddrHandler =
+        [](sdbusplus::message::message &message) {
+            uint8_t reqChannel, busId, slaveAddr;
+
+            // valid source of signal, check whether from multi-node manager
+            std::string pathName = message.get_path();
+            if (pathName != "/xyz/openbmc_project/MultiNode/Status")
+            {
+                phosphor::logging::log<phosphor::logging::level::ERR>(
+                    "addUpdateSlaveAddrHandler: invalid obj path");
+                return;
+            }
+
+            message.read(reqChannel, busId, slaveAddr);
+
+            IpmbChannel *channel =
+                getChannel(static_cast<ipmbChannelType>(reqChannel));
+            if (channel == nullptr ||
+                reqChannel != static_cast<uint8_t>(ipmbChannelType::ipmb))
+            {
+                phosphor::logging::log<phosphor::logging::level::ERR>(
+                    "addUpdateSlaveAddrHandler: invalid channel");
+                return;
+            }
+            if (busId != channel->getBusId())
+            {
+                phosphor::logging::log<phosphor::logging::level::ERR>(
+                    "addUpdateSlaveAddrHandler: invalid busId");
+                return;
+            }
+            if (channel->getBmcSlaveAddress() == slaveAddr)
+            {
+                phosphor::logging::log<phosphor::logging::level::INFO>(
+                    "addUpdateSlaveAddrHandler: channel bmc slave addr is "
+                    "unchanged, do nothing");
+                return;
+            }
+
+            channel->ipmbChannelUpdateSlaveAddress(slaveAddr);
+        };
+
+    static auto match = std::make_unique<sdbusplus::bus::match::match>(
+        static_cast<sdbusplus::bus::bus &>(*conn),
+        "type='signal',member='updateBmcSlaveAddr',", updateSlaveAddrHandler);
+}
+
 /**
  * @brief Main
  */
@@ -763,6 +936,8 @@
         return -1;
     }
 
+    addUpdateSlaveAddrHandler();
+
     io.run();
     return 0;
 }
diff --git a/ipmbbridged.hpp b/ipmbbridged.hpp
index 4d24790..01b2f44 100644
--- a/ipmbbridged.hpp
+++ b/ipmbbridged.hpp
@@ -257,10 +257,14 @@
 
     int ipmbChannelInit(const char *ipmbI2cSlave, const char *ipmbI2cMaster);
 
+    int ipmbChannelUpdateSlaveAddress(const uint8_t newBmcSlaveAddr);
+
     bool seqNumGet(uint8_t &seq);
 
     ipmbChannelType getChannelType();
 
+    uint8_t getBusId();
+
     uint8_t getBmcSlaveAddress();
 
     uint8_t getRqSlaveAddress();
@@ -285,6 +289,7 @@
 
     uint8_t ipmbBmcSlaveAddress;
     uint8_t ipmbRqSlaveAddress;
+    uint8_t ipmbBusId;
 
     ipmbChannelType type;