blob: 22a9dcd3502d8213103b58bbaa0c2b3d035b265e [file] [log] [blame]
Jason M. Bills3f7c5e42018-10-03 14:00:41 -07001/*
Jason M. Bills1a2fbdd2019-05-10 09:05:37 -07002// Copyright (c) 2017-2019 Intel Corporation
Jason M. Bills3f7c5e42018-10-03 14:00:41 -07003//
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
Jason M. Bills1d4d54d2019-04-23 11:26:11 -070017#include <boost/algorithm/string.hpp>
Jason M. Bills3f7c5e42018-10-03 14:00:41 -070018#include <boost/container/flat_map.hpp>
Jason M. Billsc04e2e72018-11-28 15:15:56 -080019#include <boost/process.hpp>
Jason M. Bills3f7c5e42018-10-03 14:00:41 -070020#include <commandutils.hpp>
Jason M. Bills1d4d54d2019-04-23 11:26:11 -070021#include <filesystem>
Jason M. Bills3f7c5e42018-10-03 14:00:41 -070022#include <iostream>
Jason M. Bills99b78ec2019-01-18 10:42:18 -080023#include <ipmi_to_redfish_hooks.hpp>
James Feist2a265d52019-04-08 11:16:27 -070024#include <ipmid/api.hpp>
Jason M. Billsc04e2e72018-11-28 15:15:56 -080025#include <phosphor-ipmi-host/selutility.hpp>
Jason M. Bills3f7c5e42018-10-03 14:00:41 -070026#include <phosphor-logging/log.hpp>
27#include <sdbusplus/message/types.hpp>
28#include <sdbusplus/timer.hpp>
Jason M. Billsc04e2e72018-11-28 15:15:56 -080029#include <sdrutils.hpp>
30#include <stdexcept>
Jason M. Bills3f7c5e42018-10-03 14:00:41 -070031#include <storagecommands.hpp>
Jason M. Bills52aaa7d2019-05-08 15:21:39 -070032#include <string_view>
Jason M. Bills3f7c5e42018-10-03 14:00:41 -070033
Jason M. Bills1d4d54d2019-04-23 11:26:11 -070034namespace intel_oem::ipmi::sel
35{
36static const std::filesystem::path selLogDir = "/var/log";
37static const std::string selLogFilename = "ipmi_sel";
38
39static int getFileTimestamp(const std::filesystem::path& file)
40{
41 struct stat st;
42
43 if (stat(file.c_str(), &st) >= 0)
44 {
45 return st.st_mtime;
46 }
47 return ::ipmi::sel::invalidTimeStamp;
48}
49
50namespace erase_time
Jason M. Bills7944c302019-03-20 15:24:05 -070051{
52static constexpr const char* selEraseTimestamp = "/var/lib/ipmi/sel_erase_time";
53
54void save()
55{
56 // open the file, creating it if necessary
57 int fd = open(selEraseTimestamp, O_WRONLY | O_CREAT | O_CLOEXEC, 0644);
58 if (fd < 0)
59 {
60 std::cerr << "Failed to open file\n";
61 return;
62 }
63
64 // update the file timestamp to the current time
65 if (futimens(fd, NULL) < 0)
66 {
67 std::cerr << "Failed to update timestamp: "
68 << std::string(strerror(errno));
69 }
70 close(fd);
71}
72
73int get()
74{
Jason M. Bills1d4d54d2019-04-23 11:26:11 -070075 return getFileTimestamp(selEraseTimestamp);
Jason M. Bills7944c302019-03-20 15:24:05 -070076}
Jason M. Bills1d4d54d2019-04-23 11:26:11 -070077} // namespace erase_time
78} // namespace intel_oem::ipmi::sel
Jason M. Bills7944c302019-03-20 15:24:05 -070079
Jason M. Bills3f7c5e42018-10-03 14:00:41 -070080namespace ipmi
81{
82
83namespace storage
84{
85
Jason M. Billse2d1aee2018-10-03 15:57:18 -070086constexpr static const size_t maxMessageSize = 64;
Jason M. Bills3f7c5e42018-10-03 14:00:41 -070087constexpr static const size_t maxFruSdrNameSize = 16;
88using ManagedObjectType = boost::container::flat_map<
89 sdbusplus::message::object_path,
90 boost::container::flat_map<
91 std::string, boost::container::flat_map<std::string, DbusVariant>>>;
92using ManagedEntry = std::pair<
93 sdbusplus::message::object_path,
94 boost::container::flat_map<
95 std::string, boost::container::flat_map<std::string, DbusVariant>>>;
96
James Feist3bcba452018-12-20 12:31:03 -080097constexpr static const char* fruDeviceServiceName =
98 "xyz.openbmc_project.FruDevice";
Jason M. Billse2d1aee2018-10-03 15:57:18 -070099constexpr static const size_t cacheTimeoutSeconds = 10;
Jason M. Bills3f7c5e42018-10-03 14:00:41 -0700100
Jason M. Bills4ed6f2c2019-04-02 12:21:25 -0700101// event direction is bit[7] of eventType where 1b = Deassertion event
102constexpr static const uint8_t deassertionEvent = 0x80;
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800103
Jason M. Bills3f7c5e42018-10-03 14:00:41 -0700104static std::vector<uint8_t> fruCache;
105static uint8_t cacheBus = 0xFF;
106static uint8_t cacheAddr = 0XFF;
107
108std::unique_ptr<phosphor::Timer> cacheTimer = nullptr;
109
110// we unfortunately have to build a map of hashes in case there is a
111// collision to verify our dev-id
112boost::container::flat_map<uint8_t, std::pair<uint8_t, uint8_t>> deviceHashes;
113
Jason M. Billse2d1aee2018-10-03 15:57:18 -0700114void registerStorageFunctions() __attribute__((constructor));
Jason M. Bills3f7c5e42018-10-03 14:00:41 -0700115static sdbusplus::bus::bus dbus(ipmid_get_sd_bus_connection());
116
117bool writeFru()
118{
119 sdbusplus::message::message writeFru = dbus.new_method_call(
120 fruDeviceServiceName, "/xyz/openbmc_project/FruDevice",
121 "xyz.openbmc_project.FruDeviceManager", "WriteFru");
122 writeFru.append(cacheBus, cacheAddr, fruCache);
123 try
124 {
125 sdbusplus::message::message writeFruResp = dbus.call(writeFru);
126 }
127 catch (sdbusplus::exception_t&)
128 {
129 // todo: log sel?
130 phosphor::logging::log<phosphor::logging::level::ERR>(
131 "error writing fru");
132 return false;
133 }
134 return true;
135}
136
Jason M. Billse2d1aee2018-10-03 15:57:18 -0700137void createTimer()
138{
139 if (cacheTimer == nullptr)
140 {
141 cacheTimer = std::make_unique<phosphor::Timer>(writeFru);
142 }
143}
144
Jason M. Bills3f7c5e42018-10-03 14:00:41 -0700145ipmi_ret_t replaceCacheFru(uint8_t devId)
146{
147 static uint8_t lastDevId = 0xFF;
148
149 bool timerRunning = (cacheTimer != nullptr) && !cacheTimer->isExpired();
150 if (lastDevId == devId && timerRunning)
151 {
152 return IPMI_CC_OK; // cache already up to date
153 }
154 // if timer is running, stop it and writeFru manually
155 else if (timerRunning)
156 {
157 cacheTimer->stop();
158 writeFru();
159 }
160
161 sdbusplus::message::message getObjects = dbus.new_method_call(
162 fruDeviceServiceName, "/", "org.freedesktop.DBus.ObjectManager",
163 "GetManagedObjects");
164 ManagedObjectType frus;
165 try
166 {
167 sdbusplus::message::message resp = dbus.call(getObjects);
168 resp.read(frus);
169 }
170 catch (sdbusplus::exception_t&)
171 {
172 phosphor::logging::log<phosphor::logging::level::ERR>(
173 "replaceCacheFru: error getting managed objects");
174 return IPMI_CC_RESPONSE_ERROR;
175 }
176
177 deviceHashes.clear();
178
179 // hash the object paths to create unique device id's. increment on
180 // collision
181 std::hash<std::string> hasher;
182 for (const auto& fru : frus)
183 {
184 auto fruIface = fru.second.find("xyz.openbmc_project.FruDevice");
185 if (fruIface == fru.second.end())
186 {
187 continue;
188 }
189
190 auto busFind = fruIface->second.find("BUS");
191 auto addrFind = fruIface->second.find("ADDRESS");
192 if (busFind == fruIface->second.end() ||
193 addrFind == fruIface->second.end())
194 {
195 phosphor::logging::log<phosphor::logging::level::INFO>(
196 "fru device missing Bus or Address",
197 phosphor::logging::entry("FRU=%s", fru.first.str.c_str()));
198 continue;
199 }
200
201 uint8_t fruBus =
202 sdbusplus::message::variant_ns::get<uint32_t>(busFind->second);
203 uint8_t fruAddr =
204 sdbusplus::message::variant_ns::get<uint32_t>(addrFind->second);
205
206 uint8_t fruHash = 0;
207 if (fruBus != 0 || fruAddr != 0)
208 {
209 fruHash = hasher(fru.first.str);
210 // can't be 0xFF based on spec, and 0 is reserved for baseboard
211 if (fruHash == 0 || fruHash == 0xFF)
212 {
213 fruHash = 1;
214 }
215 }
216 std::pair<uint8_t, uint8_t> newDev(fruBus, fruAddr);
217
218 bool emplacePassed = false;
219 while (!emplacePassed)
220 {
221 auto resp = deviceHashes.emplace(fruHash, newDev);
222 emplacePassed = resp.second;
223 if (!emplacePassed)
224 {
225 fruHash++;
226 // can't be 0xFF based on spec, and 0 is reserved for
227 // baseboard
228 if (fruHash == 0XFF)
229 {
230 fruHash = 0x1;
231 }
232 }
233 }
234 }
235 auto deviceFind = deviceHashes.find(devId);
236 if (deviceFind == deviceHashes.end())
237 {
238 return IPMI_CC_SENSOR_INVALID;
239 }
240
241 fruCache.clear();
242 sdbusplus::message::message getRawFru = dbus.new_method_call(
243 fruDeviceServiceName, "/xyz/openbmc_project/FruDevice",
244 "xyz.openbmc_project.FruDeviceManager", "GetRawFru");
245 cacheBus = deviceFind->second.first;
246 cacheAddr = deviceFind->second.second;
247 getRawFru.append(cacheBus, cacheAddr);
248 try
249 {
250 sdbusplus::message::message getRawResp = dbus.call(getRawFru);
251 getRawResp.read(fruCache);
252 }
253 catch (sdbusplus::exception_t&)
254 {
255 lastDevId = 0xFF;
256 cacheBus = 0xFF;
257 cacheAddr = 0xFF;
258 return IPMI_CC_RESPONSE_ERROR;
259 }
260
261 lastDevId = devId;
262 return IPMI_CC_OK;
263}
264
Jason M. Billse2d1aee2018-10-03 15:57:18 -0700265ipmi_ret_t ipmiStorageReadFRUData(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
266 ipmi_request_t request,
267 ipmi_response_t response,
268 ipmi_data_len_t dataLen,
269 ipmi_context_t context)
270{
271 if (*dataLen != 4)
272 {
273 *dataLen = 0;
274 return IPMI_CC_REQ_DATA_LEN_INVALID;
275 }
276 *dataLen = 0; // default to 0 in case of an error
277
278 auto req = static_cast<GetFRUAreaReq*>(request);
279
280 if (req->countToRead > maxMessageSize - 1)
281 {
282 return IPMI_CC_INVALID_FIELD_REQUEST;
283 }
284 ipmi_ret_t status = replaceCacheFru(req->fruDeviceID);
285
286 if (status != IPMI_CC_OK)
287 {
288 return status;
289 }
290
291 size_t fromFRUByteLen = 0;
292 if (req->countToRead + req->fruInventoryOffset < fruCache.size())
293 {
294 fromFRUByteLen = req->countToRead;
295 }
296 else if (fruCache.size() > req->fruInventoryOffset)
297 {
298 fromFRUByteLen = fruCache.size() - req->fruInventoryOffset;
299 }
300 size_t padByteLen = req->countToRead - fromFRUByteLen;
301 uint8_t* respPtr = static_cast<uint8_t*>(response);
302 *respPtr = req->countToRead;
303 std::copy(fruCache.begin() + req->fruInventoryOffset,
304 fruCache.begin() + req->fruInventoryOffset + fromFRUByteLen,
305 ++respPtr);
306 // if longer than the fru is requested, fill with 0xFF
307 if (padByteLen)
308 {
309 respPtr += fromFRUByteLen;
310 std::fill(respPtr, respPtr + padByteLen, 0xFF);
311 }
312 *dataLen = fromFRUByteLen + 1;
313
314 return IPMI_CC_OK;
315}
316
317ipmi_ret_t ipmiStorageWriteFRUData(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
318 ipmi_request_t request,
319 ipmi_response_t response,
320 ipmi_data_len_t dataLen,
321 ipmi_context_t context)
322{
323 if (*dataLen < 4 ||
324 *dataLen >=
325 0xFF + 3) // count written return is one byte, so limit to one byte
326 // of data after the three request data bytes
327 {
328 *dataLen = 0;
329 return IPMI_CC_REQ_DATA_LEN_INVALID;
330 }
331
332 auto req = static_cast<WriteFRUDataReq*>(request);
333 size_t writeLen = *dataLen - 3;
334 *dataLen = 0; // default to 0 in case of an error
335
336 ipmi_ret_t status = replaceCacheFru(req->fruDeviceID);
337 if (status != IPMI_CC_OK)
338 {
339 return status;
340 }
341 int lastWriteAddr = req->fruInventoryOffset + writeLen;
342 if (fruCache.size() < lastWriteAddr)
343 {
344 fruCache.resize(req->fruInventoryOffset + writeLen);
345 }
346
347 std::copy(req->data, req->data + writeLen,
348 fruCache.begin() + req->fruInventoryOffset);
349
350 bool atEnd = false;
351
352 if (fruCache.size() >= sizeof(FRUHeader))
353 {
354
355 FRUHeader* header = reinterpret_cast<FRUHeader*>(fruCache.data());
356
357 int lastRecordStart = std::max(
358 header->internalOffset,
359 std::max(header->chassisOffset,
360 std::max(header->boardOffset, header->productOffset)));
361 // TODO: Handle Multi-Record FRUs?
362
363 lastRecordStart *= 8; // header starts in are multiples of 8 bytes
364
365 // get the length of the area in multiples of 8 bytes
366 if (lastWriteAddr > (lastRecordStart + 1))
367 {
368 // second byte in record area is the length
369 int areaLength(fruCache[lastRecordStart + 1]);
370 areaLength *= 8; // it is in multiples of 8 bytes
371
372 if (lastWriteAddr >= (areaLength + lastRecordStart))
373 {
374 atEnd = true;
375 }
376 }
377 }
378 uint8_t* respPtr = static_cast<uint8_t*>(response);
379 if (atEnd)
380 {
381 // cancel timer, we're at the end so might as well send it
382 cacheTimer->stop();
383 if (!writeFru())
384 {
385 return IPMI_CC_INVALID_FIELD_REQUEST;
386 }
387 *respPtr = std::min(fruCache.size(), static_cast<size_t>(0xFF));
388 }
389 else
390 {
391 // start a timer, if no further data is sent in cacheTimeoutSeconds
392 // seconds, check to see if it is valid
393 createTimer();
394 cacheTimer->start(std::chrono::duration_cast<std::chrono::microseconds>(
395 std::chrono::seconds(cacheTimeoutSeconds)));
396 *respPtr = 0;
397 }
398
399 *dataLen = 1;
400
401 return IPMI_CC_OK;
402}
403
404ipmi_ret_t ipmiStorageGetFRUInvAreaInfo(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
405 ipmi_request_t request,
406 ipmi_response_t response,
407 ipmi_data_len_t dataLen,
408 ipmi_context_t context)
409{
410 if (*dataLen != 1)
411 {
412 *dataLen = 0;
413 return IPMI_CC_REQ_DATA_LEN_INVALID;
414 }
415 *dataLen = 0; // default to 0 in case of an error
416
417 uint8_t reqDev = *(static_cast<uint8_t*>(request));
418 if (reqDev == 0xFF)
419 {
420 return IPMI_CC_INVALID_FIELD_REQUEST;
421 }
422 ipmi_ret_t status = replaceCacheFru(reqDev);
423
424 if (status != IPMI_CC_OK)
425 {
426 return status;
427 }
428
429 GetFRUAreaResp* respPtr = static_cast<GetFRUAreaResp*>(response);
430 respPtr->inventorySizeLSB = fruCache.size() & 0xFF;
431 respPtr->inventorySizeMSB = fruCache.size() >> 8;
432 respPtr->accessType = static_cast<uint8_t>(GetFRUAreaAccessType::byte);
433
434 *dataLen = sizeof(GetFRUAreaResp);
435 return IPMI_CC_OK;
436}
437
Jason M. Bills3f7c5e42018-10-03 14:00:41 -0700438ipmi_ret_t getFruSdrCount(size_t& count)
439{
440 ipmi_ret_t ret = replaceCacheFru(0);
441 if (ret != IPMI_CC_OK)
442 {
443 return ret;
444 }
445 count = deviceHashes.size();
446 return IPMI_CC_OK;
447}
448
449ipmi_ret_t getFruSdrs(size_t index, get_sdr::SensorDataFruRecord& resp)
450{
451 ipmi_ret_t ret = replaceCacheFru(0); // this will update the hash list
452 if (ret != IPMI_CC_OK)
453 {
454 return ret;
455 }
456 if (deviceHashes.size() < index)
457 {
458 return IPMI_CC_INVALID_FIELD_REQUEST;
459 }
460 auto device = deviceHashes.begin() + index;
461 uint8_t& bus = device->second.first;
462 uint8_t& address = device->second.second;
463
464 ManagedObjectType frus;
465
466 sdbusplus::message::message getObjects = dbus.new_method_call(
467 fruDeviceServiceName, "/", "org.freedesktop.DBus.ObjectManager",
468 "GetManagedObjects");
469 try
470 {
471 sdbusplus::message::message resp = dbus.call(getObjects);
472 resp.read(frus);
473 }
474 catch (sdbusplus::exception_t&)
475 {
476 return IPMI_CC_RESPONSE_ERROR;
477 }
478 boost::container::flat_map<std::string, DbusVariant>* fruData = nullptr;
479 auto fru =
480 std::find_if(frus.begin(), frus.end(),
481 [bus, address, &fruData](ManagedEntry& entry) {
482 auto findFruDevice =
483 entry.second.find("xyz.openbmc_project.FruDevice");
484 if (findFruDevice == entry.second.end())
485 {
486 return false;
487 }
488 fruData = &(findFruDevice->second);
489 auto findBus = findFruDevice->second.find("BUS");
490 auto findAddress =
491 findFruDevice->second.find("ADDRESS");
492 if (findBus == findFruDevice->second.end() ||
493 findAddress == findFruDevice->second.end())
494 {
495 return false;
496 }
497 if (sdbusplus::message::variant_ns::get<uint32_t>(
498 findBus->second) != bus)
499 {
500 return false;
501 }
502 if (sdbusplus::message::variant_ns::get<uint32_t>(
503 findAddress->second) != address)
504 {
505 return false;
506 }
507 return true;
508 });
509 if (fru == frus.end())
510 {
511 return IPMI_CC_RESPONSE_ERROR;
512 }
513 std::string name;
514 auto findProductName = fruData->find("BOARD_PRODUCT_NAME");
515 auto findBoardName = fruData->find("PRODUCT_PRODUCT_NAME");
516 if (findProductName != fruData->end())
517 {
518 name = sdbusplus::message::variant_ns::get<std::string>(
519 findProductName->second);
520 }
521 else if (findBoardName != fruData->end())
522 {
523 name = sdbusplus::message::variant_ns::get<std::string>(
524 findBoardName->second);
525 }
526 else
527 {
528 name = "UNKNOWN";
529 }
530 if (name.size() > maxFruSdrNameSize)
531 {
532 name = name.substr(0, maxFruSdrNameSize);
533 }
534 size_t sizeDiff = maxFruSdrNameSize - name.size();
535
536 resp.header.record_id_lsb = 0x0; // calling code is to implement these
537 resp.header.record_id_msb = 0x0;
538 resp.header.sdr_version = ipmiSdrVersion;
539 resp.header.record_type = 0x11; // FRU Device Locator
540 resp.header.record_length = sizeof(resp.body) + sizeof(resp.key) - sizeDiff;
541 resp.key.deviceAddress = 0x20;
542 resp.key.fruID = device->first;
543 resp.key.accessLun = 0x80; // logical / physical fru device
544 resp.key.channelNumber = 0x0;
545 resp.body.reserved = 0x0;
546 resp.body.deviceType = 0x10;
James Feist4f86d1f2019-04-03 10:30:26 -0700547 resp.body.deviceTypeModifier = 0x0;
Jason M. Bills3f7c5e42018-10-03 14:00:41 -0700548 resp.body.entityID = 0x0;
549 resp.body.entityInstance = 0x1;
550 resp.body.oem = 0x0;
551 resp.body.deviceIDLen = name.size();
552 name.copy(resp.body.deviceID, name.size());
553
554 return IPMI_CC_OK;
555}
Jason M. Billse2d1aee2018-10-03 15:57:18 -0700556
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700557static bool getSELLogFiles(std::vector<std::filesystem::path>& selLogFiles)
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800558{
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700559 // Loop through the directory looking for ipmi_sel log files
560 for (const std::filesystem::directory_entry& dirEnt :
561 std::filesystem::directory_iterator(intel_oem::ipmi::sel::selLogDir))
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800562 {
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700563 std::string filename = dirEnt.path().filename();
564 if (boost::starts_with(filename, intel_oem::ipmi::sel::selLogFilename))
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800565 {
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700566 // If we find an ipmi_sel log file, save the path
567 selLogFiles.emplace_back(intel_oem::ipmi::sel::selLogDir /
568 filename);
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800569 }
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800570 }
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700571 // As the log files rotate, they are appended with a ".#" that is higher for
572 // the older logs. Since we don't expect more than 10 log files, we
573 // can just sort the list to get them in order from newest to oldest
574 std::sort(selLogFiles.begin(), selLogFiles.end());
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800575
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700576 return !selLogFiles.empty();
577}
578
579static int countSELEntries()
580{
581 // Get the list of ipmi_sel log files
582 std::vector<std::filesystem::path> selLogFiles;
583 if (!getSELLogFiles(selLogFiles))
584 {
585 return 0;
586 }
587 int numSELEntries = 0;
588 // Loop through each log file and count the number of logs
589 for (const std::filesystem::path& file : selLogFiles)
590 {
591 std::ifstream logStream(file);
592 if (!logStream.is_open())
593 {
594 continue;
595 }
596
597 std::string line;
598 while (std::getline(logStream, line))
599 {
600 numSELEntries++;
601 }
602 }
603 return numSELEntries;
604}
605
606static bool findSELEntry(const int recordID,
607 const std::vector<std::filesystem::path> selLogFiles,
608 std::string& entry)
609{
610 // Record ID is the first entry field following the timestamp. It is
611 // preceded by a space and followed by a comma
612 std::string search = " " + std::to_string(recordID) + ",";
613
614 // Loop through the ipmi_sel log entries
615 for (const std::filesystem::path& file : selLogFiles)
616 {
617 std::ifstream logStream(file);
618 if (!logStream.is_open())
619 {
620 continue;
621 }
622
623 while (std::getline(logStream, entry))
624 {
625 // Check if the record ID matches
626 if (entry.find(search) != std::string::npos)
627 {
628 return true;
629 }
630 }
631 }
632 return false;
633}
634
635static uint16_t
636 getNextRecordID(const uint16_t recordID,
637 const std::vector<std::filesystem::path> selLogFiles)
638{
639 uint16_t nextRecordID = recordID + 1;
640 std::string entry;
641 if (findSELEntry(nextRecordID, selLogFiles, entry))
642 {
643 return nextRecordID;
644 }
645 else
646 {
647 return ipmi::sel::lastEntry;
648 }
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800649}
650
651static int fromHexStr(const std::string hexStr, std::vector<uint8_t>& data)
652{
653 for (unsigned int i = 0; i < hexStr.size(); i += 2)
654 {
655 try
656 {
657 data.push_back(static_cast<uint8_t>(
658 std::stoul(hexStr.substr(i, 2), nullptr, 16)));
659 }
660 catch (std::invalid_argument& e)
661 {
662 phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
663 return -1;
664 }
665 catch (std::out_of_range& e)
666 {
667 phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
668 return -1;
669 }
670 }
671 return 0;
672}
673
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700674ipmi::RspType<uint8_t, // SEL version
675 uint16_t, // SEL entry count
676 uint16_t, // free space
677 uint32_t, // last add timestamp
678 uint32_t, // last erase timestamp
679 uint8_t> // operation support
680 ipmiStorageGetSELInfo()
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800681{
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700682 constexpr uint8_t selVersion = ipmi::sel::selVersion;
683 uint16_t entries = countSELEntries();
684 uint32_t addTimeStamp = intel_oem::ipmi::sel::getFileTimestamp(
685 intel_oem::ipmi::sel::selLogDir / intel_oem::ipmi::sel::selLogFilename);
686 uint32_t eraseTimeStamp = intel_oem::ipmi::sel::erase_time::get();
687 constexpr uint8_t operationSupport =
688 intel_oem::ipmi::sel::selOperationSupport;
689 constexpr uint16_t freeSpace =
690 0xffff; // Spec indicates that more than 64kB is free
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800691
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700692 return ipmi::responseSuccess(selVersion, entries, freeSpace, addTimeStamp,
693 eraseTimeStamp, operationSupport);
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800694}
695
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700696using systemEventType = std::tuple<
697 uint32_t, // Timestamp
698 uint16_t, // Generator ID
699 uint8_t, // EvM Rev
700 uint8_t, // Sensor Type
701 uint8_t, // Sensor Number
702 uint7_t, // Event Type
703 bool, // Event Direction
704 std::array<uint8_t, intel_oem::ipmi::sel::systemEventSize>>; // Event Data
705using oemTsEventType = std::tuple<
706 uint32_t, // Timestamp
707 std::array<uint8_t, intel_oem::ipmi::sel::oemTsEventSize>>; // Event Data
708using oemEventType =
709 std::array<uint8_t, intel_oem::ipmi::sel::oemEventSize>; // Event Data
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800710
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700711ipmi::RspType<uint16_t, // Next Record ID
712 uint16_t, // Record ID
713 uint8_t, // Record Type
714 std::variant<systemEventType, oemTsEventType,
715 oemEventType>> // Record Content
716 ipmiStorageGetSELEntry(uint16_t reservationID, uint16_t targetID,
717 uint8_t offset, uint8_t size)
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800718{
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700719 // Only support getting the entire SEL record. If a partial size or non-zero
720 // offset is requested, return an error
721 if (offset != 0 || size != ipmi::sel::entireRecord)
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800722 {
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700723 return ipmi::responseRetBytesUnavailable();
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800724 }
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800725
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700726 // Check the reservation ID if one is provided or required (only if the
727 // offset is non-zero)
728 if (reservationID != 0 || offset != 0)
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800729 {
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700730 if (!checkSELReservation(reservationID))
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800731 {
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700732 return ipmi::responseInvalidReservationId();
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800733 }
734 }
735
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700736 // Get the ipmi_sel log files
737 std::vector<std::filesystem::path> selLogFiles;
738 if (!getSELLogFiles(selLogFiles))
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800739 {
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700740 return ipmi::responseSensorInvalid();
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800741 }
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800742
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700743 std::string targetEntry;
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800744
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800745 if (targetID == ipmi::sel::firstEntry)
746 {
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700747 // The first entry will be at the top of the oldest log file
748 std::ifstream logStream(selLogFiles.back());
749 if (!logStream.is_open())
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800750 {
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700751 return ipmi::responseUnspecifiedError();
752 }
753
754 if (!std::getline(logStream, targetEntry))
755 {
756 return ipmi::responseUnspecifiedError();
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800757 }
758 }
759 else if (targetID == ipmi::sel::lastEntry)
760 {
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700761 // The last entry will be at the bottom of the newest log file
762 std::ifstream logStream(selLogFiles.front());
763 if (!logStream.is_open())
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800764 {
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700765 return ipmi::responseUnspecifiedError();
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800766 }
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700767
768 std::string line;
769 while (std::getline(logStream, line))
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800770 {
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700771 targetEntry = line;
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800772 }
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800773 }
774 else
775 {
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700776 if (!findSELEntry(targetID, selLogFiles, targetEntry))
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800777 {
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700778 return ipmi::responseSensorInvalid();
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800779 }
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800780 }
781
Jason M. Bills52aaa7d2019-05-08 15:21:39 -0700782 // The format of the ipmi_sel message is "<Timestamp>
783 // <ID>,<Type>,<EventData>,[<Generator ID>,<Path>,<Direction>]".
784 // First get the Timestamp
785 size_t space = targetEntry.find_first_of(" ");
786 if (space == std::string::npos)
787 {
788 return ipmi::responseUnspecifiedError();
789 }
790 std::string entryTimestamp = targetEntry.substr(0, space);
791 // Then get the log contents
792 size_t entryStart = targetEntry.find_first_not_of(" ", space);
793 if (entryStart == std::string::npos)
794 {
795 return ipmi::responseUnspecifiedError();
796 }
797 std::string_view entry(targetEntry);
798 entry.remove_prefix(entryStart);
799 // Use split to separate the entry into its fields
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700800 std::vector<std::string> targetEntryFields;
Jason M. Bills52aaa7d2019-05-08 15:21:39 -0700801 boost::split(targetEntryFields, entry, boost::is_any_of(","),
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700802 boost::token_compress_on);
Jason M. Bills52aaa7d2019-05-08 15:21:39 -0700803 if (targetEntryFields.size() < 3)
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700804 {
805 return ipmi::responseUnspecifiedError();
806 }
Jason M. Bills1a2fbdd2019-05-10 09:05:37 -0700807 std::string& recordIDStr = targetEntryFields[0];
808 std::string& recordTypeStr = targetEntryFields[1];
809 std::string& eventDataStr = targetEntryFields[2];
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700810
Jason M. Bills1a2fbdd2019-05-10 09:05:37 -0700811 uint16_t recordID;
812 uint8_t recordType;
813 try
814 {
815 recordID = std::stoul(recordIDStr);
816 recordType = std::stoul(recordTypeStr, nullptr, 16);
817 }
818 catch (const std::invalid_argument&)
819 {
820 return ipmi::responseUnspecifiedError();
821 }
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700822 uint16_t nextRecordID = getNextRecordID(recordID, selLogFiles);
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700823 std::vector<uint8_t> eventDataBytes;
Jason M. Bills1a2fbdd2019-05-10 09:05:37 -0700824 if (fromHexStr(eventDataStr, eventDataBytes) < 0)
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700825 {
826 return ipmi::responseUnspecifiedError();
827 }
828
829 if (recordType == intel_oem::ipmi::sel::systemEvent)
830 {
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700831 // Get the timestamp
832 std::tm timeStruct = {};
Jason M. Bills52aaa7d2019-05-08 15:21:39 -0700833 std::istringstream entryStream(entryTimestamp);
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700834
835 uint32_t timestamp = ipmi::sel::invalidTimeStamp;
836 if (entryStream >> std::get_time(&timeStruct, "%Y-%m-%dT%H:%M:%S"))
837 {
838 timestamp = std::mktime(&timeStruct);
839 }
840
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700841 // Set the event message revision
842 uint8_t evmRev = intel_oem::ipmi::sel::eventMsgRev;
843
Jason M. Bills1a2fbdd2019-05-10 09:05:37 -0700844 uint16_t generatorID = 0;
845 uint8_t sensorType = 0;
846 uint8_t sensorNum = 0xFF;
847 uint7_t eventType = 0;
848 bool eventDir = 0;
849 // System type events should have six fields
850 if (targetEntryFields.size() >= 6)
851 {
852 std::string& generatorIDStr = targetEntryFields[3];
853 std::string& sensorPath = targetEntryFields[4];
854 std::string& eventDirStr = targetEntryFields[5];
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700855
Jason M. Bills1a2fbdd2019-05-10 09:05:37 -0700856 // Get the generator ID
857 try
858 {
859 generatorID = std::stoul(generatorIDStr, nullptr, 16);
860 }
861 catch (const std::invalid_argument&)
862 {
863 std::cerr << "Invalid Generator ID\n";
864 }
865
866 // Get the sensor type, sensor number, and event type for the sensor
867 sensorType = getSensorTypeFromPath(sensorPath);
868 sensorNum = getSensorNumberFromPath(sensorPath);
869 eventType = getSensorEventTypeFromPath(sensorPath);
870
871 // Get the event direction
872 try
873 {
874 eventDir = std::stoul(eventDirStr) ? 0 : 1;
875 }
876 catch (const std::invalid_argument&)
877 {
878 std::cerr << "Invalid Event Direction\n";
879 }
880 }
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700881
882 // Only keep the eventData bytes that fit in the record
883 std::array<uint8_t, intel_oem::ipmi::sel::systemEventSize> eventData{};
884 std::copy_n(eventDataBytes.begin(),
885 std::min(eventDataBytes.size(), eventData.size()),
886 eventData.begin());
887
888 return ipmi::responseSuccess(
889 nextRecordID, recordID, recordType,
890 systemEventType{timestamp, generatorID, evmRev, sensorType,
891 sensorNum, eventType, eventDir, eventData});
892 }
893 else if (recordType >= intel_oem::ipmi::sel::oemTsEventFirst &&
894 recordType <= intel_oem::ipmi::sel::oemTsEventLast)
895 {
896 // Get the timestamp
897 std::tm timeStruct = {};
Jason M. Bills52aaa7d2019-05-08 15:21:39 -0700898 std::istringstream entryStream(entryTimestamp);
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700899
900 uint32_t timestamp = ipmi::sel::invalidTimeStamp;
901 if (entryStream >> std::get_time(&timeStruct, "%Y-%m-%dT%H:%M:%S"))
902 {
903 timestamp = std::mktime(&timeStruct);
904 }
905
906 // Only keep the bytes that fit in the record
907 std::array<uint8_t, intel_oem::ipmi::sel::oemTsEventSize> eventData{};
908 std::copy_n(eventDataBytes.begin(),
909 std::min(eventDataBytes.size(), eventData.size()),
910 eventData.begin());
911
912 return ipmi::responseSuccess(nextRecordID, recordID, recordType,
913 oemTsEventType{timestamp, eventData});
914 }
915 else if (recordType >= intel_oem::ipmi::sel::oemEventFirst &&
916 recordType <= intel_oem::ipmi::sel::oemEventLast)
917 {
918 // Only keep the bytes that fit in the record
919 std::array<uint8_t, intel_oem::ipmi::sel::oemEventSize> eventData{};
920 std::copy_n(eventDataBytes.begin(),
921 std::min(eventDataBytes.size(), eventData.size()),
922 eventData.begin());
923
924 return ipmi::responseSuccess(nextRecordID, recordID, recordType,
925 eventData);
926 }
927
928 return ipmi::responseUnspecifiedError();
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800929}
930
Jason M. Bills6dd8f042019-04-11 10:39:02 -0700931ipmi::RspType<uint16_t> ipmiStorageAddSELEntry(
932 uint16_t recordID, uint8_t recordType, uint32_t timestamp,
933 uint16_t generatorID, uint8_t evmRev, uint8_t sensorType, uint8_t sensorNum,
934 uint8_t eventType, uint8_t eventData1, uint8_t eventData2,
935 uint8_t eventData3)
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800936{
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800937 // Per the IPMI spec, need to cancel any reservation when a SEL entry is
938 // added
939 cancelSELReservation();
940
Jason M. Bills6dd8f042019-04-11 10:39:02 -0700941 // Send this request to the Redfish hooks to log it as a Redfish message
942 // instead. There is no need to add it to the SEL, so just return success.
943 intel_oem::ipmi::sel::checkRedfishHooks(
944 recordID, recordType, timestamp, generatorID, evmRev, sensorType,
945 sensorNum, eventType, eventData1, eventData2, eventData3);
Jason M. Bills99b78ec2019-01-18 10:42:18 -0800946
Jason M. Bills6dd8f042019-04-11 10:39:02 -0700947 uint16_t responseID = 0xFFFF;
948 return ipmi::responseSuccess(responseID);
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800949}
950
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700951ipmi::RspType<uint8_t> ipmiStorageClearSEL(ipmi::Context::ptr ctx,
952 uint16_t reservationID,
953 const std::array<uint8_t, 3>& clr,
954 uint8_t eraseOperation)
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800955{
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700956 if (!checkSELReservation(reservationID))
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800957 {
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700958 return ipmi::responseInvalidReservationId();
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800959 }
960
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700961 static constexpr std::array<uint8_t, 3> clrExpected = {'C', 'L', 'R'};
962 if (clr != clrExpected)
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800963 {
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700964 return ipmi::responseInvalidFieldRequest();
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800965 }
966
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700967 // Erasure status cannot be fetched, so always return erasure status as
968 // `erase completed`.
969 if (eraseOperation == ipmi::sel::getEraseStatus)
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800970 {
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700971 return ipmi::responseSuccess(ipmi::sel::eraseComplete);
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800972 }
973
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700974 // Check that initiate erase is correct
975 if (eraseOperation != ipmi::sel::initiateErase)
976 {
977 return ipmi::responseInvalidFieldRequest();
978 }
979
980 // Per the IPMI spec, need to cancel any reservation when the SEL is
981 // cleared
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800982 cancelSELReservation();
983
Jason M. Bills7944c302019-03-20 15:24:05 -0700984 // Save the erase time
985 intel_oem::ipmi::sel::erase_time::save();
986
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700987 // Clear the SEL by deleting the log files
988 std::vector<std::filesystem::path> selLogFiles;
989 if (getSELLogFiles(selLogFiles))
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800990 {
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700991 for (const std::filesystem::path& file : selLogFiles)
992 {
993 std::error_code ec;
994 std::filesystem::remove(file, ec);
995 }
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800996 }
997
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700998 // Reload rsyslog so it knows to start new log files
999 sdbusplus::message::message rsyslogReload = dbus.new_method_call(
1000 "org.freedesktop.systemd1", "/org/freedesktop/systemd1",
1001 "org.freedesktop.systemd1.Manager", "ReloadUnit");
1002 rsyslogReload.append("rsyslog.service", "replace");
1003 try
1004 {
1005 sdbusplus::message::message reloadResponse = dbus.call(rsyslogReload);
1006 }
1007 catch (sdbusplus::exception_t& e)
1008 {
1009 phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
1010 }
1011
1012 return ipmi::responseSuccess(ipmi::sel::eraseComplete);
Jason M. Billsc04e2e72018-11-28 15:15:56 -08001013}
1014
Jason M. Bills1a474622019-06-14 14:51:33 -07001015ipmi::RspType<uint32_t> ipmiStorageGetSELTime()
1016{
1017 struct timespec selTime = {};
1018
1019 if (clock_gettime(CLOCK_REALTIME, &selTime) < 0)
1020 {
1021 return ipmi::responseUnspecifiedError();
1022 }
1023
1024 return ipmi::responseSuccess(selTime.tv_sec);
1025}
1026
Jason M. Bills1d4d54d2019-04-23 11:26:11 -07001027ipmi::RspType<> ipmiStorageSetSELTime(uint32_t selTime)
Jason M. Billscac97a52019-01-30 14:43:46 -08001028{
1029 // Set SEL Time is not supported
Jason M. Bills1d4d54d2019-04-23 11:26:11 -07001030 return ipmi::responseInvalidCommand();
Jason M. Billscac97a52019-01-30 14:43:46 -08001031}
1032
Jason M. Billse2d1aee2018-10-03 15:57:18 -07001033void registerStorageFunctions()
1034{
1035 // <Get FRU Inventory Area Info>
1036 ipmiPrintAndRegister(
1037 NETFUN_STORAGE,
1038 static_cast<ipmi_cmd_t>(IPMINetfnStorageCmds::ipmiCmdGetFRUInvAreaInfo),
1039 NULL, ipmiStorageGetFRUInvAreaInfo, PRIVILEGE_OPERATOR);
1040
Jason M. Billsc04e2e72018-11-28 15:15:56 -08001041 // <READ FRU Data>
Jason M. Billse2d1aee2018-10-03 15:57:18 -07001042 ipmiPrintAndRegister(
1043 NETFUN_STORAGE,
1044 static_cast<ipmi_cmd_t>(IPMINetfnStorageCmds::ipmiCmdReadFRUData), NULL,
1045 ipmiStorageReadFRUData, PRIVILEGE_OPERATOR);
1046
Jason M. Billsc04e2e72018-11-28 15:15:56 -08001047 // <WRITE FRU Data>
Jason M. Billse2d1aee2018-10-03 15:57:18 -07001048 ipmiPrintAndRegister(
1049 NETFUN_STORAGE,
1050 static_cast<ipmi_cmd_t>(IPMINetfnStorageCmds::ipmiCmdWriteFRUData),
1051 NULL, ipmiStorageWriteFRUData, PRIVILEGE_OPERATOR);
Jason M. Billsc04e2e72018-11-28 15:15:56 -08001052
1053 // <Get SEL Info>
Jason M. Bills1d4d54d2019-04-23 11:26:11 -07001054 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
1055 ipmi::storage::cmdGetSelInfo,
1056 ipmi::Privilege::Operator, ipmiStorageGetSELInfo);
Jason M. Billsc04e2e72018-11-28 15:15:56 -08001057
1058 // <Get SEL Entry>
Jason M. Bills1d4d54d2019-04-23 11:26:11 -07001059 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
1060 ipmi::storage::cmdGetSelEntry,
1061 ipmi::Privilege::Operator, ipmiStorageGetSELEntry);
Jason M. Billsc04e2e72018-11-28 15:15:56 -08001062
1063 // <Add SEL Entry>
Jason M. Bills6dd8f042019-04-11 10:39:02 -07001064 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
1065 static_cast<ipmi::Cmd>(ipmi::storage::cmdAddSelEntry),
1066 ipmi::Privilege::Operator, ipmiStorageAddSELEntry);
Jason M. Billsc04e2e72018-11-28 15:15:56 -08001067
1068 // <Clear SEL>
Jason M. Bills1d4d54d2019-04-23 11:26:11 -07001069 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
1070 ipmi::storage::cmdClearSel, ipmi::Privilege::Operator,
1071 ipmiStorageClearSEL);
Jason M. Billscac97a52019-01-30 14:43:46 -08001072
Jason M. Bills1a474622019-06-14 14:51:33 -07001073 // <Get SEL Time>
1074 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
1075 ipmi::storage::cmdGetSelTime,
1076 ipmi::Privilege::Operator, ipmiStorageGetSELTime);
1077
Jason M. Billscac97a52019-01-30 14:43:46 -08001078 // <Set SEL Time>
Jason M. Bills1d4d54d2019-04-23 11:26:11 -07001079 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
1080 ipmi::storage::cmdSetSelTime,
1081 ipmi::Privilege::Operator, ipmiStorageSetSELTime);
Jason M. Billse2d1aee2018-10-03 15:57:18 -07001082}
Jason M. Bills3f7c5e42018-10-03 14:00:41 -07001083} // namespace storage
Jason M. Billsc04e2e72018-11-28 15:15:56 -08001084} // namespace ipmi