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