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 /** @brief implements the read FRU data command 265 * @param fruDeviceId - FRU Device ID 266 * @param fruInventoryOffset - FRU Inventory Offset to write 267 * @param countToRead - Count to read 268 * 269 * @returns ipmi completion code plus response data 270 * - countWritten - Count written 271 */ 272 ipmi::RspType<uint8_t, // Count 273 std::vector<uint8_t> // Requested data 274 > 275 ipmiStorageReadFruData(uint8_t fruDeviceId, uint16_t fruInventoryOffset, 276 uint8_t countToRead) 277 { 278 if (fruDeviceId == 0xFF) 279 { 280 return ipmi::responseInvalidFieldRequest(); 281 } 282 283 ipmi::Cc status = replaceCacheFru(fruDeviceId); 284 285 if (status != ipmi::ccSuccess) 286 { 287 return ipmi::response(status); 288 } 289 290 size_t fromFruByteLen = 0; 291 if (countToRead + fruInventoryOffset < fruCache.size()) 292 { 293 fromFruByteLen = countToRead; 294 } 295 else if (fruCache.size() > fruInventoryOffset) 296 { 297 fromFruByteLen = fruCache.size() - fruInventoryOffset; 298 } 299 else 300 { 301 return ipmi::responseInvalidFieldRequest(); 302 } 303 304 std::vector<uint8_t> requestedData; 305 306 requestedData.insert( 307 requestedData.begin(), fruCache.begin() + fruInventoryOffset, 308 fruCache.begin() + fruInventoryOffset + fromFruByteLen); 309 310 return ipmi::responseSuccess(countToRead, requestedData); 311 } 312 313 /** @brief implements the write FRU data command 314 * @param fruDeviceId - FRU Device ID 315 * @param fruInventoryOffset - FRU Inventory Offset to write 316 * @param dataToWrite - Data to write 317 * 318 * @returns ipmi completion code plus response data 319 * - countWritten - Count written 320 */ 321 ipmi::RspType<uint8_t> 322 ipmiStorageWriteFruData(uint8_t fruDeviceId, uint16_t fruInventoryOffset, 323 std::vector<uint8_t>& dataToWrite) 324 { 325 if (fruDeviceId == 0xFF) 326 { 327 return ipmi::responseInvalidFieldRequest(); 328 } 329 330 size_t writeLen = dataToWrite.size(); 331 332 ipmi::Cc status = replaceCacheFru(fruDeviceId); 333 if (status != ipmi::ccSuccess) 334 { 335 return ipmi::response(status); 336 } 337 int lastWriteAddr = fruInventoryOffset + writeLen; 338 if (fruCache.size() < lastWriteAddr) 339 { 340 fruCache.resize(fruInventoryOffset + writeLen); 341 } 342 343 std::copy(dataToWrite.begin(), dataToWrite.begin() + writeLen, 344 fruCache.begin() + fruInventoryOffset); 345 346 bool atEnd = false; 347 348 if (fruCache.size() >= sizeof(FRUHeader)) 349 { 350 FRUHeader* header = reinterpret_cast<FRUHeader*>(fruCache.data()); 351 352 int lastRecordStart = std::max( 353 header->internalOffset, 354 std::max(header->chassisOffset, 355 std::max(header->boardOffset, header->productOffset))); 356 // TODO: Handle Multi-Record FRUs? 357 358 lastRecordStart *= 8; // header starts in are multiples of 8 bytes 359 360 // get the length of the area in multiples of 8 bytes 361 if (lastWriteAddr > (lastRecordStart + 1)) 362 { 363 // second byte in record area is the length 364 int areaLength(fruCache[lastRecordStart + 1]); 365 areaLength *= 8; // it is in multiples of 8 bytes 366 367 if (lastWriteAddr >= (areaLength + lastRecordStart)) 368 { 369 atEnd = true; 370 } 371 } 372 } 373 uint8_t countWritten = 0; 374 if (atEnd) 375 { 376 // cancel timer, we're at the end so might as well send it 377 cacheTimer->stop(); 378 if (!writeFru()) 379 { 380 return ipmi::responseInvalidFieldRequest(); 381 } 382 countWritten = std::min(fruCache.size(), static_cast<size_t>(0xFF)); 383 } 384 else 385 { 386 // start a timer, if no further data is sent in cacheTimeoutSeconds 387 // seconds, check to see if it is valid 388 createTimer(); 389 cacheTimer->start(std::chrono::duration_cast<std::chrono::microseconds>( 390 std::chrono::seconds(cacheTimeoutSeconds))); 391 countWritten = 0; 392 } 393 394 return ipmi::responseSuccess(countWritten); 395 } 396 397 /** @brief implements the get FRU inventory area info command 398 * @param fruDeviceId - FRU Device ID 399 * 400 * @returns IPMI completion code plus response data 401 * - inventorySize - Number of possible allocation units 402 * - accessType - Allocation unit size in bytes. 403 */ 404 ipmi::RspType<uint16_t, // inventorySize 405 uint8_t> // accessType 406 ipmiStorageGetFruInvAreaInfo(uint8_t fruDeviceId) 407 { 408 if (fruDeviceId == 0xFF) 409 { 410 return ipmi::responseInvalidFieldRequest(); 411 } 412 413 ipmi::Cc status = replaceCacheFru(fruDeviceId); 414 415 if (status != IPMI_CC_OK) 416 { 417 return ipmi::response(status); 418 } 419 420 constexpr uint8_t accessType = 421 static_cast<uint8_t>(GetFRUAreaAccessType::byte); 422 423 return ipmi::responseSuccess(fruCache.size(), accessType); 424 } 425 426 ipmi_ret_t getFruSdrCount(size_t& count) 427 { 428 ipmi_ret_t ret = replaceCacheFru(0); 429 if (ret != IPMI_CC_OK) 430 { 431 return ret; 432 } 433 count = deviceHashes.size(); 434 return IPMI_CC_OK; 435 } 436 437 ipmi_ret_t getFruSdrs(size_t index, get_sdr::SensorDataFruRecord& resp) 438 { 439 ipmi_ret_t ret = replaceCacheFru(0); // this will update the hash list 440 if (ret != IPMI_CC_OK) 441 { 442 return ret; 443 } 444 if (deviceHashes.size() < index) 445 { 446 return IPMI_CC_INVALID_FIELD_REQUEST; 447 } 448 auto device = deviceHashes.begin() + index; 449 uint8_t& bus = device->second.first; 450 uint8_t& address = device->second.second; 451 452 ManagedObjectType frus; 453 454 std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus(); 455 sdbusplus::message::message getObjects = dbus->new_method_call( 456 fruDeviceServiceName, "/", "org.freedesktop.DBus.ObjectManager", 457 "GetManagedObjects"); 458 try 459 { 460 sdbusplus::message::message resp = dbus->call(getObjects); 461 resp.read(frus); 462 } 463 catch (sdbusplus::exception_t&) 464 { 465 return IPMI_CC_RESPONSE_ERROR; 466 } 467 boost::container::flat_map<std::string, DbusVariant>* fruData = nullptr; 468 auto fru = 469 std::find_if(frus.begin(), frus.end(), 470 [bus, address, &fruData](ManagedEntry& entry) { 471 auto findFruDevice = 472 entry.second.find("xyz.openbmc_project.FruDevice"); 473 if (findFruDevice == entry.second.end()) 474 { 475 return false; 476 } 477 fruData = &(findFruDevice->second); 478 auto findBus = findFruDevice->second.find("BUS"); 479 auto findAddress = 480 findFruDevice->second.find("ADDRESS"); 481 if (findBus == findFruDevice->second.end() || 482 findAddress == findFruDevice->second.end()) 483 { 484 return false; 485 } 486 if (std::get<uint32_t>(findBus->second) != bus) 487 { 488 return false; 489 } 490 if (std::get<uint32_t>(findAddress->second) != address) 491 { 492 return false; 493 } 494 return true; 495 }); 496 if (fru == frus.end()) 497 { 498 return IPMI_CC_RESPONSE_ERROR; 499 } 500 std::string name; 501 auto findProductName = fruData->find("BOARD_PRODUCT_NAME"); 502 auto findBoardName = fruData->find("PRODUCT_PRODUCT_NAME"); 503 if (findProductName != fruData->end()) 504 { 505 name = std::get<std::string>(findProductName->second); 506 } 507 else if (findBoardName != fruData->end()) 508 { 509 name = std::get<std::string>(findBoardName->second); 510 } 511 else 512 { 513 name = "UNKNOWN"; 514 } 515 if (name.size() > maxFruSdrNameSize) 516 { 517 name = name.substr(0, maxFruSdrNameSize); 518 } 519 size_t sizeDiff = maxFruSdrNameSize - name.size(); 520 521 resp.header.record_id_lsb = 0x0; // calling code is to implement these 522 resp.header.record_id_msb = 0x0; 523 resp.header.sdr_version = ipmiSdrVersion; 524 resp.header.record_type = 0x11; // FRU Device Locator 525 resp.header.record_length = sizeof(resp.body) + sizeof(resp.key) - sizeDiff; 526 resp.key.deviceAddress = 0x20; 527 resp.key.fruID = device->first; 528 resp.key.accessLun = 0x80; // logical / physical fru device 529 resp.key.channelNumber = 0x0; 530 resp.body.reserved = 0x0; 531 resp.body.deviceType = 0x10; 532 resp.body.deviceTypeModifier = 0x0; 533 resp.body.entityID = 0x0; 534 resp.body.entityInstance = 0x1; 535 resp.body.oem = 0x0; 536 resp.body.deviceIDLen = name.size(); 537 name.copy(resp.body.deviceID, name.size()); 538 539 return IPMI_CC_OK; 540 } 541 542 static bool getSELLogFiles(std::vector<std::filesystem::path>& selLogFiles) 543 { 544 // Loop through the directory looking for ipmi_sel log files 545 for (const std::filesystem::directory_entry& dirEnt : 546 std::filesystem::directory_iterator(intel_oem::ipmi::sel::selLogDir)) 547 { 548 std::string filename = dirEnt.path().filename(); 549 if (boost::starts_with(filename, intel_oem::ipmi::sel::selLogFilename)) 550 { 551 // If we find an ipmi_sel log file, save the path 552 selLogFiles.emplace_back(intel_oem::ipmi::sel::selLogDir / 553 filename); 554 } 555 } 556 // As the log files rotate, they are appended with a ".#" that is higher for 557 // the older logs. Since we don't expect more than 10 log files, we 558 // can just sort the list to get them in order from newest to oldest 559 std::sort(selLogFiles.begin(), selLogFiles.end()); 560 561 return !selLogFiles.empty(); 562 } 563 564 static int countSELEntries() 565 { 566 // Get the list of ipmi_sel log files 567 std::vector<std::filesystem::path> selLogFiles; 568 if (!getSELLogFiles(selLogFiles)) 569 { 570 return 0; 571 } 572 int numSELEntries = 0; 573 // Loop through each log file and count the number of logs 574 for (const std::filesystem::path& file : selLogFiles) 575 { 576 std::ifstream logStream(file); 577 if (!logStream.is_open()) 578 { 579 continue; 580 } 581 582 std::string line; 583 while (std::getline(logStream, line)) 584 { 585 numSELEntries++; 586 } 587 } 588 return numSELEntries; 589 } 590 591 static bool findSELEntry(const int recordID, 592 const std::vector<std::filesystem::path> selLogFiles, 593 std::string& entry) 594 { 595 // Record ID is the first entry field following the timestamp. It is 596 // preceded by a space and followed by a comma 597 std::string search = " " + std::to_string(recordID) + ","; 598 599 // Loop through the ipmi_sel log entries 600 for (const std::filesystem::path& file : selLogFiles) 601 { 602 std::ifstream logStream(file); 603 if (!logStream.is_open()) 604 { 605 continue; 606 } 607 608 while (std::getline(logStream, entry)) 609 { 610 // Check if the record ID matches 611 if (entry.find(search) != std::string::npos) 612 { 613 return true; 614 } 615 } 616 } 617 return false; 618 } 619 620 static uint16_t 621 getNextRecordID(const uint16_t recordID, 622 const std::vector<std::filesystem::path> selLogFiles) 623 { 624 uint16_t nextRecordID = recordID + 1; 625 std::string entry; 626 if (findSELEntry(nextRecordID, selLogFiles, entry)) 627 { 628 return nextRecordID; 629 } 630 else 631 { 632 return ipmi::sel::lastEntry; 633 } 634 } 635 636 static int fromHexStr(const std::string hexStr, std::vector<uint8_t>& data) 637 { 638 for (unsigned int i = 0; i < hexStr.size(); i += 2) 639 { 640 try 641 { 642 data.push_back(static_cast<uint8_t>( 643 std::stoul(hexStr.substr(i, 2), nullptr, 16))); 644 } 645 catch (std::invalid_argument& e) 646 { 647 phosphor::logging::log<phosphor::logging::level::ERR>(e.what()); 648 return -1; 649 } 650 catch (std::out_of_range& e) 651 { 652 phosphor::logging::log<phosphor::logging::level::ERR>(e.what()); 653 return -1; 654 } 655 } 656 return 0; 657 } 658 659 ipmi::RspType<uint8_t, // SEL version 660 uint16_t, // SEL entry count 661 uint16_t, // free space 662 uint32_t, // last add timestamp 663 uint32_t, // last erase timestamp 664 uint8_t> // operation support 665 ipmiStorageGetSELInfo() 666 { 667 constexpr uint8_t selVersion = ipmi::sel::selVersion; 668 uint16_t entries = countSELEntries(); 669 uint32_t addTimeStamp = intel_oem::ipmi::sel::getFileTimestamp( 670 intel_oem::ipmi::sel::selLogDir / intel_oem::ipmi::sel::selLogFilename); 671 uint32_t eraseTimeStamp = intel_oem::ipmi::sel::erase_time::get(); 672 constexpr uint8_t operationSupport = 673 intel_oem::ipmi::sel::selOperationSupport; 674 constexpr uint16_t freeSpace = 675 0xffff; // Spec indicates that more than 64kB is free 676 677 return ipmi::responseSuccess(selVersion, entries, freeSpace, addTimeStamp, 678 eraseTimeStamp, operationSupport); 679 } 680 681 using systemEventType = std::tuple< 682 uint32_t, // Timestamp 683 uint16_t, // Generator ID 684 uint8_t, // EvM Rev 685 uint8_t, // Sensor Type 686 uint8_t, // Sensor Number 687 uint7_t, // Event Type 688 bool, // Event Direction 689 std::array<uint8_t, intel_oem::ipmi::sel::systemEventSize>>; // Event Data 690 using oemTsEventType = std::tuple< 691 uint32_t, // Timestamp 692 std::array<uint8_t, intel_oem::ipmi::sel::oemTsEventSize>>; // Event Data 693 using oemEventType = 694 std::array<uint8_t, intel_oem::ipmi::sel::oemEventSize>; // Event Data 695 696 ipmi::RspType<uint16_t, // Next Record ID 697 uint16_t, // Record ID 698 uint8_t, // Record Type 699 std::variant<systemEventType, oemTsEventType, 700 oemEventType>> // Record Content 701 ipmiStorageGetSELEntry(uint16_t reservationID, uint16_t targetID, 702 uint8_t offset, uint8_t size) 703 { 704 // Only support getting the entire SEL record. If a partial size or non-zero 705 // offset is requested, return an error 706 if (offset != 0 || size != ipmi::sel::entireRecord) 707 { 708 return ipmi::responseRetBytesUnavailable(); 709 } 710 711 // Check the reservation ID if one is provided or required (only if the 712 // offset is non-zero) 713 if (reservationID != 0 || offset != 0) 714 { 715 if (!checkSELReservation(reservationID)) 716 { 717 return ipmi::responseInvalidReservationId(); 718 } 719 } 720 721 // Get the ipmi_sel log files 722 std::vector<std::filesystem::path> selLogFiles; 723 if (!getSELLogFiles(selLogFiles)) 724 { 725 return ipmi::responseSensorInvalid(); 726 } 727 728 std::string targetEntry; 729 730 if (targetID == ipmi::sel::firstEntry) 731 { 732 // The first entry will be at the top of the oldest log file 733 std::ifstream logStream(selLogFiles.back()); 734 if (!logStream.is_open()) 735 { 736 return ipmi::responseUnspecifiedError(); 737 } 738 739 if (!std::getline(logStream, targetEntry)) 740 { 741 return ipmi::responseUnspecifiedError(); 742 } 743 } 744 else if (targetID == ipmi::sel::lastEntry) 745 { 746 // The last entry will be at the bottom of the newest log file 747 std::ifstream logStream(selLogFiles.front()); 748 if (!logStream.is_open()) 749 { 750 return ipmi::responseUnspecifiedError(); 751 } 752 753 std::string line; 754 while (std::getline(logStream, line)) 755 { 756 targetEntry = line; 757 } 758 } 759 else 760 { 761 if (!findSELEntry(targetID, selLogFiles, targetEntry)) 762 { 763 return ipmi::responseSensorInvalid(); 764 } 765 } 766 767 // The format of the ipmi_sel message is "<Timestamp> 768 // <ID>,<Type>,<EventData>,[<Generator ID>,<Path>,<Direction>]". 769 // First get the Timestamp 770 size_t space = targetEntry.find_first_of(" "); 771 if (space == std::string::npos) 772 { 773 return ipmi::responseUnspecifiedError(); 774 } 775 std::string entryTimestamp = targetEntry.substr(0, space); 776 // Then get the log contents 777 size_t entryStart = targetEntry.find_first_not_of(" ", space); 778 if (entryStart == std::string::npos) 779 { 780 return ipmi::responseUnspecifiedError(); 781 } 782 std::string_view entry(targetEntry); 783 entry.remove_prefix(entryStart); 784 // Use split to separate the entry into its fields 785 std::vector<std::string> targetEntryFields; 786 boost::split(targetEntryFields, entry, boost::is_any_of(","), 787 boost::token_compress_on); 788 if (targetEntryFields.size() < 3) 789 { 790 return ipmi::responseUnspecifiedError(); 791 } 792 std::string& recordIDStr = targetEntryFields[0]; 793 std::string& recordTypeStr = targetEntryFields[1]; 794 std::string& eventDataStr = targetEntryFields[2]; 795 796 uint16_t recordID; 797 uint8_t recordType; 798 try 799 { 800 recordID = std::stoul(recordIDStr); 801 recordType = std::stoul(recordTypeStr, nullptr, 16); 802 } 803 catch (const std::invalid_argument&) 804 { 805 return ipmi::responseUnspecifiedError(); 806 } 807 uint16_t nextRecordID = getNextRecordID(recordID, selLogFiles); 808 std::vector<uint8_t> eventDataBytes; 809 if (fromHexStr(eventDataStr, eventDataBytes) < 0) 810 { 811 return ipmi::responseUnspecifiedError(); 812 } 813 814 if (recordType == intel_oem::ipmi::sel::systemEvent) 815 { 816 // Get the timestamp 817 std::tm timeStruct = {}; 818 std::istringstream entryStream(entryTimestamp); 819 820 uint32_t timestamp = ipmi::sel::invalidTimeStamp; 821 if (entryStream >> std::get_time(&timeStruct, "%Y-%m-%dT%H:%M:%S")) 822 { 823 timestamp = std::mktime(&timeStruct); 824 } 825 826 // Set the event message revision 827 uint8_t evmRev = intel_oem::ipmi::sel::eventMsgRev; 828 829 uint16_t generatorID = 0; 830 uint8_t sensorType = 0; 831 uint8_t sensorNum = 0xFF; 832 uint7_t eventType = 0; 833 bool eventDir = 0; 834 // System type events should have six fields 835 if (targetEntryFields.size() >= 6) 836 { 837 std::string& generatorIDStr = targetEntryFields[3]; 838 std::string& sensorPath = targetEntryFields[4]; 839 std::string& eventDirStr = targetEntryFields[5]; 840 841 // Get the generator ID 842 try 843 { 844 generatorID = std::stoul(generatorIDStr, nullptr, 16); 845 } 846 catch (const std::invalid_argument&) 847 { 848 std::cerr << "Invalid Generator ID\n"; 849 } 850 851 // Get the sensor type, sensor number, and event type for the sensor 852 sensorType = getSensorTypeFromPath(sensorPath); 853 sensorNum = getSensorNumberFromPath(sensorPath); 854 eventType = getSensorEventTypeFromPath(sensorPath); 855 856 // Get the event direction 857 try 858 { 859 eventDir = std::stoul(eventDirStr) ? 0 : 1; 860 } 861 catch (const std::invalid_argument&) 862 { 863 std::cerr << "Invalid Event Direction\n"; 864 } 865 } 866 867 // Only keep the eventData bytes that fit in the record 868 std::array<uint8_t, intel_oem::ipmi::sel::systemEventSize> eventData{}; 869 std::copy_n(eventDataBytes.begin(), 870 std::min(eventDataBytes.size(), eventData.size()), 871 eventData.begin()); 872 873 return ipmi::responseSuccess( 874 nextRecordID, recordID, recordType, 875 systemEventType{timestamp, generatorID, evmRev, sensorType, 876 sensorNum, eventType, eventDir, eventData}); 877 } 878 else if (recordType >= intel_oem::ipmi::sel::oemTsEventFirst && 879 recordType <= intel_oem::ipmi::sel::oemTsEventLast) 880 { 881 // Get the timestamp 882 std::tm timeStruct = {}; 883 std::istringstream entryStream(entryTimestamp); 884 885 uint32_t timestamp = ipmi::sel::invalidTimeStamp; 886 if (entryStream >> std::get_time(&timeStruct, "%Y-%m-%dT%H:%M:%S")) 887 { 888 timestamp = std::mktime(&timeStruct); 889 } 890 891 // Only keep the bytes that fit in the record 892 std::array<uint8_t, intel_oem::ipmi::sel::oemTsEventSize> eventData{}; 893 std::copy_n(eventDataBytes.begin(), 894 std::min(eventDataBytes.size(), eventData.size()), 895 eventData.begin()); 896 897 return ipmi::responseSuccess(nextRecordID, recordID, recordType, 898 oemTsEventType{timestamp, eventData}); 899 } 900 else if (recordType >= intel_oem::ipmi::sel::oemEventFirst && 901 recordType <= intel_oem::ipmi::sel::oemEventLast) 902 { 903 // Only keep the bytes that fit in the record 904 std::array<uint8_t, intel_oem::ipmi::sel::oemEventSize> eventData{}; 905 std::copy_n(eventDataBytes.begin(), 906 std::min(eventDataBytes.size(), eventData.size()), 907 eventData.begin()); 908 909 return ipmi::responseSuccess(nextRecordID, recordID, recordType, 910 eventData); 911 } 912 913 return ipmi::responseUnspecifiedError(); 914 } 915 916 ipmi::RspType<uint16_t> ipmiStorageAddSELEntry( 917 uint16_t recordID, uint8_t recordType, uint32_t timestamp, 918 uint16_t generatorID, uint8_t evmRev, uint8_t sensorType, uint8_t sensorNum, 919 uint8_t eventType, uint8_t eventData1, uint8_t eventData2, 920 uint8_t eventData3) 921 { 922 // Per the IPMI spec, need to cancel any reservation when a SEL entry is 923 // added 924 cancelSELReservation(); 925 926 // Send this request to the Redfish hooks to log it as a Redfish message 927 // instead. There is no need to add it to the SEL, so just return success. 928 intel_oem::ipmi::sel::checkRedfishHooks( 929 recordID, recordType, timestamp, generatorID, evmRev, sensorType, 930 sensorNum, eventType, eventData1, eventData2, eventData3); 931 932 uint16_t responseID = 0xFFFF; 933 return ipmi::responseSuccess(responseID); 934 } 935 936 ipmi::RspType<uint8_t> ipmiStorageClearSEL(ipmi::Context::ptr ctx, 937 uint16_t reservationID, 938 const std::array<uint8_t, 3>& clr, 939 uint8_t eraseOperation) 940 { 941 if (!checkSELReservation(reservationID)) 942 { 943 return ipmi::responseInvalidReservationId(); 944 } 945 946 static constexpr std::array<uint8_t, 3> clrExpected = {'C', 'L', 'R'}; 947 if (clr != clrExpected) 948 { 949 return ipmi::responseInvalidFieldRequest(); 950 } 951 952 // Erasure status cannot be fetched, so always return erasure status as 953 // `erase completed`. 954 if (eraseOperation == ipmi::sel::getEraseStatus) 955 { 956 return ipmi::responseSuccess(ipmi::sel::eraseComplete); 957 } 958 959 // Check that initiate erase is correct 960 if (eraseOperation != ipmi::sel::initiateErase) 961 { 962 return ipmi::responseInvalidFieldRequest(); 963 } 964 965 // Per the IPMI spec, need to cancel any reservation when the SEL is 966 // cleared 967 cancelSELReservation(); 968 969 // Save the erase time 970 intel_oem::ipmi::sel::erase_time::save(); 971 972 // Clear the SEL by deleting the log files 973 std::vector<std::filesystem::path> selLogFiles; 974 if (getSELLogFiles(selLogFiles)) 975 { 976 for (const std::filesystem::path& file : selLogFiles) 977 { 978 std::error_code ec; 979 std::filesystem::remove(file, ec); 980 } 981 } 982 983 // Reload rsyslog so it knows to start new log files 984 std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus(); 985 sdbusplus::message::message rsyslogReload = dbus->new_method_call( 986 "org.freedesktop.systemd1", "/org/freedesktop/systemd1", 987 "org.freedesktop.systemd1.Manager", "ReloadUnit"); 988 rsyslogReload.append("rsyslog.service", "replace"); 989 try 990 { 991 sdbusplus::message::message reloadResponse = dbus->call(rsyslogReload); 992 } 993 catch (sdbusplus::exception_t& e) 994 { 995 phosphor::logging::log<phosphor::logging::level::ERR>(e.what()); 996 } 997 998 return ipmi::responseSuccess(ipmi::sel::eraseComplete); 999 } 1000 1001 ipmi::RspType<uint32_t> ipmiStorageGetSELTime() 1002 { 1003 struct timespec selTime = {}; 1004 1005 if (clock_gettime(CLOCK_REALTIME, &selTime) < 0) 1006 { 1007 return ipmi::responseUnspecifiedError(); 1008 } 1009 1010 return ipmi::responseSuccess(selTime.tv_sec); 1011 } 1012 1013 ipmi::RspType<> ipmiStorageSetSELTime(uint32_t selTime) 1014 { 1015 // Set SEL Time is not supported 1016 return ipmi::responseInvalidCommand(); 1017 } 1018 1019 std::vector<uint8_t> getType12SDRs(uint16_t index, uint16_t recordId) 1020 { 1021 std::vector<uint8_t> resp; 1022 if (index == 0) 1023 { 1024 Type12Record bmc = {}; 1025 bmc.header.record_id_lsb = recordId; 1026 bmc.header.record_id_msb = recordId >> 8; 1027 bmc.header.sdr_version = ipmiSdrVersion; 1028 bmc.header.record_type = 0x12; 1029 bmc.header.record_length = 0x1b; 1030 bmc.slaveAddress = 0x20; 1031 bmc.channelNumber = 0; 1032 bmc.powerStateNotification = 0; 1033 bmc.deviceCapabilities = 0xBF; 1034 bmc.reserved = 0; 1035 bmc.entityID = 0x2E; 1036 bmc.entityInstance = 1; 1037 bmc.oem = 0; 1038 bmc.typeLengthCode = 0xD0; 1039 std::string bmcName = "Basbrd Mgmt Ctlr"; 1040 std::copy(bmcName.begin(), bmcName.end(), bmc.name); 1041 uint8_t* bmcPtr = reinterpret_cast<uint8_t*>(&bmc); 1042 resp.insert(resp.end(), bmcPtr, bmcPtr + sizeof(Type12Record)); 1043 } 1044 else if (index == 1) 1045 { 1046 Type12Record me = {}; 1047 me.header.record_id_lsb = recordId; 1048 me.header.record_id_msb = recordId >> 8; 1049 me.header.sdr_version = ipmiSdrVersion; 1050 me.header.record_type = 0x12; 1051 me.header.record_length = 0x16; 1052 me.slaveAddress = 0x2C; 1053 me.channelNumber = 6; 1054 me.powerStateNotification = 0x24; 1055 me.deviceCapabilities = 0x21; 1056 me.reserved = 0; 1057 me.entityID = 0x2E; 1058 me.entityInstance = 2; 1059 me.oem = 0; 1060 me.typeLengthCode = 0xCB; 1061 std::string meName = "Mgmt Engine"; 1062 std::copy(meName.begin(), meName.end(), me.name); 1063 uint8_t* mePtr = reinterpret_cast<uint8_t*>(&me); 1064 resp.insert(resp.end(), mePtr, mePtr + sizeof(Type12Record)); 1065 } 1066 else 1067 { 1068 throw std::runtime_error("getType12SDRs:: Illegal index " + 1069 std::to_string(index)); 1070 } 1071 1072 return resp; 1073 } 1074 1075 void registerStorageFunctions() 1076 { 1077 // <Get FRU Inventory Area Info> 1078 ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnStorage, 1079 ipmi::storage::cmdGetFruInventoryAreaInfo, 1080 ipmi::Privilege::User, ipmiStorageGetFruInvAreaInfo); 1081 // <READ FRU Data> 1082 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 1083 ipmi::storage::cmdReadFruData, ipmi::Privilege::User, 1084 ipmiStorageReadFruData); 1085 1086 // <WRITE FRU Data> 1087 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 1088 ipmi::storage::cmdWriteFruData, 1089 ipmi::Privilege::Operator, ipmiStorageWriteFruData); 1090 1091 // <Get SEL Info> 1092 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 1093 ipmi::storage::cmdGetSelInfo, ipmi::Privilege::User, 1094 ipmiStorageGetSELInfo); 1095 1096 // <Get SEL Entry> 1097 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 1098 ipmi::storage::cmdGetSelEntry, ipmi::Privilege::User, 1099 ipmiStorageGetSELEntry); 1100 1101 // <Add SEL Entry> 1102 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 1103 ipmi::storage::cmdAddSelEntry, 1104 ipmi::Privilege::Operator, ipmiStorageAddSELEntry); 1105 1106 // <Clear SEL> 1107 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 1108 ipmi::storage::cmdClearSel, ipmi::Privilege::Operator, 1109 ipmiStorageClearSEL); 1110 1111 // <Get SEL Time> 1112 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 1113 ipmi::storage::cmdGetSelTime, ipmi::Privilege::User, 1114 ipmiStorageGetSELTime); 1115 1116 // <Set SEL Time> 1117 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 1118 ipmi::storage::cmdSetSelTime, 1119 ipmi::Privilege::Operator, ipmiStorageSetSELTime); 1120 } 1121 } // namespace storage 1122 } // namespace ipmi 1123