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 "storagecommands.hpp" 18 19 #include "commandutils.hpp" 20 #include "fruutils.hpp" 21 #include "ipmi_to_redfish_hooks.hpp" 22 #include "sdrutils.hpp" 23 #include "types.hpp" 24 25 #include <boost/algorithm/string.hpp> 26 #include <boost/container/flat_map.hpp> 27 #include <ipmid/api.hpp> 28 #include <ipmid/message.hpp> 29 #include <phosphor-logging/log.hpp> 30 #include <sdbusplus/message/types.hpp> 31 #include <sdbusplus/timer.hpp> 32 33 #include <filesystem> 34 #include <fstream> 35 #include <iostream> 36 #include <stdexcept> 37 #include <unordered_set> 38 39 static constexpr bool DEBUG = false; 40 41 namespace intel_oem::ipmi::sel 42 { 43 static const std::filesystem::path selLogDir = "/var/log"; 44 static const std::string selLogFilename = "ipmi_sel"; 45 46 static int getFileTimestamp(const std::filesystem::path& file) 47 { 48 struct stat st; 49 50 if (stat(file.c_str(), &st) >= 0) 51 { 52 return st.st_mtime; 53 } 54 return ::ipmi::sel::invalidTimeStamp; 55 } 56 57 namespace erase_time 58 { 59 static constexpr const char* selEraseTimestamp = "/var/lib/ipmi/sel_erase_time"; 60 61 void save() 62 { 63 // open the file, creating it if necessary 64 int fd = open(selEraseTimestamp, O_WRONLY | O_CREAT | O_CLOEXEC, 0644); 65 if (fd < 0) 66 { 67 std::cerr << "Failed to open file\n"; 68 return; 69 } 70 71 // update the file timestamp to the current time 72 if (futimens(fd, NULL) < 0) 73 { 74 std::cerr << "Failed to update timestamp: " 75 << std::string(strerror(errno)); 76 } 77 close(fd); 78 } 79 80 int get() 81 { 82 return getFileTimestamp(selEraseTimestamp); 83 } 84 } // namespace erase_time 85 } // namespace intel_oem::ipmi::sel 86 87 namespace ipmi 88 { 89 90 namespace storage 91 { 92 93 constexpr static const size_t maxMessageSize = 64; 94 constexpr static const size_t maxFruSdrNameSize = 16; 95 using ObjectType = boost::container::flat_map< 96 std::string, boost::container::flat_map<std::string, DbusVariant>>; 97 using ManagedObjectType = 98 boost::container::flat_map<sdbusplus::message::object_path, ObjectType>; 99 using ManagedEntry = std::pair<sdbusplus::message::object_path, ObjectType>; 100 using GetObjectType = 101 std::vector<std::pair<std::string, std::vector<std::string>>>; 102 103 constexpr static const char* fruDeviceServiceName = 104 "xyz.openbmc_project.FruDevice"; 105 constexpr static const size_t writeTimeoutSeconds = 10; 106 constexpr static const char* chassisTypeRackMount = "23"; 107 108 // event direction is bit[7] of eventType where 1b = Deassertion event 109 constexpr static const uint8_t deassertionEvent = 0x80; 110 111 static std::vector<uint8_t> fruCache; 112 static uint16_t cacheBus = 0xFFFF; 113 static uint8_t cacheAddr = 0XFF; 114 static uint8_t lastDevId = 0xFF; 115 116 static uint16_t writeBus = 0xFFFF; 117 static uint8_t writeAddr = 0XFF; 118 119 std::unique_ptr<sdbusplus::Timer> writeTimer = nullptr; 120 static std::vector<sdbusplus::bus::match_t> fruMatches; 121 122 ManagedObjectType frus; 123 124 // we unfortunately have to build a map of hashes in case there is a 125 // collision to verify our dev-id 126 boost::container::flat_map<uint8_t, std::pair<uint16_t, uint8_t>> deviceHashes; 127 // Map devId to Object Path 128 boost::container::flat_map<uint8_t, std::string> devicePath; 129 130 void registerStorageFunctions() __attribute__((constructor)); 131 132 bool writeFru() 133 { 134 if (writeBus == 0xFFFF && writeAddr == 0xFF) 135 { 136 return true; 137 } 138 std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus(); 139 sdbusplus::message_t writeFru = dbus->new_method_call( 140 fruDeviceServiceName, "/xyz/openbmc_project/FruDevice", 141 "xyz.openbmc_project.FruDeviceManager", "WriteFru"); 142 writeFru.append(writeBus, writeAddr, fruCache); 143 try 144 { 145 sdbusplus::message_t writeFruResp = dbus->call(writeFru); 146 } 147 catch (const sdbusplus::exception_t&) 148 { 149 // todo: log sel? 150 phosphor::logging::log<phosphor::logging::level::ERR>( 151 "error writing fru"); 152 return false; 153 } 154 writeBus = 0xFFFF; 155 writeAddr = 0xFF; 156 return true; 157 } 158 159 void createTimers() 160 { 161 writeTimer = std::make_unique<sdbusplus::Timer>(writeFru); 162 } 163 164 void recalculateHashes() 165 { 166 deviceHashes.clear(); 167 devicePath.clear(); 168 // hash the object paths to create unique device id's. increment on 169 // collision 170 std::hash<std::string> hasher; 171 for (const auto& fru : frus) 172 { 173 auto fruIface = fru.second.find("xyz.openbmc_project.FruDevice"); 174 if (fruIface == fru.second.end()) 175 { 176 continue; 177 } 178 179 auto busFind = fruIface->second.find("BUS"); 180 auto addrFind = fruIface->second.find("ADDRESS"); 181 if (busFind == fruIface->second.end() || 182 addrFind == fruIface->second.end()) 183 { 184 phosphor::logging::log<phosphor::logging::level::INFO>( 185 "fru device missing Bus or Address", 186 phosphor::logging::entry("FRU=%s", fru.first.str.c_str())); 187 continue; 188 } 189 190 uint16_t fruBus = std::get<uint32_t>(busFind->second); 191 uint8_t fruAddr = std::get<uint32_t>(addrFind->second); 192 auto chassisFind = fruIface->second.find("CHASSIS_TYPE"); 193 std::string chassisType; 194 if (chassisFind != fruIface->second.end()) 195 { 196 chassisType = std::get<std::string>(chassisFind->second); 197 } 198 199 uint8_t fruHash = 0; 200 if (chassisType.compare(chassisTypeRackMount) != 0) 201 { 202 fruHash = hasher(fru.first.str); 203 // can't be 0xFF based on spec, and 0 is reserved for baseboard 204 if (fruHash == 0 || fruHash == 0xFF) 205 { 206 fruHash = 1; 207 } 208 } 209 std::pair<uint16_t, uint8_t> newDev(fruBus, fruAddr); 210 211 bool emplacePassed = false; 212 while (!emplacePassed) 213 { 214 auto resp = deviceHashes.emplace(fruHash, newDev); 215 216 devicePath.emplace(fruHash, fru.first); 217 218 emplacePassed = resp.second; 219 if (!emplacePassed) 220 { 221 fruHash++; 222 // can't be 0xFF based on spec, and 0 is reserved for 223 // baseboard 224 if (fruHash == 0XFF) 225 { 226 fruHash = 0x1; 227 } 228 } 229 } 230 } 231 } 232 233 void replaceCacheFru(const std::shared_ptr<sdbusplus::asio::connection>& bus, 234 boost::asio::yield_context& yield) 235 { 236 boost::system::error_code ec; 237 // ObjectPaths and Services which implements "xyz.openbmc_project.FruDevice" 238 // interface 239 GetSubTreeType fruServices = bus->yield_method_call<GetSubTreeType>( 240 yield, ec, "xyz.openbmc_project.ObjectMapper", 241 "/xyz/openbmc_project/object_mapper", 242 "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 0, 243 std::array<const char*, 1>{"xyz.openbmc_project.FruDevice"}); 244 245 if (ec) 246 { 247 phosphor::logging::log<phosphor::logging::level::ERR>( 248 "GetSubTree failed for FruDevice Interface ", 249 phosphor::logging::entry("ERROR=%s", ec.message().c_str())); 250 251 return; 252 } 253 // Get List of services which have implemented FruDevice interface 254 std::unordered_set<std::string> services; 255 for (const auto& [path, serviceMap] : fruServices) 256 { 257 for (const auto& [service, interfaces] : serviceMap) 258 { 259 services.insert(service); 260 } 261 } 262 263 // GetAll the objects under services which implement FruDevice interface 264 for (const std::string& service : services) 265 { 266 ec = boost::system::errc::make_error_code(boost::system::errc::success); 267 ManagedObjectType obj = bus->yield_method_call<ManagedObjectType>( 268 yield, ec, service, "/", "org.freedesktop.DBus.ObjectManager", 269 "GetManagedObjects"); 270 if (ec) 271 { 272 phosphor::logging::log<phosphor::logging::level::ERR>( 273 "GetMangagedObjects failed", 274 phosphor::logging::entry("ERROR=%s", ec.message().c_str())); 275 continue; 276 } 277 // Save the object path which has FruDevice interface 278 for (const auto& [path, serviceMap] : fruServices) 279 { 280 for (const auto& serv : serviceMap) 281 { 282 if (serv.first == service) 283 { 284 auto fru = obj.find(path); 285 if (fru == obj.end()) 286 { 287 continue; 288 } 289 frus.emplace(fru->first, fru->second); 290 } 291 } 292 } 293 } 294 295 recalculateHashes(); 296 } 297 298 ipmi::Cc getFru(ipmi::Context::ptr& ctx, uint8_t devId) 299 { 300 if (lastDevId == devId && devId != 0xFF) 301 { 302 return ipmi::ccSuccess; 303 } 304 305 auto deviceFind = deviceHashes.find(devId); 306 auto devPath = devicePath.find(devId); 307 if (deviceFind == deviceHashes.end() || devPath == devicePath.end()) 308 { 309 return IPMI_CC_SENSOR_INVALID; 310 } 311 312 if (writeTimer->isRunning()) 313 { 314 phosphor::logging::log<phosphor::logging::level::ERR>( 315 "Couldn't get raw fru as fru is updating"); 316 return ipmi::ccBusy; 317 } 318 fruCache.clear(); 319 320 cacheBus = deviceFind->second.first; 321 cacheAddr = deviceFind->second.second; 322 323 boost::system::error_code ec; 324 GetObjectType fruService = ctx->bus->yield_method_call<GetObjectType>( 325 ctx->yield, ec, "xyz.openbmc_project.ObjectMapper", 326 "/xyz/openbmc_project/object_mapper", 327 "xyz.openbmc_project.ObjectMapper", "GetObject", devPath->second, 328 std::array<const char*, 1>{"xyz.openbmc_project.FruDevice"}); 329 330 if (ec) 331 { 332 phosphor::logging::log<phosphor::logging::level::ERR>( 333 "Couldn't get raw fru because of service", 334 phosphor::logging::entry("ERROR=%s", ec.message().c_str())); 335 return ipmi::ccResponseError; 336 } 337 338 bool foundFru = false; 339 for (auto& service : fruService) 340 { 341 fruCache = ctx->bus->yield_method_call<std::vector<uint8_t>>( 342 ctx->yield, ec, service.first, "/xyz/openbmc_project/FruDevice", 343 "xyz.openbmc_project.FruDeviceManager", "GetRawFru", cacheBus, 344 cacheAddr); 345 346 if (!ec) 347 { 348 foundFru = true; 349 break; 350 } 351 } 352 353 if (!foundFru) 354 { 355 phosphor::logging::log<phosphor::logging::level::ERR>( 356 "Couldn't get raw fru", 357 phosphor::logging::entry("ERROR=%s", ec.message().c_str())); 358 cacheBus = 0xFFFF; 359 cacheAddr = 0xFF; 360 return ipmi::ccResponseError; 361 } 362 363 lastDevId = devId; 364 return ipmi::ccSuccess; 365 } 366 367 void writeFruIfRunning() 368 { 369 if (!writeTimer->isRunning()) 370 { 371 return; 372 } 373 writeTimer->stop(); 374 writeFru(); 375 } 376 377 void startMatch(void) 378 { 379 if (fruMatches.size()) 380 { 381 return; 382 } 383 384 fruMatches.reserve(2); 385 386 auto bus = getSdBus(); 387 fruMatches.emplace_back(*bus, 388 "type='signal',arg0path='/xyz/openbmc_project/" 389 "FruDevice/',member='InterfacesAdded'", 390 [](sdbusplus::message_t& message) { 391 sdbusplus::message::object_path path; 392 ObjectType object; 393 try 394 { 395 message.read(path, object); 396 } 397 catch (const sdbusplus::exception_t&) 398 { 399 return; 400 } 401 auto findType = object.find("xyz.openbmc_project.FruDevice"); 402 if (findType == object.end()) 403 { 404 return; 405 } 406 writeFruIfRunning(); 407 frus[path] = object; 408 recalculateHashes(); 409 lastDevId = 0xFF; 410 }); 411 412 fruMatches.emplace_back(*bus, 413 "type='signal',arg0path='/xyz/openbmc_project/" 414 "FruDevice/',member='InterfacesRemoved'", 415 [](sdbusplus::message_t& message) { 416 sdbusplus::message::object_path path; 417 std::set<std::string> interfaces; 418 try 419 { 420 message.read(path, interfaces); 421 } 422 catch (const sdbusplus::exception_t&) 423 { 424 return; 425 } 426 auto findType = interfaces.find("xyz.openbmc_project.FruDevice"); 427 if (findType == interfaces.end()) 428 { 429 return; 430 } 431 writeFruIfRunning(); 432 frus.erase(path); 433 recalculateHashes(); 434 lastDevId = 0xFF; 435 }); 436 437 // call once to populate 438 boost::asio::spawn(*getIoContext(), [](boost::asio::yield_context yield) { 439 replaceCacheFru(getSdBus(), yield); 440 }); 441 } 442 443 /** @brief implements the read FRU data command 444 * @param fruDeviceId - FRU Device ID 445 * @param fruInventoryOffset - FRU Inventory Offset to write 446 * @param countToRead - Count to read 447 * 448 * @returns ipmi completion code plus response data 449 * - countWritten - Count written 450 */ 451 ipmi::RspType<uint8_t, // Count 452 std::vector<uint8_t> // Requested data 453 > 454 ipmiStorageReadFruData(ipmi::Context::ptr& ctx, uint8_t fruDeviceId, 455 uint16_t fruInventoryOffset, uint8_t countToRead) 456 { 457 if (fruDeviceId == 0xFF) 458 { 459 return ipmi::responseInvalidFieldRequest(); 460 } 461 462 ipmi::Cc status = getFru(ctx, fruDeviceId); 463 464 if (status != ipmi::ccSuccess) 465 { 466 return ipmi::response(status); 467 } 468 469 size_t fromFruByteLen = 0; 470 if (countToRead + fruInventoryOffset < fruCache.size()) 471 { 472 fromFruByteLen = countToRead; 473 } 474 else if (fruCache.size() > fruInventoryOffset) 475 { 476 fromFruByteLen = fruCache.size() - fruInventoryOffset; 477 } 478 else 479 { 480 return ipmi::responseReqDataLenExceeded(); 481 } 482 483 std::vector<uint8_t> requestedData; 484 485 requestedData.insert( 486 requestedData.begin(), fruCache.begin() + fruInventoryOffset, 487 fruCache.begin() + fruInventoryOffset + fromFruByteLen); 488 489 return ipmi::responseSuccess(static_cast<uint8_t>(requestedData.size()), 490 requestedData); 491 } 492 493 /** @brief implements the write FRU data command 494 * @param fruDeviceId - FRU Device ID 495 * @param fruInventoryOffset - FRU Inventory Offset to write 496 * @param dataToWrite - Data to write 497 * 498 * @returns ipmi completion code plus response data 499 * - countWritten - Count written 500 */ 501 ipmi::RspType<uint8_t> 502 ipmiStorageWriteFruData(ipmi::Context::ptr& ctx, uint8_t fruDeviceId, 503 uint16_t fruInventoryOffset, 504 std::vector<uint8_t>& dataToWrite) 505 { 506 if (fruDeviceId == 0xFF) 507 { 508 return ipmi::responseInvalidFieldRequest(); 509 } 510 511 size_t writeLen = dataToWrite.size(); 512 513 ipmi::Cc status = getFru(ctx, fruDeviceId); 514 if (status != ipmi::ccSuccess) 515 { 516 return ipmi::response(status); 517 } 518 size_t lastWriteAddr = fruInventoryOffset + writeLen; 519 if (fruCache.size() < lastWriteAddr) 520 { 521 fruCache.resize(fruInventoryOffset + writeLen); 522 } 523 524 std::copy(dataToWrite.begin(), dataToWrite.begin() + writeLen, 525 fruCache.begin() + fruInventoryOffset); 526 527 bool atEnd = validateBasicFruContent(fruCache, lastWriteAddr); 528 uint8_t countWritten = 0; 529 530 writeBus = cacheBus; 531 writeAddr = cacheAddr; 532 if (atEnd) 533 { 534 // cancel timer, we're at the end so might as well send it 535 writeTimer->stop(); 536 if (!writeFru()) 537 { 538 return ipmi::responseInvalidFieldRequest(); 539 } 540 countWritten = std::min(fruCache.size(), static_cast<size_t>(0xFF)); 541 } 542 else 543 { 544 // start a timer, if no further data is sent to check to see if it is 545 // valid 546 writeTimer->start(std::chrono::duration_cast<std::chrono::microseconds>( 547 std::chrono::seconds(writeTimeoutSeconds))); 548 countWritten = 0; 549 } 550 551 return ipmi::responseSuccess(countWritten); 552 } 553 554 /** @brief implements the get FRU inventory area info command 555 * @param fruDeviceId - FRU Device ID 556 * 557 * @returns IPMI completion code plus response data 558 * - inventorySize - Number of possible allocation units 559 * - accessType - Allocation unit size in bytes. 560 */ 561 ipmi::RspType<uint16_t, // inventorySize 562 uint8_t> // accessType 563 ipmiStorageGetFruInvAreaInfo(ipmi::Context::ptr& ctx, uint8_t fruDeviceId) 564 { 565 if (fruDeviceId == 0xFF) 566 { 567 return ipmi::responseInvalidFieldRequest(); 568 } 569 570 ipmi::Cc ret = getFru(ctx, fruDeviceId); 571 if (ret != ipmi::ccSuccess) 572 { 573 return ipmi::response(ret); 574 } 575 576 constexpr uint8_t accessType = 577 static_cast<uint8_t>(GetFRUAreaAccessType::byte); 578 579 return ipmi::responseSuccess(fruCache.size(), accessType); 580 } 581 582 ipmi::Cc getFruSdrCount(ipmi::Context::ptr&, size_t& count) 583 { 584 count = deviceHashes.size(); 585 return ipmi::ccSuccess; 586 } 587 588 ipmi::Cc getFruSdrs(ipmi::Context::ptr& ctx, size_t index, 589 get_sdr::SensorDataFruRecord& resp) 590 { 591 if (deviceHashes.size() < index) 592 { 593 return ipmi::ccInvalidFieldRequest; 594 } 595 auto device = deviceHashes.begin() + index; 596 uint16_t& bus = device->second.first; 597 uint8_t& address = device->second.second; 598 599 boost::container::flat_map<std::string, DbusVariant>* fruData = nullptr; 600 auto fru = std::find_if(frus.begin(), frus.end(), 601 [bus, address, &fruData](ManagedEntry& entry) { 602 auto findFruDevice = entry.second.find("xyz.openbmc_project.FruDevice"); 603 if (findFruDevice == entry.second.end()) 604 { 605 return false; 606 } 607 fruData = &(findFruDevice->second); 608 auto findBus = findFruDevice->second.find("BUS"); 609 auto findAddress = findFruDevice->second.find("ADDRESS"); 610 if (findBus == findFruDevice->second.end() || 611 findAddress == findFruDevice->second.end()) 612 { 613 return false; 614 } 615 if (std::get<uint32_t>(findBus->second) != bus) 616 { 617 return false; 618 } 619 if (std::get<uint32_t>(findAddress->second) != address) 620 { 621 return false; 622 } 623 return true; 624 }); 625 if (fru == frus.end()) 626 { 627 return ipmi::ccResponseError; 628 } 629 630 #ifdef USING_ENTITY_MANAGER_DECORATORS 631 632 boost::container::flat_map<std::string, DbusVariant>* entityData = nullptr; 633 634 // todo: this should really use caching, this is a very inefficient lookup 635 boost::system::error_code ec; 636 ManagedObjectType entities = ctx->bus->yield_method_call<ManagedObjectType>( 637 ctx->yield, ec, "xyz.openbmc_project.EntityManager", 638 "/xyz/openbmc_project/inventory", "org.freedesktop.DBus.ObjectManager", 639 "GetManagedObjects"); 640 641 if (ec) 642 { 643 phosphor::logging::log<phosphor::logging::level::ERR>( 644 "GetMangagedObjects for getSensorMap failed", 645 phosphor::logging::entry("ERROR=%s", ec.message().c_str())); 646 647 return ipmi::ccResponseError; 648 } 649 650 auto entity = 651 std::find_if(entities.begin(), entities.end(), 652 [bus, address, &entityData](ManagedEntry& entry) { 653 auto findFruDevice = entry.second.find( 654 "xyz.openbmc_project.Inventory.Decorator.FruDevice"); 655 if (findFruDevice == entry.second.end()) 656 { 657 return false; 658 } 659 660 // Integer fields added via Entity-Manager json are uint64_ts by 661 // default. 662 auto findBus = findFruDevice->second.find("Bus"); 663 auto findAddress = findFruDevice->second.find("Address"); 664 665 if (findBus == findFruDevice->second.end() || 666 findAddress == findFruDevice->second.end()) 667 { 668 return false; 669 } 670 if ((std::get<uint64_t>(findBus->second) != bus) || 671 (std::get<uint64_t>(findAddress->second) != address)) 672 { 673 return false; 674 } 675 676 // At this point we found the device entry and should return 677 // true. 678 auto findIpmiDevice = 679 entry.second.find("xyz.openbmc_project.Inventory.Decorator.Ipmi"); 680 if (findIpmiDevice != entry.second.end()) 681 { 682 entityData = &(findIpmiDevice->second); 683 } 684 685 return true; 686 }); 687 688 if (entity == entities.end()) 689 { 690 if constexpr (DEBUG) 691 { 692 std::fprintf(stderr, "Ipmi or FruDevice Decorator interface " 693 "not found for Fru\n"); 694 } 695 } 696 697 #endif 698 699 std::string name; 700 auto findProductName = fruData->find("BOARD_PRODUCT_NAME"); 701 auto findBoardName = fruData->find("PRODUCT_PRODUCT_NAME"); 702 if (findProductName != fruData->end()) 703 { 704 name = std::get<std::string>(findProductName->second); 705 } 706 else if (findBoardName != fruData->end()) 707 { 708 name = std::get<std::string>(findBoardName->second); 709 } 710 else 711 { 712 name = "UNKNOWN"; 713 } 714 if (name.size() > maxFruSdrNameSize) 715 { 716 name = name.substr(0, maxFruSdrNameSize); 717 } 718 size_t sizeDiff = maxFruSdrNameSize - name.size(); 719 720 resp.header.record_id_lsb = 0x0; // calling code is to implement these 721 resp.header.record_id_msb = 0x0; 722 resp.header.sdr_version = ipmiSdrVersion; 723 resp.header.record_type = get_sdr::SENSOR_DATA_FRU_RECORD; 724 resp.header.record_length = sizeof(resp.body) + sizeof(resp.key) - sizeDiff; 725 resp.key.deviceAddress = 0x20; 726 resp.key.fruID = device->first; 727 resp.key.accessLun = 0x80; // logical / physical fru device 728 resp.key.channelNumber = 0x0; 729 resp.body.reserved = 0x0; 730 resp.body.deviceType = 0x10; 731 resp.body.deviceTypeModifier = 0x0; 732 733 uint8_t entityID = 0; 734 uint8_t entityInstance = 0x1; 735 736 #ifdef USING_ENTITY_MANAGER_DECORATORS 737 if (entityData) 738 { 739 auto entityIdProperty = entityData->find("EntityId"); 740 auto entityInstanceProperty = entityData->find("EntityInstance"); 741 742 if (entityIdProperty != entityData->end()) 743 { 744 entityID = static_cast<uint8_t>( 745 std::get<uint64_t>(entityIdProperty->second)); 746 } 747 if (entityInstanceProperty != entityData->end()) 748 { 749 entityInstance = static_cast<uint8_t>( 750 std::get<uint64_t>(entityInstanceProperty->second)); 751 } 752 } 753 #endif 754 755 resp.body.entityID = entityID; 756 resp.body.entityInstance = entityInstance; 757 758 resp.body.oem = 0x0; 759 resp.body.deviceIDLen = name.size(); 760 name.copy(resp.body.deviceID, name.size()); 761 762 return ipmi::ccSuccess; 763 } 764 765 static bool getSELLogFiles(std::vector<std::filesystem::path>& selLogFiles) 766 { 767 // Loop through the directory looking for ipmi_sel log files 768 for (const std::filesystem::directory_entry& dirEnt : 769 std::filesystem::directory_iterator(intel_oem::ipmi::sel::selLogDir)) 770 { 771 std::string filename = dirEnt.path().filename(); 772 if (boost::starts_with(filename, intel_oem::ipmi::sel::selLogFilename)) 773 { 774 // If we find an ipmi_sel log file, save the path 775 selLogFiles.emplace_back(intel_oem::ipmi::sel::selLogDir / 776 filename); 777 } 778 } 779 // As the log files rotate, they are appended with a ".#" that is higher for 780 // the older logs. Since we don't expect more than 10 log files, we 781 // can just sort the list to get them in order from newest to oldest 782 std::sort(selLogFiles.begin(), selLogFiles.end()); 783 784 return !selLogFiles.empty(); 785 } 786 787 static int countSELEntries() 788 { 789 // Get the list of ipmi_sel log files 790 std::vector<std::filesystem::path> selLogFiles; 791 if (!getSELLogFiles(selLogFiles)) 792 { 793 return 0; 794 } 795 int numSELEntries = 0; 796 // Loop through each log file and count the number of logs 797 for (const std::filesystem::path& file : selLogFiles) 798 { 799 std::ifstream logStream(file); 800 if (!logStream.is_open()) 801 { 802 continue; 803 } 804 805 std::string line; 806 while (std::getline(logStream, line)) 807 { 808 numSELEntries++; 809 } 810 } 811 return numSELEntries; 812 } 813 814 static bool findSELEntry(const int recordID, 815 const std::vector<std::filesystem::path>& selLogFiles, 816 std::string& entry) 817 { 818 // Record ID is the first entry field following the timestamp. It is 819 // preceded by a space and followed by a comma 820 std::string search = " " + std::to_string(recordID) + ","; 821 822 // Loop through the ipmi_sel log entries 823 for (const std::filesystem::path& file : selLogFiles) 824 { 825 std::ifstream logStream(file); 826 if (!logStream.is_open()) 827 { 828 continue; 829 } 830 831 while (std::getline(logStream, entry)) 832 { 833 // Check if the record ID matches 834 if (entry.find(search) != std::string::npos) 835 { 836 return true; 837 } 838 } 839 } 840 return false; 841 } 842 843 static uint16_t 844 getNextRecordID(const uint16_t recordID, 845 const std::vector<std::filesystem::path>& selLogFiles) 846 { 847 uint16_t nextRecordID = recordID + 1; 848 std::string entry; 849 if (findSELEntry(nextRecordID, selLogFiles, entry)) 850 { 851 return nextRecordID; 852 } 853 else 854 { 855 return ipmi::sel::lastEntry; 856 } 857 } 858 859 static int fromHexStr(const std::string& hexStr, std::vector<uint8_t>& data) 860 { 861 for (unsigned int i = 0; i < hexStr.size(); i += 2) 862 { 863 try 864 { 865 data.push_back(static_cast<uint8_t>( 866 std::stoul(hexStr.substr(i, 2), nullptr, 16))); 867 } 868 catch (const std::invalid_argument& e) 869 { 870 phosphor::logging::log<phosphor::logging::level::ERR>(e.what()); 871 return -1; 872 } 873 catch (const std::out_of_range& e) 874 { 875 phosphor::logging::log<phosphor::logging::level::ERR>(e.what()); 876 return -1; 877 } 878 } 879 return 0; 880 } 881 882 ipmi::RspType<uint8_t, // SEL version 883 uint16_t, // SEL entry count 884 uint16_t, // free space 885 uint32_t, // last add timestamp 886 uint32_t, // last erase timestamp 887 uint8_t> // operation support 888 ipmiStorageGetSELInfo() 889 { 890 constexpr uint8_t selVersion = ipmi::sel::selVersion; 891 uint16_t entries = countSELEntries(); 892 uint32_t addTimeStamp = intel_oem::ipmi::sel::getFileTimestamp( 893 intel_oem::ipmi::sel::selLogDir / intel_oem::ipmi::sel::selLogFilename); 894 uint32_t eraseTimeStamp = intel_oem::ipmi::sel::erase_time::get(); 895 constexpr uint8_t operationSupport = 896 intel_oem::ipmi::sel::selOperationSupport; 897 constexpr uint16_t freeSpace = 898 0xffff; // Spec indicates that more than 64kB is free 899 900 return ipmi::responseSuccess(selVersion, entries, freeSpace, addTimeStamp, 901 eraseTimeStamp, operationSupport); 902 } 903 904 using systemEventType = std::tuple< 905 uint32_t, // Timestamp 906 uint16_t, // Generator ID 907 uint8_t, // EvM Rev 908 uint8_t, // Sensor Type 909 uint8_t, // Sensor Number 910 uint7_t, // Event Type 911 bool, // Event Direction 912 std::array<uint8_t, intel_oem::ipmi::sel::systemEventSize>>; // Event Data 913 using oemTsEventType = std::tuple< 914 uint32_t, // Timestamp 915 std::array<uint8_t, intel_oem::ipmi::sel::oemTsEventSize>>; // Event Data 916 using oemEventType = 917 std::array<uint8_t, intel_oem::ipmi::sel::oemEventSize>; // Event Data 918 919 ipmi::RspType<uint16_t, // Next Record ID 920 uint16_t, // Record ID 921 uint8_t, // Record Type 922 std::variant<systemEventType, oemTsEventType, 923 oemEventType>> // Record Content 924 ipmiStorageGetSELEntry(uint16_t reservationID, uint16_t targetID, 925 uint8_t offset, uint8_t size) 926 { 927 // Only support getting the entire SEL record. If a partial size or non-zero 928 // offset is requested, return an error 929 if (offset != 0 || size != ipmi::sel::entireRecord) 930 { 931 return ipmi::responseRetBytesUnavailable(); 932 } 933 934 // Check the reservation ID if one is provided or required (only if the 935 // offset is non-zero) 936 if (reservationID != 0 || offset != 0) 937 { 938 if (!checkSELReservation(reservationID)) 939 { 940 return ipmi::responseInvalidReservationId(); 941 } 942 } 943 944 // Get the ipmi_sel log files 945 std::vector<std::filesystem::path> selLogFiles; 946 if (!getSELLogFiles(selLogFiles)) 947 { 948 return ipmi::responseSensorInvalid(); 949 } 950 951 std::string targetEntry; 952 953 if (targetID == ipmi::sel::firstEntry) 954 { 955 // The first entry will be at the top of the oldest log file 956 std::ifstream logStream(selLogFiles.back()); 957 if (!logStream.is_open()) 958 { 959 return ipmi::responseUnspecifiedError(); 960 } 961 962 if (!std::getline(logStream, targetEntry)) 963 { 964 return ipmi::responseUnspecifiedError(); 965 } 966 } 967 else if (targetID == ipmi::sel::lastEntry) 968 { 969 // The last entry will be at the bottom of the newest log file 970 std::ifstream logStream(selLogFiles.front()); 971 if (!logStream.is_open()) 972 { 973 return ipmi::responseUnspecifiedError(); 974 } 975 976 std::string line; 977 while (std::getline(logStream, line)) 978 { 979 targetEntry = line; 980 } 981 } 982 else 983 { 984 if (!findSELEntry(targetID, selLogFiles, targetEntry)) 985 { 986 return ipmi::responseSensorInvalid(); 987 } 988 } 989 990 // The format of the ipmi_sel message is "<Timestamp> 991 // <ID>,<Type>,<EventData>,[<Generator ID>,<Path>,<Direction>]". 992 // First get the Timestamp 993 size_t space = targetEntry.find_first_of(" "); 994 if (space == std::string::npos) 995 { 996 return ipmi::responseUnspecifiedError(); 997 } 998 std::string entryTimestamp = targetEntry.substr(0, space); 999 // Then get the log contents 1000 size_t entryStart = targetEntry.find_first_not_of(" ", space); 1001 if (entryStart == std::string::npos) 1002 { 1003 return ipmi::responseUnspecifiedError(); 1004 } 1005 std::string_view entry(targetEntry); 1006 entry.remove_prefix(entryStart); 1007 // Use split to separate the entry into its fields 1008 std::vector<std::string> targetEntryFields; 1009 boost::split(targetEntryFields, entry, boost::is_any_of(","), 1010 boost::token_compress_on); 1011 if (targetEntryFields.size() < 3) 1012 { 1013 return ipmi::responseUnspecifiedError(); 1014 } 1015 std::string& recordIDStr = targetEntryFields[0]; 1016 std::string& recordTypeStr = targetEntryFields[1]; 1017 std::string& eventDataStr = targetEntryFields[2]; 1018 1019 uint16_t recordID; 1020 uint8_t recordType; 1021 try 1022 { 1023 recordID = std::stoul(recordIDStr); 1024 recordType = std::stoul(recordTypeStr, nullptr, 16); 1025 } 1026 catch (const std::invalid_argument&) 1027 { 1028 return ipmi::responseUnspecifiedError(); 1029 } 1030 uint16_t nextRecordID = getNextRecordID(recordID, selLogFiles); 1031 std::vector<uint8_t> eventDataBytes; 1032 if (fromHexStr(eventDataStr, eventDataBytes) < 0) 1033 { 1034 return ipmi::responseUnspecifiedError(); 1035 } 1036 1037 if (recordType == intel_oem::ipmi::sel::systemEvent) 1038 { 1039 // Get the timestamp 1040 std::tm timeStruct = {}; 1041 std::istringstream entryStream(entryTimestamp); 1042 1043 uint32_t timestamp = ipmi::sel::invalidTimeStamp; 1044 if (entryStream >> std::get_time(&timeStruct, "%Y-%m-%dT%H:%M:%S")) 1045 { 1046 timestamp = std::mktime(&timeStruct); 1047 } 1048 1049 // Set the event message revision 1050 uint8_t evmRev = intel_oem::ipmi::sel::eventMsgRev; 1051 1052 uint16_t generatorID = 0; 1053 uint8_t sensorType = 0; 1054 uint16_t sensorAndLun = 0; 1055 uint8_t sensorNum = 0xFF; 1056 uint7_t eventType = 0; 1057 bool eventDir = 0; 1058 // System type events should have six fields 1059 if (targetEntryFields.size() >= 6) 1060 { 1061 std::string& generatorIDStr = targetEntryFields[3]; 1062 std::string& sensorPath = targetEntryFields[4]; 1063 std::string& eventDirStr = targetEntryFields[5]; 1064 1065 // Get the generator ID 1066 try 1067 { 1068 generatorID = std::stoul(generatorIDStr, nullptr, 16); 1069 } 1070 catch (const std::invalid_argument&) 1071 { 1072 std::cerr << "Invalid Generator ID\n"; 1073 } 1074 1075 // Get the sensor type, sensor number, and event type for the sensor 1076 sensorType = getSensorTypeFromPath(sensorPath); 1077 sensorAndLun = getSensorNumberFromPath(sensorPath); 1078 sensorNum = static_cast<uint8_t>(sensorAndLun); 1079 generatorID |= sensorAndLun >> 8; 1080 eventType = getSensorEventTypeFromPath(sensorPath); 1081 1082 // Get the event direction 1083 try 1084 { 1085 eventDir = std::stoul(eventDirStr) ? 0 : 1; 1086 } 1087 catch (const std::invalid_argument&) 1088 { 1089 std::cerr << "Invalid Event Direction\n"; 1090 } 1091 } 1092 1093 // Only keep the eventData bytes that fit in the record 1094 std::array<uint8_t, intel_oem::ipmi::sel::systemEventSize> eventData{}; 1095 std::copy_n(eventDataBytes.begin(), 1096 std::min(eventDataBytes.size(), eventData.size()), 1097 eventData.begin()); 1098 1099 return ipmi::responseSuccess( 1100 nextRecordID, recordID, recordType, 1101 systemEventType{timestamp, generatorID, evmRev, sensorType, 1102 sensorNum, eventType, eventDir, eventData}); 1103 } 1104 else if (recordType >= intel_oem::ipmi::sel::oemTsEventFirst && 1105 recordType <= intel_oem::ipmi::sel::oemTsEventLast) 1106 { 1107 // Get the timestamp 1108 std::tm timeStruct = {}; 1109 std::istringstream entryStream(entryTimestamp); 1110 1111 uint32_t timestamp = ipmi::sel::invalidTimeStamp; 1112 if (entryStream >> std::get_time(&timeStruct, "%Y-%m-%dT%H:%M:%S")) 1113 { 1114 timestamp = std::mktime(&timeStruct); 1115 } 1116 1117 // Only keep the bytes that fit in the record 1118 std::array<uint8_t, intel_oem::ipmi::sel::oemTsEventSize> eventData{}; 1119 std::copy_n(eventDataBytes.begin(), 1120 std::min(eventDataBytes.size(), eventData.size()), 1121 eventData.begin()); 1122 1123 return ipmi::responseSuccess(nextRecordID, recordID, recordType, 1124 oemTsEventType{timestamp, eventData}); 1125 } 1126 else if (recordType >= intel_oem::ipmi::sel::oemEventFirst) 1127 { 1128 // Only keep the bytes that fit in the record 1129 std::array<uint8_t, intel_oem::ipmi::sel::oemEventSize> eventData{}; 1130 std::copy_n(eventDataBytes.begin(), 1131 std::min(eventDataBytes.size(), eventData.size()), 1132 eventData.begin()); 1133 1134 return ipmi::responseSuccess(nextRecordID, recordID, recordType, 1135 eventData); 1136 } 1137 1138 return ipmi::responseUnspecifiedError(); 1139 } 1140 1141 ipmi::RspType<uint16_t> ipmiStorageAddSELEntry( 1142 uint16_t recordID, uint8_t recordType, uint32_t timestamp, 1143 uint16_t generatorID, uint8_t evmRev, uint8_t sensorType, uint8_t sensorNum, 1144 uint8_t eventType, uint8_t eventData1, uint8_t eventData2, 1145 uint8_t eventData3) 1146 { 1147 // Per the IPMI spec, need to cancel any reservation when a SEL entry is 1148 // added 1149 cancelSELReservation(); 1150 1151 // Send this request to the Redfish hooks to log it as a Redfish message 1152 // instead. There is no need to add it to the SEL, so just return success. 1153 intel_oem::ipmi::sel::checkRedfishHooks( 1154 recordID, recordType, timestamp, generatorID, evmRev, sensorType, 1155 sensorNum, eventType, eventData1, eventData2, eventData3); 1156 1157 uint16_t responseID = 0xFFFF; 1158 return ipmi::responseSuccess(responseID); 1159 } 1160 1161 ipmi::RspType<uint8_t> ipmiStorageClearSEL(ipmi::Context::ptr&, 1162 uint16_t reservationID, 1163 const std::array<uint8_t, 3>& clr, 1164 uint8_t eraseOperation) 1165 { 1166 if (!checkSELReservation(reservationID)) 1167 { 1168 return ipmi::responseInvalidReservationId(); 1169 } 1170 1171 static constexpr std::array<uint8_t, 3> clrExpected = {'C', 'L', 'R'}; 1172 if (clr != clrExpected) 1173 { 1174 return ipmi::responseInvalidFieldRequest(); 1175 } 1176 1177 // Erasure status cannot be fetched, so always return erasure status as 1178 // `erase completed`. 1179 if (eraseOperation == ipmi::sel::getEraseStatus) 1180 { 1181 return ipmi::responseSuccess(ipmi::sel::eraseComplete); 1182 } 1183 1184 // Check that initiate erase is correct 1185 if (eraseOperation != ipmi::sel::initiateErase) 1186 { 1187 return ipmi::responseInvalidFieldRequest(); 1188 } 1189 1190 // Per the IPMI spec, need to cancel any reservation when the SEL is 1191 // cleared 1192 cancelSELReservation(); 1193 1194 // Save the erase time 1195 intel_oem::ipmi::sel::erase_time::save(); 1196 1197 // Clear the SEL by deleting the log files 1198 std::vector<std::filesystem::path> selLogFiles; 1199 if (getSELLogFiles(selLogFiles)) 1200 { 1201 for (const std::filesystem::path& file : selLogFiles) 1202 { 1203 std::error_code ec; 1204 std::filesystem::remove(file, ec); 1205 } 1206 } 1207 1208 // Reload rsyslog so it knows to start new log files 1209 std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus(); 1210 sdbusplus::message_t rsyslogReload = dbus->new_method_call( 1211 "org.freedesktop.systemd1", "/org/freedesktop/systemd1", 1212 "org.freedesktop.systemd1.Manager", "ReloadUnit"); 1213 rsyslogReload.append("rsyslog.service", "replace"); 1214 try 1215 { 1216 sdbusplus::message_t reloadResponse = dbus->call(rsyslogReload); 1217 } 1218 catch (const sdbusplus::exception_t& e) 1219 { 1220 phosphor::logging::log<phosphor::logging::level::ERR>(e.what()); 1221 } 1222 1223 return ipmi::responseSuccess(ipmi::sel::eraseComplete); 1224 } 1225 1226 ipmi::RspType<uint32_t> ipmiStorageGetSELTime() 1227 { 1228 struct timespec selTime = {}; 1229 1230 if (clock_gettime(CLOCK_REALTIME, &selTime) < 0) 1231 { 1232 return ipmi::responseUnspecifiedError(); 1233 } 1234 1235 return ipmi::responseSuccess(selTime.tv_sec); 1236 } 1237 1238 ipmi::RspType<> ipmiStorageSetSELTime([[maybe_unused]] uint32_t selTime) 1239 { 1240 // Set SEL Time is not supported 1241 return ipmi::responseInvalidCommand(); 1242 } 1243 1244 std::vector<uint8_t> getType12SDRs(uint16_t index, uint16_t recordId) 1245 { 1246 std::vector<uint8_t> resp; 1247 if (index == 0) 1248 { 1249 std::string bmcName = "Basbrd Mgmt Ctlr"; 1250 Type12Record bmc(recordId, 0x20, 0, 0, 0xbf, 0x2e, 1, 0, bmcName); 1251 uint8_t* bmcPtr = reinterpret_cast<uint8_t*>(&bmc); 1252 resp.insert(resp.end(), bmcPtr, bmcPtr + sizeof(Type12Record)); 1253 } 1254 else if (index == 1) 1255 { 1256 std::string meName = "Mgmt Engine"; 1257 Type12Record me(recordId, 0x2c, 6, 0x24, 0x21, 0x2e, 2, 0, meName); 1258 uint8_t* mePtr = reinterpret_cast<uint8_t*>(&me); 1259 resp.insert(resp.end(), mePtr, mePtr + sizeof(Type12Record)); 1260 } 1261 else 1262 { 1263 throw std::runtime_error("getType12SDRs:: Illegal index " + 1264 std::to_string(index)); 1265 } 1266 1267 return resp; 1268 } 1269 1270 std::vector<uint8_t> getNMDiscoverySDR(uint16_t index, uint16_t recordId) 1271 { 1272 std::vector<uint8_t> resp; 1273 if (index == 0) 1274 { 1275 NMDiscoveryRecord nm = {}; 1276 nm.header.record_id_lsb = recordId; 1277 nm.header.record_id_msb = recordId >> 8; 1278 nm.header.sdr_version = ipmiSdrVersion; 1279 nm.header.record_type = 0xC0; 1280 nm.header.record_length = 0xB; 1281 nm.oemID0 = 0x57; 1282 nm.oemID1 = 0x1; 1283 nm.oemID2 = 0x0; 1284 nm.subType = 0x0D; 1285 nm.version = 0x1; 1286 nm.targetAddress = 0x2C; 1287 nm.channelNumber = 0x60; 1288 nm.healthEventSensor = 0x19; 1289 nm.exceptionEventSensor = 0x18; 1290 nm.operationalCapSensor = 0x1A; 1291 nm.thresholdExceededSensor = 0x1B; 1292 1293 uint8_t* nmPtr = reinterpret_cast<uint8_t*>(&nm); 1294 resp.insert(resp.end(), nmPtr, nmPtr + sizeof(NMDiscoveryRecord)); 1295 } 1296 else 1297 { 1298 throw std::runtime_error("getNMDiscoverySDR:: Illegal index " + 1299 std::to_string(index)); 1300 } 1301 1302 return resp; 1303 } 1304 1305 void registerStorageFunctions() 1306 { 1307 createTimers(); 1308 startMatch(); 1309 1310 // <Get FRU Inventory Area Info> 1311 ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnStorage, 1312 ipmi::storage::cmdGetFruInventoryAreaInfo, 1313 ipmi::Privilege::User, ipmiStorageGetFruInvAreaInfo); 1314 // <READ FRU Data> 1315 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 1316 ipmi::storage::cmdReadFruData, ipmi::Privilege::User, 1317 ipmiStorageReadFruData); 1318 1319 // <WRITE FRU Data> 1320 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 1321 ipmi::storage::cmdWriteFruData, 1322 ipmi::Privilege::Operator, ipmiStorageWriteFruData); 1323 1324 // <Get SEL Info> 1325 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 1326 ipmi::storage::cmdGetSelInfo, ipmi::Privilege::User, 1327 ipmiStorageGetSELInfo); 1328 1329 // <Get SEL Entry> 1330 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 1331 ipmi::storage::cmdGetSelEntry, ipmi::Privilege::User, 1332 ipmiStorageGetSELEntry); 1333 1334 // <Add SEL Entry> 1335 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 1336 ipmi::storage::cmdAddSelEntry, 1337 ipmi::Privilege::Operator, ipmiStorageAddSELEntry); 1338 1339 // <Clear SEL> 1340 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 1341 ipmi::storage::cmdClearSel, ipmi::Privilege::Operator, 1342 ipmiStorageClearSEL); 1343 1344 // <Get SEL Time> 1345 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 1346 ipmi::storage::cmdGetSelTime, ipmi::Privilege::User, 1347 ipmiStorageGetSELTime); 1348 1349 // <Set SEL Time> 1350 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 1351 ipmi::storage::cmdSetSelTime, 1352 ipmi::Privilege::Operator, ipmiStorageSetSELTime); 1353 } 1354 } // namespace storage 1355 } // namespace ipmi 1356