summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--callback-manager/include/callback_manager.hpp13
-rw-r--r--host_error_monitor/CMakeLists.txt2
-rw-r--r--host_error_monitor/src/host_error_monitor.cpp1190
-rw-r--r--libpeci/.clang-format98
-rw-r--r--libpeci/CMakeLists.txt20
-rw-r--r--libpeci/peci.c1081
-rw-r--r--libpeci/peci.h224
-rw-r--r--libpeci/peci_cmds.c370
-rw-r--r--prov-mode-mgr/src/prov-mode-mgr.cpp12
-rw-r--r--psu-manager/include/cold_redundancy.hpp4
-rw-r--r--psu-manager/src/cold_redundancy.cpp168
-rw-r--r--services/smbios-mdrv2/src/cpu.cpp5
-rw-r--r--services/smbios/src/cpu.cpp5
-rw-r--r--services/smbios/src/dimm.cpp5
-rw-r--r--settings/include/defaults.hpp7
-rw-r--r--virtual-media/.clang-format98
-rw-r--r--virtual-media/CMakeLists.txt144
-rw-r--r--virtual-media/LICENSE201
-rw-r--r--virtual-media/cmake/Findudev.cmake77
-rw-r--r--virtual-media/src/main.cpp1087
-rw-r--r--virtual-media/virtual-media.json30
-rw-r--r--virtual-media/xyz.openbmc_project.VirtualMedia.service11
22 files changed, 4808 insertions, 44 deletions
diff --git a/callback-manager/include/callback_manager.hpp b/callback-manager/include/callback_manager.hpp
index 4ecdd60..490e596 100644
--- a/callback-manager/include/callback_manager.hpp
+++ b/callback-manager/include/callback_manager.hpp
@@ -12,7 +12,8 @@ constexpr const char* sensorPath = "/xyz/openbmc_project/sensors";
constexpr const char* globalInventoryIface =
"xyz.openbmc_project.Inventory.Item.Global";
-constexpr const char* associationIface = "org.openbmc.Associations";
+constexpr const char* associationIface =
+ "xyz.openbmc_project.Association.Definitions";
namespace threshold
{
@@ -29,8 +30,8 @@ struct AssociationManager
sensorAssociation(
objectServer.add_interface(sensorPath, associationIface))
{
- association->register_property("associations", std::set<Association>());
- sensorAssociation->register_property("associations",
+ association->register_property("Associations", std::set<Association>());
+ sensorAssociation->register_property("Associations",
std::set<Association>());
association->initialize();
sensorAssociation->initialize();
@@ -61,7 +62,7 @@ struct AssociationManager
{
result.emplace(threshold::warning, "", path);
}
- association->set_property("associations", result);
+ association->set_property("Associations", result);
}
void setSensorAssociations(const std::vector<std::string>& critical,
@@ -84,10 +85,10 @@ struct AssociationManager
}
result.emplace(threshold::warning, "", path);
}
- sensorAssociation->set_property("associations", result);
+ sensorAssociation->set_property("Associations", result);
}
sdbusplus::asio::object_server& objectServer;
std::shared_ptr<sdbusplus::asio::dbus_interface> association;
std::shared_ptr<sdbusplus::asio::dbus_interface> sensorAssociation;
-}; \ No newline at end of file
+};
diff --git a/host_error_monitor/CMakeLists.txt b/host_error_monitor/CMakeLists.txt
index ce7dbc1..43f9510 100644
--- a/host_error_monitor/CMakeLists.txt
+++ b/host_error_monitor/CMakeLists.txt
@@ -7,7 +7,7 @@ add_executable (host-error-monitor src/host_error_monitor.cpp)
target_include_directories (host-error-monitor PRIVATE ${CMAKE_SOURCE_DIR})
-target_link_libraries (host-error-monitor sdbusplus -lsystemd gpiodcxx)
+target_link_libraries (host-error-monitor sdbusplus -lsystemd gpiodcxx peci)
include_directories (${CMAKE_CURRENT_SOURCE_DIR}/include)
diff --git a/host_error_monitor/src/host_error_monitor.cpp b/host_error_monitor/src/host_error_monitor.cpp
index 07ee69a..900ead6 100644
--- a/host_error_monitor/src/host_error_monitor.cpp
+++ b/host_error_monitor/src/host_error_monitor.cpp
@@ -13,12 +13,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.
*/
+#include <peci.h>
#include <systemd/sd-journal.h>
+#include <bitset>
#include <boost/asio/posix/stream_descriptor.hpp>
#include <gpiod.hpp>
#include <iostream>
#include <sdbusplus/asio/object_server.hpp>
+#include <variant>
namespace host_error_monitor
{
@@ -27,23 +30,145 @@ static std::shared_ptr<sdbusplus::asio::connection> conn;
static bool hostOff = true;
-const static constexpr int caterrTimeoutMs = 1000;
+const static constexpr int caterrTimeoutMs = 2000;
+const static constexpr int errTimeoutMs = 90000;
+const static constexpr int smiTimeoutMs = 90000;
const static constexpr int crashdumpTimeoutS = 300;
// Timers
// Timer for CATERR asserted
static boost::asio::steady_timer caterrAssertTimer(io);
+// Timer for ERR0 asserted
+static boost::asio::steady_timer err0AssertTimer(io);
+// Timer for ERR1 asserted
+static boost::asio::steady_timer err1AssertTimer(io);
+// Timer for ERR2 asserted
+static boost::asio::steady_timer err2AssertTimer(io);
+// Timer for SMI asserted
+static boost::asio::steady_timer smiAssertTimer(io);
// GPIO Lines and Event Descriptors
static gpiod::line caterrLine;
static boost::asio::posix::stream_descriptor caterrEvent(io);
+static gpiod::line err0Line;
+static boost::asio::posix::stream_descriptor err0Event(io);
+static gpiod::line err1Line;
+static boost::asio::posix::stream_descriptor err1Event(io);
+static gpiod::line err2Line;
+static boost::asio::posix::stream_descriptor err2Event(io);
+static gpiod::line smiLine;
+static boost::asio::posix::stream_descriptor smiEvent(io);
+static gpiod::line cpu1FIVRFaultLine;
+static gpiod::line cpu1ThermtripLine;
+static boost::asio::posix::stream_descriptor cpu1ThermtripEvent(io);
+static gpiod::line cpu2FIVRFaultLine;
+static gpiod::line cpu2ThermtripLine;
+static boost::asio::posix::stream_descriptor cpu2ThermtripEvent(io);
+static gpiod::line cpu1VRHotLine;
+static boost::asio::posix::stream_descriptor cpu1VRHotEvent(io);
+static gpiod::line cpu2VRHotLine;
+static boost::asio::posix::stream_descriptor cpu1MemABCDVRHotEvent(io);
+static gpiod::line cpu1MemEFGHVRHotLine;
+static boost::asio::posix::stream_descriptor cpu1MemEFGHVRHotEvent(io);
+static gpiod::line cpu2MemABCDVRHotLine;
+static boost::asio::posix::stream_descriptor cpu2VRHotEvent(io);
+static gpiod::line cpu1MemABCDVRHotLine;
+static boost::asio::posix::stream_descriptor cpu2MemABCDVRHotEvent(io);
+static gpiod::line cpu2MemEFGHVRHotLine;
+static boost::asio::posix::stream_descriptor cpu2MemEFGHVRHotEvent(io);
//----------------------------------
// PCH_BMC_THERMTRIP function related definition
//----------------------------------
-// GPIO Lines and Event Descriptors
static gpiod::line pchThermtripLine;
static boost::asio::posix::stream_descriptor pchThermtripEvent(io);
+static void cpuIERRLog()
+{
+ sd_journal_send("MESSAGE=HostError: IERR", "PRIORITY=%i", LOG_INFO,
+ "REDFISH_MESSAGE_ID=%s", "OpenBMC.0.1.CPUError",
+ "REDFISH_MESSAGE_ARGS=%s", "IERR", NULL);
+}
+
+static void cpuIERRLog(const int cpuNum)
+{
+ std::string msg = "IERR on CPU " + std::to_string(cpuNum + 1);
+
+ sd_journal_send("MESSAGE=HostError: %s", msg.c_str(), "PRIORITY=%i",
+ LOG_INFO, "REDFISH_MESSAGE_ID=%s", "OpenBMC.0.1.CPUError",
+ "REDFISH_MESSAGE_ARGS=%s", msg.c_str(), NULL);
+}
+
+static void cpuIERRLog(const int cpuNum, const std::string& type)
+{
+ std::string msg = type + " IERR on CPU " + std::to_string(cpuNum + 1);
+
+ sd_journal_send("MESSAGE=HostError: %s", msg.c_str(), "PRIORITY=%i",
+ LOG_INFO, "REDFISH_MESSAGE_ID=%s", "OpenBMC.0.1.CPUError",
+ "REDFISH_MESSAGE_ARGS=%s", msg.c_str(), NULL);
+}
+
+static void cpuERRXLog(const int errPin)
+{
+ std::string msg = "ERR" + std::to_string(errPin) + " Timeout";
+
+ sd_journal_send("MESSAGE=HostError: %s", msg.c_str(), "PRIORITY=%i",
+ LOG_INFO, "REDFISH_MESSAGE_ID=%s", "OpenBMC.0.1.CPUError",
+ "REDFISH_MESSAGE_ARGS=%s", msg.c_str(), NULL);
+}
+
+static void cpuERRXLog(const int errPin, const int cpuNum)
+{
+ std::string msg = "ERR" + std::to_string(errPin) + " Timeout on CPU " +
+ std::to_string(cpuNum + 1);
+
+ sd_journal_send("MESSAGE=HostError: %s", msg.c_str(), "PRIORITY=%i",
+ LOG_INFO, "REDFISH_MESSAGE_ID=%s", "OpenBMC.0.1.CPUError",
+ "REDFISH_MESSAGE_ARGS=%s", msg.c_str(), NULL);
+}
+
+static void smiTimeoutLog()
+{
+ sd_journal_send("MESSAGE=HostError: SMI Timeout", "PRIORITY=%i", LOG_INFO,
+ "REDFISH_MESSAGE_ID=%s", "OpenBMC.0.1.CPUError",
+ "REDFISH_MESSAGE_ARGS=%s", "SMI Timeout", NULL);
+}
+
+static void cpuBootFIVRFaultLog(const int cpuNum)
+{
+ std::string msg = "Boot FIVR Fault on CPU " + std::to_string(cpuNum);
+
+ sd_journal_send("MESSAGE=HostError: %s", msg.c_str(), "PRIORITY=%i",
+ LOG_INFO, "REDFISH_MESSAGE_ID=%s", "OpenBMC.0.1.CPUError",
+ "REDFISH_MESSAGE_ARGS=%s", msg.c_str(), NULL);
+}
+
+static void cpuThermTripLog(const int cpuNum)
+{
+ std::string msg = "CPU " + std::to_string(cpuNum) + " thermal trip";
+
+ sd_journal_send("MESSAGE=HostError: %s", msg.c_str(), "PRIORITY=%i",
+ LOG_INFO, "REDFISH_MESSAGE_ID=%s",
+ "OpenBMC.0.1.CPUThermalTrip", "REDFISH_MESSAGE_ARGS=%d",
+ cpuNum, NULL);
+}
+
+static void cpuVRHotLog(const std::string& vr)
+{
+ std::string msg = vr + " Voltage Regulator Overheated.";
+
+ sd_journal_send("MESSAGE=HostError: %s", msg.c_str(), "PRIORITY=%i",
+ LOG_INFO, "REDFISH_MESSAGE_ID=%s",
+ "OpenBMC.0.1.VoltageRegulatorOverheated",
+ "REDFISH_MESSAGE_ARGS=%s", vr.c_str(), NULL);
+}
+
+static void ssbThermTripLog()
+{
+ sd_journal_send("MESSAGE=HostError: SSB thermal trip", "PRIORITY=%i",
+ LOG_INFO, "REDFISH_MESSAGE_ID=%s",
+ "OpenBMC.0.1.SsbThermalTrip", NULL);
+}
+
static void initializeErrorState();
static void initializeHostState()
{
@@ -97,11 +222,20 @@ static std::shared_ptr<sdbusplus::bus::match::match> startHostStateMonitor()
}
hostOff = state == "xyz.openbmc_project.State.Host.HostState.Off";
- // No host events should fire while off, so cancel any pending
- // timers
if (hostOff)
{
+ // No host events should fire while off, so cancel any pending
+ // timers
caterrAssertTimer.cancel();
+ err0AssertTimer.cancel();
+ err1AssertTimer.cancel();
+ err2AssertTimer.cancel();
+ smiAssertTimer.cancel();
+ }
+ else
+ {
+ // Handle any initial errors when the host turns on
+ initializeErrorState();
}
});
}
@@ -153,6 +287,30 @@ static bool requestGPIOEvents(
return true;
}
+static bool requestGPIOInput(const std::string& name, gpiod::line& gpioLine)
+{
+ // Find the GPIO line
+ gpioLine = gpiod::find_line(name);
+ if (!gpioLine)
+ {
+ std::cerr << "Failed to find the " << name << " line.\n";
+ return false;
+ }
+
+ // Request GPIO input
+ try
+ {
+ gpioLine.request({__FUNCTION__, gpiod::line_request::DIRECTION_INPUT});
+ }
+ catch (std::exception&)
+ {
+ std::cerr << "Failed to request " << name << " input\n";
+ return false;
+ }
+
+ return true;
+}
+
static void startPowerCycle()
{
conn->async_method_call(
@@ -221,9 +379,257 @@ static void startCrashdumpAndRecovery(bool recoverSystem)
"com.intel.crashdump.Stored", "GenerateStoredLog");
}
+static void incrementCPUErrorCount(int cpuNum)
+{
+ std::string propertyName = "ErrorCountCPU" + std::to_string(cpuNum + 1);
+
+ // Get the current count
+ conn->async_method_call(
+ [propertyName](boost::system::error_code ec,
+ const std::variant<uint8_t>& property) {
+ if (ec)
+ {
+ std::cerr << "Failed to read " << propertyName << ": "
+ << ec.message() << "\n";
+ return;
+ }
+ const uint8_t* errorCountVariant = std::get_if<uint8_t>(&property);
+ if (errorCountVariant == nullptr)
+ {
+ std::cerr << propertyName << " invalid\n";
+ return;
+ }
+ uint8_t errorCount = *errorCountVariant;
+ if (errorCount == std::numeric_limits<uint8_t>::max())
+ {
+ std::cerr << "Maximum error count reached\n";
+ return;
+ }
+ // Increment the count
+ errorCount++;
+ conn->async_method_call(
+ [propertyName](boost::system::error_code ec) {
+ if (ec)
+ {
+ std::cerr << "Failed to set " << propertyName << ": "
+ << ec.message() << "\n";
+ }
+ },
+ "xyz.openbmc_project.Settings",
+ "/xyz/openbmc_project/control/processor_error_config",
+ "org.freedesktop.DBus.Properties", "Set",
+ "xyz.openbmc_project.Control.Processor.ErrConfig", propertyName,
+ std::variant<uint8_t>{errorCount});
+ },
+ "xyz.openbmc_project.Settings",
+ "/xyz/openbmc_project/control/processor_error_config",
+ "org.freedesktop.DBus.Properties", "Get",
+ "xyz.openbmc_project.Control.Processor.ErrConfig", propertyName);
+}
+
+static bool checkIERRCPUs()
+{
+ bool cpuIERRFound = false;
+ for (int cpu = 0, addr = MIN_CLIENT_ADDR; addr <= MAX_CLIENT_ADDR;
+ cpu++, addr++)
+ {
+ uint8_t cc = 0;
+ CPUModel model{};
+ if (peci_GetCPUID(addr, &model, &cc) != PECI_CC_SUCCESS)
+ {
+ std::cerr << "Cannot get CPUID!\n";
+ continue;
+ }
+
+ switch (model)
+ {
+ case skx:
+ {
+ // First check the MCA_ERR_SRC_LOG to see if this is the CPU
+ // that caused the IERR
+ uint32_t mcaErrSrcLog = 0;
+ if (peci_RdPkgConfig(addr, 0, 5, 4, (uint8_t*)&mcaErrSrcLog,
+ &cc) != PECI_CC_SUCCESS)
+ {
+ continue;
+ }
+ // Check MSMI_INTERNAL (20) and IERR_INTERNAL (27)
+ if ((mcaErrSrcLog & (1 << 20)) || (mcaErrSrcLog & (1 << 27)))
+ {
+ // TODO: Light the CPU fault LED?
+ cpuIERRFound = true;
+ incrementCPUErrorCount(cpu);
+ // Next check if it's a CPU/VR mismatch by reading the
+ // IA32_MC4_STATUS MSR (0x411)
+ uint64_t mc4Status = 0;
+ if (peci_RdIAMSR(addr, 0, 0x411, &mc4Status, &cc) !=
+ PECI_CC_SUCCESS)
+ {
+ continue;
+ }
+ // Check MSEC bits 31:24 for
+ // MCA_SVID_VCCIN_VR_ICC_MAX_FAILURE (0x40),
+ // MCA_SVID_VCCIN_VR_VOUT_FAILURE (0x42), or
+ // MCA_SVID_CPU_VR_CAPABILITY_ERROR (0x43)
+ if ((mc4Status & (0x40 << 24)) ||
+ (mc4Status & (0x42 << 24)) ||
+ (mc4Status & (0x43 << 24)))
+ {
+ cpuIERRLog(cpu, "CPU/VR Mismatch");
+ continue;
+ }
+
+ // Next check if it's a Core FIVR fault by looking for a
+ // non-zero value of CORE_FIVR_ERR_LOG (B(1) D30 F2 offset
+ // 80h)
+ uint32_t coreFIVRErrLog = 0;
+ if (peci_RdPCIConfigLocal(
+ addr, 1, 30, 2, 0x80, sizeof(uint32_t),
+ (uint8_t*)&coreFIVRErrLog, &cc) != PECI_CC_SUCCESS)
+ {
+ continue;
+ }
+ if (coreFIVRErrLog)
+ {
+ cpuIERRLog(cpu, "Core FIVR Fault");
+ continue;
+ }
+
+ // Next check if it's an Uncore FIVR fault by looking for a
+ // non-zero value of UNCORE_FIVR_ERR_LOG (B(1) D30 F2 offset
+ // 84h)
+ uint32_t uncoreFIVRErrLog = 0;
+ if (peci_RdPCIConfigLocal(addr, 1, 30, 2, 0x84,
+ sizeof(uint32_t),
+ (uint8_t*)&uncoreFIVRErrLog,
+ &cc) != PECI_CC_SUCCESS)
+ {
+ continue;
+ }
+ if (uncoreFIVRErrLog)
+ {
+ cpuIERRLog(cpu, "Uncore FIVR Fault");
+ continue;
+ }
+
+ // Last if CORE_FIVR_ERR_LOG and UNCORE_FIVR_ERR_LOG are
+ // both zero, but MSEC bits 31:24 have either
+ // MCA_FIVR_CATAS_OVERVOL_FAULT (0x51) or
+ // MCA_FIVR_CATAS_OVERCUR_FAULT (0x52), then log it as an
+ // uncore FIVR fault
+ if (!coreFIVRErrLog && !uncoreFIVRErrLog &&
+ ((mc4Status & (0x51 << 24)) ||
+ (mc4Status & (0x52 << 24))))
+ {
+ cpuIERRLog(cpu, "Uncore FIVR Fault");
+ continue;
+ }
+ cpuIERRLog(cpu);
+ }
+ break;
+ }
+ case icx:
+ {
+ // First check the MCA_ERR_SRC_LOG to see if this is the CPU
+ // that caused the IERR
+ uint32_t mcaErrSrcLog = 0;
+ if (peci_RdPkgConfig(addr, 0, 5, 4, (uint8_t*)&mcaErrSrcLog,
+ &cc) != PECI_CC_SUCCESS)
+ {
+ continue;
+ }
+ // Check MSMI_INTERNAL (20) and IERR_INTERNAL (27)
+ if ((mcaErrSrcLog & (1 << 20)) || (mcaErrSrcLog & (1 << 27)))
+ {
+ // TODO: Light the CPU fault LED?
+ cpuIERRFound = true;
+ incrementCPUErrorCount(cpu);
+ // Next check if it's a CPU/VR mismatch by reading the
+ // IA32_MC4_STATUS MSR (0x411)
+ uint64_t mc4Status = 0;
+ if (peci_RdIAMSR(addr, 0, 0x411, &mc4Status, &cc) !=
+ PECI_CC_SUCCESS)
+ {
+ continue;
+ }
+ // TODO: Update MSEC/MSCOD_31_24 check
+ // Check MSEC bits 31:24 for
+ // MCA_SVID_VCCIN_VR_ICC_MAX_FAILURE (0x40),
+ // MCA_SVID_VCCIN_VR_VOUT_FAILURE (0x42), or
+ // MCA_SVID_CPU_VR_CAPABILITY_ERROR (0x43)
+ if ((mc4Status & (0x40 << 24)) ||
+ (mc4Status & (0x42 << 24)) ||
+ (mc4Status & (0x43 << 24)))
+ {
+ cpuIERRLog(cpu, "CPU/VR Mismatch");
+ continue;
+ }
+
+ // Next check if it's a Core FIVR fault by looking for a
+ // non-zero value of CORE_FIVR_ERR_LOG (B(31) D30 F2 offsets
+ // C0h and C4h) (Note: Bus 31 is accessed on PECI as bus 14)
+ uint32_t coreFIVRErrLog0 = 0;
+ uint32_t coreFIVRErrLog1 = 0;
+ if (peci_RdEndPointConfigPciLocal(
+ addr, 0, 14, 30, 2, 0xC0, sizeof(uint32_t),
+ (uint8_t*)&coreFIVRErrLog0, &cc) != PECI_CC_SUCCESS)
+ {
+ continue;
+ }
+ if (peci_RdEndPointConfigPciLocal(
+ addr, 0, 14, 30, 2, 0xC4, sizeof(uint32_t),
+ (uint8_t*)&coreFIVRErrLog1, &cc) != PECI_CC_SUCCESS)
+ {
+ continue;
+ }
+ if (coreFIVRErrLog0 || coreFIVRErrLog1)
+ {
+ cpuIERRLog(cpu, "Core FIVR Fault");
+ continue;
+ }
+
+ // Next check if it's an Uncore FIVR fault by looking for a
+ // non-zero value of UNCORE_FIVR_ERR_LOG (B(31) D30 F2
+ // offset 84h) (Note: Bus 31 is accessed on PECI as bus 14)
+ uint32_t uncoreFIVRErrLog = 0;
+ if (peci_RdEndPointConfigPciLocal(
+ addr, 0, 14, 30, 2, 0x84, sizeof(uint32_t),
+ (uint8_t*)&uncoreFIVRErrLog,
+ &cc) != PECI_CC_SUCCESS)
+ {
+ continue;
+ }
+ if (uncoreFIVRErrLog)
+ {
+ cpuIERRLog(cpu, "Uncore FIVR Fault");
+ continue;
+ }
+
+ // TODO: Update MSEC/MSCOD_31_24 check
+ // Last if CORE_FIVR_ERR_LOG and UNCORE_FIVR_ERR_LOG are
+ // both zero, but MSEC bits 31:24 have either
+ // MCA_FIVR_CATAS_OVERVOL_FAULT (0x51) or
+ // MCA_FIVR_CATAS_OVERCUR_FAULT (0x52), then log it as an
+ // uncore FIVR fault
+ if (!coreFIVRErrLog0 && !coreFIVRErrLog1 &&
+ !uncoreFIVRErrLog &&
+ ((mc4Status & (0x51 << 24)) ||
+ (mc4Status & (0x52 << 24))))
+ {
+ cpuIERRLog(cpu, "Uncore FIVR Fault");
+ continue;
+ }
+ cpuIERRLog(cpu);
+ }
+ break;
+ }
+ }
+ }
+ return cpuIERRFound;
+}
+
static void caterrAssertHandler()
{
- std::cout << "CPU CATERR detected, starting timer\n";
caterrAssertTimer.expires_after(std::chrono::milliseconds(caterrTimeoutMs));
caterrAssertTimer.async_wait([](const boost::system::error_code ec) {
if (ec)
@@ -235,10 +641,14 @@ static void caterrAssertHandler()
std::cerr << "caterr timeout async_wait failed: "
<< ec.message() << "\n";
}
- std::cout << "CATERR assert timer canceled\n";
return;
}
- std::cout << "CATERR asset timer completed\n";
+ std::cerr << "CATERR asserted for " << std::to_string(caterrTimeoutMs)
+ << " ms\n";
+ if (!checkIERRCPUs())
+ {
+ cpuIERRLog();
+ }
conn->async_method_call(
[](boost::system::error_code ec,
const std::variant<bool>& property) {
@@ -289,6 +699,267 @@ static void caterrHandler()
caterrHandler();
});
}
+
+static void cpu1ThermtripAssertHandler()
+{
+ if (cpu1FIVRFaultLine.get_value() == 0)
+ {
+ cpuBootFIVRFaultLog(1);
+ }
+ else
+ {
+ cpuThermTripLog(1);
+ }
+}
+
+static void cpu1ThermtripHandler()
+{
+ if (!hostOff)
+ {
+ gpiod::line_event gpioLineEvent = cpu1ThermtripLine.event_read();
+
+ bool cpu1Thermtrip =
+ gpioLineEvent.event_type == gpiod::line_event::FALLING_EDGE;
+ if (cpu1Thermtrip)
+ {
+ cpu1ThermtripAssertHandler();
+ }
+ }
+ cpu1ThermtripEvent.async_wait(
+ boost::asio::posix::stream_descriptor::wait_read,
+ [](const boost::system::error_code ec) {
+ if (ec)
+ {
+ std::cerr << "CPU 1 Thermtrip handler error: " << ec.message()
+ << "\n";
+ return;
+ }
+ cpu1ThermtripHandler();
+ });
+}
+
+static void cpu2ThermtripAssertHandler()
+{
+ if (cpu2FIVRFaultLine.get_value() == 0)
+ {
+ cpuBootFIVRFaultLog(2);
+ }
+ else
+ {
+ cpuThermTripLog(2);
+ }
+}
+
+static void cpu2ThermtripHandler()
+{
+ if (!hostOff)
+ {
+ gpiod::line_event gpioLineEvent = cpu2ThermtripLine.event_read();
+
+ bool cpu2Thermtrip =
+ gpioLineEvent.event_type == gpiod::line_event::FALLING_EDGE;
+ if (cpu2Thermtrip)
+ {
+ cpu2ThermtripAssertHandler();
+ }
+ }
+ cpu2ThermtripEvent.async_wait(
+ boost::asio::posix::stream_descriptor::wait_read,
+ [](const boost::system::error_code ec) {
+ if (ec)
+ {
+ std::cerr << "CPU 2 Thermtrip handler error: " << ec.message()
+ << "\n";
+ return;
+ }
+ cpu2ThermtripHandler();
+ });
+}
+
+static void cpu1VRHotAssertHandler()
+{
+ cpuVRHotLog("CPU 1");
+}
+
+static void cpu1VRHotHandler()
+{
+ if (!hostOff)
+ {
+ gpiod::line_event gpioLineEvent = cpu1VRHotLine.event_read();
+
+ bool cpu1VRHot =
+ gpioLineEvent.event_type == gpiod::line_event::FALLING_EDGE;
+ if (cpu1VRHot)
+ {
+ cpu1VRHotAssertHandler();
+ }
+ }
+ cpu1VRHotEvent.async_wait(boost::asio::posix::stream_descriptor::wait_read,
+ [](const boost::system::error_code ec) {
+ if (ec)
+ {
+ std::cerr << "CPU 1 VRHot handler error: "
+ << ec.message() << "\n";
+ return;
+ }
+ cpu1VRHotHandler();
+ });
+}
+
+static void cpu1MemABCDVRHotAssertHandler()
+{
+ cpuVRHotLog("CPU 1 Memory ABCD");
+}
+
+static void cpu1MemABCDVRHotHandler()
+{
+ if (!hostOff)
+ {
+ gpiod::line_event gpioLineEvent = cpu1MemABCDVRHotLine.event_read();
+
+ bool cpu1MemABCDVRHot =
+ gpioLineEvent.event_type == gpiod::line_event::FALLING_EDGE;
+ if (cpu1MemABCDVRHot)
+ {
+ cpu1MemABCDVRHotAssertHandler();
+ }
+ }
+ cpu1MemABCDVRHotEvent.async_wait(
+ boost::asio::posix::stream_descriptor::wait_read,
+ [](const boost::system::error_code ec) {
+ if (ec)
+ {
+ std::cerr << "CPU 1 Memory ABCD VRHot handler error: "
+ << ec.message() << "\n";
+ return;
+ }
+ cpu1MemABCDVRHotHandler();
+ });
+}
+
+static void cpu1MemEFGHVRHotAssertHandler()
+{
+ cpuVRHotLog("CPU 1 Memory EFGH");
+}
+
+static void cpu1MemEFGHVRHotHandler()
+{
+ if (!hostOff)
+ {
+ gpiod::line_event gpioLineEvent = cpu1MemEFGHVRHotLine.event_read();
+
+ bool cpu1MemEFGHVRHot =
+ gpioLineEvent.event_type == gpiod::line_event::FALLING_EDGE;
+ if (cpu1MemEFGHVRHot)
+ {
+ cpu1MemEFGHVRHotAssertHandler();
+ }
+ }
+ cpu1MemEFGHVRHotEvent.async_wait(
+ boost::asio::posix::stream_descriptor::wait_read,
+ [](const boost::system::error_code ec) {
+ if (ec)
+ {
+ std::cerr << "CPU 1 Memory EFGH VRHot handler error: "
+ << ec.message() << "\n";
+ return;
+ }
+ cpu1MemEFGHVRHotHandler();
+ });
+}
+
+static void cpu2VRHotAssertHandler()
+{
+ cpuVRHotLog("CPU 2");
+}
+
+static void cpu2VRHotHandler()
+{
+ if (!hostOff)
+ {
+ gpiod::line_event gpioLineEvent = cpu2VRHotLine.event_read();
+
+ bool cpu2VRHot =
+ gpioLineEvent.event_type == gpiod::line_event::FALLING_EDGE;
+ if (cpu2VRHot)
+ {
+ cpu2VRHotAssertHandler();
+ }
+ }
+ cpu2VRHotEvent.async_wait(boost::asio::posix::stream_descriptor::wait_read,
+ [](const boost::system::error_code ec) {
+ if (ec)
+ {
+ std::cerr << "CPU 2 VRHot handler error: "
+ << ec.message() << "\n";
+ return;
+ }
+ cpu2VRHotHandler();
+ });
+}
+
+static void cpu2MemABCDVRHotAssertHandler()
+{
+ cpuVRHotLog("CPU 2 Memory ABCD");
+}
+
+static void cpu2MemABCDVRHotHandler()
+{
+ if (!hostOff)
+ {
+ gpiod::line_event gpioLineEvent = cpu2MemABCDVRHotLine.event_read();
+
+ bool cpu2MemABCDVRHot =
+ gpioLineEvent.event_type == gpiod::line_event::FALLING_EDGE;
+ if (cpu2MemABCDVRHot)
+ {
+ cpu2MemABCDVRHotAssertHandler();
+ }
+ }
+ cpu2MemABCDVRHotEvent.async_wait(
+ boost::asio::posix::stream_descriptor::wait_read,
+ [](const boost::system::error_code ec) {
+ if (ec)
+ {
+ std::cerr << "CPU 2 Memory ABCD VRHot handler error: "
+ << ec.message() << "\n";
+ return;
+ }
+ cpu2MemABCDVRHotHandler();
+ });
+}
+
+static void cpu2MemEFGHVRHotAssertHandler()
+{
+ cpuVRHotLog("CPU 2 Memory EFGH");
+}
+
+static void cpu2MemEFGHVRHotHandler()
+{
+ if (!hostOff)
+ {
+ gpiod::line_event gpioLineEvent = cpu2MemEFGHVRHotLine.event_read();
+
+ bool cpu2MemEFGHVRHot =
+ gpioLineEvent.event_type == gpiod::line_event::FALLING_EDGE;
+ if (cpu2MemEFGHVRHot)
+ {
+ cpu2MemEFGHVRHotAssertHandler();
+ }
+ }
+ cpu2MemEFGHVRHotEvent.async_wait(
+ boost::asio::posix::stream_descriptor::wait_read,
+ [](const boost::system::error_code ec) {
+ if (ec)
+ {
+ std::cerr << "CPU 2 Memory EFGH VRHot handler error: "
+ << ec.message() << "\n";
+ return;
+ }
+ cpu2MemEFGHVRHotHandler();
+ });
+}
+
static void pchThermtripHandler()
{
if (!hostOff)
@@ -299,11 +970,7 @@ static void pchThermtripHandler()
gpioLineEvent.event_type == gpiod::line_event::FALLING_EDGE;
if (pchThermtrip)
{
- std::cout << "PCH Thermtrip detected \n";
- // log to redfish, call API
- sd_journal_send("MESSAGE=SSBThermtrip: SSB Thermtrip",
- "PRIORITY=%i", LOG_INFO, "REDFISH_MESSAGE_ID=%s",
- "OpenBMC.0.1.SSBThermtrip", NULL);
+ ssbThermTripLog();
}
}
pchThermtripEvent.async_wait(
@@ -311,7 +978,7 @@ static void pchThermtripHandler()
[](const boost::system::error_code ec) {
if (ec)
{
- std::cerr << "PCH Thermtrip handler error: " << ec.message()
+ std::cerr << "PCH Thermal trip handler error: " << ec.message()
<< "\n";
return;
}
@@ -319,6 +986,307 @@ static void pchThermtripHandler()
});
}
+static std::bitset<MAX_CPUS> checkERRPinCPUs(const int errPin)
+{
+ int errPinSts = (1 << errPin);
+ std::bitset<MAX_CPUS> errPinCPUs = 0;
+ for (int cpu = 0, addr = MIN_CLIENT_ADDR; addr <= MAX_CLIENT_ADDR;
+ cpu++, addr++)
+ {
+ if (peci_Ping(addr) == PECI_CC_SUCCESS)
+ {
+ uint8_t cc = 0;
+ CPUModel model{};
+ if (peci_GetCPUID(addr, &model, &cc) != PECI_CC_SUCCESS)
+ {
+ std::cerr << "Cannot get CPUID!\n";
+ continue;
+ }
+
+ switch (model)
+ {
+ case skx:
+ {
+ // Check the ERRPINSTS to see if this is the CPU that caused
+ // the ERRx (B(0) D8 F0 offset 210h)
+ uint32_t errpinsts = 0;
+ if (peci_RdPCIConfigLocal(
+ addr, 0, 8, 0, 0x210, sizeof(uint32_t),
+ (uint8_t*)&errpinsts, &cc) == PECI_CC_SUCCESS)
+ {
+ errPinCPUs[cpu] = (errpinsts & errPinSts) != 0;
+ }
+ break;
+ }
+ case icx:
+ {
+ // Check the ERRPINSTS to see if this is the CPU that caused
+ // the ERRx (B(30) D0 F3 offset 274h) (Note: Bus 30 is
+ // accessed on PECI as bus 13)
+ uint32_t errpinsts = 0;
+ if (peci_RdEndPointConfigPciLocal(
+ addr, 0, 13, 0, 3, 0x274, sizeof(uint32_t),
+ (uint8_t*)&errpinsts, &cc) == PECI_CC_SUCCESS)
+ {
+ errPinCPUs[cpu] = (errpinsts & errPinSts) != 0;
+ }
+ break;
+ }
+ }
+ }
+ }
+ return errPinCPUs;
+}
+
+static void errXAssertHandler(const int errPin,
+ boost::asio::steady_timer& errXAssertTimer)
+{
+ // ERRx status is not guaranteed through the timeout, so save which
+ // CPUs have it asserted
+ std::bitset<MAX_CPUS> errPinCPUs = checkERRPinCPUs(errPin);
+ errXAssertTimer.expires_after(std::chrono::milliseconds(errTimeoutMs));
+ errXAssertTimer.async_wait([errPin, errPinCPUs](
+ const boost::system::error_code ec) {
+ if (ec)
+ {
+ // operation_aborted is expected if timer is canceled before
+ // completion.
+ if (ec != boost::asio::error::operation_aborted)
+ {
+ std::cerr << "err2 timeout async_wait failed: " << ec.message()
+ << "\n";
+ }
+ return;
+ }
+ std::cerr << "ERR" << std::to_string(errPin) << " asserted for "
+ << std::to_string(errTimeoutMs) << " ms\n";
+ if (errPinCPUs.count())
+ {
+ for (int i = 0; i < errPinCPUs.size(); i++)
+ {
+ if (errPinCPUs[i])
+ {
+ cpuERRXLog(errPin, i);
+ }
+ }
+ }
+ else
+ {
+ cpuERRXLog(errPin);
+ }
+ });
+}
+
+static void err0AssertHandler()
+{
+ // Handle the standard ERR0 detection and logging
+ const static constexpr int err0 = 0;
+ errXAssertHandler(err0, err0AssertTimer);
+}
+
+static void err0Handler()
+{
+ if (!hostOff)
+ {
+ gpiod::line_event gpioLineEvent = err0Line.event_read();
+
+ bool err0 = gpioLineEvent.event_type == gpiod::line_event::FALLING_EDGE;
+ if (err0)
+ {
+ err0AssertHandler();
+ }
+ else
+ {
+ err0AssertTimer.cancel();
+ }
+ }
+ err0Event.async_wait(boost::asio::posix::stream_descriptor::wait_read,
+ [](const boost::system::error_code ec) {
+ if (ec)
+ {
+ std::cerr
+ << "err0 handler error: " << ec.message()
+ << "\n";
+ return;
+ }
+ err0Handler();
+ });
+}
+
+static void err1AssertHandler()
+{
+ // Handle the standard ERR1 detection and logging
+ const static constexpr int err1 = 1;
+ errXAssertHandler(err1, err1AssertTimer);
+}
+
+static void err1Handler()
+{
+ if (!hostOff)
+ {
+ gpiod::line_event gpioLineEvent = err1Line.event_read();
+
+ bool err1 = gpioLineEvent.event_type == gpiod::line_event::FALLING_EDGE;
+ if (err1)
+ {
+ err1AssertHandler();
+ }
+ else
+ {
+ err1AssertTimer.cancel();
+ }
+ }
+ err1Event.async_wait(boost::asio::posix::stream_descriptor::wait_read,
+ [](const boost::system::error_code ec) {
+ if (ec)
+ {
+ std::cerr
+ << "err1 handler error: " << ec.message()
+ << "\n";
+ return;
+ }
+ err1Handler();
+ });
+}
+
+static void err2AssertHandler()
+{
+ // Handle the standard ERR2 detection and logging
+ const static constexpr int err2 = 2;
+ errXAssertHandler(err2, err2AssertTimer);
+ // Also handle reset for ERR2
+ err2AssertTimer.async_wait([](const boost::system::error_code ec) {
+ if (ec)
+ {
+ // operation_aborted is expected if timer is canceled before
+ // completion.
+ if (ec != boost::asio::error::operation_aborted)
+ {
+ std::cerr << "err2 timeout async_wait failed: " << ec.message()
+ << "\n";
+ }
+ return;
+ }
+ conn->async_method_call(
+ [](boost::system::error_code ec,
+ const std::variant<bool>& property) {
+ if (ec)
+ {
+ return;
+ }
+ const bool* reset = std::get_if<bool>(&property);
+ if (reset == nullptr)
+ {
+ std::cerr << "Unable to read reset on ERR2 value\n";
+ return;
+ }
+ startCrashdumpAndRecovery(*reset);
+ },
+ "xyz.openbmc_project.Settings",
+ "/xyz/openbmc_project/control/processor_error_config",
+ "org.freedesktop.DBus.Properties", "Get",
+ "xyz.openbmc_project.Control.Processor.ErrConfig", "ResetOnERR2");
+ });
+}
+
+static void err2Handler()
+{
+ if (!hostOff)
+ {
+ gpiod::line_event gpioLineEvent = err2Line.event_read();
+
+ bool err2 = gpioLineEvent.event_type == gpiod::line_event::FALLING_EDGE;
+ if (err2)
+ {
+ err2AssertHandler();
+ }
+ else
+ {
+ err2AssertTimer.cancel();
+ }
+ }
+ err2Event.async_wait(boost::asio::posix::stream_descriptor::wait_read,
+ [](const boost::system::error_code ec) {
+ if (ec)
+ {
+ std::cerr
+ << "err2 handler error: " << ec.message()
+ << "\n";
+ return;
+ }
+ err2Handler();
+ });
+}
+
+static void smiAssertHandler()
+{
+ smiAssertTimer.expires_after(std::chrono::milliseconds(smiTimeoutMs));
+ smiAssertTimer.async_wait([](const boost::system::error_code ec) {
+ if (ec)
+ {
+ // operation_aborted is expected if timer is canceled before
+ // completion.
+ if (ec != boost::asio::error::operation_aborted)
+ {
+ std::cerr << "smi timeout async_wait failed: " << ec.message()
+ << "\n";
+ }
+ return;
+ }
+ std::cerr << "SMI asserted for " << std::to_string(smiTimeoutMs)
+ << " ms\n";
+ smiTimeoutLog();
+ conn->async_method_call(
+ [](boost::system::error_code ec,
+ const std::variant<bool>& property) {
+ if (ec)
+ {
+ return;
+ }
+ const bool* reset = std::get_if<bool>(&property);
+ if (reset == nullptr)
+ {
+ std::cerr << "Unable to read reset on SMI value\n";
+ return;
+ }
+ startCrashdumpAndRecovery(*reset);
+ },
+ "xyz.openbmc_project.Settings",
+ "/xyz/openbmc_project/control/bmc_reset_disables",
+ "org.freedesktop.DBus.Properties", "Get",
+ "xyz.openbmc_project.Control.ResetDisables", "ResetOnSMI");
+ });
+}
+
+static void smiHandler()
+{
+ if (!hostOff)
+ {
+ gpiod::line_event gpioLineEvent = smiLine.event_read();
+
+ bool smi = gpioLineEvent.event_type == gpiod::line_event::FALLING_EDGE;
+ if (smi)
+ {
+ smiAssertHandler();
+ }
+ else
+ {
+ smiAssertTimer.cancel();
+ }
+ }
+ smiEvent.async_wait(boost::asio::posix::stream_descriptor::wait_read,
+ [](const boost::system::error_code ec) {
+ if (ec)
+ {
+ std::cerr
+ << "smi handler error: " << ec.message()
+ << "\n";
+ return;
+ }
+ smiHandler();
+ });
+}
+
static void initializeErrorState()
{
// Handle CPU_CATERR if it's asserted now
@@ -326,6 +1294,84 @@ static void initializeErrorState()
{
caterrAssertHandler();
}
+
+ // Handle CPU_ERR0 if it's asserted now
+ if (err0Line.get_value() == 0)
+ {
+ err0AssertHandler();
+ }
+
+ // Handle CPU_ERR1 if it's asserted now
+ if (err1Line.get_value() == 0)
+ {
+ err1AssertHandler();
+ }
+
+ // Handle CPU_ERR2 if it's asserted now
+ if (err2Line.get_value() == 0)
+ {
+ err2AssertHandler();
+ }
+
+ // Handle SMI if it's asserted now
+ if (smiLine.get_value() == 0)
+ {
+ smiAssertHandler();
+ }
+
+ // Handle CPU1_THERMTRIP if it's asserted now
+ if (cpu1ThermtripLine.get_value() == 0)
+ {
+ cpu1ThermtripAssertHandler();
+ }
+
+ // Handle CPU2_THERMTRIP if it's asserted now
+ if (cpu2ThermtripLine.get_value() == 0)
+ {
+ cpu2ThermtripAssertHandler();
+ }
+
+ // Handle CPU1_VRHOT if it's asserted now
+ if (cpu1VRHotLine.get_value() == 0)
+ {
+ cpu1VRHotAssertHandler();
+ }
+
+ // Handle CPU1_MEM_ABCD_VRHOT if it's asserted now
+ if (cpu1MemABCDVRHotLine.get_value() == 0)
+ {
+ cpu1MemABCDVRHotAssertHandler();
+ }
+
+ // Handle CPU1_MEM_EFGH_VRHOT if it's asserted now
+ if (cpu1MemEFGHVRHotLine.get_value() == 0)
+ {
+ cpu1MemEFGHVRHotAssertHandler();
+ }
+
+ // Handle CPU2_VRHOT if it's asserted now
+ if (cpu2VRHotLine.get_value() == 0)
+ {
+ cpu2VRHotAssertHandler();
+ }
+
+ // Handle CPU2_MEM_ABCD_VRHOT if it's asserted now
+ if (cpu2MemABCDVRHotLine.get_value() == 0)
+ {
+ cpu2MemABCDVRHotAssertHandler();
+ }
+
+ // Handle CPU2_MEM_EFGH_VRHOT if it's asserted now
+ if (cpu2MemEFGHVRHotLine.get_value() == 0)
+ {
+ cpu2MemEFGHVRHotAssertHandler();
+ }
+
+ // Handle PCH_BMC_THERMTRIP if it's asserted now
+ if (pchThermtripLine.get_value() == 0)
+ {
+ ssbThermTripLog();
+ }
}
} // namespace host_error_monitor
@@ -356,6 +1402,124 @@ int main(int argc, char* argv[])
return -1;
}
+ // Request CPU_ERR0 GPIO events
+ if (!host_error_monitor::requestGPIOEvents(
+ "CPU_ERR0", host_error_monitor::err0Handler,
+ host_error_monitor::err0Line, host_error_monitor::err0Event))
+ {
+ return -1;
+ }
+
+ // Request CPU_ERR1 GPIO events
+ if (!host_error_monitor::requestGPIOEvents(
+ "CPU_ERR1", host_error_monitor::err1Handler,
+ host_error_monitor::err1Line, host_error_monitor::err1Event))
+ {
+ return -1;
+ }
+
+ // Request CPU_ERR2 GPIO events
+ if (!host_error_monitor::requestGPIOEvents(
+ "CPU_ERR2", host_error_monitor::err2Handler,
+ host_error_monitor::err2Line, host_error_monitor::err2Event))
+ {
+ return -1;
+ }
+
+ // Request SMI GPIO events
+ if (!host_error_monitor::requestGPIOEvents(
+ "SMI", host_error_monitor::smiHandler, host_error_monitor::smiLine,
+ host_error_monitor::smiEvent))
+ {
+ return -1;
+ }
+
+ // Request CPU1_FIVR_FAULT GPIO input
+ if (!host_error_monitor::requestGPIOInput(
+ "CPU1_FIVR_FAULT", host_error_monitor::cpu1FIVRFaultLine))
+ {
+ return -1;
+ }
+
+ // Request CPU1_THERMTRIP GPIO events
+ if (!host_error_monitor::requestGPIOEvents(
+ "CPU1_THERMTRIP", host_error_monitor::cpu1ThermtripHandler,
+ host_error_monitor::cpu1ThermtripLine,
+ host_error_monitor::cpu1ThermtripEvent))
+ {
+ return -1;
+ }
+
+ // Request CPU2_FIVR_FAULT GPIO input
+ if (!host_error_monitor::requestGPIOInput(
+ "CPU2_FIVR_FAULT", host_error_monitor::cpu2FIVRFaultLine))
+ {
+ return -1;
+ }
+
+ // Request CPU2_THERMTRIP GPIO events
+ if (!host_error_monitor::requestGPIOEvents(
+ "CPU2_THERMTRIP", host_error_monitor::cpu2ThermtripHandler,
+ host_error_monitor::cpu2ThermtripLine,
+ host_error_monitor::cpu2ThermtripEvent))
+ {
+ return -1;
+ }
+
+ // Request CPU1_VRHOT GPIO events
+ if (!host_error_monitor::requestGPIOEvents(
+ "CPU1_VRHOT", host_error_monitor::cpu1VRHotHandler,
+ host_error_monitor::cpu1VRHotLine,
+ host_error_monitor::cpu1VRHotEvent))
+ {
+ return -1;
+ }
+
+ // Request CPU1_MEM_ABCD_VRHOT GPIO events
+ if (!host_error_monitor::requestGPIOEvents(
+ "CPU1_MEM_ABCD_VRHOT", host_error_monitor::cpu1MemABCDVRHotHandler,
+ host_error_monitor::cpu1MemABCDVRHotLine,
+ host_error_monitor::cpu1MemABCDVRHotEvent))
+ {
+ return -1;
+ }
+
+ // Request CPU1_MEM_EFGH_VRHOT GPIO events
+ if (!host_error_monitor::requestGPIOEvents(
+ "CPU1_MEM_EFGH_VRHOT", host_error_monitor::cpu1MemEFGHVRHotHandler,
+ host_error_monitor::cpu1MemEFGHVRHotLine,
+ host_error_monitor::cpu1MemEFGHVRHotEvent))
+ {
+ return -1;
+ }
+
+ // Request CPU2_VRHOT GPIO events
+ if (!host_error_monitor::requestGPIOEvents(
+ "CPU2_VRHOT", host_error_monitor::cpu2VRHotHandler,
+ host_error_monitor::cpu2VRHotLine,
+ host_error_monitor::cpu2VRHotEvent))
+ {
+ return -1;
+ }
+
+ // Request CPU2_MEM_ABCD_VRHOT GPIO events
+ if (!host_error_monitor::requestGPIOEvents(
+ "CPU2_MEM_ABCD_VRHOT", host_error_monitor::cpu2MemABCDVRHotHandler,
+ host_error_monitor::cpu2MemABCDVRHotLine,
+ host_error_monitor::cpu2MemABCDVRHotEvent))
+ {
+ return -1;
+ }
+
+ // Request CPU2_MEM_EFGH_VRHOT GPIO events
+ if (!host_error_monitor::requestGPIOEvents(
+ "CPU2_MEM_EFGH_VRHOT", host_error_monitor::cpu2MemEFGHVRHotHandler,
+ host_error_monitor::cpu2MemEFGHVRHotLine,
+ host_error_monitor::cpu2MemEFGHVRHotEvent))
+ {
+ return -1;
+ }
+
// Request PCH_BMC_THERMTRIP GPIO events
if (!host_error_monitor::requestGPIOEvents(
"PCH_BMC_THERMTRIP", host_error_monitor::pchThermtripHandler,
diff --git a/libpeci/.clang-format b/libpeci/.clang-format
new file mode 100644
index 0000000..ae9ad39
--- /dev/null
+++ b/libpeci/.clang-format
@@ -0,0 +1,98 @@
+---
+Language: Cpp
+# BasedOnStyle: LLVM
+AccessModifierOffset: -2
+AlignAfterOpenBracket: Align
+AlignConsecutiveAssignments: false
+AlignConsecutiveDeclarations: false
+AlignEscapedNewlinesLeft: false
+AlignOperands: true
+AlignTrailingComments: true
+AllowAllParametersOfDeclarationOnNextLine: true
+AllowShortBlocksOnASingleLine: false
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: None
+AllowShortIfStatementsOnASingleLine: false
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakAfterDefinitionReturnType: None
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: false
+AlwaysBreakTemplateDeclarations: true
+BinPackArguments: true
+BinPackParameters: true
+BraceWrapping:
+ AfterClass: true
+ AfterControlStatement: true
+ AfterEnum: true
+ AfterFunction: true
+ AfterNamespace: true
+ AfterObjCDeclaration: true
+ AfterStruct: true
+ AfterUnion: true
+ BeforeCatch: true
+ BeforeElse: true
+ IndentBraces: false
+BreakBeforeBinaryOperators: None
+BreakBeforeBraces: Custom
+BreakBeforeTernaryOperators: true
+BreakConstructorInitializers: AfterColon
+ColumnLimit: 80
+CommentPragmas: '^ IWYU pragma:'
+ConstructorInitializerAllOnOneLineOrOnePerLine: false
+ConstructorInitializerIndentWidth: 4
+ContinuationIndentWidth: 4
+Cpp11BracedListStyle: true
+DerivePointerAlignment: false
+PointerAlignment: Left
+DisableFormat: false
+ExperimentalAutoDetectBinPacking: false
+FixNamespaceComments: true
+ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ]
+IncludeBlocks: Regroup
+IncludeCategories:
+ - Regex: '^[<"](gtest|gmock)'
+ Priority: 5
+ - Regex: '^"config.h"'
+ Priority: -1
+ - Regex: '^".*\.hpp"'
+ Priority: 1
+ - Regex: '^<.*\.h>'
+ Priority: 2
+ - Regex: '^<.*'
+ Priority: 3
+ - Regex: '.*'
+ Priority: 4
+IndentCaseLabels: true
+IndentWidth: 4
+IndentWrappedFunctionNames: true
+KeepEmptyLinesAtTheStartOfBlocks: true
+MacroBlockBegin: ''
+MacroBlockEnd: ''
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: None
+ObjCBlockIndentWidth: 2
+ObjCSpaceAfterProperty: false
+ObjCSpaceBeforeProtocolList: true
+PenaltyBreakBeforeFirstCallParameter: 19
+PenaltyBreakComment: 300
+PenaltyBreakFirstLessLess: 120
+PenaltyBreakString: 1000
+PenaltyExcessCharacter: 1000000
+PenaltyReturnTypeOnItsOwnLine: 60
+ReflowComments: true
+SortIncludes: true
+SortUsingDeclarations: true
+SpaceAfterCStyleCast: false
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeParens: ControlStatements
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles: false
+SpacesInContainerLiterals: true
+SpacesInCStyleCastParentheses: false
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+Standard: Cpp11
+TabWidth: 4
+UseTab: Never
+...
diff --git a/libpeci/CMakeLists.txt b/libpeci/CMakeLists.txt
new file mode 100644
index 0000000..227ed1d
--- /dev/null
+++ b/libpeci/CMakeLists.txt
@@ -0,0 +1,20 @@
+cmake_minimum_required(VERSION 3.6)
+project(libpeci)
+
+add_library(peci SHARED peci.c)
+
+set_property(TARGET peci PROPERTY C_STANDARD 99)
+target_include_directories(peci PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
+set_target_properties(peci PROPERTIES VERSION "1.0" SOVERSION "1")
+
+install(TARGETS peci DESTINATION lib)
+install(FILES peci.h DESTINATION include)
+
+add_executable(peci_cmds peci_cmds.c)
+add_dependencies(peci_cmds peci)
+target_link_libraries(peci_cmds peci)
+
+install(TARGETS peci_cmds
+ RUNTIME DESTINATION bin
+ LIBRARY DESTINATION lib
+ ARCHIVE DESTINATION lib/static)
diff --git a/libpeci/peci.c b/libpeci/peci.c
new file mode 100644
index 0000000..400e2b5
--- /dev/null
+++ b/libpeci/peci.c
@@ -0,0 +1,1081 @@
+/*
+// Copyright (c) 2019 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+#include <fcntl.h>
+#include <peci.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <syslog.h>
+#include <time.h>
+#include <unistd.h>
+
+EPECIStatus peci_GetDIB_seq(uint8_t target, uint64_t* dib, int peci_fd);
+
+/*-------------------------------------------------------------------------
+ * This function unlocks the peci interface
+ *------------------------------------------------------------------------*/
+void peci_Unlock(int peci_fd)
+{
+ if (close(peci_fd) != 0)
+ {
+ syslog(LOG_ERR, "PECI device failed to unlock.\n");
+ }
+}
+
+#define PECI_DEVICE "/dev/peci-0"
+/*-------------------------------------------------------------------------
+ * This function attempts to lock the peci interface with the specified
+ * timeout and returns a file descriptor if successful.
+ *------------------------------------------------------------------------*/
+EPECIStatus peci_Lock(int* peci_fd, uint32_t timeout_ms)
+{
+ struct timespec sRequest;
+ sRequest.tv_sec = 0;
+ sRequest.tv_nsec = PECI_TIMEOUT_RESOLUTION_MS * 1000 * 1000;
+ uint32_t timeout_count = 0;
+
+ if (NULL == peci_fd)
+ {
+ return PECI_CC_INVALID_REQ;
+ }
+
+ // Open the PECI driver with the specified timeout
+ *peci_fd = open(PECI_DEVICE, O_RDWR | O_CLOEXEC);
+ switch (timeout_ms)
+ {
+ case PECI_NO_WAIT:
+ break;
+ case PECI_WAIT_FOREVER:
+ while (-1 == *peci_fd)
+ {
+ nanosleep(&sRequest, NULL);
+ *peci_fd = open(PECI_DEVICE, O_RDWR | O_CLOEXEC);
+ }
+ default:
+ while (-1 == *peci_fd && timeout_count < timeout_ms)
+ {
+ nanosleep(&sRequest, NULL);
+ timeout_count += PECI_TIMEOUT_RESOLUTION_MS;
+ *peci_fd = open(PECI_DEVICE, O_RDWR | O_CLOEXEC);
+ }
+ }
+ if (-1 == *peci_fd)
+ {
+ syslog(LOG_ERR, "%s(%d): >>> PECI Device Busy <<< \n", __FUNCTION__,
+ __LINE__);
+ return PECI_CC_DRIVER_ERR;
+ }
+ return PECI_CC_SUCCESS;
+}
+
+/*-------------------------------------------------------------------------
+ * This function closes the peci interface
+ *------------------------------------------------------------------------*/
+static void peci_Close(int peci_fd)
+{
+ peci_Unlock(peci_fd);
+}
+
+/*-------------------------------------------------------------------------
+ * This function opens the peci interface and returns a file descriptor
+ *------------------------------------------------------------------------*/
+static EPECIStatus peci_Open(int* peci_fd)
+{
+ if (NULL == peci_fd)
+ {
+ return PECI_CC_INVALID_REQ;
+ }
+
+ // Lock the PECI driver with a default timeout
+ return peci_Lock(peci_fd, PECI_TIMEOUT_MS);
+}
+
+/*-------------------------------------------------------------------------
+ * This function issues peci commands to peci driver
+ *------------------------------------------------------------------------*/
+static EPECIStatus HW_peci_issue_cmd(unsigned int cmd, char* cmdPtr,
+ int peci_fd)
+{
+ if (cmdPtr == NULL)
+ {
+ return PECI_CC_INVALID_REQ;
+ }
+
+ if (ioctl(peci_fd, cmd, cmdPtr) != 0)
+ {
+ return PECI_CC_DRIVER_ERR;
+ }
+
+ return PECI_CC_SUCCESS;
+}
+
+/*-------------------------------------------------------------------------
+ * Find the specified PCI bus number value
+ *------------------------------------------------------------------------*/
+EPECIStatus FindBusNumber(uint8_t u8Bus, uint8_t u8Cpu, uint8_t* pu8BusValue)
+{
+ uint8_t u8CpuBus0[] = {
+ PECI_PCI_BUS0_CPU0,
+ PECI_PCI_BUS0_CPU1,
+ };
+ uint8_t u8Bus0 = 0;
+ uint8_t u8Offset = 0;
+ EPECIStatus ret;
+ uint8_t u8Reg[4];
+ uint8_t cc = 0;
+
+ // First check for valid inputs
+ // Check cpu and bus numbers, only support buses [5:0]
+ if ((u8Bus > 5) || (u8Cpu >= (sizeof(u8CpuBus0) / sizeof(uint8_t))) ||
+ (pu8BusValue == NULL))
+ {
+ return PECI_CC_INVALID_REQ;
+ }
+
+ // Get the Bus 0 value for the requested CPU
+ u8Bus0 = u8CpuBus0[u8Cpu];
+
+ // Next check that the bus numbers are valid
+ // CPUBUSNO_VALID register - Above registers valid? - B(0) D5 F0 offset
+ // D4h
+ ret = peci_RdPCIConfig(u8Cpu, u8Bus0, PECI_PCI_CPUBUSNO_DEV,
+ PECI_PCI_CPUBUSNO_FUNC, PECI_PCI_CPUBUSNO_VALID,
+ u8Reg, &cc);
+ if (ret != PECI_CC_SUCCESS)
+ {
+ return ret;
+ }
+ // BIOS will set bit 31 of CPUBUSNO_VALID when the bus numbers are valid
+ if ((u8Reg[3] & 0x80) == 0)
+ {
+ return PECI_CC_HW_ERR;
+ }
+
+ // Bus numbers are valid so read the correct offset for the requested
+ // bus CPUBUSNO register - CPU Internal Bus Numbers [3:0] - B(0) D5 F0
+ // offset CCh CPUBUSNO_1 register - CPU Internal Bus Numbers [5:4] -
+ // B(0) D5 F0 offset D0h
+ u8Offset = u8Bus <= 3 ? PECI_PCI_CPUBUSNO : PECI_PCI_CPUBUSNO_1;
+ ret = peci_RdPCIConfig(u8Cpu, u8Bus0, PECI_PCI_CPUBUSNO_DEV,
+ PECI_PCI_CPUBUSNO_FUNC, u8Offset, u8Reg, &cc);
+ if (ret != PECI_CC_SUCCESS)
+ {
+ return ret;
+ }
+
+ // Now return the bus value for the requested bus
+ *pu8BusValue = u8Reg[u8Bus % 4];
+
+ // Unused bus numbers are set to zero which is only valid for bus 0
+ // so, return an error for any other bus set to zero
+ if (*pu8BusValue == 0 && u8Bus != 0)
+ {
+ return PECI_CC_CPU_NOT_PRESENT;
+ }
+
+ return PECI_CC_SUCCESS;
+}
+
+/*-------------------------------------------------------------------------
+ * This function checks the CPU PECI interface
+ *------------------------------------------------------------------------*/
+EPECIStatus peci_Ping(uint8_t target)
+{
+ int peci_fd = -1;
+ EPECIStatus ret;
+
+ if (peci_Open(&peci_fd) != PECI_CC_SUCCESS)
+ {
+ return PECI_CC_DRIVER_ERR;
+ }
+ ret = peci_Ping_seq(target, peci_fd);
+
+ peci_Close(peci_fd);
+ return ret;
+}
+
+/*-------------------------------------------------------------------------
+ * This function allows sequential Ping with the provided
+ * peci file descriptor.
+ *------------------------------------------------------------------------*/
+EPECIStatus peci_Ping_seq(uint8_t target, int peci_fd)
+{
+ EPECIStatus ret;
+ struct peci_ping_msg cmd;
+
+ cmd.addr = target;
+ ret = HW_peci_issue_cmd(PECI_IOC_PING, (char*)&cmd, peci_fd);
+
+ return ret;
+}
+
+/*-------------------------------------------------------------------------
+ * This function gets PECI device information
+ *------------------------------------------------------------------------*/
+EPECIStatus peci_GetDIB(uint8_t target, uint64_t* dib)
+{
+ int peci_fd = -1;
+ EPECIStatus ret;
+
+ if (dib == NULL)
+ {
+ return PECI_CC_INVALID_REQ;
+ }
+
+ if (peci_Open(&peci_fd) != PECI_CC_SUCCESS)
+ {
+ return PECI_CC_DRIVER_ERR;
+ }
+ ret = peci_GetDIB_seq(target, dib, peci_fd);
+
+ peci_Close(peci_fd);
+ return ret;
+}
+
+/*-------------------------------------------------------------------------
+ * This function allows sequential GetDIB with the provided
+ * peci file descriptor.
+ *------------------------------------------------------------------------*/
+EPECIStatus peci_GetDIB_seq(uint8_t target, uint64_t* dib, int peci_fd)
+{
+ struct peci_get_dib_msg cmd;
+ EPECIStatus ret;
+ cmd.addr = target;
+
+ if (dib == NULL)
+ {
+ return PECI_CC_INVALID_REQ;
+ }
+
+ ret = HW_peci_issue_cmd(PECI_IOC_GET_DIB, (char*)&cmd, peci_fd);
+
+ if (ret == PECI_CC_SUCCESS)
+ {
+ *dib = cmd.dib;
+ }
+
+ return ret;
+}
+
+/*-------------------------------------------------------------------------
+ * This function get PECI Thermal temperature
+ * Expressed in signed fixed point value of 1/64 degrees celsius
+ *------------------------------------------------------------------------*/
+EPECIStatus peci_GetTemp(uint8_t target, int16_t* temperature)
+{
+ int peci_fd = -1;
+ struct peci_get_temp_msg cmd;
+
+ if (temperature == NULL)
+ {
+ return PECI_CC_INVALID_REQ;
+ }
+
+ if (peci_Open(&peci_fd) != PECI_CC_SUCCESS)
+ {
+ return PECI_CC_DRIVER_ERR;
+ }
+
+ cmd.addr = target;
+
+ EPECIStatus ret =
+ HW_peci_issue_cmd(PECI_IOC_GET_TEMP, (char*)&cmd, peci_fd);
+
+ if (ret == PECI_CC_SUCCESS)
+ {
+ *temperature = cmd.temp_raw;
+ }
+
+ peci_Close(peci_fd);
+
+ return ret;
+}
+
+/*-------------------------------------------------------------------------
+ * This function provides read access to the package configuration
+ * space within the processor.
+ *------------------------------------------------------------------------*/
+EPECIStatus peci_RdPkgConfig(uint8_t target, uint8_t u8Index, uint16_t u16Value,
+ uint8_t u8ReadLen, uint8_t* pPkgConfig,
+ uint8_t* cc)
+{
+ int peci_fd = -1;
+ EPECIStatus ret;
+
+ if (pPkgConfig == NULL || cc == NULL)
+ {
+ return PECI_CC_INVALID_REQ;
+ }
+
+ if (peci_Open(&peci_fd) != PECI_CC_SUCCESS)
+ {
+ return PECI_CC_DRIVER_ERR;
+ }
+ ret = peci_RdPkgConfig_seq(target, u8Index, u16Value, u8ReadLen, pPkgConfig,
+ peci_fd, cc);
+
+ peci_Close(peci_fd);
+ return ret;
+}
+
+/*-------------------------------------------------------------------------
+ * This function allows sequential RdPkgConfig with the provided
+ * peci file descriptor.
+ *------------------------------------------------------------------------*/
+EPECIStatus peci_RdPkgConfig_seq(uint8_t target, uint8_t u8Index,
+ uint16_t u16Value, uint8_t u8ReadLen,
+ uint8_t* pPkgConfig, int peci_fd, uint8_t* cc)
+{
+ struct peci_rd_pkg_cfg_msg cmd;
+ EPECIStatus ret;
+
+ if (pPkgConfig == NULL || cc == NULL)
+ {
+ return PECI_CC_INVALID_REQ;
+ }
+
+ // Per the PECI spec, the write length must be a byte, word, or dword
+ if (u8ReadLen != 1 && u8ReadLen != 2 && u8ReadLen != 4)
+ {
+ return PECI_CC_INVALID_REQ;
+ }
+
+ // The PECI buffer must be large enough to hold the requested data
+ if (sizeof(cmd.pkg_config) < u8ReadLen)
+ {
+ return PECI_CC_INVALID_REQ;
+ }
+
+ cmd.addr = target;
+ cmd.index = u8Index; // RdPkgConfig index
+ cmd.param = u16Value; // Config parameter value
+ cmd.rx_len = u8ReadLen;
+
+ ret = HW_peci_issue_cmd(PECI_IOC_RD_PKG_CFG, (char*)&cmd, peci_fd);
+ *cc = cmd.cc;
+ if (ret == PECI_CC_SUCCESS)
+ {
+ memcpy(pPkgConfig, cmd.pkg_config, u8ReadLen);
+ }
+
+ return ret;
+}
+
+/*-------------------------------------------------------------------------
+ * This function provides write access to the package configuration
+ * space within the processor
+ *------------------------------------------------------------------------*/
+EPECIStatus peci_WrPkgConfig(uint8_t target, uint8_t u8Index, uint16_t u16Param,
+ uint32_t u32Value, uint8_t u8WriteLen, uint8_t* cc)
+{
+ int peci_fd = -1;
+ EPECIStatus ret;
+
+ if (cc == NULL)
+ {
+ return PECI_CC_INVALID_REQ;
+ }
+
+ if (peci_Open(&peci_fd) != PECI_CC_SUCCESS)
+ {
+ return PECI_CC_DRIVER_ERR;
+ }
+ ret = peci_WrPkgConfig_seq(target, u8Index, u16Param, u32Value, u8WriteLen,
+ peci_fd, cc);
+
+ peci_Close(peci_fd);
+ return ret;
+}
+
+/*-------------------------------------------------------------------------
+ * This function allows sequential WrPkgConfig with the provided
+ * peci file descriptor.
+ *------------------------------------------------------------------------*/
+EPECIStatus peci_WrPkgConfig_seq(uint8_t target, uint8_t u8Index,
+ uint16_t u16Param, uint32_t u32Value,
+ uint8_t u8WriteLen, int peci_fd, uint8_t* cc)
+{
+ struct peci_wr_pkg_cfg_msg cmd;
+ EPECIStatus ret;
+
+ if (cc == NULL)
+ {
+ return PECI_CC_INVALID_REQ;
+ }
+
+ // Per the PECI spec, the write length must be a byte, word, or dword
+ if ((u8WriteLen != 1) && (u8WriteLen != 2) && (u8WriteLen != 4))
+ {
+ return PECI_CC_INVALID_REQ;
+ }
+
+ cmd.addr = target;
+ cmd.index = u8Index; // RdPkgConfig index
+ cmd.param = u16Param; // parameter value
+ cmd.tx_len = u8WriteLen;
+ cmd.value = u32Value;
+
+ ret = HW_peci_issue_cmd(PECI_IOC_WR_PKG_CFG, (char*)&cmd, peci_fd);
+ *cc = cmd.cc;
+
+ return ret;
+}
+
+/*-------------------------------------------------------------------------
+ * This function provides read access to Model Specific Registers
+ * defined in the processor doc.
+ *------------------------------------------------------------------------*/
+EPECIStatus peci_RdIAMSR(uint8_t target, uint8_t threadID, uint16_t MSRAddress,
+ uint64_t* u64MsrVal, uint8_t* cc)
+{
+ int peci_fd = -1;
+ struct peci_rd_ia_msr_msg cmd;
+ EPECIStatus ret;
+
+ if (u64MsrVal == NULL || cc == NULL)
+ {
+ return PECI_CC_INVALID_REQ;
+ }
+
+ if (peci_Open(&peci_fd) != PECI_CC_SUCCESS)
+ {
+ return PECI_CC_DRIVER_ERR;
+ }
+
+ cmd.addr = target;
+ cmd.thread_id = threadID; // request byte for thread ID
+ cmd.address = MSRAddress; // MSR Address
+
+ ret = HW_peci_issue_cmd(PECI_IOC_RD_IA_MSR, (char*)&cmd, peci_fd);
+ *cc = cmd.cc;
+ if (ret == PECI_CC_SUCCESS)
+ {
+ *u64MsrVal = cmd.value;
+ }
+
+ peci_Close(peci_fd);
+ return ret;
+}
+
+/*-------------------------------------------------------------------------
+ * This function provides read access to the PCI configuration space at
+ * the requested PCI configuration address.
+ *------------------------------------------------------------------------*/
+EPECIStatus peci_RdPCIConfig(uint8_t target, uint8_t u8Bus, uint8_t u8Device,
+ uint8_t u8Fcn, uint16_t u16Reg, uint8_t* pPCIData,
+ uint8_t* cc)
+{
+ int peci_fd = -1;
+ EPECIStatus ret;
+
+ if (pPCIData == NULL || cc == NULL)
+ {
+ return PECI_CC_INVALID_REQ;
+ }
+
+ if (peci_Open(&peci_fd) != PECI_CC_SUCCESS)
+ {
+ return PECI_CC_DRIVER_ERR;
+ }
+ ret = peci_RdPCIConfig_seq(target, u8Bus, u8Device, u8Fcn, u16Reg, pPCIData,
+ peci_fd, cc);
+
+ peci_Close(peci_fd);
+ return ret;
+}
+
+/*-------------------------------------------------------------------------
+ * This function allows sequential RdPCIConfig with the provided
+ * peci file descriptor.
+ *------------------------------------------------------------------------*/
+EPECIStatus peci_RdPCIConfig_seq(uint8_t target, uint8_t u8Bus,
+ uint8_t u8Device, uint8_t u8Fcn,
+ uint16_t u16Reg, uint8_t* pPCIData,
+ int peci_fd, uint8_t* cc)
+{
+ struct peci_rd_pci_cfg_msg cmd;
+ EPECIStatus ret;
+
+ if (pPCIData == NULL || cc == NULL)
+ {
+ return PECI_CC_INVALID_REQ;
+ }
+
+ // The PECI buffer must be large enough to hold the PCI data
+ if (sizeof(cmd.pci_config) < 4)
+ {
+ return PECI_CC_INVALID_REQ;
+ }
+
+ cmd.addr = target;
+ cmd.bus = u8Bus;
+ cmd.device = u8Device;
+ cmd.function = u8Fcn;
+ cmd.reg = u16Reg;
+
+ ret = HW_peci_issue_cmd(PECI_IOC_RD_PCI_CFG, (char*)&cmd, peci_fd);
+ *cc = cmd.cc;
+
+ if (ret == PECI_CC_SUCCESS)
+ {
+ memcpy(pPCIData, cmd.pci_config, 4);
+ }
+
+ return ret;
+}
+
+/*-------------------------------------------------------------------------
+ * This function provides read access to the local PCI configuration space
+ *------------------------------------------------------------------------*/
+EPECIStatus peci_RdPCIConfigLocal(uint8_t target, uint8_t u8Bus,
+ uint8_t u8Device, uint8_t u8Fcn,
+ uint16_t u16Reg, uint8_t u8ReadLen,
+ uint8_t* pPCIReg, uint8_t* cc)
+{
+ int peci_fd = -1;
+ EPECIStatus ret;
+
+ if (pPCIReg == NULL || cc == NULL)
+ {
+ return PECI_CC_INVALID_REQ;
+ }
+
+ if (peci_Open(&peci_fd) != PECI_CC_SUCCESS)
+ {
+ return PECI_CC_DRIVER_ERR;
+ }
+ ret = peci_RdPCIConfigLocal_seq(target, u8Bus, u8Device, u8Fcn, u16Reg,
+ u8ReadLen, pPCIReg, peci_fd, cc);
+
+ peci_Close(peci_fd);
+ return ret;
+}
+
+/*-------------------------------------------------------------------------
+ * This function allows sequential RdPCIConfigLocal with the provided
+ * peci file descriptor.
+ *------------------------------------------------------------------------*/
+EPECIStatus peci_RdPCIConfigLocal_seq(uint8_t target, uint8_t u8Bus,
+ uint8_t u8Device, uint8_t u8Fcn,
+ uint16_t u16Reg, uint8_t u8ReadLen,
+ uint8_t* pPCIReg, int peci_fd,
+ uint8_t* cc)
+{
+ struct peci_rd_pci_cfg_local_msg cmd;
+ EPECIStatus ret;
+
+ if (pPCIReg == NULL || cc == NULL)
+ {
+ return PECI_CC_INVALID_REQ;
+ }
+
+ // Per the PECI spec, the read length must be a byte, word, or dword
+ if (u8ReadLen != 1 && u8ReadLen != 2 && u8ReadLen != 4)
+ {
+ return PECI_CC_INVALID_REQ;
+ }
+
+ // The PECI buffer must be large enough to hold the requested data
+ if (sizeof(cmd.pci_config) < u8ReadLen)
+ {
+ return PECI_CC_INVALID_REQ;
+ }
+
+ cmd.addr = target;
+ cmd.bus = u8Bus;
+ cmd.device = u8Device;
+ cmd.function = u8Fcn;
+ cmd.reg = u16Reg;
+ cmd.rx_len = u8ReadLen;
+
+ ret = HW_peci_issue_cmd(PECI_IOC_RD_PCI_CFG_LOCAL, (char*)&cmd, peci_fd);
+ *cc = cmd.cc;
+
+ if (ret == PECI_CC_SUCCESS)
+ {
+ memcpy(pPCIReg, cmd.pci_config, u8ReadLen);
+ }
+
+ return ret;
+}
+
+/*-------------------------------------------------------------------------
+ * This function provides write access to the local PCI configuration space
+ *------------------------------------------------------------------------*/
+EPECIStatus peci_WrPCIConfigLocal(uint8_t target, uint8_t u8Bus,
+ uint8_t u8Device, uint8_t u8Fcn,
+ uint16_t u16Reg, uint8_t DataLen,
+ uint32_t DataVal, uint8_t* cc)
+{
+ int peci_fd = -1;
+ struct peci_wr_pci_cfg_local_msg cmd;
+ EPECIStatus ret;
+
+ if (cc == NULL)
+ {
+ return PECI_CC_INVALID_REQ;
+ }
+
+ if (peci_Open(&peci_fd) != PECI_CC_SUCCESS)
+ {
+ return PECI_CC_DRIVER_ERR;
+ }
+
+ // Per the PECI spec, the write length must be a byte, word, or dword
+ if (DataLen != 1 && DataLen != 2 && DataLen != 4)
+ {
+ peci_Close(peci_fd);
+ return PECI_CC_INVALID_REQ;
+ }
+
+ cmd.addr = target;
+ cmd.bus = u8Bus;
+ cmd.device = u8Device;
+ cmd.function = u8Fcn;
+ cmd.reg = u16Reg;
+ cmd.tx_len = DataLen;
+ cmd.value = DataVal;
+
+ ret = HW_peci_issue_cmd(PECI_IOC_WR_PCI_CFG_LOCAL, (char*)&cmd, peci_fd);
+ *cc = cmd.cc;
+
+ peci_Close(peci_fd);
+ return ret;
+}
+
+/*-------------------------------------------------------------------------
+ * This internal function is the common interface for RdEndPointConfig to PCI
+ *------------------------------------------------------------------------*/
+static EPECIStatus peci_RdEndPointConfigPciCommon(
+ uint8_t target, uint8_t u8MsgType, uint8_t u8Seg, uint8_t u8Bus,
+ uint8_t u8Device, uint8_t u8Fcn, uint16_t u16Reg, uint8_t u8ReadLen,
+ uint8_t* pPCIData, int peci_fd, uint8_t* cc)
+{
+ struct peci_rd_end_pt_cfg_msg cmd;
+ EPECIStatus ret;
+
+ if (pPCIData == NULL || cc == NULL)
+ {
+ return PECI_CC_INVALID_REQ;
+ }
+
+ // The PECI buffer must be large enough to hold the requested data
+ if (sizeof(cmd.data) < u8ReadLen)
+ {
+ return PECI_CC_INVALID_REQ;
+ }
+
+ cmd.addr = target;
+ cmd.msg_type = u8MsgType;
+ cmd.params.pci_cfg.seg = u8Seg;
+ cmd.params.pci_cfg.bus = u8Bus;
+ cmd.params.pci_cfg.device = u8Device;
+ cmd.params.pci_cfg.function = u8Fcn;
+ cmd.params.pci_cfg.reg = u16Reg;
+ cmd.rx_len = u8ReadLen;
+
+ ret = HW_peci_issue_cmd(PECI_IOC_RD_END_PT_CFG, (char*)&cmd, peci_fd);
+ *cc = cmd.cc;
+
+ if (ret == PECI_CC_SUCCESS)
+ {
+ memcpy(pPCIData, cmd.data, u8ReadLen);
+ }
+ else
+ {
+ ret = PECI_CC_DRIVER_ERR;
+ }
+
+ return ret;
+}
+
+/*-------------------------------------------------------------------------
+ * This function provides read access to the PCI configuration space at
+ * the requested PCI configuration address.
+ *------------------------------------------------------------------------*/
+EPECIStatus peci_RdEndPointConfigPci(uint8_t target, uint8_t u8Seg,
+ uint8_t u8Bus, uint8_t u8Device,
+ uint8_t u8Fcn, uint16_t u16Reg,
+ uint8_t u8ReadLen, uint8_t* pPCIData,
+ uint8_t* cc)
+{
+ int peci_fd = -1;
+ EPECIStatus ret;
+
+ if (pPCIData == NULL || cc == NULL)
+ {
+ return PECI_CC_INVALID_REQ;
+ }
+
+ if (peci_Open(&peci_fd) != PECI_CC_SUCCESS)
+ {
+ return PECI_CC_DRIVER_ERR;
+ }
+ ret =
+ peci_RdEndPointConfigPci_seq(target, u8Seg, u8Bus, u8Device, u8Fcn,
+ u16Reg, u8ReadLen, pPCIData, peci_fd, cc);
+ peci_Close(peci_fd);
+ return ret;
+}
+
+/*-------------------------------------------------------------------------
+ * This function allows sequential RdEndPointConfig to PCI with the provided
+ * peci file descriptor.
+ *------------------------------------------------------------------------*/
+EPECIStatus peci_RdEndPointConfigPci_seq(uint8_t target, uint8_t u8Seg,
+ uint8_t u8Bus, uint8_t u8Device,
+ uint8_t u8Fcn, uint16_t u16Reg,
+ uint8_t u8ReadLen, uint8_t* pPCIData,
+ int peci_fd, uint8_t* cc)
+{
+ if (pPCIData == NULL || cc == NULL)
+ {
+ return PECI_CC_INVALID_REQ;
+ }
+
+ // Per the PECI spec, the read length must be a byte, word, or dword
+ if (u8ReadLen != 1 && u8ReadLen != 2 && u8ReadLen != 4)
+ {
+ return PECI_CC_INVALID_REQ;
+ }
+
+ return peci_RdEndPointConfigPciCommon(target, PECI_ENDPTCFG_TYPE_PCI, u8Seg,
+ u8Bus, u8Device, u8Fcn, u16Reg,
+ u8ReadLen, pPCIData, peci_fd, cc);
+}
+
+/*-------------------------------------------------------------------------
+ * This function provides read access to the Local PCI configuration space at
+ * the requested PCI configuration address.
+ *------------------------------------------------------------------------*/
+EPECIStatus peci_RdEndPointConfigPciLocal(uint8_t target, uint8_t u8Seg,
+ uint8_t u8Bus, uint8_t u8Device,
+ uint8_t u8Fcn, uint16_t u16Reg,
+ uint8_t u8ReadLen, uint8_t* pPCIData,
+ uint8_t* cc)
+{
+ int peci_fd = -1;
+ EPECIStatus ret;
+
+ if (pPCIData == NULL || cc == NULL)
+ {
+ return PECI_CC_INVALID_REQ;
+ }
+
+ if (peci_Open(&peci_fd) != PECI_CC_SUCCESS)
+ {
+ return PECI_CC_DRIVER_ERR;
+ }
+ ret = peci_RdEndPointConfigPciLocal_seq(target, u8Seg, u8Bus, u8Device,
+ u8Fcn, u16Reg, u8ReadLen, pPCIData,
+ peci_fd, cc);
+ peci_Close(peci_fd);
+ return ret;
+}
+
+/*-------------------------------------------------------------------------
+ * This function allows sequential RdEndPointConfig to PCI Local with the
+ *provided peci file descriptor.
+ *------------------------------------------------------------------------*/
+EPECIStatus peci_RdEndPointConfigPciLocal_seq(uint8_t target, uint8_t u8Seg,
+ uint8_t u8Bus, uint8_t u8Device,
+ uint8_t u8Fcn, uint16_t u16Reg,
+ uint8_t u8ReadLen,
+ uint8_t* pPCIData, int peci_fd,
+ uint8_t* cc)
+{
+ if (pPCIData == NULL || cc == NULL)
+ {
+ return PECI_CC_INVALID_REQ;
+ }
+
+ // Per the PECI spec, the read length must be a byte, word, or dword
+ if (u8ReadLen != 1 && u8ReadLen != 2 && u8ReadLen != 4)
+ {
+ return PECI_CC_INVALID_REQ;
+ }
+
+ return peci_RdEndPointConfigPciCommon(target, PECI_ENDPTCFG_TYPE_LOCAL_PCI,
+ u8Seg, u8Bus, u8Device, u8Fcn, u16Reg,
+ u8ReadLen, pPCIData, peci_fd, cc);
+}
+
+/*-------------------------------------------------------------------------
+ * This function provides read access to PCI MMIO space at
+ * the requested PCI configuration address.
+ *------------------------------------------------------------------------*/
+EPECIStatus peci_RdEndPointConfigMmio(uint8_t target, uint8_t u8Seg,
+ uint8_t u8Bus, uint8_t u8Device,
+ uint8_t u8Fcn, uint8_t u8Bar,
+ uint8_t u8AddrType, uint64_t u64Offset,
+ uint8_t u8ReadLen, uint8_t* pMmioData,
+ uint8_t* cc)
+{
+ int peci_fd = -1;
+ EPECIStatus ret;
+
+ if (pMmioData == NULL || cc == NULL)
+ {
+ return PECI_CC_INVALID_REQ;
+ }
+
+ if (peci_Open(&peci_fd) != PECI_CC_SUCCESS)
+ {
+ return PECI_CC_DRIVER_ERR;
+ }
+ ret = peci_RdEndPointConfigMmio_seq(target, u8Seg, u8Bus, u8Device, u8Fcn,
+ u8Bar, u8AddrType, u64Offset, u8ReadLen,
+ pMmioData, peci_fd, cc);
+ peci_Close(peci_fd);
+ return ret;
+}
+
+/*-------------------------------------------------------------------------
+ * This function allows sequential RdEndPointConfig to PCI MMIO with the
+ *provided peci file descriptor.
+ *------------------------------------------------------------------------*/
+EPECIStatus peci_RdEndPointConfigMmio_seq(
+ uint8_t target, uint8_t u8Seg, uint8_t u8Bus, uint8_t u8Device,
+ uint8_t u8Fcn, uint8_t u8Bar, uint8_t u8AddrType, uint64_t u64Offset,
+ uint8_t u8ReadLen, uint8_t* pMmioData, int peci_fd, uint8_t* cc)
+{
+ struct peci_rd_end_pt_cfg_msg cmd;
+ EPECIStatus ret;
+
+ if (pMmioData == NULL || cc == NULL)
+ {
+ return PECI_CC_INVALID_REQ;
+ }
+
+ // Per the PECI spec, the read length must be a byte, word, dword, or qword
+ if (u8ReadLen != 1 && u8ReadLen != 2 && u8ReadLen != 4 && u8ReadLen != 8)
+ {
+ return PECI_CC_INVALID_REQ;
+ }
+
+ // The PECI buffer must be large enough to hold the requested data
+ if (sizeof(cmd.data) < u8ReadLen)
+ {
+ return PECI_CC_INVALID_REQ;
+ }
+
+ cmd.addr = target;
+ cmd.msg_type = PECI_ENDPTCFG_TYPE_MMIO;
+ cmd.params.mmio.seg = u8Seg;
+ cmd.params.mmio.bus = u8Bus;
+ cmd.params.mmio.device = u8Device;
+ cmd.params.mmio.function = u8Fcn;
+ cmd.params.mmio.bar = u8Bar;
+ cmd.params.mmio.addr_type = u8AddrType;
+ cmd.params.mmio.offset = u64Offset;
+ cmd.rx_len = u8ReadLen;
+
+ ret = HW_peci_issue_cmd(PECI_IOC_RD_END_PT_CFG, (char*)&cmd, peci_fd);
+ *cc = cmd.cc;
+
+ if (ret == PECI_CC_SUCCESS)
+ {
+ memcpy(pMmioData, cmd.data, u8ReadLen);
+ }
+ else
+ {
+ ret = PECI_CC_DRIVER_ERR;
+ }
+
+ return ret;
+}
+
+/*-------------------------------------------------------------------------
+ * This function provides crashdump discovery data over PECI
+ *------------------------------------------------------------------------*/
+EPECIStatus peci_CrashDump_Discovery(uint8_t target, uint8_t subopcode,
+ uint8_t param0, uint16_t param1,
+ uint8_t param2, uint8_t u8ReadLen,
+ uint8_t* pData, uint8_t* cc)
+{
+ int peci_fd = -1;
+ struct peci_crashdump_disc_msg cmd;
+ EPECIStatus ret;
+
+ if (pData == NULL || cc == NULL)
+ {
+ return PECI_CC_INVALID_REQ;
+ }
+
+ // Per the PECI spec, the read length must be a byte, word, or qword
+ if (u8ReadLen != 1 && u8ReadLen != 2 && u8ReadLen != 8)
+ {
+ return PECI_CC_INVALID_REQ;
+ }
+
+ // The PECI buffer must be large enough to hold the requested data
+ if (sizeof(cmd.data) < u8ReadLen)
+ {
+ return PECI_CC_INVALID_REQ;
+ }
+
+ if (peci_Open(&peci_fd) != PECI_CC_SUCCESS)
+ {
+ return PECI_CC_DRIVER_ERR;
+ }
+
+ cmd.addr = target;
+ cmd.subopcode = subopcode;
+ cmd.param0 = param0;
+ cmd.param1 = param1;
+ cmd.param2 = param2;
+ cmd.rx_len = u8ReadLen;
+
+ ret = HW_peci_issue_cmd(PECI_IOC_CRASHDUMP_DISC, (char*)&cmd, peci_fd);
+ *cc = cmd.cc;
+ if (ret == PECI_CC_SUCCESS)
+ {
+ memcpy(pData, cmd.data, u8ReadLen);
+ }
+ else
+ {
+ ret = PECI_CC_DRIVER_ERR;
+ }
+
+ peci_Close(peci_fd);
+ return ret;
+}
+
+/*-------------------------------------------------------------------------
+ * This function provides crashdump GetFrame data over PECI
+ *------------------------------------------------------------------------*/
+EPECIStatus peci_CrashDump_GetFrame(uint8_t target, uint16_t param0,
+ uint16_t param1, uint16_t param2,
+ uint8_t u8ReadLen, uint8_t* pData,
+ uint8_t* cc)
+{
+ int peci_fd = -1;
+ struct peci_crashdump_get_frame_msg cmd;
+ EPECIStatus ret;
+
+ if (pData == NULL || cc == NULL)
+ {
+ return PECI_CC_INVALID_REQ;
+ }
+
+ // Per the PECI spec, the read length must be a qword or dqword
+ if (u8ReadLen != 8 && u8ReadLen != 16)
+ {
+ return PECI_CC_INVALID_REQ;
+ }
+
+ // The PECI buffer must be large enough to hold the requested data
+ if (sizeof(cmd.data) < u8ReadLen)
+ {
+ return PECI_CC_INVALID_REQ;
+ }
+
+ if (peci_Open(&peci_fd) != PECI_CC_SUCCESS)
+ {
+ return PECI_CC_DRIVER_ERR;
+ }
+
+ cmd.addr = target;
+ cmd.param0 = param0;
+ cmd.param1 = param1;
+ cmd.param2 = param2;
+ cmd.rx_len = u8ReadLen;
+
+ ret = HW_peci_issue_cmd(PECI_IOC_CRASHDUMP_GET_FRAME, (char*)&cmd, peci_fd);
+ *cc = cmd.cc;
+ if (ret == PECI_CC_SUCCESS)
+ {
+ memcpy(pData, cmd.data, u8ReadLen);
+ }
+ else
+ {
+ ret = PECI_CC_DRIVER_ERR;
+ }
+
+ peci_Close(peci_fd);
+ return ret;
+}
+
+/*-------------------------------------------------------------------------
+ * This function provides raw PECI command access
+ *------------------------------------------------------------------------*/
+EPECIStatus peci_raw(uint8_t target, uint8_t u8ReadLen, const uint8_t* pRawCmd,
+ const uint32_t cmdSize, uint8_t* pRawResp,
+ uint32_t respSize)
+{
+ int peci_fd = -1;
+ struct peci_xfer_msg cmd;
+ uint8_t u8TxBuf[PECI_BUFFER_SIZE];
+ uint8_t u8RxBuf[PECI_BUFFER_SIZE];
+ EPECIStatus ret;
+
+ if (pRawResp == NULL)
+ {
+ return PECI_CC_INVALID_REQ;
+ }
+
+ if (peci_Open(&peci_fd) != PECI_CC_SUCCESS)
+ {
+ return PECI_CC_DRIVER_ERR;
+ }
+
+ // Check for valid buffer sizes
+ if (cmdSize > PECI_BUFFER_SIZE || respSize < u8ReadLen ||
+ u8ReadLen >
+ (PECI_BUFFER_SIZE - 1)) // response buffer is data + 1 status byte
+ {
+ peci_Close(peci_fd);
+ return PECI_CC_INVALID_REQ;
+ }
+
+ cmd.addr = target;
+ cmd.tx_len = cmdSize;
+ cmd.rx_len = u8ReadLen;
+
+ memcpy(u8TxBuf, pRawCmd, cmdSize);
+
+ cmd.tx_buf = u8TxBuf;
+ cmd.rx_buf = u8RxBuf;
+ ret = HW_peci_issue_cmd(PECI_IOC_XFER, (char*)&cmd, peci_fd);
+
+ if (ret == PECI_CC_SUCCESS)
+ {
+ memcpy(pRawResp, u8RxBuf, u8ReadLen);
+ }
+
+ peci_Close(peci_fd);
+ return ret;
+}
+
+/*-------------------------------------------------------------------------
+ * This function returns the CPUID for the given PECI client address
+ *------------------------------------------------------------------------*/
+EPECIStatus peci_GetCPUID(const uint8_t clientAddr, CPUModel* cpuModel,
+ uint8_t* cc)
+{
+ if (cpuModel == NULL || cc == NULL)
+ {
+ return PECI_CC_INVALID_REQ;
+ }
+
+ if (peci_Ping(clientAddr) != PECI_CC_SUCCESS)
+ {
+ return PECI_CC_CPU_NOT_PRESENT;
+ }
+
+ return peci_RdPkgConfig(clientAddr, PECI_MBX_INDEX_CPU_ID,
+ PECI_PKG_ID_CPU_ID, sizeof(uint32_t),
+ (uint8_t*)cpuModel, cc);
+}
diff --git a/libpeci/peci.h b/libpeci/peci.h
new file mode 100644
index 0000000..05a60bd
--- /dev/null
+++ b/libpeci/peci.h
@@ -0,0 +1,224 @@
+/*
+// Copyright (c) 2019 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+#pragma once
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <inttypes.h>
+#include <linux/peci-ioctl.h>
+#include <stdbool.h>
+
+// PECI Client Address List
+#define MIN_CLIENT_ADDR 0x30
+#define MAX_CLIENT_ADDR 0x37
+#define MAX_CPUS (MAX_CLIENT_ADDR - MIN_CLIENT_ADDR + 1)
+
+typedef enum
+{
+ skx = 0x00050654,
+ clx = 0x00050656,
+ clx2 = 0x00050657,
+ cpx = 0x0005065A,
+ icx = 0x000606A0,
+} CPUModel;
+
+// PECI Status Codes
+typedef enum
+{
+ PECI_CC_SUCCESS = 0,
+ PECI_CC_INVALID_REQ,
+ PECI_CC_HW_ERR,
+ PECI_CC_DRIVER_ERR,
+ PECI_CC_CPU_NOT_PRESENT,
+ PECI_CC_MEM_ERR,
+} EPECIStatus;
+
+// PECI Timeout Options
+typedef enum
+{
+ PECI_WAIT_FOREVER = -1,
+ PECI_NO_WAIT = 0,
+} EPECITimeout;
+
+#define PECI_TIMEOUT_RESOLUTION_MS 10 // 10 ms
+#define PECI_TIMEOUT_MS 100 // 100 ms
+
+// VCU Index and Sequence Paramaters
+#define VCU_SET_PARAM 0x0001
+#define VCU_READ 0x0002
+#define VCU_OPEN_SEQ 0x0003
+#define VCU_CLOSE_SEQ 0x0004
+#define VCU_ABORT_SEQ 0x0005
+#define VCU_VERSION 0x0009
+
+typedef enum
+{
+ VCU_READ_LOCAL_CSR_SEQ = 0x2,
+ VCU_READ_LOCAL_MMIO_SEQ = 0x6,
+ VCU_EN_SECURE_DATA_SEQ = 0x14,
+ VCU_CORE_MCA_SEQ = 0x10000,
+ VCU_UNCORE_MCA_SEQ = 0x10000,
+ VCU_IOT_BRKPT_SEQ = 0x10010,
+ VCU_MBP_CONFIG_SEQ = 0x10026,
+ VCU_PWR_MGT_SEQ = 0x1002a,
+ VCU_CRASHDUMP_SEQ = 0x10038,
+ VCU_ARRAY_DUMP_SEQ = 0x20000,
+ VCU_SCAN_DUMP_SEQ = 0x20008,
+ VCU_TOR_DUMP_SEQ = 0x30002,
+ VCU_SQ_DUMP_SEQ = 0x30004,
+ VCU_UNCORE_CRASHDUMP_SEQ = 0x30006,
+} EPECISequence;
+
+#define MBX_INDEX_VCU 128 // VCU Index
+
+typedef enum
+{
+ MMIO_DWORD_OFFSET = 0x05,
+ MMIO_QWORD_OFFSET = 0x06,
+} EEndPtMmioAddrType;
+
+// Find the specified PCI bus number value
+EPECIStatus FindBusNumber(uint8_t u8Bus, uint8_t u8Cpu, uint8_t* pu8BusValue);
+
+// Gets the temperature from the target
+// Expressed in signed fixed point value of 1/64 degrees celsius
+EPECIStatus peci_GetTemp(uint8_t target, int16_t* temperature);
+
+// Provides read access to the package configuration space within the processor
+EPECIStatus peci_RdPkgConfig(uint8_t target, uint8_t u8Index, uint16_t u16Value,
+ uint8_t u8ReadLen, uint8_t* pPkgConfig,
+ uint8_t* cc);
+
+// Allows sequential RdPkgConfig with the provided peci file descriptor
+EPECIStatus peci_RdPkgConfig_seq(uint8_t target, uint8_t u8Index,
+ uint16_t u16Value, uint8_t u8ReadLen,
+ uint8_t* pPkgConfig, int peci_fd, uint8_t* cc);
+
+// Provides write access to the package configuration space within the processor
+EPECIStatus peci_WrPkgConfig(uint8_t target, uint8_t u8Index, uint16_t u16Param,
+ uint32_t u32Value, uint8_t u8WriteLen,
+ uint8_t* cc);
+
+// Allows sequential WrPkgConfig with the provided peci file descriptor
+EPECIStatus peci_WrPkgConfig_seq(uint8_t target, uint8_t u8Index,
+ uint16_t u16Param, uint32_t u32Value,
+ uint8_t u8WriteLen, int peci_fd, uint8_t* cc);
+
+// Provides read access to Model Specific Registers
+EPECIStatus peci_RdIAMSR(uint8_t target, uint8_t threadID, uint16_t MSRAddress,
+ uint64_t* u64MsrVal, uint8_t* cc);
+
+// Provides read access to PCI Configuration space
+EPECIStatus peci_RdPCIConfig(uint8_t target, uint8_t u8Bus, uint8_t u8Device,
+ uint8_t u8Fcn, uint16_t u16Reg, uint8_t* pPCIReg,
+ uint8_t* cc);
+
+// Allows sequential RdPCIConfig with the provided peci file descriptor
+EPECIStatus peci_RdPCIConfig_seq(uint8_t target, uint8_t u8Bus,
+ uint8_t u8Device, uint8_t u8Fcn,
+ uint16_t u16Reg, uint8_t* pPCIData,
+ int peci_fd, uint8_t* cc);
+
+// Provides read access to the local PCI Configuration space
+EPECIStatus peci_RdPCIConfigLocal(uint8_t target, uint8_t u8Bus,
+ uint8_t u8Device, uint8_t u8Fcn,
+ uint16_t u16Reg, uint8_t u8ReadLen,
+ uint8_t* pPCIReg, uint8_t* cc);
+
+// Allows sequential RdPCIConfigLocal with the provided peci file descriptor
+EPECIStatus peci_RdPCIConfigLocal_seq(uint8_t target, uint8_t u8Bus,
+ uint8_t u8Device, uint8_t u8Fcn,
+ uint16_t u16Reg, uint8_t u8ReadLen,
+ uint8_t* pPCIReg, int peci_fd,
+ uint8_t* cc);
+
+// Provides write access to the local PCI Configuration space
+EPECIStatus peci_WrPCIConfigLocal(uint8_t target, uint8_t u8Bus,
+ uint8_t u8Device, uint8_t u8Fcn,
+ uint16_t u16Reg, uint8_t DataLen,
+ uint32_t DataVal, uint8_t* cc);
+
+// Provides read access to PCI configuration space
+EPECIStatus peci_RdEndPointConfigPci(uint8_t target, uint8_t u8Seg,
+ uint8_t u8Bus, uint8_t u8Device,
+ uint8_t u8Fcn, uint16_t u16Reg,
+ uint8_t u8ReadLen, uint8_t* pPCIData,
+ uint8_t* cc);
+
+// Allows sequential RdEndPointConfig to PCI Configuration space
+EPECIStatus peci_RdEndPointConfigPci_seq(uint8_t target, uint8_t u8Seg,
+ uint8_t u8Bus, uint8_t u8Device,
+ uint8_t u8Fcn, uint16_t u16Reg,
+ uint8_t u8ReadLen, uint8_t* pPCIData,
+ int peci_fd, uint8_t* cc);
+
+// Provides read access to the local PCI configuration space
+EPECIStatus peci_RdEndPointConfigPciLocal(uint8_t target, uint8_t u8Seg,
+ uint8_t u8Bus, uint8_t u8Device,
+ uint8_t u8Fcn, uint16_t u16Reg,
+ uint8_t u8ReadLen, uint8_t* pPCIData,
+ uint8_t* cc);
+
+// Allows sequential RdEndPointConfig to the local PCI Configuration space
+EPECIStatus peci_RdEndPointConfigPciLocal_seq(uint8_t target, uint8_t u8Seg,
+ uint8_t u8Bus, uint8_t u8Device,
+ uint8_t u8Fcn, uint16_t u16Reg,
+ uint8_t u8ReadLen,
+ uint8_t* pPCIData, int peci_fd,
+ uint8_t* cc);
+
+// Provides read access to PCI MMIO space
+EPECIStatus peci_RdEndPointConfigMmio(uint8_t target, uint8_t u8Seg,
+ uint8_t u8Bus, uint8_t u8Device,
+ uint8_t u8Fcn, uint8_t u8Bar,
+ uint8_t u8AddrType, uint64_t u64Offset,
+ uint8_t u8ReadLen, uint8_t* pMmioData,
+ uint8_t* cc);
+
+// Allows sequential RdEndPointConfig to PCI MMIO space
+EPECIStatus peci_RdEndPointConfigMmio_seq(
+ uint8_t target, uint8_t u8Seg, uint8_t u8Bus, uint8_t u8Device,
+ uint8_t u8Fcn, uint8_t u8Bar, uint8_t u8AddrType, uint64_t u64Offset,
+ uint8_t u8ReadLen, uint8_t* pMmioData, int peci_fd, uint8_t* cc);
+
+// Provides access to the Crashdump Discovery API
+EPECIStatus peci_CrashDump_Discovery(uint8_t target, uint8_t subopcode,
+ uint8_t param0, uint16_t param1,
+ uint8_t param2, uint8_t u8ReadLen,
+ uint8_t* pData, uint8_t* cc);
+
+// Provides access to the Crashdump GetFrame API
+EPECIStatus peci_CrashDump_GetFrame(uint8_t target, uint16_t param0,
+ uint16_t param1, uint16_t param2,
+ uint8_t u8ReadLen, uint8_t* pData,
+ uint8_t* cc);
+
+// Provides raw PECI command access
+EPECIStatus peci_raw(uint8_t target, uint8_t u8ReadLen, const uint8_t* pRawCmd,
+ const uint32_t cmdSize, uint8_t* pRawResp,
+ uint32_t respSize);
+
+EPECIStatus peci_Lock(int* peci_fd, uint32_t timeout_ms);
+void peci_Unlock(int peci_fd);
+EPECIStatus peci_Ping(uint8_t target);
+EPECIStatus peci_Ping_seq(uint8_t target, int peci_fd);
+EPECIStatus peci_GetCPUID(const uint8_t clientAddr, CPUModel* cpuModel,
+ uint8_t* cc);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/libpeci/peci_cmds.c b/libpeci/peci_cmds.c
new file mode 100644
index 0000000..ba37810
--- /dev/null
+++ b/libpeci/peci_cmds.c
@@ -0,0 +1,370 @@
+/*
+// Copyright (c) 2019 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+#include <inttypes.h>
+#include <peci.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#ifndef ABS
+#define ABS(_v_) (((_v_) > 0) ? (_v_) : -(_v_))
+#endif
+
+extern EPECIStatus peci_GetDIB(uint8_t target, uint64_t* dib);
+
+void Usage(char* progname)
+{
+ printf("Usage :\n");
+ printf("\t%s [-a <addr>] [-s <size>] <command> [parameters]\n", progname);
+ printf("\t\t-a : Address of the target in decimal. Default is 48.\n");
+ printf("\t\t-s : Size of data to read or write in bytes. Default is 4.\n");
+ printf("\t\t-b : Display completion code.\n");
+ printf("\t\t-n : Ping the target.\n");
+ printf("\t\t-t : Get the temperature.\n");
+ printf("\t\t-d : Get the DIB.\n");
+ printf(
+ "\t\t-p : PCI Read for specific hex address <Bus Dev Func [Reg]>.\n");
+ printf("\t\t-pw : PCI Write for specific hex address <Bus Dev Func [Reg] "
+ "Data>.\n");
+ printf("\t\t-c : Read Package Config <Index Parameter>.\n");
+ printf("\t\t-cw : Write Package Config <Index Parameter Data>.\n");
+ printf("\t\t-m : MSR Read <Thread Address>.\n");
+ printf("\t\t-l : Local PCI Read for specific hex address <Bus Dev Func "
+ "[Reg]>.\n");
+ printf("\t\t-lw : Local PCI Write for specific hex address <Bus Dev Func "
+ "[Reg] Data>.\n");
+ printf("\n");
+}
+
+int main(int argc, char* argv[])
+{
+ int c;
+ EPECIStatus ret;
+ int cnt = 0;
+ uint8_t u8Cmd = PECI_CMD_MAX;
+ uint8_t address = 0x30; // use default address of 48d
+ uint8_t u8Size = 4; // default to a DWORD
+ uint32_t u32PciReadVal = 0;
+ uint8_t u8PciBus = 0;
+ uint8_t u8PciDev = 0;
+ uint8_t u8PciFunc = 0;
+ uint16_t u16PciReg = 0;
+ uint32_t u32PciWriteVal = 0;
+ uint8_t u8PkgIndex = 0;
+ uint16_t u16PkgParam = 0;
+ uint32_t u32PkgValue = 0;
+ uint8_t u8MsrThread = 0;
+ uint16_t u16MsrAddr = 0;
+ uint64_t u64MsrVal = 0;
+ short temperature;
+ uint64_t dib;
+ uint8_t u8Index = 0;
+ uint8_t cc = 0;
+ bool showCc = false;
+
+ //
+ // Parse arguments.
+ //
+ while (-1 != (c = getopt(argc, argv, "a:s:nbtdp::c::m::l::")))
+ {
+ switch (c)
+ {
+ case 'a':
+ if (optarg != NULL)
+ address = (unsigned char)atoi(optarg);
+ break;
+
+ case 's':
+ if (optarg != NULL)
+ u8Size = (unsigned char)atoi(optarg);
+ break;
+
+ case 'b':
+ showCc = true;
+ break;
+
+ case 'n':
+ cnt++;
+ u8Cmd = PECI_CMD_PING;
+ break;
+
+ case 't':
+ cnt++;
+ u8Cmd = PECI_CMD_GET_TEMP;
+ break;
+
+ case 'd':
+ cnt++;
+ u8Cmd = PECI_CMD_GET_DIB;
+ break;
+
+ case 'p':
+ cnt++;
+ u8Cmd = PECI_CMD_RD_PCI_CFG;
+ if (optarg != NULL && optarg[0] == 'w')
+ u8Cmd = PECI_CMD_WR_PCI_CFG;
+ break;
+
+ case 'c':
+ cnt++;
+ u8Cmd = PECI_CMD_RD_PKG_CFG;
+ if (optarg != NULL && optarg[0] == 'w')
+ u8Cmd = PECI_CMD_WR_PKG_CFG;
+ break;
+
+ case 'm':
+ cnt++;
+ u8Cmd = PECI_CMD_RD_IA_MSR;
+ break;
+
+ case 'l':
+ cnt++;
+ u8Cmd = PECI_CMD_RD_PCI_CFG_LOCAL;
+ if (optarg != NULL && optarg[0] == 'w')
+ u8Cmd = PECI_CMD_WR_PCI_CFG_LOCAL;
+ break;
+
+ default:
+ printf("ERROR: Unrecognized option \"-%c\".\n", optopt);
+ goto ErrorExit;
+ break;
+ }
+ }
+
+ if (1 != cnt)
+ {
+ printf("ERROR: Invalid options.\n");
+ goto ErrorExit;
+ }
+
+ //
+ // Execute the command
+ //
+ printf("PECI target[%u]: ", (int)address);
+ switch (u8Cmd)
+ {
+ case PECI_CMD_PING:
+ printf("Pinging ... ");
+ (0 == peci_Ping(address)) ? printf("Succeeded.\n")
+ : printf("Failed.\n");
+ break;
+
+ case PECI_CMD_GET_TEMP:
+ ret = peci_GetTemp(address, &temperature);
+ if (0 != ret)
+ {
+ printf("ERROR: Retrieving temperature failed.\n");
+ break;
+ }
+ printf("Temperature is %04xh (%c%d.%02dC).\n",
+ (int)(unsigned int)(unsigned short)temperature,
+ (0 > temperature) ? '-' : '+',
+ (int)((unsigned int)ABS(temperature) / 64),
+ (int)(((unsigned int)ABS(temperature) % 64) * 100) / 64);
+ break;
+
+ case PECI_CMD_GET_DIB:
+ ret = peci_GetDIB(address, &dib);
+ if (0 != ret)
+ {
+ printf("ERROR: Retrieving DIB failed.\n");
+ break;
+ }
+ printf("GetDIB Returned: 0x%" PRIx64 "\n", dib);
+ break;
+
+ case PECI_CMD_RD_PCI_CFG:
+ u8Index = argc;
+ switch (argc - optind)
+ {
+ case 4:
+ u16PciReg = strtoul(argv[--u8Index], NULL, 16);
+ case 3:
+ u8PciFunc = strtoul(argv[--u8Index], NULL, 16);
+ case 2:
+ u8PciDev = strtoul(argv[--u8Index], NULL, 16);
+ case 1:
+ u8PciBus = strtoul(argv[--u8Index], NULL, 16);
+ break;
+ default:
+ printf("ERROR: Unsupported arguments for PCI Write\n");
+ goto ErrorExit;
+ break;
+ }
+ ret = peci_RdPCIConfig(address, u8PciBus, u8PciDev, u8PciFunc,
+ u16PciReg, (uint8_t*)&u32PciReadVal, &cc);
+ if (showCc)
+ printf("PCI Read cc:0x%x\n", cc);
+ if (0 != ret)
+ {
+ printf("ERROR: PCI Read failed or is not supported.\n");
+ break;
+ }
+ printf("PCI Read of %02x:%02x:%02x Reg %02x: 0x%0*x\n", u8PciBus,
+ u8PciDev, u8PciFunc, u16PciReg, u8Size * 2, u32PciReadVal);
+ break;
+
+ case PECI_CMD_RD_PKG_CFG:
+ u8Index = argc;
+ switch (argc - optind)
+ {
+ case 2:
+ u16PkgParam = strtoul(argv[--u8Index], NULL, 16);
+ u8PkgIndex = strtoul(argv[--u8Index], NULL, 16);
+ break;
+ default:
+ printf("ERROR: Unsupported arguments for Pkg Read\n");
+ goto ErrorExit;
+ break;
+ }
+ ret = peci_RdPkgConfig(address, u8PkgIndex, u16PkgParam, u8Size,
+ (uint8_t*)&u32PkgValue, &cc);
+ if (showCc)
+ printf("Pkg Read cc:0x%x\n", cc);
+ if (0 != ret)
+ {
+ printf("ERROR: Read Package failed or is not supported.\n");
+ break;
+ }
+ printf("Pkg Read of Index %02x Param %04x: 0x%0*x\n", u8PkgIndex,
+ u16PkgParam, u8Size * 2, u32PkgValue);
+ break;
+
+ case PECI_CMD_WR_PKG_CFG:
+ u8Index = argc;
+ switch (argc - optind)
+ {
+ case 3:
+ u32PkgValue = strtoul(argv[--u8Index], NULL, 16);
+ u16PkgParam = strtoul(argv[--u8Index], NULL, 16);
+ u8PkgIndex = strtoul(argv[--u8Index], NULL, 16);
+ break;
+ default:
+ printf("ERROR: Unsupported arguments for Pkg Write\n");
+ goto ErrorExit;
+ break;
+ }
+ printf("Pkg Write of Index %02x Param %04x: 0x%0*x\n", u8PkgIndex,
+ u16PkgParam, u8Size * 2, u32PkgValue);
+ ret = peci_WrPkgConfig(address, u8PkgIndex, u16PkgParam,
+ u32PkgValue, u8Size, &cc);
+ if (showCc)
+ printf("Pkg Write cc:0x%x\n", cc);
+ (0 == ret) ? printf("Succeeded.\n")
+ : printf("Failed or not supported.\n");
+ break;
+
+ case PECI_CMD_RD_IA_MSR:
+ u8Index = argc;
+ switch (argc - optind)
+ {
+ case 2:
+ u16MsrAddr = strtoul(argv[--u8Index], NULL, 16);
+ u8MsrThread = strtoul(argv[--u8Index], NULL, 16);
+ break;
+ default:
+ printf("ERROR: Unsupported arguments for MSR Read\n");
+ goto ErrorExit;
+ break;
+ }
+ ret =
+ peci_RdIAMSR(address, u8MsrThread, u16MsrAddr, &u64MsrVal, &cc);
+ if (showCc)
+ printf("MSR Read cc:0x%x\n", cc);
+ if (0 != ret)
+ {
+ printf("ERROR: Read MSR failed or is not supported. %x\n", ret);
+ break;
+ }
+ printf("MSR Read of Thread %02x MSR %04x: 0x%0*" PRIx64 "\n",
+ u8MsrThread, u16MsrAddr, u8Size * 2, u64MsrVal);
+ break;
+
+ case PECI_CMD_RD_PCI_CFG_LOCAL:
+ u8Index = argc;
+ switch (argc - optind)
+ {
+ case 4:
+ u16PciReg = strtoul(argv[--u8Index], NULL, 16);
+ case 3:
+ u8PciFunc = strtoul(argv[--u8Index], NULL, 16);
+ case 2:
+ u8PciDev = strtoul(argv[--u8Index], NULL, 16);
+ case 1:
+ u8PciBus = strtoul(argv[--u8Index], NULL, 16);
+ break;
+ default:
+ printf(
+ "ERROR: Unsupported arguments for Local PCI Write\n");
+ goto ErrorExit;
+ break;
+ }
+ ret = peci_RdPCIConfigLocal(address, u8PciBus, u8PciDev, u8PciFunc,
+ u16PciReg, u8Size,
+ (uint8_t*)&u32PciReadVal, &cc);
+ if (showCc)
+ printf("Local PCI Read cc:0x%x\n", cc);
+ if (0 != ret)
+ {
+ printf("ERROR: Local PCI Read failed or is not supported.\n");
+ break;
+ }
+ printf("Local PCI Read of %02x:%02x:%02x Reg %02x: 0x%0*x\n",
+ u8PciBus, u8PciDev, u8PciFunc, u16PciReg, u8Size * 2,
+ u32PciReadVal);
+ break;
+
+ case PECI_CMD_WR_PCI_CFG_LOCAL:
+ u8Index = argc;
+ u32PciWriteVal = strtoul(argv[--u8Index], NULL, 16);
+ switch (argc - optind)
+ {
+ case 5:
+ u16PciReg = strtoul(argv[--u8Index], NULL, 16);
+ case 4:
+ u8PciFunc = strtoul(argv[--u8Index], NULL, 16);
+ case 3:
+ u8PciDev = strtoul(argv[--u8Index], NULL, 16);
+ case 2:
+ u8PciBus = strtoul(argv[--u8Index], NULL, 16);
+ break;
+ default:
+ printf(
+ "ERROR: Unsupported arguments for Local PCI Write\n");
+ goto ErrorExit;
+ break;
+ }
+ printf("Local PCI Write of %02x:%02x:%02x Reg %02x: 0x%0*x\n",
+ u8PciBus, u8PciDev, u8PciFunc, u16PciReg, u8Size * 2,
+ u32PciWriteVal);
+ ret = peci_WrPCIConfigLocal(address, u8PciBus, u8PciDev, u8PciFunc,
+ u16PciReg, u8Size, u32PciWriteVal, &cc);
+ if (showCc)
+ printf("Local PCI Write cc:0x%x\n", cc);
+ (0 == ret) ? printf("Succeeded.\n")
+ : printf("Failed or not supported.\n");
+ break;
+
+ default:
+ printf("ERROR: Unrecognized command\n");
+ goto ErrorExit;
+ break;
+ }
+ return 0;
+
+ErrorExit:
+ Usage(argv[0]);
+ return 1;
+}
diff --git a/prov-mode-mgr/src/prov-mode-mgr.cpp b/prov-mode-mgr/src/prov-mode-mgr.cpp
index c6c3315..33d084a 100644
--- a/prov-mode-mgr/src/prov-mode-mgr.cpp
+++ b/prov-mode-mgr/src/prov-mode-mgr.cpp
@@ -36,8 +36,9 @@ ProvModeMgr::ProvModeMgr(
phosphor::logging::log<phosphor::logging::level::ERR>(
"Error in querying provision mode",
phosphor::logging::entry("MSG=%s", ec.message().c_str()));
- phosphor::logging::elog<sdbusplus::xyz::openbmc_project::
- Common::Error::InternalFailure>();
+ provMode =
+ secCtrl::RestrictionMode::Modes::ProvisionedHostDisabled;
+ // Fall through - Continue with ProvisionedHostDisabled value.
}
if (modeStr.empty())
{
@@ -65,8 +66,7 @@ void ProvModeMgr::updateProvModeProperty(
phosphor::logging::log<phosphor::logging::level::ERR>(
"RestrictionMode set-property failed",
phosphor::logging::entry("MSG=%s", ec.message().c_str()));
- phosphor::logging::elog<sdbusplus::xyz::openbmc_project::
- Common::Error::InternalFailure>();
+ // Continue, even for u-boot param update failure.
}
},
uBootEnvMgrService, uBootEnvMgrPath, uBootEnvMgrIntf,
@@ -101,9 +101,7 @@ void ProvModeMgr::init()
phosphor::logging::log<phosphor::logging::level::ERR>(
"RestrictionMode set-property failed",
phosphor::logging::entry("Mode=%s", req.c_str()),
- phosphor::logging::entry("EXCEPTION:%s", e.what()));
- phosphor::logging::elog<sdbusplus::xyz::openbmc_project::
- Common::Error::InternalFailure>();
+ phosphor::logging::entry("EXCEPTION=%s", e.what()));
}
return 0;
},
diff --git a/psu-manager/include/cold_redundancy.hpp b/psu-manager/include/cold_redundancy.hpp
index a8c53b5..8597615 100644
--- a/psu-manager/include/cold_redundancy.hpp
+++ b/psu-manager/include/cold_redundancy.hpp
@@ -49,6 +49,7 @@ class ColdRedundancy
uint8_t psOrder;
uint8_t numberOfPSU = 0;
uint32_t rotationPeriod = 7 * secondsInOneDay;
+ uint8_t redundancyPSURequire = 1;
void startRotateCR(void);
void startCRCheck(void);
@@ -59,6 +60,8 @@ class ColdRedundancy
void putWarmRedundant(void);
void keepAliveCheck(void);
+ void checkRedundancyEvent();
+
std::shared_ptr<sdbusplus::asio::connection>& systemBus;
boost::asio::steady_timer timerRotation;
@@ -67,6 +70,7 @@ class ColdRedundancy
boost::asio::steady_timer warmRedundantTimer2;
boost::asio::steady_timer keepAliveTimer;
boost::asio::steady_timer filterTimer;
+ boost::asio::steady_timer puRedundantTimer;
};
constexpr const uint8_t pmbusCmdCRSupport = 0xd0;
diff --git a/psu-manager/src/cold_redundancy.cpp b/psu-manager/src/cold_redundancy.cpp
index 14fabca..86b23b3 100644
--- a/psu-manager/src/cold_redundancy.cpp
+++ b/psu-manager/src/cold_redundancy.cpp
@@ -31,9 +31,10 @@
static constexpr const bool debug = false;
-static constexpr const std::array<const char*, 2> psuInterfaceTypes = {
+static constexpr const std::array<const char*, 3> psuInterfaceTypes = {
"xyz.openbmc_project.Configuration.pmbus",
- "xyz.openbmc_project.Configuration.PSUPresence"};
+ "xyz.openbmc_project.Configuration.PSUPresence",
+ "xyz.openbmc_project.Configuration.PURedundancy"};
static const constexpr char* inventoryPath =
"/xyz/openbmc_project/inventory/system";
static const constexpr char* eventPath = "/xyz/openbmc_project/State/Decorator";
@@ -52,7 +53,7 @@ ColdRedundancy::ColdRedundancy(
*systemBus, coldRedundancyPath),
timerRotation(io), timerCheck(io), systemBus(systemBus),
warmRedundantTimer1(io), warmRedundantTimer2(io), keepAliveTimer(io),
- filterTimer(io)
+ filterTimer(io), puRedundantTimer(io)
{
io.post([this, &io, &objectServer, &systemBus]() {
createPSU(io, objectServer, systemBus);
@@ -196,26 +197,26 @@ ColdRedundancy::ColdRedundancy(
for (auto& psu : powerSupplies)
{
-
if (psu->name != psuName)
{
continue;
}
- std::string psuEventName = "OperationalStatus";
+ std::string psuEventName = "functional";
auto findEvent = values.find(psuEventName);
if (findEvent != values.end())
{
if (std::get<bool>(findEvent->second))
{
- psu->state = PSUState::acLost;
+ psu->state = PSUState::normal;
}
else
{
- psu->state = PSUState::normal;
+ psu->state = PSUState::acLost;
}
}
}
+ checkRedundancyEvent();
};
for (const char* type : psuInterfaceTypes)
@@ -366,8 +367,8 @@ void ColdRedundancy::createPSU(
conn->async_method_call(
[this, &conn,
- &interface](const boost::system::error_code ec,
- PropertyMapType propMap) {
+ interface](const boost::system::error_code ec,
+ PropertyMapType propMap) {
if (ec)
{
std::cerr
@@ -388,8 +389,30 @@ void ColdRedundancy::createPSU(
"entry in configuration\n";
return;
}
+
if (interface == "xyz.openbmc_project."
- "Configuration.PSUPresence")
+ "Configuration.PURedundancy")
+ {
+ uint64_t* redunancyCount =
+ std::get_if<uint64_t>(
+ &propMap["RedundantCount"]);
+ if (redunancyCount != nullptr)
+ {
+ redundancyPSURequire =
+ static_cast<uint8_t>(
+ *redunancyCount);
+ }
+ else
+ {
+ std::cerr << "Failed to get Power Unit "
+ "Redundancy count, will "
+ "use default value\n";
+ }
+ return;
+ }
+ else if (interface ==
+ "xyz.openbmc_project."
+ "Configuration.PSUPresence")
{
auto psuBus =
std::get_if<uint64_t>(&propMap["Bus"]);
@@ -422,6 +445,16 @@ void ColdRedundancy::createPSU(
"entry in configuration\n";
return;
}
+ for (auto& psu : powerSupplies)
+ {
+ if ((static_cast<uint8_t>(*configBus) ==
+ psu->bus) &&
+ (static_cast<uint8_t>(*configAddress) ==
+ psu->address))
+ {
+ return;
+ }
+ }
powerSupplies.emplace_back(
std::make_unique<PowerSupply>(
@@ -437,6 +470,7 @@ void ColdRedundancy::createPSU(
}
}
}
+ checkRedundancyEvent();
},
"xyz.openbmc_project.ObjectMapper",
"/xyz/openbmc_project/object_mapper",
@@ -696,3 +730,117 @@ void ColdRedundancy::putWarmRedundant(void)
PowerSupply::~PowerSupply()
{
}
+
+void ColdRedundancy::checkRedundancyEvent()
+{
+ puRedundantTimer.expires_after(std::chrono::seconds(2));
+ puRedundantTimer.async_wait([this](const boost::system::error_code& ec) {
+ if (ec == boost::asio::error::operation_aborted)
+ {
+ return;
+ }
+
+ uint8_t psuWorkable = 0;
+ static uint8_t psuPreviousWorkable = numberOfPSU;
+
+ for (const auto& psu : powerSupplies)
+ {
+ if (psu->state == PSUState::normal)
+ {
+ psuWorkable++;
+ }
+ }
+
+ if (psuWorkable > psuPreviousWorkable)
+ {
+ if (psuWorkable > redundancyPSURequire)
+ {
+ if (psuWorkable == numberOfPSU)
+ {
+ // When all PSU are work correctly, it is full redundant
+ sd_journal_send(
+ "MESSAGE=%s", "Power Unit Full Redundancy Regained",
+ "PRIORITY=%i", LOG_INFO, "REDFISH_MESSAGE_ID=%s",
+ "OpenBMC.0.1.PowerUnitRedundancyRegained", NULL);
+ }
+ else if (psuPreviousWorkable <= redundancyPSURequire)
+ {
+ // Not all PSU can work correctly but system still in
+ // redundancy mode and previous status is non redundant
+ sd_journal_send(
+ "MESSAGE=%s",
+ "Power Unit Redundancy Regained but not in Full "
+ "Redundancy",
+ "PRIORITY=%i", LOG_INFO, "REDFISH_MESSAGE_ID=%s",
+ "OpenBMC.0.1.PowerUnitDegradedFromNonRedundant", NULL);
+ }
+ }
+ else if (psuPreviousWorkable == 0)
+ {
+ // Now system is not in redundancy mode but still some PSU are
+ // workable and previously there is no any workable PSU in the
+ // system
+ sd_journal_send(
+ "MESSAGE=%s",
+ "Power Unit Redundancy Sufficient from insufficient",
+ "PRIORITY=%i", LOG_INFO, "REDFISH_MESSAGE_ID=%s",
+ "OpenBMC.0.1.PowerUnitNonRedundantFromInsufficient", NULL);
+ }
+ }
+ else if (psuWorkable < psuPreviousWorkable)
+ {
+ if (psuWorkable > redundancyPSURequire)
+ {
+ // One PSU is now not workable, but other workable PSU can still
+ // support redundancy mode.
+ sd_journal_send(
+ "MESSAGE=%s", "Power Unit Redundancy Degraded",
+ "PRIORITY=%i", LOG_WARNING, "REDFISH_MESSAGE_ID=%s",
+ "OpenBMC.0.1.PowerUnitRedundancyDegraded", NULL);
+
+ if (psuPreviousWorkable == numberOfPSU)
+ {
+ // One PSU become not workable and system was in full
+ // redundancy mode.
+ sd_journal_send(
+ "MESSAGE=%s",
+ "Power Unit Redundancy Degraded from Full Redundant",
+ "PRIORITY=%i", LOG_WARNING, "REDFISH_MESSAGE_ID=%s",
+ "OpenBMC.0.1.PowerUnitDegradedFromRedundant", NULL);
+ }
+ }
+ else
+ {
+ if (psuPreviousWorkable > redundancyPSURequire)
+ {
+ // No enough workable PSU to support redundancy and
+ // previously system is in redundancy mode.
+ sd_journal_send(
+ "MESSAGE=%s", "Power Unit Redundancy Lost",
+ "PRIORITY=%i", LOG_ERR, "REDFISH_MESSAGE_ID=%s",
+ "OpenBMC.0.1.PowerUnitRedundancyLost", NULL);
+ if (psuWorkable > 0)
+ {
+ // There still some workable PSU, but system is not
+ // in redundancy mode.
+ sd_journal_send(
+ "MESSAGE=%s",
+ "Power Unit Redundancy NonRedundant Sufficient",
+ "PRIORITY=%i", LOG_WARNING, "REDFISH_MESSAGE_ID=%s",
+ "OpenBMC.0.1.PowerUnitNonRedundantSufficient",
+ NULL);
+ }
+ }
+ if (psuWorkable == 0)
+ {
+ // No any workable PSU on the system.
+ sd_journal_send(
+ "MESSAGE=%s", "Power Unit Redundancy Insufficient",
+ "PRIORITY=%i", LOG_ERR, "REDFISH_MESSAGE_ID=%s",
+ "OpenBMC.0.1.PowerUnitNonRedundantInsufficient", NULL);
+ }
+ }
+ }
+ psuPreviousWorkable = psuWorkable;
+ });
+}
diff --git a/services/smbios-mdrv2/src/cpu.cpp b/services/smbios-mdrv2/src/cpu.cpp
index a0e2840..cda96da 100644
--- a/services/smbios-mdrv2/src/cpu.cpp
+++ b/services/smbios-mdrv2/src/cpu.cpp
@@ -158,6 +158,11 @@ void Cpu::processorInfoUpdate(void)
uint8_t *dataIn = storage;
dataIn = getSMBIOSTypePtr(dataIn, processorsType);
+ if (dataIn == nullptr)
+ {
+ return;
+ }
+
for (uint8_t index = 0; index < cpuNum; index++)
{
dataIn = smbiosNextPtr(dataIn);
diff --git a/services/smbios/src/cpu.cpp b/services/smbios/src/cpu.cpp
index b6cd382..a92d9e6 100644
--- a/services/smbios/src/cpu.cpp
+++ b/services/smbios/src/cpu.cpp
@@ -159,6 +159,11 @@ void Cpu::processorInfoUpdate(void)
uint8_t *dataIn = regionS[0].regionData;
dataIn = smbiosTypePtr(dataIn, processorsType);
+ if (dataIn == nullptr)
+ {
+ return;
+ }
+
for (uint8_t index = 0; index < cpuNum; index++)
{
dataIn = smbiosNextPtr(dataIn);
diff --git a/services/smbios/src/dimm.cpp b/services/smbios/src/dimm.cpp
index 6ac31aa..c4c1d15 100644
--- a/services/smbios/src/dimm.cpp
+++ b/services/smbios/src/dimm.cpp
@@ -28,6 +28,11 @@ void Dimm::memoryInfoUpdate(void)
uint8_t *dataIn = regionS[0].regionData;
dataIn = smbiosTypePtr(dataIn, memoryDeviceType);
+ if (dataIn == nullptr)
+ {
+ return;
+ }
+
for (uint8_t index = 0; index < dimmNum; index++)
{
dataIn = smbiosNextPtr(dataIn);
diff --git a/settings/include/defaults.hpp b/settings/include/defaults.hpp
index d71ea42..387993f 100644
--- a/settings/include/defaults.hpp
+++ b/settings/include/defaults.hpp
@@ -287,13 +287,6 @@ inline void loadSettings(sdbusplus::asio::object_server &objectServer,
setting->addProperty("RetryIntervalMS", static_cast<uint8_t>(20));
setting = &settings.emplace_back(
- objectServer, "/xyz/openbmc_project/control/host0/restart_cause",
- "xyz.openbmc_project.Common.RestartCause");
-
- setting->addProperty("RestartCause",
- "xyz.openbmc_project.State.Host.RestartCause.Unknown");
-
- setting = &settings.emplace_back(
objectServer, "/xyz/openbmc_project/control/host0/ac_boot",
"xyz.openbmc_project.Common.ACBoot");
diff --git a/virtual-media/.clang-format b/virtual-media/.clang-format
new file mode 100644
index 0000000..ae9ad39
--- /dev/null
+++ b/virtual-media/.clang-format
@@ -0,0 +1,98 @@
+---
+Language: Cpp
+# BasedOnStyle: LLVM
+AccessModifierOffset: -2
+AlignAfterOpenBracket: Align
+AlignConsecutiveAssignments: false
+AlignConsecutiveDeclarations: false
+AlignEscapedNewlinesLeft: false
+AlignOperands: true
+AlignTrailingComments: true
+AllowAllParametersOfDeclarationOnNextLine: true
+AllowShortBlocksOnASingleLine: false
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: None
+AllowShortIfStatementsOnASingleLine: false
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakAfterDefinitionReturnType: None
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: false
+AlwaysBreakTemplateDeclarations: true
+BinPackArguments: true
+BinPackParameters: true
+BraceWrapping:
+ AfterClass: true
+ AfterControlStatement: true
+ AfterEnum: true
+ AfterFunction: true
+ AfterNamespace: true
+ AfterObjCDeclaration: true
+ AfterStruct: true
+ AfterUnion: true
+ BeforeCatch: true
+ BeforeElse: true
+ IndentBraces: false
+BreakBeforeBinaryOperators: None
+BreakBeforeBraces: Custom
+BreakBeforeTernaryOperators: true
+BreakConstructorInitializers: AfterColon
+ColumnLimit: 80
+CommentPragmas: '^ IWYU pragma:'
+ConstructorInitializerAllOnOneLineOrOnePerLine: false
+ConstructorInitializerIndentWidth: 4
+ContinuationIndentWidth: 4
+Cpp11BracedListStyle: true
+DerivePointerAlignment: false
+PointerAlignment: Left
+DisableFormat: false
+ExperimentalAutoDetectBinPacking: false
+FixNamespaceComments: true
+ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ]
+IncludeBlocks: Regroup
+IncludeCategories:
+ - Regex: '^[<"](gtest|gmock)'
+ Priority: 5
+ - Regex: '^"config.h"'
+ Priority: -1
+ - Regex: '^".*\.hpp"'
+ Priority: 1
+ - Regex: '^<.*\.h>'
+ Priority: 2
+ - Regex: '^<.*'
+ Priority: 3
+ - Regex: '.*'
+ Priority: 4
+IndentCaseLabels: true
+IndentWidth: 4
+IndentWrappedFunctionNames: true
+KeepEmptyLinesAtTheStartOfBlocks: true
+MacroBlockBegin: ''
+MacroBlockEnd: ''
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: None
+ObjCBlockIndentWidth: 2
+ObjCSpaceAfterProperty: false
+ObjCSpaceBeforeProtocolList: true
+PenaltyBreakBeforeFirstCallParameter: 19
+PenaltyBreakComment: 300
+PenaltyBreakFirstLessLess: 120
+PenaltyBreakString: 1000
+PenaltyExcessCharacter: 1000000
+PenaltyReturnTypeOnItsOwnLine: 60
+ReflowComments: true
+SortIncludes: true
+SortUsingDeclarations: true
+SpaceAfterCStyleCast: false
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeParens: ControlStatements
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles: false
+SpacesInContainerLiterals: true
+SpacesInCStyleCastParentheses: false
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+Standard: Cpp11
+TabWidth: 4
+UseTab: Never
+...
diff --git a/virtual-media/CMakeLists.txt b/virtual-media/CMakeLists.txt
new file mode 100644
index 0000000..60af112
--- /dev/null
+++ b/virtual-media/CMakeLists.txt
@@ -0,0 +1,144 @@
+cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
+
+project(VirtualMedia CXX)
+
+cmake_policy(SET CMP0054 NEW)
+
+set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake ${CMAKE_MODULE_PATH})
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -fno-rtti")
+set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -Os -flto")
+set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0")
+
+option(YOCTO_DEPENDENCIES "Use YOCTO dependencies system" OFF)
+
+option(VM_USE_VALGRIND "Build VirtualMedia to work with valgrind" OFF)
+
+if(NOT ${YOCTO_DEPENDENCIES})
+ include(ExternalProject)
+
+ externalproject_add(sdbusplus-project
+ PREFIX
+ ${CMAKE_BINARY_DIR}/sdbusplus-project
+ GIT_REPOSITORY
+ https://github.com/openbmc/sdbusplus.git
+ GIT_TAG
+ c08cf5283b80a071d19506d9a462f6c69e1797f1
+ SOURCE_DIR
+ ${CMAKE_BINARY_DIR}/sdbusplus-src
+ BINARY_DIR
+ ${CMAKE_BINARY_DIR}/sdbusplus-build
+ CONFIGURE_COMMAND
+ ""
+ BUILD_COMMAND
+ cd
+ ${CMAKE_BINARY_DIR}/sdbusplus-src
+ &&
+ ./bootstrap.sh
+ &&
+ ./configure
+ --enable-transaction
+ &&
+ make
+ -j
+ libsdbusplus.la
+ INSTALL_COMMAND
+ ""
+ LOG_DOWNLOAD
+ ON
+ UPDATE_COMMAND
+ "")
+
+ externalproject_add(nlohmann-json
+ GIT_REPOSITORY
+ "https://github.com/nlohmann/json.git"
+ GIT_TAG
+ aafad2be1f3cd259a1e79d2f6fcf267d1ede9ec7
+ SOURCE_DIR
+ "${CMAKE_BINARY_DIR}/nlohmann-json-src"
+ BINARY_DIR
+ "${CMAKE_BINARY_DIR}/nlohmann-json-build"
+ CONFIGURE_COMMAND
+ ""
+ BUILD_COMMAND
+ ""
+ INSTALL_COMMAND
+ mkdir
+ -p
+ "${CMAKE_BINARY_DIR}/prefix/include/nlohmann"
+ &&
+ cp
+ -r
+ "${CMAKE_BINARY_DIR}/nlohmann-json-src/include/nlohmann"
+ "${CMAKE_BINARY_DIR}/prefix/include"
+ UPDATE_COMMAND
+ "")
+
+ include_directories(${CMAKE_BINARY_DIR}/prefix/include)
+ include_directories(${CMAKE_BINARY_DIR}/sdbusplus-src)
+
+ link_directories(${CMAKE_BINARY_DIR}/sdbusplus-src/.libs)
+
+endif()
+
+# Include UDEV library
+find_package(udev REQUIRED)
+include_directories(${UDEV_INCLUDE_DIRS})
+link_directories(${UDEV_LIBRARIES})
+
+# Include Boost library This allows specify exact version of BOOST to be used,
+# especially important while using valgrind, to point BOOST that is compiled
+# with valgrind support
+if(${BOOST_VERSION})
+ find_package(Boost ${BOOST_VERSION} EXACT)
+else()
+ find_package(Boost 1.69 REQUIRED)
+endif()
+message("++ Using Boost version: " ${Boost_VERSION})
+
+include_directories(${Boost_INCLUDE_DIRS})
+link_directories(${Boost_LIBRARY_DIRS})
+
+# Boost related definitions
+add_definitions(-DBOOST_COROUTINES_NO_DEPRECATION_WARNING)
+add_definitions(-DBOOST_ERROR_CODE_HEADER_ONLY)
+add_definitions(-DBOOST_SYSTEM_NO_DEPRECATED)
+add_definitions(-DBOOST_ALL_NO_LIB)
+add_definitions(-DBOOST_NO_RTTI)
+add_definitions(-DBOOST_NO_TYPEID)
+add_definitions(-DBOOST_ASIO_DISABLE_THREADS)
+
+# Define source files
+set(SRC_FILES src/main.cpp)
+
+# Executables
+add_executable(virtual-media ${SRC_FILES} ${HEADER_FILES})
+if(NOT ${YOCTO_DEPENDENCIES})
+ add_dependencies(virtual-media nlohmann-json sdbusplus-project)
+endif()
+
+# Default linkage
+target_link_libraries(virtual-media systemd)
+target_link_libraries(virtual-media -lsdbusplus)
+target_link_libraries(virtual-media -ludev)
+target_link_libraries(virtual-media -lboost_coroutine)
+target_link_libraries(virtual-media -lboost_context)
+install(TARGETS virtual-media DESTINATION sbin)
+
+# Options based compile definitions
+target_compile_definitions(virtual-media
+ PRIVATE
+ $<$<BOOL:${VM_USE_VALGRIND}>:
+ -DBOOST_USE_VALGRIND>
+ $<$<BOOL:${CUSTOM_DBUS_PATH}>:
+ -DCUSTOM_DBUS_PATH="${CUSTOM_DBUS_PATH}">)
+
+if(CMAKE_INSTALL_SYSCONFDIR)
+ install(FILES ${PROJECT_SOURCE_DIR}/virtual-media.json DESTINATION
+ ${CMAKE_INSTALL_SYSCONFDIR})
+endif()
+install(FILES ${PROJECT_SOURCE_DIR}/xyz.openbmc_project.VirtualMedia.service
+ DESTINATION /lib/systemd/system/)
diff --git a/virtual-media/LICENSE b/virtual-media/LICENSE
new file mode 100644
index 0000000..8dada3e
--- /dev/null
+++ b/virtual-media/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/virtual-media/cmake/Findudev.cmake b/virtual-media/cmake/Findudev.cmake
new file mode 100644
index 0000000..ce454d5
--- /dev/null
+++ b/virtual-media/cmake/Findudev.cmake
@@ -0,0 +1,77 @@
+# - try to find the udev library
+#
+# Cache Variables: (probably not for direct use in your scripts)
+# UDEV_INCLUDE_DIR
+# UDEV_SOURCE_DIR
+# UDEV_LIBRARY
+#
+# Non-cache variables you might use in your CMakeLists.txt:
+# UDEV_FOUND
+# UDEV_INCLUDE_DIRS
+# UDEV_LIBRARIES
+#
+# Requires these CMake modules:
+# FindPackageHandleStandardArgs (known included with CMake >=2.6.2)
+#
+# Original Author:
+# 2014 Kevin M. Godby <kevin@godby.org>
+#
+# Distributed under the Boost Software License, Version 1.0.
+# (See accompanying file LICENSE_1_0.txt or copy at
+# http://www.boost.org/LICENSE_1_0.txt)
+
+set(UDEV_ROOT_DIR
+ "${UDEV_ROOT_DIR}"
+ CACHE
+ PATH
+ "Directory to search for udev")
+
+find_package(PkgConfig QUIET)
+if(PKG_CONFIG_FOUND)
+ pkg_check_modules(PC_LIBUDEV libudev)
+endif()
+
+find_library(UDEV_LIBRARY
+ NAMES
+ udev
+ PATHS
+ ${PC_LIBUDEV_LIBRARY_DIRS}
+ ${PC_LIBUDEV_LIBDIR}
+ HINTS
+ "${UDEV_ROOT_DIR}"
+ PATH_SUFFIXES
+ lib
+ )
+
+get_filename_component(_libdir "${UDEV_LIBRARY}" PATH)
+
+find_path(UDEV_INCLUDE_DIR
+ NAMES
+ libudev.h
+ PATHS
+ ${PC_LIBUDEV_INCLUDE_DIRS}
+ ${PC_LIBUDEV_INCLUDEDIR}
+ HINTS
+ "${_libdir}"
+ "${_libdir}/.."
+ "${UDEV_ROOT_DIR}"
+ PATH_SUFFIXES
+ include
+ )
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(UDEV
+ DEFAULT_MSG
+ UDEV_LIBRARY
+ UDEV_INCLUDE_DIR
+ )
+
+if(UDEV_FOUND)
+ list(APPEND UDEV_LIBRARIES ${UDEV_LIBRARY})
+ list(APPEND UDEV_INCLUDE_DIRS ${UDEV_INCLUDE_DIR})
+ mark_as_advanced(UDEV_ROOT_DIR)
+endif()
+
+mark_as_advanced(UDEV_INCLUDE_DIR
+ UDEV_LIBRARY)
+
diff --git a/virtual-media/src/main.cpp b/virtual-media/src/main.cpp
new file mode 100644
index 0000000..6d5ffb6
--- /dev/null
+++ b/virtual-media/src/main.cpp
@@ -0,0 +1,1087 @@
+#include <sys/mount.h>
+
+#include <boost/asio.hpp>
+#include <boost/asio/buffer.hpp>
+#include <boost/asio/posix/stream_descriptor.hpp>
+#include <boost/asio/spawn.hpp>
+#include <boost/container/flat_map.hpp>
+#include <boost/container/flat_set.hpp>
+#include <boost/process.hpp>
+#include <filesystem>
+#include <iostream>
+#include <memory>
+#include <nlohmann/json.hpp>
+#include <sdbusplus/asio/connection.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+
+namespace fs = std::filesystem;
+
+namespace udev
+{
+#include <libudev.h>
+struct udev;
+struct udev_monitor;
+struct udev_device;
+
+struct udevDeleter
+{
+ void operator()(udev* desc) const
+ {
+ udev_unref(desc);
+ }
+};
+
+struct monitorDeleter
+{
+ void operator()(udev_monitor* desc) const
+ {
+ udev_monitor_unref(desc);
+ };
+};
+
+struct deviceDeleter
+{
+ void operator()(udev_device* desc) const
+ {
+ udev_device_unref(desc);
+ };
+};
+} // namespace udev
+
+enum class StateChange
+{
+ notmonitored,
+ unknown,
+ removed,
+ inserted
+};
+
+class DeviceMonitor
+{
+ public:
+ DeviceMonitor(boost::asio::io_context& ioc) : ioc(ioc), monitorSd(ioc)
+ {
+ udev = std::unique_ptr<udev::udev, udev::udevDeleter>(udev::udev_new());
+ if (!udev)
+ {
+ throw std::system_error(EFAULT, std::generic_category(),
+ "Unable to create uDev handler.");
+ }
+
+ monitor = std::unique_ptr<udev::udev_monitor, udev::monitorDeleter>(
+ udev::udev_monitor_new_from_netlink(udev.get(), "kernel"));
+
+ if (!monitor)
+ {
+ throw std::system_error(EFAULT, std::generic_category(),
+ "Unable to create uDev Monitor handler.");
+ }
+ int rc = udev_monitor_filter_add_match_subsystem_devtype(
+ monitor.get(), "block", "disk");
+
+ if (rc)
+ {
+ throw std::system_error(EFAULT, std::generic_category(),
+ "Could not apply filters.");
+ }
+ rc = udev_monitor_enable_receiving(monitor.get());
+ if (rc)
+ {
+ throw std::system_error(EFAULT, std::generic_category(),
+ "Enable receiving failed.");
+ }
+ monitorSd.assign(udev_monitor_get_fd(monitor.get()));
+ }
+
+ DeviceMonitor(const DeviceMonitor&) = delete;
+ DeviceMonitor(DeviceMonitor&&) = delete;
+
+ DeviceMonitor& operator=(const DeviceMonitor&) = delete;
+ DeviceMonitor& operator=(DeviceMonitor&&) = delete;
+
+ template <typename DeviceChangeStateCb>
+ void run(DeviceChangeStateCb callback)
+ {
+ boost::asio::spawn(ioc, [this,
+ callback](boost::asio::yield_context yield) {
+ boost::system::error_code ec;
+ while (1)
+ {
+ monitorSd.async_wait(
+ boost::asio::posix::stream_descriptor::wait_read,
+ yield[ec]);
+
+ std::unique_ptr<udev::udev_device, udev::deviceDeleter> device =
+ std::unique_ptr<udev::udev_device, udev::deviceDeleter>(
+ udev::udev_monitor_receive_device(monitor.get()));
+ if (device)
+ {
+ const char* devAction =
+ udev_device_get_action(device.get());
+ if (devAction == nullptr)
+ {
+ std::cerr << "Received NULL action.\n";
+ continue;
+ }
+ if (strcmp(devAction, "change") == 0)
+ {
+ const char* sysname =
+ udev_device_get_sysname(device.get());
+ if (sysname == nullptr)
+ {
+ std::cerr << "Received NULL sysname.\n";
+ continue;
+ }
+ auto monitoredDevice = devices.find(sysname);
+ if (monitoredDevice != devices.cend())
+ {
+ const char* size_str =
+ udev_device_get_sysattr_value(device.get(),
+ "size");
+ if (size_str == nullptr)
+ {
+ std::cerr << "Received NULL size.\n";
+ continue;
+ }
+ uint64_t size = 0;
+ try
+ {
+ size = std::stoul(size_str, 0, 0);
+ }
+ catch (const std::exception& e)
+ {
+ std::cerr
+ << "Could not convert size to integer.\n";
+ continue;
+ }
+ if (size > 0 && monitoredDevice->second !=
+ StateChange::inserted)
+ {
+ std::cout << sysname << " inserted.\n";
+ monitoredDevice->second = StateChange::inserted;
+ callback(sysname, StateChange::inserted);
+ }
+ else if (size == 0 && monitoredDevice->second !=
+ StateChange::removed)
+ {
+ std::cout << sysname << " removed.\n";
+ monitoredDevice->second = StateChange::removed;
+ callback(sysname, StateChange::removed);
+ }
+ };
+ }
+ }
+ }
+ });
+ }
+
+ void addDevice(const std::string& device)
+ {
+ std::cout << "Watch on /dev/" << device << '\n';
+ devices.insert(
+ std::pair<std::string, StateChange>(device, StateChange::unknown));
+ }
+
+ StateChange getState(const std::string& device)
+ {
+ auto monitoredDevice = devices.find(device);
+ if (monitoredDevice != devices.cend())
+ {
+ return monitoredDevice->second;
+ }
+ return StateChange::notmonitored;
+ }
+
+ private:
+ boost::asio::io_context& ioc;
+ boost::asio::posix::stream_descriptor monitorSd;
+
+ std::unique_ptr<udev::udev, udev::udevDeleter> udev;
+ std::unique_ptr<udev::udev_monitor, udev::monitorDeleter> monitor;
+
+ boost::container::flat_map<std::string, StateChange> devices;
+};
+
+class Process : public std::enable_shared_from_this<Process>
+{
+ public:
+ Process(boost::asio::io_context& ioc, const std::string& name) :
+ ioc(ioc), pipe(ioc), name(name)
+ {
+ }
+
+ template <typename ExitCb>
+ bool spawn(const std::vector<std::string>& args, ExitCb&& onExit)
+ {
+ std::error_code ec;
+ child = boost::process::child(
+ "/usr/sbin/nbd-client",
+ //"/sbin/nbd-client", "-N", "otherexport", "127.0.0.1", "/dev/nbd0",
+ //"-n",
+ boost::process::args(args),
+ boost::process::on_exit(
+ [name(this->name), onExit{std::move(onExit)}](
+ int exit, const std::error_code& ecIn) {
+ std::cout << "- child " << name << "(" << exit << " "
+ << ecIn << ")\n";
+ auto process = processList.find(name);
+ if (process != processList.end() && process->second)
+ {
+ std::cout << "Removing process from processList\n";
+ process->second.reset();
+ }
+ onExit(name);
+ }),
+ (boost::process::std_out & boost::process::std_err) > pipe, ec,
+ ioc);
+
+ if (ec)
+ {
+ std::cerr << "Error while creating child process: " << ec << '\n';
+ return false;
+ }
+
+ boost::asio::spawn(ioc, [this, self = shared_from_this()](
+ boost::asio::yield_context yield) {
+ boost::system::error_code bec;
+ std::string line;
+ boost::asio::dynamic_string_buffer buffer{line};
+ std::cout << "Entering COUT Loop\n";
+ while (1)
+ {
+ auto x = boost::asio::async_read_until(pipe, std::move(buffer),
+ '\n', yield[bec]);
+ std::cout << "-[" << name << "]-> " << line;
+ buffer.consume(x);
+ if (bec)
+ {
+ std::cerr << "Loop Error: " << bec << '\n';
+ break;
+ }
+ }
+ std::cout << "Exiting from COUT Loop\n";
+ });
+ return true;
+ }
+
+ void terminate(int signal = SIGTERM)
+ {
+ int rc = kill(child.id(), SIGTERM);
+ if (rc)
+ {
+ return;
+ }
+ child.wait();
+ }
+
+ static void addProcess(const std::string& name)
+ {
+ processList[name] = nullptr;
+ }
+
+ template <typename ExitCb>
+ static bool spawn(boost::asio::io_context& ioc, const std::string& name,
+ const std::vector<std::string>& args, ExitCb&& onExit)
+ {
+ auto process = processList.find(name);
+ if (process != processList.end() && !process->second)
+ {
+ std::cout << "Spawning process for " << name << '\n';
+
+ auto newProcess = std::make_shared<Process>(ioc, name);
+ if (newProcess->spawn(args, onExit))
+ {
+ process->second = newProcess;
+ return true;
+ }
+ std::cout << "Process (" << name << ") exited immediately.\n";
+ return false;
+ }
+ std::cout << "No process (" << name << ") to be spawned.\n";
+ return false;
+ }
+
+ static bool terminate(const std::string& name, int signal = SIGTERM)
+ {
+ auto process = processList.find(name);
+ if (process != processList.end() && process->second)
+ {
+ std::cout << "Terminating " << name << '\n';
+ process->second->terminate(signal);
+ return true;
+ }
+ std::cout << "No process (" << name << ") for termination.\n";
+ return false;
+ }
+
+ static bool isActive(const std::string& name)
+ {
+ auto process = processList.find(name);
+ if (process != processList.end())
+ {
+ if (process->second)
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private:
+ boost::asio::io_context& ioc;
+ boost::process::child child;
+ boost::process::async_pipe pipe;
+ std::string name;
+ inline static boost::container::flat_map<std::string,
+ std::shared_ptr<Process>>
+ processList;
+};
+
+class Configuration
+{
+ public:
+ enum class Mode
+ {
+ // Proxy mode - works directly from browser and uses JavaScript/HTML5
+ // to communicate over Secure WebSockets directly to HTTPS endpoint
+ // hosted by bmcweb on BMC
+ Proxy = 0,
+
+ // Legacy mode - is initiated from browser using Redfish defined
+ // VirtualMedia schemas, then BMC process connects to external
+ // CIFS/HTTPS image pointed during initialization.
+ Legacy = 1,
+ };
+
+ struct MountPoint
+ {
+ std::string nbdDevice;
+ std::string unixSocket;
+ std::string endPointId;
+ std::optional<int> timeout;
+ std::optional<int> blocksize;
+ Mode mode;
+
+ static std::vector<std::string> toArgs(const MountPoint& mp)
+ {
+ std::vector<std::string> args = {
+ "-N", "otherexport", "-u",
+ mp.unixSocket, "/dev/" + mp.nbdDevice, "-n"};
+ return args;
+ }
+ };
+
+ bool valid = false;
+ boost::container::flat_map<std::string, MountPoint> mountPoints;
+
+ Configuration(const std::string& file)
+ {
+ valid = loadConfiguration(file);
+ }
+
+ private:
+ bool loadConfiguration(const std::string& file) noexcept
+ {
+ std::ifstream configFile(file);
+ if (!configFile.is_open())
+ {
+ std::cout << "Could not open configuration file\n";
+ return false;
+ }
+ try
+ {
+ auto data = nlohmann::json::parse(configFile, nullptr);
+ setupVariables(data);
+ }
+ catch (nlohmann::json::exception& e)
+ {
+ std::cerr << "Error parsing JSON file\n";
+ return false;
+ }
+ catch (std::out_of_range& e)
+ {
+ std::cerr << "Error parsing JSON file\n";
+ return false;
+ }
+ return true;
+ }
+
+ bool setupVariables(const nlohmann::json& config)
+ {
+ for (const auto& item : config.items())
+ {
+ if (item.key() == "MountPoints")
+ {
+ for (const auto& mountpoint : item.value().items())
+ {
+ MountPoint mp;
+ const auto nbdDeviceIter =
+ mountpoint.value().find("NBDDevice");
+ if (nbdDeviceIter != mountpoint.value().cend())
+ {
+ const std::string* value =
+ nbdDeviceIter->get_ptr<const std::string*>();
+ if (value)
+ {
+ mp.nbdDevice = *value;
+ }
+ else
+ {
+ std::cerr << "NBDDevice required, not set\n";
+ continue;
+ }
+ };
+ const auto unixSocketIter =
+ mountpoint.value().find("UnixSocket");
+ if (unixSocketIter != mountpoint.value().cend())
+ {
+ const std::string* value =
+ unixSocketIter->get_ptr<const std::string*>();
+ if (value)
+ {
+ mp.unixSocket = *value;
+ }
+ else
+ {
+ std::cerr << "UnixSocket required, not set\n";
+ continue;
+ }
+ }
+ const auto endPointIdIter =
+ mountpoint.value().find("EndpointId");
+ if (endPointIdIter != mountpoint.value().cend())
+ {
+ const std::string* value =
+ endPointIdIter->get_ptr<const std::string*>();
+ if (value)
+ {
+ mp.endPointId = *value;
+ }
+ else
+ {
+ std::cerr << "EndpointId required, not set\n";
+ continue;
+ }
+ }
+ const auto timeoutIter = mountpoint.value().find("Timeout");
+ if (timeoutIter != mountpoint.value().cend())
+ {
+ const uint64_t* value =
+ timeoutIter->get_ptr<const uint64_t*>();
+ if (value)
+ {
+ mp.timeout = *value;
+ }
+ else
+ {
+ std::cout << "Timeout not set, use default\n";
+ }
+ }
+ const auto blocksizeIter =
+ mountpoint.value().find("BlockSize");
+ if (blocksizeIter != mountpoint.value().cend())
+ {
+ const uint64_t* value =
+ blocksizeIter->get_ptr<const uint64_t*>();
+ if (value)
+ {
+ mp.blocksize = *value;
+ }
+ else
+ {
+ std::cout << "BlockSize not set, use default\n";
+ }
+ }
+ const auto modeIter = mountpoint.value().find("Mode");
+ if (modeIter != mountpoint.value().cend())
+ {
+ const uint64_t* value =
+ modeIter->get_ptr<const uint64_t*>();
+ if (value)
+ {
+ if (*value == 0)
+ {
+ mp.mode = Configuration::Mode::Proxy;
+ }
+ else if (*value == 1)
+ {
+ mp.mode = Configuration::Mode::Legacy;
+ }
+ else
+ {
+ std::cerr << "Incorrect Mode, skip this mount "
+ "point\n";
+ continue;
+ }
+ }
+ else
+ {
+ std::cerr
+ << "Mode not set, skip this mount point\n";
+ continue;
+ }
+ }
+ else
+ {
+ std::cerr
+ << "Mode does not exist, skip this mount point\n";
+ continue;
+ }
+ mountPoints[mountpoint.key()] = std::move(mp);
+ }
+ }
+ }
+ return true;
+ }
+};
+
+class ParametersManager
+{
+ public:
+ struct Parameters
+ {
+ // Legacy mode specific parameters
+ std::string imageUrl;
+ std::string mountDirectoryPath;
+ };
+
+ ParametersManager()
+ {
+ }
+
+ void addMountPoint(const std::string& name)
+ {
+ Parameters parameters;
+ parameters.imageUrl.clear();
+ parameters.mountDirectoryPath.clear();
+ mountPoints[name] = std::move(parameters);
+ }
+
+ Parameters* getMountPoint(const std::string& name)
+ {
+ if (mountPoints.find(name) != mountPoints.end())
+ {
+ return &mountPoints[name];
+ }
+ else
+ {
+ return nullptr;
+ }
+ }
+
+ private:
+ boost::container::flat_map<std::string, Parameters> mountPoints;
+};
+
+class App
+{
+ public:
+ App(boost::asio::io_context& ioc, const Configuration& config,
+ sd_bus* custom_bus = nullptr) :
+ ioc(ioc),
+ devMonitor(ioc), config(config), paramMgr()
+ {
+ if (!custom_bus)
+ {
+ bus = std::make_shared<sdbusplus::asio::connection>(ioc);
+ }
+ else
+ {
+ bus =
+ std::make_shared<sdbusplus::asio::connection>(ioc, custom_bus);
+ }
+ objServer = std::make_shared<sdbusplus::asio::object_server>(bus);
+ bus->request_name("xyz.openbmc_project.VirtualMedia");
+ objManager = std::make_shared<sdbusplus::server::manager::manager>(
+ *bus, "/xyz/openbmc_project/VirtualMedia");
+ for (const auto& entry : config.mountPoints)
+ {
+ if (entry.second.mode == Configuration::Mode::Proxy)
+ {
+ devMonitor.addDevice(entry.second.nbdDevice);
+ Process::addProcess(entry.first);
+ }
+
+ addMountPointInterface(entry.first, entry.second);
+
+ if (entry.second.mode == Configuration::Mode::Proxy)
+ {
+ addProxyInterface(entry.first, entry.second);
+ addProcessInterface(entry.first);
+ }
+ else
+ {
+ addLegacyInterface(entry.first);
+ }
+
+ paramMgr.addMountPoint(entry.first);
+ }
+ devMonitor.run([this](const std::string& device, StateChange change) {
+ configureUsbGadget(device, change);
+ });
+ };
+
+ private:
+ bool echoToFile(const fs::path& fname, const std::string& content)
+ {
+ std::ofstream fileWriter;
+ fileWriter.exceptions(std::ofstream::failbit | std::ofstream::badbit);
+ fileWriter.open(fname, std::ios::out | std::ios::app);
+ fileWriter << content << std::endl; // Make sure for new line and flush
+ fileWriter.close();
+ return true;
+ }
+
+ int configureUsbGadget(const std::string& device, StateChange change,
+ const std::string& lunFile = "")
+ {
+ int result = 0;
+ std::error_code ec;
+ if (change == StateChange::unknown)
+ {
+ std::cerr << "Change to unknown state is not possible\n";
+ return -1;
+ }
+
+ // If lun file provided just use it to inject to the lun.0/file
+ // instead of /dev/<device>
+ std::string lunFileInternal;
+ if (lunFile.empty())
+ {
+ lunFileInternal = "/dev/" + device;
+ }
+ else
+ {
+ lunFileInternal = lunFile;
+ }
+
+ const fs::path gadgetDir =
+ "/sys/kernel/config/usb_gadget/mass-storage-" + device;
+ const fs::path funcMassStorageDir =
+ gadgetDir / "functions/mass_storage.usb0";
+ const fs::path stringsDir = gadgetDir / "strings/0x409";
+ const fs::path configDir = gadgetDir / "configs/c.1";
+ const fs::path massStorageDir = configDir / "mass_storage.usb0";
+ const fs::path configStringsDir = configDir / "strings/0x409";
+
+ if (change == StateChange::inserted)
+ {
+ try
+ {
+ fs::create_directories(gadgetDir);
+ echoToFile(gadgetDir / "idVendor", "0x1d6b");
+ echoToFile(gadgetDir / "idProduct", "0x0104");
+ fs::create_directories(stringsDir);
+ echoToFile(stringsDir / "manufacturer", "OpenBMC");
+ echoToFile(stringsDir / "product", "Virtual Media Device");
+ fs::create_directories(configStringsDir);
+ echoToFile(configStringsDir / "configuration", "config 1");
+ fs::create_directories(funcMassStorageDir);
+ fs::create_directories(funcMassStorageDir / "lun.0");
+ fs::create_directory_symlink(funcMassStorageDir,
+ massStorageDir);
+ echoToFile(funcMassStorageDir / "lun.0/removable", "1");
+ echoToFile(funcMassStorageDir / "lun.0/ro", "1");
+ echoToFile(funcMassStorageDir / "lun.0/cdrom", "0");
+ echoToFile(funcMassStorageDir / "lun.0/file", lunFileInternal);
+
+ for (const auto& port : fs::directory_iterator(
+ "/sys/bus/platform/devices/1e6a0000.usb-vhub"))
+ {
+ if (fs::is_directory(port) && !fs::is_symlink(port) &&
+ !fs::exists(port.path() / "gadget/suspended"))
+ {
+ const std::string portId = port.path().filename();
+ std::cout << "Use port : " << port.path().filename()
+ << '\n';
+ echoToFile(gadgetDir / "UDC", portId);
+ return 0;
+ }
+ }
+ }
+ catch (fs::filesystem_error& e)
+ {
+ // Got error perform cleanup
+ std::cerr << e.what() << '\n';
+ result = -1;
+ }
+ catch (std::ofstream::failure& e)
+ {
+ // Got error perform cleanup
+ std::cerr << e.what() << '\n';
+ result = -1;
+ }
+ }
+ // StateChange: unknown, notmonitored, inserted were handler
+ // earlier. We'll get here only for removed, or cleanup
+
+ fs::remove_all(massStorageDir, ec);
+ if (ec)
+ {
+ std::cerr << ec.message() << '\n';
+ }
+ fs::remove_all(funcMassStorageDir, ec);
+ if (ec)
+ {
+ std::cerr << ec.message() << '\n';
+ }
+ fs::remove_all(configStringsDir, ec);
+ if (ec)
+ {
+ std::cerr << ec.message() << '\n';
+ }
+ fs::remove_all(configDir, ec);
+ if (ec)
+ {
+ std::cerr << ec.message() << '\n';
+ }
+ fs::remove_all(stringsDir, ec);
+ if (ec)
+ {
+ std::cerr << ec.message() << '\n';
+ }
+ fs::remove_all(gadgetDir, ec);
+ if (ec)
+ {
+ std::cerr << ec.message() << '\n';
+ result = -1;
+ }
+
+ return result;
+ }
+
+ void addMountPointInterface(const std::string& name,
+ const Configuration::MountPoint& mp)
+ {
+ std::string objPath;
+ if (mp.mode == Configuration::Mode::Proxy)
+ {
+ objPath = "/xyz/openbmc_project/VirtualMedia/Proxy/";
+ }
+ else
+ {
+ objPath = "/xyz/openbmc_project/VirtualMedia/Legacy/";
+ }
+
+ auto iface = objServer->add_interface(
+ objPath + name, "xyz.openbmc_project.VirtualMedia.MountPoint");
+ iface->register_property("Device", mp.nbdDevice);
+ iface->register_property("EndpointId", mp.endPointId);
+ iface->register_property("Socket", mp.unixSocket);
+ iface->register_property("Timeout", *mp.timeout);
+ iface->register_property("BlockSize", *mp.blocksize);
+ iface->register_property(
+ "ImageUrl", std::string(""),
+ [](const std::string& req, std::string& property) {
+ throw sdbusplus::exception::SdBusError(
+ EPERM, "Setting ImageUrl property is not allowed");
+ return -1;
+ },
+ [this, name](const std::string& property) {
+ auto parameters = paramMgr.getMountPoint(name);
+ if (parameters == nullptr)
+ {
+ return std::string("");
+ }
+ else
+ {
+ return parameters->imageUrl;
+ }
+ });
+ iface->initialize();
+ };
+
+ void addProxyInterface(const std::string& name,
+ const Configuration::MountPoint& mp)
+ {
+ auto iface = objServer->add_interface(
+ "/xyz/openbmc_project/VirtualMedia/Proxy/" + name,
+ "xyz.openbmc_project.VirtualMedia.Proxy");
+ iface->register_method(
+ "Mount", [&, name, args(Configuration::MountPoint::toArgs(mp))]() {
+ return Process::spawn(
+ ioc, name, args, [this](const std::string& name) {
+ configureUsbGadget(name, StateChange::removed);
+ });
+ });
+ iface->register_method("Unmount", [this, name]() {
+ const auto mp = config.mountPoints.find(name);
+ std::string nbdDevice = mp->second.nbdDevice;
+ configureUsbGadget(nbdDevice, StateChange::removed);
+ return Process::terminate(name);
+ });
+ iface->initialize();
+ }
+
+ int prepareTempDirForLegacyMode(std::string& path)
+ {
+ int result = -1;
+ char mountPathTemplate[] = "/tmp/vm_legacy.XXXXXX";
+ const char* tmpPath = mkdtemp(mountPathTemplate);
+ if (tmpPath != nullptr)
+ {
+ path = tmpPath;
+ result = 0;
+ }
+
+ return result;
+ }
+
+ bool checkUrl(const std::string& urlScheme, const std::string& imageUrl)
+ {
+ if (urlScheme.compare(imageUrl.substr(0, urlScheme.size())) == 0)
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ bool getImagePathFromUrl(const std::string& urlScheme,
+ const std::string& imageUrl,
+ std::string* imagePath)
+ {
+ if (checkUrl(urlScheme, imageUrl))
+ {
+ if (imagePath != nullptr)
+ {
+ *imagePath = imageUrl.substr(urlScheme.size() - 1);
+ return true;
+ }
+ else
+ {
+ std::cerr << "Invalid parameter provied\n";
+ return false;
+ }
+ }
+ else
+ {
+ std::cerr << "Provied url does not match scheme\n";
+ return false;
+ }
+ }
+
+ bool checkHttpsUrl(const std::string& imageUrl)
+ {
+ return checkUrl("https://", imageUrl);
+ }
+
+ bool getImagePathFromHttpsUrl(const std::string& imageUrl,
+ std::string* imagePath)
+ {
+ return getImagePathFromUrl("https://", imageUrl, imagePath);
+ }
+
+ bool checkCifsUrl(const std::string& imageUrl)
+ {
+ return checkUrl("smb://", imageUrl);
+ }
+
+ bool getImagePathFromCifsUrl(const std::string& imageUrl,
+ std::string* imagePath)
+ {
+ return getImagePathFromUrl("smb://", imageUrl, imagePath);
+ }
+
+ fs::path getImagePath(const std::string& imageUrl)
+ {
+ std::string imagePath;
+
+ if (getImagePathFromHttpsUrl(imageUrl, &imagePath))
+ {
+ return fs::path(imagePath);
+ }
+ else if (getImagePathFromCifsUrl(imageUrl, &imagePath))
+ {
+ return fs::path(imagePath);
+ }
+ else
+ {
+ std::cerr << "Unrecognized url's scheme encountered\n";
+ return fs::path("");
+ }
+ }
+
+ int mountCifsUrlForLegcyMode(const std::string& name,
+ const std::string& imageUrl,
+ ParametersManager::Parameters* parameters)
+ {
+ int result = -1;
+ fs::path imageUrlPath = getImagePath(imageUrl);
+ const std::string imageUrlParentPath =
+ "/" + imageUrlPath.parent_path().string();
+ std::string mountDirectoryPath;
+ result = prepareTempDirForLegacyMode(mountDirectoryPath);
+ if (result != 0)
+ {
+ std::cerr << "Failed to create tmp directory\n";
+ return result;
+ }
+
+ result = mount(imageUrlParentPath.c_str(), mountDirectoryPath.c_str(),
+ "cifs", 0,
+ "username=,password=,nolock,sec="
+ "ntlmsspi,seal,vers=3.0");
+ if (result != 0)
+ {
+ std::cerr << "Failed to mount the url\n";
+ fs::remove_all(fs::path(mountDirectoryPath));
+ return result;
+ }
+
+ const std::string imageMountPath =
+ mountDirectoryPath + "/" + imageUrlPath.filename().string();
+ result =
+ configureUsbGadget(name, StateChange::inserted, imageMountPath);
+ if (result != 0)
+ {
+ std::cerr << "Failed to run usb gadget\n";
+ umount(mountDirectoryPath.c_str());
+ fs::remove_all(fs::path(mountDirectoryPath));
+ return result;
+ }
+
+ parameters->mountDirectoryPath = mountDirectoryPath;
+
+ return result;
+ }
+
+ int umountCifsUrlForLegcyMode(const std::string& name,
+ ParametersManager::Parameters* parameters)
+ {
+ int result = -1;
+
+ result = configureUsbGadget(name, StateChange::removed);
+ if (result != 0)
+ {
+ std::cerr << "Failed to unmount resource\n";
+ return result;
+ }
+
+ umount(parameters->mountDirectoryPath.c_str());
+ fs::remove_all(fs::path(parameters->mountDirectoryPath));
+ parameters->mountDirectoryPath.clear();
+
+ return result;
+ }
+
+ void addLegacyInterface(const std::string& name)
+ {
+ auto iface = objServer->add_interface(
+ "/xyz/openbmc_project/VirtualMedia/Legacy/" + name,
+ "xyz.openbmc_project.VirtualMedia.Legacy");
+ iface->register_method(
+ "Mount", [this, name](const std::string& imageUrl) {
+ auto parameters = paramMgr.getMountPoint(name);
+ if (parameters == nullptr)
+ {
+ throw sdbusplus::exception::SdBusError(
+ ENOENT, "Failed to find the node.");
+ }
+
+ if (!parameters->imageUrl.empty())
+ {
+ throw sdbusplus::exception::SdBusError(
+ ETXTBSY, "Node already used and resource mounted.");
+ }
+
+ if (checkCifsUrl(imageUrl))
+ {
+ int result =
+ mountCifsUrlForLegcyMode(name, imageUrl, parameters);
+ if (result != 0)
+ {
+ throw sdbusplus::exception::SdBusError(
+ -result, "Failed to mount cifs url.");
+ }
+ }
+ else
+ {
+ throw sdbusplus::exception::SdBusError(
+ EINVAL, "Not supported url's scheme.");
+ }
+
+ parameters->imageUrl = imageUrl;
+ });
+ iface->register_method("Unmount", [this, name]() {
+ auto parameters = paramMgr.getMountPoint(name);
+ if (parameters == nullptr)
+ {
+ throw sdbusplus::exception::SdBusError(
+ ENOENT, "Failed to find the node.");
+ }
+
+ if (parameters->imageUrl.empty())
+ {
+ throw sdbusplus::exception::SdBusError(
+ ENOENT, "Node is not used and no resource mounted.");
+ }
+
+ if (checkCifsUrl(parameters->imageUrl))
+ {
+ int result = umountCifsUrlForLegcyMode(name, parameters);
+ if (result != 0)
+ {
+ throw sdbusplus::exception::SdBusError(
+ -result, "Failed to unmount cifs resource.");
+ }
+ }
+ else
+ {
+ throw sdbusplus::exception::SdBusError(
+ EINVAL, "Not supported url's scheme.");
+ }
+
+ parameters->imageUrl.clear();
+ });
+ iface->initialize();
+ }
+
+ void addProcessInterface(const std::string& name)
+ {
+ auto iface = objServer->add_interface(
+ "/xyz/openbmc_project/VirtualMedia/Proxy/" + name,
+ "xyz.openbmc_project.VirtualMedia.Process");
+ iface->register_property(
+ "Active", bool(false),
+ [](const bool& req, bool& property) { return 0; },
+ [name](const bool& property) { return Process::isActive(name); });
+ iface->initialize();
+ }
+
+ boost::asio::io_context& ioc;
+ DeviceMonitor devMonitor;
+ std::shared_ptr<sdbusplus::asio::connection> bus;
+ std::shared_ptr<sdbusplus::asio::object_server> objServer;
+ std::shared_ptr<sdbusplus::server::manager::manager> objManager;
+ const Configuration& config;
+ ParametersManager paramMgr;
+};
+
+int main()
+{
+ Configuration config("/etc/virtual-media.json");
+ if (!config.valid)
+ return -1;
+
+ boost::asio::io_context ioc;
+ boost::asio::signal_set signals(ioc, SIGINT, SIGTERM);
+ signals.async_wait(
+ [&ioc](const boost::system::error_code&, const int&) { ioc.stop(); });
+
+ sd_bus* b = nullptr;
+#if defined(CUSTOM_DBUS_PATH)
+#pragma message("You are using custom DBUS path set to " CUSTOM_DBUS_PATH)
+ sd_bus_new(&b);
+ sd_bus_set_bus_client(b, true);
+ sd_bus_set_address(b, CUSTOM_DBUS_PATH);
+ sd_bus_start(b);
+#endif
+ App app(ioc, config, b);
+
+ ioc.run();
+
+ return 0;
+}
diff --git a/virtual-media/virtual-media.json b/virtual-media/virtual-media.json
new file mode 100644
index 0000000..4f3aba1
--- /dev/null
+++ b/virtual-media/virtual-media.json
@@ -0,0 +1,30 @@
+{
+ "InactivityTimeout": 1800,
+ "MountPoints": {
+ "ISO0": {
+ "EndpointId": "/nbd/0",
+ "Mode": 0,
+ "NBDDevice": "nbd0",
+ "UnixSocket": "/tmp/nbd0.sock",
+ "Timeout": 30,
+ "BlockSize": 512
+ },
+ "USB0": {
+ "EndpointId": "/nbd/1",
+ "Mode": 0,
+ "NBDDevice": "nbd1",
+ "UnixSocket": "/tmp/nbd1.sock",
+ "Timeout": 30,
+ "BlockSize": 512
+ },
+ "USB1": {
+ "EndpointId": "",
+ "Mode": 1,
+ "NBDDevice": "",
+ "UnixSocket": "",
+ "Timeout": 0,
+ "BlockSize": 0
+ }
+ }
+}
+
diff --git a/virtual-media/xyz.openbmc_project.VirtualMedia.service b/virtual-media/xyz.openbmc_project.VirtualMedia.service
new file mode 100644
index 0000000..b5c21ae
--- /dev/null
+++ b/virtual-media/xyz.openbmc_project.VirtualMedia.service
@@ -0,0 +1,11 @@
+[Unit]
+Description=Virtual Media Service
+After=dbus.service
+
+[Service]
+ExecStart=/usr/sbin/virtual-media
+Restart=always
+Type=simple
+
+[Install]
+WantedBy=multi-user.target