| #pragma once |
| #include <boost/algorithm/string/join.hpp> |
| #include <boost/asio.hpp> |
| #include <phosphor-logging/lg2.hpp> |
| |
| #include <chrono> |
| #include <filesystem> |
| #include <mutex> |
| #include <optional> |
| #include <system_error> |
| |
| /** |
| * @brief Logs a message via lg2 at most once every N seconds specific to the |
| * call site. |
| * |
| * This macro utilizes static local variables to maintain state specific |
| * to the exact line of code where the macro is expanded. |
| * |
| * It uses double-checked locking to ensure thread safety if multiple threads |
| * hit the exact same log statement simultaneously. |
| * |
| * @param level The lg2 severity level (e.g., info, error, warning, debug). |
| * @param N The interval in seconds. |
| * @param msg The message format string. |
| * @param ... Optional arguments for the format string. |
| */ |
| #define LG2_LOG_EVERY_N_SEC(level, N, msg, ...) \ |
| do \ |
| { \ |
| /* The magic happens here: These static variables are unique per */ \ |
| /* every distinct place this macro is used in the source code. */ \ |
| static std::chrono::steady_clock::time_point _lg2_site_last_time; \ |
| static std::mutex _lg2_site_mutex; \ |
| \ |
| /* Use steady_clock so system time changes don't break the timer */ \ |
| auto _lg2_now = std::chrono::steady_clock::now(); \ |
| \ |
| /* Optimization: Quick check without locking first */ \ |
| if (_lg2_now - _lg2_site_last_time >= std::chrono::seconds(N)) \ |
| { \ |
| std::lock_guard<std::mutex> _lg2_lock(_lg2_site_mutex); \ |
| /* Thread Safety: Double-check inside lock */ \ |
| if (_lg2_now - _lg2_site_last_time >= std::chrono::seconds(N)) \ |
| { \ |
| lg2::level(msg, ##__VA_ARGS__); \ |
| _lg2_site_last_time = _lg2_now; \ |
| } \ |
| } \ |
| } while (0) |
| |
| namespace nvme |
| { |
| static constexpr const char* sensorType = "NVME1000"; |
| } // namespace nvme |
| |
| inline std::filesystem::path deriveRootBusPath(int busNumber) |
| { |
| return "/sys/bus/i2c/devices/i2c-" + std::to_string(busNumber) + |
| "/mux_device"; |
| } |
| |
| inline std::optional<int> deriveRootBus(std::optional<int> busNumber) |
| { |
| if (!busNumber) |
| { |
| return std::nullopt; |
| } |
| |
| std::filesystem::path muxPath = deriveRootBusPath(*busNumber); |
| |
| if (!std::filesystem::is_symlink(muxPath)) |
| { |
| return *busNumber; |
| } |
| |
| std::string rootName = std::filesystem::read_symlink(muxPath).filename(); |
| size_t dash = rootName.find('-'); |
| if (dash == std::string::npos) |
| { |
| lg2::error("Error finding root bus for {ROOT_NAME}", "ROOT_NAME", |
| rootName); |
| return std::nullopt; |
| } |
| |
| return std::stoi(rootName.substr(0, dash)); |
| } |
| |
| inline std::optional<std::string> |
| extractOneFromTail(std::string::const_reverse_iterator& rbegin, |
| const std::string::const_reverse_iterator& rend) |
| { |
| std::string name; |
| auto curr = rbegin; |
| // remove the ending '/'s |
| while (rbegin != rend && *rbegin == '/') |
| { |
| rbegin++; |
| } |
| if (rbegin == rend) |
| { |
| return std::nullopt; |
| } |
| curr = rbegin++; |
| |
| // extract word |
| while (rbegin != rend && *rbegin != '/') |
| { |
| rbegin++; |
| } |
| if (rbegin == rend) |
| { |
| return std::nullopt; |
| } |
| name.append(rbegin.base(), curr.base()); |
| return {name}; |
| } |
| |
| // a path of |
| // "/xyz/openbmc_project/inventory/system/board/{prod}/{nvme}/{substruct}..." |
| // will generates a sensor name {prod}_{nvme}_{substruct}... |
| inline std::optional<std::string> |
| createSensorNameFromPath(const std::string& path) |
| { |
| if (path.empty()) |
| { |
| return std::nullopt; |
| } |
| auto rbegin = path.crbegin(); |
| std::vector<std::string> names; |
| do |
| { |
| auto name = extractOneFromTail(rbegin, path.crend()); |
| if (!name) |
| { |
| return std::nullopt; |
| } |
| if (*name == "board") |
| { |
| break; |
| } |
| |
| if (!names.empty() && names.back().starts_with(*name)) |
| { |
| // Skip potential duplicate prefix |
| continue; |
| } |
| names.push_back(*name); |
| } while (rbegin != path.rend()); |
| |
| std::reverse(names.begin(), names.end()); |
| auto sensorName = boost::algorithm::join(names, "_"); |
| return sensorName; |
| } |
| |
| // Function to update NVMe temp sensor |
| |
| // Function type for fetching ctemp which encaplucated in a structure of T. |
| // The fetcher function take a callback as input to process the result. |
| template <class T> |
| using ctemp_fetch_t = |
| std::function<void(std::function<void(const std::error_code&, T)>&&)>; |
| |
| // Function type for processing ctemp out the structure of type T. |
| // The process function will update the properties based on input data. |
| template <class T> |
| using ctemp_process_t = |
| std::function<void(const std::error_code& error, T data)>; |
| |
| template <class T> |
| void pollCtemp( |
| const std::weak_ptr<boost::asio::steady_timer>& timer, |
| std::chrono::duration<double, std::milli> delay, |
| const std::function<void(std::function<void(const std::error_code&, T)>&&)>& |
| dataFetcher, |
| const std::function<void(const std::error_code& error, T data)>& |
| dataProcessor); |
| |
| namespace detail |
| { |
| |
| template <class T> |
| void updateCtemp(std::weak_ptr<boost::asio::steady_timer> timer, |
| std::chrono::duration<double, std::milli> delay, |
| ctemp_process_t<T> dataProcessor, ctemp_fetch_t<T> dataFetcher, |
| const std::error_code& error, T data) |
| { |
| if (timer.expired()) |
| { |
| dataProcessor(std::make_error_code(std::errc::operation_canceled), |
| data); |
| return; |
| } |
| |
| dataProcessor(error, data); |
| ::pollCtemp(std::move(timer), delay, dataFetcher, dataProcessor); |
| } |
| |
| template <class T> |
| void pollCtemp(std::weak_ptr<boost::asio::steady_timer> timer, |
| std::chrono::duration<double, std::milli> delay, |
| ctemp_fetch_t<T> dataFetcher, ctemp_process_t<T> dataProcessor, |
| const boost::system::error_code errorCode) |
| { |
| if (errorCode == boost::asio::error::operation_aborted || timer.expired()) |
| { |
| lg2::info("poll loop has been cancelled"); |
| return; |
| } |
| if (errorCode) |
| { |
| lg2::error("{ERROR}", "ERROR", errorCode.message()); |
| ::pollCtemp(std::move(timer), delay, dataFetcher, dataProcessor); |
| return; |
| } |
| |
| dataFetcher(std::bind_front(detail::updateCtemp<T>, std::move(timer), delay, |
| dataProcessor, dataFetcher)); |
| } |
| |
| } // namespace detail |
| |
| template <class T> |
| void pollCtemp( |
| const std::weak_ptr<boost::asio::steady_timer>& weakTimer, |
| std::chrono::duration<double, std::milli> delay, |
| const std::function<void(std::function<void(const std::error_code&, T)>&&)>& |
| dataFetcher, |
| const std::function<void(const std::error_code& error, T data)>& |
| dataProcessor) |
| { |
| auto timer = weakTimer.lock(); |
| if (!timer) |
| { |
| return; |
| } |
| timer->expires_after( |
| std::chrono::duration_cast<std::chrono::milliseconds>(delay)); |
| timer->async_wait(std::bind_front(detail::pollCtemp<T>, timer, delay, |
| dataFetcher, dataProcessor)); |
| } |
| |
| // Strips space padding from a NVMe string, replaces invalid characters with |
| // spaces. |
| static inline std::string nvmeString(const char* data, size_t size) |
| { |
| std::string s(data, size); |
| |
| size_t n = s.find_last_not_of(' '); |
| if (n == std::string::npos) |
| { |
| s.clear(); |
| } |
| else |
| { |
| s.resize(n + 1); |
| } |
| |
| for (auto& c : s) |
| { |
| if (c < 0x20 || c > 0x7e) |
| { |
| c = ' '; |
| } |
| } |
| return s; |
| } |
| |
| static inline uint64_t char128ToUint64(const unsigned char* data) |
| { |
| int i = 0; |
| uint64_t result = 0; |
| |
| for (i = 0; i < 16; i++) |
| { |
| result *= 256; |
| result += data[15 - i]; |
| } |
| return result; |
| } |