| #include "tlbmc/deterministic_bmc/i2c_walker/uhmm_i2c_walker.h" |
| |
| #include <charconv> |
| #include <cstdint> |
| #include <filesystem> // NOLINT |
| #include <fstream> |
| #include <memory> |
| #include <optional> |
| #include <string> |
| #include <system_error> // NOLINT |
| #include <utility> |
| #include <vector> |
| |
| #include "absl/log/log.h" |
| #include "absl/memory/memory.h" |
| #include "absl/status/status.h" |
| #include "absl/status/statusor.h" |
| #include "absl/strings/ascii.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/string_view.h" |
| #include "g3/macros.h" |
| #include "tlbmc/deterministic_bmc/offline_config_parser/offline_config_parser.h" |
| #include "tlbmc/deterministic_bmc/offline_config_parser/proto_reader.h" |
| #include "fru_i2c.pb.h" |
| |
| namespace deterministic_bmc { |
| |
| absl::StatusOr<std::unique_ptr<UhmmI2cWalker>> UhmmI2cWalker::Create( |
| UhmmI2cWalkerOptions options) { |
| if (options.uhmm_offline_config_path.empty()) { |
| return absl::InvalidArgumentError( |
| "uhmm_offline_config_path cannot be empty"); |
| } |
| |
| OfflineConfigParser::ConfigParserOptions config_parser_options; |
| config_parser_options.fru_i2c_config_format = options.fru_i2c_config_format; |
| if (options.fru_i2c_config_format == ProtoFormat::kTextProto) { |
| config_parser_options.fru_i2c_config_path_txtpb = |
| options.uhmm_offline_config_path; |
| } else if (options.fru_i2c_config_format == ProtoFormat::kBinaryProto) { |
| config_parser_options.fru_i2c_config_path_pb = |
| options.uhmm_offline_config_path; |
| } else { |
| return absl::InvalidArgumentError(absl::StrCat( |
| "Invalid fru_i2c_config_format: ", options.fru_i2c_config_format)); |
| } |
| |
| ECCLESIA_ASSIGN_OR_RETURN( |
| std::unique_ptr<OfflineConfigParser> offline_config_parser, |
| OfflineConfigParser::Create(config_parser_options)); |
| |
| ECCLESIA_ASSIGN_OR_RETURN(fru_i2c::I2cRoot i2c_root, |
| offline_config_parser->GetFruI2cConfigFromProto()); |
| |
| std::error_code ec; |
| if (!std::filesystem::exists(options.root_directory, ec)) { |
| std::string error_message = absl::StrCat( |
| "Root directory ", options.root_directory, |
| " does not exist or filesystem::exists() failed with error: ", |
| ec ? ec.message() : "N/A"); |
| LOG(ERROR) << error_message; |
| return absl::InvalidArgumentError(error_message); |
| } |
| |
| return absl::WrapUnique(new UhmmI2cWalker(i2c_root, options.root_directory)); |
| } |
| |
| absl::StatusOr<uint32_t> UhmmI2cWalker::GetLogicalBusId( |
| absl::string_view root_directory, uint32_t root_bus, |
| const fru_i2c::I2CAddress& mux_address_identifier, |
| absl::string_view mux_model, uint32_t channel_id) { |
| if (!mux_address_identifier.has_address() || |
| !mux_address_identifier.has_address_length()) { |
| return absl::InvalidArgumentError( |
| "Uhmm config error: Ivalid Mux address identifier: does not have an " |
| "address or address length."); |
| } |
| // Only 7 bit address is supported now. Will get notified by the error here |
| // if the address length is 10 bits so we can update to add support for 10 |
| // bit address in the future. |
| if (mux_address_identifier.address_length() != |
| fru_i2c::I2CAddress::ADDRESS_LENGTH_BIT7) { |
| return absl::InvalidArgumentError( |
| "Uhmm config error: Mux address length is not 7 bits."); |
| } |
| |
| // The mux path is expected to be |
| // /sys/bus/i2c/devices/i2c-<root_bus>/<root_bus>-<mux_address_hex> |
| // See https://docs.kernel.org/i2c/i2c-sysfs.html#walk-through-logical-i2c-bus |
| // for more details. |
| std::string root_bus_path = |
| absl::StrCat(root_directory, "/sys/bus/i2c/devices/i2c-", root_bus); |
| if (!std::filesystem::exists(root_bus_path)) { |
| return absl::NotFoundError( |
| absl::StrCat("Root bus path ", root_bus_path, " does not exist.")); |
| } |
| |
| std::string mux_address_hex = absl::StrCat( |
| absl::Hex(mux_address_identifier.address(), absl::kZeroPad4)); |
| std::string mux_path = |
| absl::StrCat(root_bus_path, "/", root_bus, "-", mux_address_hex); |
| std::error_code error_code; |
| if (!std::filesystem::exists(mux_path, error_code)) { |
| std::string error_message = absl::StrCat( |
| "Mux path ", mux_path, |
| " does not exist or filesystem::exists() failed with error: ", |
| error_code ? error_code.message() : "N/A"); |
| LOG(ERROR) << error_message; |
| return absl::NotFoundError(error_message); |
| } |
| |
| // Read the mux name from the file. The mux name is expected to be the same as |
| // the mux model. |
| std::string mux_name_from_file; |
| { |
| std::ifstream name_file(absl::StrCat(mux_path, "/name")); |
| if (!name_file.is_open()) { |
| return absl::NotFoundError( |
| absl::StrCat("Failed to open mux name file at ", mux_path, "/name.")); |
| } |
| |
| // Both operator >> and istreambuf_iterator are vulnerable to exceptions in |
| // std::streambuf::underflow(). |
| std::getline(name_file, mux_name_from_file); |
| |
| if (name_file.fail() || name_file.bad()) { |
| return absl::NotFoundError( |
| absl::StrCat("Failed to read mux name file at ", mux_path, "/name.")); |
| } |
| } |
| if (mux_name_from_file != absl::AsciiStrToLower(mux_model)) { |
| return absl::NotFoundError( |
| absl::StrCat("Mux model mismatch for ", mux_path, "/name. Expected '", |
| absl::AsciiStrToLower(mux_model), "', but got '", |
| mux_name_from_file, "'.")); |
| } |
| |
| std::string mux_channel_path = |
| absl::StrCat(mux_path, "/channel-", channel_id); |
| |
| // Currently we decide not to check if the mux channel path actually exists |
| // in the i2c walker(filesystem::exists will resolve the symlink). |
| // Instead, we let the walker run and collect all the hardware info. Then we |
| // can later let the scanner take care of the issue. |
| |
| // The mux_channel_path is a symlink to the logical i2c bus. |
| // For example, /sys/bus/i2c/devices/i2c-0/0-0070/channel-0 is a symlink to |
| // ../i2c-8. The logical bus id is 8. (i.e. the full path on a machine is |
| // /sys/bus/i2c/devices/i2c-0/i2c-8) |
| if (!std::filesystem::is_symlink(mux_channel_path, error_code)) { |
| std::string error_message = absl::StrCat( |
| "Mux channel ", mux_channel_path, |
| " is not a symlink or filesystem::is_symlink() failed with error: ", |
| error_code ? error_code.message() : "N/A"); |
| LOG(ERROR) << error_message; |
| return absl::NotFoundError(error_message); |
| } |
| std::filesystem::path logical_bus_path = |
| std::filesystem::read_symlink(mux_channel_path, error_code); |
| if (error_code) { |
| return absl::NotFoundError( |
| absl::StrCat("read_symlink failed: Failed to read symlink from ", |
| mux_channel_path, ": ", error_code.message())); |
| } |
| |
| // The logical_bus_path is expected to be in the format of ../i2c-X. |
| // We need to extract the bus id X from the path. |
| std::string logical_bus_name = logical_bus_path.filename().string(); |
| constexpr absl::string_view kI2cPrefix = "i2c-"; |
| if (!absl::string_view(logical_bus_name).starts_with(kI2cPrefix)) { |
| return absl::NotFoundError(absl::StrCat( |
| "Invalid symlink target for ", mux_channel_path, ": ", |
| logical_bus_path.string(), ". Expected to start with 'i2c-'.")); |
| } |
| |
| // The logical bus name is expected to be in the format of i2c-X. |
| // We need to extract the bus id X from the name. |
| absl::string_view bus_id_str = |
| absl::string_view(logical_bus_name).substr(kI2cPrefix.length()); |
| LOG(INFO) << "bus_id_str: " << bus_id_str; |
| uint32_t bus_id; |
| auto [ptr, ec] = |
| std::from_chars(bus_id_str.begin(), bus_id_str.end(), bus_id); |
| |
| if (ec != std::errc() || ptr != bus_id_str.end()) { |
| return absl::NotFoundError(absl::StrCat( |
| "Failed to parse bus id from mux channel '", mux_channel_path, |
| "' with logical bus name '", logical_bus_name, "'")); |
| } |
| LOG(INFO) << "logical bus id: " << bus_id; |
| return bus_id; |
| } |
| |
| absl::StatusOr<std::vector<UhmmI2cWalker::HardwareInfo>> |
| UhmmI2cWalker::GetLogicalI2cAddresses() const { |
| std::vector<HardwareInfo> hardware_i2c_info; |
| for (const auto& bus : i2c_root_.buses()) { |
| // walk the top level bus and collect all eeproms. For top level bus, the |
| // logical bus_id will be the same as the physical bus_id in the uhmm |
| // config. |
| for (const auto& eeprom : bus.eeproms()) { |
| // Collect eeproms from Physical bus. |
| if (!eeprom.has_address_identifier()) { |
| std::string error_message = absl::StrCat( |
| "Uhmm config error: physical bus ", bus.bus_id(), " Eeprom ", |
| eeprom.barepath(), " does not have an address identifier."); |
| LOG(ERROR) << error_message |
| << " Stopping the i2c walker. eeprom: " << eeprom; |
| return absl::InternalError(error_message); |
| } |
| |
| if (!eeprom.has_part_information() || |
| !eeprom.part_information().has_part_number() || |
| !eeprom.part_information().has_serial_number()) { |
| std::string error_message = |
| absl::StrCat("Uhmm config error: physical bus ", bus.bus_id(), |
| " Eeprom ", eeprom.barepath(), |
| " does not have part information or has imcomplete " |
| "part information."); |
| LOG(ERROR) << error_message |
| << " Stopping the i2c walker. eeprom: " << eeprom; |
| return absl::InternalError(error_message); |
| } |
| |
| HardwareInfo hardware_info; |
| hardware_info.logical_bus = bus.bus_id(); |
| hardware_info.address = eeprom.address_identifier().address(); |
| hardware_info.barepath = eeprom.barepath(); |
| hardware_info.part_number = eeprom.part_information().part_number(); |
| hardware_info.serial_number = eeprom.part_information().serial_number(); |
| hardware_i2c_info.push_back(std::move(hardware_info)); |
| } |
| |
| // walk the mux buses and collect all eeproms. For mux buses, the logical |
| // bus_id will be the bus_id relative to the mux. |
| for (const auto& mux : bus.muxes()) { |
| if (!mux.has_address_identifier()) { |
| std::string error_message = absl::StrCat( |
| "Uhmm config error: physical bus ", bus.bus_id(), " mux ", |
| mux.model(), " does not have an address identifier."); |
| LOG(ERROR) << error_message << " Stopping the i2c walker. mux: " << mux; |
| return absl::InternalError(error_message); |
| } |
| |
| // Get the mux address identifier and model. |
| fru_i2c::I2CAddress mux_address_identifier = mux.address_identifier(); |
| std::string mux_model = mux.model(); |
| |
| for (const auto& downstream_bus : mux.downstream_buses()) { |
| // Each downstream bus is expected to have a bus id(channel id) now. |
| // Error out if it does not have a bus id. This is not expected |
| // to happen. |
| if (!downstream_bus.has_bus_id()) { |
| std::string error_message = absl::StrCat( |
| "Uhmm config error: physical bus ", bus.bus_id(), " mux ", |
| mux.model(), " at address ", mux_address_identifier.address(), |
| " downstream bus (unknown channel id) does not have a bus id."); |
| LOG(ERROR) << error_message |
| << " Stopping the i2c walker. " |
| "downstream_bus: " |
| << downstream_bus; |
| return absl::InternalError(error_message); |
| } |
| |
| // Each channel is expected to have one eeprom now. Error out |
| // if it has more than one eeprom. |
| if (downstream_bus.eeproms().size() != 1) { |
| std::string error_message = absl::StrCat( |
| "Unimplemented error: physical bus ", bus.bus_id(), " mux ", |
| mux.model(), " address ", mux_address_identifier.address(), |
| " channel ", downstream_bus.bus_id(), |
| " has more than one eeprom which is not supported yet."); |
| LOG(ERROR) << error_message |
| << " Stopping the i2c walker. downstream_bus: " |
| << downstream_bus; |
| return absl::UnimplementedError(error_message); |
| } |
| |
| if (!downstream_bus.muxes().empty()) { |
| std::string error_message = absl::StrCat( |
| "Unimplemented error: physical bus ", bus.bus_id(), " mux ", |
| mux.model(), " address ", mux_address_identifier.address(), |
| " channel ", downstream_bus.bus_id(), |
| " has nested muxes which is not supported yet."); |
| LOG(ERROR) << error_message |
| << " Stopping the i2c walker. downstream_bus: " |
| << downstream_bus; |
| return absl::UnimplementedError(error_message); |
| } |
| for (const auto& eeprom : downstream_bus.eeproms()) { |
| if (!eeprom.has_address_identifier()) { |
| std::string error_message = absl::StrCat( |
| "Uhmm config error: physical bus ", bus.bus_id(), " mux ", |
| mux.model(), " address ", mux_address_identifier.address(), |
| " channel ", downstream_bus.bus_id(), " eeprom ", |
| eeprom.barepath(), " does not have an address identifier."); |
| LOG(ERROR) << error_message |
| << " Stopping the i2c walker. eeprom: " << eeprom; |
| return absl::InternalError(error_message); |
| } |
| |
| if (!eeprom.has_part_information() || |
| !eeprom.part_information().has_part_number() || |
| !eeprom.part_information().has_serial_number()) { |
| std::string error_message = absl::StrCat( |
| "Uhmm config error: physical bus ", bus.bus_id(), " mux ", |
| mux.model(), " address ", mux_address_identifier.address(), |
| " channel ", downstream_bus.bus_id(), " eeprom ", |
| eeprom.barepath(), |
| " does not have part information or has imcomplete part " |
| "information."); |
| LOG(ERROR) << error_message |
| << " Stopping the i2c walker. eeprom: " << eeprom; |
| return absl::InternalError(error_message); |
| } |
| |
| absl::StatusOr<uint32_t> logical_bus_id = GetLogicalBusId( |
| root_directory_, bus.bus_id(), mux_address_identifier, mux_model, |
| downstream_bus.bus_id()); |
| if (!logical_bus_id.ok() && |
| !absl::IsNotFound(logical_bus_id.status())) { |
| // Error out immediately for all errors(i.e. uhmm config error) |
| // except for not found error. Not found error now particularly |
| // refers to i2c sysfs work issues. |
| std::string error_message = absl::StrCat( |
| "Uhmm config error: Failed to get logical bus id " |
| "for physical bus ", |
| bus.bus_id(), " channel ", downstream_bus.bus_id(), " for mux ", |
| mux.model(), ": ", logical_bus_id.status()); |
| LOG(ERROR) << error_message |
| << " Stopping the i2c walker. Please check the uhmm " |
| "config. mux: " |
| << mux; |
| return absl::InternalError(error_message); |
| } |
| |
| if (absl::IsNotFound(logical_bus_id.status())) { |
| HardwareInfo hardware_info; |
| hardware_info.address = eeprom.address_identifier().address(); |
| hardware_info.barepath = eeprom.barepath(); |
| hardware_info.part_number = eeprom.part_information().part_number(); |
| hardware_info.serial_number = |
| eeprom.part_information().serial_number(); |
| hardware_info.not_found_info = { |
| .status = logical_bus_id.status(), |
| .physical_bus = bus.bus_id(), |
| .mux_model = mux_model, |
| .mux_address = mux_address_identifier.address(), |
| .channel = downstream_bus.bus_id(), |
| }; |
| hardware_i2c_info.push_back(std::move(hardware_info)); |
| continue; |
| } |
| |
| HardwareInfo hardware_info; |
| hardware_info.logical_bus = logical_bus_id.value(); |
| hardware_info.address = eeprom.address_identifier().address(); |
| hardware_info.barepath = eeprom.barepath(); |
| hardware_info.part_number = eeprom.part_information().part_number(); |
| hardware_info.serial_number = |
| eeprom.part_information().serial_number(); |
| hardware_i2c_info.push_back(std::move(hardware_info)); |
| } |
| } |
| } |
| } |
| return hardware_i2c_info; |
| } |
| |
| } // namespace deterministic_bmc |