blob: 4dc70caa7f8c3265e4604d68304c0af5adc1c4c3 [file] [log] [blame] [edit]
#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