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