Add ECC metadata
This change adds a ECC service that handles memory error and
to add new IPMI SEL records to the journal with the correct
metadata.
Tested:
CE Error:
1. Triger memory ce error:
devmem 0xf0824178 32 0x7501
2. ipmi sel list should log ce error:
1 | Pre-Init |0000015435| Memory #0xf0 | Correctable ECC | Asserted
UE Error:
1. Triger memory ue error:
devmem 0xf0824178 32 0x301
2.ipmi sel list should log ue error:
2 | Pre-Init |0000001677| Memory #0xf0 | Uncorrectable ECC | Asserted
Change-Id: Ia63a62c2e80697639fef4c4e114fee52f65c71af
Signed-off-by: Will Liang <will.liang@quantatw.com>
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..ea71ad6
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,99 @@
+---
+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/ecc_main.cpp b/ecc_main.cpp
new file mode 100644
index 0000000..a1da584
--- /dev/null
+++ b/ecc_main.cpp
@@ -0,0 +1,21 @@
+#include "config.h"
+
+#include "ecc_manager.hpp"
+
+#include <iostream>
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/sdbus.hpp>
+#include <sdbusplus/server/manager.hpp>
+
+int main(void)
+{
+
+ /** @brief Dbus constructs */
+ auto bus = sdbusplus::bus::new_default();
+
+ phosphor::memory::ECC obj(bus, OBJPATH);
+
+ obj.run();
+
+ return 0;
+}
diff --git a/ecc_manager.cpp b/ecc_manager.cpp
new file mode 100644
index 0000000..3fa1f88
--- /dev/null
+++ b/ecc_manager.cpp
@@ -0,0 +1,244 @@
+#include "config.h"
+
+#include "ecc_manager.hpp"
+
+#include <filesystem>
+#include <fstream>
+#include <iostream>
+#include <phosphor-logging/elog-errors.hpp>
+#include <string>
+
+using namespace phosphor::logging;
+
+namespace phosphor
+{
+namespace memory
+{
+static constexpr const char ECC_FILE[] = "/etc/ecc/maxlog.conf";
+static constexpr const auto RESET_COUNT = "1";
+static constexpr const char CLOSE_EDAC_REPORT[] = "off";
+
+auto retries = 3;
+static constexpr auto delay = std::chrono::milliseconds{100};
+static constexpr auto interval = 1000000;
+static constexpr uint16_t selBMCGenID = 0x0020;
+void ECC::init()
+{
+
+ namespace fs = std::filesystem;
+
+ if (fs::exists(sysfsRootPath))
+ {
+ try
+ {
+ resetCounter();
+ getMaxLogValue();
+ }
+ catch (const std::system_error& e)
+ {
+
+ log<level::INFO>(
+ "Logging failing sysfs file",
+ phosphor::logging::entry("FILE=%s", sysfsRootPath));
+ }
+ }
+ _bus.request_name(BUSNAME);
+}
+
+std::string ECC::getValue(std::string fullPath)
+{
+ std::string val;
+ std::ifstream ifs;
+
+ while (true)
+ {
+ try
+ {
+ if (!ifs.is_open())
+ ifs.open(fullPath);
+ ifs.clear();
+ ifs.seekg(0);
+ ifs >> val;
+ }
+ catch (const std::exception& e)
+ {
+ --retries;
+ std::this_thread::sleep_for(delay);
+ continue;
+ }
+ break;
+ }
+
+ ifs.close();
+ return val;
+}
+
+void ECC::writeValue(std::string fullPath, std::string value)
+{
+ std::ofstream ofs;
+ while (true)
+ {
+ try
+ {
+ if (!ofs.is_open())
+ ofs.open(fullPath);
+ ofs.clear();
+ ofs.seekp(0);
+ ofs << value;
+ ofs.flush();
+ }
+ catch (const std::exception& e)
+ {
+ --retries;
+ std::this_thread::sleep_for(delay);
+ continue;
+ }
+ break;
+ }
+ ofs.close();
+}
+
+void ECC::run()
+{
+ init();
+ std::function<void()> callback(std::bind(&ECC::read, this));
+ try
+ {
+ _timer.restart(std::chrono::microseconds(interval));
+
+ _bus.attach_event(_event.get(), SD_EVENT_PRIORITY_IMPORTANT);
+ _event.loop();
+ }
+ catch (const std::exception& e)
+ {
+ log<level::ERR>("Error in sysfs polling loop",
+ entry("ERROR=%s", e.what()));
+ throw;
+ }
+}
+
+void ECC::checkEccLogFull(int64_t ceCount, int64_t ueCount)
+{
+ std::string errorMsg = "ECC error(memory error logging limit reached)";
+ std::vector<uint8_t> eccLogFullEventData{0x05, 0xff, 0xfe};
+ bool assert = true;
+
+ auto total = ceCount + ueCount;
+ bool isReached = false;
+ if (total == 0)
+ {
+ // someone reset edac report from driver
+ // so clear all parameter
+ EccInterface::ceCount(ceCount);
+ EccInterface::ueCount(ueCount);
+ previousCeCounter = 0;
+ previousUeCounter = 0;
+ EccInterface::isLoggingLimitReached(isReached);
+ }
+ else if (total >= maxECCLog)
+ {
+ // add SEL log
+ addSELLog(errorMsg, OBJPATH, eccLogFullEventData, assert, selBMCGenID);
+ isReached = true;
+ EccInterface::isLoggingLimitReached(isReached);
+ controlEDACReport(CLOSE_EDAC_REPORT);
+ // set ECC state
+ EccInterface::state(MemoryECC::ECCStatus::LogFull);
+ }
+}
+
+int ECC::checkCeCount()
+{
+ std::string item = "ce_count";
+ std::string errorMsg = "ECC error(correctable)";
+ int64_t value = 0;
+ std::string fullPath = sysfsRootPath;
+ fullPath.append(item);
+ value = std::stoi(getValue(fullPath));
+ std::vector<uint8_t> eccCeEventData{0x00, 0xff, 0xfe};
+ bool assert = true;
+
+ while (previousCeCounter < value)
+ {
+ previousCeCounter++;
+ // add phosphor-logging log
+ EccInterface::ceCount(previousCeCounter);
+ // add SEL log
+ addSELLog(errorMsg, OBJPATH, eccCeEventData, assert, selBMCGenID);
+ // set ECC state
+ EccInterface::state(MemoryECC::ECCStatus::CE);
+ }
+ return value;
+}
+
+int ECC::checkUeCount()
+{
+ std::string item = "ue_count";
+ std::string errorMsg = "ECC error(uncorrectable)";
+ int64_t value = 0;
+ std::string fullPath = sysfsRootPath;
+ fullPath.append(item);
+ value = std::stoi(getValue(fullPath));
+ std::vector<uint8_t> eccUeEventData{0x01, 0xff, 0xfe};
+ bool assert = true;
+
+ while (previousUeCounter < value)
+ {
+ previousUeCounter++;
+ // add phosphor-logging log
+ EccInterface::ueCount(previousUeCounter);
+ // add SEL log
+ addSELLog(errorMsg, OBJPATH, eccUeEventData, assert, selBMCGenID);
+ // set ECC state
+ EccInterface::state(MemoryECC::ECCStatus::UE);
+ }
+ return value;
+}
+
+void ECC::resetCounter()
+{
+ std::string item = "reset_counters";
+ std::string fullPath = sysfsRootPath;
+ fullPath.append(item);
+ writeValue(fullPath, RESET_COUNT);
+}
+
+void ECC::read()
+{
+ int64_t ceCount = 0;
+ int64_t ueCount = 0;
+ ceCount = checkCeCount();
+ ueCount = checkUeCount();
+ checkEccLogFull(ceCount, ueCount);
+}
+
+void ECC::controlEDACReport(std::string op)
+{
+ writeValue(sysfsEDACReportPath, op);
+}
+
+// get max log from file
+void ECC::getMaxLogValue()
+{
+ maxECCLog = std::stoi(getValue(ECC_FILE));
+}
+
+void ECC::addSELLog(std::string message, std::string path,
+ std::vector<uint8_t> selData, bool assert, uint16_t genId)
+{
+ // sdbusplus::bus::bus bus = sdbusplus::bus::new_default();
+
+ auto selCall = _bus.new_method_call(
+ "xyz.openbmc_project.Logging.IPMI", "/xyz/openbmc_project/Logging/IPMI",
+ "xyz.openbmc_project.Logging.IPMI", "IpmiSelAdd");
+ selCall.append(message, path, selData, assert, genId);
+
+ auto selReply = _bus.call(selCall);
+ if (selReply.is_method_error())
+ {
+ log<level::ERR>("add SEL log error\n");
+ }
+}
+
+} // namespace memory
+} // namespace phosphor
diff --git a/ecc_manager.hpp b/ecc_manager.hpp
new file mode 100644
index 0000000..bbad72c
--- /dev/null
+++ b/ecc_manager.hpp
@@ -0,0 +1,87 @@
+#pragma once
+
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/server/object.hpp>
+#include <sdeventplus/clock.hpp>
+#include <sdeventplus/event.hpp>
+#include <sdeventplus/utility/timer.hpp>
+#include <xyz/openbmc_project/Memory/MemoryECC/server.hpp>
+
+namespace phosphor
+{
+namespace memory
+{
+
+template <typename... T>
+using ServerObject = typename sdbusplus::server::object::object<T...>;
+
+using EccInterface = sdbusplus::xyz::openbmc_project::Memory::server::MemoryECC;
+using EccObject = ServerObject<EccInterface>;
+/** @class
+ * @brief Manages ECC
+ */
+class ECC : sdbusplus::server::object::object<
+ sdbusplus::xyz::openbmc_project::Memory::server::MemoryECC>
+{
+ public:
+ ECC() = delete;
+ ~ECC() = default;
+ ECC(const ECC&) = delete;
+ ECC& operator=(const ECC&) = delete;
+ ECC(ECC&&) = default;
+ ECC& operator=(ECC&&) = default;
+
+ /** @brief Constructs
+ *
+ * @param[in] bus - Handle to system dbus
+ * @param[in] objPath - The Dbus path
+ */
+ ECC(sdbusplus::bus::bus& bus, const std::string& objPath) :
+ sdbusplus::server::object::object<
+ sdbusplus::xyz::openbmc_project::Memory::server::MemoryECC>(
+ bus, objPath.c_str()),
+ _bus(bus), _event(sdeventplus::Event::get_default()),
+ _timer(_event, std::bind(&ECC::read, this)){
+ // Nothing to do here
+ };
+
+ int64_t previousCeCounter = 0;
+ int64_t previousUeCounter = 0;
+ int64_t maxECCLog = 0;
+
+ void run();
+ void controlEDACReport(std::string);
+
+ private:
+ /** @brief sdbusplus bus client connection. */
+ sdbusplus::bus::bus& _bus;
+ /** @brief the Event Loop structure */
+ sdeventplus::Event _event;
+ /** @brief Read Timer */
+ sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic> _timer;
+ /** @brief Read sysfs entries */
+ void read();
+
+ /** @brief Set up D-Bus object init */
+ void init();
+
+ std::string getValue(std::string);
+
+ void writeValue(std::string, std::string);
+ // set ce_count to dbus
+ int checkCeCount();
+ // set ue_count to dbus
+ int checkUeCount();
+ // set eccErrorCount to dbus
+ void checkEccLogFull(int64_t, int64_t);
+
+ void resetCounter();
+ // set maxECCLog value
+ void getMaxLogValue();
+
+ void addSELLog(std::string, std::string, std::vector<uint8_t>, bool,
+ uint16_t);
+};
+
+} // namespace memory
+} // namespace phosphor
diff --git a/maxlog.conf b/maxlog.conf
new file mode 100644
index 0000000..29d6383
--- /dev/null
+++ b/maxlog.conf
@@ -0,0 +1 @@
+100
diff --git a/meson.build b/meson.build
new file mode 100644
index 0000000..766f89b
--- /dev/null
+++ b/meson.build
@@ -0,0 +1,37 @@
+project(
+ 'phosphor-ecc',
+ 'cpp',
+ version: '1.0',
+ default_options: [
+ 'cpp_std=c++17',
+ ],
+)
+
+executable(
+ 'ecc_main',
+ [
+ 'ecc_main.cpp',
+ 'ecc_manager.cpp',
+ ],
+ dependencies: [
+ dependency('sdbusplus'),
+ dependency('sdeventplus'),
+ dependency('phosphor-dbus-interfaces'),
+ dependency('phosphor-logging'),
+ ],
+ install: true,
+ install_dir: get_option('bindir')
+)
+
+install_data(sources : 'maxlog.conf', install_dir : '/etc/ecc')
+
+conf_data = configuration_data()
+conf_data.set('BUSNAME', '"xyz.openbmc_project.memory.ECC"')
+conf_data.set('OBJROOT', '"/xyz/openbmc_project/metrics/memory/"')
+conf_data.set('OBJPATH', '"/xyz/openbmc_project/metrics/memory/BmcECC"')
+conf_data.set('sysfsRootPath', '"/sys/devices/system/edac/mc/mc0/"')
+conf_data.set('sysfsEDACReportPath', '"/sys/module/edac_core/parameters/edac_report"')
+
+configure_file(output : 'config.h',
+ configuration : conf_data)
+