[ipmi] Generate bifurcation for item without associations

This CL make IPMI dynamic bifurcation to generate bifurcation for device
without association.

This is the case for devices like Y-cable on
RP. Only the primary side of y-cable has downstream, the other
side(P1) doesn't have dowmstream. Without this fix, P1 side of the
Y-cable doesn't have the right bifurcation.

Tested:
All sides of Y-cable has 8x8 bifurcation
root@lcsqli-j6-nfd01:~# for slot in 1 2 5 6; do echo -n "Slot $slot Bifurcation: "; ipmitool raw 0x2e 0x32 0x79 0x2b 0x00 0x0f $slot; done
Slot 1 Bifurcation:  79 2b 00 0f 02 08 08
Slot 2 Bifurcation:  79 2b 00 0f 02 08 08
Slot 5 Bifurcation:  79 2b 00 0f 02 08 08
Slot 6 Bifurcation:  79 2b 00 0f 02 08 08

Google-Bug-Id: 483873442
Change-Id: I17fa5095a29cd219c3d5dee5f28dc8d835e35c52
Signed-off-by: Tao Ke <taok@google.com>
diff --git a/recipes-google/ipmi/google-ipmi-sys/0001-pcie_bifurcation-Fetch-highest-level-bifurcation-at-.patch b/recipes-google/ipmi/google-ipmi-sys/0001-pcie_bifurcation-Fetch-highest-level-bifurcation-at-.patch
index 38a4652..63e000f 100644
--- a/recipes-google/ipmi/google-ipmi-sys/0001-pcie_bifurcation-Fetch-highest-level-bifurcation-at-.patch
+++ b/recipes-google/ipmi/google-ipmi-sys/0001-pcie_bifurcation-Fetch-highest-level-bifurcation-at-.patch
@@ -41,28 +41,28 @@
 
 Change-Id: I0c473ae5657533113958b0b57f3145dd072f9b8a
 ---
- bifurcation/bifurcation.hpp         | 140 +++++++++++++++++-
- bifurcation/bifurcation_dynamic.cpp | 193 ++++++++++++++++++++++++
+ bifurcation/bifurcation.hpp         | 159 +++++++++++++++-
+ bifurcation/bifurcation_dynamic.cpp | 222 +++++++++++++++++++++++
  bifurcation/bifurcation_static.cpp  |   3 +-
  bifurcation/meson.build             |   1 +
- handler.cpp                         |  55 ++++++-
+ handler.cpp                         |  55 +++++-
  handler.hpp                         |   7 +-
  handler_impl.hpp                    |  14 +-
  ipmi.cpp                            |   4 +-
- meson.build                         |   9 ++
+ meson.build                         |   9 +
  meson.options                       |  13 ++
  pcie_bifurcation.cpp                |   9 +-
  pcie_bifurcation.hpp                |   5 +-
- test/bifurcation_mock.hpp           |  53 +++++++
+ test/bifurcation_mock.hpp           |  56 ++++++
  test/handler_mock.hpp               |   3 +-
- test/handler_unittest.cpp           | 221 +++++++++++++++++++++++++++-
- test/pcie_bifurcation_unittest.cpp  |  66 ++++++++-
- 16 files changed, 768 insertions(+), 28 deletions(-)
+ test/handler_unittest.cpp           | 271 +++++++++++++++++++++++++++-
+ test/pcie_bifurcation_unittest.cpp  |  66 ++++++-
+ 16 files changed, 869 insertions(+), 28 deletions(-)
  create mode 100644 bifurcation/bifurcation_dynamic.cpp
  create mode 100644 test/bifurcation_mock.hpp
 
 diff --git a/bifurcation/bifurcation.hpp b/bifurcation/bifurcation.hpp
-index b774aa0..70394b4 100644
+index b774aa0..7b406aa 100644
 --- a/bifurcation/bifurcation.hpp
 +++ b/bifurcation/bifurcation.hpp
 @@ -13,8 +13,11 @@
