Implement tlbmc led module PiperOrigin-RevId: 970236748 Change-Id: Ife28857d25a31468fbd0465d5259e80d6fe4d6dc
diff --git a/copy.bara.sky b/copy.bara.sky index 53c11f6..7046332 100644 --- a/copy.bara.sky +++ b/copy.bara.sky
@@ -465,6 +465,8 @@ "google3/third_party/milotic/external/cc/tlbmc/collector/fru_collector.h", "google3/third_party/milotic/external/cc/tlbmc/collector/gpio_collector.cc", "google3/third_party/milotic/external/cc/tlbmc/collector/gpio_collector.h", + "google3/third_party/milotic/external/cc/tlbmc/collector/led_collector.cc", + "google3/third_party/milotic/external/cc/tlbmc/collector/led_collector.h", "google3/third_party/milotic/external/cc/tlbmc/collector/metric_collector.cc", "google3/third_party/milotic/external/cc/tlbmc/collector/metric_collector.h", "google3/third_party/milotic/external/cc/tlbmc/collector/monitoring_change_base.h", @@ -507,6 +509,7 @@ "google3/third_party/milotic/external/cc/tlbmc/configs/hal_common_config.proto", "google3/third_party/milotic/external/cc/tlbmc/configs/hwmon_temp_sensor_config.proto", "google3/third_party/milotic/external/cc/tlbmc/configs/intel_cpu_sensor_config.proto", + "google3/third_party/milotic/external/cc/tlbmc/configs/led_config.proto", "google3/third_party/milotic/external/cc/tlbmc/configs/nic_telemetry_config.proto", "google3/third_party/milotic/external/cc/tlbmc/configs/pcie_config.proto", "google3/third_party/milotic/external/cc/tlbmc/configs/power_control.proto", @@ -609,6 +612,10 @@ "google3/third_party/milotic/external/cc/tlbmc/hal/gpio/gpio_monitor.h", "google3/third_party/milotic/external/cc/tlbmc/hal/gpio/on_demand_gpio.cc", "google3/third_party/milotic/external/cc/tlbmc/hal/gpio/on_demand_gpio.h", + "google3/third_party/milotic/external/cc/tlbmc/hal/led/led.cc", + "google3/third_party/milotic/external/cc/tlbmc/hal/led/led.h", + "google3/third_party/milotic/external/cc/tlbmc/hal/led/led_sysfs.cc", + "google3/third_party/milotic/external/cc/tlbmc/hal/led/led_sysfs.h", "google3/third_party/milotic/external/cc/tlbmc/hal/nic_veeprom/accessor_base.h", "google3/third_party/milotic/external/cc/tlbmc/hal/nic_veeprom/interface.cc", "google3/third_party/milotic/external/cc/tlbmc/hal/nic_veeprom/interface.h", @@ -645,6 +652,8 @@ "google3/third_party/milotic/external/cc/tlbmc/hal/sysfs/i3c.h", "google3/third_party/milotic/external/cc/tlbmc/hal/sysfs/iio.cc", "google3/third_party/milotic/external/cc/tlbmc/hal/sysfs/iio.h", + "google3/third_party/milotic/external/cc/tlbmc/hal/sysfs/led.cc", + "google3/third_party/milotic/external/cc/tlbmc/hal/sysfs/led.h", "google3/third_party/milotic/external/cc/tlbmc/hal/sysfs/peci.cc", "google3/third_party/milotic/external/cc/tlbmc/hal/sysfs/peci.h", "google3/third_party/milotic/external/cc/tlbmc/hal/system_registry.h", @@ -930,6 +939,8 @@ "tlbmc/collector/fru_collector.h", "tlbmc/collector/gpio_collector.cc", "tlbmc/collector/gpio_collector.h", + "tlbmc/collector/led_collector.cc", + "tlbmc/collector/led_collector.h", "tlbmc/collector/metric_collector.cc", "tlbmc/collector/metric_collector.h", "tlbmc/collector/monitoring_change_base.h", @@ -1006,6 +1017,10 @@ "tlbmc/hal/gpio/gpio_monitor.h", "tlbmc/hal/gpio/on_demand_gpio.cc", "tlbmc/hal/gpio/on_demand_gpio.h", + "tlbmc/hal/led/led.cc", + "tlbmc/hal/led/led.h", + "tlbmc/hal/led/led_sysfs.cc", + "tlbmc/hal/led/led_sysfs.h", "tlbmc/hal/nic_veeprom/accessor_base.h", "tlbmc/hal/nic_veeprom/interface.cc", "tlbmc/hal/nic_veeprom/interface.h", @@ -1038,6 +1053,8 @@ "tlbmc/hal/sysfs/i3c.h", "tlbmc/hal/sysfs/iio.cc", "tlbmc/hal/sysfs/iio.h", + "tlbmc/hal/sysfs/led.cc", + "tlbmc/hal/sysfs/led.h", "tlbmc/hal/sysfs/peci.cc", "tlbmc/hal/sysfs/peci.h", "tlbmc/hal_common_config.proto", @@ -1061,6 +1078,7 @@ "tlbmc/hwmon_temp_sensor_config.proto", "tlbmc/identifier.proto", "tlbmc/intel_cpu_sensor_config.proto", + "tlbmc/led_config.proto", "tlbmc/memory.proto", "tlbmc/meson.build", "tlbmc/metrics/bmc_static_metrics.cc", @@ -1282,6 +1300,8 @@ "google3/third_party/milotic/external/cc/tlbmc/collector/fru_collector.h", "google3/third_party/milotic/external/cc/tlbmc/collector/gpio_collector.cc", "google3/third_party/milotic/external/cc/tlbmc/collector/gpio_collector.h", + "google3/third_party/milotic/external/cc/tlbmc/collector/led_collector.cc", + "google3/third_party/milotic/external/cc/tlbmc/collector/led_collector.h", "google3/third_party/milotic/external/cc/tlbmc/collector/metric_collector.cc", "google3/third_party/milotic/external/cc/tlbmc/collector/metric_collector.h", "google3/third_party/milotic/external/cc/tlbmc/collector/monitoring_change_base.h", @@ -1318,6 +1338,7 @@ "google3/third_party/milotic/external/cc/tlbmc/configs/hal_common_config.proto", "google3/third_party/milotic/external/cc/tlbmc/configs/hwmon_temp_sensor_config.proto", "google3/third_party/milotic/external/cc/tlbmc/configs/intel_cpu_sensor_config.proto", + "google3/third_party/milotic/external/cc/tlbmc/configs/led_config.proto", "google3/third_party/milotic/external/cc/tlbmc/configs/nic_telemetry_config.proto", "google3/third_party/milotic/external/cc/tlbmc/configs/pcie_config.proto", "google3/third_party/milotic/external/cc/tlbmc/configs/power_control.proto", @@ -1371,6 +1392,10 @@ "google3/third_party/milotic/external/cc/tlbmc/hal/gpio/gpio_monitor.h", "google3/third_party/milotic/external/cc/tlbmc/hal/gpio/on_demand_gpio.cc", "google3/third_party/milotic/external/cc/tlbmc/hal/gpio/on_demand_gpio.h", + "google3/third_party/milotic/external/cc/tlbmc/hal/led/led.cc", + "google3/third_party/milotic/external/cc/tlbmc/hal/led/led.h", + "google3/third_party/milotic/external/cc/tlbmc/hal/led/led_sysfs.cc", + "google3/third_party/milotic/external/cc/tlbmc/hal/led/led_sysfs.h", "google3/third_party/milotic/external/cc/tlbmc/hal/nic_veeprom/accessor_base.h", "google3/third_party/milotic/external/cc/tlbmc/hal/nic_veeprom/interface.cc", "google3/third_party/milotic/external/cc/tlbmc/hal/nic_veeprom/interface.h", @@ -1406,6 +1431,8 @@ "google3/third_party/milotic/external/cc/tlbmc/hal/sysfs/i3c.h", "google3/third_party/milotic/external/cc/tlbmc/hal/sysfs/iio.cc", "google3/third_party/milotic/external/cc/tlbmc/hal/sysfs/iio.h", + "google3/third_party/milotic/external/cc/tlbmc/hal/sysfs/led.cc", + "google3/third_party/milotic/external/cc/tlbmc/hal/sysfs/led.h", "google3/third_party/milotic/external/cc/tlbmc/hal/sysfs/peci.cc", "google3/third_party/milotic/external/cc/tlbmc/hal/sysfs/peci.h", "google3/third_party/milotic/external/cc/tlbmc/hft/hft_service.cc", @@ -2655,6 +2682,7 @@ "tlbmc/configs/gpio_sensor_config.proto.h": "gpio_sensor_config.pb.h", "tlbmc/configs/thermal_config.proto.h": "thermal_config.pb.h", "tlbmc/configs/thermal_controller_specification.proto.h": "thermal_controller_specification.pb.h", + "tlbmc/configs/led_config.proto.h": "led_config.pb.h", "tlbmc/types/payload.proto.h": "payload.pb.h", "tlbmc/types/sensor_payload.proto.h": "sensor_payload.pb.h", "tlbmc/types/subscription_params.proto.h": "subscription_params.pb.h", @@ -3592,6 +3620,7 @@ "gpio_fru_config.pb.h": "third_party/milotic/external/cc/tlbmc/configs/gpio_fru_config.proto.h", "usb_fru_config.pb.h": "third_party/milotic/external/cc/tlbmc/configs/usb_fru_config.proto.h", "gpio_sensor_config.pb.h": "third_party/milotic/external/cc/tlbmc/configs/gpio_sensor_config.proto.h", + "led_config.pb.h": "third_party/milotic/external/cc/tlbmc/configs/led_config.proto.h", "hal_common_config.pb.h": "third_party/milotic/external/cc/tlbmc/configs/hal_common_config.proto.h", "hft_capabilities.pb.h": "third_party/milotic/external/cc/hft/service/hft_capabilities.proto.h", "hft_service.pb.h": "third_party/milotic/external/cc/hft/service/hft_service.proto.h",
diff --git a/g3/server.cc b/g3/server.cc index f4e3489..f1fad79 100644 --- a/g3/server.cc +++ b/g3/server.cc
@@ -27,6 +27,7 @@ #include <vector> #include "tlbmc/credentials/credential_manager.h" +#include "tlbmc/hal/sysfs/led.h" #pragma GCC diagnostic push #pragma GCC diagnostic warning \ @@ -1130,6 +1131,7 @@ using ::milotic_tlbmc::I3cSysfs; using ::milotic_tlbmc::IioSysfs; using ::milotic_tlbmc::InitializeTlbmcConfig; + using ::milotic_tlbmc::LedSysfs; using ::milotic_tlbmc::PeciSysfs; using ::milotic_tlbmc::RateLimiterConfig; using ::milotic_tlbmc::RedfishApp; @@ -1420,6 +1422,8 @@ .i2c_sysfs_path = config.tlbmc_i2c_sysfs_path}); options.i3c_sysfs = std::make_unique<I3cSysfs>(milotic_tlbmc::I3cSysfsConfig{ .i3c_sysfs_path = config.tlbmc_i3c_sysfs_path}); + options.led_sysfs = std::make_unique<LedSysfs>(milotic_tlbmc::LedSysfsConfig{ + .led_sysfs_path = config.tlbmc_led_sysfs_path}); options.peci_sysfs = std::make_unique<PeciSysfs>(milotic_tlbmc::PeciSysfsConfig{ .peci_sysfs_path = config.tlbmc_peci_sysfs_path});
diff --git a/g3/server.h b/g3/server.h index 4fe2305..35840c0 100644 --- a/g3/server.h +++ b/g3/server.h
@@ -92,6 +92,7 @@ std::string tlbmc_iio_sysfs_path = "/sys/bus/iio/devices/"; std::string tlbmc_i2c_sysfs_path = "/sys/bus/i2c/devices/"; std::string tlbmc_i3c_sysfs_path = "/sys/bus/i3c/devices/"; + std::string tlbmc_led_sysfs_path = "/sys/class/leds/"; std::string tlbmc_peci_sysfs_path = "/sys/bus/peci"; std::string tlbmc_root_dir = "/";
diff --git a/tlbmc/central_config.proto b/tlbmc/central_config.proto index f9d86da..eb88e02 100644 --- a/tlbmc/central_config.proto +++ b/tlbmc/central_config.proto
@@ -226,6 +226,11 @@ bool enabled = 1 [default = false]; } +// Controls the LED collector module. +message LedCollectorModule { + bool enabled = 1 [default = false]; +} + // A proto message to hold all configurations of the modules in tlbmc. message TlbmcConfig { string platform_name = 8; @@ -240,6 +245,7 @@ PowerFaultLogCollectorModule power_fault_log_collector_module = 9; SelCollectorModule sel_collector_module = 10; PcieCollectorModule pcie_collector_module = 11; + LedCollectorModule led_collector_module = 14; // If true, this platform uses the TLBMC Lite configuration as a base. bool use_tlbmc_lite_config = 12;
diff --git a/tlbmc/collector/led_collector.cc b/tlbmc/collector/led_collector.cc new file mode 100644 index 0000000..9106f96 --- /dev/null +++ b/tlbmc/collector/led_collector.cc
@@ -0,0 +1,167 @@ +#include "tlbmc/collector/led_collector.h" + +#include <memory> +#include <string> +#include <utility> +#include <vector> + +#include "absl/container/flat_hash_map.h" +#include "absl/log/log.h" +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" +#include "absl/time/time.h" +#include "g3/macros.h" +#include <nlohmann/json.hpp> +#include "led_config.pb.h" +#include "tlbmc/hal/led/led.h" +#include "tlbmc/hal/led/led_sysfs.h" +#include "tlbmc/scheduler/scheduler.h" + +namespace milotic_tlbmc { + +absl::StatusOr<std::unique_ptr<LedCollector>> LedCollector::Create( + const Params& params) { + auto task_scheduler = std::make_unique<TaskScheduler>(params.clock); + + absl::flat_hash_map<std::string, std::unique_ptr<Led>> leds; + for (const LedConfig& config : params.led_configs.leds()) { + auto [it, inserted] = leds.try_emplace(config.name(), nullptr); + if (!inserted) { + return absl::AlreadyExistsError( + absl::StrCat("Duplicate LED name: ", config.name())); + } + + switch (config.output().target_case()) { + case LedOutput::TargetCase::kSysfsName: { + ECCLESIA_ASSIGN_OR_RETURN(std::unique_ptr<Led> led, + SysfsLed::Create(config, params.led_sysfs)); + it->second = std::move(led); + break; + } + // TODO b/541951164 - Add GPIO support. + default: + return absl::InvalidArgumentError( + absl::StrCat("Unsupported output type for LED ", config.name())); + } + + ECCLESIA_RETURN_IF_ERROR( + it->second->SetToDefaultBehavior(params.clock->Now())); + } + + absl::flat_hash_map<std::string, std::vector<LedPresetBehavior>> presets; + for (const LedPreset& preset : params.led_configs.presets()) { + if (preset.name().empty()) { + return absl::InvalidArgumentError("Preset name cannot be empty"); + } + std::vector<LedPresetBehavior> preset_behaviors; + preset_behaviors.reserve(preset.members_size()); + for (const LedPresetMember& member : preset.members()) { + auto led_it = leds.find(member.name()); + if (led_it == leds.end()) { + return absl::NotFoundError( + absl::StrCat("LED in preset not found: ", member.name())); + } + preset_behaviors.push_back({*(led_it->second), member.behavior()}); + } + auto [_, inserted] = + presets.try_emplace(preset.name(), std::move(preset_behaviors)); + if (!inserted) { + return absl::AlreadyExistsError( + absl::StrCat("Duplicate preset name: ", preset.name())); + } + } + + return std::unique_ptr<LedCollector>(new LedCollector( + std::move(task_scheduler), std::move(leds), std::move(presets))); +} + +absl::Status LedCollector::SetLedBehavior(absl::string_view led_name, + const LedBehavior& behavior, + absl::Time timestamp) { + auto it = leds_.find(led_name); + if (it == leds_.end()) { + return absl::NotFoundError(absl::StrCat("LED not found: ", led_name)); + } + return it->second->SetBehavior(behavior, timestamp); +} + +absl::StatusOr<LedBehavior> LedCollector::GetLedBehavior( + absl::string_view led_name) const { + auto it = leds_.find(led_name); + if (it == leds_.end()) { + return absl::NotFoundError(absl::StrCat("LED not found: ", led_name)); + } + return it->second->GetBehavior(); +} + +absl::Status LedCollector::ApplyPreset(absl::string_view preset_name, + absl::Time timestamp) { + auto it = presets_.find(preset_name); + if (it == presets_.end()) { + return absl::NotFoundError(absl::StrCat("Preset not found: ", preset_name)); + } + absl::Status overall_status = absl::OkStatus(); + for (const LedPresetBehavior& member : it->second) { + overall_status.Update(member.led.SetBehavior(member.behavior, timestamp)); + } + return overall_status; +} + +std::vector<std::string> LedCollector::GetLedNames() const { + std::vector<std::string> names; + names.reserve(leds_.size()); + for (const auto& [name, _] : leds_) { + names.push_back(name); + } + return names; +} + +std::vector<std::string> LedCollector::GetPresetNames() const { + std::vector<std::string> names; + names.reserve(presets_.size()); + for (const auto& [name, _] : presets_) { + names.push_back(name); + } + return names; +} + +nlohmann::json LedCollector::ToJson() const { + nlohmann::json response; + + nlohmann::json leds = nlohmann::json::object(); + for (const auto& [name, led] : leds_) { + leds[name] = led->ToJson(); + } + response["leds"] = leds; + + nlohmann::json presets = nlohmann::json::object(); + for (const auto& [name, preset] : presets_) { + nlohmann::json preset_json = nlohmann::json::array(); + for (const LedPresetBehavior& member : preset) { + preset_json.push_back({ + {"name", member.led.GetName()}, + {"behavior", Led::BehaviorToJson(member.behavior)}, + }); + } + presets[name] = preset_json; + } + response["presets"] = presets; + + return response; +} + +nlohmann::json EmptyLedCollector::GetSchedulerStats() const { + return nlohmann::json::parse("{\"Warning\": \"EmptyLedCollector used.\"}"); +} + +nlohmann::json EmptyLedCollector::ToJson() const { + return nlohmann::json::parse("{\"Warning\": \"EmptyLedCollector used.\"}"); +} + +std::unique_ptr<EmptyLedCollector> EmptyLedCollector::Create() { + return std::make_unique<EmptyLedCollector>(); +} + +} // namespace milotic_tlbmc
diff --git a/tlbmc/collector/led_collector.h b/tlbmc/collector/led_collector.h new file mode 100644 index 0000000..8a1050b --- /dev/null +++ b/tlbmc/collector/led_collector.h
@@ -0,0 +1,111 @@ +#ifndef THIRD_PARTY_MILOTIC_EXTERNAL_CC_TLBMC_COLLECTOR_LED_COLLECTOR_H_ +#define THIRD_PARTY_MILOTIC_EXTERNAL_CC_TLBMC_COLLECTOR_LED_COLLECTOR_H_ + +#include <memory> +#include <string> +#include <utility> +#include <vector> + +#include "absl/container/flat_hash_map.h" +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/string_view.h" +#include "absl/time/time.h" +#include "time/clock.h" +#include <nlohmann/json.hpp> +#include "tlbmc/collector/collector.h" +#include "led_config.pb.h" +#include "tlbmc/hal/led/led.h" +#include "tlbmc/hal/sysfs/led.h" +#include "tlbmc/scheduler/scheduler.h" + +namespace milotic_tlbmc { + +struct LedPresetBehavior { + Led& led; + LedBehavior behavior; +}; + +// Collector for managing LEDs and applying presets in tlBMC. +// +// The collector owns a set of generic Led (see hal/led/led.h), keyed by their +// configured name, and a set of named presets. LEDs are created via +// hardware-backend factory methods (such as SysfsLed::Create()). +// The collector is agnostic to the underlying hardware. +class LedCollector : public Collector { + public: + struct Params { + LedConfigs led_configs; + const LedSysfs& led_sysfs; + ecclesia::Clock* clock = ecclesia::Clock::RealClock(); + }; + + static absl::StatusOr<std::unique_ptr<LedCollector>> Create( + const Params& params); + + ~LedCollector() override { + // Destroy the scheduler before the LED handles so any in-flight blink tasks + // are cancelled while the LEDs they reference are still alive. + task_scheduler_.reset(); + } + + nlohmann::json ToJson() const override; + nlohmann::json GetSchedulerStats() const override { + return task_scheduler_->ToJson(); + } + + // Returns the current behavior of a specific LED by name. + virtual absl::StatusOr<LedBehavior> GetLedBehavior( + absl::string_view led_name) const; + + // Returns all LED names managed by this collector. + virtual std::vector<std::string> GetLedNames() const; + + // Returns all preset names managed by this collector. + virtual std::vector<std::string> GetPresetNames() const; + + protected: + LedCollector() = default; + + // Sets the behavior of a specific LED by name. + virtual absl::Status SetLedBehavior(absl::string_view led_name, + const LedBehavior& behavior, + absl::Time timestamp); + + // Applies a preset (sets the behavior for all member LEDs in the preset). + virtual absl::Status ApplyPreset(absl::string_view preset_name, + absl::Time timestamp); + + private: + LedCollector( + std::unique_ptr<TaskScheduler> task_scheduler, + absl::flat_hash_map<std::string, std::unique_ptr<Led>> leds, + absl::flat_hash_map<std::string, std::vector<LedPresetBehavior>> presets) + : leds_(std::move(leds)), + presets_(std::move(presets)), + task_scheduler_(std::move(task_scheduler)) {} + + const absl::flat_hash_map<std::string, std::unique_ptr<Led>> leds_; + const absl::flat_hash_map<std::string, std::vector<LedPresetBehavior>> + presets_; + + // Reserved for future GPIO-backed LEDs that blink in software. Sysfs LEDs + // blink via the kernel timer trigger and do not use the scheduler. + // TaskScheduler must declared last so it is destroyed first. + std::unique_ptr<TaskScheduler> task_scheduler_; + + friend class LedCollectorTest; +}; + +class EmptyLedCollector final : public LedCollector { + public: + static std::unique_ptr<EmptyLedCollector> Create(); + + nlohmann::json GetSchedulerStats() const override; + + nlohmann::json ToJson() const override; +}; + +} // namespace milotic_tlbmc + +#endif // THIRD_PARTY_MILOTIC_EXTERNAL_CC_TLBMC_COLLECTOR_LED_COLLECTOR_H_
diff --git a/tlbmc/configs/proto_config_parser.cc b/tlbmc/configs/proto_config_parser.cc index d1af44f..5516833 100644 --- a/tlbmc/configs/proto_config_parser.cc +++ b/tlbmc/configs/proto_config_parser.cc
@@ -10,6 +10,7 @@ #include "absl/strings/string_view.h" #include "gpio_config.pb.h" #include "gpio_fru_config.pb.h" +#include "led_config.pb.h" #include "pcie_config.pb.h" #include "power_control.pb.h" #include "power_fault_log_config.pb.h" @@ -72,6 +73,8 @@ data_store_.usb_fru_component_configs = GetConfigFromProto<UsbFruComponentConfigs>( absl::StrCat(config_path_, "usb_fru_config.textproto")); + data_store_.led_configs = GetConfigFromProto<LedConfigs>( + absl::StrCat(config_path_, "led_config.textproto")); } SoftwareMetricsConfig ProtoConfigParser::GetSoftwareMetricsConfig() const { @@ -110,6 +113,10 @@ return data_store_.usb_fru_component_configs; } +LedConfigs ProtoConfigParser::GetLedConfigs() const { + return data_store_.led_configs; +} + std::unique_ptr<ProtoConfigParser> ProtoConfigParser::Create( absl::string_view config_path) { return std::make_unique<ProtoConfigParser>(config_path);
diff --git a/tlbmc/configs/proto_config_parser.h b/tlbmc/configs/proto_config_parser.h index d480fb7..1d61b6c 100644 --- a/tlbmc/configs/proto_config_parser.h +++ b/tlbmc/configs/proto_config_parser.h
@@ -9,6 +9,7 @@ #include "absl/strings/string_view.h" #include "gpio_config.pb.h" #include "gpio_fru_config.pb.h" +#include "led_config.pb.h" #include "pcie_config.pb.h" #include "power_control.pb.h" #include "power_fault_log_config.pb.h" @@ -29,6 +30,7 @@ PcieConfigs pcie_configs; GpioFruComponentConfigs gpio_fru_component_configs; UsbFruComponentConfigs usb_fru_component_configs; + LedConfigs led_configs; }; class ProtoConfigParser { @@ -46,6 +48,7 @@ PcieConfigs GetPcieConfigs() const; GpioFruComponentConfigs GetGpioFruComponentConfigs() const; UsbFruComponentConfigs GetUsbFruComponentConfigs() const; + LedConfigs GetLedConfigs() const; explicit ProtoConfigParser(absl::string_view config_path) : config_path_(config_path) {}
diff --git a/tlbmc/g3doc/modules/collectors.md b/tlbmc/g3doc/modules/collectors.md index 1d3339d..7d470ef 100644 --- a/tlbmc/g3doc/modules/collectors.md +++ b/tlbmc/g3doc/modules/collectors.md
@@ -93,6 +93,12 @@ : : : : aggregates : : : : : link : : : : : telemetry. : +| **LED | `led_collector_module` | `false` | Manages | +: Collector** : : : physical LEDs : +: : : : (sysfs/GPIO) : +: : : : and applies : +: : : : LED behavior : +: : : : presets. : -------------------------------------------------------------------------------- @@ -174,6 +180,14 @@ - Evaluates slot stable IDs against physical Redfish locations. - Monitors presence via GPIO pins and aggregates link telemetry. +### 10. [LED Collector](/third_party/milotic/external/cc/tlbmc/g3doc/components/collectors/led_collector.md) (`led_collector.cc`) + +Manages physical LEDs and applies preset behaviors: + +- Controls hardware LED outputs (sysfs, GPIO) and states (ON, OFF, BLINK). +- Applies named LED presets (groupings of LED behaviors) across managed + indicators. + ## Protobuf Configuration Schemas The collectors run background tasks that map hardware state to local memory @@ -207,6 +221,10 @@ [`thermal_config.proto`](http://google3/third_party/milotic/external/cc/tlbmc/configs/thermal_config.proto)): Defines multi-zone thermal PID coefficients and fan stepwise management rules. +- **`LedConfig`** / **`LedPreset`** (defined in + [`led_config.proto`](http://google3/third_party/milotic/external/cc/tlbmc/configs/led_config.proto)): + Defines LED hardware output interfaces (sysfs/GPIO), default behaviors, and + named preset mappings. -------------------------------------------------------------------------------- @@ -236,6 +254,9 @@ sel_collector_module { enabled: true } + led_collector_module { + enabled: true + } } ``` @@ -255,6 +276,7 @@ - [`power_fault_log_entry.proto`](http://google3/third_party/milotic/external/cc/tlbmc/resource/power_fault_log_entry.proto) - [`sse_event.proto`](http://google3/third_party/milotic/external/cc/tlbmc/resource/sse_event.proto) - [`thermal_config.proto`](http://google3/third_party/milotic/external/cc/tlbmc/configs/thermal_config.proto) +- [`led_config.proto`](http://google3/third_party/milotic/external/cc/tlbmc/configs/led_config.proto) ### Core Code @@ -268,3 +290,4 @@ - [`sel_collector.h`](http://google3/third_party/milotic/external/cc/tlbmc/collector/sel_collector.h) - [`pcie_collector.h`](http://google3/third_party/milotic/external/cc/tlbmc/collector/pcie_collector.h) - [`thermal_collector.h`](http://google3/third_party/milotic/external/cc/tlbmc/collector/thermal_collector.h) +- [`led_collector.h`](http://google3/third_party/milotic/external/cc/tlbmc/collector/led_collector.h)
diff --git a/tlbmc/hal/led/led.cc b/tlbmc/hal/led/led.cc new file mode 100644 index 0000000..34e3af4 --- /dev/null +++ b/tlbmc/hal/led/led.cc
@@ -0,0 +1,73 @@ +#include "tlbmc/hal/led/led.h" + +#include <optional> +#include <string> + +#include "absl/log/log.h" +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/synchronization/mutex.h" +#include "absl/time/time.h" +#include "g3/macros.h" +#include <nlohmann/json.hpp> +#include "led_config.pb.h" +#include "google/protobuf/json/json.h" +#include "google/protobuf/util/json_util.h" + +namespace milotic_tlbmc { + +nlohmann::json Led::BehaviorToJson(const LedBehavior& behavior) { + std::string json_string; + ::google::protobuf::util::JsonPrintOptions opts; + opts.preserve_proto_field_names = true; + if (::google::protobuf::json::MessageToJsonString(behavior, &json_string, opts).ok()) { + return nlohmann::json::parse(json_string, nullptr, false); + } + LOG(ERROR) << "Failed to convert behavior to JSON: " << behavior; + return nullptr; +} + +absl::Status Led::SetToDefaultBehavior(absl::Time timestamp) { + if (!default_behavior_.has_value() || + default_behavior_->state() == LED_STATE_UNSPECIFIED) { + return absl::OkStatus(); + } + return SetBehavior(*default_behavior_, timestamp); +} + +absl::Status Led::SetBehavior(const LedBehavior& behavior, + absl::Time timestamp) { + absl::MutexLock lock(mutex_); + if (last_update_time_.has_value() && timestamp <= *last_update_time_) { + return absl::OkStatus(); + } + ECCLESIA_RETURN_IF_ERROR(SetBehaviorImpl(behavior)); + last_update_time_ = timestamp; + return absl::OkStatus(); +} + +nlohmann::json Led::ToJson() const { + nlohmann::json led_json; + std::string name = GetName(); + led_json["name"] = name; + if (default_behavior_.has_value()) { + led_json["default_behavior"] = BehaviorToJson(*default_behavior_); + } + { + absl::MutexLock lock(mutex_); + absl::StatusOr<LedBehavior> behavior = GetBehaviorImpl(); + if (behavior.ok()) { + led_json["behavior"] = BehaviorToJson(*behavior); + } else { + LOG(ERROR) << "Failed to get behavior for LED " << name << " : " + << behavior.status(); + led_json["behavior"] = nullptr; + } + if (last_update_time_.has_value()) { + led_json["last_update_time"] = absl::FormatTime(*last_update_time_); + } + } + return led_json; +} + +} // namespace milotic_tlbmc
diff --git a/tlbmc/hal/led/led.h b/tlbmc/hal/led/led.h new file mode 100644 index 0000000..63c55c6 --- /dev/null +++ b/tlbmc/hal/led/led.h
@@ -0,0 +1,89 @@ +#ifndef THIRD_PARTY_MILOTIC_EXTERNAL_CC_TLBMC_HAL_LED_LED_H_ +#define THIRD_PARTY_MILOTIC_EXTERNAL_CC_TLBMC_HAL_LED_LED_H_ + +#include <optional> +#include <string> + +#include "absl/base/thread_annotations.h" +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/synchronization/mutex.h" +#include "absl/time/time.h" +#include <nlohmann/json.hpp> +#include "led_config.pb.h" + +namespace milotic_tlbmc { +// Interface for controlling a single LED. +// +// Implementations back the LED with a specific hardware interface (e.g. the +// Linux LED sysfs subsystem, or - in the future - a raw GPIO line). Higher +// level components such as the LedCollector interact with LEDs only through +// this interface, so that support for new hardware backends can be added +// without changing the collector. +// +// This class is thread-safe. +class Led { + public: + static nlohmann::json BehaviorToJson(const LedBehavior& behavior); + + virtual ~Led() = default; + + // Led is a resource-managing class and should not be copied or moved. + Led(const Led&) = delete; + Led& operator=(const Led&) = delete; + Led(Led&&) = delete; + Led& operator=(Led&&) = delete; + + // Returns the default behavior of the LED. + std::optional<LedBehavior> GetDefaultBehavior() const { + return default_behavior_; + } + + // Returns the last update timestamp of the LED. + std::optional<absl::Time> GetLastUpdateTime() const { + absl::MutexLock lock(mutex_); + return last_update_time_; + } + + // Sets the LED to its default behavior. + absl::Status SetToDefaultBehavior(absl::Time timestamp); + + // Applies the given behavior (OFF / ON / BLINK) to the LED. + // If `timestamp` is less than or equal to `last_update_time_`, the update is + // skipped and absl::OkStatus() is returned. + absl::Status SetBehavior(const LedBehavior& behavior, absl::Time timestamp); + + // Returns the current behavior of the LED, read back from the hardware. + absl::StatusOr<LedBehavior> GetBehavior() const { + absl::MutexLock lock(mutex_); + return GetBehaviorImpl(); + } + + // Returns the unique name of this LED (LedConfig.name). + std::string GetName() const { return name_; } + + // Returns the JSON representation of the LED. + nlohmann::json ToJson() const; + + protected: + explicit Led(const LedConfig& config) + : name_(config.name()), + default_behavior_(config.has_default_behavior() + ? std::make_optional(config.default_behavior()) + : std::nullopt), + last_update_time_(std::nullopt) {} + + // Hardware-specific implementation of setting the LED behavior. + virtual absl::Status SetBehaviorImpl(const LedBehavior& behavior) = 0; + // Hardware-specific implementation of reading the LED behavior. + virtual absl::StatusOr<LedBehavior> GetBehaviorImpl() const = 0; + + const std::string name_; + const std::optional<LedBehavior> default_behavior_; + mutable absl::Mutex mutex_; + std::optional<absl::Time> last_update_time_ ABSL_GUARDED_BY(mutex_); +}; + +} // namespace milotic_tlbmc + +#endif // THIRD_PARTY_MILOTIC_EXTERNAL_CC_TLBMC_HAL_LED_LED_H_
diff --git a/tlbmc/hal/led/led_sysfs.cc b/tlbmc/hal/led/led_sysfs.cc new file mode 100644 index 0000000..a11becf --- /dev/null +++ b/tlbmc/hal/led/led_sysfs.cc
@@ -0,0 +1,87 @@ +#include "tlbmc/hal/led/led_sysfs.h" + +#include <cstdint> +#include <memory> +#include <string> + +#include "absl/memory/memory.h" +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/str_cat.h" +#include "g3/macros.h" +#include "led_config.pb.h" +#include "tlbmc/hal/sysfs/led.h" + +namespace milotic_tlbmc { + +absl::Status SysfsLed::SetBehaviorImpl(const LedBehavior& behavior) { + switch (behavior.state()) { + case LED_STATE_OFF: + ECCLESIA_RETURN_IF_ERROR(led_sysfs_.SetTrigger(sysfs_name_, "none")); + return led_sysfs_.SetBrightness(sysfs_name_, 0); + case LED_STATE_ON: { + ECCLESIA_RETURN_IF_ERROR(led_sysfs_.SetTrigger(sysfs_name_, "none")); + return led_sysfs_.SetBrightness(sysfs_name_, max_brightness_); + } + case LED_STATE_BLINK: { + // The kernel "timer" trigger blinks the LED in hardware using the + // delay_on / delay_off attributes. + ECCLESIA_RETURN_IF_ERROR(led_sysfs_.SetTrigger(sysfs_name_, "timer")); + ECCLESIA_RETURN_IF_ERROR(led_sysfs_.SetDelayOn( + sysfs_name_, behavior.blink_config().duty_on_ms())); + return led_sysfs_.SetDelayOff(sysfs_name_, + behavior.blink_config().duty_off_ms()); + } + default: + return absl::InvalidArgumentError("Unknown LED state"); + } +} + +absl::StatusOr<LedBehavior> SysfsLed::GetBehaviorImpl() const { + ECCLESIA_ASSIGN_OR_RETURN(std::string trigger, + led_sysfs_.GetTrigger(sysfs_name_)); + if (trigger == "timer") { + ECCLESIA_ASSIGN_OR_RETURN(uint32_t delay_on, + led_sysfs_.GetDelayOn(sysfs_name_)); + ECCLESIA_ASSIGN_OR_RETURN(uint32_t delay_off, + led_sysfs_.GetDelayOff(sysfs_name_)); + LedBehavior behavior; + behavior.set_state(LED_STATE_BLINK); + behavior.mutable_blink_config()->set_duty_on_ms(delay_on); + behavior.mutable_blink_config()->set_duty_off_ms(delay_off); + return behavior; + } + + ECCLESIA_ASSIGN_OR_RETURN(uint32_t brightness, + led_sysfs_.GetBrightness(sysfs_name_)); + LedBehavior behavior; + behavior.set_state(brightness > 0 ? LED_STATE_ON : LED_STATE_OFF); + return behavior; +} + +absl::StatusOr<std::unique_ptr<SysfsLed>> SysfsLed::Create( + const LedConfig& config, const LedSysfs& led_sysfs) { + if (config.name().empty()) { + return absl::InvalidArgumentError("LedConfig name must not be empty"); + } + if (!config.has_output()) { + return absl::InvalidArgumentError("LedConfig output must be set"); + } + if (!config.output().has_sysfs_name() || + config.output().sysfs_name().empty()) { + return absl::InvalidArgumentError("Sysfs LED name must not be empty"); + } + + if (!led_sysfs.IsLedPresent(config.output().sysfs_name())) { + return absl::InvalidArgumentError( + absl::StrCat("Sysfs LED ", config.output().sysfs_name(), " not found")); + } + + ECCLESIA_ASSIGN_OR_RETURN( + uint32_t max_brightness, + led_sysfs.GetMaxBrightness(config.output().sysfs_name())); + + return absl::WrapUnique(new SysfsLed(config, led_sysfs, max_brightness)); +} + +} // namespace milotic_tlbmc
diff --git a/tlbmc/hal/led/led_sysfs.h b/tlbmc/hal/led/led_sysfs.h new file mode 100644 index 0000000..d7b945c --- /dev/null +++ b/tlbmc/hal/led/led_sysfs.h
@@ -0,0 +1,50 @@ +#ifndef THIRD_PARTY_MILOTIC_EXTERNAL_CC_TLBMC_HAL_LED_LED_SYSFS_H_ +#define THIRD_PARTY_MILOTIC_EXTERNAL_CC_TLBMC_HAL_LED_LED_SYSFS_H_ + +#include <cstdint> +#include <memory> +#include <string> + +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "led_config.pb.h" +#include "tlbmc/hal/led/led.h" +#include "tlbmc/hal/sysfs/led.h" + +namespace milotic_tlbmc { +// LED backed by the Linux LED sysfs subsystem. +// +// Reuses the LedSysfs HAL to translate high-level LedBehavior into sysfs +// attribute writes: +// - LED_STATE_OFF: trigger="none", brightness=0. +// - LED_STATE_ON: trigger="none", brightness=max_brightness. +// - LED_STATE_BLINK: trigger="timer", delay_on/delay_off derived from the +// blink period and duty cycle. Blinking is handled by the +// kernel timer trigger, so no software timer is required. +// +// This class is thread-safe. +class SysfsLed : public Led { + public: + static absl::StatusOr<std::unique_ptr<SysfsLed>> Create( + const LedConfig& config, const LedSysfs& led_sysfs); + + protected: + absl::Status SetBehaviorImpl(const LedBehavior& behavior) override; + absl::StatusOr<LedBehavior> GetBehaviorImpl() const override; + + SysfsLed(const LedConfig& config, const LedSysfs& led_sysfs, + uint32_t max_brightness = 255) + : Led(config), + led_sysfs_(led_sysfs), + sysfs_name_(config.output().sysfs_name()), + max_brightness_(max_brightness) {} + + private: + const LedSysfs& led_sysfs_; + const std::string sysfs_name_; + const uint32_t max_brightness_; +}; + +} // namespace milotic_tlbmc + +#endif // THIRD_PARTY_MILOTIC_EXTERNAL_CC_TLBMC_HAL_LED_LED_SYSFS_H_
diff --git a/tlbmc/hal/sysfs/led.cc b/tlbmc/hal/sysfs/led.cc new file mode 100644 index 0000000..eb7ae27 --- /dev/null +++ b/tlbmc/hal/sysfs/led.cc
@@ -0,0 +1,202 @@ +#include "tlbmc/hal/sysfs/led.h" + +#include <cstdint> +#include <fstream> +#include <string> +#include <vector> + +#include "absl/log/log.h" +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/numbers.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_split.h" +#include "absl/strings/string_view.h" +#include "absl/strings/strip.h" +#include "boost/filesystem.hpp" // NOLINT: boost::filesystem is commonly used in BMC + +namespace milotic_tlbmc { +namespace { + +std::string GetLedAttributePath(absl::string_view sysfs_path, + absl::string_view led_name, + absl::string_view attribute) { + return absl::StrCat(sysfs_path, led_name, "/", attribute); +} + +absl::StatusOr<uint32_t> ReadUint32FromFile(const std::string& path) { + std::ifstream file(path); + if (!file.good()) { + return absl::InternalError(absl::StrCat("Failed to open ", path)); + } + + std::string content; + std::getline(file, content); + if (!file.good() && !file.eof()) { + return absl::InternalError(absl::StrCat("Failed to read from ", path)); + } + + uint32_t value; + if (!absl::SimpleAtoi(content, &value)) { + return absl::InternalError( + absl::StrCat("Failed to parse integer from '", content, "' in ", path)); + } + + return value; +} + +absl::Status WriteUint32ToFile(const std::string& path, uint32_t value) { + std::ofstream file(path); + if (!file.good()) { + return absl::InternalError(absl::StrCat("Failed to open ", path)); + } + + file << value << "\n"; + file.flush(); + + if (!file.good()) { + return absl::InternalError(absl::StrCat("Failed to write to ", path)); + } + + return absl::OkStatus(); +} + +absl::Status WriteStringToFile(const std::string& path, + absl::string_view value) { + std::ofstream file(path); + if (!file.good()) { + return absl::InternalError(absl::StrCat("Failed to open ", path)); + } + + file << value << "\n"; + file.flush(); + + if (!file.good()) { + return absl::InternalError(absl::StrCat("Failed to write to ", path)); + } + + return absl::OkStatus(); +} + +} // namespace + +bool LedSysfs::IsLedPresent(absl::string_view led_name) const { + boost::filesystem::path path(absl::StrCat(config_.led_sysfs_path, led_name)); + boost::system::error_code ec; + bool exists = boost::filesystem::exists(path, ec); + if (ec) { + LOG(ERROR) << "Error checking exists for path '" << path + << "': " << ec.message(); + return false; + } + return exists; +} + +absl::StatusOr<uint32_t> LedSysfs::GetBrightness( + absl::string_view led_name) const { + return ReadUint32FromFile( + GetLedAttributePath(config_.led_sysfs_path, led_name, "brightness")); +} + +absl::Status LedSysfs::SetBrightness(absl::string_view led_name, + uint32_t brightness) const { + return WriteUint32ToFile( + GetLedAttributePath(config_.led_sysfs_path, led_name, "brightness"), + brightness); +} + +absl::StatusOr<uint32_t> LedSysfs::GetMaxBrightness( + absl::string_view led_name) const { + return ReadUint32FromFile( + GetLedAttributePath(config_.led_sysfs_path, led_name, "max_brightness")); +} + +absl::StatusOr<std::string> LedSysfs::GetTrigger( + absl::string_view led_name) const { + std::string path = + GetLedAttributePath(config_.led_sysfs_path, led_name, "trigger"); + std::ifstream file(path); + if (!file.good()) { + return absl::InternalError(absl::StrCat("Failed to open ", path)); + } + + std::string content; + std::getline(file, content); + if (!file.good() && !file.eof()) { + return absl::InternalError(absl::StrCat("Failed to read from ", path)); + } + + // The active trigger is enclosed in brackets: "none [timer] heartbeat" + // Find the bracketed trigger. + auto start = content.find('['); + auto end = content.find(']'); + if (start == std::string::npos || end == std::string::npos || end <= start) { + return absl::InternalError(absl::StrCat( + "Failed to parse active trigger from '", content, "' in ", path)); + } + + return content.substr(start + 1, end - start - 1); +} + +absl::StatusOr<std::vector<std::string>> LedSysfs::GetAvailableTriggers( + absl::string_view led_name) const { + std::string path = + GetLedAttributePath(config_.led_sysfs_path, led_name, "trigger"); + std::ifstream file(path); + if (!file.good()) { + return absl::InternalError(absl::StrCat("Failed to open ", path)); + } + + std::string content; + std::getline(file, content); + if (!file.good() && !file.eof()) { + return absl::InternalError(absl::StrCat("Failed to read from ", path)); + } + + // Split by whitespace, stripping brackets from the active trigger. + std::vector<std::string> triggers; + for (absl::string_view token : + absl::StrSplit(content, ' ', absl::SkipEmpty())) { + absl::string_view stripped = token; + absl::ConsumePrefix(&stripped, "["); + absl::ConsumeSuffix(&stripped, "]"); + triggers.emplace_back(stripped); + } + + return triggers; +} + +absl::Status LedSysfs::SetTrigger(absl::string_view led_name, + absl::string_view trigger) const { + return WriteStringToFile( + GetLedAttributePath(config_.led_sysfs_path, led_name, "trigger"), + trigger); +} + +absl::StatusOr<uint32_t> LedSysfs::GetDelayOn( + absl::string_view led_name) const { + return ReadUint32FromFile( + GetLedAttributePath(config_.led_sysfs_path, led_name, "delay_on")); +} + +absl::Status LedSysfs::SetDelayOn(absl::string_view led_name, + uint32_t delay_ms) const { + return WriteUint32ToFile( + GetLedAttributePath(config_.led_sysfs_path, led_name, "delay_on"), + delay_ms); +} + +absl::StatusOr<uint32_t> LedSysfs::GetDelayOff( + absl::string_view led_name) const { + return ReadUint32FromFile( + GetLedAttributePath(config_.led_sysfs_path, led_name, "delay_off")); +} + +absl::Status LedSysfs::SetDelayOff(absl::string_view led_name, + uint32_t delay_ms) const { + return WriteUint32ToFile( + GetLedAttributePath(config_.led_sysfs_path, led_name, "delay_off"), + delay_ms); +} + +} // namespace milotic_tlbmc
diff --git a/tlbmc/hal/sysfs/led.h b/tlbmc/hal/sysfs/led.h new file mode 100644 index 0000000..d7f4405 --- /dev/null +++ b/tlbmc/hal/sysfs/led.h
@@ -0,0 +1,95 @@ +#ifndef THIRD_PARTY_MILOTIC_EXTERNAL_CC_TLBMC_HAL_SYSFS_LED_H_ +#define THIRD_PARTY_MILOTIC_EXTERNAL_CC_TLBMC_HAL_SYSFS_LED_H_ + +#include <cstdint> +#include <string> +#include <vector> + +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/string_view.h" + +namespace milotic_tlbmc { + +struct LedSysfsConfig { + std::string led_sysfs_path = "/sys/class/leds/"; +}; + +// An abstraction for interacting with the Linux LED sysfs subsystem. +// +// The Linux LED subsystem exposes LEDs at /sys/class/leds/<led_name>/ with +// the following key attributes: +// - brightness: Current brightness level (0 to max_brightness). +// - max_brightness: Maximum brightness value supported by the LED. +// - trigger: The trigger source that controls the LED behavior +// (e.g., "none", "timer", "heartbeat", "default-on"). +// - delay_on: Duration in milliseconds the LED stays on during +// blinking (only available when trigger is "timer"). +// - delay_off: Duration in milliseconds the LED stays off during +// blinking (only available when trigger is "timer"). +// +// The class is thread-safe. Thread safety is mostly guaranteed by the +// underlying sysfs filesystem. +class LedSysfs { + public: + // Default constructor for unit tests. + LedSysfs() : LedSysfs(LedSysfsConfig()) {} + + explicit LedSysfs(const LedSysfsConfig& config) : config_(config) {} + + virtual ~LedSysfs() = default; + + // Returns the current brightness of the LED. + virtual absl::StatusOr<uint32_t> GetBrightness( + absl::string_view led_name) const; + + // Sets the brightness of the LED to the given value. + // The value must be in the range [0, max_brightness]. + virtual absl::Status SetBrightness(absl::string_view led_name, + uint32_t brightness) const; + + // Returns the maximum brightness value supported by the LED. + virtual absl::StatusOr<uint32_t> GetMaxBrightness( + absl::string_view led_name) const; + + // Returns the current trigger of the LED. + // The active trigger is enclosed in brackets in the sysfs trigger file, + // e.g., "none [timer] heartbeat default-on". + virtual absl::StatusOr<std::string> GetTrigger( + absl::string_view led_name) const; + + // Returns the list of available triggers for the LED. + virtual absl::StatusOr<std::vector<std::string>> GetAvailableTriggers( + absl::string_view led_name) const; + + // Sets the trigger of the LED to the given value. + virtual absl::Status SetTrigger(absl::string_view led_name, + absl::string_view trigger) const; + + // Returns the delay_on value in milliseconds (timer trigger only). + virtual absl::StatusOr<uint32_t> GetDelayOn(absl::string_view led_name) const; + + // Sets the delay_on value in milliseconds (timer trigger only). + virtual absl::Status SetDelayOn(absl::string_view led_name, + uint32_t delay_ms) const; + + // Returns the delay_off value in milliseconds (timer trigger only). + virtual absl::StatusOr<uint32_t> GetDelayOff( + absl::string_view led_name) const; + + // Sets the delay_off value in milliseconds (timer trigger only). + virtual absl::Status SetDelayOff(absl::string_view led_name, + uint32_t delay_ms) const; + + // Returns true if the LED exists in sysfs. + virtual bool IsLedPresent(absl::string_view led_name) const; + + std::string GetLedSysfsPath() const { return config_.led_sysfs_path; } + + private: + const LedSysfsConfig config_; +}; + +} // namespace milotic_tlbmc + +#endif // THIRD_PARTY_MILOTIC_EXTERNAL_CC_TLBMC_HAL_SYSFS_LED_H_
diff --git a/tlbmc/led_config.proto b/tlbmc/led_config.proto new file mode 100644 index 0000000..c80dc60 --- /dev/null +++ b/tlbmc/led_config.proto
@@ -0,0 +1,89 @@ +edition = "2023"; + +package milotic_tlbmc; + +import "power_control.proto"; + +enum LedState { + LED_STATE_UNSPECIFIED = 0; + LED_STATE_OFF = 1; + LED_STATE_BLINK = 2; + LED_STATE_ON = 3; +} + +message BlinkConfig { + uint32 duty_on_ms = 1 [default = 500]; + uint32 duty_off_ms = 2 [default = 500]; +} + +message LedBehavior { + LedState state = 1 [default = LED_STATE_OFF]; + // Configuration applied ONLY if state == LED_STATE_BLINK. + BlinkConfig blink_config = 2; +} + +message LedOutput { + oneof target { + // Name of the sysfs LED (e.g., "blue:identify"). + string sysfs_name = 1; + // Name of the GPIO LED line (e.g., "power_led" or "<chip_id>:<gpio_num>"). + // TODO: b/541951164 - impl GPIO LEDs. + // string gpio_name = 2; + } +} + +message LedConfig { + // Unique name identifying the LED (e.g., "fault_led", "identify_led"). + string name = 1; + // Hardware output interface (sysfs or GPIO). + LedOutput output = 2; + // Default behavior applied on start / no rules matched + // set to LED_STATE_UNSPECIFIED to persist the previous state + LedBehavior default_behavior = 3; +} + +message LedPresetMember { + string name = 1; + LedBehavior behavior = 2; +} + +message LedPreset { + // Name of the preset (e.g., "system_fault", "chassis_identify", "power_on"). + string name = 1; + // List of LEDs and their associated state controls. + repeated LedPresetMember members = 2; +} + +message LedConditionVariable { + string name = 1; + oneof value { + string source_path = 2; + PowerState power_state = 3; + OSState os_state = 4; + } +} + +message LedCondition { + string expression = 1; +} + +enum LedTrigger { + LED_TRIGGER_UNSPECIFIED = 0; + // TODO: b/546330351 - impl GPIO trigger +} + +message LedPolicy { + string name = 1; + string preset_name = 2; + oneof event { + LedCondition condition = 3; + LedTrigger trigger = 4; + } +} + +message LedConfigs { + repeated LedConfig leds = 1; + repeated LedPreset presets = 2; + repeated LedConditionVariable variables = 3; + repeated LedPolicy policies = 4; +}
diff --git a/tlbmc/meson.build b/tlbmc/meson.build index 3f40afa..deaa482 100644 --- a/tlbmc/meson.build +++ b/tlbmc/meson.build
@@ -28,6 +28,7 @@ 'hwmon_temp_sensor_config.proto', 'identifier.proto', 'intel_cpu_sensor_config.proto', + 'led_config.proto', 'memory.proto', 'nic_telemetry_config.proto', 'offline_data.proto', @@ -138,6 +139,7 @@ 'collector/fake_gpio_collector.cc', 'collector/fru_collector.cc', 'collector/gpio_collector.cc', + 'collector/led_collector.cc', 'collector/metric_collector.cc', 'collector/pcie_collector.cc', 'collector/peci_scanner.cc', @@ -173,6 +175,8 @@ 'hal/gpio/gpio.cc', 'hal/gpio/gpio_monitor.cc', 'hal/gpio/on_demand_gpio.cc', + 'hal/led/led.cc', + 'hal/led/led_sysfs.cc', 'hal/nic_veeprom/interface.cc', 'hal/peci/peci_access_impl.cc', 'hal/power_fault_detector/power_fault_detector.cc', @@ -184,6 +188,7 @@ 'hal/sysfs/i2c.cc', 'hal/sysfs/i3c.cc', 'hal/sysfs/iio.cc', + 'hal/sysfs/led.cc', 'hal/sysfs/peci.cc', 'hal/system_registry_impl.cc', 'hft/core/edge_filter.cc',
diff --git a/tlbmc/store/factory.h b/tlbmc/store/factory.h index dd739d8..1da3848 100644 --- a/tlbmc/store/factory.h +++ b/tlbmc/store/factory.h
@@ -13,6 +13,7 @@ #include <nlohmann/json_fwd.hpp> #include "tlbmc/collector/fru_collector.h" #include "tlbmc/collector/gpio_collector.h" +#include "tlbmc/collector/led_collector.h" #include "tlbmc/collector/metric_collector.h" #include "tlbmc/collector/pcie_collector.h" #include "tlbmc/collector/power_control_collector.h" @@ -43,6 +44,8 @@ CreateMetricCollector(const MetricCollector::Params& params) = 0; virtual absl::StatusOr<std::unique_ptr<GpioCollector>> CreateGpioCollector( const GpioCollector::Params& params) = 0; + virtual absl::StatusOr<std::unique_ptr<LedCollector>> CreateLedCollector( + const LedCollector::Params& params) = 0; virtual absl::StatusOr<std::unique_ptr<ThermalCollector>> CreateThermalCollector(const ThermalCollector::Params& params, SensorCollectorAggregator* absl_nonnull aggregator, @@ -81,6 +84,11 @@ return GpioCollector::Create(params); } + absl::StatusOr<std::unique_ptr<LedCollector>> CreateLedCollector( + const LedCollector::Params& params) override { + return LedCollector::Create(params); + } + absl::StatusOr<std::unique_ptr<ThermalCollector>> CreateThermalCollector( const ThermalCollector::Params& params, SensorCollectorAggregator* absl_nonnull aggregator,
diff --git a/tlbmc/store/octeon10_evk_store.cc b/tlbmc/store/octeon10_evk_store.cc index caec0c6..7c35b5f 100644 --- a/tlbmc/store/octeon10_evk_store.cc +++ b/tlbmc/store/octeon10_evk_store.cc
@@ -29,6 +29,7 @@ #include "tlbmc/collector/collector.h" #include "tlbmc/collector/fru_collector.h" #include "tlbmc/collector/gpio_collector.h" +#include "tlbmc/collector/led_collector.h" #include "tlbmc/collector/monitoring_change_base.h" #include "tlbmc/collector/pcie_collector.h" #include "power_control.pb.h" @@ -691,6 +692,8 @@ GpioCollector* Octeon10EvkStore::GetGpioCollector() const { return nullptr; } +LedCollector* Octeon10EvkStore::GetLedCollector() const { return nullptr; } + PcieCollector* Octeon10EvkStore::GetPcieCollector() const { return nullptr; } SoftwareMetricsValue Octeon10EvkStore::GetMetricValues() const {
diff --git a/tlbmc/store/octeon10_evk_store.h b/tlbmc/store/octeon10_evk_store.h index c29df30..9510684 100644 --- a/tlbmc/store/octeon10_evk_store.h +++ b/tlbmc/store/octeon10_evk_store.h
@@ -118,6 +118,7 @@ FruCollector* GetFruCollector() const override; GpioCollector* GetGpioCollector() const override; + LedCollector* GetLedCollector() const override; PcieCollector* GetPcieCollector() const override; SoftwareMetricsValue GetMetricValues() const override;
diff --git a/tlbmc/store/store.h b/tlbmc/store/store.h index 64c27e1..56a430d 100644 --- a/tlbmc/store/store.h +++ b/tlbmc/store/store.h
@@ -12,6 +12,7 @@ #include "absl/time/time.h" #include "tlbmc/collector/gpio_collector.h" +#include "tlbmc/collector/led_collector.h" #include "tlbmc/collector/sensor_collector.h" #include "resource.pb.h" #include "tlbmc/thermal/controller/dff_controller.h" @@ -171,6 +172,7 @@ virtual FruCollector* GetFruCollector() const = 0; virtual GpioCollector* GetGpioCollector() const = 0; + virtual LedCollector* GetLedCollector() const = 0; virtual PcieCollector* GetPcieCollector() const = 0; virtual SoftwareMetricsValue GetMetricValues() const = 0; @@ -470,6 +472,7 @@ FruCollector* GetFruCollector() const override { return nullptr; } GpioCollector* GetGpioCollector() const override { return nullptr; } + LedCollector* GetLedCollector() const override { return nullptr; } PcieCollector* GetPcieCollector() const override { return nullptr; } SoftwareMetricsValue GetMetricValues() const override {
diff --git a/tlbmc/store/store_impl.cc b/tlbmc/store/store_impl.cc index c09acb5..78434e5 100644 --- a/tlbmc/store/store_impl.cc +++ b/tlbmc/store/store_impl.cc
@@ -35,6 +35,7 @@ #include "tlbmc/collector/collector.h" #include "tlbmc/collector/fru_collector.h" #include "tlbmc/collector/gpio_collector.h" +#include "tlbmc/collector/led_collector.h" #include "tlbmc/collector/metric_collector.h" #include "tlbmc/collector/monitoring_change_base.h" #include "tlbmc/collector/pcie_collector.h" @@ -108,6 +109,10 @@ return all_collectors_.gpio_collector.get(); } +LedCollector* StoreImpl::GetLedCollector() const { + return all_collectors_.led_collector.get(); +} + PcieCollector* StoreImpl::GetPcieCollector() const { return all_collectors_.pcie_collector.get(); } @@ -440,6 +445,7 @@ json["Fru"] = all_collectors_.fru_collector->ToJson(); json["Metric"] = all_collectors_.metric_collector->ToJson(); json["Gpio"] = all_collectors_.gpio_collector->ToJson(); + json["Led"] = all_collectors_.led_collector->ToJson(); json["Fan"] = all_collectors_.thermal_collector->ToJson(); json["PowerFaultLog"] = all_collectors_.power_fault_log_collector->ToJson(); json["Sel"] = all_collectors_.sel_collector->ToJson(); @@ -457,6 +463,7 @@ json["MetricCollector"] = all_collectors_.metric_collector->GetSchedulerStats(); json["GpioCollector"] = all_collectors_.gpio_collector->GetSchedulerStats(); + json["LedCollector"] = all_collectors_.led_collector->GetSchedulerStats(); json["Fan"] = all_collectors_.thermal_collector->GetSchedulerStats(); json["PowerFaultLogCollector"] = all_collectors_.power_fault_log_collector->GetSchedulerStats(); @@ -786,6 +793,7 @@ .metric_collector = EmptyMetricCollector::Create(), .gpio_collector = EmptyGpioCollector::Create(), .power_control_collector = EmptyPowerControlCollector::Create(), + .led_collector = EmptyLedCollector::Create(), .sensor_collector = EmptySensorCollector::Create(), .thermal_collector = EmptyThermalCollector::Create(), .power_fault_log_collector = EmptyPowerFaultLogCollector::Create(), @@ -822,6 +830,15 @@ all_collectors.power_control_collector = *std::move(collector); } + if (GetTlbmcConfig().led_collector_module().enabled()) { + ECCLESIA_ASSIGN_OR_RETURN( + std::unique_ptr<LedCollector> led_collector, + options.collector_factory->CreateLedCollector( + {.led_configs = options.proto_parser->GetLedConfigs(), + .led_sysfs = *options.led_sysfs})); + all_collectors.led_collector = std::move(led_collector); + } + if (GetTlbmcConfig().fru_collector_module().enabled()) { absl::Time fru_scan_start_time = absl::Now(); Tracer::GetInstance().AddOneOffDatapoint("Tlbmc-Scan-FRUs-Begin",
diff --git a/tlbmc/store/store_impl.h b/tlbmc/store/store_impl.h index 0f2b272..37e56a8 100644 --- a/tlbmc/store/store_impl.h +++ b/tlbmc/store/store_impl.h
@@ -26,6 +26,7 @@ #include "tlbmc/collector/collector.h" #include "tlbmc/collector/fru_collector.h" #include "tlbmc/collector/gpio_collector.h" +#include "tlbmc/collector/led_collector.h" #include "tlbmc/collector/metric_collector.h" #include "tlbmc/collector/monitoring_change_base.h" #include "tlbmc/collector/pcie_collector.h" @@ -49,6 +50,7 @@ #include "tlbmc/hal/sysfs/i2c.h" #include "tlbmc/hal/sysfs/i3c.h" #include "tlbmc/hal/sysfs/iio.h" +#include "tlbmc/hal/sysfs/led.h" #include "tlbmc/hal/sysfs/peci.h" #include "tlbmc/hal/system_registry.h" #include "tlbmc/host_state/power_control.h" @@ -81,6 +83,7 @@ std::unique_ptr<MetricCollector> metric_collector = nullptr; std::unique_ptr<GpioCollector> gpio_collector = nullptr; std::unique_ptr<PowerControlCollector> power_control_collector = nullptr; + std::unique_ptr<LedCollector> led_collector = nullptr; std::unique_ptr<SensorCollector> sensor_collector = nullptr; std::unique_ptr<ThermalCollector> thermal_collector = nullptr; std::unique_ptr<PowerFaultLogCollector> power_fault_log_collector = nullptr; @@ -106,6 +109,7 @@ std::unique_ptr<IioSysfs> iio_sysfs = nullptr; std::unique_ptr<I2cSysfs> i2c_sysfs = nullptr; std::unique_ptr<I3cSysfs> i3c_sysfs = nullptr; + std::unique_ptr<LedSysfs> led_sysfs = nullptr; std::unique_ptr<PeciSysfs> peci_sysfs = nullptr; std::unique_ptr<PeciAccessInterface> peci_access = std::make_unique<PeciAccessImpl>(); @@ -186,6 +190,7 @@ bool tlbmc_unit_test_enable_fake_gpio_collector = false; std::unique_ptr<GpioCollector> gpio_collector = nullptr; + std::unique_ptr<LedCollector> led_collector = nullptr; absl::AnyInvocable<std::unique_ptr<nic_veeprom::Accessor>( nic_veeprom::NicTelemetryVersion version, int bus, int address) const> @@ -257,6 +262,7 @@ // Returns the fru collector as raw pointer. FruCollector* GetFruCollector() const override; GpioCollector* GetGpioCollector() const override; + LedCollector* GetLedCollector() const override; PcieCollector* GetPcieCollector() const override; absl::StatusOr<PsiMetrics> GetPsiMetrics() const override;