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