Get i2c addresses from configuration files.

Different platforms have different bus topology.
Use the peci address and i2c address specified in
json file to read data from cpu.

Tested:
Update baseboard json file:
{
            "Address": "0x30",
            "Bus": 0,
            "CpuID": 1,
            "Name": "CPU 1",
            "PresenceGpio": [
                {
                    "Name": "CPU1_PRESENCE",
                    "Polarity": "Low"
                }
            ],
            "PiromI2cBus": 13,
            "PiromI2cAddress": "0x50",
            "Type": "XeonCPU"
}
Verified that correct bus addresses are used.

Signed-off-by: Zhikui Ren <zhikui.ren@intel.com>
Change-Id: Ib133958af8349b43c2f8f73c32d1aaa0d5bf52eb
diff --git a/src/cpuinfo_main.cpp b/src/cpuinfo_main.cpp
index 24bb858..de21071 100644
--- a/src/cpuinfo_main.cpp
+++ b/src/cpuinfo_main.cpp
@@ -25,6 +25,7 @@
 #include <boost/asio/io_service.hpp>
 #include <boost/asio/steady_timer.hpp>
 
+#include <iostream>
 #include <optional>
 #include <sstream>
 #include <string>
@@ -44,25 +45,26 @@
 {
 namespace cpu_info
 {
-
+static constexpr bool debug = false;
 static constexpr const char* cpuInterfaceName =
     "xyz.openbmc_project.Inventory.Decorator.Asset";
 static constexpr const char* cpuProcessName =
     "xyz.openbmc_project.Smbios.MDR_V2";
 
-struct ProcessorInfo
-{
-    uint64_t ppin;
-    std::string sspec;
-};
+// constants for reading SSPEC or QDF string from PIROM
+// Currently, they are the same for platforms with icx
+static constexpr uint8_t defaultI2cBus = 13;
+static constexpr uint8_t defaultI2cSlaveAddr0 = 0x50;
+static constexpr uint8_t sspecRegAddr = 0xd;
+static constexpr uint8_t sspecSize = 6;
 
-using CPUMap =
-    boost::container::flat_map<size_t,
-                               std::pair<int, std::shared_ptr<CPUInfo>>>;
+using CPUInfoMap = boost::container::flat_map<size_t, std::shared_ptr<CPUInfo>>;
 
-static CPUMap cpuMap = {};
+static CPUInfoMap cpuInfoMap = {};
 
-static std::unique_ptr<sdbusplus::bus::match_t> cpuUpdatedMatch = nullptr;
+static boost::container::flat_map<size_t,
+                                  std::unique_ptr<sdbusplus::bus::match_t>>
+    cpuUpdatedMatch = {};
 
 static std::optional<std::string> readSSpec(uint8_t bus, uint8_t slaveAddr,
                                             uint8_t regAddr, size_t count)
@@ -116,7 +118,7 @@
 
     for (size_t i = 0; i < count; i++)
     {
-        value = ::i2c_smbus_read_byte_data(fd, regAddr++);
+        value = ::i2c_smbus_read_byte_data(fd, regAddr + i);
         if (value < 0)
         {
             phosphor::logging::log<phosphor::logging::level::ERR>(
@@ -132,47 +134,30 @@
                 "Non printable value in sspec, ignored.");
             continue;
         }
+        // sspec always starts with S,
+        // if not assume it is QDF string which starts at offset 2
+        if (i == 0 && static_cast<unsigned char>(value) != 'S')
+        {
+            i = 1;
+            continue;
+        }
         sspec.push_back(static_cast<unsigned char>(value));
     }
     ::close(fd);
     return sspec;
 }
 
-// PECI Client Address Map
-static void getPECIAddrMap(CPUMap& cpuMap)
-{
-    int idx = 0;
-    for (size_t i = MIN_CLIENT_ADDR; i <= MAX_CLIENT_ADDR; i++)
-    {
-        if (peci_Ping(i) == PECI_CC_SUCCESS)
-        {
-            cpuMap.emplace(std::make_pair(i, std::make_pair(idx, nullptr)));
-            idx++;
-        }
-    }
-}
-
-static std::shared_ptr<CPUInfo>
-    createCPUInfo(std::shared_ptr<sdbusplus::asio::connection>& conn,
-                  const int& cpu)
-{
-    std::string path = cpuPath + std::to_string(cpu);
-    std::shared_ptr<CPUInfo> cpuInfo = std::make_shared<CPUInfo>(
-        static_cast<sdbusplus::bus::bus&>(*conn), path);
-    return cpuInfo;
-}
-
 static void setAssetProperty(
-    std::shared_ptr<sdbusplus::asio::connection>& conn, const int& cpu,
+    const std::shared_ptr<sdbusplus::asio::connection>& conn, const int& cpu,
     const std::vector<std::pair<std::string, std::string>>& propValues)
 {
-
-    const std::string objectPath = cpuPath + std::to_string(cpu);
+    // cpuId from configuration is one based as
+    // dbus object path used by smbios is 0 based
+    const std::string objectPath = cpuPath + std::to_string(cpu - 1);
     for (const auto& prop : propValues)
     {
         conn->async_method_call(
             [](const boost::system::error_code ec) {
-                // Use "Set" method to set the property value.
                 if (ec)
                 {
                     phosphor::logging::log<phosphor::logging::level::ERR>(
@@ -187,197 +172,332 @@
 }
 
 static void createCpuUpdatedMatch(
-    std::shared_ptr<sdbusplus::asio::connection>& conn, const int& cpu,
+    const std::shared_ptr<sdbusplus::asio::connection>& conn, const int cpu,
     const std::vector<std::pair<std::string, std::string>>& propValues)
 {
-    if (cpuUpdatedMatch)
+    if (cpuUpdatedMatch[cpu])
     {
         return;
     }
 
-    const std::string objectPath = cpuPath + std::to_string(cpu);
+    const std::string objectPath = cpuPath + std::to_string(cpu - 1);
 
-    cpuUpdatedMatch = std::make_unique<sdbusplus::bus::match::match>(
-        static_cast<sdbusplus::bus::bus&>(*conn),
-        sdbusplus::bus::match::rules::interfacesAdded() +
-            sdbusplus::bus::match::rules::argNpath(0, objectPath.c_str()),
-        [&conn, cpu, propValues](sdbusplus::message::message& msg) {
-            sdbusplus::message::object_path objectName;
-            boost::container::flat_map<
-                std::string,
-                boost::container::flat_map<std::string,
-                                           std::variant<std::string, uint64_t>>>
-                msgData;
+    cpuUpdatedMatch.insert_or_assign(
+        cpu,
+        std::make_unique<sdbusplus::bus::match::match>(
+            static_cast<sdbusplus::bus::bus&>(*conn),
+            sdbusplus::bus::match::rules::interfacesAdded() +
+                sdbusplus::bus::match::rules::argNpath(0, objectPath.c_str()),
+            [conn, cpu, propValues](sdbusplus::message::message& msg) {
+                sdbusplus::message::object_path objectName;
+                boost::container::flat_map<
+                    std::string,
+                    boost::container::flat_map<
+                        std::string, std::variant<std::string, uint64_t>>>
+                    msgData;
 
-            msg.read(objectName, msgData);
+                msg.read(objectName, msgData);
 
-            // Check for xyz.openbmc_project.Inventory.Item.Cpu
-            // interface match
-            auto intfFound = msgData.find(cpuInterfaceName);
-            if (msgData.end() != intfFound)
-            {
-                setAssetProperty(conn, cpu, propValues);
-            }
-        });
-}
-
-// constants for reading QDF string from PIROM
-// Currently, they are the same for platforms with icx
-// \todo: move into configuration file to be more robust
-static constexpr uint8_t i2cBus = 13;
-static constexpr uint8_t slaveAddr0 = 0x50;
-static constexpr uint8_t regAddr = 0xf;
-static constexpr uint8_t sspecSize = 4;
-
-static void getProcessorInfo(std::shared_ptr<sdbusplus::asio::connection>& conn,
-                             sdbusplus::asio::object_server& objServer,
-                             CPUMap& cpuMap)
-{
-
-    for (auto& cpu : cpuMap)
-    {
-        uint8_t cc = 0;
-        CPUModel model{};
-        uint8_t stepping = 0;
-
-        /// \todo in a follwup patch
-        // CPUInfo will be updated as the centrol place for CPU information
-        // std::shared_ptr<CPUInfo> cpuInfo =
-        //    createCPUInfo(conn, cpu.second.first);
-        // cpu.second.second = cpuInfo;
-
-        if (peci_GetCPUID(cpu.first, &model, &stepping, &cc) != PECI_CC_SUCCESS)
-        {
-            phosphor::logging::log<phosphor::logging::level::ERR>(
-                "Cannot get CPUID!",
-                phosphor::logging::entry("PECIADDR=0x%x", cpu.first));
-            continue;
-        }
-
-        switch (model)
-        {
-            case icx:
-            {
-                // get processor ID
-                static constexpr uint8_t u8Size = 4; // default to a DWORD
-                static constexpr uint8_t u8PPINPkgIndex = 19;
-                static constexpr uint16_t u16PPINPkgParamHigh = 2;
-                static constexpr uint16_t u16PPINPkgParamLow = 1;
-                uint64_t cpuPPIN = 0;
-                uint32_t u32PkgValue = 0;
-
-                int ret = peci_RdPkgConfig(cpu.first, u8PPINPkgIndex,
-                                           u16PPINPkgParamLow, u8Size,
-                                           (uint8_t*)&u32PkgValue, &cc);
-                if (0 != ret)
+                // Check for xyz.openbmc_project.Inventory.Item.Cpu
+                // interface match
+                const auto& intfFound = msgData.find(cpuInterfaceName);
+                if (msgData.end() != intfFound)
                 {
-                    phosphor::logging::log<phosphor::logging::level::ERR>(
-                        "peci read package config failed at address",
-                        phosphor::logging::entry("PECIADDR=0x%x", cpu.first),
-                        phosphor::logging::entry("CC=0x%x", cc));
-                    u32PkgValue = 0;
+                    setAssetProperty(conn, cpu, propValues);
                 }
-
-                cpuPPIN = u32PkgValue;
-                ret = peci_RdPkgConfig(cpu.first, u8PPINPkgIndex,
-                                       u16PPINPkgParamHigh, u8Size,
-                                       (uint8_t*)&u32PkgValue, &cc);
-                if (0 != ret)
-                {
-                    phosphor::logging::log<phosphor::logging::level::ERR>(
-                        "peci read package config failed at address",
-                        phosphor::logging::entry("PECIADDR=0x%x", cpu.first),
-                        phosphor::logging::entry("CC=0x%x", cc));
-                    cpuPPIN = 0;
-                    u32PkgValue = 0;
-                }
-
-                cpuPPIN |= static_cast<uint64_t>(u32PkgValue) << 32;
-
-                std::vector<std::pair<std::string, std::string>> values;
-
-                // set SerialNumber if cpuPPIN is valid
-                if (0 != cpuPPIN)
-                {
-                    std::stringstream stream;
-                    stream << std::hex << cpuPPIN;
-                    std::string serialNumber(stream.str());
-                    // cpuInfo->serialNumber(serialNumber);
-                    values.emplace_back(
-                        std::make_pair("SerialNumber", serialNumber));
-                }
-
-                // assuming the slaveAddress will be incrementing like peci
-                // client address
-                std::optional<std::string> sspec = readSSpec(
-                    i2cBus, static_cast<uint8_t>(slaveAddr0 + cpu.second.first),
-                    regAddr, sspecSize);
-                // cpuInfo->model(sspec.value_or(""));
-                values.emplace_back(
-                    std::make_pair("Model", sspec.value_or("")));
-
-                /// \todo in followup patch
-                // CPUInfo is created by this service
-                // update the below logic, which is needed because smbios
-                // service creates the cpu object
-                createCpuUpdatedMatch(conn, cpu.second.first, values);
-                setAssetProperty(conn, cpu.second.first, values);
-                break;
-            }
-            default:
-                phosphor::logging::log<phosphor::logging::level::INFO>(
-                    "in-compatible cpu for cpu asset info");
-                break;
-        }
-    }
-}
-
-static bool isPECIAvailable(void)
-{
-    for (size_t i = MIN_CLIENT_ADDR; i <= MAX_CLIENT_ADDR; i++)
-    {
-        if (peci_Ping(i) == PECI_CC_SUCCESS)
-        {
-            return true;
-        }
-    }
-    return false;
+            }));
 }
 
 static void
-    peciAvailableCheck(boost::asio::steady_timer& peciWaitTimer,
-                       boost::asio::io_service& io,
-                       std::shared_ptr<sdbusplus::asio::connection>& conn,
-                       sdbusplus::asio::object_server& objServer)
+    getProcessorInfo(boost::asio::io_service& io,
+                     const std::shared_ptr<sdbusplus::asio::connection>& conn,
+                     const size_t& cpu)
 {
-    bool peciAvailable = isPECIAvailable();
-    if (peciAvailable)
+    if (cpuInfoMap.find(cpu) == cpuInfoMap.end() || cpuInfoMap[cpu] == nullptr)
     {
-        // get the PECI client address list
-        getPECIAddrMap(cpuMap);
-        getProcessorInfo(conn, objServer, cpuMap);
+        std::cerr << "No information found for cpu " << cpu << "\n";
+        return;
     }
-    if (!peciAvailable || !cpuMap.size())
+
+    if (cpuInfoMap[cpu]->id != cpu)
     {
-        peciWaitTimer.expires_after(
-            std::chrono::seconds(6 * peciCheckInterval));
-        peciWaitTimer.async_wait([&peciWaitTimer, &io, &conn, &objServer](
-                                     const boost::system::error_code& ec) {
+        std::cerr << "Incorrect CPU id " << (unsigned)cpuInfoMap[cpu]->id
+                  << " expect " << cpu << "\n";
+        return;
+    }
+
+    uint8_t cpuAddr = cpuInfoMap[cpu]->peciAddr;
+    uint8_t i2cBus = cpuInfoMap[cpu]->i2cBus;
+    uint8_t i2cDevice = cpuInfoMap[cpu]->i2cDevice;
+
+    uint8_t cc = 0;
+    CPUModel model{};
+    uint8_t stepping = 0;
+
+    if (peci_GetCPUID(cpuAddr, &model, &stepping, &cc) != PECI_CC_SUCCESS)
+    {
+        // Start the PECI check loop
+        auto waitTimer = std::make_shared<boost::asio::steady_timer>(io);
+        waitTimer->expires_after(
+            std::chrono::seconds(phosphor::cpu_info::peciCheckInterval));
+
+        waitTimer->async_wait(
+            [waitTimer, &io, conn, cpu](const boost::system::error_code& ec) {
+                if (ec)
+                {
+                    // operation_aborted is expected if timer is canceled
+                    // before completion.
+                    if (ec != boost::asio::error::operation_aborted)
+                    {
+                        phosphor::logging::log<phosphor::logging::level::ERR>(
+                            "info update timer async_wait failed ",
+                            phosphor::logging::entry("EC=0x%x", ec.value()));
+                    }
+                    return;
+                }
+                getProcessorInfo(io, conn, cpu);
+            });
+        return;
+    }
+
+    switch (model)
+    {
+        case icx:
+        {
+            // PPIN can be read through PCS 19
+            static constexpr uint8_t u8Size = 4; // default to a DWORD
+            static constexpr uint8_t u8PPINPkgIndex = 19;
+            static constexpr uint16_t u16PPINPkgParamHigh = 2;
+            static constexpr uint16_t u16PPINPkgParamLow = 1;
+            uint64_t cpuPPIN = 0;
+            uint32_t u32PkgValue = 0;
+
+            int ret =
+                peci_RdPkgConfig(cpuAddr, u8PPINPkgIndex, u16PPINPkgParamLow,
+                                 u8Size, (uint8_t*)&u32PkgValue, &cc);
+            if (0 != ret)
+            {
+                phosphor::logging::log<phosphor::logging::level::ERR>(
+                    "peci read package config failed at address",
+                    phosphor::logging::entry("PECIADDR=0x%x",
+                                             (unsigned)cpuAddr),
+                    phosphor::logging::entry("CC=0x%x", cc));
+                u32PkgValue = 0;
+            }
+
+            cpuPPIN = u32PkgValue;
+            ret = peci_RdPkgConfig(cpuAddr, u8PPINPkgIndex, u16PPINPkgParamHigh,
+                                   u8Size, (uint8_t*)&u32PkgValue, &cc);
+            if (0 != ret)
+            {
+                phosphor::logging::log<phosphor::logging::level::ERR>(
+                    "peci read package config failed at address",
+                    phosphor::logging::entry("PECIADDR=0x%x",
+                                             (unsigned)cpuAddr),
+                    phosphor::logging::entry("CC=0x%x", cc));
+                cpuPPIN = 0;
+                u32PkgValue = 0;
+            }
+
+            cpuPPIN |= static_cast<uint64_t>(u32PkgValue) << 32;
+
+            std::vector<std::pair<std::string, std::string>> values;
+
+            // set SerialNumber if cpuPPIN is valid
+            if (0 != cpuPPIN)
+            {
+                std::stringstream stream;
+                stream << std::hex << cpuPPIN;
+                std::string serialNumber(stream.str());
+                // cpuInfo->serialNumber(serialNumber);
+                values.emplace_back(
+                    std::make_pair("SerialNumber", serialNumber));
+            }
+
+            std::optional<std::string> sspec =
+                readSSpec(i2cBus, i2cDevice, sspecRegAddr, sspecSize);
+
+            // cpuInfo->model(sspec.value_or(""));
+            values.emplace_back(std::make_pair("Model", sspec.value_or("")));
+
+            /// \todo in followup patch
+            // CPUInfo is created by this service
+            // update the below logic, which is needed because smbios
+            // service creates the cpu object
+            createCpuUpdatedMatch(conn, cpu, values);
+            setAssetProperty(conn, cpu, values);
+            break;
+        }
+        default:
+            phosphor::logging::log<phosphor::logging::level::INFO>(
+                "in-compatible cpu for cpu asset info");
+            break;
+    }
+}
+
+/**
+ * Get cpu and pirom address
+ */
+static void
+    getCpuAddress(boost::asio::io_service& io,
+                  const std::shared_ptr<sdbusplus::asio::connection>& conn,
+                  const std::string& service, const std::string& object,
+                  const std::string& interface)
+{
+    conn->async_method_call(
+        [&io, conn](boost::system::error_code ec,
+                    const boost::container::flat_map<
+                        std::string,
+                        std::variant<std::string, uint64_t, uint32_t, uint16_t,
+                                     std::vector<std::string>>>& properties) {
+            const uint64_t* value = nullptr;
+            uint8_t peciAddress = 0;
+            uint8_t i2cBus = defaultI2cBus;
+            uint8_t i2cDevice;
+            bool i2cDeviceFound = false;
+            size_t cpu = 0;
+
             if (ec)
             {
-                // operation_aborted is expected if timer is canceled
-                // before completion.
-                if (ec != boost::asio::error::operation_aborted)
-                {
-                    phosphor::logging::log<phosphor::logging::level::ERR>(
-                        "PECI Available Check async_wait failed",
-                        phosphor::logging::entry("EC=0x%x", ec.value()));
-                }
+                std::cerr << "DBUS response error " << ec.value() << ": "
+                          << ec.message() << "\n";
                 return;
             }
-            peciAvailableCheck(peciWaitTimer, io, conn, objServer);
-        });
-    }
+
+            for (const auto& property : properties)
+            {
+                std::cerr << "property " << property.first << "\n";
+                if (property.first == "Address")
+                {
+                    value = std::get_if<uint64_t>(&property.second);
+                    if (value != nullptr)
+                    {
+                        peciAddress = static_cast<uint8_t>(*value);
+                    }
+                }
+                if (property.first == "CpuID")
+                {
+                    value = std::get_if<uint64_t>(&property.second);
+                    if (value != nullptr)
+                    {
+                        cpu = static_cast<size_t>(*value);
+                    }
+                }
+                if (property.first == "PiromI2cAddress")
+                {
+                    value = std::get_if<uint64_t>(&property.second);
+                    if (value != nullptr)
+                    {
+                        i2cDevice = static_cast<uint8_t>(*value);
+                        i2cDeviceFound = true;
+                    }
+                }
+                if (property.first == "PiromI2cBus")
+                {
+                    value = std::get_if<uint64_t>(&property.second);
+                    if (value != nullptr)
+                    {
+                        i2cBus = static_cast<uint8_t>(*value);
+                    }
+                }
+            }
+
+            ///\todo replace this with present + power state
+            if (cpu != 0 && peciAddress != 0)
+            {
+                if (!i2cDeviceFound)
+                {
+                    i2cDevice = defaultI2cSlaveAddr0 + cpu - 1;
+                }
+                cpuInfoMap.insert_or_assign(
+                    cpu, std::make_shared<CPUInfo>(cpu, peciAddress, i2cBus,
+                                                   i2cDevice));
+
+                getProcessorInfo(io, conn, cpu);
+            }
+        },
+        service, object, "org.freedesktop.DBus.Properties", "GetAll",
+        interface);
+}
+
+/**
+ * D-Bus client: to get platform specific configs
+ */
+static void getCpuConfiguration(
+    boost::asio::io_service& io,
+    const std::shared_ptr<sdbusplus::asio::connection>& conn,
+    sdbusplus::asio::object_server& objServer)
+{
+    // Get the Cpu configuration
+    // In case it's not available, set a match for it
+    static std::unique_ptr<sdbusplus::bus::match::match> cpuConfigMatch =
+        std::make_unique<sdbusplus::bus::match::match>(
+            *conn,
+            "type='signal',interface='org.freedesktop.DBus.Properties',member='"
+            "PropertiesChanged',arg0='xyz.openbmc_project."
+            "Configuration.XeonCPU'",
+            [&io, conn, &objServer](sdbusplus::message::message& msg) {
+                std::cerr << "get cpu configuration match\n";
+                static boost::asio::steady_timer filterTimer(io);
+                filterTimer.expires_after(
+                    std::chrono::seconds(configCheckInterval));
+
+                filterTimer.async_wait(
+                    [&io, conn,
+                     &objServer](const boost::system::error_code& ec) {
+                        if (ec == boost::asio::error::operation_aborted)
+                        {
+                            return; // we're being canceled
+                        }
+                        else if (ec)
+                        {
+                            std::cerr << "Error: " << ec.message() << "\n";
+                            return;
+                        }
+                        getCpuConfiguration(io, conn, objServer);
+                    });
+            });
+
+    conn->async_method_call(
+        [&io, conn](
+            boost::system::error_code ec,
+            const std::vector<std::pair<
+                std::string,
+                std::vector<std::pair<std::string, std::vector<std::string>>>>>&
+                subtree) {
+            if constexpr (debug)
+                std::cerr << "async_method_call callback\n";
+
+            if (ec)
+            {
+                std::cerr << "error with async_method_call\n";
+                return;
+            }
+            if (subtree.empty())
+            {
+                // No config data yet, so wait for the match
+                return;
+            }
+
+            for (const auto& object : subtree)
+            {
+                for (const auto& service : object.second)
+                {
+                    getCpuAddress(io, conn, service.first, object.first,
+                                  "xyz.openbmc_project.Configuration.XeonCPU");
+                }
+            }
+            if constexpr (debug)
+                std::cerr << "getCpuConfiguration callback complete\n";
+
+            return;
+        },
+        "xyz.openbmc_project.ObjectMapper",
+        "/xyz/openbmc_project/object_mapper",
+        "xyz.openbmc_project.ObjectMapper", "GetSubTree",
+        "/xyz/openbmc_project/", 0,
+        std::array<const char*, 1>{
+            "xyz.openbmc_project.Configuration.XeonCPU"});
 }
 
 } // namespace cpu_info
@@ -400,25 +520,9 @@
 
     cpu_info::sst::init(io, conn);
 
-    // Start the PECI check loop
-    boost::asio::steady_timer peciWaitTimer(
-        io, std::chrono::seconds(phosphor::cpu_info::peciCheckInterval));
-    peciWaitTimer.async_wait([&peciWaitTimer, &io, &conn,
-                              &server](const boost::system::error_code& ec) {
-        if (ec)
-        {
-            // operation_aborted is expected if timer is canceled
-            // before completion.
-            if (ec != boost::asio::error::operation_aborted)
-            {
-                phosphor::logging::log<phosphor::logging::level::ERR>(
-                    "PECI Available Check async_wait failed ",
-                    phosphor::logging::entry("EC=0x%x", ec.value()));
-            }
-            return;
-        }
-        phosphor::cpu_info::peciAvailableCheck(peciWaitTimer, io, conn, server);
-    });
+    // shared_ptr conn is global for the service
+    // const reference of conn is passed to async calls
+    phosphor::cpu_info::getCpuConfiguration(io, conn, server);
 
     io.run();