@@ -100,7 +100,7 @@
  class BifurcationInterface
  {
    public:
-@@ -34,16 +53,62 @@ class BifurcationInterface
+@@ -34,16 +53,72 @@ class BifurcationInterface
      /**
       * Get the Bifurcation of the device at the i2c bus
       *
@@ -143,6 +143,16 @@
 +        ::ipmi::Context::ptr ctx, const std::string& path) noexcept = 0;
 +
 +    /**
++     * Request the SupportedChannels from the PCIeSlot dbus object
++     *
++     * @param[in] ctx    IPMI Context Pointer
++     * @param[in] path   Dbus object path for PCIeSlot
++     * @return the SupportedChannels of the dbus object
++     */
++    virtual std::optional<uint64_t> pcieSlotChannels(
++        ::ipmi::Context::ptr ctx, const std::string& path) noexcept = 0;
++
++    /**
 +     * Physical Association that is conatained_by the target dbus object.
 +     *
 +     * @param[in] ctx    IPMI Context Pointer
@@ -164,7 +174,7 @@
      static std::reference_wrapper<BifurcationInterface> createBifurcation()
      {
          static BifurcationStatic bifurcationStatic;
-@@ -54,7 +119,31 @@ class BifurcationStatic : public BifurcationInterface
+@@ -54,7 +129,37 @@ class BifurcationStatic : public BifurcationInterface
      BifurcationStatic(std::string_view bifurcationFile);
  
      std::optional<std::vector<uint8_t>> getBifurcation(
@@ -189,6 +199,12 @@
 +        return std::nullopt;
 +    };
 +
++    std::optional<uint64_t> pcieSlotChannels(
++        ::ipmi::Context::ptr, const std::string&) noexcept override
++    {
++        return std::nullopt;
++    };
++
 +    std::vector<std::string> physicalAssociations(
 +        ::ipmi::Context::ptr, const std::string&) noexcept override
 +    {
@@ -197,7 +213,7 @@
  
    protected:
      BifurcationStatic();
-@@ -63,5 +152,52 @@ class BifurcationStatic : public BifurcationInterface
+@@ -63,5 +168,55 @@ class BifurcationStatic : public BifurcationInterface
      std::string bifurcationFile;
  };
  
@@ -230,6 +246,9 @@
 +    std::optional<uint64_t> pcieSlotLanes(
 +        ::ipmi::Context::ptr ctx, const std::string& path) noexcept override;
 +
++    std::optional<uint64_t> pcieSlotChannels(
++        ::ipmi::Context::ptr ctx, const std::string& path) noexcept override;
++
 +    std::vector<std::string> physicalAssociations(
 +        ::ipmi::Context::ptr ctx, const std::string& path) noexcept override;
 +
@@ -252,10 +271,10 @@
  } // namespace google
 diff --git a/bifurcation/bifurcation_dynamic.cpp b/bifurcation/bifurcation_dynamic.cpp
 new file mode 100644
-index 0000000..ca299cc
+index 0000000..53f959a
 --- /dev/null
 +++ b/bifurcation/bifurcation_dynamic.cpp
-@@ -0,0 +1,193 @@
+@@ -0,0 +1,222 @@
 +// Copyright 2022 Google LLC
 +//
 +// Licensed under the Apache License, Version 2.0 (the "License");
@@ -362,6 +381,23 @@
 +    return std::get<uint64_t>(deviceLanes);
 +}
 +
++std::optional<uint64_t>
++    BifurcationDynamic::pcieSlotChannels(::ipmi::Context::ptr ctx,
++                                         const std::string& path) noexcept
++{
++    boost::system::error_code ec;
++    std::variant<uint64_t> channels =
++        ctx->bus->yield_method_call<std::variant<uint64_t>>(
++            ctx->yield, ec, kEntityManagerServiceName, path,
++            "org.freedesktop.DBus.Properties", "Get", kPCIeSlotInterfaceName,
++            "SupportedChannels");
++    if (ec)
++    {
++        return std::nullopt;
++    }
++    return std::get<uint64_t>(channels);
++}
++
 +std::vector<std::string>
 +    BifurcationDynamic::physicalAssociations(::ipmi::Context::ptr ctx,
 +                                             const std::string& path) noexcept
@@ -417,7 +453,19 @@
 +        return std::vector<uint8_t>();
 +    }
 +
-+    if (totalLanes < maxLanes)
++    if (bifurication.empty())
++    {
++        auto channels = pcieSlotChannels(ctx, path).value_or(1);
++        if (channels > 1 && (maxLanes % channels == 0))
++        {
++            bifurication.assign(channels, maxLanes / channels);
++        }
++        else
++        {
++            bifurication.emplace_back(maxLanes);
++        }
++    }
++    else if (totalLanes < maxLanes)
 +    {
 +        bifurication.emplace_back(maxLanes - totalLanes);
 +    }
@@ -656,9 +704,17 @@
              return linuxBootDone(data, handler);
          case SysGetAccelVrSettings:
 diff --git a/meson.build b/meson.build
+index 39ae9c5..d0b6394 100644
 --- a/meson.build
 +++ b/meson.build
-@@ -19,2 +19,10 @@ conf_data.set10('IPMI_ALLOWLIST', get_option('ipmi_allowlist'))
+@@ -13,10 +13,19 @@ conf_data.set_quoted(
+     'STATIC_BIFURCATION_CONFIG',
+     get_option('static-bifurcation'),
+ )
++
+ conf_data.set_quoted('CPU_CONFIG_PATH', get_option('cpu-config-path'))
+ 
+ conf_data.set10('IPMI_ALLOWLIST', get_option('ipmi_allowlist'))
  
 +if get_option('dynamic-bifurcation').allowed()
 +  conf_data.set('DYNAMIC_BIFURCATION', 'true')
@@ -669,6 +725,8 @@
 +endif
 +
  conf_h = configure_file(output: 'config.h', configuration: conf_data)
+ 
+ bm_conf_data = configuration_data()
 diff --git a/meson.options b/meson.options
 index 4239f04..8a8125b 100644
 --- a/meson.options
@@ -756,10 +814,10 @@
  } // namespace google
 diff --git a/test/bifurcation_mock.hpp b/test/bifurcation_mock.hpp
 new file mode 100644
-index 0000000..7d40e8d
+index 0000000..4c2936e
 --- /dev/null
 +++ b/test/bifurcation_mock.hpp
-@@ -0,0 +1,53 @@
+@@ -0,0 +1,56 @@
 +// Copyright 2023 Google LLC
 +//
 +// Licensed under the Apache License, Version 2.0 (the "License");
@@ -806,6 +864,9 @@
 +    MOCK_METHOD((std::optional<uint64_t>), pcieSlotLanes,
 +                (::ipmi::Context::ptr, const std::string&),
 +                (noexcept, override));
++    MOCK_METHOD((std::optional<uint64_t>), pcieSlotChannels,
++                (::ipmi::Context::ptr, const std::string&),
++                (noexcept, override));
 +    MOCK_METHOD(std::vector<std::string>, physicalAssociations,
 +                (::ipmi::Context::ptr, const std::string&),
 +                (noexcept, override));
@@ -829,7 +890,7 @@
      MOCK_METHOD(void, accelSetVrSettings,
                  (::ipmi::Context::ptr, uint8_t, uint8_t, uint16_t),
 diff --git a/test/handler_unittest.cpp b/test/handler_unittest.cpp
-index d28014a..e69a307 100644
+index d28014a..2f6ab1f 100644
 --- a/test/handler_unittest.cpp
 +++ b/test/handler_unittest.cpp
 @@ -12,9 +12,13 @@
@@ -896,7 +957,7 @@
          EXPECT_TRUE(bifurcation.empty());
      }
  }
-@@ -722,6 +729,212 @@ TEST(HandlerTest, GetCoreCountWithExtraKeys)
+@@ -722,6 +729,262 @@ TEST(HandlerTest, GetCoreCountWithExtraKeys)
      std::remove(testFilename);
  }
  
@@ -906,6 +967,7 @@
 +    uint8_t bus;
 +    std::optional<uint8_t> lanes;
 +    std::optional<std::vector<std::string>> association;
++    std::optional<uint64_t> channels = std::nullopt;
 +};
 +
 +void createPhysicalAssociation(BifurcationDynamicMock& bifurcationHelper,
@@ -936,6 +998,8 @@
 +        }
 +        EXPECT_CALL(bifurcationHelper, pcieSlotLanes(_, device.name))
 +            .WillRepeatedly(Return(device.lanes));
++        EXPECT_CALL(bifurcationHelper, pcieSlotChannels(_, device.name))
++            .WillRepeatedly(Return(device.channels));
 +        EXPECT_CALL(bifurcationHelper, physicalAssociations(_, device.name))
 +            .WillRepeatedly(Return(device.association.value()));
 +    }
@@ -1106,6 +1170,53 @@
 +    std::remove(testFilename);
 +}
 +
++TEST(HandlerTest, PcieBifurcationDynamicChannels)
++{
++    std::vector<std::string> baseAssociations = {
++        "slot-10", "slot-20", "slot-30",
++    };
++    std::vector<PhysicalTopology> topology = {
++        {"slot-10", 10, 16, std::vector<std::string>{}, 2},
++        {"slot-20", 20, 16, std::vector<std::string>{}, 4},
++        {"slot-30", 30, 16, std::vector<std::string>{}, 3},
++    };
++    HandlerMock hMock;
++    BifurcationDynamicMock bifurcationHelper;
++
++    createPhysicalAssociation(bifurcationHelper, baseAssociations, topology);
++
++    const char* testFilename = "test_channels.json";
++    std::string contents = R"(
++        {
++            "add_in_card": [
++                {"instance": 10, "name": "/PE0"},
++                {"instance": 20, "name": "/PE1"},
++                {"instance": 30, "name": "/PE2"}
++            ]
++        }
++    )";
++    std::ofstream outputJson(testFilename);
++    outputJson << contents;
++    outputJson.flush();
++    outputJson.close();
++
++    Handler h(std::ref(bifurcationHelper), testFilename);
++
++    // 16 lanes, 2 channels -> (8, 8)
++    auto bifurcation = h.pcieBifurcation(nullptr, 0, true);
++    EXPECT_THAT(bifurcation, ElementsAre(8, 8));
++
++    // 16 lanes, 4 channels -> (4, 4, 4, 4)
++    bifurcation = h.pcieBifurcation(nullptr, 1, true);
++    EXPECT_THAT(bifurcation, ElementsAre(4, 4, 4, 4));
++
++    // 16 lanes, 3 channels (not divisible) -> fallback to (16)
++    bifurcation = h.pcieBifurcation(nullptr, 2, true);
++    EXPECT_THAT(bifurcation, ElementsAre(16));
++
++    std::remove(testFilename);
++}
++
  // TODO: Add checks for other functions of handler.
  
  } // namespace ipmi
@@ -1220,5 +1331,5 @@
  
  } // namespace ipmi
 -- 
-2.53.0.473.g4a7958ca14-goog
+2.55.0.1007.g17ff1f9808-goog