blob: 5efdf665867a6aca9acfbd8a75b9c281cd221c8f [file] [log] [blame]
Jason M. Bills3f7c5e42018-10-03 14:00:41 -07001/*
2// Copyright (c) 2017 2018 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
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>
James Feist2a265d52019-04-08 11:16:27 -070023#include <ipmid/api.hpp>
Jason M. Billsc04e2e72018-11-28 15:15:56 -080024#include <phosphor-ipmi-host/selutility.hpp>
Jason M. Bills3f7c5e42018-10-03 14:00:41 -070025#include <phosphor-logging/log.hpp>
26#include <sdbusplus/message/types.hpp>
27#include <sdbusplus/timer.hpp>
Jason M. Billsc04e2e72018-11-28 15:15:56 -080028#include <sdrutils.hpp>
29#include <stdexcept>
Jason M. Bills3f7c5e42018-10-03 14:00:41 -070030#include <storagecommands.hpp>
Jason M. Bills52aaa7d2019-05-08 15:21:39 -070031#include <string_view>
Jason M. Bills3f7c5e42018-10-03 14:00:41 -070032
Jason M. Bills1d4d54d2019-04-23 11:26:11 -070033namespace intel_oem::ipmi::sel
34{
35static const std::filesystem::path selLogDir = "/var/log";
36static const std::string selLogFilename = "ipmi_sel";
37
38static int getFileTimestamp(const std::filesystem::path& file)
39{
40 struct stat st;
41
42 if (stat(file.c_str(), &st) >= 0)
43 {
44 return st.st_mtime;
45 }
46 return ::ipmi::sel::invalidTimeStamp;
47}
48
49namespace erase_time
Jason M. Bills7944c302019-03-20 15:24:05 -070050{
51static constexpr const char* selEraseTimestamp = "/var/lib/ipmi/sel_erase_time";
52
53void save()
54{
55 // open the file, creating it if necessary
56 int fd = open(selEraseTimestamp, O_WRONLY | O_CREAT | O_CLOEXEC, 0644);
57 if (fd < 0)
58 {
59 std::cerr << "Failed to open file\n";
60 return;
61 }
62
63 // update the file timestamp to the current time
64 if (futimens(fd, NULL) < 0)
65 {
66 std::cerr << "Failed to update timestamp: "
67 << std::string(strerror(errno));
68 }
69 close(fd);
70}
71
72int get()
73{
Jason M. Bills1d4d54d2019-04-23 11:26:11 -070074 return getFileTimestamp(selEraseTimestamp);
Jason M. Bills7944c302019-03-20 15:24:05 -070075}
Jason M. Bills1d4d54d2019-04-23 11:26:11 -070076} // namespace erase_time
77} // namespace intel_oem::ipmi::sel
Jason M. Bills7944c302019-03-20 15:24:05 -070078
Jason M. Bills3f7c5e42018-10-03 14:00:41 -070079namespace ipmi
80{
81
82namespace storage
83{
84
Jason M. Billse2d1aee2018-10-03 15:57:18 -070085constexpr static const size_t maxMessageSize = 64;
Jason M. Bills3f7c5e42018-10-03 14:00:41 -070086constexpr static const size_t maxFruSdrNameSize = 16;
87using ManagedObjectType = boost::container::flat_map<
88 sdbusplus::message::object_path,
89 boost::container::flat_map<
90 std::string, boost::container::flat_map<std::string, DbusVariant>>>;
91using ManagedEntry = std::pair<
92 sdbusplus::message::object_path,
93 boost::container::flat_map<
94 std::string, boost::container::flat_map<std::string, DbusVariant>>>;
95
James Feist3bcba452018-12-20 12:31:03 -080096constexpr static const char* fruDeviceServiceName =
97 "xyz.openbmc_project.FruDevice";
Jason M. Billse2d1aee2018-10-03 15:57:18 -070098constexpr static const size_t cacheTimeoutSeconds = 10;
Jason M. Bills3f7c5e42018-10-03 14:00:41 -070099
Jason M. Bills4ed6f2c2019-04-02 12:21:25 -0700100// event direction is bit[7] of eventType where 1b = Deassertion event
101constexpr static const uint8_t deassertionEvent = 0x80;
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800102
Jason M. Bills3f7c5e42018-10-03 14:00:41 -0700103static std::vector<uint8_t> fruCache;
104static uint8_t cacheBus = 0xFF;
105static uint8_t cacheAddr = 0XFF;
106
107std::unique_ptr<phosphor::Timer> cacheTimer = nullptr;
108
109// we unfortunately have to build a map of hashes in case there is a
110// collision to verify our dev-id
111boost::container::flat_map<uint8_t, std::pair<uint8_t, uint8_t>> deviceHashes;
112
Jason M. Billse2d1aee2018-10-03 15:57:18 -0700113void registerStorageFunctions() __attribute__((constructor));
Jason M. Bills3f7c5e42018-10-03 14:00:41 -0700114static sdbusplus::bus::bus dbus(ipmid_get_sd_bus_connection());
115
116bool writeFru()
117{
118 sdbusplus::message::message writeFru = dbus.new_method_call(
119 fruDeviceServiceName, "/xyz/openbmc_project/FruDevice",
120 "xyz.openbmc_project.FruDeviceManager", "WriteFru");
121 writeFru.append(cacheBus, cacheAddr, fruCache);
122 try
123 {
124 sdbusplus::message::message writeFruResp = dbus.call(writeFru);
125 }
126 catch (sdbusplus::exception_t&)
127 {
128 // todo: log sel?
129 phosphor::logging::log<phosphor::logging::level::ERR>(
130 "error writing fru");
131 return false;
132 }
133 return true;
134}
135
Jason M. Billse2d1aee2018-10-03 15:57:18 -0700136void createTimer()
137{
138 if (cacheTimer == nullptr)
139 {
140 cacheTimer = std::make_unique<phosphor::Timer>(writeFru);
141 }
142}
143
Jason M. Bills3f7c5e42018-10-03 14:00:41 -0700144ipmi_ret_t replaceCacheFru(uint8_t devId)
145{
146 static uint8_t lastDevId = 0xFF;
147
148 bool timerRunning = (cacheTimer != nullptr) && !cacheTimer->isExpired();
149 if (lastDevId == devId && timerRunning)
150 {
151 return IPMI_CC_OK; // cache already up to date
152 }
153 // if timer is running, stop it and writeFru manually
154 else if (timerRunning)
155 {
156 cacheTimer->stop();
157 writeFru();
158 }
159
160 sdbusplus::message::message getObjects = dbus.new_method_call(
161 fruDeviceServiceName, "/", "org.freedesktop.DBus.ObjectManager",
162 "GetManagedObjects");
163 ManagedObjectType frus;
164 try
165 {
166 sdbusplus::message::message resp = dbus.call(getObjects);
167 resp.read(frus);
168 }
169 catch (sdbusplus::exception_t&)
170 {
171 phosphor::logging::log<phosphor::logging::level::ERR>(
172 "replaceCacheFru: error getting managed objects");
173 return IPMI_CC_RESPONSE_ERROR;
174 }
175
176 deviceHashes.clear();
177
178 // hash the object paths to create unique device id's. increment on
179 // collision
180 std::hash<std::string> hasher;
181 for (const auto& fru : frus)
182 {
183 auto fruIface = fru.second.find("xyz.openbmc_project.FruDevice");
184 if (fruIface == fru.second.end())
185 {
186 continue;
187 }
188
189 auto busFind = fruIface->second.find("BUS");
190 auto addrFind = fruIface->second.find("ADDRESS");
191 if (busFind == fruIface->second.end() ||
192 addrFind == fruIface->second.end())
193 {
194 phosphor::logging::log<phosphor::logging::level::INFO>(
195 "fru device missing Bus or Address",
196 phosphor::logging::entry("FRU=%s", fru.first.str.c_str()));
197 continue;
198 }
199
200 uint8_t fruBus =
201 sdbusplus::message::variant_ns::get<uint32_t>(busFind->second);
202 uint8_t fruAddr =
203 sdbusplus::message::variant_ns::get<uint32_t>(addrFind->second);
204
205 uint8_t fruHash = 0;
206 if (fruBus != 0 || fruAddr != 0)
207 {
208 fruHash = hasher(fru.first.str);
209 // can't be 0xFF based on spec, and 0 is reserved for baseboard
210 if (fruHash == 0 || fruHash == 0xFF)
211 {
212 fruHash = 1;
213 }
214 }
215 std::pair<uint8_t, uint8_t> newDev(fruBus, fruAddr);
216
217 bool emplacePassed = false;
218 while (!emplacePassed)
219 {
220 auto resp = deviceHashes.emplace(fruHash, newDev);
221 emplacePassed = resp.second;
222 if (!emplacePassed)
223 {
224 fruHash++;
225 // can't be 0xFF based on spec, and 0 is reserved for
226 // baseboard
227 if (fruHash == 0XFF)
228 {
229 fruHash = 0x1;
230 }
231 }
232 }
233 }
234 auto deviceFind = deviceHashes.find(devId);
235 if (deviceFind == deviceHashes.end())
236 {
237 return IPMI_CC_SENSOR_INVALID;
238 }
239
240 fruCache.clear();
241 sdbusplus::message::message getRawFru = dbus.new_method_call(
242 fruDeviceServiceName, "/xyz/openbmc_project/FruDevice",
243 "xyz.openbmc_project.FruDeviceManager", "GetRawFru");
244 cacheBus = deviceFind->second.first;
245 cacheAddr = deviceFind->second.second;
246 getRawFru.append(cacheBus, cacheAddr);
247 try
248 {
249 sdbusplus::message::message getRawResp = dbus.call(getRawFru);
250 getRawResp.read(fruCache);
251 }
252 catch (sdbusplus::exception_t&)
253 {
254 lastDevId = 0xFF;
255 cacheBus = 0xFF;
256 cacheAddr = 0xFF;
257 return IPMI_CC_RESPONSE_ERROR;
258 }
259
260 lastDevId = devId;
261 return IPMI_CC_OK;
262}
263
Jason M. Billse2d1aee2018-10-03 15:57:18 -0700264ipmi_ret_t ipmiStorageReadFRUData(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
265 ipmi_request_t request,
266 ipmi_response_t response,
267 ipmi_data_len_t dataLen,
268 ipmi_context_t context)
269{
270 if (*dataLen != 4)
271 {
272 *dataLen = 0;
273 return IPMI_CC_REQ_DATA_LEN_INVALID;
274 }
275 *dataLen = 0; // default to 0 in case of an error
276
277 auto req = static_cast<GetFRUAreaReq*>(request);
278
279 if (req->countToRead > maxMessageSize - 1)
280 {
281 return IPMI_CC_INVALID_FIELD_REQUEST;
282 }
283 ipmi_ret_t status = replaceCacheFru(req->fruDeviceID);
284
285 if (status != IPMI_CC_OK)
286 {
287 return status;
288 }
289
290 size_t fromFRUByteLen = 0;
291 if (req->countToRead + req->fruInventoryOffset < fruCache.size())
292 {
293 fromFRUByteLen = req->countToRead;
294 }
295 else if (fruCache.size() > req->fruInventoryOffset)
296 {
297 fromFRUByteLen = fruCache.size() - req->fruInventoryOffset;
298 }
299 size_t padByteLen = req->countToRead - fromFRUByteLen;
300 uint8_t* respPtr = static_cast<uint8_t*>(response);
301 *respPtr = req->countToRead;
302 std::copy(fruCache.begin() + req->fruInventoryOffset,
303 fruCache.begin() + req->fruInventoryOffset + fromFRUByteLen,
304 ++respPtr);
305 // if longer than the fru is requested, fill with 0xFF
306 if (padByteLen)
307 {
308 respPtr += fromFRUByteLen;
309 std::fill(respPtr, respPtr + padByteLen, 0xFF);
310 }
311 *dataLen = fromFRUByteLen + 1;
312
313 return IPMI_CC_OK;
314}
315
316ipmi_ret_t ipmiStorageWriteFRUData(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
317 ipmi_request_t request,
318 ipmi_response_t response,
319 ipmi_data_len_t dataLen,
320 ipmi_context_t context)
321{
322 if (*dataLen < 4 ||
323 *dataLen >=
324 0xFF + 3) // count written return is one byte, so limit to one byte
325 // of data after the three request data bytes
326 {
327 *dataLen = 0;
328 return IPMI_CC_REQ_DATA_LEN_INVALID;
329 }
330
331 auto req = static_cast<WriteFRUDataReq*>(request);
332 size_t writeLen = *dataLen - 3;
333 *dataLen = 0; // default to 0 in case of an error
334
335 ipmi_ret_t status = replaceCacheFru(req->fruDeviceID);
336 if (status != IPMI_CC_OK)
337 {
338 return status;
339 }
340 int lastWriteAddr = req->fruInventoryOffset + writeLen;
341 if (fruCache.size() < lastWriteAddr)
342 {
343 fruCache.resize(req->fruInventoryOffset + writeLen);
344 }
345
346 std::copy(req->data, req->data + writeLen,
347 fruCache.begin() + req->fruInventoryOffset);
348
349 bool atEnd = false;
350
351 if (fruCache.size() >= sizeof(FRUHeader))
352 {
353
354 FRUHeader* header = reinterpret_cast<FRUHeader*>(fruCache.data());
355
356 int lastRecordStart = std::max(
357 header->internalOffset,
358 std::max(header->chassisOffset,
359 std::max(header->boardOffset, header->productOffset)));
360 // TODO: Handle Multi-Record FRUs?
361
362 lastRecordStart *= 8; // header starts in are multiples of 8 bytes
363
364 // get the length of the area in multiples of 8 bytes
365 if (lastWriteAddr > (lastRecordStart + 1))
366 {
367 // second byte in record area is the length
368 int areaLength(fruCache[lastRecordStart + 1]);
369 areaLength *= 8; // it is in multiples of 8 bytes
370
371 if (lastWriteAddr >= (areaLength + lastRecordStart))
372 {
373 atEnd = true;
374 }
375 }
376 }
377 uint8_t* respPtr = static_cast<uint8_t*>(response);
378 if (atEnd)
379 {
380 // cancel timer, we're at the end so might as well send it
381 cacheTimer->stop();
382 if (!writeFru())
383 {
384 return IPMI_CC_INVALID_FIELD_REQUEST;
385 }
386 *respPtr = std::min(fruCache.size(), static_cast<size_t>(0xFF));
387 }
388 else
389 {
390 // start a timer, if no further data is sent in cacheTimeoutSeconds
391 // seconds, check to see if it is valid
392 createTimer();
393 cacheTimer->start(std::chrono::duration_cast<std::chrono::microseconds>(
394 std::chrono::seconds(cacheTimeoutSeconds)));
395 *respPtr = 0;
396 }
397
398 *dataLen = 1;
399
400 return IPMI_CC_OK;
401}
402
403ipmi_ret_t ipmiStorageGetFRUInvAreaInfo(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
404 ipmi_request_t request,
405 ipmi_response_t response,
406 ipmi_data_len_t dataLen,
407 ipmi_context_t context)
408{
409 if (*dataLen != 1)
410 {
411 *dataLen = 0;
412 return IPMI_CC_REQ_DATA_LEN_INVALID;
413 }
414 *dataLen = 0; // default to 0 in case of an error
415
416 uint8_t reqDev = *(static_cast<uint8_t*>(request));
417 if (reqDev == 0xFF)
418 {
419 return IPMI_CC_INVALID_FIELD_REQUEST;
420 }
421 ipmi_ret_t status = replaceCacheFru(reqDev);
422
423 if (status != IPMI_CC_OK)
424 {
425 return status;
426 }
427
428 GetFRUAreaResp* respPtr = static_cast<GetFRUAreaResp*>(response);
429 respPtr->inventorySizeLSB = fruCache.size() & 0xFF;
430 respPtr->inventorySizeMSB = fruCache.size() >> 8;
431 respPtr->accessType = static_cast<uint8_t>(GetFRUAreaAccessType::byte);
432
433 *dataLen = sizeof(GetFRUAreaResp);
434 return IPMI_CC_OK;
435}
436
Jason M. Bills3f7c5e42018-10-03 14:00:41 -0700437ipmi_ret_t getFruSdrCount(size_t& count)
438{
439 ipmi_ret_t ret = replaceCacheFru(0);
440 if (ret != IPMI_CC_OK)
441 {
442 return ret;
443 }
444 count = deviceHashes.size();
445 return IPMI_CC_OK;
446}
447
448ipmi_ret_t getFruSdrs(size_t index, get_sdr::SensorDataFruRecord& resp)
449{
450 ipmi_ret_t ret = replaceCacheFru(0); // this will update the hash list
451 if (ret != IPMI_CC_OK)
452 {
453 return ret;
454 }
455 if (deviceHashes.size() < index)
456 {
457 return IPMI_CC_INVALID_FIELD_REQUEST;
458 }
459 auto device = deviceHashes.begin() + index;
460 uint8_t& bus = device->second.first;
461 uint8_t& address = device->second.second;
462
463 ManagedObjectType frus;
464
465 sdbusplus::message::message getObjects = dbus.new_method_call(
466 fruDeviceServiceName, "/", "org.freedesktop.DBus.ObjectManager",
467 "GetManagedObjects");
468 try
469 {
470 sdbusplus::message::message resp = dbus.call(getObjects);
471 resp.read(frus);
472 }
473 catch (sdbusplus::exception_t&)
474 {
475 return IPMI_CC_RESPONSE_ERROR;
476 }
477 boost::container::flat_map<std::string, DbusVariant>* fruData = nullptr;
478 auto fru =
479 std::find_if(frus.begin(), frus.end(),
480 [bus, address, &fruData](ManagedEntry& entry) {
481 auto findFruDevice =
482 entry.second.find("xyz.openbmc_project.FruDevice");
483 if (findFruDevice == entry.second.end())
484 {
485 return false;
486 }
487 fruData = &(findFruDevice->second);
488 auto findBus = findFruDevice->second.find("BUS");
489 auto findAddress =
490 findFruDevice->second.find("ADDRESS");
491 if (findBus == findFruDevice->second.end() ||
492 findAddress == findFruDevice->second.end())
493 {
494 return false;
495 }
496 if (sdbusplus::message::variant_ns::get<uint32_t>(
497 findBus->second) != bus)
498 {
499 return false;
500 }
501 if (sdbusplus::message::variant_ns::get<uint32_t>(
502 findAddress->second) != address)
503 {
504 return false;
505 }
506 return true;
507 });
508 if (fru == frus.end())
509 {
510 return IPMI_CC_RESPONSE_ERROR;
511 }
512 std::string name;
513 auto findProductName = fruData->find("BOARD_PRODUCT_NAME");
514 auto findBoardName = fruData->find("PRODUCT_PRODUCT_NAME");
515 if (findProductName != fruData->end())
516 {
517 name = sdbusplus::message::variant_ns::get<std::string>(
518 findProductName->second);
519 }
520 else if (findBoardName != fruData->end())
521 {
522 name = sdbusplus::message::variant_ns::get<std::string>(
523 findBoardName->second);
524 }
525 else
526 {
527 name = "UNKNOWN";
528 }
529 if (name.size() > maxFruSdrNameSize)
530 {
531 name = name.substr(0, maxFruSdrNameSize);
532 }
533 size_t sizeDiff = maxFruSdrNameSize - name.size();
534
535 resp.header.record_id_lsb = 0x0; // calling code is to implement these
536 resp.header.record_id_msb = 0x0;
537 resp.header.sdr_version = ipmiSdrVersion;
538 resp.header.record_type = 0x11; // FRU Device Locator
539 resp.header.record_length = sizeof(resp.body) + sizeof(resp.key) - sizeDiff;
540 resp.key.deviceAddress = 0x20;
541 resp.key.fruID = device->first;
542 resp.key.accessLun = 0x80; // logical / physical fru device
543 resp.key.channelNumber = 0x0;
544 resp.body.reserved = 0x0;
545 resp.body.deviceType = 0x10;
James Feist4f86d1f2019-04-03 10:30:26 -0700546 resp.body.deviceTypeModifier = 0x0;
Jason M. Bills3f7c5e42018-10-03 14:00:41 -0700547 resp.body.entityID = 0x0;
548 resp.body.entityInstance = 0x1;
549 resp.body.oem = 0x0;
550 resp.body.deviceIDLen = name.size();
551 name.copy(resp.body.deviceID, name.size());
552
553 return IPMI_CC_OK;
554}
Jason M. Billse2d1aee2018-10-03 15:57:18 -0700555
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700556static bool getSELLogFiles(std::vector<std::filesystem::path>& selLogFiles)
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800557{
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700558 // Loop through the directory looking for ipmi_sel log files
559 for (const std::filesystem::directory_entry& dirEnt :
560 std::filesystem::directory_iterator(intel_oem::ipmi::sel::selLogDir))
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800561 {
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700562 std::string filename = dirEnt.path().filename();
563 if (boost::starts_with(filename, intel_oem::ipmi::sel::selLogFilename))
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800564 {
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700565 // If we find an ipmi_sel log file, save the path
566 selLogFiles.emplace_back(intel_oem::ipmi::sel::selLogDir /
567 filename);
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800568 }
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800569 }
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700570 // As the log files rotate, they are appended with a ".#" that is higher for
571 // the older logs. Since we don't expect more than 10 log files, we
572 // can just sort the list to get them in order from newest to oldest
573 std::sort(selLogFiles.begin(), selLogFiles.end());
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800574
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700575 return !selLogFiles.empty();
576}
577
578static int countSELEntries()
579{
580 // Get the list of ipmi_sel log files
581 std::vector<std::filesystem::path> selLogFiles;
582 if (!getSELLogFiles(selLogFiles))
583 {
584 return 0;
585 }
586 int numSELEntries = 0;
587 // Loop through each log file and count the number of logs
588 for (const std::filesystem::path& file : selLogFiles)
589 {
590 std::ifstream logStream(file);
591 if (!logStream.is_open())
592 {
593 continue;
594 }
595
596 std::string line;
597 while (std::getline(logStream, line))
598 {
599 numSELEntries++;
600 }
601 }
602 return numSELEntries;
603}
604
605static bool findSELEntry(const int recordID,
606 const std::vector<std::filesystem::path> selLogFiles,
607 std::string& entry)
608{
609 // Record ID is the first entry field following the timestamp. It is
610 // preceded by a space and followed by a comma
611 std::string search = " " + std::to_string(recordID) + ",";
612
613 // Loop through the ipmi_sel log entries
614 for (const std::filesystem::path& file : selLogFiles)
615 {
616 std::ifstream logStream(file);
617 if (!logStream.is_open())
618 {
619 continue;
620 }
621
622 while (std::getline(logStream, entry))
623 {
624 // Check if the record ID matches
625 if (entry.find(search) != std::string::npos)
626 {
627 return true;
628 }
629 }
630 }
631 return false;
632}
633
634static uint16_t
635 getNextRecordID(const uint16_t recordID,
636 const std::vector<std::filesystem::path> selLogFiles)
637{
638 uint16_t nextRecordID = recordID + 1;
639 std::string entry;
640 if (findSELEntry(nextRecordID, selLogFiles, entry))
641 {
642 return nextRecordID;
643 }
644 else
645 {
646 return ipmi::sel::lastEntry;
647 }
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800648}
649
650static int fromHexStr(const std::string hexStr, std::vector<uint8_t>& data)
651{
652 for (unsigned int i = 0; i < hexStr.size(); i += 2)
653 {
654 try
655 {
656 data.push_back(static_cast<uint8_t>(
657 std::stoul(hexStr.substr(i, 2), nullptr, 16)));
658 }
659 catch (std::invalid_argument& e)
660 {
661 phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
662 return -1;
663 }
664 catch (std::out_of_range& e)
665 {
666 phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
667 return -1;
668 }
669 }
670 return 0;
671}
672
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700673ipmi::RspType<uint8_t, // SEL version
674 uint16_t, // SEL entry count
675 uint16_t, // free space
676 uint32_t, // last add timestamp
677 uint32_t, // last erase timestamp
678 uint8_t> // operation support
679 ipmiStorageGetSELInfo()
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800680{
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700681 constexpr uint8_t selVersion = ipmi::sel::selVersion;
682 uint16_t entries = countSELEntries();
683 uint32_t addTimeStamp = intel_oem::ipmi::sel::getFileTimestamp(
684 intel_oem::ipmi::sel::selLogDir / intel_oem::ipmi::sel::selLogFilename);
685 uint32_t eraseTimeStamp = intel_oem::ipmi::sel::erase_time::get();
686 constexpr uint8_t operationSupport =
687 intel_oem::ipmi::sel::selOperationSupport;
688 constexpr uint16_t freeSpace =
689 0xffff; // Spec indicates that more than 64kB is free
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800690
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700691 return ipmi::responseSuccess(selVersion, entries, freeSpace, addTimeStamp,
692 eraseTimeStamp, operationSupport);
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800693}
694
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700695using systemEventType = std::tuple<
696 uint32_t, // Timestamp
697 uint16_t, // Generator ID
698 uint8_t, // EvM Rev
699 uint8_t, // Sensor Type
700 uint8_t, // Sensor Number
701 uint7_t, // Event Type
702 bool, // Event Direction
703 std::array<uint8_t, intel_oem::ipmi::sel::systemEventSize>>; // Event Data
704using oemTsEventType = std::tuple<
705 uint32_t, // Timestamp
706 std::array<uint8_t, intel_oem::ipmi::sel::oemTsEventSize>>; // Event Data
707using oemEventType =
708 std::array<uint8_t, intel_oem::ipmi::sel::oemEventSize>; // Event Data
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800709
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700710ipmi::RspType<uint16_t, // Next Record ID
711 uint16_t, // Record ID
712 uint8_t, // Record Type
713 std::variant<systemEventType, oemTsEventType,
714 oemEventType>> // Record Content
715 ipmiStorageGetSELEntry(uint16_t reservationID, uint16_t targetID,
716 uint8_t offset, uint8_t size)
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800717{
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700718 // Only support getting the entire SEL record. If a partial size or non-zero
719 // offset is requested, return an error
720 if (offset != 0 || size != ipmi::sel::entireRecord)
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800721 {
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700722 return ipmi::responseRetBytesUnavailable();
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800723 }
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800724
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700725 // Check the reservation ID if one is provided or required (only if the
726 // offset is non-zero)
727 if (reservationID != 0 || offset != 0)
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800728 {
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700729 if (!checkSELReservation(reservationID))
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800730 {
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700731 return ipmi::responseInvalidReservationId();
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800732 }
733 }
734
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700735 // Get the ipmi_sel log files
736 std::vector<std::filesystem::path> selLogFiles;
737 if (!getSELLogFiles(selLogFiles))
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800738 {
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700739 return ipmi::responseSensorInvalid();
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800740 }
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800741
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700742 std::string targetEntry;
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800743
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800744 if (targetID == ipmi::sel::firstEntry)
745 {
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700746 // The first entry will be at the top of the oldest log file
747 std::ifstream logStream(selLogFiles.back());
748 if (!logStream.is_open())
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800749 {
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700750 return ipmi::responseUnspecifiedError();
751 }
752
753 if (!std::getline(logStream, targetEntry))
754 {
755 return ipmi::responseUnspecifiedError();
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800756 }
757 }
758 else if (targetID == ipmi::sel::lastEntry)
759 {
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700760 // The last entry will be at the bottom of the newest log file
761 std::ifstream logStream(selLogFiles.front());
762 if (!logStream.is_open())
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800763 {
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700764 return ipmi::responseUnspecifiedError();
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800765 }
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700766
767 std::string line;
768 while (std::getline(logStream, line))
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800769 {
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700770 targetEntry = line;
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800771 }
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800772 }
773 else
774 {
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700775 if (!findSELEntry(targetID, selLogFiles, targetEntry))
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800776 {
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700777 return ipmi::responseSensorInvalid();
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800778 }
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800779 }
780
Jason M. Bills52aaa7d2019-05-08 15:21:39 -0700781 // The format of the ipmi_sel message is "<Timestamp>
782 // <ID>,<Type>,<EventData>,[<Generator ID>,<Path>,<Direction>]".
783 // First get the Timestamp
784 size_t space = targetEntry.find_first_of(" ");
785 if (space == std::string::npos)
786 {
787 return ipmi::responseUnspecifiedError();
788 }
789 std::string entryTimestamp = targetEntry.substr(0, space);
790 // Then get the log contents
791 size_t entryStart = targetEntry.find_first_not_of(" ", space);
792 if (entryStart == std::string::npos)
793 {
794 return ipmi::responseUnspecifiedError();
795 }
796 std::string_view entry(targetEntry);
797 entry.remove_prefix(entryStart);
798 // Use split to separate the entry into its fields
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700799 std::vector<std::string> targetEntryFields;
Jason M. Bills52aaa7d2019-05-08 15:21:39 -0700800 boost::split(targetEntryFields, entry, boost::is_any_of(","),
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700801 boost::token_compress_on);
Jason M. Bills52aaa7d2019-05-08 15:21:39 -0700802 if (targetEntryFields.size() < 3)
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700803 {
804 return ipmi::responseUnspecifiedError();
805 }
806
Jason M. Bills52aaa7d2019-05-08 15:21:39 -0700807 uint16_t recordID = std::stoul(targetEntryFields[0]);
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700808 uint16_t nextRecordID = getNextRecordID(recordID, selLogFiles);
Jason M. Bills52aaa7d2019-05-08 15:21:39 -0700809 uint8_t recordType = std::stoul(targetEntryFields[1], nullptr, 16);
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700810 std::vector<uint8_t> eventDataBytes;
Jason M. Bills52aaa7d2019-05-08 15:21:39 -0700811 if (fromHexStr(targetEntryFields[2], eventDataBytes) < 0)
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700812 {
813 return ipmi::responseUnspecifiedError();
814 }
815
816 if (recordType == intel_oem::ipmi::sel::systemEvent)
817 {
818 // System type events have three additional fields
Jason M. Bills52aaa7d2019-05-08 15:21:39 -0700819 if (targetEntryFields.size() < 6)
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700820 {
821 return ipmi::responseUnspecifiedError();
822 }
823
824 // Get the timestamp
825 std::tm timeStruct = {};
Jason M. Bills52aaa7d2019-05-08 15:21:39 -0700826 std::istringstream entryStream(entryTimestamp);
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700827
828 uint32_t timestamp = ipmi::sel::invalidTimeStamp;
829 if (entryStream >> std::get_time(&timeStruct, "%Y-%m-%dT%H:%M:%S"))
830 {
831 timestamp = std::mktime(&timeStruct);
832 }
833
834 // Get the generator ID
Jason M. Bills52aaa7d2019-05-08 15:21:39 -0700835 uint16_t generatorID = std::stoul(targetEntryFields[3], nullptr, 16);
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700836
837 // Set the event message revision
838 uint8_t evmRev = intel_oem::ipmi::sel::eventMsgRev;
839
840 // Get the sensor type, sensor number, and event type for the sensor
Jason M. Bills52aaa7d2019-05-08 15:21:39 -0700841 uint8_t sensorType = getSensorTypeFromPath(targetEntryFields[4]);
842 uint8_t sensorNum = getSensorNumberFromPath(targetEntryFields[4]);
843 uint7_t eventType = getSensorEventTypeFromPath(targetEntryFields[4]);
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700844
845 // Get the event direction
Jason M. Bills52aaa7d2019-05-08 15:21:39 -0700846 bool eventDir = std::stoul(targetEntryFields[5]) ? 0 : 1;
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700847
848 // Only keep the eventData bytes that fit in the record
849 std::array<uint8_t, intel_oem::ipmi::sel::systemEventSize> eventData{};
850 std::copy_n(eventDataBytes.begin(),
851 std::min(eventDataBytes.size(), eventData.size()),
852 eventData.begin());
853
854 return ipmi::responseSuccess(
855 nextRecordID, recordID, recordType,
856 systemEventType{timestamp, generatorID, evmRev, sensorType,
857 sensorNum, eventType, eventDir, eventData});
858 }
859 else if (recordType >= intel_oem::ipmi::sel::oemTsEventFirst &&
860 recordType <= intel_oem::ipmi::sel::oemTsEventLast)
861 {
862 // Get the timestamp
863 std::tm timeStruct = {};
Jason M. Bills52aaa7d2019-05-08 15:21:39 -0700864 std::istringstream entryStream(entryTimestamp);
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700865
866 uint32_t timestamp = ipmi::sel::invalidTimeStamp;
867 if (entryStream >> std::get_time(&timeStruct, "%Y-%m-%dT%H:%M:%S"))
868 {
869 timestamp = std::mktime(&timeStruct);
870 }
871
872 // Only keep the bytes that fit in the record
873 std::array<uint8_t, intel_oem::ipmi::sel::oemTsEventSize> eventData{};
874 std::copy_n(eventDataBytes.begin(),
875 std::min(eventDataBytes.size(), eventData.size()),
876 eventData.begin());
877
878 return ipmi::responseSuccess(nextRecordID, recordID, recordType,
879 oemTsEventType{timestamp, eventData});
880 }
881 else if (recordType >= intel_oem::ipmi::sel::oemEventFirst &&
882 recordType <= intel_oem::ipmi::sel::oemEventLast)
883 {
884 // Only keep the bytes that fit in the record
885 std::array<uint8_t, intel_oem::ipmi::sel::oemEventSize> eventData{};
886 std::copy_n(eventDataBytes.begin(),
887 std::min(eventDataBytes.size(), eventData.size()),
888 eventData.begin());
889
890 return ipmi::responseSuccess(nextRecordID, recordID, recordType,
891 eventData);
892 }
893
894 return ipmi::responseUnspecifiedError();
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800895}
896
897ipmi_ret_t ipmiStorageAddSELEntry(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
898 ipmi_request_t request,
899 ipmi_response_t response,
900 ipmi_data_len_t data_len,
901 ipmi_context_t context)
902{
903 static constexpr char const* ipmiSELObject =
904 "xyz.openbmc_project.Logging.IPMI";
905 static constexpr char const* ipmiSELPath =
906 "/xyz/openbmc_project/Logging/IPMI";
907 static constexpr char const* ipmiSELAddInterface =
908 "xyz.openbmc_project.Logging.IPMI";
909 static const std::string ipmiSELAddMessage =
910 "IPMI SEL entry logged using IPMI Add SEL Entry command.";
911 uint16_t recordID = 0;
912 sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()};
913
914 if (*data_len != sizeof(AddSELRequest))
915 {
916 *data_len = 0;
917 return IPMI_CC_REQ_DATA_LEN_INVALID;
918 }
919 AddSELRequest* req = static_cast<AddSELRequest*>(request);
920
921 // Per the IPMI spec, need to cancel any reservation when a SEL entry is
922 // added
923 cancelSELReservation();
924
925 if (req->recordType == intel_oem::ipmi::sel::systemEvent)
926 {
927 std::string sensorPath =
928 getPathFromSensorNumber(req->record.system.sensorNum);
929 std::vector<uint8_t> eventData(
930 req->record.system.eventData,
931 req->record.system.eventData +
932 intel_oem::ipmi::sel::systemEventSize);
Jason M. Bills4ed6f2c2019-04-02 12:21:25 -0700933 bool assert = !(req->record.system.eventType & deassertionEvent);
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800934 uint16_t genId = req->record.system.generatorID;
935 sdbusplus::message::message writeSEL = bus.new_method_call(
936 ipmiSELObject, ipmiSELPath, ipmiSELAddInterface, "IpmiSelAdd");
937 writeSEL.append(ipmiSELAddMessage, sensorPath, eventData, assert,
938 genId);
939 try
940 {
941 sdbusplus::message::message writeSELResp = bus.call(writeSEL);
942 writeSELResp.read(recordID);
943 }
944 catch (sdbusplus::exception_t& e)
945 {
946 phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
947 *data_len = 0;
948 return IPMI_CC_UNSPECIFIED_ERROR;
949 }
950 }
951 else if (req->recordType >= intel_oem::ipmi::sel::oemTsEventFirst &&
952 req->recordType <= intel_oem::ipmi::sel::oemEventLast)
953 {
954 std::vector<uint8_t> eventData;
955 if (req->recordType <= intel_oem::ipmi::sel::oemTsEventLast)
956 {
957 eventData =
958 std::vector<uint8_t>(req->record.oemTs.eventData,
959 req->record.oemTs.eventData +
960 intel_oem::ipmi::sel::oemTsEventSize);
961 }
962 else
963 {
964 eventData = std::vector<uint8_t>(
965 req->record.oem.eventData,
966 req->record.oem.eventData + intel_oem::ipmi::sel::oemEventSize);
967 }
968 sdbusplus::message::message writeSEL = bus.new_method_call(
969 ipmiSELObject, ipmiSELPath, ipmiSELAddInterface, "IpmiSelAddOem");
970 writeSEL.append(ipmiSELAddMessage, eventData, req->recordType);
971 try
972 {
973 sdbusplus::message::message writeSELResp = bus.call(writeSEL);
974 writeSELResp.read(recordID);
975 }
976 catch (sdbusplus::exception_t& e)
977 {
978 phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
979 *data_len = 0;
980 return IPMI_CC_UNSPECIFIED_ERROR;
981 }
982 }
983 else
984 {
985 *data_len = 0;
986 return IPMI_CC_PARM_OUT_OF_RANGE;
987 }
988
989 *static_cast<uint16_t*>(response) = recordID;
990 *data_len = sizeof(recordID);
991 return IPMI_CC_OK;
992}
993
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700994ipmi::RspType<uint8_t> ipmiStorageClearSEL(ipmi::Context::ptr ctx,
995 uint16_t reservationID,
996 const std::array<uint8_t, 3>& clr,
997 uint8_t eraseOperation)
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800998{
Jason M. Bills1d4d54d2019-04-23 11:26:11 -0700999 if (!checkSELReservation(reservationID))
Jason M. Billsc04e2e72018-11-28 15:15:56 -08001000 {
Jason M. Bills1d4d54d2019-04-23 11:26:11 -07001001 return ipmi::responseInvalidReservationId();
Jason M. Billsc04e2e72018-11-28 15:15:56 -08001002 }
1003
Jason M. Bills1d4d54d2019-04-23 11:26:11 -07001004 static constexpr std::array<uint8_t, 3> clrExpected = {'C', 'L', 'R'};
1005 if (clr != clrExpected)
Jason M. Billsc04e2e72018-11-28 15:15:56 -08001006 {
Jason M. Bills1d4d54d2019-04-23 11:26:11 -07001007 return ipmi::responseInvalidFieldRequest();
Jason M. Billsc04e2e72018-11-28 15:15:56 -08001008 }
1009
Jason M. Bills1d4d54d2019-04-23 11:26:11 -07001010 // Erasure status cannot be fetched, so always return erasure status as
1011 // `erase completed`.
1012 if (eraseOperation == ipmi::sel::getEraseStatus)
Jason M. Billsc04e2e72018-11-28 15:15:56 -08001013 {
Jason M. Bills1d4d54d2019-04-23 11:26:11 -07001014 return ipmi::responseSuccess(ipmi::sel::eraseComplete);
Jason M. Billsc04e2e72018-11-28 15:15:56 -08001015 }
1016
Jason M. Bills1d4d54d2019-04-23 11:26:11 -07001017 // Check that initiate erase is correct
1018 if (eraseOperation != ipmi::sel::initiateErase)
1019 {
1020 return ipmi::responseInvalidFieldRequest();
1021 }
1022
1023 // Per the IPMI spec, need to cancel any reservation when the SEL is
1024 // cleared
Jason M. Billsc04e2e72018-11-28 15:15:56 -08001025 cancelSELReservation();
1026
Jason M. Bills7944c302019-03-20 15:24:05 -07001027 // Save the erase time
1028 intel_oem::ipmi::sel::erase_time::save();
1029
Jason M. Bills1d4d54d2019-04-23 11:26:11 -07001030 // Clear the SEL by deleting the log files
1031 std::vector<std::filesystem::path> selLogFiles;
1032 if (getSELLogFiles(selLogFiles))
Jason M. Billsc04e2e72018-11-28 15:15:56 -08001033 {
Jason M. Bills1d4d54d2019-04-23 11:26:11 -07001034 for (const std::filesystem::path& file : selLogFiles)
1035 {
1036 std::error_code ec;
1037 std::filesystem::remove(file, ec);
1038 }
Jason M. Billsc04e2e72018-11-28 15:15:56 -08001039 }
1040
Jason M. Bills1d4d54d2019-04-23 11:26:11 -07001041 // Reload rsyslog so it knows to start new log files
1042 sdbusplus::message::message rsyslogReload = dbus.new_method_call(
1043 "org.freedesktop.systemd1", "/org/freedesktop/systemd1",
1044 "org.freedesktop.systemd1.Manager", "ReloadUnit");
1045 rsyslogReload.append("rsyslog.service", "replace");
1046 try
1047 {
1048 sdbusplus::message::message reloadResponse = dbus.call(rsyslogReload);
1049 }
1050 catch (sdbusplus::exception_t& e)
1051 {
1052 phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
1053 }
1054
1055 return ipmi::responseSuccess(ipmi::sel::eraseComplete);
Jason M. Billsc04e2e72018-11-28 15:15:56 -08001056}
1057
Jason M. Bills1d4d54d2019-04-23 11:26:11 -07001058ipmi::RspType<> ipmiStorageSetSELTime(uint32_t selTime)
Jason M. Billscac97a52019-01-30 14:43:46 -08001059{
1060 // Set SEL Time is not supported
Jason M. Bills1d4d54d2019-04-23 11:26:11 -07001061 return ipmi::responseInvalidCommand();
Jason M. Billscac97a52019-01-30 14:43:46 -08001062}
1063
Jason M. Billse2d1aee2018-10-03 15:57:18 -07001064void registerStorageFunctions()
1065{
1066 // <Get FRU Inventory Area Info>
1067 ipmiPrintAndRegister(
1068 NETFUN_STORAGE,
1069 static_cast<ipmi_cmd_t>(IPMINetfnStorageCmds::ipmiCmdGetFRUInvAreaInfo),
1070 NULL, ipmiStorageGetFRUInvAreaInfo, PRIVILEGE_OPERATOR);
1071
Jason M. Billsc04e2e72018-11-28 15:15:56 -08001072 // <READ FRU Data>
Jason M. Billse2d1aee2018-10-03 15:57:18 -07001073 ipmiPrintAndRegister(
1074 NETFUN_STORAGE,
1075 static_cast<ipmi_cmd_t>(IPMINetfnStorageCmds::ipmiCmdReadFRUData), NULL,
1076 ipmiStorageReadFRUData, PRIVILEGE_OPERATOR);
1077
Jason M. Billsc04e2e72018-11-28 15:15:56 -08001078 // <WRITE FRU Data>
Jason M. Billse2d1aee2018-10-03 15:57:18 -07001079 ipmiPrintAndRegister(
1080 NETFUN_STORAGE,
1081 static_cast<ipmi_cmd_t>(IPMINetfnStorageCmds::ipmiCmdWriteFRUData),
1082 NULL, ipmiStorageWriteFRUData, PRIVILEGE_OPERATOR);
Jason M. Billsc04e2e72018-11-28 15:15:56 -08001083
1084 // <Get SEL Info>
Jason M. Bills1d4d54d2019-04-23 11:26:11 -07001085 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
1086 ipmi::storage::cmdGetSelInfo,
1087 ipmi::Privilege::Operator, ipmiStorageGetSELInfo);
Jason M. Billsc04e2e72018-11-28 15:15:56 -08001088
1089 // <Get SEL Entry>
Jason M. Bills1d4d54d2019-04-23 11:26:11 -07001090 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
1091 ipmi::storage::cmdGetSelEntry,
1092 ipmi::Privilege::Operator, ipmiStorageGetSELEntry);
Jason M. Billsc04e2e72018-11-28 15:15:56 -08001093
1094 // <Add SEL Entry>
1095 ipmiPrintAndRegister(
1096 NETFUN_STORAGE,
1097 static_cast<ipmi_cmd_t>(IPMINetfnStorageCmds::ipmiCmdAddSEL), NULL,
1098 ipmiStorageAddSELEntry, PRIVILEGE_OPERATOR);
1099
1100 // <Clear SEL>
Jason M. Bills1d4d54d2019-04-23 11:26:11 -07001101 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
1102 ipmi::storage::cmdClearSel, ipmi::Privilege::Operator,
1103 ipmiStorageClearSEL);
Jason M. Billscac97a52019-01-30 14:43:46 -08001104
1105 // <Set SEL Time>
Jason M. Bills1d4d54d2019-04-23 11:26:11 -07001106 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
1107 ipmi::storage::cmdSetSelTime,
1108 ipmi::Privilege::Operator, ipmiStorageSetSELTime);
Jason M. Billse2d1aee2018-10-03 15:57:18 -07001109}
Jason M. Bills3f7c5e42018-10-03 14:00:41 -07001110} // namespace storage
Jason M. Billsc04e2e72018-11-28 15:15:56 -08001111} // namespace ipmi