blob: 06a63142507586ecfd54aff98fdb946249a554fc [file]
#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 "bus_topology.pb.h"
#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"
namespace deterministic_bmc {
std::unique_ptr<UhmmI2cWalker> UhmmI2cWalker::Create(
UhmmI2cWalkerOptions options) {
return absl::WrapUnique(
new UhmmI2cWalker(options.i2c_root, options.root_directory));
}
absl::StatusOr<uint32_t> UhmmI2cWalker::GetLogicalBusId(
absl::string_view root_directory, uint32_t root_bus,
const uhmm::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() !=
uhmm::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(channel_id) will be the same as the physical
// bus_id(channel_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.channel_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);
}
// We are not complicating the logic for now as we don't use serial
// number or part number for real check in the deterministic bmc now. So
// we just assume that there is only one part information for each eeprom.
// This will fail theuhmm config validation if there are more than one
// part information for now so we can revisit this later.
if (eeprom.part_information().empty()) {
std::string error_message = absl::StrCat(
"Uhmm config error: physical bus ", bus.channel_id(), " Eeprom ",
eeprom.barepath(), " has no part information.");
LOG(ERROR) << error_message
<< " Stopping the i2c walker. eeprom: " << eeprom;
return absl::InternalError(error_message);
}
if (eeprom.part_information().size() > 1) {
std::string error_message = absl::StrCat(
"Uhmm config error: physical bus ", bus.channel_id(), " Eeprom ",
eeprom.barepath(), " has more than one part information.");
LOG(ERROR) << error_message
<< " Stopping the i2c walker. eeprom: " << eeprom;
return absl::InternalError(error_message);
}
if (!eeprom.part_information(0).has_part_number() ||
!eeprom.part_information(0).has_serial_number()) {
std::string error_message =
absl::StrCat("Uhmm config error: physical bus ", bus.channel_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.channel_id();
hardware_info.address = eeprom.address_identifier().address();
hardware_info.barepath = eeprom.barepath();
hardware_info.part_number = eeprom.part_information(0).part_number();
hardware_info.serial_number = eeprom.part_information(0).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(channel_id) will be the bus_id(channel_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.channel_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.
uhmm::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_channel_id()) {
std::string error_message = absl::StrCat(
"Uhmm config error: physical bus ", bus.channel_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 downstream bus is expected to have one eeprom, one sensor
// controller or one gpio controller. Error out if it has more than
// one. We are not expecting muxes under mux (downstream bus) for now.
if (downstream_bus.eeproms().size() +
downstream_bus.sensor_controllers().size() +
downstream_bus.gpio_controllers().size() !=
1) {
std::string error_message = absl::StrCat(
"Uhmm config error: physical bus ", bus.channel_id(), " mux ",
mux.model(), " at address ", mux_address_identifier.address(),
" downstream bus (channel id ", downstream_bus.channel_id(),
") has more than one eeprom, sensor controller or gpio "
"controller which is not supported yet.");
LOG(ERROR) << error_message
<< " Stopping the i2c walker. "
"downstream_bus: "
<< downstream_bus;
return absl::InternalError(error_message);
}
if (!downstream_bus.muxes().empty()) {
std::string error_message = absl::StrCat(
"Unimplemented error: physical bus ", bus.channel_id(), " mux ",
mux.model(), " address ", mux_address_identifier.address(),
" channel ", downstream_bus.channel_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);
}
// Deal with eeproms on the downstream bus.
for (const auto& eeprom : downstream_bus.eeproms()) {
if (!eeprom.has_address_identifier()) {
std::string error_message = absl::StrCat(
"Uhmm config error: physical bus ", bus.channel_id(), " mux ",
mux.model(), " address ", mux_address_identifier.address(),
" channel ", downstream_bus.channel_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);
}
// We are not complicating the logic for now as we don't use serial
// number or part number for real check in the deterministic bmc now.
// So we just assume that there is only one part information for each
// eeprom. This will fail the UHMM config validation if there are more
// than one part information for now so we can revisit this later.
if (eeprom.part_information().empty()) {
std::string error_message = absl::StrCat(
"Uhmm config error: physical bus ", bus.channel_id(),
" Eeprom ", eeprom.barepath(), " has no part information.");
LOG(ERROR) << error_message
<< " Stopping the i2c walker. eeprom: " << eeprom;
return absl::InternalError(error_message);
}
if (eeprom.part_information().size() > 1) {
std::string error_message =
absl::StrCat("Uhmm config error: physical bus ",
bus.channel_id(), " Eeprom ", eeprom.barepath(),
" has more than one part information.");
LOG(ERROR) << error_message
<< " Stopping the i2c walker. eeprom: " << eeprom;
return absl::InternalError(error_message);
}
if (!eeprom.part_information(0).has_part_number() ||
!eeprom.part_information(0).has_serial_number()) {
std::string error_message = absl::StrCat(
"Uhmm config error: physical bus ", bus.channel_id(), " mux ",
mux.model(), " address ", mux_address_identifier.address(),
" channel ", downstream_bus.channel_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.channel_id(), mux_address_identifier,
mux_model, downstream_bus.channel_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.channel_id(), " channel ", downstream_bus.channel_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(0).part_number();
hardware_info.serial_number =
eeprom.part_information(0).serial_number();
hardware_info.not_found_info = {
.status = logical_bus_id.status(),
.physical_bus = static_cast<uint32_t>(bus.channel_id()),
.mux_model = mux_model,
.mux_address =
static_cast<uint32_t>(mux_address_identifier.address()),
.channel = static_cast<uint32_t>(downstream_bus.channel_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(0).part_number();
hardware_info.serial_number =
eeprom.part_information(0).serial_number();
hardware_i2c_info.push_back(std::move(hardware_info));
}
// Deal with sensor controllers on the downstream bus.
for (const auto& sensor_controller :
downstream_bus.sensor_controllers()) {
if (!sensor_controller.has_address_identifier()) {
std::string error_message = absl::StrCat(
"Uhmm config error: physical bus ", bus.channel_id(), " mux ",
mux.model(), " address ", mux_address_identifier.address(),
" channel ", downstream_bus.channel_id(), " sensor controller ",
sensor_controller.barepath(),
" does not have an address identifier.");
LOG(ERROR) << error_message
<< " Stopping the i2c walker. sensor_controller: "
<< sensor_controller;
return absl::InternalError(error_message);
}
// Expect each sensor controller have to have a model for setup i2c
// sensor device.
if (!sensor_controller.has_model()) {
std::string error_message = absl::StrCat(
"Uhmm config error: physical bus ", bus.channel_id(), " mux ",
mux.model(), " address ", mux_address_identifier.address(),
" channel ", downstream_bus.channel_id(), " sensor controller ",
sensor_controller.barepath(), " does not have a model.");
LOG(ERROR) << error_message
<< " Stopping the i2c walker. sensor_controller: "
<< sensor_controller;
return absl::InternalError(error_message);
}
std::string sensor_controller_model = sensor_controller.model();
// We don't expect the barepath for sensor controller itself as this
// usually not a FRU device. Also, we technically can use the sensor
// controller model for identification, but we are not doing any of
// that now to keep logic simple.
absl::StatusOr<uint32_t> logical_bus_id = GetLogicalBusId(
root_directory_, bus.channel_id(), mux_address_identifier,
mux_model, downstream_bus.channel_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.channel_id(), " channel ", downstream_bus.channel_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);
}
bool is_not_found = absl::IsNotFound(logical_bus_id.status());
for (const auto& monitored_component :
sensor_controller.monitored_components()) {
HardwareInfo hardware_info;
// Expect each monitored component to have a barepath.
if (!monitored_component.has_barepath()) {
std::string error_message = absl::StrCat(
"Uhmm config error: physical bus ", bus.channel_id(), " mux ",
mux.model(), " address ", mux_address_identifier.address(),
" channel ", downstream_bus.channel_id(),
" sensor controller ", sensor_controller.barepath(),
" monitored component does not have a barepath.");
LOG(ERROR) << error_message
<< " Stopping the i2c walker. sensor_controller: "
<< sensor_controller;
return absl::InternalError(error_message);
}
hardware_info.barepath = monitored_component.barepath();
// Expect each monitored component to have a sensor id.
if (!monitored_component.has_sensor_id()) {
std::string error_message = absl::StrCat(
"Uhmm config error: physical bus ", bus.channel_id(), " mux ",
mux.model(), " address ", mux_address_identifier.address(),
" channel ", downstream_bus.channel_id(),
" sensor controller ", sensor_controller.barepath(),
" monitored component does not have a sensor id.");
LOG(ERROR) << error_message
<< " Stopping the i2c walker. sensor_controller: "
<< sensor_controller;
return absl::InternalError(error_message);
}
hardware_info.sensor_id = monitored_component.sensor_id();
hardware_info.address =
sensor_controller.address_identifier().address();
hardware_info.sensor_controller_model = sensor_controller_model;
if (is_not_found) {
hardware_info.not_found_info = {
.status = logical_bus_id.status(),
.physical_bus = static_cast<uint32_t>(bus.channel_id()),
.mux_model = mux_model,
.mux_address =
static_cast<uint32_t>(mux_address_identifier.address()),
.channel = static_cast<uint32_t>(downstream_bus.channel_id()),
};
} else {
hardware_info.logical_bus = logical_bus_id.value();
}
hardware_i2c_info.push_back(std::move(hardware_info));
}
}
}
}
}
return hardware_i2c_info;
}
} // namespace deterministic_bmc