blob: b8bfd566958a0852bd18014c301dcc13efa07efb [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
Jason M. Bills7944c302019-03-20 15:24:05 -070032namespace intel_oem::ipmi::sel::erase_time
33{
34static constexpr const char* selEraseTimestamp = "/var/lib/ipmi/sel_erase_time";
35
36void save()
37{
38 // open the file, creating it if necessary
39 int fd = open(selEraseTimestamp, O_WRONLY | O_CREAT | O_CLOEXEC, 0644);
40 if (fd < 0)
41 {
42 std::cerr << "Failed to open file\n";
43 return;
44 }
45
46 // update the file timestamp to the current time
47 if (futimens(fd, NULL) < 0)
48 {
49 std::cerr << "Failed to update timestamp: "
50 << std::string(strerror(errno));
51 }
52 close(fd);
53}
54
55int get()
56{
57 struct stat st;
58 // default to an invalid timestamp
59 int timestamp = ::ipmi::sel::invalidTimeStamp;
60
61 int fd = open(selEraseTimestamp, O_RDWR | O_CLOEXEC, 0644);
62 if (fd < 0)
63 {
64 return timestamp;
65 }
66
67 if (fstat(fd, &st) >= 0)
68 {
69 timestamp = st.st_mtime;
70 }
71
72 return timestamp;
73}
74} // namespace intel_oem::ipmi::sel::erase_time
75
Jason M. Bills3f7c5e42018-10-03 14:00:41 -070076namespace ipmi
77{
78
79namespace storage
80{
81
Jason M. Billse2d1aee2018-10-03 15:57:18 -070082constexpr static const size_t maxMessageSize = 64;
Jason M. Bills3f7c5e42018-10-03 14:00:41 -070083constexpr static const size_t maxFruSdrNameSize = 16;
84using ManagedObjectType = boost::container::flat_map<
85 sdbusplus::message::object_path,
86 boost::container::flat_map<
87 std::string, boost::container::flat_map<std::string, DbusVariant>>>;
88using ManagedEntry = std::pair<
89 sdbusplus::message::object_path,
90 boost::container::flat_map<
91 std::string, boost::container::flat_map<std::string, DbusVariant>>>;
92
James Feist3bcba452018-12-20 12:31:03 -080093constexpr static const char* fruDeviceServiceName =
94 "xyz.openbmc_project.FruDevice";
Jason M. Billse2d1aee2018-10-03 15:57:18 -070095constexpr static const size_t cacheTimeoutSeconds = 10;
Jason M. Bills3f7c5e42018-10-03 14:00:41 -070096
Jason M. Billsc04e2e72018-11-28 15:15:56 -080097constexpr static const uint8_t deassertionEvent = 1;
98
Jason M. Bills3f7c5e42018-10-03 14:00:41 -070099static std::vector<uint8_t> fruCache;
100static uint8_t cacheBus = 0xFF;
101static uint8_t cacheAddr = 0XFF;
102
103std::unique_ptr<phosphor::Timer> cacheTimer = nullptr;
104
105// we unfortunately have to build a map of hashes in case there is a
106// collision to verify our dev-id
107boost::container::flat_map<uint8_t, std::pair<uint8_t, uint8_t>> deviceHashes;
108
Jason M. Billse2d1aee2018-10-03 15:57:18 -0700109void registerStorageFunctions() __attribute__((constructor));
Jason M. Bills3f7c5e42018-10-03 14:00:41 -0700110static sdbusplus::bus::bus dbus(ipmid_get_sd_bus_connection());
111
112bool writeFru()
113{
114 sdbusplus::message::message writeFru = dbus.new_method_call(
115 fruDeviceServiceName, "/xyz/openbmc_project/FruDevice",
116 "xyz.openbmc_project.FruDeviceManager", "WriteFru");
117 writeFru.append(cacheBus, cacheAddr, fruCache);
118 try
119 {
120 sdbusplus::message::message writeFruResp = dbus.call(writeFru);
121 }
122 catch (sdbusplus::exception_t&)
123 {
124 // todo: log sel?
125 phosphor::logging::log<phosphor::logging::level::ERR>(
126 "error writing fru");
127 return false;
128 }
129 return true;
130}
131
Jason M. Billse2d1aee2018-10-03 15:57:18 -0700132void createTimer()
133{
134 if (cacheTimer == nullptr)
135 {
136 cacheTimer = std::make_unique<phosphor::Timer>(writeFru);
137 }
138}
139
Jason M. Bills3f7c5e42018-10-03 14:00:41 -0700140ipmi_ret_t replaceCacheFru(uint8_t devId)
141{
142 static uint8_t lastDevId = 0xFF;
143
144 bool timerRunning = (cacheTimer != nullptr) && !cacheTimer->isExpired();
145 if (lastDevId == devId && timerRunning)
146 {
147 return IPMI_CC_OK; // cache already up to date
148 }
149 // if timer is running, stop it and writeFru manually
150 else if (timerRunning)
151 {
152 cacheTimer->stop();
153 writeFru();
154 }
155
156 sdbusplus::message::message getObjects = dbus.new_method_call(
157 fruDeviceServiceName, "/", "org.freedesktop.DBus.ObjectManager",
158 "GetManagedObjects");
159 ManagedObjectType frus;
160 try
161 {
162 sdbusplus::message::message resp = dbus.call(getObjects);
163 resp.read(frus);
164 }
165 catch (sdbusplus::exception_t&)
166 {
167 phosphor::logging::log<phosphor::logging::level::ERR>(
168 "replaceCacheFru: error getting managed objects");
169 return IPMI_CC_RESPONSE_ERROR;
170 }
171
172 deviceHashes.clear();
173
174 // hash the object paths to create unique device id's. increment on
175 // collision
176 std::hash<std::string> hasher;
177 for (const auto& fru : frus)
178 {
179 auto fruIface = fru.second.find("xyz.openbmc_project.FruDevice");
180 if (fruIface == fru.second.end())
181 {
182 continue;
183 }
184
185 auto busFind = fruIface->second.find("BUS");
186 auto addrFind = fruIface->second.find("ADDRESS");
187 if (busFind == fruIface->second.end() ||
188 addrFind == fruIface->second.end())
189 {
190 phosphor::logging::log<phosphor::logging::level::INFO>(
191 "fru device missing Bus or Address",
192 phosphor::logging::entry("FRU=%s", fru.first.str.c_str()));
193 continue;
194 }
195
196 uint8_t fruBus =
197 sdbusplus::message::variant_ns::get<uint32_t>(busFind->second);
198 uint8_t fruAddr =
199 sdbusplus::message::variant_ns::get<uint32_t>(addrFind->second);
200
201 uint8_t fruHash = 0;
202 if (fruBus != 0 || fruAddr != 0)
203 {
204 fruHash = hasher(fru.first.str);
205 // can't be 0xFF based on spec, and 0 is reserved for baseboard
206 if (fruHash == 0 || fruHash == 0xFF)
207 {
208 fruHash = 1;
209 }
210 }
211 std::pair<uint8_t, uint8_t> newDev(fruBus, fruAddr);
212
213 bool emplacePassed = false;
214 while (!emplacePassed)
215 {
216 auto resp = deviceHashes.emplace(fruHash, newDev);
217 emplacePassed = resp.second;
218 if (!emplacePassed)
219 {
220 fruHash++;
221 // can't be 0xFF based on spec, and 0 is reserved for
222 // baseboard
223 if (fruHash == 0XFF)
224 {
225 fruHash = 0x1;
226 }
227 }
228 }
229 }
230 auto deviceFind = deviceHashes.find(devId);
231 if (deviceFind == deviceHashes.end())
232 {
233 return IPMI_CC_SENSOR_INVALID;
234 }
235
236 fruCache.clear();
237 sdbusplus::message::message getRawFru = dbus.new_method_call(
238 fruDeviceServiceName, "/xyz/openbmc_project/FruDevice",
239 "xyz.openbmc_project.FruDeviceManager", "GetRawFru");
240 cacheBus = deviceFind->second.first;
241 cacheAddr = deviceFind->second.second;
242 getRawFru.append(cacheBus, cacheAddr);
243 try
244 {
245 sdbusplus::message::message getRawResp = dbus.call(getRawFru);
246 getRawResp.read(fruCache);
247 }
248 catch (sdbusplus::exception_t&)
249 {
250 lastDevId = 0xFF;
251 cacheBus = 0xFF;
252 cacheAddr = 0xFF;
253 return IPMI_CC_RESPONSE_ERROR;
254 }
255
256 lastDevId = devId;
257 return IPMI_CC_OK;
258}
259
Jason M. Billse2d1aee2018-10-03 15:57:18 -0700260ipmi_ret_t ipmiStorageReadFRUData(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
261 ipmi_request_t request,
262 ipmi_response_t response,
263 ipmi_data_len_t dataLen,
264 ipmi_context_t context)
265{
266 if (*dataLen != 4)
267 {
268 *dataLen = 0;
269 return IPMI_CC_REQ_DATA_LEN_INVALID;
270 }
271 *dataLen = 0; // default to 0 in case of an error
272
273 auto req = static_cast<GetFRUAreaReq*>(request);
274
275 if (req->countToRead > maxMessageSize - 1)
276 {
277 return IPMI_CC_INVALID_FIELD_REQUEST;
278 }
279 ipmi_ret_t status = replaceCacheFru(req->fruDeviceID);
280
281 if (status != IPMI_CC_OK)
282 {
283 return status;
284 }
285
286 size_t fromFRUByteLen = 0;
287 if (req->countToRead + req->fruInventoryOffset < fruCache.size())
288 {
289 fromFRUByteLen = req->countToRead;
290 }
291 else if (fruCache.size() > req->fruInventoryOffset)
292 {
293 fromFRUByteLen = fruCache.size() - req->fruInventoryOffset;
294 }
295 size_t padByteLen = req->countToRead - fromFRUByteLen;
296 uint8_t* respPtr = static_cast<uint8_t*>(response);
297 *respPtr = req->countToRead;
298 std::copy(fruCache.begin() + req->fruInventoryOffset,
299 fruCache.begin() + req->fruInventoryOffset + fromFRUByteLen,
300 ++respPtr);
301 // if longer than the fru is requested, fill with 0xFF
302 if (padByteLen)
303 {
304 respPtr += fromFRUByteLen;
305 std::fill(respPtr, respPtr + padByteLen, 0xFF);
306 }
307 *dataLen = fromFRUByteLen + 1;
308
309 return IPMI_CC_OK;
310}
311
312ipmi_ret_t ipmiStorageWriteFRUData(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
313 ipmi_request_t request,
314 ipmi_response_t response,
315 ipmi_data_len_t dataLen,
316 ipmi_context_t context)
317{
318 if (*dataLen < 4 ||
319 *dataLen >=
320 0xFF + 3) // count written return is one byte, so limit to one byte
321 // of data after the three request data bytes
322 {
323 *dataLen = 0;
324 return IPMI_CC_REQ_DATA_LEN_INVALID;
325 }
326
327 auto req = static_cast<WriteFRUDataReq*>(request);
328 size_t writeLen = *dataLen - 3;
329 *dataLen = 0; // default to 0 in case of an error
330
331 ipmi_ret_t status = replaceCacheFru(req->fruDeviceID);
332 if (status != IPMI_CC_OK)
333 {
334 return status;
335 }
336 int lastWriteAddr = req->fruInventoryOffset + writeLen;
337 if (fruCache.size() < lastWriteAddr)
338 {
339 fruCache.resize(req->fruInventoryOffset + writeLen);
340 }
341
342 std::copy(req->data, req->data + writeLen,
343 fruCache.begin() + req->fruInventoryOffset);
344
345 bool atEnd = false;
346
347 if (fruCache.size() >= sizeof(FRUHeader))
348 {
349
350 FRUHeader* header = reinterpret_cast<FRUHeader*>(fruCache.data());
351
352 int lastRecordStart = std::max(
353 header->internalOffset,
354 std::max(header->chassisOffset,
355 std::max(header->boardOffset, header->productOffset)));
356 // TODO: Handle Multi-Record FRUs?
357
358 lastRecordStart *= 8; // header starts in are multiples of 8 bytes
359
360 // get the length of the area in multiples of 8 bytes
361 if (lastWriteAddr > (lastRecordStart + 1))
362 {
363 // second byte in record area is the length
364 int areaLength(fruCache[lastRecordStart + 1]);
365 areaLength *= 8; // it is in multiples of 8 bytes
366
367 if (lastWriteAddr >= (areaLength + lastRecordStart))
368 {
369 atEnd = true;
370 }
371 }
372 }
373 uint8_t* respPtr = static_cast<uint8_t*>(response);
374 if (atEnd)
375 {
376 // cancel timer, we're at the end so might as well send it
377 cacheTimer->stop();
378 if (!writeFru())
379 {
380 return IPMI_CC_INVALID_FIELD_REQUEST;
381 }
382 *respPtr = std::min(fruCache.size(), static_cast<size_t>(0xFF));
383 }
384 else
385 {
386 // start a timer, if no further data is sent in cacheTimeoutSeconds
387 // seconds, check to see if it is valid
388 createTimer();
389 cacheTimer->start(std::chrono::duration_cast<std::chrono::microseconds>(
390 std::chrono::seconds(cacheTimeoutSeconds)));
391 *respPtr = 0;
392 }
393
394 *dataLen = 1;
395
396 return IPMI_CC_OK;
397}
398
399ipmi_ret_t ipmiStorageGetFRUInvAreaInfo(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
400 ipmi_request_t request,
401 ipmi_response_t response,
402 ipmi_data_len_t dataLen,
403 ipmi_context_t context)
404{
405 if (*dataLen != 1)
406 {
407 *dataLen = 0;
408 return IPMI_CC_REQ_DATA_LEN_INVALID;
409 }
410 *dataLen = 0; // default to 0 in case of an error
411
412 uint8_t reqDev = *(static_cast<uint8_t*>(request));
413 if (reqDev == 0xFF)
414 {
415 return IPMI_CC_INVALID_FIELD_REQUEST;
416 }
417 ipmi_ret_t status = replaceCacheFru(reqDev);
418
419 if (status != IPMI_CC_OK)
420 {
421 return status;
422 }
423
424 GetFRUAreaResp* respPtr = static_cast<GetFRUAreaResp*>(response);
425 respPtr->inventorySizeLSB = fruCache.size() & 0xFF;
426 respPtr->inventorySizeMSB = fruCache.size() >> 8;
427 respPtr->accessType = static_cast<uint8_t>(GetFRUAreaAccessType::byte);
428
429 *dataLen = sizeof(GetFRUAreaResp);
430 return IPMI_CC_OK;
431}
432
Jason M. Bills3f7c5e42018-10-03 14:00:41 -0700433ipmi_ret_t getFruSdrCount(size_t& count)
434{
435 ipmi_ret_t ret = replaceCacheFru(0);
436 if (ret != IPMI_CC_OK)
437 {
438 return ret;
439 }
440 count = deviceHashes.size();
441 return IPMI_CC_OK;
442}
443
444ipmi_ret_t getFruSdrs(size_t index, get_sdr::SensorDataFruRecord& resp)
445{
446 ipmi_ret_t ret = replaceCacheFru(0); // this will update the hash list
447 if (ret != IPMI_CC_OK)
448 {
449 return ret;
450 }
451 if (deviceHashes.size() < index)
452 {
453 return IPMI_CC_INVALID_FIELD_REQUEST;
454 }
455 auto device = deviceHashes.begin() + index;
456 uint8_t& bus = device->second.first;
457 uint8_t& address = device->second.second;
458
459 ManagedObjectType frus;
460
461 sdbusplus::message::message getObjects = dbus.new_method_call(
462 fruDeviceServiceName, "/", "org.freedesktop.DBus.ObjectManager",
463 "GetManagedObjects");
464 try
465 {
466 sdbusplus::message::message resp = dbus.call(getObjects);
467 resp.read(frus);
468 }
469 catch (sdbusplus::exception_t&)
470 {
471 return IPMI_CC_RESPONSE_ERROR;
472 }
473 boost::container::flat_map<std::string, DbusVariant>* fruData = nullptr;
474 auto fru =
475 std::find_if(frus.begin(), frus.end(),
476 [bus, address, &fruData](ManagedEntry& entry) {
477 auto findFruDevice =
478 entry.second.find("xyz.openbmc_project.FruDevice");
479 if (findFruDevice == entry.second.end())
480 {
481 return false;
482 }
483 fruData = &(findFruDevice->second);
484 auto findBus = findFruDevice->second.find("BUS");
485 auto findAddress =
486 findFruDevice->second.find("ADDRESS");
487 if (findBus == findFruDevice->second.end() ||
488 findAddress == findFruDevice->second.end())
489 {
490 return false;
491 }
492 if (sdbusplus::message::variant_ns::get<uint32_t>(
493 findBus->second) != bus)
494 {
495 return false;
496 }
497 if (sdbusplus::message::variant_ns::get<uint32_t>(
498 findAddress->second) != address)
499 {
500 return false;
501 }
502 return true;
503 });
504 if (fru == frus.end())
505 {
506 return IPMI_CC_RESPONSE_ERROR;
507 }
508 std::string name;
509 auto findProductName = fruData->find("BOARD_PRODUCT_NAME");
510 auto findBoardName = fruData->find("PRODUCT_PRODUCT_NAME");
511 if (findProductName != fruData->end())
512 {
513 name = sdbusplus::message::variant_ns::get<std::string>(
514 findProductName->second);
515 }
516 else if (findBoardName != fruData->end())
517 {
518 name = sdbusplus::message::variant_ns::get<std::string>(
519 findBoardName->second);
520 }
521 else
522 {
523 name = "UNKNOWN";
524 }
525 if (name.size() > maxFruSdrNameSize)
526 {
527 name = name.substr(0, maxFruSdrNameSize);
528 }
529 size_t sizeDiff = maxFruSdrNameSize - name.size();
530
531 resp.header.record_id_lsb = 0x0; // calling code is to implement these
532 resp.header.record_id_msb = 0x0;
533 resp.header.sdr_version = ipmiSdrVersion;
534 resp.header.record_type = 0x11; // FRU Device Locator
535 resp.header.record_length = sizeof(resp.body) + sizeof(resp.key) - sizeDiff;
536 resp.key.deviceAddress = 0x20;
537 resp.key.fruID = device->first;
538 resp.key.accessLun = 0x80; // logical / physical fru device
539 resp.key.channelNumber = 0x0;
540 resp.body.reserved = 0x0;
541 resp.body.deviceType = 0x10;
542 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