1 /* 2 // Copyright (c) 2017-2019 Intel Corporation 3 // 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at 7 // 8 // http://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 */ 16 17 #include <boost/algorithm/string.hpp> 18 #include <boost/container/flat_map.hpp> 19 #include <boost/process.hpp> 20 #include <commandutils.hpp> 21 #include <filesystem> 22 #include <iostream> 23 #include <ipmi_to_redfish_hooks.hpp> 24 #include <ipmid/api.hpp> 25 #include <phosphor-ipmi-host/selutility.hpp> 26 #include <phosphor-logging/log.hpp> 27 #include <sdbusplus/message/types.hpp> 28 #include <sdbusplus/timer.hpp> 29 #include <sdrutils.hpp> 30 #include <stdexcept> 31 #include <storagecommands.hpp> 32 #include <string_view> 33 34 namespace intel_oem::ipmi::sel 35 { 36 static const std::filesystem::path selLogDir = "/var/log"; 37 static const std::string selLogFilename = "ipmi_sel"; 38 39 static int getFileTimestamp(const std::filesystem::path& file) 40 { 41 struct stat st; 42 43 if (stat(file.c_str(), &st) >= 0) 44 { 45 return st.st_mtime; 46 } 47 return ::ipmi::sel::invalidTimeStamp; 48 } 49 50 namespace erase_time 51 { 52 static constexpr const char* selEraseTimestamp = "/var/lib/ipmi/sel_erase_time"; 53 54 void save() 55 { 56 // open the file, creating it if necessary 57 int fd = open(selEraseTimestamp, O_WRONLY | O_CREAT | O_CLOEXEC, 0644); 58 if (fd < 0) 59 { 60 std::cerr << "Failed to open file\n"; 61 return; 62 } 63 64 // update the file timestamp to the current time 65 if (futimens(fd, NULL) < 0) 66 { 67 std::cerr << "Failed to update timestamp: " 68 << std::string(strerror(errno)); 69 } 70 close(fd); 71 } 72 73 int get() 74 { 75 return getFileTimestamp(selEraseTimestamp); 76 } 77 } // namespace erase_time 78 } // namespace intel_oem::ipmi::sel 79 80 namespace ipmi 81 { 82 83 namespace storage 84 { 85 86 constexpr static const size_t maxMessageSize = 64; 87 constexpr static const size_t maxFruSdrNameSize = 16; 88 using ManagedObjectType = boost::container::flat_map< 89 sdbusplus::message::object_path, 90 boost::container::flat_map< 91 std::string, boost::container::flat_map<std::string, DbusVariant>>>; 92 using ManagedEntry = std::pair< 93 sdbusplus::message::object_path, 94 boost::container::flat_map< 95 std::string, boost::container::flat_map<std::string, DbusVariant>>>; 96 97 constexpr static const char* fruDeviceServiceName = 98 "xyz.openbmc_project.FruDevice"; 99 constexpr static const size_t cacheTimeoutSeconds = 10; 100 101 // event direction is bit[7] of eventType where 1b = Deassertion event 102 constexpr static const uint8_t deassertionEvent = 0x80; 103 104 static std::vector<uint8_t> fruCache; 105 static uint8_t cacheBus = 0xFF; 106 static uint8_t cacheAddr = 0XFF; 107 108 std::unique_ptr<phosphor::Timer> cacheTimer = nullptr; 109 110 // we unfortunately have to build a map of hashes in case there is a 111 // collision to verify our dev-id 112 boost::container::flat_map<uint8_t, std::pair<uint8_t, uint8_t>> deviceHashes; 113 114 void registerStorageFunctions() __attribute__((constructor)); 115 116 bool writeFru() 117 { 118 std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus(); 119 sdbusplus::message::message writeFru = dbus->new_method_call( 120 fruDeviceServiceName, "/xyz/openbmc_project/FruDevice", 121 "xyz.openbmc_project.FruDeviceManager", "WriteFru"); 122 writeFru.append(cacheBus, cacheAddr, fruCache); 123 try 124 { 125 sdbusplus::message::message writeFruResp = dbus->call(writeFru); 126 } 127 catch (sdbusplus::exception_t&) 128 { 129 // todo: log sel? 130 phosphor::logging::log<phosphor::logging::level::ERR>( 131 "error writing fru"); 132 return false; 133 } 134 return true; 135 } 136 137 void createTimer() 138 { 139 if (cacheTimer == nullptr) 140 { 141 cacheTimer = std::make_unique<phosphor::Timer>(writeFru); 142 } 143 } 144 145 ipmi_ret_t replaceCacheFru(uint8_t devId) 146 { 147 static uint8_t lastDevId = 0xFF; 148 149 bool timerRunning = (cacheTimer != nullptr) && !cacheTimer->isExpired(); 150 if (lastDevId == devId && timerRunning) 151 { 152 return IPMI_CC_OK; // cache already up to date 153 } 154 // if timer is running, stop it and writeFru manually 155 else if (timerRunning) 156 { 157 cacheTimer->stop(); 158 writeFru(); 159 } 160 161 std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus(); 162 sdbusplus::message::message getObjects = dbus->new_method_call( 163 fruDeviceServiceName, "/", "org.freedesktop.DBus.ObjectManager", 164 "GetManagedObjects"); 165 ManagedObjectType frus; 166 try 167 { 168 sdbusplus::message::message resp = dbus->call(getObjects); 169 resp.read(frus); 170 } 171 catch (sdbusplus::exception_t&) 172 { 173 phosphor::logging::log<phosphor::logging::level::ERR>( 174 "replaceCacheFru: error getting managed objects"); 175 return IPMI_CC_RESPONSE_ERROR; 176 } 177 178 deviceHashes.clear(); 179 180 // hash the object paths to create unique device id's. increment on 181 // collision 182 std::hash<std::string> hasher; 183 for (const auto& fru : frus) 184 { 185 auto fruIface = fru.second.find("xyz.openbmc_project.FruDevice"); 186 if (fruIface == fru.second.end()) 187 { 188 continue; 189 } 190 191 auto busFind = fruIface->second.find("BUS"); 192 auto addrFind = fruIface->second.find("ADDRESS"); 193 if (busFind == fruIface->second.end() || 194 addrFind == fruIface->second.end()) 195 { 196 phosphor::logging::log<phosphor::logging::level::INFO>( 197 "fru device missing Bus or Address", 198 phosphor::logging::entry("FRU=%s", fru.first.str.c_str())); 199 continue; 200 } 201 202 uint8_t fruBus = std::get<uint32_t>(busFind->second); 203 uint8_t fruAddr = std::get<uint32_t>(addrFind->second); 204 205 uint8_t fruHash = 0; 206 if (fruBus != 0 || fruAddr != 0) 207 { 208 fruHash = hasher(fru.first.str); 209 // can't be 0xFF based on spec, and 0 is reserved for baseboard 210 if (fruHash == 0 || fruHash == 0xFF) 211 { 212 fruHash = 1; 213 } 214 } 215 std::pair<uint8_t, uint8_t> newDev(fruBus, fruAddr); 216 217 bool emplacePassed = false; 218 while (!emplacePassed) 219 { 220 auto resp = deviceHashes.emplace(fruHash, newDev); 221 emplacePassed = resp.second; 222 if (!emplacePassed) 223 { 224 fruHash++; 225 // can't be 0xFF based on spec, and 0 is reserved for 226 // baseboard 227 if (fruHash == 0XFF) 228 { 229 fruHash = 0x1; 230 } 231 } 232 } 233 } 234 auto deviceFind = deviceHashes.find(devId); 235 if (deviceFind == deviceHashes.end()) 236 { 237 return IPMI_CC_SENSOR_INVALID; 238 } 239 240 fruCache.clear(); 241 sdbusplus::message::message getRawFru = dbus->new_method_call( 242 fruDeviceServiceName, "/xyz/openbmc_project/FruDevice", 243 "xyz.openbmc_project.FruDeviceManager", "GetRawFru"); 244 cacheBus = deviceFind->second.first; 245 cacheAddr = deviceFind->second.second; 246 getRawFru.append(cacheBus, cacheAddr); 247 try 248 { 249 sdbusplus::message::message getRawResp = dbus->call(getRawFru); 250 getRawResp.read(fruCache); 251 } 252 catch (sdbusplus::exception_t&) 253 { 254 lastDevId = 0xFF; 255 cacheBus = 0xFF; 256 cacheAddr = 0xFF; 257 return IPMI_CC_RESPONSE_ERROR; 258 } 259 260 lastDevId = devId; 261 return IPMI_CC_OK; 262 } 263 264 ipmi_ret_t ipmiStorageReadFRUData(ipmi_netfn_t netfn, ipmi_cmd_t cmd, 265 ipmi_request_t request, 266 ipmi_response_t response, 267 ipmi_data_len_t dataLen, 268 ipmi_context_t context) 269 { 270 if (*dataLen != 4) 271 { 272 *dataLen = 0; 273 return IPMI_CC_REQ_DATA_LEN_INVALID; 274 } 275 *dataLen = 0; // default to 0 in case of an error 276 277 auto req = static_cast<GetFRUAreaReq*>(request); 278 279 if (req->countToRead > maxMessageSize - 1) 280 { 281 return IPMI_CC_INVALID_FIELD_REQUEST; 282 } 283 ipmi_ret_t status = replaceCacheFru(req->fruDeviceID); 284 285 if (status != IPMI_CC_OK) 286 { 287 return status; 288 } 289 290 size_t fromFRUByteLen = 0; 291 if (req->countToRead + req->fruInventoryOffset < fruCache.size()) 292 { 293 fromFRUByteLen = req->countToRead; 294 } 295 else if (fruCache.size() > req->fruInventoryOffset) 296 { 297 fromFRUByteLen = fruCache.size() - req->fruInventoryOffset; 298 } 299 size_t padByteLen = req->countToRead - fromFRUByteLen; 300 uint8_t* respPtr = static_cast<uint8_t*>(response); 301 *respPtr = req->countToRead; 302 std::copy(fruCache.begin() + req->fruInventoryOffset, 303 fruCache.begin() + req->fruInventoryOffset + fromFRUByteLen, 304 ++respPtr); 305 // if longer than the fru is requested, fill with 0xFF 306 if (padByteLen) 307 { 308 respPtr += fromFRUByteLen; 309 std::fill(respPtr, respPtr + padByteLen, 0xFF); 310 } 311 *dataLen = fromFRUByteLen + 1; 312 313 return IPMI_CC_OK; 314 } 315 316 ipmi_ret_t ipmiStorageWriteFRUData(ipmi_netfn_t netfn, ipmi_cmd_t cmd, 317 ipmi_request_t request, 318 ipmi_response_t response, 319 ipmi_data_len_t dataLen, 320 ipmi_context_t context) 321 { 322 if (*dataLen < 4 || 323 *dataLen >= 324 0xFF + 3) // count written return is one byte, so limit to one byte 325 // of data after the three request data bytes 326 { 327 *dataLen = 0; 328 return IPMI_CC_REQ_DATA_LEN_INVALID; 329 } 330 331 auto req = static_cast<WriteFRUDataReq*>(request); 332 size_t writeLen = *dataLen - 3; 333 *dataLen = 0; // default to 0 in case of an error 334 335 ipmi_ret_t status = replaceCacheFru(req->fruDeviceID); 336 if (status != IPMI_CC_OK) 337 { 338 return status; 339 } 340 int lastWriteAddr = req->fruInventoryOffset + writeLen; 341 if (fruCache.size() < lastWriteAddr) 342 { 343 fruCache.resize(req->fruInventoryOffset + writeLen); 344 } 345 346 std::copy(req->data, req->data + writeLen, 347 fruCache.begin() + req->fruInventoryOffset); 348 349 bool atEnd = false; 350 351 if (fruCache.size() >= sizeof(FRUHeader)) 352 { 353 354 FRUHeader* header = reinterpret_cast<FRUHeader*>(fruCache.data()); 355 356 int lastRecordStart = std::max( 357 header->internalOffset, 358 std::max(header->chassisOffset, 359 std::max(header->boardOffset, header->productOffset))); 360 // TODO: Handle Multi-Record FRUs? 361 362 lastRecordStart *= 8; // header starts in are multiples of 8 bytes 363 364 // get the length of the area in multiples of 8 bytes 365 if (lastWriteAddr > (lastRecordStart + 1)) 366 { 367 // second byte in record area is the length 368 int areaLength(fruCache[lastRecordStart + 1]); 369 areaLength *= 8; // it is in multiples of 8 bytes 370 371 if (lastWriteAddr >= (areaLength + lastRecordStart)) 372 { 373 atEnd = true; 374 } 375 } 376 } 377 uint8_t* respPtr = static_cast<uint8_t*>(response); 378 if (atEnd) 379 { 380 // cancel timer, we're at the end so might as well send it 381 cacheTimer->stop(); 382 if (!writeFru()) 383 { 384 return IPMI_CC_INVALID_FIELD_REQUEST; 385 } 386 *respPtr = std::min(fruCache.size(), static_cast<size_t>(0xFF)); 387 } 388 else 389 { 390 // start a timer, if no further data is sent in cacheTimeoutSeconds 391 // seconds, check to see if it is valid 392 createTimer(); 393 cacheTimer->start(std::chrono::duration_cast<std::chrono::microseconds>( 394 std::chrono::seconds(cacheTimeoutSeconds))); 395 *respPtr = 0; 396 } 397 398 *dataLen = 1; 399 400 return IPMI_CC_OK; 401 } 402 403 /** @brief implements the get FRU inventory area info command 404 * @param fruDeviceId - FRU Device ID 405 * 406 * @returns IPMI completion code plus response data 407 * - inventorySize - Number of possible allocation units 408 * - accessType - Allocation unit size in bytes. 409 */ 410 ipmi::RspType<uint16_t, // inventorySize 411 uint8_t> // accessType 412 ipmiStorageGetFruInvAreaInfo(uint8_t fruDeviceId) 413 { 414 if (fruDeviceId == 0xFF) 415 { 416 return ipmi::responseInvalidFieldRequest(); 417 } 418 419 ipmi::Cc status = replaceCacheFru(fruDeviceId); 420 421 if (status != IPMI_CC_OK) 422 { 423 return ipmi::response(status); 424 } 425 426 constexpr uint8_t accessType = 427 static_cast<uint8_t>(GetFRUAreaAccessType::byte); 428 429 return ipmi::responseSuccess(fruCache.size(), accessType); 430 } 431 432 ipmi_ret_t getFruSdrCount(size_t& count) 433 { 434 ipmi_ret_t ret = replaceCacheFru(0); 435 if (ret != IPMI_CC_OK) 436 { 437 return ret; 438 } 439 count = deviceHashes.size(); 440 return IPMI_CC_OK; 441 } 442 443 ipmi_ret_t getFruSdrs(size_t index, get_sdr::SensorDataFruRecord& resp) 444 { 445 ipmi_ret_t ret = replaceCacheFru(0); // this will update the hash list 446 if (ret != IPMI_CC_OK) 447 { 448 return ret; 449 } 450 if (deviceHashes.size() < index) 451 { 452 return IPMI_CC_INVALID_FIELD_REQUEST; 453 } 454 auto device = deviceHashes.begin() + index; 455 uint8_t& bus = device->second.first; 456 uint8_t& address = device->second.second; 457 458 ManagedObjectType frus; 459 460 std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus(); 461 sdbusplus::message::message getObjects = dbus->new_method_call( 462 fruDeviceServiceName, "/", "org.freedesktop.DBus.ObjectManager", 463 "GetManagedObjects"); 464 try 465 { 466 sdbusplus::message::message resp = dbus->call(getObjects); 467 resp.read(frus); 468 } 469 catch (sdbusplus::exception_t&) 470 { 471 return IPMI_CC_RESPONSE_ERROR; 472 } 473 boost::container::flat_map<std::string, DbusVariant>* fruData = nullptr; 474 auto fru = 475 std::find_if(frus.begin(), frus.end(), 476 [bus, address, &fruData](ManagedEntry& entry) { 477 auto findFruDevice = 478 entry.second.find("xyz.openbmc_project.FruDevice"); 479 if (findFruDevice == entry.second.end()) 480 { 481 return false; 482 } 483 fruData = &(findFruDevice->second); 484 auto findBus = findFruDevice->second.find("BUS"); 485 auto findAddress = 486 findFruDevice->second.find("ADDRESS"); 487 if (findBus == findFruDevice->second.end() || 488 findAddress == findFruDevice->second.end()) 489 { 490 return false; 491 } 492 if (std::get<uint32_t>(findBus->second) != bus) 493 { 494 return false; 495 } 496 if (std::get<uint32_t>(findAddress->second) != address) 497 { 498 return false; 499 } 500 return true; 501 }); 502 if (fru == frus.end()) 503 { 504 return IPMI_CC_RESPONSE_ERROR; 505 } 506 std::string name; 507 auto findProductName = fruData->find("BOARD_PRODUCT_NAME"); 508 auto findBoardName = fruData->find("PRODUCT_PRODUCT_NAME"); 509 if (findProductName != fruData->end()) 510 { 511 name = std::get<std::string>(findProductName->second); 512 } 513 else if (findBoardName != fruData->end()) 514 { 515 name = std::get<std::string>(findBoardName->second); 516 } 517 else 518 { 519 name = "UNKNOWN"; 520 } 521 if (name.size() > maxFruSdrNameSize) 522 { 523 name = name.substr(0, maxFruSdrNameSize); 524 } 525 size_t sizeDiff = maxFruSdrNameSize - name.size(); 526 527 resp.header.record_id_lsb = 0x0; // calling code is to implement these 528 resp.header.record_id_msb = 0x0; 529 resp.header.sdr_version = ipmiSdrVersion; 530 resp.header.record_type = 0x11; // FRU Device Locator 531 resp.header.record_length = sizeof(resp.body) + sizeof(resp.key) - sizeDiff; 532 resp.key.deviceAddress = 0x20; 533 resp.key.fruID = device->first; 534 resp.key.accessLun = 0x80; // logical / physical fru device 535 resp.key.channelNumber = 0x0; 536 resp.body.reserved = 0x0; 537 resp.body.deviceType = 0x10; 538 resp.body.deviceTypeModifier = 0x0; 539 resp.body.entityID = 0x0; 540 resp.body.entityInstance = 0x1; 541 resp.body.oem = 0x0; 542 resp.body.deviceIDLen = name.size(); 543 name.copy(resp.body.deviceID, name.size()); 544 545 return IPMI_CC_OK; 546 } 547 548 static bool getSELLogFiles(std::vector<std::filesystem::path>& selLogFiles) 549 { 550 // Loop through the directory looking for ipmi_sel log files 551 for (const std::filesystem::directory_entry& dirEnt : 552 std::filesystem::directory_iterator(intel_oem::ipmi::sel::selLogDir)) 553 { 554 std::string filename = dirEnt.path().filename(); 555 if (boost::starts_with(filename, intel_oem::ipmi::sel::selLogFilename)) 556 { 557 // If we find an ipmi_sel log file, save the path 558 selLogFiles.emplace_back(intel_oem::ipmi::sel::selLogDir / 559 filename); 560 } 561 } 562 // As the log files rotate, they are appended with a ".#" that is higher for 563 // the older logs. Since we don't expect more than 10 log files, we 564 // can just sort the list to get them in order from newest to oldest 565 std::sort(selLogFiles.begin(), selLogFiles.end()); 566 567 return !selLogFiles.empty(); 568 } 569 570 static int countSELEntries() 571 { 572 // Get the list of ipmi_sel log files 573 std::vector<std::filesystem::path> selLogFiles; 574 if (!getSELLogFiles(selLogFiles)) 575 { 576 return 0; 577 } 578 int numSELEntries = 0; 579 // Loop through each log file and count the number of logs 580 for (const std::filesystem::path& file : selLogFiles) 581 { 582 std::ifstream logStream(file); 583 if (!logStream.is_open()) 584 { 585 continue; 586 } 587 588 std::string line; 589 while (std::getline(logStream, line)) 590 { 591 numSELEntries++; 592 } 593 } 594 return numSELEntries; 595 } 596 597 static bool findSELEntry(const int recordID, 598 const std::vector<std::filesystem::path> selLogFiles, 599 std::string& entry) 600 { 601 // Record ID is the first entry field following the timestamp. It is 602 // preceded by a space and followed by a comma 603 std::string search = " " + std::to_string(recordID) + ","; 604 605 // Loop through the ipmi_sel log entries 606 for (const std::filesystem::path& file : selLogFiles) 607 { 608 std::ifstream logStream(file); 609 if (!logStream.is_open()) 610 { 611 continue; 612 } 613 614 while (std::getline(logStream, entry)) 615 { 616 // Check if the record ID matches 617 if (entry.find(search) != std::string::npos) 618 { 619 return true; 620 } 621 } 622 } 623 return false; 624 } 625 626 static uint16_t 627 getNextRecordID(const uint16_t recordID, 628 const std::vector<std::filesystem::path> selLogFiles) 629 { 630 uint16_t nextRecordID = recordID + 1; 631 std::string entry; 632 if (findSELEntry(nextRecordID, selLogFiles, entry)) 633 { 634 return nextRecordID; 635 } 636 else 637 { 638 return ipmi::sel::lastEntry; 639 } 640 } 641 642 static int fromHexStr(const std::string hexStr, std::vector<uint8_t>& data) 643 { 644 for (unsigned int i = 0; i < hexStr.size(); i += 2) 645 { 646 try 647 { 648 data.push_back(static_cast<uint8_t>( 649 std::stoul(hexStr.substr(i, 2), nullptr, 16))); 650 } 651 catch (std::invalid_argument& e) 652 { 653 phosphor::logging::log<phosphor::logging::level::ERR>(e.what()); 654 return -1; 655 } 656 catch (std::out_of_range& e) 657 { 658 phosphor::logging::log<phosphor::logging::level::ERR>(e.what()); 659 return -1; 660 } 661 } 662 return 0; 663 } 664 665 ipmi::RspType<uint8_t, // SEL version 666 uint16_t, // SEL entry count 667 uint16_t, // free space 668 uint32_t, // last add timestamp 669 uint32_t, // last erase timestamp 670 uint8_t> // operation support 671 ipmiStorageGetSELInfo() 672 { 673 constexpr uint8_t selVersion = ipmi::sel::selVersion; 674 uint16_t entries = countSELEntries(); 675 uint32_t addTimeStamp = intel_oem::ipmi::sel::getFileTimestamp( 676 intel_oem::ipmi::sel::selLogDir / intel_oem::ipmi::sel::selLogFilename); 677 uint32_t eraseTimeStamp = intel_oem::ipmi::sel::erase_time::get(); 678 constexpr uint8_t operationSupport = 679 intel_oem::ipmi::sel::selOperationSupport; 680 constexpr uint16_t freeSpace = 681 0xffff; // Spec indicates that more than 64kB is free 682 683 return ipmi::responseSuccess(selVersion, entries, freeSpace, addTimeStamp, 684 eraseTimeStamp, operationSupport); 685 } 686 687 using systemEventType = std::tuple< 688 uint32_t, // Timestamp 689 uint16_t, // Generator ID 690 uint8_t, // EvM Rev 691 uint8_t, // Sensor Type 692 uint8_t, // Sensor Number 693 uint7_t, // Event Type 694 bool, // Event Direction 695 std::array<uint8_t, intel_oem::ipmi::sel::systemEventSize>>; // Event Data 696 using oemTsEventType = std::tuple< 697 uint32_t, // Timestamp 698 std::array<uint8_t, intel_oem::ipmi::sel::oemTsEventSize>>; // Event Data 699 using oemEventType = 700 std::array<uint8_t, intel_oem::ipmi::sel::oemEventSize>; // Event Data 701 702 ipmi::RspType<uint16_t, // Next Record ID 703 uint16_t, // Record ID 704 uint8_t, // Record Type 705 std::variant<systemEventType, oemTsEventType, 706 oemEventType>> // Record Content 707 ipmiStorageGetSELEntry(uint16_t reservationID, uint16_t targetID, 708 uint8_t offset, uint8_t size) 709 { 710 // Only support getting the entire SEL record. If a partial size or non-zero 711 // offset is requested, return an error 712 if (offset != 0 || size != ipmi::sel::entireRecord) 713 { 714 return ipmi::responseRetBytesUnavailable(); 715 } 716 717 // Check the reservation ID if one is provided or required (only if the 718 // offset is non-zero) 719 if (reservationID != 0 || offset != 0) 720 { 721 if (!checkSELReservation(reservationID)) 722 { 723 return ipmi::responseInvalidReservationId(); 724 } 725 } 726 727 // Get the ipmi_sel log files 728 std::vector<std::filesystem::path> selLogFiles; 729 if (!getSELLogFiles(selLogFiles)) 730 { 731 return ipmi::responseSensorInvalid(); 732 } 733 734 std::string targetEntry; 735 736 if (targetID == ipmi::sel::firstEntry) 737 { 738 // The first entry will be at the top of the oldest log file 739 std::ifstream logStream(selLogFiles.back()); 740 if (!logStream.is_open()) 741 { 742 return ipmi::responseUnspecifiedError(); 743 } 744 745 if (!std::getline(logStream, targetEntry)) 746 { 747 return ipmi::responseUnspecifiedError(); 748 } 749 } 750 else if (targetID == ipmi::sel::lastEntry) 751 { 752 // The last entry will be at the bottom of the newest log file 753 std::ifstream logStream(selLogFiles.front()); 754 if (!logStream.is_open()) 755 { 756 return ipmi::responseUnspecifiedError(); 757 } 758 759 std::string line; 760 while (std::getline(logStream, line)) 761 { 762 targetEntry = line; 763 } 764 } 765 else 766 { 767 if (!findSELEntry(targetID, selLogFiles, targetEntry)) 768 { 769 return ipmi::responseSensorInvalid(); 770 } 771 } 772 773 // The format of the ipmi_sel message is "<Timestamp> 774 // <ID>,<Type>,<EventData>,[<Generator ID>,<Path>,<Direction>]". 775 // First get the Timestamp 776 size_t space = targetEntry.find_first_of(" "); 777 if (space == std::string::npos) 778 { 779 return ipmi::responseUnspecifiedError(); 780 } 781 std::string entryTimestamp = targetEntry.substr(0, space); 782 // Then get the log contents 783 size_t entryStart = targetEntry.find_first_not_of(" ", space); 784 if (entryStart == std::string::npos) 785 { 786 return ipmi::responseUnspecifiedError(); 787 } 788 std::string_view entry(targetEntry); 789 entry.remove_prefix(entryStart); 790 // Use split to separate the entry into its fields 791 std::vector<std::string> targetEntryFields; 792 boost::split(targetEntryFields, entry, boost::is_any_of(","), 793 boost::token_compress_on); 794 if (targetEntryFields.size() < 3) 795 { 796 return ipmi::responseUnspecifiedError(); 797 } 798 std::string& recordIDStr = targetEntryFields[0]; 799 std::string& recordTypeStr = targetEntryFields[1]; 800 std::string& eventDataStr = targetEntryFields[2]; 801 802 uint16_t recordID; 803 uint8_t recordType; 804 try 805 { 806 recordID = std::stoul(recordIDStr); 807 recordType = std::stoul(recordTypeStr, nullptr, 16); 808 } 809 catch (const std::invalid_argument&) 810 { 811 return ipmi::responseUnspecifiedError(); 812 } 813 uint16_t nextRecordID = getNextRecordID(recordID, selLogFiles); 814 std::vector<uint8_t> eventDataBytes; 815 if (fromHexStr(eventDataStr, eventDataBytes) < 0) 816 { 817 return ipmi::responseUnspecifiedError(); 818 } 819 820 if (recordType == intel_oem::ipmi::sel::systemEvent) 821 { 822 // Get the timestamp 823 std::tm timeStruct = {}; 824 std::istringstream entryStream(entryTimestamp); 825 826 uint32_t timestamp = ipmi::sel::invalidTimeStamp; 827 if (entryStream >> std::get_time(&timeStruct, "%Y-%m-%dT%H:%M:%S")) 828 { 829 timestamp = std::mktime(&timeStruct); 830 } 831 832 // Set the event message revision 833 uint8_t evmRev = intel_oem::ipmi::sel::eventMsgRev; 834 835 uint16_t generatorID = 0; 836 uint8_t sensorType = 0; 837 uint8_t sensorNum = 0xFF; 838 uint7_t eventType = 0; 839 bool eventDir = 0; 840 // System type events should have six fields 841 if (targetEntryFields.size() >= 6) 842 { 843 std::string& generatorIDStr = targetEntryFields[3]; 844 std::string& sensorPath = targetEntryFields[4]; 845 std::string& eventDirStr = targetEntryFields[5]; 846 847 // Get the generator ID 848 try 849 { 850 generatorID = std::stoul(generatorIDStr, nullptr, 16); 851 } 852 catch (const std::invalid_argument&) 853 { 854 std::cerr << "Invalid Generator ID\n"; 855 } 856 857 // Get the sensor type, sensor number, and event type for the sensor 858 sensorType = getSensorTypeFromPath(sensorPath); 859 sensorNum = getSensorNumberFromPath(sensorPath); 860 eventType = getSensorEventTypeFromPath(sensorPath); 861 862 // Get the event direction 863 try 864 { 865 eventDir = std::stoul(eventDirStr) ? 0 : 1; 866 } 867 catch (const std::invalid_argument&) 868 { 869 std::cerr << "Invalid Event Direction\n"; 870 } 871 } 872 873 // Only keep the eventData bytes that fit in the record 874 std::array<uint8_t, intel_oem::ipmi::sel::systemEventSize> eventData{}; 875 std::copy_n(eventDataBytes.begin(), 876 std::min(eventDataBytes.size(), eventData.size()), 877 eventData.begin()); 878 879 return ipmi::responseSuccess( 880 nextRecordID, recordID, recordType, 881 systemEventType{timestamp, generatorID, evmRev, sensorType, 882 sensorNum, eventType, eventDir, eventData}); 883 } 884 else if (recordType >= intel_oem::ipmi::sel::oemTsEventFirst && 885 recordType <= intel_oem::ipmi::sel::oemTsEventLast) 886 { 887 // Get the timestamp 888 std::tm timeStruct = {}; 889 std::istringstream entryStream(entryTimestamp); 890 891 uint32_t timestamp = ipmi::sel::invalidTimeStamp; 892 if (entryStream >> std::get_time(&timeStruct, "%Y-%m-%dT%H:%M:%S")) 893 { 894 timestamp = std::mktime(&timeStruct); 895 } 896 897 // Only keep the bytes that fit in the record 898 std::array<uint8_t, intel_oem::ipmi::sel::oemTsEventSize> eventData{}; 899 std::copy_n(eventDataBytes.begin(), 900 std::min(eventDataBytes.size(), eventData.size()), 901 eventData.begin()); 902 903 return ipmi::responseSuccess(nextRecordID, recordID, recordType, 904 oemTsEventType{timestamp, eventData}); 905 } 906 else if (recordType >= intel_oem::ipmi::sel::oemEventFirst && 907 recordType <= intel_oem::ipmi::sel::oemEventLast) 908 { 909 // Only keep the bytes that fit in the record 910 std::array<uint8_t, intel_oem::ipmi::sel::oemEventSize> eventData{}; 911 std::copy_n(eventDataBytes.begin(), 912 std::min(eventDataBytes.size(), eventData.size()), 913 eventData.begin()); 914 915 return ipmi::responseSuccess(nextRecordID, recordID, recordType, 916 eventData); 917 } 918 919 return ipmi::responseUnspecifiedError(); 920 } 921 922 ipmi::RspType<uint16_t> ipmiStorageAddSELEntry( 923 uint16_t recordID, uint8_t recordType, uint32_t timestamp, 924 uint16_t generatorID, uint8_t evmRev, uint8_t sensorType, uint8_t sensorNum, 925 uint8_t eventType, uint8_t eventData1, uint8_t eventData2, 926 uint8_t eventData3) 927 { 928 // Per the IPMI spec, need to cancel any reservation when a SEL entry is 929 // added 930 cancelSELReservation(); 931 932 // Send this request to the Redfish hooks to log it as a Redfish message 933 // instead. There is no need to add it to the SEL, so just return success. 934 intel_oem::ipmi::sel::checkRedfishHooks( 935 recordID, recordType, timestamp, generatorID, evmRev, sensorType, 936 sensorNum, eventType, eventData1, eventData2, eventData3); 937 938 uint16_t responseID = 0xFFFF; 939 return ipmi::responseSuccess(responseID); 940 } 941 942 ipmi::RspType<uint8_t> ipmiStorageClearSEL(ipmi::Context::ptr ctx, 943 uint16_t reservationID, 944 const std::array<uint8_t, 3>& clr, 945 uint8_t eraseOperation) 946 { 947 if (!checkSELReservation(reservationID)) 948 { 949 return ipmi::responseInvalidReservationId(); 950 } 951 952 static constexpr std::array<uint8_t, 3> clrExpected = {'C', 'L', 'R'}; 953 if (clr != clrExpected) 954 { 955 return ipmi::responseInvalidFieldRequest(); 956 } 957 958 // Erasure status cannot be fetched, so always return erasure status as 959 // `erase completed`. 960 if (eraseOperation == ipmi::sel::getEraseStatus) 961 { 962 return ipmi::responseSuccess(ipmi::sel::eraseComplete); 963 } 964 965 // Check that initiate erase is correct 966 if (eraseOperation != ipmi::sel::initiateErase) 967 { 968 return ipmi::responseInvalidFieldRequest(); 969 } 970 971 // Per the IPMI spec, need to cancel any reservation when the SEL is 972 // cleared 973 cancelSELReservation(); 974 975 // Save the erase time 976 intel_oem::ipmi::sel::erase_time::save(); 977 978 // Clear the SEL by deleting the log files 979 std::vector<std::filesystem::path> selLogFiles; 980 if (getSELLogFiles(selLogFiles)) 981 { 982 for (const std::filesystem::path& file : selLogFiles) 983 { 984 std::error_code ec; 985 std::filesystem::remove(file, ec); 986 } 987 } 988 989 // Reload rsyslog so it knows to start new log files 990 std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus(); 991 sdbusplus::message::message rsyslogReload = dbus->new_method_call( 992 "org.freedesktop.systemd1", "/org/freedesktop/systemd1", 993 "org.freedesktop.systemd1.Manager", "ReloadUnit"); 994 rsyslogReload.append("rsyslog.service", "replace"); 995 try 996 { 997 sdbusplus::message::message reloadResponse = dbus->call(rsyslogReload); 998 } 999 catch (sdbusplus::exception_t& e) 1000 { 1001 phosphor::logging::log<phosphor::logging::level::ERR>(e.what()); 1002 } 1003 1004 return ipmi::responseSuccess(ipmi::sel::eraseComplete); 1005 } 1006 1007 ipmi::RspType<uint32_t> ipmiStorageGetSELTime() 1008 { 1009 struct timespec selTime = {}; 1010 1011 if (clock_gettime(CLOCK_REALTIME, &selTime) < 0) 1012 { 1013 return ipmi::responseUnspecifiedError(); 1014 } 1015 1016 return ipmi::responseSuccess(selTime.tv_sec); 1017 } 1018 1019 ipmi::RspType<> ipmiStorageSetSELTime(uint32_t selTime) 1020 { 1021 // Set SEL Time is not supported 1022 return ipmi::responseInvalidCommand(); 1023 } 1024 1025 std::vector<uint8_t> getType12SDRs(uint16_t index, uint16_t recordId) 1026 { 1027 std::vector<uint8_t> resp; 1028 if (index == 0) 1029 { 1030 Type12Record bmc = {}; 1031 bmc.header.record_id_lsb = recordId; 1032 bmc.header.record_id_msb = recordId >> 8; 1033 bmc.header.sdr_version = ipmiSdrVersion; 1034 bmc.header.record_type = 0x12; 1035 bmc.header.record_length = 0x1b; 1036 bmc.slaveAddress = 0x20; 1037 bmc.channelNumber = 0; 1038 bmc.powerStateNotification = 0; 1039 bmc.deviceCapabilities = 0xBF; 1040 bmc.reserved = 0; 1041 bmc.entityID = 0x2E; 1042 bmc.entityInstance = 1; 1043 bmc.oem = 0; 1044 bmc.typeLengthCode = 0xD0; 1045 std::string bmcName = "Basbrd Mgmt Ctlr"; 1046 std::copy(bmcName.begin(), bmcName.end(), bmc.name); 1047 uint8_t* bmcPtr = reinterpret_cast<uint8_t*>(&bmc); 1048 resp.insert(resp.end(), bmcPtr, bmcPtr + sizeof(Type12Record)); 1049 } 1050 else if (index == 1) 1051 { 1052 Type12Record me = {}; 1053 me.header.record_id_lsb = recordId; 1054 me.header.record_id_msb = recordId >> 8; 1055 me.header.sdr_version = ipmiSdrVersion; 1056 me.header.record_type = 0x12; 1057 me.header.record_length = 0x16; 1058 me.slaveAddress = 0x2C; 1059 me.channelNumber = 6; 1060 me.powerStateNotification = 0x24; 1061 me.deviceCapabilities = 0x21; 1062 me.reserved = 0; 1063 me.entityID = 0x2E; 1064 me.entityInstance = 2; 1065 me.oem = 0; 1066 me.typeLengthCode = 0xCB; 1067 std::string meName = "Mgmt Engine"; 1068 std::copy(meName.begin(), meName.end(), me.name); 1069 uint8_t* mePtr = reinterpret_cast<uint8_t*>(&me); 1070 resp.insert(resp.end(), mePtr, mePtr + sizeof(Type12Record)); 1071 } 1072 else 1073 { 1074 throw std::runtime_error("getType12SDRs:: Illegal index " + 1075 std::to_string(index)); 1076 } 1077 1078 return resp; 1079 } 1080 1081 void registerStorageFunctions() 1082 { 1083 // <Get FRU Inventory Area Info> 1084 ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnStorage, 1085 ipmi::storage::cmdGetFruInventoryAreaInfo, 1086 ipmi::Privilege::User, ipmiStorageGetFruInvAreaInfo); 1087 // <READ FRU Data> 1088 ipmiPrintAndRegister( 1089 NETFUN_STORAGE, 1090 static_cast<ipmi_cmd_t>(IPMINetfnStorageCmds::ipmiCmdReadFRUData), NULL, 1091 ipmiStorageReadFRUData, PRIVILEGE_USER); 1092 1093 // <WRITE FRU Data> 1094 ipmiPrintAndRegister( 1095 NETFUN_STORAGE, 1096 static_cast<ipmi_cmd_t>(IPMINetfnStorageCmds::ipmiCmdWriteFRUData), 1097 NULL, ipmiStorageWriteFRUData, PRIVILEGE_OPERATOR); 1098 1099 // <Get SEL Info> 1100 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 1101 ipmi::storage::cmdGetSelInfo, ipmi::Privilege::User, 1102 ipmiStorageGetSELInfo); 1103 1104 // <Get SEL Entry> 1105 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 1106 ipmi::storage::cmdGetSelEntry, ipmi::Privilege::User, 1107 ipmiStorageGetSELEntry); 1108 1109 // <Add SEL Entry> 1110 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 1111 static_cast<ipmi::Cmd>(ipmi::storage::cmdAddSelEntry), 1112 ipmi::Privilege::Operator, ipmiStorageAddSELEntry); 1113 1114 // <Clear SEL> 1115 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 1116 ipmi::storage::cmdClearSel, ipmi::Privilege::Operator, 1117 ipmiStorageClearSEL); 1118 1119 // <Get SEL Time> 1120 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 1121 ipmi::storage::cmdGetSelTime, ipmi::Privilege::User, 1122 ipmiStorageGetSELTime); 1123 1124 // <Set SEL Time> 1125 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 1126 ipmi::storage::cmdSetSelTime, 1127 ipmi::Privilege::Operator, ipmiStorageSetSELTime); 1128 } 1129 } // namespace storage 1130 } // namespace ipmi 1131