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