blob: 695e0a94cd928ec7562061e199b1544c60aebfaf [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. Bills6d9c83f2019-02-08 14:02:19 -080017#include <ipmid/api.h>
Jason M. Bills3f7c5e42018-10-03 14:00:41 -070018
19#include <boost/container/flat_map.hpp>
Jason M. Billsc04e2e72018-11-28 15:15:56 -080020#include <boost/process.hpp>
Jason M. Bills3f7c5e42018-10-03 14:00:41 -070021#include <commandutils.hpp>
22#include <iostream>
Jason M. Billsc04e2e72018-11-28 15:15:56 -080023#include <phosphor-ipmi-host/selutility.hpp>
Jason M. Bills3f7c5e42018-10-03 14:00:41 -070024#include <phosphor-logging/log.hpp>
25#include <sdbusplus/message/types.hpp>
26#include <sdbusplus/timer.hpp>
Jason M. Billsc04e2e72018-11-28 15:15:56 -080027#include <sdrutils.hpp>
28#include <stdexcept>
Jason M. Bills3f7c5e42018-10-03 14:00:41 -070029#include <storagecommands.hpp>
Jason M. Billsc04e2e72018-11-28 15:15:56 -080030#include <string_view>
Jason M. Bills3f7c5e42018-10-03 14:00:41 -070031
32namespace ipmi
33{
34
35namespace storage
36{
37
Jason M. Billse2d1aee2018-10-03 15:57:18 -070038constexpr static const size_t maxMessageSize = 64;
Jason M. Bills3f7c5e42018-10-03 14:00:41 -070039constexpr static const size_t maxFruSdrNameSize = 16;
40using ManagedObjectType = boost::container::flat_map<
41 sdbusplus::message::object_path,
42 boost::container::flat_map<
43 std::string, boost::container::flat_map<std::string, DbusVariant>>>;
44using ManagedEntry = std::pair<
45 sdbusplus::message::object_path,
46 boost::container::flat_map<
47 std::string, boost::container::flat_map<std::string, DbusVariant>>>;
48
James Feist3bcba452018-12-20 12:31:03 -080049constexpr static const char* fruDeviceServiceName =
50 "xyz.openbmc_project.FruDevice";
Jason M. Billse2d1aee2018-10-03 15:57:18 -070051constexpr static const size_t cacheTimeoutSeconds = 10;
Jason M. Bills3f7c5e42018-10-03 14:00:41 -070052
Jason M. Billsc04e2e72018-11-28 15:15:56 -080053constexpr static const uint8_t deassertionEvent = 1;
54
Jason M. Bills3f7c5e42018-10-03 14:00:41 -070055static std::vector<uint8_t> fruCache;
56static uint8_t cacheBus = 0xFF;
57static uint8_t cacheAddr = 0XFF;
58
59std::unique_ptr<phosphor::Timer> cacheTimer = nullptr;
60
61// we unfortunately have to build a map of hashes in case there is a
62// collision to verify our dev-id
63boost::container::flat_map<uint8_t, std::pair<uint8_t, uint8_t>> deviceHashes;
64
Jason M. Billse2d1aee2018-10-03 15:57:18 -070065void registerStorageFunctions() __attribute__((constructor));
Jason M. Bills3f7c5e42018-10-03 14:00:41 -070066static sdbusplus::bus::bus dbus(ipmid_get_sd_bus_connection());
67
68bool writeFru()
69{
70 sdbusplus::message::message writeFru = dbus.new_method_call(
71 fruDeviceServiceName, "/xyz/openbmc_project/FruDevice",
72 "xyz.openbmc_project.FruDeviceManager", "WriteFru");
73 writeFru.append(cacheBus, cacheAddr, fruCache);
74 try
75 {
76 sdbusplus::message::message writeFruResp = dbus.call(writeFru);
77 }
78 catch (sdbusplus::exception_t&)
79 {
80 // todo: log sel?
81 phosphor::logging::log<phosphor::logging::level::ERR>(
82 "error writing fru");
83 return false;
84 }
85 return true;
86}
87
Jason M. Billse2d1aee2018-10-03 15:57:18 -070088void createTimer()
89{
90 if (cacheTimer == nullptr)
91 {
92 cacheTimer = std::make_unique<phosphor::Timer>(writeFru);
93 }
94}
95
Jason M. Bills3f7c5e42018-10-03 14:00:41 -070096ipmi_ret_t replaceCacheFru(uint8_t devId)
97{
98 static uint8_t lastDevId = 0xFF;
99
100 bool timerRunning = (cacheTimer != nullptr) && !cacheTimer->isExpired();
101 if (lastDevId == devId && timerRunning)
102 {
103 return IPMI_CC_OK; // cache already up to date
104 }
105 // if timer is running, stop it and writeFru manually
106 else if (timerRunning)
107 {
108 cacheTimer->stop();
109 writeFru();
110 }
111
112 sdbusplus::message::message getObjects = dbus.new_method_call(
113 fruDeviceServiceName, "/", "org.freedesktop.DBus.ObjectManager",
114 "GetManagedObjects");
115 ManagedObjectType frus;
116 try
117 {
118 sdbusplus::message::message resp = dbus.call(getObjects);
119 resp.read(frus);
120 }
121 catch (sdbusplus::exception_t&)
122 {
123 phosphor::logging::log<phosphor::logging::level::ERR>(
124 "replaceCacheFru: error getting managed objects");
125 return IPMI_CC_RESPONSE_ERROR;
126 }
127
128 deviceHashes.clear();
129
130 // hash the object paths to create unique device id's. increment on
131 // collision
132 std::hash<std::string> hasher;
133 for (const auto& fru : frus)
134 {
135 auto fruIface = fru.second.find("xyz.openbmc_project.FruDevice");
136 if (fruIface == fru.second.end())
137 {
138 continue;
139 }
140
141 auto busFind = fruIface->second.find("BUS");
142 auto addrFind = fruIface->second.find("ADDRESS");
143 if (busFind == fruIface->second.end() ||
144 addrFind == fruIface->second.end())
145 {
146 phosphor::logging::log<phosphor::logging::level::INFO>(
147 "fru device missing Bus or Address",
148 phosphor::logging::entry("FRU=%s", fru.first.str.c_str()));
149 continue;
150 }
151
152 uint8_t fruBus =
153 sdbusplus::message::variant_ns::get<uint32_t>(busFind->second);
154 uint8_t fruAddr =
155 sdbusplus::message::variant_ns::get<uint32_t>(addrFind->second);
156
157 uint8_t fruHash = 0;
158 if (fruBus != 0 || fruAddr != 0)
159 {
160 fruHash = hasher(fru.first.str);
161 // can't be 0xFF based on spec, and 0 is reserved for baseboard
162 if (fruHash == 0 || fruHash == 0xFF)
163 {
164 fruHash = 1;
165 }
166 }
167 std::pair<uint8_t, uint8_t> newDev(fruBus, fruAddr);
168
169 bool emplacePassed = false;
170 while (!emplacePassed)
171 {
172 auto resp = deviceHashes.emplace(fruHash, newDev);
173 emplacePassed = resp.second;
174 if (!emplacePassed)
175 {
176 fruHash++;
177 // can't be 0xFF based on spec, and 0 is reserved for
178 // baseboard
179 if (fruHash == 0XFF)
180 {
181 fruHash = 0x1;
182 }
183 }
184 }
185 }
186 auto deviceFind = deviceHashes.find(devId);
187 if (deviceFind == deviceHashes.end())
188 {
189 return IPMI_CC_SENSOR_INVALID;
190 }
191
192 fruCache.clear();
193 sdbusplus::message::message getRawFru = dbus.new_method_call(
194 fruDeviceServiceName, "/xyz/openbmc_project/FruDevice",
195 "xyz.openbmc_project.FruDeviceManager", "GetRawFru");
196 cacheBus = deviceFind->second.first;
197 cacheAddr = deviceFind->second.second;
198 getRawFru.append(cacheBus, cacheAddr);
199 try
200 {
201 sdbusplus::message::message getRawResp = dbus.call(getRawFru);
202 getRawResp.read(fruCache);
203 }
204 catch (sdbusplus::exception_t&)
205 {
206 lastDevId = 0xFF;
207 cacheBus = 0xFF;
208 cacheAddr = 0xFF;
209 return IPMI_CC_RESPONSE_ERROR;
210 }
211
212 lastDevId = devId;
213 return IPMI_CC_OK;
214}
215
Jason M. Billse2d1aee2018-10-03 15:57:18 -0700216ipmi_ret_t ipmiStorageReadFRUData(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
217 ipmi_request_t request,
218 ipmi_response_t response,
219 ipmi_data_len_t dataLen,
220 ipmi_context_t context)
221{
222 if (*dataLen != 4)
223 {
224 *dataLen = 0;
225 return IPMI_CC_REQ_DATA_LEN_INVALID;
226 }
227 *dataLen = 0; // default to 0 in case of an error
228
229 auto req = static_cast<GetFRUAreaReq*>(request);
230
231 if (req->countToRead > maxMessageSize - 1)
232 {
233 return IPMI_CC_INVALID_FIELD_REQUEST;
234 }
235 ipmi_ret_t status = replaceCacheFru(req->fruDeviceID);
236
237 if (status != IPMI_CC_OK)
238 {
239 return status;
240 }
241
242 size_t fromFRUByteLen = 0;
243 if (req->countToRead + req->fruInventoryOffset < fruCache.size())
244 {
245 fromFRUByteLen = req->countToRead;
246 }
247 else if (fruCache.size() > req->fruInventoryOffset)
248 {
249 fromFRUByteLen = fruCache.size() - req->fruInventoryOffset;
250 }
251 size_t padByteLen = req->countToRead - fromFRUByteLen;
252 uint8_t* respPtr = static_cast<uint8_t*>(response);
253 *respPtr = req->countToRead;
254 std::copy(fruCache.begin() + req->fruInventoryOffset,
255 fruCache.begin() + req->fruInventoryOffset + fromFRUByteLen,
256 ++respPtr);
257 // if longer than the fru is requested, fill with 0xFF
258 if (padByteLen)
259 {
260 respPtr += fromFRUByteLen;
261 std::fill(respPtr, respPtr + padByteLen, 0xFF);
262 }
263 *dataLen = fromFRUByteLen + 1;
264
265 return IPMI_CC_OK;
266}
267
268ipmi_ret_t ipmiStorageWriteFRUData(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
269 ipmi_request_t request,
270 ipmi_response_t response,
271 ipmi_data_len_t dataLen,
272 ipmi_context_t context)
273{
274 if (*dataLen < 4 ||
275 *dataLen >=
276 0xFF + 3) // count written return is one byte, so limit to one byte
277 // of data after the three request data bytes
278 {
279 *dataLen = 0;
280 return IPMI_CC_REQ_DATA_LEN_INVALID;
281 }
282
283 auto req = static_cast<WriteFRUDataReq*>(request);
284 size_t writeLen = *dataLen - 3;
285 *dataLen = 0; // default to 0 in case of an error
286
287 ipmi_ret_t status = replaceCacheFru(req->fruDeviceID);
288 if (status != IPMI_CC_OK)
289 {
290 return status;
291 }
292 int lastWriteAddr = req->fruInventoryOffset + writeLen;
293 if (fruCache.size() < lastWriteAddr)
294 {
295 fruCache.resize(req->fruInventoryOffset + writeLen);
296 }
297
298 std::copy(req->data, req->data + writeLen,
299 fruCache.begin() + req->fruInventoryOffset);
300
301 bool atEnd = false;
302
303 if (fruCache.size() >= sizeof(FRUHeader))
304 {
305
306 FRUHeader* header = reinterpret_cast<FRUHeader*>(fruCache.data());
307
308 int lastRecordStart = std::max(
309 header->internalOffset,
310 std::max(header->chassisOffset,
311 std::max(header->boardOffset, header->productOffset)));
312 // TODO: Handle Multi-Record FRUs?
313
314 lastRecordStart *= 8; // header starts in are multiples of 8 bytes
315
316 // get the length of the area in multiples of 8 bytes
317 if (lastWriteAddr > (lastRecordStart + 1))
318 {
319 // second byte in record area is the length
320 int areaLength(fruCache[lastRecordStart + 1]);
321 areaLength *= 8; // it is in multiples of 8 bytes
322
323 if (lastWriteAddr >= (areaLength + lastRecordStart))
324 {
325 atEnd = true;
326 }
327 }
328 }
329 uint8_t* respPtr = static_cast<uint8_t*>(response);
330 if (atEnd)
331 {
332 // cancel timer, we're at the end so might as well send it
333 cacheTimer->stop();
334 if (!writeFru())
335 {
336 return IPMI_CC_INVALID_FIELD_REQUEST;
337 }
338 *respPtr = std::min(fruCache.size(), static_cast<size_t>(0xFF));
339 }
340 else
341 {
342 // start a timer, if no further data is sent in cacheTimeoutSeconds
343 // seconds, check to see if it is valid
344 createTimer();
345 cacheTimer->start(std::chrono::duration_cast<std::chrono::microseconds>(
346 std::chrono::seconds(cacheTimeoutSeconds)));
347 *respPtr = 0;
348 }
349
350 *dataLen = 1;
351
352 return IPMI_CC_OK;
353}
354
355ipmi_ret_t ipmiStorageGetFRUInvAreaInfo(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
356 ipmi_request_t request,
357 ipmi_response_t response,
358 ipmi_data_len_t dataLen,
359 ipmi_context_t context)
360{
361 if (*dataLen != 1)
362 {
363 *dataLen = 0;
364 return IPMI_CC_REQ_DATA_LEN_INVALID;
365 }
366 *dataLen = 0; // default to 0 in case of an error
367
368 uint8_t reqDev = *(static_cast<uint8_t*>(request));
369 if (reqDev == 0xFF)
370 {
371 return IPMI_CC_INVALID_FIELD_REQUEST;
372 }
373 ipmi_ret_t status = replaceCacheFru(reqDev);
374
375 if (status != IPMI_CC_OK)
376 {
377 return status;
378 }
379
380 GetFRUAreaResp* respPtr = static_cast<GetFRUAreaResp*>(response);
381 respPtr->inventorySizeLSB = fruCache.size() & 0xFF;
382 respPtr->inventorySizeMSB = fruCache.size() >> 8;
383 respPtr->accessType = static_cast<uint8_t>(GetFRUAreaAccessType::byte);
384
385 *dataLen = sizeof(GetFRUAreaResp);
386 return IPMI_CC_OK;
387}
388
Jason M. Bills3f7c5e42018-10-03 14:00:41 -0700389ipmi_ret_t getFruSdrCount(size_t& count)
390{
391 ipmi_ret_t ret = replaceCacheFru(0);
392 if (ret != IPMI_CC_OK)
393 {
394 return ret;
395 }
396 count = deviceHashes.size();
397 return IPMI_CC_OK;
398}
399
400ipmi_ret_t getFruSdrs(size_t index, get_sdr::SensorDataFruRecord& resp)
401{
402 ipmi_ret_t ret = replaceCacheFru(0); // this will update the hash list
403 if (ret != IPMI_CC_OK)
404 {
405 return ret;
406 }
407 if (deviceHashes.size() < index)
408 {
409 return IPMI_CC_INVALID_FIELD_REQUEST;
410 }
411 auto device = deviceHashes.begin() + index;
412 uint8_t& bus = device->second.first;
413 uint8_t& address = device->second.second;
414
415 ManagedObjectType frus;
416
417 sdbusplus::message::message getObjects = dbus.new_method_call(
418 fruDeviceServiceName, "/", "org.freedesktop.DBus.ObjectManager",
419 "GetManagedObjects");
420 try
421 {
422 sdbusplus::message::message resp = dbus.call(getObjects);
423 resp.read(frus);
424 }
425 catch (sdbusplus::exception_t&)
426 {
427 return IPMI_CC_RESPONSE_ERROR;
428 }
429 boost::container::flat_map<std::string, DbusVariant>* fruData = nullptr;
430 auto fru =
431 std::find_if(frus.begin(), frus.end(),
432 [bus, address, &fruData](ManagedEntry& entry) {
433 auto findFruDevice =
434 entry.second.find("xyz.openbmc_project.FruDevice");
435 if (findFruDevice == entry.second.end())
436 {
437 return false;
438 }
439 fruData = &(findFruDevice->second);
440 auto findBus = findFruDevice->second.find("BUS");
441 auto findAddress =
442 findFruDevice->second.find("ADDRESS");
443 if (findBus == findFruDevice->second.end() ||
444 findAddress == findFruDevice->second.end())
445 {
446 return false;
447 }
448 if (sdbusplus::message::variant_ns::get<uint32_t>(
449 findBus->second) != bus)
450 {
451 return false;
452 }
453 if (sdbusplus::message::variant_ns::get<uint32_t>(
454 findAddress->second) != address)
455 {
456 return false;
457 }
458 return true;
459 });
460 if (fru == frus.end())
461 {
462 return IPMI_CC_RESPONSE_ERROR;
463 }
464 std::string name;
465 auto findProductName = fruData->find("BOARD_PRODUCT_NAME");
466 auto findBoardName = fruData->find("PRODUCT_PRODUCT_NAME");
467 if (findProductName != fruData->end())
468 {
469 name = sdbusplus::message::variant_ns::get<std::string>(
470 findProductName->second);
471 }
472 else if (findBoardName != fruData->end())
473 {
474 name = sdbusplus::message::variant_ns::get<std::string>(
475 findBoardName->second);
476 }
477 else
478 {
479 name = "UNKNOWN";
480 }
481 if (name.size() > maxFruSdrNameSize)
482 {
483 name = name.substr(0, maxFruSdrNameSize);
484 }
485 size_t sizeDiff = maxFruSdrNameSize - name.size();
486
487 resp.header.record_id_lsb = 0x0; // calling code is to implement these
488 resp.header.record_id_msb = 0x0;
489 resp.header.sdr_version = ipmiSdrVersion;
490 resp.header.record_type = 0x11; // FRU Device Locator
491 resp.header.record_length = sizeof(resp.body) + sizeof(resp.key) - sizeDiff;
492 resp.key.deviceAddress = 0x20;
493 resp.key.fruID = device->first;
494 resp.key.accessLun = 0x80; // logical / physical fru device
495 resp.key.channelNumber = 0x0;
496 resp.body.reserved = 0x0;
497 resp.body.deviceType = 0x10;
498 resp.body.entityID = 0x0;
499 resp.body.entityInstance = 0x1;
500 resp.body.oem = 0x0;
501 resp.body.deviceIDLen = name.size();
502 name.copy(resp.body.deviceID, name.size());
503
504 return IPMI_CC_OK;
505}
Jason M. Billse2d1aee2018-10-03 15:57:18 -0700506
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800507ipmi_ret_t ipmiStorageGetSELInfo(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
508 ipmi_request_t request,
509 ipmi_response_t response,
510 ipmi_data_len_t data_len,
511 ipmi_context_t context)
512{
513 if (*data_len != 0)
514 {
515 *data_len = 0;
516 return IPMI_CC_REQ_DATA_LEN_INVALID;
517 }
518 ipmi::sel::GetSELInfoResponse* responseData =
519 static_cast<ipmi::sel::GetSELInfoResponse*>(response);
520
521 responseData->selVersion = ipmi::sel::selVersion;
522 // Last erase timestamp is not available from log manager.
523 responseData->eraseTimeStamp = ipmi::sel::invalidTimeStamp;
524 responseData->addTimeStamp = ipmi::sel::invalidTimeStamp;
525 responseData->operationSupport = intel_oem::ipmi::sel::selOperationSupport;
526 responseData->entries = 0;
527
528 // Open the journal
529 sd_journal* journalTmp = nullptr;
530 if (int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); ret < 0)
531 {
532 phosphor::logging::log<phosphor::logging::level::ERR>(
533 "Failed to open journal: ",
534 phosphor::logging::entry("ERRNO=%s", strerror(-ret)));
535 return IPMI_CC_RESPONSE_ERROR;
536 }
537 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
538 journalTmp, sd_journal_close);
539 journalTmp = nullptr;
540
541 // Filter the journal based on the SEL MESSAGE_ID
542 std::string match =
543 "MESSAGE_ID=" + std::string(intel_oem::ipmi::sel::selMessageId);
544 sd_journal_add_match(journal.get(), match.c_str(), 0);
545
546 // Count the number of SEL Entries in the journal and get the timestamp of
547 // the newest entry
548 bool timestampRecorded = false;
549 SD_JOURNAL_FOREACH_BACKWARDS(journal.get())
550 {
551 if (!timestampRecorded)
552 {
553 uint64_t timestamp;
554 if (int ret =
555 sd_journal_get_realtime_usec(journal.get(), &timestamp);
556 ret < 0)
557 {
558 phosphor::logging::log<phosphor::logging::level::ERR>(
559 "Failed to read timestamp: ",
560 phosphor::logging::entry("ERRNO=%s", strerror(-ret)));
561 return IPMI_CC_RESPONSE_ERROR;
562 }
563 timestamp /= (1000 * 1000); // convert from us to s
564 responseData->addTimeStamp = static_cast<uint32_t>(timestamp);
565 timestampRecorded = true;
566 }
567 responseData->entries++;
568 }
569
570 *data_len = sizeof(ipmi::sel::GetSELInfoResponse);
571 return IPMI_CC_OK;
572}
573
574static int fromHexStr(const std::string hexStr, std::vector<uint8_t>& data)
575{
576 for (unsigned int i = 0; i < hexStr.size(); i += 2)
577 {
578 try
579 {
580 data.push_back(static_cast<uint8_t>(
581 std::stoul(hexStr.substr(i, 2), nullptr, 16)));
582 }
583 catch (std::invalid_argument& e)
584 {
585 phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
586 return -1;
587 }
588 catch (std::out_of_range& e)
589 {
590 phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
591 return -1;
592 }
593 }
594 return 0;
595}
596
597static int getJournalMetadata(sd_journal* journal,
598 const std::string_view& field,
599 std::string& contents)
600{
601 const char* data = nullptr;
602 size_t length = 0;
603
604 // Get the metadata from the requested field of the journal entry
605 if (int ret = sd_journal_get_data(journal, field.data(),
606 (const void**)&data, &length);
607 ret < 0)
608 {
609 return ret;
610 }
611 std::string_view metadata(data, length);
612 // Only use the content after the "=" character.
613 metadata.remove_prefix(std::min(metadata.find("=") + 1, metadata.size()));
614 contents = std::string(metadata);
615 return 0;
616}
617
618static int getJournalMetadata(sd_journal* journal,
619 const std::string_view& field, const int& base,
620 int& contents)
621{
622 std::string metadata;
623 // Get the metadata from the requested field of the journal entry
624 if (int ret = getJournalMetadata(journal, field, metadata); ret < 0)
625 {
626 return ret;
627 }
628 try
629 {
630 contents = static_cast<int>(std::stoul(metadata, nullptr, base));
631 }
632 catch (std::invalid_argument& e)
633 {
634 phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
635 return -1;
636 }
637 catch (std::out_of_range& e)
638 {
639 phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
640 return -1;
641 }
642 return 0;
643}
644
645static int getJournalSelData(sd_journal* journal, std::vector<uint8_t>& evtData)
646{
647 std::string evtDataStr;
648 // Get the OEM data from the IPMI_SEL_DATA field
649 if (int ret = getJournalMetadata(journal, "IPMI_SEL_DATA", evtDataStr);
650 ret < 0)
651 {
652 return ret;
653 }
654 return fromHexStr(evtDataStr, evtData);
655}
656
657ipmi_ret_t ipmiStorageGetSELEntry(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
658 ipmi_request_t request,
659 ipmi_response_t response,
660 ipmi_data_len_t data_len,
661 ipmi_context_t context)
662{
663 if (*data_len != sizeof(ipmi::sel::GetSELEntryRequest))
664 {
665 *data_len = 0;
666 return IPMI_CC_REQ_DATA_LEN_INVALID;
667 }
668 *data_len = 0; // Default to 0 in case of errors
669 auto requestData =
670 static_cast<const ipmi::sel::GetSELEntryRequest*>(request);
671
672 if (requestData->reservationID != 0 || requestData->offset != 0)
673 {
674 if (!checkSELReservation(requestData->reservationID))
675 {
676 return IPMI_CC_INVALID_RESERVATION_ID;
677 }
678 }
679
680 GetSELEntryResponse record{};
681 // Default as the last entry
682 record.nextRecordID = ipmi::sel::lastEntry;
683
684 // Check for the requested SEL Entry.
685 sd_journal* journalTmp;
686 if (int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); ret < 0)
687 {
688 phosphor::logging::log<phosphor::logging::level::ERR>(
689 "Failed to open journal: ",
690 phosphor::logging::entry("ERRNO=%s", strerror(-ret)));
691 return IPMI_CC_UNSPECIFIED_ERROR;
692 }
693 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
694 journalTmp, sd_journal_close);
695 journalTmp = nullptr;
696
697 std::string match =
698 "MESSAGE_ID=" + std::string(intel_oem::ipmi::sel::selMessageId);
699 sd_journal_add_match(journal.get(), match.c_str(), 0);
700
701 // Get the requested target SEL record ID if first or last is requested.
702 int targetID = requestData->selRecordID;
703 if (targetID == ipmi::sel::firstEntry)
704 {
705 SD_JOURNAL_FOREACH(journal.get())
706 {
707 // Get the record ID from the IPMI_SEL_RECORD_ID field of the first
708 // entry
709 if (getJournalMetadata(journal.get(), "IPMI_SEL_RECORD_ID", 10,
710 targetID) < 0)
711 {
712 return IPMI_CC_UNSPECIFIED_ERROR;
713 }
714 break;
715 }
716 }
717 else if (targetID == ipmi::sel::lastEntry)
718 {
719 SD_JOURNAL_FOREACH_BACKWARDS(journal.get())
720 {
721 // Get the record ID from the IPMI_SEL_RECORD_ID field of the first
722 // entry
723 if (getJournalMetadata(journal.get(), "IPMI_SEL_RECORD_ID", 10,
724 targetID) < 0)
725 {
726 return IPMI_CC_UNSPECIFIED_ERROR;
727 }
728 break;
729 }
730 }
731 // Find the requested ID
732 match = "IPMI_SEL_RECORD_ID=" + std::to_string(targetID);
733 sd_journal_add_match(journal.get(), match.c_str(), 0);
734 // And find the next ID (wrapping to Record ID 1 when necessary)
735 int nextID = targetID + 1;
736 if (nextID == ipmi::sel::lastEntry)
737 {
738 nextID = 1;
739 }
740 match = "IPMI_SEL_RECORD_ID=" + std::to_string(nextID);
741 sd_journal_add_match(journal.get(), match.c_str(), 0);
742 SD_JOURNAL_FOREACH(journal.get())
743 {
744 // Get the record ID from the IPMI_SEL_RECORD_ID field
745 int id = 0;
746 if (getJournalMetadata(journal.get(), "IPMI_SEL_RECORD_ID", 10, id) < 0)
747 {
748 return IPMI_CC_UNSPECIFIED_ERROR;
749 }
750 if (id == targetID)
751 {
752 // Found the desired record, so fill in the data
753 record.recordID = id;
754
755 int recordType = 0;
756 // Get the record type from the IPMI_SEL_RECORD_TYPE field
757 if (getJournalMetadata(journal.get(), "IPMI_SEL_RECORD_TYPE", 16,
758 recordType) < 0)
759 {
760 return IPMI_CC_UNSPECIFIED_ERROR;
761 }
762 record.recordType = recordType;
763 // The rest of the record depends on the record type
764 if (record.recordType == intel_oem::ipmi::sel::systemEvent)
765 {
766 // Get the timestamp
767 uint64_t ts = 0;
768 if (sd_journal_get_realtime_usec(journal.get(), &ts) < 0)
769 {
770 return IPMI_CC_UNSPECIFIED_ERROR;
771 }
772 record.record.system.timestamp = static_cast<uint32_t>(
773 ts / 1000 / 1000); // Convert from us to s
774
775 int generatorID = 0;
776 // Get the generator ID from the IPMI_SEL_GENERATOR_ID field
777 if (getJournalMetadata(journal.get(), "IPMI_SEL_GENERATOR_ID",
778 16, generatorID) < 0)
779 {
780 return IPMI_CC_UNSPECIFIED_ERROR;
781 }
782 record.record.system.generatorID = generatorID;
783
784 // Set the event message revision
785 record.record.system.eventMsgRevision =
786 intel_oem::ipmi::sel::eventMsgRev;
787
788 std::string path;
789 // Get the IPMI_SEL_SENSOR_PATH field
790 if (getJournalMetadata(journal.get(), "IPMI_SEL_SENSOR_PATH",
791 path) < 0)
792 {
793 return IPMI_CC_UNSPECIFIED_ERROR;
794 }
795 record.record.system.sensorType = getSensorTypeFromPath(path);
796 record.record.system.sensorNum = getSensorNumberFromPath(path);
797 record.record.system.eventType =
798 getSensorEventTypeFromPath(path);
799
800 int eventDir = 0;
801 // Get the event direction from the IPMI_SEL_EVENT_DIR field
802 if (getJournalMetadata(journal.get(), "IPMI_SEL_EVENT_DIR", 16,
803 eventDir) < 0)
804 {
805 return IPMI_CC_UNSPECIFIED_ERROR;
806 }
807 // Set the event direction
808 if (eventDir == 0)
809 {
810 record.record.system.eventDir = deassertionEvent;
811 }
812
813 std::vector<uint8_t> evtData;
814 // Get the event data from the IPMI_SEL_DATA field
815 if (getJournalSelData(journal.get(), evtData) < 0)
816 {
817 return IPMI_CC_UNSPECIFIED_ERROR;
818 }
819 record.record.system.eventData[0] = evtData[0];
820 record.record.system.eventData[1] = evtData[1];
821 record.record.system.eventData[2] = evtData[2];
822 }
823 else if (record.recordType >=
824 intel_oem::ipmi::sel::oemTsEventFirst &&
825 record.recordType <= intel_oem::ipmi::sel::oemTsEventLast)
826 {
827 // Get the timestamp
828 uint64_t timestamp = 0;
829 if (sd_journal_get_realtime_usec(journal.get(), &timestamp) < 0)
830 {
831 return IPMI_CC_UNSPECIFIED_ERROR;
832 }
833 record.record.oemTs.timestamp = static_cast<uint32_t>(
834 timestamp / 1000 / 1000); // Convert from us to s
835
836 std::vector<uint8_t> evtData;
837 // Get the OEM data from the IPMI_SEL_DATA field
838 if (getJournalSelData(journal.get(), evtData) < 0)
839 {
840 return IPMI_CC_UNSPECIFIED_ERROR;
841 }
842 // Only keep the bytes that fit in the record
843 std::copy_n(evtData.begin(),
844 std::min(evtData.size(),
845 intel_oem::ipmi::sel::oemTsEventSize),
846 record.record.oemTs.eventData);
847 }
848 else if (record.recordType >= intel_oem::ipmi::sel::oemEventFirst &&
849 record.recordType <= intel_oem::ipmi::sel::oemEventLast)
850 {
851 std::vector<uint8_t> evtData;
852 // Get the OEM data from the IPMI_SEL_DATA field
853 if (getJournalSelData(journal.get(), evtData) < 0)
854 {
855 return IPMI_CC_UNSPECIFIED_ERROR;
856 }
857 // Only keep the bytes that fit in the record
858 std::copy_n(evtData.begin(),
859 std::min(evtData.size(),
860 intel_oem::ipmi::sel::oemEventSize),
861 record.record.oem.eventData);
862 }
863 }
864 else if (id == nextID)
865 {
866 record.nextRecordID = id;
867 }
868 }
869
870 // If we didn't find the requested record, return an error
871 if (record.recordID == 0)
872 {
873 return IPMI_CC_SENSOR_INVALID;
874 }
875
876 if (requestData->readLength == ipmi::sel::entireRecord)
877 {
878 std::copy(&record, &record + 1,
879 static_cast<GetSELEntryResponse*>(response));
880 *data_len = sizeof(record);
881 }
882 else
883 {
884 if (requestData->offset >= ipmi::sel::selRecordSize ||
885 requestData->readLength > ipmi::sel::selRecordSize)
886 {
887 return IPMI_CC_PARM_OUT_OF_RANGE;
888 }
889
890 auto diff = ipmi::sel::selRecordSize - requestData->offset;
891 auto readLength =
892 std::min(diff, static_cast<int>(requestData->readLength));
893
894 *static_cast<uint16_t*>(response) = record.nextRecordID;
895 std::copy_n(
896 reinterpret_cast<uint8_t*>(&record.recordID) + requestData->offset,
897 readLength,
898 static_cast<uint8_t*>(response) + sizeof(record.nextRecordID));
899 *data_len = sizeof(record.nextRecordID) + readLength;
900 }
901
902 return IPMI_CC_OK;
903}
904
905ipmi_ret_t ipmiStorageAddSELEntry(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
906 ipmi_request_t request,
907 ipmi_response_t response,
908 ipmi_data_len_t data_len,
909 ipmi_context_t context)
910{
911 static constexpr char const* ipmiSELObject =
912 "xyz.openbmc_project.Logging.IPMI";
913 static constexpr char const* ipmiSELPath =
914 "/xyz/openbmc_project/Logging/IPMI";
915 static constexpr char const* ipmiSELAddInterface =
916 "xyz.openbmc_project.Logging.IPMI";
917 static const std::string ipmiSELAddMessage =
918 "IPMI SEL entry logged using IPMI Add SEL Entry command.";
919 uint16_t recordID = 0;
920 sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()};
921
922 if (*data_len != sizeof(AddSELRequest))
923 {
924 *data_len = 0;
925 return IPMI_CC_REQ_DATA_LEN_INVALID;
926 }
927 AddSELRequest* req = static_cast<AddSELRequest*>(request);
928
929 // Per the IPMI spec, need to cancel any reservation when a SEL entry is
930 // added
931 cancelSELReservation();
932
933 if (req->recordType == intel_oem::ipmi::sel::systemEvent)
934 {
935 std::string sensorPath =
936 getPathFromSensorNumber(req->record.system.sensorNum);
937 std::vector<uint8_t> eventData(
938 req->record.system.eventData,
939 req->record.system.eventData +
940 intel_oem::ipmi::sel::systemEventSize);
941 bool assert = req->record.system.eventDir ? false : true;
942 uint16_t genId = req->record.system.generatorID;
943 sdbusplus::message::message writeSEL = bus.new_method_call(
944 ipmiSELObject, ipmiSELPath, ipmiSELAddInterface, "IpmiSelAdd");
945 writeSEL.append(ipmiSELAddMessage, sensorPath, eventData, assert,
946 genId);
947 try
948 {
949 sdbusplus::message::message writeSELResp = bus.call(writeSEL);
950 writeSELResp.read(recordID);
951 }
952 catch (sdbusplus::exception_t& e)
953 {
954 phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
955 *data_len = 0;
956 return IPMI_CC_UNSPECIFIED_ERROR;
957 }
958 }
959 else if (req->recordType >= intel_oem::ipmi::sel::oemTsEventFirst &&
960 req->recordType <= intel_oem::ipmi::sel::oemEventLast)
961 {
962 std::vector<uint8_t> eventData;
963 if (req->recordType <= intel_oem::ipmi::sel::oemTsEventLast)
964 {
965 eventData =
966 std::vector<uint8_t>(req->record.oemTs.eventData,
967 req->record.oemTs.eventData +
968 intel_oem::ipmi::sel::oemTsEventSize);
969 }
970 else
971 {
972 eventData = std::vector<uint8_t>(
973 req->record.oem.eventData,
974 req->record.oem.eventData + intel_oem::ipmi::sel::oemEventSize);
975 }
976 sdbusplus::message::message writeSEL = bus.new_method_call(
977 ipmiSELObject, ipmiSELPath, ipmiSELAddInterface, "IpmiSelAddOem");
978 writeSEL.append(ipmiSELAddMessage, eventData, req->recordType);
979 try
980 {
981 sdbusplus::message::message writeSELResp = bus.call(writeSEL);
982 writeSELResp.read(recordID);
983 }
984 catch (sdbusplus::exception_t& e)
985 {
986 phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
987 *data_len = 0;
988 return IPMI_CC_UNSPECIFIED_ERROR;
989 }
990 }
991 else
992 {
993 *data_len = 0;
994 return IPMI_CC_PARM_OUT_OF_RANGE;
995 }
996
997 *static_cast<uint16_t*>(response) = recordID;
998 *data_len = sizeof(recordID);
999 return IPMI_CC_OK;
1000}
1001
1002ipmi_ret_t ipmiStorageClearSEL(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
1003 ipmi_request_t request, ipmi_response_t response,
1004 ipmi_data_len_t data_len, ipmi_context_t context)
1005{
1006 if (*data_len != sizeof(ipmi::sel::ClearSELRequest))
1007 {
1008 *data_len = 0;
1009 return IPMI_CC_REQ_DATA_LEN_INVALID;
1010 }
1011 auto requestData = static_cast<const ipmi::sel::ClearSELRequest*>(request);
1012
1013 if (!checkSELReservation(requestData->reservationID))
1014 {
1015 *data_len = 0;
1016 return IPMI_CC_INVALID_RESERVATION_ID;
1017 }
1018
1019 if (requestData->charC != 'C' || requestData->charL != 'L' ||
1020 requestData->charR != 'R')
1021 {
1022 *data_len = 0;
1023 return IPMI_CC_INVALID_FIELD_REQUEST;
1024 }
1025
1026 uint8_t eraseProgress = ipmi::sel::eraseComplete;
1027
1028 /*
1029 * Erasure status cannot be fetched from DBUS, so always return erasure
1030 * status as `erase completed`.
1031 */
1032 if (requestData->eraseOperation == ipmi::sel::getEraseStatus)
1033 {
1034 *static_cast<uint8_t*>(response) = eraseProgress;
1035 *data_len = sizeof(eraseProgress);
1036 return IPMI_CC_OK;
1037 }
1038
1039 // Per the IPMI spec, need to cancel any reservation when the SEL is cleared
1040 cancelSELReservation();
1041
1042 // Clear the SEL by by rotating the journal to start a new file then
1043 // vacuuming to keep only the new file
1044 if (boost::process::system("journalctl", "--rotate") != 0)
1045 {
1046 return IPMI_CC_UNSPECIFIED_ERROR;
1047 }
1048 if (boost::process::system("journalctl", "--vacuum-files=1") != 0)
1049 {
1050 return IPMI_CC_UNSPECIFIED_ERROR;
1051 }
1052
1053 *static_cast<uint8_t*>(response) = eraseProgress;
1054 *data_len = sizeof(eraseProgress);
1055 return IPMI_CC_OK;
1056}
1057
Jason M. Billse2d1aee2018-10-03 15:57:18 -07001058void registerStorageFunctions()
1059{
1060 // <Get FRU Inventory Area Info>
1061 ipmiPrintAndRegister(
1062 NETFUN_STORAGE,
1063 static_cast<ipmi_cmd_t>(IPMINetfnStorageCmds::ipmiCmdGetFRUInvAreaInfo),
1064 NULL, ipmiStorageGetFRUInvAreaInfo, PRIVILEGE_OPERATOR);
1065
Jason M. Billsc04e2e72018-11-28 15:15:56 -08001066 // <READ FRU Data>
Jason M. Billse2d1aee2018-10-03 15:57:18 -07001067 ipmiPrintAndRegister(
1068 NETFUN_STORAGE,
1069 static_cast<ipmi_cmd_t>(IPMINetfnStorageCmds::ipmiCmdReadFRUData), NULL,
1070 ipmiStorageReadFRUData, PRIVILEGE_OPERATOR);
1071
Jason M. Billsc04e2e72018-11-28 15:15:56 -08001072 // <WRITE FRU Data>
Jason M. Billse2d1aee2018-10-03 15:57:18 -07001073 ipmiPrintAndRegister(
1074 NETFUN_STORAGE,
1075 static_cast<ipmi_cmd_t>(IPMINetfnStorageCmds::ipmiCmdWriteFRUData),
1076 NULL, ipmiStorageWriteFRUData, PRIVILEGE_OPERATOR);
Jason M. Billsc04e2e72018-11-28 15:15:56 -08001077
1078 // <Get SEL Info>
1079 ipmiPrintAndRegister(
1080 NETFUN_STORAGE,
1081 static_cast<ipmi_cmd_t>(IPMINetfnStorageCmds::ipmiCmdGetSELInfo), NULL,
1082 ipmiStorageGetSELInfo, PRIVILEGE_OPERATOR);
1083
1084 // <Get SEL Entry>
1085 ipmiPrintAndRegister(
1086 NETFUN_STORAGE,
1087 static_cast<ipmi_cmd_t>(IPMINetfnStorageCmds::ipmiCmdGetSELEntry), NULL,
1088 ipmiStorageGetSELEntry, PRIVILEGE_OPERATOR);
1089
1090 // <Add SEL Entry>
1091 ipmiPrintAndRegister(
1092 NETFUN_STORAGE,
1093 static_cast<ipmi_cmd_t>(IPMINetfnStorageCmds::ipmiCmdAddSEL), NULL,
1094 ipmiStorageAddSELEntry, PRIVILEGE_OPERATOR);
1095
1096 // <Clear SEL>
1097 ipmiPrintAndRegister(
1098 NETFUN_STORAGE,
1099 static_cast<ipmi_cmd_t>(IPMINetfnStorageCmds::ipmiCmdClearSEL), NULL,
1100 ipmiStorageClearSEL, PRIVILEGE_OPERATOR);
Jason M. Billse2d1aee2018-10-03 15:57:18 -07001101}
Jason M. Bills3f7c5e42018-10-03 14:00:41 -07001102} // namespace storage
Jason M. Billsc04e2e72018-11-28 15:15:56 -08001103} // namespace ipmi