13f7c5e40SJason M. Bills /* 21a2fbddfSJason M. Bills // Copyright (c) 2017-2019 Intel Corporation 33f7c5e40SJason M. Bills // 43f7c5e40SJason M. Bills // Licensed under the Apache License, Version 2.0 (the "License"); 53f7c5e40SJason M. Bills // you may not use this file except in compliance with the License. 63f7c5e40SJason M. Bills // You may obtain a copy of the License at 73f7c5e40SJason M. Bills // 83f7c5e40SJason M. Bills // http://www.apache.org/licenses/LICENSE-2.0 93f7c5e40SJason M. Bills // 103f7c5e40SJason M. Bills // Unless required by applicable law or agreed to in writing, software 113f7c5e40SJason M. Bills // distributed under the License is distributed on an "AS IS" BASIS, 123f7c5e40SJason M. Bills // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 133f7c5e40SJason M. Bills // See the License for the specific language governing permissions and 143f7c5e40SJason M. Bills // limitations under the License. 153f7c5e40SJason M. Bills */ 163f7c5e40SJason M. Bills 171d4d54ddSJason M. Bills #include <boost/algorithm/string.hpp> 183f7c5e40SJason M. Bills #include <boost/container/flat_map.hpp> 19c04e2e70SJason M. Bills #include <boost/process.hpp> 203f7c5e40SJason M. Bills #include <commandutils.hpp> 211d4d54ddSJason M. Bills #include <filesystem> 223f7c5e40SJason M. Bills #include <iostream> 2399b78ec8SJason M. Bills #include <ipmi_to_redfish_hooks.hpp> 242a265d54SJames Feist #include <ipmid/api.hpp> 25c04e2e70SJason M. Bills #include <phosphor-ipmi-host/selutility.hpp> 263f7c5e40SJason M. Bills #include <phosphor-logging/log.hpp> 273f7c5e40SJason M. Bills #include <sdbusplus/message/types.hpp> 283f7c5e40SJason M. Bills #include <sdbusplus/timer.hpp> 29c04e2e70SJason M. Bills #include <sdrutils.hpp> 30c04e2e70SJason M. Bills #include <stdexcept> 313f7c5e40SJason M. Bills #include <storagecommands.hpp> 3252aaa7d5SJason M. Bills #include <string_view> 333f7c5e40SJason M. Bills 341d4d54ddSJason M. Bills namespace intel_oem::ipmi::sel 351d4d54ddSJason M. Bills { 361d4d54ddSJason M. Bills static const std::filesystem::path selLogDir = "/var/log"; 371d4d54ddSJason M. Bills static const std::string selLogFilename = "ipmi_sel"; 381d4d54ddSJason M. Bills 391d4d54ddSJason M. Bills static int getFileTimestamp(const std::filesystem::path& file) 401d4d54ddSJason M. Bills { 411d4d54ddSJason M. Bills struct stat st; 421d4d54ddSJason M. Bills 431d4d54ddSJason M. Bills if (stat(file.c_str(), &st) >= 0) 441d4d54ddSJason M. Bills { 451d4d54ddSJason M. Bills return st.st_mtime; 461d4d54ddSJason M. Bills } 471d4d54ddSJason M. Bills return ::ipmi::sel::invalidTimeStamp; 481d4d54ddSJason M. Bills } 491d4d54ddSJason M. Bills 501d4d54ddSJason M. Bills namespace erase_time 517944c307SJason M. Bills { 527944c307SJason M. Bills static constexpr const char* selEraseTimestamp = "/var/lib/ipmi/sel_erase_time"; 537944c307SJason M. Bills 547944c307SJason M. Bills void save() 557944c307SJason M. Bills { 567944c307SJason M. Bills // open the file, creating it if necessary 577944c307SJason M. Bills int fd = open(selEraseTimestamp, O_WRONLY | O_CREAT | O_CLOEXEC, 0644); 587944c307SJason M. Bills if (fd < 0) 597944c307SJason M. Bills { 607944c307SJason M. Bills std::cerr << "Failed to open file\n"; 617944c307SJason M. Bills return; 627944c307SJason M. Bills } 637944c307SJason M. Bills 647944c307SJason M. Bills // update the file timestamp to the current time 657944c307SJason M. Bills if (futimens(fd, NULL) < 0) 667944c307SJason M. Bills { 677944c307SJason M. Bills std::cerr << "Failed to update timestamp: " 687944c307SJason M. Bills << std::string(strerror(errno)); 697944c307SJason M. Bills } 707944c307SJason M. Bills close(fd); 717944c307SJason M. Bills } 727944c307SJason M. Bills 737944c307SJason M. Bills int get() 747944c307SJason M. Bills { 751d4d54ddSJason M. Bills return getFileTimestamp(selEraseTimestamp); 767944c307SJason M. Bills } 771d4d54ddSJason M. Bills } // namespace erase_time 781d4d54ddSJason M. Bills } // namespace intel_oem::ipmi::sel 797944c307SJason M. Bills 803f7c5e40SJason M. Bills namespace ipmi 813f7c5e40SJason M. Bills { 823f7c5e40SJason M. Bills 833f7c5e40SJason M. Bills namespace storage 843f7c5e40SJason M. Bills { 853f7c5e40SJason M. Bills 86e2d1aee3SJason M. Bills constexpr static const size_t maxMessageSize = 64; 873f7c5e40SJason M. Bills constexpr static const size_t maxFruSdrNameSize = 16; 883f7c5e40SJason M. Bills using ManagedObjectType = boost::container::flat_map< 893f7c5e40SJason M. Bills sdbusplus::message::object_path, 903f7c5e40SJason M. Bills boost::container::flat_map< 913f7c5e40SJason M. Bills std::string, boost::container::flat_map<std::string, DbusVariant>>>; 923f7c5e40SJason M. Bills using ManagedEntry = std::pair< 933f7c5e40SJason M. Bills sdbusplus::message::object_path, 943f7c5e40SJason M. Bills boost::container::flat_map< 953f7c5e40SJason M. Bills std::string, boost::container::flat_map<std::string, DbusVariant>>>; 963f7c5e40SJason M. Bills 973bcba457SJames Feist constexpr static const char* fruDeviceServiceName = 983bcba457SJames Feist "xyz.openbmc_project.FruDevice"; 99e2d1aee3SJason M. Bills constexpr static const size_t cacheTimeoutSeconds = 10; 1003f7c5e40SJason M. Bills 1014ed6f2c1SJason M. Bills // event direction is bit[7] of eventType where 1b = Deassertion event 1024ed6f2c1SJason M. Bills constexpr static const uint8_t deassertionEvent = 0x80; 103c04e2e70SJason M. Bills 1043f7c5e40SJason M. Bills static std::vector<uint8_t> fruCache; 1053f7c5e40SJason M. Bills static uint8_t cacheBus = 0xFF; 1063f7c5e40SJason M. Bills static uint8_t cacheAddr = 0XFF; 1073f7c5e40SJason M. Bills 1083f7c5e40SJason M. Bills std::unique_ptr<phosphor::Timer> cacheTimer = nullptr; 1093f7c5e40SJason M. Bills 1103f7c5e40SJason M. Bills // we unfortunately have to build a map of hashes in case there is a 1113f7c5e40SJason M. Bills // collision to verify our dev-id 1123f7c5e40SJason M. Bills boost::container::flat_map<uint8_t, std::pair<uint8_t, uint8_t>> deviceHashes; 1133f7c5e40SJason M. Bills 114e2d1aee3SJason M. Bills void registerStorageFunctions() __attribute__((constructor)); 1153f7c5e40SJason M. Bills 1163f7c5e40SJason M. Bills bool writeFru() 1173f7c5e40SJason M. Bills { 11815419dd5SVernon Mauery std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus(); 11915419dd5SVernon Mauery sdbusplus::message::message writeFru = dbus->new_method_call( 1203f7c5e40SJason M. Bills fruDeviceServiceName, "/xyz/openbmc_project/FruDevice", 1213f7c5e40SJason M. Bills "xyz.openbmc_project.FruDeviceManager", "WriteFru"); 1223f7c5e40SJason M. Bills writeFru.append(cacheBus, cacheAddr, fruCache); 1233f7c5e40SJason M. Bills try 1243f7c5e40SJason M. Bills { 12515419dd5SVernon Mauery sdbusplus::message::message writeFruResp = dbus->call(writeFru); 1263f7c5e40SJason M. Bills } 1273f7c5e40SJason M. Bills catch (sdbusplus::exception_t&) 1283f7c5e40SJason M. Bills { 1293f7c5e40SJason M. Bills // todo: log sel? 1303f7c5e40SJason M. Bills phosphor::logging::log<phosphor::logging::level::ERR>( 1313f7c5e40SJason M. Bills "error writing fru"); 1323f7c5e40SJason M. Bills return false; 1333f7c5e40SJason M. Bills } 1343f7c5e40SJason M. Bills return true; 1353f7c5e40SJason M. Bills } 1363f7c5e40SJason M. Bills 137e2d1aee3SJason M. Bills void createTimer() 138e2d1aee3SJason M. Bills { 139e2d1aee3SJason M. Bills if (cacheTimer == nullptr) 140e2d1aee3SJason M. Bills { 141e2d1aee3SJason M. Bills cacheTimer = std::make_unique<phosphor::Timer>(writeFru); 142e2d1aee3SJason M. Bills } 143e2d1aee3SJason M. Bills } 144e2d1aee3SJason M. Bills 1453f7c5e40SJason M. Bills ipmi_ret_t replaceCacheFru(uint8_t devId) 1463f7c5e40SJason M. Bills { 1473f7c5e40SJason M. Bills static uint8_t lastDevId = 0xFF; 1483f7c5e40SJason M. Bills 1493f7c5e40SJason M. Bills bool timerRunning = (cacheTimer != nullptr) && !cacheTimer->isExpired(); 1503f7c5e40SJason M. Bills if (lastDevId == devId && timerRunning) 1513f7c5e40SJason M. Bills { 1523f7c5e40SJason M. Bills return IPMI_CC_OK; // cache already up to date 1533f7c5e40SJason M. Bills } 1543f7c5e40SJason M. Bills // if timer is running, stop it and writeFru manually 1553f7c5e40SJason M. Bills else if (timerRunning) 1563f7c5e40SJason M. Bills { 1573f7c5e40SJason M. Bills cacheTimer->stop(); 1583f7c5e40SJason M. Bills writeFru(); 1593f7c5e40SJason M. Bills } 1603f7c5e40SJason M. Bills 16115419dd5SVernon Mauery std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus(); 16215419dd5SVernon Mauery sdbusplus::message::message getObjects = dbus->new_method_call( 1633f7c5e40SJason M. Bills fruDeviceServiceName, "/", "org.freedesktop.DBus.ObjectManager", 1643f7c5e40SJason M. Bills "GetManagedObjects"); 1653f7c5e40SJason M. Bills ManagedObjectType frus; 1663f7c5e40SJason M. Bills try 1673f7c5e40SJason M. Bills { 16815419dd5SVernon Mauery sdbusplus::message::message resp = dbus->call(getObjects); 1693f7c5e40SJason M. Bills resp.read(frus); 1703f7c5e40SJason M. Bills } 1713f7c5e40SJason M. Bills catch (sdbusplus::exception_t&) 1723f7c5e40SJason M. Bills { 1733f7c5e40SJason M. Bills phosphor::logging::log<phosphor::logging::level::ERR>( 1743f7c5e40SJason M. Bills "replaceCacheFru: error getting managed objects"); 1753f7c5e40SJason M. Bills return IPMI_CC_RESPONSE_ERROR; 1763f7c5e40SJason M. Bills } 1773f7c5e40SJason M. Bills 1783f7c5e40SJason M. Bills deviceHashes.clear(); 1793f7c5e40SJason M. Bills 1803f7c5e40SJason M. Bills // hash the object paths to create unique device id's. increment on 1813f7c5e40SJason M. Bills // collision 1823f7c5e40SJason M. Bills std::hash<std::string> hasher; 1833f7c5e40SJason M. Bills for (const auto& fru : frus) 1843f7c5e40SJason M. Bills { 1853f7c5e40SJason M. Bills auto fruIface = fru.second.find("xyz.openbmc_project.FruDevice"); 1863f7c5e40SJason M. Bills if (fruIface == fru.second.end()) 1873f7c5e40SJason M. Bills { 1883f7c5e40SJason M. Bills continue; 1893f7c5e40SJason M. Bills } 1903f7c5e40SJason M. Bills 1913f7c5e40SJason M. Bills auto busFind = fruIface->second.find("BUS"); 1923f7c5e40SJason M. Bills auto addrFind = fruIface->second.find("ADDRESS"); 1933f7c5e40SJason M. Bills if (busFind == fruIface->second.end() || 1943f7c5e40SJason M. Bills addrFind == fruIface->second.end()) 1953f7c5e40SJason M. Bills { 1963f7c5e40SJason M. Bills phosphor::logging::log<phosphor::logging::level::INFO>( 1973f7c5e40SJason M. Bills "fru device missing Bus or Address", 1983f7c5e40SJason M. Bills phosphor::logging::entry("FRU=%s", fru.first.str.c_str())); 1993f7c5e40SJason M. Bills continue; 2003f7c5e40SJason M. Bills } 2013f7c5e40SJason M. Bills 2028166c8d7SVernon Mauery uint8_t fruBus = std::get<uint32_t>(busFind->second); 2038166c8d7SVernon Mauery uint8_t fruAddr = std::get<uint32_t>(addrFind->second); 2043f7c5e40SJason M. Bills 2053f7c5e40SJason M. Bills uint8_t fruHash = 0; 2063f7c5e40SJason M. Bills if (fruBus != 0 || fruAddr != 0) 2073f7c5e40SJason M. Bills { 2083f7c5e40SJason M. Bills fruHash = hasher(fru.first.str); 2093f7c5e40SJason M. Bills // can't be 0xFF based on spec, and 0 is reserved for baseboard 2103f7c5e40SJason M. Bills if (fruHash == 0 || fruHash == 0xFF) 2113f7c5e40SJason M. Bills { 2123f7c5e40SJason M. Bills fruHash = 1; 2133f7c5e40SJason M. Bills } 2143f7c5e40SJason M. Bills } 2153f7c5e40SJason M. Bills std::pair<uint8_t, uint8_t> newDev(fruBus, fruAddr); 2163f7c5e40SJason M. Bills 2173f7c5e40SJason M. Bills bool emplacePassed = false; 2183f7c5e40SJason M. Bills while (!emplacePassed) 2193f7c5e40SJason M. Bills { 2203f7c5e40SJason M. Bills auto resp = deviceHashes.emplace(fruHash, newDev); 2213f7c5e40SJason M. Bills emplacePassed = resp.second; 2223f7c5e40SJason M. Bills if (!emplacePassed) 2233f7c5e40SJason M. Bills { 2243f7c5e40SJason M. Bills fruHash++; 2253f7c5e40SJason M. Bills // can't be 0xFF based on spec, and 0 is reserved for 2263f7c5e40SJason M. Bills // baseboard 2273f7c5e40SJason M. Bills if (fruHash == 0XFF) 2283f7c5e40SJason M. Bills { 2293f7c5e40SJason M. Bills fruHash = 0x1; 2303f7c5e40SJason M. Bills } 2313f7c5e40SJason M. Bills } 2323f7c5e40SJason M. Bills } 2333f7c5e40SJason M. Bills } 2343f7c5e40SJason M. Bills auto deviceFind = deviceHashes.find(devId); 2353f7c5e40SJason M. Bills if (deviceFind == deviceHashes.end()) 2363f7c5e40SJason M. Bills { 2373f7c5e40SJason M. Bills return IPMI_CC_SENSOR_INVALID; 2383f7c5e40SJason M. Bills } 2393f7c5e40SJason M. Bills 2403f7c5e40SJason M. Bills fruCache.clear(); 24115419dd5SVernon Mauery sdbusplus::message::message getRawFru = dbus->new_method_call( 2423f7c5e40SJason M. Bills fruDeviceServiceName, "/xyz/openbmc_project/FruDevice", 2433f7c5e40SJason M. Bills "xyz.openbmc_project.FruDeviceManager", "GetRawFru"); 2443f7c5e40SJason M. Bills cacheBus = deviceFind->second.first; 2453f7c5e40SJason M. Bills cacheAddr = deviceFind->second.second; 2463f7c5e40SJason M. Bills getRawFru.append(cacheBus, cacheAddr); 2473f7c5e40SJason M. Bills try 2483f7c5e40SJason M. Bills { 24915419dd5SVernon Mauery sdbusplus::message::message getRawResp = dbus->call(getRawFru); 2503f7c5e40SJason M. Bills getRawResp.read(fruCache); 2513f7c5e40SJason M. Bills } 2523f7c5e40SJason M. Bills catch (sdbusplus::exception_t&) 2533f7c5e40SJason M. Bills { 2543f7c5e40SJason M. Bills lastDevId = 0xFF; 2553f7c5e40SJason M. Bills cacheBus = 0xFF; 2563f7c5e40SJason M. Bills cacheAddr = 0xFF; 2573f7c5e40SJason M. Bills return IPMI_CC_RESPONSE_ERROR; 2583f7c5e40SJason M. Bills } 2593f7c5e40SJason M. Bills 2603f7c5e40SJason M. Bills lastDevId = devId; 2613f7c5e40SJason M. Bills return IPMI_CC_OK; 2623f7c5e40SJason M. Bills } 2633f7c5e40SJason M. Bills 264e2d1aee3SJason M. Bills ipmi_ret_t ipmiStorageReadFRUData(ipmi_netfn_t netfn, ipmi_cmd_t cmd, 265e2d1aee3SJason M. Bills ipmi_request_t request, 266e2d1aee3SJason M. Bills ipmi_response_t response, 267e2d1aee3SJason M. Bills ipmi_data_len_t dataLen, 268e2d1aee3SJason M. Bills ipmi_context_t context) 269e2d1aee3SJason M. Bills { 270e2d1aee3SJason M. Bills if (*dataLen != 4) 271e2d1aee3SJason M. Bills { 272e2d1aee3SJason M. Bills *dataLen = 0; 273e2d1aee3SJason M. Bills return IPMI_CC_REQ_DATA_LEN_INVALID; 274e2d1aee3SJason M. Bills } 275e2d1aee3SJason M. Bills *dataLen = 0; // default to 0 in case of an error 276e2d1aee3SJason M. Bills 277e2d1aee3SJason M. Bills auto req = static_cast<GetFRUAreaReq*>(request); 278e2d1aee3SJason M. Bills 279e2d1aee3SJason M. Bills if (req->countToRead > maxMessageSize - 1) 280e2d1aee3SJason M. Bills { 281e2d1aee3SJason M. Bills return IPMI_CC_INVALID_FIELD_REQUEST; 282e2d1aee3SJason M. Bills } 283e2d1aee3SJason M. Bills ipmi_ret_t status = replaceCacheFru(req->fruDeviceID); 284e2d1aee3SJason M. Bills 285e2d1aee3SJason M. Bills if (status != IPMI_CC_OK) 286e2d1aee3SJason M. Bills { 287e2d1aee3SJason M. Bills return status; 288e2d1aee3SJason M. Bills } 289e2d1aee3SJason M. Bills 290e2d1aee3SJason M. Bills size_t fromFRUByteLen = 0; 291e2d1aee3SJason M. Bills if (req->countToRead + req->fruInventoryOffset < fruCache.size()) 292e2d1aee3SJason M. Bills { 293e2d1aee3SJason M. Bills fromFRUByteLen = req->countToRead; 294e2d1aee3SJason M. Bills } 295e2d1aee3SJason M. Bills else if (fruCache.size() > req->fruInventoryOffset) 296e2d1aee3SJason M. Bills { 297e2d1aee3SJason M. Bills fromFRUByteLen = fruCache.size() - req->fruInventoryOffset; 298e2d1aee3SJason M. Bills } 299e2d1aee3SJason M. Bills size_t padByteLen = req->countToRead - fromFRUByteLen; 300e2d1aee3SJason M. Bills uint8_t* respPtr = static_cast<uint8_t*>(response); 301e2d1aee3SJason M. Bills *respPtr = req->countToRead; 302e2d1aee3SJason M. Bills std::copy(fruCache.begin() + req->fruInventoryOffset, 303e2d1aee3SJason M. Bills fruCache.begin() + req->fruInventoryOffset + fromFRUByteLen, 304e2d1aee3SJason M. Bills ++respPtr); 305e2d1aee3SJason M. Bills // if longer than the fru is requested, fill with 0xFF 306e2d1aee3SJason M. Bills if (padByteLen) 307e2d1aee3SJason M. Bills { 308e2d1aee3SJason M. Bills respPtr += fromFRUByteLen; 309e2d1aee3SJason M. Bills std::fill(respPtr, respPtr + padByteLen, 0xFF); 310e2d1aee3SJason M. Bills } 311e2d1aee3SJason M. Bills *dataLen = fromFRUByteLen + 1; 312e2d1aee3SJason M. Bills 313e2d1aee3SJason M. Bills return IPMI_CC_OK; 314e2d1aee3SJason M. Bills } 315e2d1aee3SJason M. Bills 316e2d1aee3SJason M. Bills ipmi_ret_t ipmiStorageWriteFRUData(ipmi_netfn_t netfn, ipmi_cmd_t cmd, 317e2d1aee3SJason M. Bills ipmi_request_t request, 318e2d1aee3SJason M. Bills ipmi_response_t response, 319e2d1aee3SJason M. Bills ipmi_data_len_t dataLen, 320e2d1aee3SJason M. Bills ipmi_context_t context) 321e2d1aee3SJason M. Bills { 322e2d1aee3SJason M. Bills if (*dataLen < 4 || 323e2d1aee3SJason M. Bills *dataLen >= 324e2d1aee3SJason M. Bills 0xFF + 3) // count written return is one byte, so limit to one byte 325e2d1aee3SJason M. Bills // of data after the three request data bytes 326e2d1aee3SJason M. Bills { 327e2d1aee3SJason M. Bills *dataLen = 0; 328e2d1aee3SJason M. Bills return IPMI_CC_REQ_DATA_LEN_INVALID; 329e2d1aee3SJason M. Bills } 330e2d1aee3SJason M. Bills 331e2d1aee3SJason M. Bills auto req = static_cast<WriteFRUDataReq*>(request); 332e2d1aee3SJason M. Bills size_t writeLen = *dataLen - 3; 333e2d1aee3SJason M. Bills *dataLen = 0; // default to 0 in case of an error 334e2d1aee3SJason M. Bills 335e2d1aee3SJason M. Bills ipmi_ret_t status = replaceCacheFru(req->fruDeviceID); 336e2d1aee3SJason M. Bills if (status != IPMI_CC_OK) 337e2d1aee3SJason M. Bills { 338e2d1aee3SJason M. Bills return status; 339e2d1aee3SJason M. Bills } 340e2d1aee3SJason M. Bills int lastWriteAddr = req->fruInventoryOffset + writeLen; 341e2d1aee3SJason M. Bills if (fruCache.size() < lastWriteAddr) 342e2d1aee3SJason M. Bills { 343e2d1aee3SJason M. Bills fruCache.resize(req->fruInventoryOffset + writeLen); 344e2d1aee3SJason M. Bills } 345e2d1aee3SJason M. Bills 346e2d1aee3SJason M. Bills std::copy(req->data, req->data + writeLen, 347e2d1aee3SJason M. Bills fruCache.begin() + req->fruInventoryOffset); 348e2d1aee3SJason M. Bills 349e2d1aee3SJason M. Bills bool atEnd = false; 350e2d1aee3SJason M. Bills 351e2d1aee3SJason M. Bills if (fruCache.size() >= sizeof(FRUHeader)) 352e2d1aee3SJason M. Bills { 353e2d1aee3SJason M. Bills 354e2d1aee3SJason M. Bills FRUHeader* header = reinterpret_cast<FRUHeader*>(fruCache.data()); 355e2d1aee3SJason M. Bills 356e2d1aee3SJason M. Bills int lastRecordStart = std::max( 357e2d1aee3SJason M. Bills header->internalOffset, 358e2d1aee3SJason M. Bills std::max(header->chassisOffset, 359e2d1aee3SJason M. Bills std::max(header->boardOffset, header->productOffset))); 360e2d1aee3SJason M. Bills // TODO: Handle Multi-Record FRUs? 361e2d1aee3SJason M. Bills 362e2d1aee3SJason M. Bills lastRecordStart *= 8; // header starts in are multiples of 8 bytes 363e2d1aee3SJason M. Bills 364e2d1aee3SJason M. Bills // get the length of the area in multiples of 8 bytes 365e2d1aee3SJason M. Bills if (lastWriteAddr > (lastRecordStart + 1)) 366e2d1aee3SJason M. Bills { 367e2d1aee3SJason M. Bills // second byte in record area is the length 368e2d1aee3SJason M. Bills int areaLength(fruCache[lastRecordStart + 1]); 369e2d1aee3SJason M. Bills areaLength *= 8; // it is in multiples of 8 bytes 370e2d1aee3SJason M. Bills 371e2d1aee3SJason M. Bills if (lastWriteAddr >= (areaLength + lastRecordStart)) 372e2d1aee3SJason M. Bills { 373e2d1aee3SJason M. Bills atEnd = true; 374e2d1aee3SJason M. Bills } 375e2d1aee3SJason M. Bills } 376e2d1aee3SJason M. Bills } 377e2d1aee3SJason M. Bills uint8_t* respPtr = static_cast<uint8_t*>(response); 378e2d1aee3SJason M. Bills if (atEnd) 379e2d1aee3SJason M. Bills { 380e2d1aee3SJason M. Bills // cancel timer, we're at the end so might as well send it 381e2d1aee3SJason M. Bills cacheTimer->stop(); 382e2d1aee3SJason M. Bills if (!writeFru()) 383e2d1aee3SJason M. Bills { 384e2d1aee3SJason M. Bills return IPMI_CC_INVALID_FIELD_REQUEST; 385e2d1aee3SJason M. Bills } 386e2d1aee3SJason M. Bills *respPtr = std::min(fruCache.size(), static_cast<size_t>(0xFF)); 387e2d1aee3SJason M. Bills } 388e2d1aee3SJason M. Bills else 389e2d1aee3SJason M. Bills { 390e2d1aee3SJason M. Bills // start a timer, if no further data is sent in cacheTimeoutSeconds 391e2d1aee3SJason M. Bills // seconds, check to see if it is valid 392e2d1aee3SJason M. Bills createTimer(); 393e2d1aee3SJason M. Bills cacheTimer->start(std::chrono::duration_cast<std::chrono::microseconds>( 394e2d1aee3SJason M. Bills std::chrono::seconds(cacheTimeoutSeconds))); 395e2d1aee3SJason M. Bills *respPtr = 0; 396e2d1aee3SJason M. Bills } 397e2d1aee3SJason M. Bills 398e2d1aee3SJason M. Bills *dataLen = 1; 399e2d1aee3SJason M. Bills 400e2d1aee3SJason M. Bills return IPMI_CC_OK; 401e2d1aee3SJason M. Bills } 402e2d1aee3SJason M. Bills 403*d33acd6bSjayaprakash Mutyala /** @brief implements the get FRU inventory area info command 404*d33acd6bSjayaprakash Mutyala * @param fruDeviceId - FRU Device ID 405*d33acd6bSjayaprakash Mutyala * 406*d33acd6bSjayaprakash Mutyala * @returns IPMI completion code plus response data 407*d33acd6bSjayaprakash Mutyala * - inventorySize - Number of possible allocation units 408*d33acd6bSjayaprakash Mutyala * - accessType - Allocation unit size in bytes. 409*d33acd6bSjayaprakash Mutyala */ 410*d33acd6bSjayaprakash Mutyala ipmi::RspType<uint16_t, // inventorySize 411*d33acd6bSjayaprakash Mutyala uint8_t> // accessType 412*d33acd6bSjayaprakash Mutyala ipmiStorageGetFruInvAreaInfo(uint8_t fruDeviceId) 413e2d1aee3SJason M. Bills { 414*d33acd6bSjayaprakash Mutyala if (fruDeviceId == 0xFF) 415e2d1aee3SJason M. Bills { 416*d33acd6bSjayaprakash Mutyala return ipmi::responseInvalidFieldRequest(); 417e2d1aee3SJason M. Bills } 418e2d1aee3SJason M. Bills 419*d33acd6bSjayaprakash Mutyala ipmi::Cc status = replaceCacheFru(fruDeviceId); 420e2d1aee3SJason M. Bills 421e2d1aee3SJason M. Bills if (status != IPMI_CC_OK) 422e2d1aee3SJason M. Bills { 423*d33acd6bSjayaprakash Mutyala return ipmi::response(status); 424e2d1aee3SJason M. Bills } 425e2d1aee3SJason M. Bills 426*d33acd6bSjayaprakash Mutyala constexpr uint8_t accessType = 427*d33acd6bSjayaprakash Mutyala static_cast<uint8_t>(GetFRUAreaAccessType::byte); 428e2d1aee3SJason M. Bills 429*d33acd6bSjayaprakash Mutyala return ipmi::responseSuccess(fruCache.size(), accessType); 430e2d1aee3SJason M. Bills } 431e2d1aee3SJason M. Bills 4323f7c5e40SJason M. Bills ipmi_ret_t getFruSdrCount(size_t& count) 4333f7c5e40SJason M. Bills { 4343f7c5e40SJason M. Bills ipmi_ret_t ret = replaceCacheFru(0); 4353f7c5e40SJason M. Bills if (ret != IPMI_CC_OK) 4363f7c5e40SJason M. Bills { 4373f7c5e40SJason M. Bills return ret; 4383f7c5e40SJason M. Bills } 4393f7c5e40SJason M. Bills count = deviceHashes.size(); 4403f7c5e40SJason M. Bills return IPMI_CC_OK; 4413f7c5e40SJason M. Bills } 4423f7c5e40SJason M. Bills 4433f7c5e40SJason M. Bills ipmi_ret_t getFruSdrs(size_t index, get_sdr::SensorDataFruRecord& resp) 4443f7c5e40SJason M. Bills { 4453f7c5e40SJason M. Bills ipmi_ret_t ret = replaceCacheFru(0); // this will update the hash list 4463f7c5e40SJason M. Bills if (ret != IPMI_CC_OK) 4473f7c5e40SJason M. Bills { 4483f7c5e40SJason M. Bills return ret; 4493f7c5e40SJason M. Bills } 4503f7c5e40SJason M. Bills if (deviceHashes.size() < index) 4513f7c5e40SJason M. Bills { 4523f7c5e40SJason M. Bills return IPMI_CC_INVALID_FIELD_REQUEST; 4533f7c5e40SJason M. Bills } 4543f7c5e40SJason M. Bills auto device = deviceHashes.begin() + index; 4553f7c5e40SJason M. Bills uint8_t& bus = device->second.first; 4563f7c5e40SJason M. Bills uint8_t& address = device->second.second; 4573f7c5e40SJason M. Bills 4583f7c5e40SJason M. Bills ManagedObjectType frus; 4593f7c5e40SJason M. Bills 46015419dd5SVernon Mauery std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus(); 46115419dd5SVernon Mauery sdbusplus::message::message getObjects = dbus->new_method_call( 4623f7c5e40SJason M. Bills fruDeviceServiceName, "/", "org.freedesktop.DBus.ObjectManager", 4633f7c5e40SJason M. Bills "GetManagedObjects"); 4643f7c5e40SJason M. Bills try 4653f7c5e40SJason M. Bills { 46615419dd5SVernon Mauery sdbusplus::message::message resp = dbus->call(getObjects); 4673f7c5e40SJason M. Bills resp.read(frus); 4683f7c5e40SJason M. Bills } 4693f7c5e40SJason M. Bills catch (sdbusplus::exception_t&) 4703f7c5e40SJason M. Bills { 4713f7c5e40SJason M. Bills return IPMI_CC_RESPONSE_ERROR; 4723f7c5e40SJason M. Bills } 4733f7c5e40SJason M. Bills boost::container::flat_map<std::string, DbusVariant>* fruData = nullptr; 4743f7c5e40SJason M. Bills auto fru = 4753f7c5e40SJason M. Bills std::find_if(frus.begin(), frus.end(), 4763f7c5e40SJason M. Bills [bus, address, &fruData](ManagedEntry& entry) { 4773f7c5e40SJason M. Bills auto findFruDevice = 4783f7c5e40SJason M. Bills entry.second.find("xyz.openbmc_project.FruDevice"); 4793f7c5e40SJason M. Bills if (findFruDevice == entry.second.end()) 4803f7c5e40SJason M. Bills { 4813f7c5e40SJason M. Bills return false; 4823f7c5e40SJason M. Bills } 4833f7c5e40SJason M. Bills fruData = &(findFruDevice->second); 4843f7c5e40SJason M. Bills auto findBus = findFruDevice->second.find("BUS"); 4853f7c5e40SJason M. Bills auto findAddress = 4863f7c5e40SJason M. Bills findFruDevice->second.find("ADDRESS"); 4873f7c5e40SJason M. Bills if (findBus == findFruDevice->second.end() || 4883f7c5e40SJason M. Bills findAddress == findFruDevice->second.end()) 4893f7c5e40SJason M. Bills { 4903f7c5e40SJason M. Bills return false; 4913f7c5e40SJason M. Bills } 4928166c8d7SVernon Mauery if (std::get<uint32_t>(findBus->second) != bus) 4933f7c5e40SJason M. Bills { 4943f7c5e40SJason M. Bills return false; 4953f7c5e40SJason M. Bills } 4968166c8d7SVernon Mauery if (std::get<uint32_t>(findAddress->second) != address) 4973f7c5e40SJason M. Bills { 4983f7c5e40SJason M. Bills return false; 4993f7c5e40SJason M. Bills } 5003f7c5e40SJason M. Bills return true; 5013f7c5e40SJason M. Bills }); 5023f7c5e40SJason M. Bills if (fru == frus.end()) 5033f7c5e40SJason M. Bills { 5043f7c5e40SJason M. Bills return IPMI_CC_RESPONSE_ERROR; 5053f7c5e40SJason M. Bills } 5063f7c5e40SJason M. Bills std::string name; 5073f7c5e40SJason M. Bills auto findProductName = fruData->find("BOARD_PRODUCT_NAME"); 5083f7c5e40SJason M. Bills auto findBoardName = fruData->find("PRODUCT_PRODUCT_NAME"); 5093f7c5e40SJason M. Bills if (findProductName != fruData->end()) 5103f7c5e40SJason M. Bills { 5118166c8d7SVernon Mauery name = std::get<std::string>(findProductName->second); 5123f7c5e40SJason M. Bills } 5133f7c5e40SJason M. Bills else if (findBoardName != fruData->end()) 5143f7c5e40SJason M. Bills { 5158166c8d7SVernon Mauery name = std::get<std::string>(findBoardName->second); 5163f7c5e40SJason M. Bills } 5173f7c5e40SJason M. Bills else 5183f7c5e40SJason M. Bills { 5193f7c5e40SJason M. Bills name = "UNKNOWN"; 5203f7c5e40SJason M. Bills } 5213f7c5e40SJason M. Bills if (name.size() > maxFruSdrNameSize) 5223f7c5e40SJason M. Bills { 5233f7c5e40SJason M. Bills name = name.substr(0, maxFruSdrNameSize); 5243f7c5e40SJason M. Bills } 5253f7c5e40SJason M. Bills size_t sizeDiff = maxFruSdrNameSize - name.size(); 5263f7c5e40SJason M. Bills 5273f7c5e40SJason M. Bills resp.header.record_id_lsb = 0x0; // calling code is to implement these 5283f7c5e40SJason M. Bills resp.header.record_id_msb = 0x0; 5293f7c5e40SJason M. Bills resp.header.sdr_version = ipmiSdrVersion; 5303f7c5e40SJason M. Bills resp.header.record_type = 0x11; // FRU Device Locator 5313f7c5e40SJason M. Bills resp.header.record_length = sizeof(resp.body) + sizeof(resp.key) - sizeDiff; 5323f7c5e40SJason M. Bills resp.key.deviceAddress = 0x20; 5333f7c5e40SJason M. Bills resp.key.fruID = device->first; 5343f7c5e40SJason M. Bills resp.key.accessLun = 0x80; // logical / physical fru device 5353f7c5e40SJason M. Bills resp.key.channelNumber = 0x0; 5363f7c5e40SJason M. Bills resp.body.reserved = 0x0; 5373f7c5e40SJason M. Bills resp.body.deviceType = 0x10; 5384f86d1f2SJames Feist resp.body.deviceTypeModifier = 0x0; 5393f7c5e40SJason M. Bills resp.body.entityID = 0x0; 5403f7c5e40SJason M. Bills resp.body.entityInstance = 0x1; 5413f7c5e40SJason M. Bills resp.body.oem = 0x0; 5423f7c5e40SJason M. Bills resp.body.deviceIDLen = name.size(); 5433f7c5e40SJason M. Bills name.copy(resp.body.deviceID, name.size()); 5443f7c5e40SJason M. Bills 5453f7c5e40SJason M. Bills return IPMI_CC_OK; 5463f7c5e40SJason M. Bills } 547e2d1aee3SJason M. Bills 5481d4d54ddSJason M. Bills static bool getSELLogFiles(std::vector<std::filesystem::path>& selLogFiles) 549c04e2e70SJason M. Bills { 5501d4d54ddSJason M. Bills // Loop through the directory looking for ipmi_sel log files 5511d4d54ddSJason M. Bills for (const std::filesystem::directory_entry& dirEnt : 5521d4d54ddSJason M. Bills std::filesystem::directory_iterator(intel_oem::ipmi::sel::selLogDir)) 553c04e2e70SJason M. Bills { 5541d4d54ddSJason M. Bills std::string filename = dirEnt.path().filename(); 5551d4d54ddSJason M. Bills if (boost::starts_with(filename, intel_oem::ipmi::sel::selLogFilename)) 5561d4d54ddSJason M. Bills { 5571d4d54ddSJason M. Bills // If we find an ipmi_sel log file, save the path 5581d4d54ddSJason M. Bills selLogFiles.emplace_back(intel_oem::ipmi::sel::selLogDir / 5591d4d54ddSJason M. Bills filename); 560c04e2e70SJason M. Bills } 561c04e2e70SJason M. Bills } 5621d4d54ddSJason M. Bills // As the log files rotate, they are appended with a ".#" that is higher for 5631d4d54ddSJason M. Bills // the older logs. Since we don't expect more than 10 log files, we 5641d4d54ddSJason M. Bills // can just sort the list to get them in order from newest to oldest 5651d4d54ddSJason M. Bills std::sort(selLogFiles.begin(), selLogFiles.end()); 566c04e2e70SJason M. Bills 5671d4d54ddSJason M. Bills return !selLogFiles.empty(); 568c04e2e70SJason M. Bills } 569c04e2e70SJason M. Bills 5701d4d54ddSJason M. Bills static int countSELEntries() 5711d4d54ddSJason M. Bills { 5721d4d54ddSJason M. Bills // Get the list of ipmi_sel log files 5731d4d54ddSJason M. Bills std::vector<std::filesystem::path> selLogFiles; 5741d4d54ddSJason M. Bills if (!getSELLogFiles(selLogFiles)) 5751d4d54ddSJason M. Bills { 5761d4d54ddSJason M. Bills return 0; 5771d4d54ddSJason M. Bills } 5781d4d54ddSJason M. Bills int numSELEntries = 0; 5791d4d54ddSJason M. Bills // Loop through each log file and count the number of logs 5801d4d54ddSJason M. Bills for (const std::filesystem::path& file : selLogFiles) 5811d4d54ddSJason M. Bills { 5821d4d54ddSJason M. Bills std::ifstream logStream(file); 5831d4d54ddSJason M. Bills if (!logStream.is_open()) 5841d4d54ddSJason M. Bills { 5851d4d54ddSJason M. Bills continue; 5861d4d54ddSJason M. Bills } 5871d4d54ddSJason M. Bills 5881d4d54ddSJason M. Bills std::string line; 5891d4d54ddSJason M. Bills while (std::getline(logStream, line)) 5901d4d54ddSJason M. Bills { 5911d4d54ddSJason M. Bills numSELEntries++; 5921d4d54ddSJason M. Bills } 5931d4d54ddSJason M. Bills } 5941d4d54ddSJason M. Bills return numSELEntries; 5951d4d54ddSJason M. Bills } 5961d4d54ddSJason M. Bills 5971d4d54ddSJason M. Bills static bool findSELEntry(const int recordID, 5981d4d54ddSJason M. Bills const std::vector<std::filesystem::path> selLogFiles, 5991d4d54ddSJason M. Bills std::string& entry) 6001d4d54ddSJason M. Bills { 6011d4d54ddSJason M. Bills // Record ID is the first entry field following the timestamp. It is 6021d4d54ddSJason M. Bills // preceded by a space and followed by a comma 6031d4d54ddSJason M. Bills std::string search = " " + std::to_string(recordID) + ","; 6041d4d54ddSJason M. Bills 6051d4d54ddSJason M. Bills // Loop through the ipmi_sel log entries 6061d4d54ddSJason M. Bills for (const std::filesystem::path& file : selLogFiles) 6071d4d54ddSJason M. Bills { 6081d4d54ddSJason M. Bills std::ifstream logStream(file); 6091d4d54ddSJason M. Bills if (!logStream.is_open()) 6101d4d54ddSJason M. Bills { 6111d4d54ddSJason M. Bills continue; 6121d4d54ddSJason M. Bills } 6131d4d54ddSJason M. Bills 6141d4d54ddSJason M. Bills while (std::getline(logStream, entry)) 6151d4d54ddSJason M. Bills { 6161d4d54ddSJason M. Bills // Check if the record ID matches 6171d4d54ddSJason M. Bills if (entry.find(search) != std::string::npos) 6181d4d54ddSJason M. Bills { 6191d4d54ddSJason M. Bills return true; 6201d4d54ddSJason M. Bills } 6211d4d54ddSJason M. Bills } 6221d4d54ddSJason M. Bills } 6231d4d54ddSJason M. Bills return false; 6241d4d54ddSJason M. Bills } 6251d4d54ddSJason M. Bills 6261d4d54ddSJason M. Bills static uint16_t 6271d4d54ddSJason M. Bills getNextRecordID(const uint16_t recordID, 6281d4d54ddSJason M. Bills const std::vector<std::filesystem::path> selLogFiles) 6291d4d54ddSJason M. Bills { 6301d4d54ddSJason M. Bills uint16_t nextRecordID = recordID + 1; 6311d4d54ddSJason M. Bills std::string entry; 6321d4d54ddSJason M. Bills if (findSELEntry(nextRecordID, selLogFiles, entry)) 6331d4d54ddSJason M. Bills { 6341d4d54ddSJason M. Bills return nextRecordID; 6351d4d54ddSJason M. Bills } 6361d4d54ddSJason M. Bills else 6371d4d54ddSJason M. Bills { 6381d4d54ddSJason M. Bills return ipmi::sel::lastEntry; 6391d4d54ddSJason M. Bills } 640c04e2e70SJason M. Bills } 641c04e2e70SJason M. Bills 642c04e2e70SJason M. Bills static int fromHexStr(const std::string hexStr, std::vector<uint8_t>& data) 643c04e2e70SJason M. Bills { 644c04e2e70SJason M. Bills for (unsigned int i = 0; i < hexStr.size(); i += 2) 645c04e2e70SJason M. Bills { 646c04e2e70SJason M. Bills try 647c04e2e70SJason M. Bills { 648c04e2e70SJason M. Bills data.push_back(static_cast<uint8_t>( 649c04e2e70SJason M. Bills std::stoul(hexStr.substr(i, 2), nullptr, 16))); 650c04e2e70SJason M. Bills } 651c04e2e70SJason M. Bills catch (std::invalid_argument& e) 652c04e2e70SJason M. Bills { 653c04e2e70SJason M. Bills phosphor::logging::log<phosphor::logging::level::ERR>(e.what()); 654c04e2e70SJason M. Bills return -1; 655c04e2e70SJason M. Bills } 656c04e2e70SJason M. Bills catch (std::out_of_range& e) 657c04e2e70SJason M. Bills { 658c04e2e70SJason M. Bills phosphor::logging::log<phosphor::logging::level::ERR>(e.what()); 659c04e2e70SJason M. Bills return -1; 660c04e2e70SJason M. Bills } 661c04e2e70SJason M. Bills } 662c04e2e70SJason M. Bills return 0; 663c04e2e70SJason M. Bills } 664c04e2e70SJason M. Bills 6651d4d54ddSJason M. Bills ipmi::RspType<uint8_t, // SEL version 6661d4d54ddSJason M. Bills uint16_t, // SEL entry count 6671d4d54ddSJason M. Bills uint16_t, // free space 6681d4d54ddSJason M. Bills uint32_t, // last add timestamp 6691d4d54ddSJason M. Bills uint32_t, // last erase timestamp 6701d4d54ddSJason M. Bills uint8_t> // operation support 6711d4d54ddSJason M. Bills ipmiStorageGetSELInfo() 672c04e2e70SJason M. Bills { 6731d4d54ddSJason M. Bills constexpr uint8_t selVersion = ipmi::sel::selVersion; 6741d4d54ddSJason M. Bills uint16_t entries = countSELEntries(); 6751d4d54ddSJason M. Bills uint32_t addTimeStamp = intel_oem::ipmi::sel::getFileTimestamp( 6761d4d54ddSJason M. Bills intel_oem::ipmi::sel::selLogDir / intel_oem::ipmi::sel::selLogFilename); 6771d4d54ddSJason M. Bills uint32_t eraseTimeStamp = intel_oem::ipmi::sel::erase_time::get(); 6781d4d54ddSJason M. Bills constexpr uint8_t operationSupport = 6791d4d54ddSJason M. Bills intel_oem::ipmi::sel::selOperationSupport; 6801d4d54ddSJason M. Bills constexpr uint16_t freeSpace = 6811d4d54ddSJason M. Bills 0xffff; // Spec indicates that more than 64kB is free 682c04e2e70SJason M. Bills 6831d4d54ddSJason M. Bills return ipmi::responseSuccess(selVersion, entries, freeSpace, addTimeStamp, 6841d4d54ddSJason M. Bills eraseTimeStamp, operationSupport); 685c04e2e70SJason M. Bills } 686c04e2e70SJason M. Bills 6871d4d54ddSJason M. Bills using systemEventType = std::tuple< 6881d4d54ddSJason M. Bills uint32_t, // Timestamp 6891d4d54ddSJason M. Bills uint16_t, // Generator ID 6901d4d54ddSJason M. Bills uint8_t, // EvM Rev 6911d4d54ddSJason M. Bills uint8_t, // Sensor Type 6921d4d54ddSJason M. Bills uint8_t, // Sensor Number 6931d4d54ddSJason M. Bills uint7_t, // Event Type 6941d4d54ddSJason M. Bills bool, // Event Direction 6951d4d54ddSJason M. Bills std::array<uint8_t, intel_oem::ipmi::sel::systemEventSize>>; // Event Data 6961d4d54ddSJason M. Bills using oemTsEventType = std::tuple< 6971d4d54ddSJason M. Bills uint32_t, // Timestamp 6981d4d54ddSJason M. Bills std::array<uint8_t, intel_oem::ipmi::sel::oemTsEventSize>>; // Event Data 6991d4d54ddSJason M. Bills using oemEventType = 7001d4d54ddSJason M. Bills std::array<uint8_t, intel_oem::ipmi::sel::oemEventSize>; // Event Data 7011d4d54ddSJason M. Bills 7021d4d54ddSJason M. Bills ipmi::RspType<uint16_t, // Next Record ID 7031d4d54ddSJason M. Bills uint16_t, // Record ID 7041d4d54ddSJason M. Bills uint8_t, // Record Type 7051d4d54ddSJason M. Bills std::variant<systemEventType, oemTsEventType, 7061d4d54ddSJason M. Bills oemEventType>> // Record Content 7071d4d54ddSJason M. Bills ipmiStorageGetSELEntry(uint16_t reservationID, uint16_t targetID, 7081d4d54ddSJason M. Bills uint8_t offset, uint8_t size) 709c04e2e70SJason M. Bills { 7101d4d54ddSJason M. Bills // Only support getting the entire SEL record. If a partial size or non-zero 7111d4d54ddSJason M. Bills // offset is requested, return an error 7121d4d54ddSJason M. Bills if (offset != 0 || size != ipmi::sel::entireRecord) 713c04e2e70SJason M. Bills { 7141d4d54ddSJason M. Bills return ipmi::responseRetBytesUnavailable(); 715c04e2e70SJason M. Bills } 716c04e2e70SJason M. Bills 7171d4d54ddSJason M. Bills // Check the reservation ID if one is provided or required (only if the 7181d4d54ddSJason M. Bills // offset is non-zero) 7191d4d54ddSJason M. Bills if (reservationID != 0 || offset != 0) 720c04e2e70SJason M. Bills { 7211d4d54ddSJason M. Bills if (!checkSELReservation(reservationID)) 722c04e2e70SJason M. Bills { 7231d4d54ddSJason M. Bills return ipmi::responseInvalidReservationId(); 724c04e2e70SJason M. Bills } 725c04e2e70SJason M. Bills } 726c04e2e70SJason M. Bills 7271d4d54ddSJason M. Bills // Get the ipmi_sel log files 7281d4d54ddSJason M. Bills std::vector<std::filesystem::path> selLogFiles; 7291d4d54ddSJason M. Bills if (!getSELLogFiles(selLogFiles)) 730c04e2e70SJason M. Bills { 7311d4d54ddSJason M. Bills return ipmi::responseSensorInvalid(); 732c04e2e70SJason M. Bills } 733c04e2e70SJason M. Bills 7341d4d54ddSJason M. Bills std::string targetEntry; 735c04e2e70SJason M. Bills 736c04e2e70SJason M. Bills if (targetID == ipmi::sel::firstEntry) 737c04e2e70SJason M. Bills { 7381d4d54ddSJason M. Bills // The first entry will be at the top of the oldest log file 7391d4d54ddSJason M. Bills std::ifstream logStream(selLogFiles.back()); 7401d4d54ddSJason M. Bills if (!logStream.is_open()) 741c04e2e70SJason M. Bills { 7421d4d54ddSJason M. Bills return ipmi::responseUnspecifiedError(); 743c04e2e70SJason M. Bills } 7441d4d54ddSJason M. Bills 7451d4d54ddSJason M. Bills if (!std::getline(logStream, targetEntry)) 7461d4d54ddSJason M. Bills { 7471d4d54ddSJason M. Bills return ipmi::responseUnspecifiedError(); 748c04e2e70SJason M. Bills } 749c04e2e70SJason M. Bills } 750c04e2e70SJason M. Bills else if (targetID == ipmi::sel::lastEntry) 751c04e2e70SJason M. Bills { 7521d4d54ddSJason M. Bills // The last entry will be at the bottom of the newest log file 7531d4d54ddSJason M. Bills std::ifstream logStream(selLogFiles.front()); 7541d4d54ddSJason M. Bills if (!logStream.is_open()) 755c04e2e70SJason M. Bills { 7561d4d54ddSJason M. Bills return ipmi::responseUnspecifiedError(); 757c04e2e70SJason M. Bills } 758c04e2e70SJason M. Bills 7591d4d54ddSJason M. Bills std::string line; 7601d4d54ddSJason M. Bills while (std::getline(logStream, line)) 761c04e2e70SJason M. Bills { 7621d4d54ddSJason M. Bills targetEntry = line; 763c04e2e70SJason M. Bills } 764c04e2e70SJason M. Bills } 765c04e2e70SJason M. Bills else 766c04e2e70SJason M. Bills { 7671d4d54ddSJason M. Bills if (!findSELEntry(targetID, selLogFiles, targetEntry)) 768c04e2e70SJason M. Bills { 7691d4d54ddSJason M. Bills return ipmi::responseSensorInvalid(); 7701d4d54ddSJason M. Bills } 771c04e2e70SJason M. Bills } 772c04e2e70SJason M. Bills 77352aaa7d5SJason M. Bills // The format of the ipmi_sel message is "<Timestamp> 77452aaa7d5SJason M. Bills // <ID>,<Type>,<EventData>,[<Generator ID>,<Path>,<Direction>]". 77552aaa7d5SJason M. Bills // First get the Timestamp 77652aaa7d5SJason M. Bills size_t space = targetEntry.find_first_of(" "); 77752aaa7d5SJason M. Bills if (space == std::string::npos) 77852aaa7d5SJason M. Bills { 77952aaa7d5SJason M. Bills return ipmi::responseUnspecifiedError(); 78052aaa7d5SJason M. Bills } 78152aaa7d5SJason M. Bills std::string entryTimestamp = targetEntry.substr(0, space); 78252aaa7d5SJason M. Bills // Then get the log contents 78352aaa7d5SJason M. Bills size_t entryStart = targetEntry.find_first_not_of(" ", space); 78452aaa7d5SJason M. Bills if (entryStart == std::string::npos) 78552aaa7d5SJason M. Bills { 78652aaa7d5SJason M. Bills return ipmi::responseUnspecifiedError(); 78752aaa7d5SJason M. Bills } 78852aaa7d5SJason M. Bills std::string_view entry(targetEntry); 78952aaa7d5SJason M. Bills entry.remove_prefix(entryStart); 79052aaa7d5SJason M. Bills // Use split to separate the entry into its fields 7911d4d54ddSJason M. Bills std::vector<std::string> targetEntryFields; 79252aaa7d5SJason M. Bills boost::split(targetEntryFields, entry, boost::is_any_of(","), 7931d4d54ddSJason M. Bills boost::token_compress_on); 79452aaa7d5SJason M. Bills if (targetEntryFields.size() < 3) 7951d4d54ddSJason M. Bills { 7961d4d54ddSJason M. Bills return ipmi::responseUnspecifiedError(); 797c04e2e70SJason M. Bills } 7981a2fbddfSJason M. Bills std::string& recordIDStr = targetEntryFields[0]; 7991a2fbddfSJason M. Bills std::string& recordTypeStr = targetEntryFields[1]; 8001a2fbddfSJason M. Bills std::string& eventDataStr = targetEntryFields[2]; 801c04e2e70SJason M. Bills 8021a2fbddfSJason M. Bills uint16_t recordID; 8031a2fbddfSJason M. Bills uint8_t recordType; 8041a2fbddfSJason M. Bills try 8051a2fbddfSJason M. Bills { 8061a2fbddfSJason M. Bills recordID = std::stoul(recordIDStr); 8071a2fbddfSJason M. Bills recordType = std::stoul(recordTypeStr, nullptr, 16); 8081a2fbddfSJason M. Bills } 8091a2fbddfSJason M. Bills catch (const std::invalid_argument&) 8101a2fbddfSJason M. Bills { 8111a2fbddfSJason M. Bills return ipmi::responseUnspecifiedError(); 8121a2fbddfSJason M. Bills } 8131d4d54ddSJason M. Bills uint16_t nextRecordID = getNextRecordID(recordID, selLogFiles); 8141d4d54ddSJason M. Bills std::vector<uint8_t> eventDataBytes; 8151a2fbddfSJason M. Bills if (fromHexStr(eventDataStr, eventDataBytes) < 0) 8161d4d54ddSJason M. Bills { 8171d4d54ddSJason M. Bills return ipmi::responseUnspecifiedError(); 8181d4d54ddSJason M. Bills } 8191d4d54ddSJason M. Bills 8201d4d54ddSJason M. Bills if (recordType == intel_oem::ipmi::sel::systemEvent) 8211d4d54ddSJason M. Bills { 8221d4d54ddSJason M. Bills // Get the timestamp 8231d4d54ddSJason M. Bills std::tm timeStruct = {}; 82452aaa7d5SJason M. Bills std::istringstream entryStream(entryTimestamp); 8251d4d54ddSJason M. Bills 8261d4d54ddSJason M. Bills uint32_t timestamp = ipmi::sel::invalidTimeStamp; 8271d4d54ddSJason M. Bills if (entryStream >> std::get_time(&timeStruct, "%Y-%m-%dT%H:%M:%S")) 8281d4d54ddSJason M. Bills { 8291d4d54ddSJason M. Bills timestamp = std::mktime(&timeStruct); 8301d4d54ddSJason M. Bills } 8311d4d54ddSJason M. Bills 8321d4d54ddSJason M. Bills // Set the event message revision 8331d4d54ddSJason M. Bills uint8_t evmRev = intel_oem::ipmi::sel::eventMsgRev; 8341d4d54ddSJason M. Bills 8351a2fbddfSJason M. Bills uint16_t generatorID = 0; 8361a2fbddfSJason M. Bills uint8_t sensorType = 0; 8371a2fbddfSJason M. Bills uint8_t sensorNum = 0xFF; 8381a2fbddfSJason M. Bills uint7_t eventType = 0; 8391a2fbddfSJason M. Bills bool eventDir = 0; 8401a2fbddfSJason M. Bills // System type events should have six fields 8411a2fbddfSJason M. Bills if (targetEntryFields.size() >= 6) 8421a2fbddfSJason M. Bills { 8431a2fbddfSJason M. Bills std::string& generatorIDStr = targetEntryFields[3]; 8441a2fbddfSJason M. Bills std::string& sensorPath = targetEntryFields[4]; 8451a2fbddfSJason M. Bills std::string& eventDirStr = targetEntryFields[5]; 8461a2fbddfSJason M. Bills 8471a2fbddfSJason M. Bills // Get the generator ID 8481a2fbddfSJason M. Bills try 8491a2fbddfSJason M. Bills { 8501a2fbddfSJason M. Bills generatorID = std::stoul(generatorIDStr, nullptr, 16); 8511a2fbddfSJason M. Bills } 8521a2fbddfSJason M. Bills catch (const std::invalid_argument&) 8531a2fbddfSJason M. Bills { 8541a2fbddfSJason M. Bills std::cerr << "Invalid Generator ID\n"; 8551a2fbddfSJason M. Bills } 8561a2fbddfSJason M. Bills 8571d4d54ddSJason M. Bills // Get the sensor type, sensor number, and event type for the sensor 8581a2fbddfSJason M. Bills sensorType = getSensorTypeFromPath(sensorPath); 8591a2fbddfSJason M. Bills sensorNum = getSensorNumberFromPath(sensorPath); 8601a2fbddfSJason M. Bills eventType = getSensorEventTypeFromPath(sensorPath); 8611d4d54ddSJason M. Bills 8621d4d54ddSJason M. Bills // Get the event direction 8631a2fbddfSJason M. Bills try 8641a2fbddfSJason M. Bills { 8651a2fbddfSJason M. Bills eventDir = std::stoul(eventDirStr) ? 0 : 1; 8661a2fbddfSJason M. Bills } 8671a2fbddfSJason M. Bills catch (const std::invalid_argument&) 8681a2fbddfSJason M. Bills { 8691a2fbddfSJason M. Bills std::cerr << "Invalid Event Direction\n"; 8701a2fbddfSJason M. Bills } 8711a2fbddfSJason M. Bills } 8721d4d54ddSJason M. Bills 8731d4d54ddSJason M. Bills // Only keep the eventData bytes that fit in the record 8741d4d54ddSJason M. Bills std::array<uint8_t, intel_oem::ipmi::sel::systemEventSize> eventData{}; 8751d4d54ddSJason M. Bills std::copy_n(eventDataBytes.begin(), 8761d4d54ddSJason M. Bills std::min(eventDataBytes.size(), eventData.size()), 8771d4d54ddSJason M. Bills eventData.begin()); 8781d4d54ddSJason M. Bills 8791d4d54ddSJason M. Bills return ipmi::responseSuccess( 8801d4d54ddSJason M. Bills nextRecordID, recordID, recordType, 8811d4d54ddSJason M. Bills systemEventType{timestamp, generatorID, evmRev, sensorType, 8821d4d54ddSJason M. Bills sensorNum, eventType, eventDir, eventData}); 8831d4d54ddSJason M. Bills } 8841d4d54ddSJason M. Bills else if (recordType >= intel_oem::ipmi::sel::oemTsEventFirst && 8851d4d54ddSJason M. Bills recordType <= intel_oem::ipmi::sel::oemTsEventLast) 8861d4d54ddSJason M. Bills { 8871d4d54ddSJason M. Bills // Get the timestamp 8881d4d54ddSJason M. Bills std::tm timeStruct = {}; 88952aaa7d5SJason M. Bills std::istringstream entryStream(entryTimestamp); 8901d4d54ddSJason M. Bills 8911d4d54ddSJason M. Bills uint32_t timestamp = ipmi::sel::invalidTimeStamp; 8921d4d54ddSJason M. Bills if (entryStream >> std::get_time(&timeStruct, "%Y-%m-%dT%H:%M:%S")) 8931d4d54ddSJason M. Bills { 8941d4d54ddSJason M. Bills timestamp = std::mktime(&timeStruct); 8951d4d54ddSJason M. Bills } 8961d4d54ddSJason M. Bills 8971d4d54ddSJason M. Bills // Only keep the bytes that fit in the record 8981d4d54ddSJason M. Bills std::array<uint8_t, intel_oem::ipmi::sel::oemTsEventSize> eventData{}; 8991d4d54ddSJason M. Bills std::copy_n(eventDataBytes.begin(), 9001d4d54ddSJason M. Bills std::min(eventDataBytes.size(), eventData.size()), 9011d4d54ddSJason M. Bills eventData.begin()); 9021d4d54ddSJason M. Bills 9031d4d54ddSJason M. Bills return ipmi::responseSuccess(nextRecordID, recordID, recordType, 9041d4d54ddSJason M. Bills oemTsEventType{timestamp, eventData}); 9051d4d54ddSJason M. Bills } 9061d4d54ddSJason M. Bills else if (recordType >= intel_oem::ipmi::sel::oemEventFirst && 9071d4d54ddSJason M. Bills recordType <= intel_oem::ipmi::sel::oemEventLast) 9081d4d54ddSJason M. Bills { 9091d4d54ddSJason M. Bills // Only keep the bytes that fit in the record 9101d4d54ddSJason M. Bills std::array<uint8_t, intel_oem::ipmi::sel::oemEventSize> eventData{}; 9111d4d54ddSJason M. Bills std::copy_n(eventDataBytes.begin(), 9121d4d54ddSJason M. Bills std::min(eventDataBytes.size(), eventData.size()), 9131d4d54ddSJason M. Bills eventData.begin()); 9141d4d54ddSJason M. Bills 9151d4d54ddSJason M. Bills return ipmi::responseSuccess(nextRecordID, recordID, recordType, 9161d4d54ddSJason M. Bills eventData); 9171d4d54ddSJason M. Bills } 9181d4d54ddSJason M. Bills 9191d4d54ddSJason M. Bills return ipmi::responseUnspecifiedError(); 920c04e2e70SJason M. Bills } 921c04e2e70SJason M. Bills 9226dd8f047SJason M. Bills ipmi::RspType<uint16_t> ipmiStorageAddSELEntry( 9236dd8f047SJason M. Bills uint16_t recordID, uint8_t recordType, uint32_t timestamp, 9246dd8f047SJason M. Bills uint16_t generatorID, uint8_t evmRev, uint8_t sensorType, uint8_t sensorNum, 9256dd8f047SJason M. Bills uint8_t eventType, uint8_t eventData1, uint8_t eventData2, 9266dd8f047SJason M. Bills uint8_t eventData3) 927c04e2e70SJason M. Bills { 928c04e2e70SJason M. Bills // Per the IPMI spec, need to cancel any reservation when a SEL entry is 929c04e2e70SJason M. Bills // added 930c04e2e70SJason M. Bills cancelSELReservation(); 931c04e2e70SJason M. Bills 9326dd8f047SJason M. Bills // Send this request to the Redfish hooks to log it as a Redfish message 9336dd8f047SJason M. Bills // instead. There is no need to add it to the SEL, so just return success. 9346dd8f047SJason M. Bills intel_oem::ipmi::sel::checkRedfishHooks( 9356dd8f047SJason M. Bills recordID, recordType, timestamp, generatorID, evmRev, sensorType, 9366dd8f047SJason M. Bills sensorNum, eventType, eventData1, eventData2, eventData3); 93799b78ec8SJason M. Bills 9386dd8f047SJason M. Bills uint16_t responseID = 0xFFFF; 9396dd8f047SJason M. Bills return ipmi::responseSuccess(responseID); 940c04e2e70SJason M. Bills } 941c04e2e70SJason M. Bills 9421d4d54ddSJason M. Bills ipmi::RspType<uint8_t> ipmiStorageClearSEL(ipmi::Context::ptr ctx, 9431d4d54ddSJason M. Bills uint16_t reservationID, 9441d4d54ddSJason M. Bills const std::array<uint8_t, 3>& clr, 9451d4d54ddSJason M. Bills uint8_t eraseOperation) 946c04e2e70SJason M. Bills { 9471d4d54ddSJason M. Bills if (!checkSELReservation(reservationID)) 948c04e2e70SJason M. Bills { 9491d4d54ddSJason M. Bills return ipmi::responseInvalidReservationId(); 950c04e2e70SJason M. Bills } 951c04e2e70SJason M. Bills 9521d4d54ddSJason M. Bills static constexpr std::array<uint8_t, 3> clrExpected = {'C', 'L', 'R'}; 9531d4d54ddSJason M. Bills if (clr != clrExpected) 954c04e2e70SJason M. Bills { 9551d4d54ddSJason M. Bills return ipmi::responseInvalidFieldRequest(); 956c04e2e70SJason M. Bills } 957c04e2e70SJason M. Bills 9581d4d54ddSJason M. Bills // Erasure status cannot be fetched, so always return erasure status as 9591d4d54ddSJason M. Bills // `erase completed`. 9601d4d54ddSJason M. Bills if (eraseOperation == ipmi::sel::getEraseStatus) 961c04e2e70SJason M. Bills { 9621d4d54ddSJason M. Bills return ipmi::responseSuccess(ipmi::sel::eraseComplete); 963c04e2e70SJason M. Bills } 964c04e2e70SJason M. Bills 9651d4d54ddSJason M. Bills // Check that initiate erase is correct 9661d4d54ddSJason M. Bills if (eraseOperation != ipmi::sel::initiateErase) 9671d4d54ddSJason M. Bills { 9681d4d54ddSJason M. Bills return ipmi::responseInvalidFieldRequest(); 9691d4d54ddSJason M. Bills } 9701d4d54ddSJason M. Bills 9711d4d54ddSJason M. Bills // Per the IPMI spec, need to cancel any reservation when the SEL is 9721d4d54ddSJason M. Bills // cleared 973c04e2e70SJason M. Bills cancelSELReservation(); 974c04e2e70SJason M. Bills 9757944c307SJason M. Bills // Save the erase time 9767944c307SJason M. Bills intel_oem::ipmi::sel::erase_time::save(); 9777944c307SJason M. Bills 9781d4d54ddSJason M. Bills // Clear the SEL by deleting the log files 9791d4d54ddSJason M. Bills std::vector<std::filesystem::path> selLogFiles; 9801d4d54ddSJason M. Bills if (getSELLogFiles(selLogFiles)) 981c04e2e70SJason M. Bills { 9821d4d54ddSJason M. Bills for (const std::filesystem::path& file : selLogFiles) 9831d4d54ddSJason M. Bills { 9841d4d54ddSJason M. Bills std::error_code ec; 9851d4d54ddSJason M. Bills std::filesystem::remove(file, ec); 986c04e2e70SJason M. Bills } 987c04e2e70SJason M. Bills } 988c04e2e70SJason M. Bills 9891d4d54ddSJason M. Bills // Reload rsyslog so it knows to start new log files 99015419dd5SVernon Mauery std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus(); 99115419dd5SVernon Mauery sdbusplus::message::message rsyslogReload = dbus->new_method_call( 9921d4d54ddSJason M. Bills "org.freedesktop.systemd1", "/org/freedesktop/systemd1", 9931d4d54ddSJason M. Bills "org.freedesktop.systemd1.Manager", "ReloadUnit"); 9941d4d54ddSJason M. Bills rsyslogReload.append("rsyslog.service", "replace"); 9951d4d54ddSJason M. Bills try 9961d4d54ddSJason M. Bills { 99715419dd5SVernon Mauery sdbusplus::message::message reloadResponse = dbus->call(rsyslogReload); 9981d4d54ddSJason M. Bills } 9991d4d54ddSJason M. Bills catch (sdbusplus::exception_t& e) 10001d4d54ddSJason M. Bills { 10011d4d54ddSJason M. Bills phosphor::logging::log<phosphor::logging::level::ERR>(e.what()); 1002c04e2e70SJason M. Bills } 1003c04e2e70SJason M. Bills 10041d4d54ddSJason M. Bills return ipmi::responseSuccess(ipmi::sel::eraseComplete); 10051d4d54ddSJason M. Bills } 10061d4d54ddSJason M. Bills 10071a47462eSJason M. Bills ipmi::RspType<uint32_t> ipmiStorageGetSELTime() 10081a47462eSJason M. Bills { 10091a47462eSJason M. Bills struct timespec selTime = {}; 10101a47462eSJason M. Bills 10111a47462eSJason M. Bills if (clock_gettime(CLOCK_REALTIME, &selTime) < 0) 10121a47462eSJason M. Bills { 10131a47462eSJason M. Bills return ipmi::responseUnspecifiedError(); 10141a47462eSJason M. Bills } 10151a47462eSJason M. Bills 10161a47462eSJason M. Bills return ipmi::responseSuccess(selTime.tv_sec); 10171a47462eSJason M. Bills } 10181a47462eSJason M. Bills 10191d4d54ddSJason M. Bills ipmi::RspType<> ipmiStorageSetSELTime(uint32_t selTime) 1020cac97a53SJason M. Bills { 1021cac97a53SJason M. Bills // Set SEL Time is not supported 10221d4d54ddSJason M. Bills return ipmi::responseInvalidCommand(); 1023cac97a53SJason M. Bills } 1024cac97a53SJason M. Bills 1025e2d1aee3SJason M. Bills void registerStorageFunctions() 1026e2d1aee3SJason M. Bills { 1027e2d1aee3SJason M. Bills // <Get FRU Inventory Area Info> 1028*d33acd6bSjayaprakash Mutyala ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnStorage, 1029*d33acd6bSjayaprakash Mutyala ipmi::storage::cmdGetFruInventoryAreaInfo, 1030*d33acd6bSjayaprakash Mutyala ipmi::Privilege::User, ipmiStorageGetFruInvAreaInfo); 1031c04e2e70SJason M. Bills // <READ FRU Data> 1032e2d1aee3SJason M. Bills ipmiPrintAndRegister( 1033e2d1aee3SJason M. Bills NETFUN_STORAGE, 1034e2d1aee3SJason M. Bills static_cast<ipmi_cmd_t>(IPMINetfnStorageCmds::ipmiCmdReadFRUData), NULL, 1035542498e9SJason M. Bills ipmiStorageReadFRUData, PRIVILEGE_USER); 1036e2d1aee3SJason M. Bills 1037c04e2e70SJason M. Bills // <WRITE FRU Data> 1038e2d1aee3SJason M. Bills ipmiPrintAndRegister( 1039e2d1aee3SJason M. Bills NETFUN_STORAGE, 1040e2d1aee3SJason M. Bills static_cast<ipmi_cmd_t>(IPMINetfnStorageCmds::ipmiCmdWriteFRUData), 1041e2d1aee3SJason M. Bills NULL, ipmiStorageWriteFRUData, PRIVILEGE_OPERATOR); 1042c04e2e70SJason M. Bills 1043c04e2e70SJason M. Bills // <Get SEL Info> 10441d4d54ddSJason M. Bills ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 1045542498e9SJason M. Bills ipmi::storage::cmdGetSelInfo, ipmi::Privilege::User, 1046542498e9SJason M. Bills ipmiStorageGetSELInfo); 1047c04e2e70SJason M. Bills 1048c04e2e70SJason M. Bills // <Get SEL Entry> 10491d4d54ddSJason M. Bills ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 1050542498e9SJason M. Bills ipmi::storage::cmdGetSelEntry, ipmi::Privilege::User, 1051542498e9SJason M. Bills ipmiStorageGetSELEntry); 1052c04e2e70SJason M. Bills 1053c04e2e70SJason M. Bills // <Add SEL Entry> 10546dd8f047SJason M. Bills ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 10556dd8f047SJason M. Bills static_cast<ipmi::Cmd>(ipmi::storage::cmdAddSelEntry), 10566dd8f047SJason M. Bills ipmi::Privilege::Operator, ipmiStorageAddSELEntry); 1057c04e2e70SJason M. Bills 1058c04e2e70SJason M. Bills // <Clear SEL> 10591d4d54ddSJason M. Bills ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 10601d4d54ddSJason M. Bills ipmi::storage::cmdClearSel, ipmi::Privilege::Operator, 10611d4d54ddSJason M. Bills ipmiStorageClearSEL); 1062cac97a53SJason M. Bills 10631a47462eSJason M. Bills // <Get SEL Time> 10641a47462eSJason M. Bills ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 1065542498e9SJason M. Bills ipmi::storage::cmdGetSelTime, ipmi::Privilege::User, 1066542498e9SJason M. Bills ipmiStorageGetSELTime); 10671a47462eSJason M. Bills 1068cac97a53SJason M. Bills // <Set SEL Time> 10691d4d54ddSJason M. Bills ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 10701d4d54ddSJason M. Bills ipmi::storage::cmdSetSelTime, 10711d4d54ddSJason M. Bills ipmi::Privilege::Operator, ipmiStorageSetSELTime); 1072e2d1aee3SJason M. Bills } 10733f7c5e40SJason M. Bills } // namespace storage 10743f7c5e40SJason M. Bills } // namespace ipmi 1075