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