Add a platform init daemon
This commit adds a daemon that can initialize platforms, and adds in the
requisite CLI interfaces to be able to add new platforms in the future.
Change-Id: If4a801fe38e9f4141737d2b5c8b494dc00bf4d59
Signed-off-by: Ed Tanous <etanous@nvidia.com>
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..02cf3c0
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+/subprojects/*
+!/subprojects/*.wrap
diff --git a/meson.build b/meson.build
new file mode 100644
index 0000000..26dbac2
--- /dev/null
+++ b/meson.build
@@ -0,0 +1,33 @@
+project(
+ 'platform-init',
+ 'cpp',
+ meson_version: '>=1.7.0',
+ version: '0.1',
+ default_options: ['warning_level=3', 'cpp_std=c++23'],
+)
+
+gpiodcxx_dep = dependency('libgpiodcxx', default_options: ['bindings=cxx'])
+systemd_dep = dependency('systemd')
+libsystemd_dep = dependency('libsystemd')
+
+cli11_dep = dependency('CLI11', required: true, include_type: 'system')
+
+exe = executable(
+ 'platform',
+ 'platform.cpp',
+ dependencies: [gpiodcxx_dep, libsystemd_dep, cli11_dep],
+ include_directories: ['.'],
+ install: true,
+ install_dir: get_option('libexecdir'),
+)
+
+systemd_system_unit_dir = systemd_dep.get_variable('systemd_system_unit_dir')
+configure_file(
+ input: 'platform_init.service.in',
+ output: 'platform_init.service',
+ install_dir: systemd_system_unit_dir,
+ install: true,
+ configuration: configuration_data(
+ {'PLATFORM_NAME': get_option('platform-name')},
+ ),
+)
diff --git a/meson.options b/meson.options
new file mode 100644
index 0000000..9004923
--- /dev/null
+++ b/meson.options
@@ -0,0 +1,6 @@
+option(
+ 'platform-name',
+ type: 'string',
+ value: 'nvidia-gb200',
+ description: 'Platform to init (Temporary)',
+)
diff --git a/platform.cpp b/platform.cpp
new file mode 100644
index 0000000..7a947cf
--- /dev/null
+++ b/platform.cpp
@@ -0,0 +1,464 @@
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-FileCopyrightText: 2025 NVIDIA
+
+#include <fcntl.h>
+#include <systemd/sd-daemon.h>
+
+#include <CLI/CLI.hpp>
+#include <gpiod.hpp>
+
+#include <chrono>
+#include <cstdint>
+#include <cstdlib>
+#include <cstring>
+#include <filesystem>
+#include <format>
+#include <fstream>
+#include <iostream>
+#include <thread>
+#include <unordered_map>
+
+using namespace std::chrono_literals;
+
+constexpr static const char* app_name = "platform_init";
+
+// Map of GPIO name to line. Holds lines open for the duration of the program
+static std::unordered_map<std::string, gpiod::line> io;
+
+void sleep_milliseconds(std::chrono::milliseconds milliseconds)
+{
+ std::cerr << std::format("Sleeping for {} milliseconds\n",
+ milliseconds.count());
+ std::this_thread::sleep_for(milliseconds);
+}
+
+void set_gpio(const char* line_name, int value,
+ std::chrono::milliseconds find_timeout = 0ms)
+{
+ std::cerr << std::format("{} Request to set to {}\n", line_name, value);
+ std::chrono::milliseconds polling_time = 10ms;
+ gpiod::line& line = io[line_name];
+ if (!line)
+ {
+ do
+ {
+ line = gpiod::find_line(line_name);
+ if (!line)
+ {
+ std::cerr << std::format(
+ "{} not found yet, waiting and retrying\n", line_name);
+
+ sleep_milliseconds(polling_time);
+ find_timeout -= polling_time;
+ }
+ } while (!line && find_timeout > 0s);
+ if (!line && find_timeout <= 0s)
+ {
+ std::cerr << std::format("{} Unable to find\n", line_name);
+ return;
+ }
+ try
+ {
+ line.request({app_name, gpiod::line_request::DIRECTION_OUTPUT, 0},
+ value);
+ }
+ catch (const std::system_error& e)
+ {
+ std::cerr << std::format(
+ "{} unable to set direction and value {}\n", line_name,
+ e.what());
+ return;
+ }
+ // No need to set if the init did it for us
+ std::cerr << std::format("{} Set to {}\n", line_name, value);
+ return;
+ }
+ std::cerr << std::format("{} Settingto {}\n", line_name, value);
+ line.set_value(value);
+}
+
+int get_gpio(const char* line_name)
+{
+ std::cerr << std::format("{} Request to get\n", line_name);
+
+ gpiod::line line = gpiod::find_line(line_name);
+ if (!line)
+ {
+ std::cerr << std::format("{} Set unable to find\n", line_name);
+ return -1;
+ }
+ try
+ {
+ line.request({app_name, gpiod::line_request::DIRECTION_INPUT, 0});
+ }
+ catch (const std::system_error& e)
+ {
+ std::cerr << std::format("{} unable to set {}\n", line_name, e.what());
+ }
+
+ int value = line.get_value();
+ std::cerr << std::format("{} was {}\n", line_name, value);
+ return value;
+}
+
+enum class GpioEventResult
+{
+ Error,
+ Asserted,
+ Timeout
+};
+
+struct GpioEvent
+{
+ GpioEvent(const char* line_name_in, int value_in) :
+ line_name(line_name_in), value(value_in)
+ {
+ line = gpiod::find_line(line_name);
+ if (!line)
+ {
+ std::cerr << std::format("{} GpioEvent: Unable to find\n",
+ line_name);
+ return;
+ }
+ int edge = (value != 0) ? ::gpiod::line_request::EVENT_RISING_EDGE
+ : ::gpiod::line_request::EVENT_FALLING_EDGE;
+
+ line.request({app_name, edge, 0});
+
+ int val = line.get_value();
+ if (val == value)
+ {
+ std::cerr << std::format("{} GpioEvent is already {}\n", line_name,
+ val);
+ }
+ else
+ {
+ std::cerr << std::format("GpioEvent created for {}\n", line_name);
+ }
+ }
+ GpioEventResult wait()
+ {
+ if (!line)
+ {
+ std::cerr << std::format("Line {} wasn't initialized\n", line_name);
+ return GpioEventResult::Error;
+ }
+ std::cerr << std::format("{} Waiting to go to {}\n", line_name,
+ (value != 0) ? "assert" : "deassert");
+ auto events = line.event_wait(std::chrono::seconds(120));
+ if (!events)
+ {
+ std::cerr << std::format("{} Timeout\n", line_name);
+ return GpioEventResult::Timeout;
+ }
+
+ std::cerr << std::format("{} Asserted\n", line_name);
+
+ return GpioEventResult::Asserted;
+ }
+
+ gpiod::line line;
+ std::string line_name;
+ int value;
+};
+
+void rebind_i2c(std::string number)
+{
+ std::string bindpath =
+ std::format("/sys/bus/platform/drivers/aspeed-i2c-bus/unbind", number);
+ std::ofstream bindofs(bindpath);
+ if (!bindofs)
+ {
+ std::cerr << std::format("{} unable to open\n", bindpath);
+ return;
+ }
+ try
+ {
+ bindofs << std::format("{}.i2c\n", number);
+ }
+ catch (const std::system_error& e)
+ {
+ std::cerr << std::format("{} unable to write\n", bindpath);
+ return;
+ }
+ bindofs.close();
+ std::cerr << std::format("{} unbound\n", number);
+
+ std::string unbindpath =
+ std::format("/sys/bus/platform/drivers/aspeed-i2c-bus/bind", number);
+ std::ofstream unbindofs(unbindpath);
+ if (!unbindofs)
+ {
+ std::cerr << std::format("{} unable to open\n", unbindpath);
+ return;
+ }
+ try
+ {
+ unbindofs << std::format("{}.i2c\n", number);
+ }
+ catch (const std::system_error& e)
+ {
+ std::cerr << std::format("{} unable to write\n", unbindpath);
+ return;
+ }
+ std::cerr << std::format("{} bound\n", number);
+}
+
+void set_gpio_raw(unsigned int chip_num, unsigned int bit_num, int value)
+{
+ std::string syspath = std::format("gpiochip{}", chip_num);
+ std::cerr << std::format("Setting gpiochip{} bit {} to {}\n", chip_num,
+ bit_num, value);
+ try
+ {
+ gpiod::chip chip(syspath);
+ gpiod::line line = chip.get_line(bit_num);
+ line.request({app_name, gpiod::line_request::DIRECTION_OUTPUT, 0},
+ value);
+ std::cerr << std::format("gpiochip{} bit {} set to {}\n", chip_num,
+ bit_num, value);
+ }
+ catch (const std::system_error& e)
+ {
+ std::cerr << std::format("Error setting gpiochip{} bit {}: {}\n",
+ chip_num, bit_num, e.what());
+ }
+}
+
+void new_device(unsigned int bus, unsigned int address,
+ std::string_view device_type)
+{
+ std::string path =
+ std::format("/sys/bus/i2c/devices/i2c-{}/new_device", bus);
+ std::cerr << std::format("attempting to open {}", path);
+ std::ofstream new_device(path);
+ if (!new_device)
+ {
+ std::cerr << "Error: Unable to create I2C device\n";
+ return;
+ }
+ new_device << std::format("{} 0x{:02x}", device_type, address);
+ new_device.close();
+
+ std::cerr << std::format("{} device created at bus {}", device_type, bus);
+}
+
+void wait_for_path_to_exist(std::string_view path,
+ std::chrono::milliseconds timeout)
+{
+ while (true)
+ {
+ std::error_code ec;
+ bool exists = std::filesystem::exists(path, ec);
+ if (exists)
+ {
+ return;
+ }
+ sleep_milliseconds(1ms);
+ timeout -= 1ms;
+ }
+ std::cerr << std::format("Failed to wait for {} to exist", path);
+}
+
+void init_p2020_gpu_card()
+{
+ std::cerr << "Initializing GPU card...\n";
+
+ // Init the P2020 gpio expander
+ new_device(14, 0x20, "pca6408");
+
+ // Wait for device to be created
+ const auto* device_path = "/sys/bus/i2c/devices/14-0020";
+ wait_for_path_to_exist(device_path, 1000ms);
+
+ // Find the GPIO chip number
+ std::string gpio_chip;
+ for (const auto& entry : std::filesystem::directory_iterator(device_path))
+ {
+ std::string path = entry.path().string();
+ if (path.find("gpiochip") != std::string::npos)
+ {
+ gpio_chip =
+ path.substr(path.find("gpiochip") + std::strlen("gpiochip"));
+ break;
+ }
+ }
+ if (gpio_chip.empty())
+ {
+ std::cerr << "Error: Could not find GPIO chip number\n";
+ return;
+ }
+
+ std::cerr << "Found GPIO chip: gpiochip" << gpio_chip << "\n";
+ unsigned int gpiochipint = 0;
+ std::from_chars_result r =
+ std::from_chars(&*gpio_chip.begin(), &*gpio_chip.end(), gpiochipint);
+ if (r.ec != std::error_code() || r.ptr != &*gpio_chip.end())
+ {
+ std::cout << "Failed to convert gpiochip\n";
+ return;
+ }
+
+ // Set MCU in recovery
+ set_gpio_raw(gpiochipint, 3, 1);
+
+ // Reset MCU
+ set_gpio_raw(gpiochipint, 4, 0);
+ set_gpio_raw(gpiochipint, 4, 1);
+
+ // Switch MUX to MCU
+ set_gpio_raw(gpiochipint, 5, 1);
+}
+
+bool hmc_is_present()
+{
+ std::error_code ec;
+ bool exists = std::filesystem::exists("/sys/bus/i2c/devices/9-0074", ec);
+ if (ec)
+ {
+ exists = false;
+ }
+ if (exists)
+ {
+ std::cerr << "HMC present in platform";
+ }
+ else
+ {
+ std::cerr << "HMC not present in platform";
+ }
+ return exists;
+}
+
+int init_nvidia_gb200(bool has_p2020)
+{
+ // Reset USB hubs
+ set_gpio("USB_HUB_RESET_L-O", 0, 10000ms);
+ bool hmc_present = hmc_is_present();
+ if (!hmc_present)
+ {
+ set_gpio("SEC_USB2_HUB_RST_L-O", 0, 10000ms);
+ }
+
+ sleep_milliseconds(100ms);
+ if (!hmc_present)
+ {
+ set_gpio("SEC_USB2_HUB_RST_L-O", 1);
+ }
+ // Write SGPIO_BMC_EN-O=1 to correctly set mux to send SGPIO signals to
+ // FPGA
+ set_gpio("SGPIO_BMC_EN-O", 1);
+
+ // Write the bit for BMC without HMC
+ set_gpio("HMC_BMC_DETECT-O", static_cast<int>(!hmc_present), 30000ms);
+
+ // Set BMC_EROT_FPGA_SPI_MUX_SEL-O = 1 to enable FPGA to access its EROT
+ set_gpio("BMC_EROT_FPGA_SPI_MUX_SEL-O", 1);
+
+ // Enable 12V
+ set_gpio("BMC_12V_CTRL-O", 1, 10000ms);
+
+ set_gpio("PWR_BRAKE_L-O", 1);
+ set_gpio("SHDN_REQ_L-O", 1);
+ set_gpio("SHDN_FORCE_L-O", 1);
+ // Hold in reset (asserted) after standby power enabled
+ set_gpio("SYS_RST_IN_L-O", 0);
+
+ GpioEvent fpga_ready_wait = GpioEvent("FPGA_READY_BMC-I", 1);
+ GpioEvent sec_erot_fpga_rst = GpioEvent("SEC_FPGA_READY_BMC-I", 1);
+
+ // Release FPGA EROT from reset
+ set_gpio("EROT_FPGA_RST_L-O", 1);
+ set_gpio("SEC_EROT_FPGA_RST_L-O", 1);
+
+ sleep_milliseconds(100ms);
+
+ set_gpio("FPGA_RST_L-O", 1);
+
+ if (fpga_ready_wait.wait() != GpioEventResult::Asserted)
+ {
+ std::cerr << "FPGA_READY_BMC-I failed to assert\n";
+ // return EXIT_FAILURE;
+ }
+
+ if (sec_erot_fpga_rst.wait() != GpioEventResult::Asserted)
+ {
+ std::cerr << "SEC_FPGA_READY_BMC-I failed to assert\n";
+ // return EXIT_FAILURE;
+ }
+
+ // ReInitialize the FPGA connected I2C buses to unstick them and let
+ // FruDevice know it can scan for FRUs I2c bus 1
+ rebind_i2c("1e78a100");
+ // I2c bus 2
+ rebind_i2c("1e78a180");
+
+ // Set sgpio signals
+ set_gpio("RUN_POWER_EN-O", 1);
+ set_gpio("SYS_RST_IN_L-O", 1);
+ set_gpio("GLOBAL_WP_BMC-O", 0);
+
+ set_gpio("BMC_READY-O", 1);
+
+ if (has_p2020)
+ {
+ init_p2020_gpu_card();
+ }
+
+ set_gpio("USB_HUB_RESET_L-O", 1);
+ if (!hmc_present)
+ {
+ set_gpio("SEC_USB2_HUB_RST_L-O", 1);
+ }
+
+ sd_notify(0, "READY=1");
+ std::cerr << "Platform init complete\n";
+ pause();
+ std::cerr << "Releasing platform\n";
+
+ return EXIT_SUCCESS;
+}
+
+int init_nvidia_gb200_base()
+{
+ return init_nvidia_gb200(false);
+}
+
+int init_nvidia_gb200_with_p2020()
+{
+ return init_nvidia_gb200(true);
+}
+
+constexpr std::array<std::pair<std::string_view, int (*)()>, 2> init_functions{
+ {{"nvidia-gb200", init_nvidia_gb200_base},
+ {"nvidia-gb200-with-p2020", init_nvidia_gb200_with_p2020}}};
+
+int main(int argc, char** argv)
+{
+ CLI::App app("Platform init CLI");
+
+ app.require_subcommand();
+
+ CLI::App* init_sub =
+ app.add_subcommand("init", "Initialize the platform and daemonize");
+ std::string platform_name;
+ init_sub
+ ->add_option("platform_name", platform_name,
+ "Name of the platform to init")
+ ->required();
+ app.require_subcommand();
+
+ CLI11_PARSE(app, argc, argv)
+
+ const auto* it = std::ranges::find_if(
+ init_functions,
+ [&platform_name](const std::pair<std::string_view, int (*)()> val) {
+ return val.first == platform_name;
+ });
+ if (it == init_functions.end())
+ {
+ std::cerr << init_sub->help() << "\n";
+ return EXIT_FAILURE;
+ }
+
+ return it->second();
+}
diff --git a/platform_init.service.in b/platform_init.service.in
new file mode 100644
index 0000000..5c6d77f
--- /dev/null
+++ b/platform_init.service.in
@@ -0,0 +1,12 @@
+[Unit]
+Description=Start up the platform hardware
+Before=xyz.openbmc_project.FruDevice.service
+
+[Service]
+Type=notify
+ExecStart=/usr/libexec/platform init @PLATFORM_NAME@
+SyslogIdentifier=platform-init
+TimeoutStartSec=5000
+
+[Install]
+WantedBy=multi-user.target
diff --git a/subprojects/cli11.wrap b/subprojects/cli11.wrap
new file mode 100644
index 0000000..dd26d59
--- /dev/null
+++ b/subprojects/cli11.wrap
@@ -0,0 +1,10 @@
+[wrap-file]
+directory = CLI11-2.4.1
+source_url = https://github.com/CLIUtils/CLI11/archive/refs/tags/v2.4.1.tar.gz
+source_filename = CLI11-2.4.1.tar.gz
+source_hash = 73b7ec52261ce8fe980a29df6b4ceb66243bb0b779451dbd3d014cfec9fdbb58
+source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/cli11_2.4.1-1/CLI11-2.4.1.tar.gz
+wrapdb_version = 2.4.1-1
+
+[provide]
+cli11 = CLI11_dep
diff --git a/subprojects/libgpiod.wrap b/subprojects/libgpiod.wrap
new file mode 100644
index 0000000..e85aa49
--- /dev/null
+++ b/subprojects/libgpiod.wrap
@@ -0,0 +1,12 @@
+[wrap-file]
+directory = libgpiod-1.6.3
+source_url = https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/snapshot/libgpiod-1.6.3.tar.gz
+source_filename = libgpiod-1.6.3.tar.gz
+source_hash = eb446070be1444fd7d32d32bbca53c2f3bbb0a21193db86198cf6050b7a28441
+patch_filename = libgpiod_1.6.3-1_patch.zip
+patch_url = https://wrapdb.mesonbuild.com/v2/libgpiod_1.6.3-1/get_patch
+patch_hash = 76821c637073679a88f77593c6f7ce65b4b5abf8c998f823fffa13918c8761df
+
+[provide]
+libgpiod = gpiod_dep
+libgpiodcxx = gpiodcxx_dep