blob: 569be59d9749477479dbd5419d5dc6f24bca3a52 [file]
/*
* SPDX-FileCopyrightText: Copyright OpenBMC Authors
* SPDX-License-Identifier: Apache-2.0
*/
#include "NvidiaGpuDevice.hpp"
#include "Thresholds.hpp"
#include "Utils.hpp"
#include <Inventory.hpp>
#include <MctpRequester.hpp>
#include <NvidiaDriverInformation.hpp>
#include <NvidiaEventReporting.hpp>
#include <NvidiaGpuClockFrequencyMetric.hpp>
#include <NvidiaGpuClockSpeedControl.hpp>
#include <NvidiaGpuEccMode.hpp>
#include <NvidiaGpuEnergySensor.hpp>
#include <NvidiaGpuMctpVdm.hpp>
#include <NvidiaGpuMemoryCapacityUtilization.hpp>
#include <NvidiaGpuMemoryClockFrequency.hpp>
#include <NvidiaGpuMemoryDevice.hpp>
#include <NvidiaGpuPowerControl.hpp>
#include <NvidiaGpuPowerPeakReading.hpp>
#include <NvidiaGpuPowerSensor.hpp>
#include <NvidiaGpuResetRequired.hpp>
#include <NvidiaGpuTempSensor.hpp>
#include <NvidiaGpuUtilizationMetrics.hpp>
#include <NvidiaGpuViolationDuration.hpp>
#include <NvidiaGpuVoltageSensor.hpp>
#include <NvidiaGpuXid.hpp>
#include <NvidiaLongRunningHandler.hpp>
#include <NvidiaPcieFunction.hpp>
#include <NvidiaPcieInterface.hpp>
#include <NvidiaPciePort.hpp>
#include <NvidiaPciePortMetrics.hpp>
#include <NvidiaSensorConfig.hpp>
#include <NvidiaUtils.hpp>
#include <OcpMctpVdm.hpp>
#include <SerialQueue.hpp>
#include <boost/asio/io_context.hpp>
#include <phosphor-logging/lg2.hpp>
#include <sdbusplus/asio/connection.hpp>
#include <sdbusplus/asio/object_server.hpp>
#include <sdbusplus/message/native_types.hpp>
#include <array>
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <format>
#include <functional>
#include <initializer_list>
#include <limits>
#include <memory>
#include <span>
#include <string>
#include <system_error>
#include <utility>
#include <vector>
// Each long-running command can take up to 2s on timeout; worst-case
// capacity is ~15 commands/device.
static constexpr auto longRunningSensorPollRate = std::chrono::seconds{30};
static constexpr const char* controlPowerPrefix =
"/xyz/openbmc_project/control/power/";
static constexpr uint8_t gpuTLimitCriticalThresholdId{1};
static constexpr uint8_t gpuTLimitWarningThresholdId{2};
static constexpr uint8_t gpuTLimitHardshutDownThresholdId{4};
// nota bene: the order has to match the order in processTLimitThresholds
static constexpr auto thresholdIds = std::to_array<uint8_t>(
{gpuTLimitWarningThresholdId, gpuTLimitCriticalThresholdId,
gpuTLimitHardshutDownThresholdId});
static constexpr const char* dramIfaceName =
"xyz.openbmc_project.Inventory.Item.Dimm";
GpuDevice::GpuDevice(const SensorConfigs& configs, const std::string& name,
const sdbusplus::object_path& path,
const std::shared_ptr<sdbusplus::asio::connection>& conn,
uint8_t eid, boost::asio::io_context& io,
mctp::MctpRequester& mctpRequester,
sdbusplus::asio::object_server& objectServer) :
eid(eid), sensorPollMs(std::chrono::milliseconds{configs.pollRate}),
waitTimer(io, std::chrono::steady_clock::duration(0)),
waitTimerLongRunning(io, std::chrono::steady_clock::duration(0)),
mctpRequester(mctpRequester), io(io), conn(conn),
objectServer(objectServer), configs(configs), name(escapeName(name)),
path(path)
{
const std::string powerControlPath = controlPowerPrefix + this->name;
powerCapInterface = objectServer.add_interface(
powerControlPath, "xyz.openbmc_project.Control.Power.Cap");
powerCapInterface->register_property("MinPowerCapValue", uint32_t{0});
powerCapInterface->register_property("MaxPowerCapValue",
std::numeric_limits<uint32_t>::max());
powerCapInterface->register_property("DefaultPowerCap",
std::numeric_limits<uint32_t>::max());
const sdbusplus::object_path gpuPath = inventoryPrefix / this->name;
const sdbusplus::object_path dramPath =
inventoryPrefix / std::format("{}_{}", this->name, dramInventorySuffix);
std::vector<Association> associations;
associations.emplace_back("contained_by", "containing", gpuPath);
dramAssociationInterface =
objectServer.add_interface(dramPath, association::interface);
dramAssociationInterface->register_property("Associations", associations);
if (!dramAssociationInterface->initialize())
{
lg2::error(
"Error initializing DRAM association interface for {NAME}, eid={EID}",
"NAME", this->name, "EID", eid);
}
dramItemInterface = objectServer.add_interface(dramPath, dramIfaceName);
dramItemInterface->register_property(
"MemoryType",
std::string("xyz.openbmc_project.Inventory.Item.Dimm.DeviceType.HBM"));
dramItemInterface->register_property(
"ECC", std::string(
"xyz.openbmc_project.Inventory.Item.Dimm.Ecc.SingleBitECC"));
dramItemInterface->register_property("MemorySizeInKB", size_t{0});
dramItemInterface->register_property("MemoryConfiguredSpeedInMhz",
uint16_t{0});
dramItemInterface->register_property("AllowedSpeedsMT",
std::vector<uint16_t>(2, 0));
if (!dramItemInterface->initialize())
{
lg2::error(
"Error initializing DRAM Item.Dimm interface for {NAME}, eid={EID}",
"NAME", this->name, "EID", eid);
}
}
GpuDevice::~GpuDevice()
{
objectServer.remove_interface(powerCapInterface);
objectServer.remove_interface(dramAssociationInterface);
objectServer.remove_interface(dramItemInterface);
}
void GpuDevice::init()
{
inventory = std::make_shared<Inventory>(
conn, objectServer, name, mctpRequester,
gpu::DeviceIdentification::DEVICE_GPU, eid, io, powerCapInterface,
dramItemInterface);
inventory->init();
makeSensors();
eventReporting->init();
}
void GpuDevice::makeSensors()
{
tempSensor = std::make_shared<NvidiaGpuTempSensor>(
conn, mctpRequester, name + "_TEMP_0", path, eid, gpuTempSensorId,
objectServer, std::vector<thresholds::Threshold>{},
gpu::DeviceIdentification::DEVICE_GPU);
dramTempSensor = std::make_shared<NvidiaGpuTempSensor>(
conn, mctpRequester, name + "_DRAM_0_TEMP_0", path, eid,
gpuDramTempSensorId, objectServer,
std::vector<thresholds::Threshold>{thresholds::Threshold{
thresholds::Level::CRITICAL, thresholds::Direction::HIGH, 95.0}},
gpu::DeviceIdentification::DEVICE_GPU);
powerSensor = std::make_shared<NvidiaGpuPowerSensor>(
conn, mctpRequester, name + "_Power_0", path, eid, gpuPowerSensorId,
objectServer, std::vector<thresholds::Threshold>{},
gpu::DeviceIdentification::DEVICE_GPU);
peakPower = std::make_shared<NvidiaGpuPowerPeakReading>(
mctpRequester, name + "_Power_0", eid, gpuPeakPowerSensorId,
objectServer);
energySensor = std::make_shared<NvidiaGpuEnergySensor>(
conn, mctpRequester, name + "_Energy_0", path, eid, gpuEnergySensorId,
objectServer, std::vector<thresholds::Threshold>{},
gpu::DeviceIdentification::DEVICE_GPU);
voltageSensor = std::make_shared<NvidiaGpuVoltageSensor>(
conn, mctpRequester, name + "_Voltage_0", path, eid, gpuVoltageSensorId,
objectServer, std::vector<thresholds::Threshold>{},
gpu::DeviceIdentification::DEVICE_GPU);
longRunningQueue = std::make_shared<SerialQueue>(io);
longRunningHandler = std::make_shared<NvidiaLongRunningResponseHandler>(io);
xidEventHandler = std::make_shared<NvidiaXidEventHandler>(name, conn);
resetRequiredEventHandler =
std::make_shared<NvidiaResetRequiredEventHandler>(name, conn);
eventReporting = std::make_shared<NvidiaEventReportingConfig>(
eid, mctpRequester,
std::initializer_list<EventDescriptor>{
{gpu::MessageType::DEVICE_CAPABILITY_DISCOVERY,
static_cast<uint8_t>(
gpu::DeviceCapabilityDiscoveryEvents::LONG_RUNNING_RESPONSE),
std::bind_front(&NvidiaLongRunningResponseHandler::handler,
longRunningHandler)},
{gpu::MessageType::PLATFORM_ENVIRONMENTAL,
static_cast<uint8_t>(gpu::PlatformEnvironmentalEvent::XID),
std::bind_front(&NvidiaXidEventHandler::handleXidEvent,
xidEventHandler)},
{gpu::MessageType::PLATFORM_ENVIRONMENTAL,
static_cast<uint8_t>(
gpu::PlatformEnvironmentalEvent::RESET_REQUIRED),
std::bind_front(
&NvidiaResetRequiredEventHandler::handleResetRequiredEvent,
resetRequiredEventHandler)}});
utilizationMetrics = std::make_shared<NvidiaGpuUtilizationMetrics>(
mctpRequester, objectServer, name, eid, longRunningQueue,
longRunningHandler);
violationDuration = std::make_shared<NvidiaGpuViolationDuration>(
mctpRequester, objectServer, name, eid, longRunningQueue,
longRunningHandler);
eccMode = std::make_shared<NvidiaGpuEccMode>(
mctpRequester, objectServer, name, eid, longRunningQueue,
longRunningHandler);
memoryCapacityUtilization =
std::make_shared<NvidiaGpuMemoryCapacityUtilization>(
mctpRequester, objectServer, name, eid, longRunningQueue,
longRunningHandler, inventory);
driverInfo = std::make_shared<NvidiaDriverInformation>(
conn, mctpRequester, name, eid, objectServer, path.parent_path());
gpuPowerControl = std::make_shared<NvidiaGpuPowerControl>(
objectServer, name, mctpRequester, eid, io, powerCapInterface,
inventory);
gpuClockSpeedControl = std::make_shared<NvidiaGpuClockSpeedControl>(
objectServer, name, mctpRequester, eid, io, inventory);
pcieInterface = std::make_shared<NvidiaPcieInterface>(
conn, mctpRequester, name, path, eid, objectServer,
gpu::DeviceIdentification::DEVICE_GPU);
pciePort = std::make_shared<NvidiaPciePortInfo>(
conn, mctpRequester, "UP_0", name, path, eid,
gpu::PciePortType::UPSTREAM, 0, 0, objectServer,
gpu::DeviceIdentification::DEVICE_GPU);
pcieFunction = std::make_shared<NvidiaPcieFunction>(
conn, mctpRequester, name, path, eid, 0, objectServer,
gpu::DeviceIdentification::DEVICE_GPU);
pciePortMetrics.emplace_back(makeNvidiaPciePortErrors(
conn, mctpRequester, "UP_0", name, path, eid,
gpu::PciePortType::UPSTREAM, 0, 0, objectServer,
gpu::DeviceIdentification::DEVICE_GPU));
pciePortMetrics.emplace_back(makeNvidiaPciePortCounters(
conn, mctpRequester, "UP_0", name, path, eid,
gpu::PciePortType::UPSTREAM, 0, 0, objectServer,
gpu::DeviceIdentification::DEVICE_GPU));
pciePortMetrics.emplace_back(makeNvidiaPciePortL0ToRecoveryCount(
conn, mctpRequester, "UP_0", name, path, eid,
gpu::PciePortType::UPSTREAM, 0, 0, objectServer,
gpu::DeviceIdentification::DEVICE_GPU));
memoryDevice = std::make_shared<NvidiaGpuMemoryDevice>(
conn, mctpRequester, name, eid, objectServer);
memoryClockFrequency = std::make_shared<NvidiaGpuMemoryClockFrequency>(
mctpRequester, name, eid, dramItemInterface);
clockFrequencyMetric = std::make_shared<NvidiaGpuClockFrequencyMetric>(
mctpRequester, name, eid, objectServer);
getTLimitThresholds();
lg2::info("Added GPU {NAME} Sensors with chassis path: {PATH}.", "NAME",
name, "PATH", path);
read();
readLongRunning();
}
void GpuDevice::getTLimitThresholds()
{
thresholds = {};
current_threshold_index = 0;
getNextThermalParameter();
}
void GpuDevice::readThermalParameterCallback(const std::error_code& ec,
std::span<const uint8_t> buffer)
{
if (ec)
{
lg2::error(
"Error reading thermal parameter: sending message over MCTP failed, rc={RC}",
"RC", ec.message());
processTLimitThresholds(ec);
return;
}
ocp::accelerator_management::CompletionCode cc{};
uint16_t reasonCode = 0;
int32_t threshold = 0;
int rc = gpu::decodeReadThermalParametersResponse(buffer, cc, reasonCode,
threshold);
if (rc != 0 || cc != ocp::accelerator_management::CompletionCode::SUCCESS)
{
lg2::error(
"Error reading thermal parameter: decode failed, rc={RC}, cc={CC}, reasonCode={RESC}",
"RC", rc, "CC", cc, "RESC", reasonCode);
processTLimitThresholds(
std::make_error_code(std::errc::protocol_error));
return;
}
thresholds[current_threshold_index] = threshold;
current_threshold_index++;
if (current_threshold_index < thresholdIds.size())
{
getNextThermalParameter();
return;
}
processTLimitThresholds(std::error_code{});
}
void GpuDevice::getNextThermalParameter()
{
uint8_t id = thresholdIds[current_threshold_index];
auto rc =
gpu::encodeReadThermalParametersRequest(0, id, thermalParamReqMsg);
if (rc != 0)
{
lg2::error(
"Error reading thermal parameter for eid {EID} and parameter id {PID} : encode failed. rc={RC}",
"EID", eid, "PID", id, "RC", rc);
processTLimitThresholds(
std::make_error_code(static_cast<std::errc>(rc)));
return;
}
mctpRequester.sendRecvMsg(
eid, thermalParamReqMsg,
[weak{weak_from_this()}](const std::error_code& ec,
std::span<const uint8_t> buffer) {
std::shared_ptr<GpuDevice> self = weak.lock();
if (!self)
{
lg2::error("Failed to get lock on GpuDevice");
return;
}
self->readThermalParameterCallback(ec, buffer);
});
}
void GpuDevice::processTLimitThresholds(const std::error_code& ec)
{
std::vector<thresholds::Threshold> tLimitThresholds{};
if (!ec)
{
tLimitThresholds = {
thresholds::Threshold{thresholds::Level::WARNING,
thresholds::Direction::LOW,
static_cast<double>(thresholds[0])},
thresholds::Threshold{thresholds::Level::CRITICAL,
thresholds::Direction::LOW,
static_cast<double>(thresholds[1])},
thresholds::Threshold{thresholds::Level::HARDSHUTDOWN,
thresholds::Direction::LOW,
static_cast<double>(thresholds[2])}};
}
tLimitSensor = std::make_shared<NvidiaGpuTempSensor>(
conn, mctpRequester, name + "_TEMP_1", path, eid, gpuTLimitSensorId,
objectServer, std::move(tLimitThresholds),
gpu::DeviceIdentification::DEVICE_GPU);
}
void GpuDevice::read()
{
tempSensor->update();
if (tLimitSensor)
{
tLimitSensor->update();
}
dramTempSensor->update();
powerSensor->update();
peakPower->update();
energySensor->update();
voltageSensor->update();
driverInfo->update();
gpuPowerControl->update();
gpuClockSpeedControl->update();
pcieInterface->update();
pciePort->update();
pcieFunction->update();
for (auto& metrics : pciePortMetrics)
{
metrics->update();
}
memoryDevice->update();
memoryClockFrequency->update();
clockFrequencyMetric->update();
waitTimer.expires_after(std::chrono::milliseconds(sensorPollMs));
waitTimer.async_wait(
[weak{weak_from_this()}](const boost::system::error_code& ec) {
std::shared_ptr<GpuDevice> self = weak.lock();
if (!self)
{
lg2::error("Invalid reference to GpuDevice");
return;
}
if (ec)
{
return;
}
self->read();
});
}
void GpuDevice::readLongRunning()
{
utilizationMetrics->update();
violationDuration->update();
eccMode->update();
memoryCapacityUtilization->update();
waitTimerLongRunning.expires_after(longRunningSensorPollRate);
waitTimerLongRunning.async_wait(
[weak{weak_from_this()}](const boost::system::error_code& ec) {
std::shared_ptr<GpuDevice> self = weak.lock();
if (!self)
{
lg2::error("Invalid reference to GpuDevice");
return;
}
if (ec)
{
return;
}
self->readLongRunning();
});
}