diff options
-rw-r--r-- | callback-manager/include/callback_manager.hpp | 13 | ||||
-rw-r--r-- | host_error_monitor/CMakeLists.txt | 2 | ||||
-rw-r--r-- | host_error_monitor/src/host_error_monitor.cpp | 1190 | ||||
-rw-r--r-- | libpeci/.clang-format | 98 | ||||
-rw-r--r-- | libpeci/CMakeLists.txt | 20 | ||||
-rw-r--r-- | libpeci/peci.c | 1081 | ||||
-rw-r--r-- | libpeci/peci.h | 224 | ||||
-rw-r--r-- | libpeci/peci_cmds.c | 370 | ||||
-rw-r--r-- | prov-mode-mgr/src/prov-mode-mgr.cpp | 12 | ||||
-rw-r--r-- | psu-manager/include/cold_redundancy.hpp | 4 | ||||
-rw-r--r-- | psu-manager/src/cold_redundancy.cpp | 168 | ||||
-rw-r--r-- | services/smbios-mdrv2/src/cpu.cpp | 5 | ||||
-rw-r--r-- | services/smbios/src/cpu.cpp | 5 | ||||
-rw-r--r-- | services/smbios/src/dimm.cpp | 5 | ||||
-rw-r--r-- | settings/include/defaults.hpp | 7 | ||||
-rw-r--r-- | virtual-media/.clang-format | 98 | ||||
-rw-r--r-- | virtual-media/CMakeLists.txt | 144 | ||||
-rw-r--r-- | virtual-media/LICENSE | 201 | ||||
-rw-r--r-- | virtual-media/cmake/Findudev.cmake | 77 | ||||
-rw-r--r-- | virtual-media/src/main.cpp | 1087 | ||||
-rw-r--r-- | virtual-media/virtual-media.json | 30 | ||||
-rw-r--r-- | virtual-media/xyz.openbmc_project.VirtualMedia.service | 11 |
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 |