blob: daf1fec506d701545afa23bf031e91bae2e2e4b3 [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. Bills4ed6f2c2019-04-02 12:21:25 -070096// event direction is bit[7] of eventType where 1b = Deassertion event
97constexpr static const uint8_t deassertionEvent = 0x80;
Jason M. Billsc04e2e72018-11-28 15:15:56 -080098
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;
James Feist4f86d1f2019-04-03 10:30:26 -0700542 resp.body.deviceTypeModifier = 0x0;
Jason M. Bills3f7c5e42018-10-03 14:00:41 -0700543 resp.body.entityID = 0x0;
544 resp.body.entityInstance = 0x1;
545 resp.body.oem = 0x0;
546 resp.body.deviceIDLen = name.size();
547 name.copy(resp.body.deviceID, name.size());
548
549 return IPMI_CC_OK;
550}
Jason M. Billse2d1aee2018-10-03 15:57:18 -0700551
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800552ipmi_ret_t ipmiStorageGetSELInfo(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
553 ipmi_request_t request,
554 ipmi_response_t response,
555 ipmi_data_len_t data_len,
556 ipmi_context_t context)
557{
558 if (*data_len != 0)
559 {
560 *data_len = 0;
561 return IPMI_CC_REQ_DATA_LEN_INVALID;
562 }
563 ipmi::sel::GetSELInfoResponse* responseData =
564 static_cast<ipmi::sel::GetSELInfoResponse*>(response);
565
566 responseData->selVersion = ipmi::sel::selVersion;
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800567 responseData->addTimeStamp = ipmi::sel::invalidTimeStamp;
568 responseData->operationSupport = intel_oem::ipmi::sel::selOperationSupport;
569 responseData->entries = 0;
570
Jason M. Bills7944c302019-03-20 15:24:05 -0700571 // Fill in the last erase time
572 responseData->eraseTimeStamp = intel_oem::ipmi::sel::erase_time::get();
573
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800574 // Open the journal
575 sd_journal* journalTmp = nullptr;
576 if (int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); ret < 0)
577 {
578 phosphor::logging::log<phosphor::logging::level::ERR>(
579 "Failed to open journal: ",
580 phosphor::logging::entry("ERRNO=%s", strerror(-ret)));
581 return IPMI_CC_RESPONSE_ERROR;
582 }
583 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
584 journalTmp, sd_journal_close);
585 journalTmp = nullptr;
586
587 // Filter the journal based on the SEL MESSAGE_ID
588 std::string match =
589 "MESSAGE_ID=" + std::string(intel_oem::ipmi::sel::selMessageId);
590 sd_journal_add_match(journal.get(), match.c_str(), 0);
591
592 // Count the number of SEL Entries in the journal and get the timestamp of
593 // the newest entry
594 bool timestampRecorded = false;
595 SD_JOURNAL_FOREACH_BACKWARDS(journal.get())
596 {
597 if (!timestampRecorded)
598 {
599 uint64_t timestamp;
600 if (int ret =
601 sd_journal_get_realtime_usec(journal.get(), &timestamp);
602 ret < 0)
603 {
604 phosphor::logging::log<phosphor::logging::level::ERR>(
605 "Failed to read timestamp: ",
606 phosphor::logging::entry("ERRNO=%s", strerror(-ret)));
607 return IPMI_CC_RESPONSE_ERROR;
608 }
609 timestamp /= (1000 * 1000); // convert from us to s
610 responseData->addTimeStamp = static_cast<uint32_t>(timestamp);
611 timestampRecorded = true;
612 }
613 responseData->entries++;
614 }
615
616 *data_len = sizeof(ipmi::sel::GetSELInfoResponse);
617 return IPMI_CC_OK;
618}
619
620static int fromHexStr(const std::string hexStr, std::vector<uint8_t>& data)
621{
622 for (unsigned int i = 0; i < hexStr.size(); i += 2)
623 {
624 try
625 {
626 data.push_back(static_cast<uint8_t>(
627 std::stoul(hexStr.substr(i, 2), nullptr, 16)));
628 }
629 catch (std::invalid_argument& e)
630 {
631 phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
632 return -1;
633 }
634 catch (std::out_of_range& e)
635 {
636 phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
637 return -1;
638 }
639 }
640 return 0;
641}
642
643static int getJournalMetadata(sd_journal* journal,
644 const std::string_view& field,
645 std::string& contents)
646{
647 const char* data = nullptr;
648 size_t length = 0;
649
650 // Get the metadata from the requested field of the journal entry
651 if (int ret = sd_journal_get_data(journal, field.data(),
652 (const void**)&data, &length);
653 ret < 0)
654 {
655 return ret;
656 }
657 std::string_view metadata(data, length);
658 // Only use the content after the "=" character.
659 metadata.remove_prefix(std::min(metadata.find("=") + 1, metadata.size()));
660 contents = std::string(metadata);
661 return 0;
662}
663
664static int getJournalMetadata(sd_journal* journal,
665 const std::string_view& field, const int& base,
666 int& contents)
667{
668 std::string metadata;
669 // Get the metadata from the requested field of the journal entry
670 if (int ret = getJournalMetadata(journal, field, metadata); ret < 0)
671 {
672 return ret;
673 }
674 try
675 {
676 contents = static_cast<int>(std::stoul(metadata, nullptr, base));
677 }
678 catch (std::invalid_argument& e)
679 {
680 phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
681 return -1;
682 }
683 catch (std::out_of_range& e)
684 {
685 phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
686 return -1;
687 }
688 return 0;
689}
690
691static int getJournalSelData(sd_journal* journal, std::vector<uint8_t>& evtData)
692{
693 std::string evtDataStr;
694 // Get the OEM data from the IPMI_SEL_DATA field
695 if (int ret = getJournalMetadata(journal, "IPMI_SEL_DATA", evtDataStr);
696 ret < 0)
697 {
698 return ret;
699 }
700 return fromHexStr(evtDataStr, evtData);
701}
702
703ipmi_ret_t ipmiStorageGetSELEntry(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
704 ipmi_request_t request,
705 ipmi_response_t response,
706 ipmi_data_len_t data_len,
707 ipmi_context_t context)
708{
709 if (*data_len != sizeof(ipmi::sel::GetSELEntryRequest))
710 {
711 *data_len = 0;
712 return IPMI_CC_REQ_DATA_LEN_INVALID;
713 }
714 *data_len = 0; // Default to 0 in case of errors
715 auto requestData =
716 static_cast<const ipmi::sel::GetSELEntryRequest*>(request);
717
718 if (requestData->reservationID != 0 || requestData->offset != 0)
719 {
720 if (!checkSELReservation(requestData->reservationID))
721 {
722 return IPMI_CC_INVALID_RESERVATION_ID;
723 }
724 }
725
726 GetSELEntryResponse record{};
727 // Default as the last entry
728 record.nextRecordID = ipmi::sel::lastEntry;
729
730 // Check for the requested SEL Entry.
731 sd_journal* journalTmp;
732 if (int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); ret < 0)
733 {
734 phosphor::logging::log<phosphor::logging::level::ERR>(
735 "Failed to open journal: ",
736 phosphor::logging::entry("ERRNO=%s", strerror(-ret)));
737 return IPMI_CC_UNSPECIFIED_ERROR;
738 }
739 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
740 journalTmp, sd_journal_close);
741 journalTmp = nullptr;
742
743 std::string match =
744 "MESSAGE_ID=" + std::string(intel_oem::ipmi::sel::selMessageId);
745 sd_journal_add_match(journal.get(), match.c_str(), 0);
746
747 // Get the requested target SEL record ID if first or last is requested.
748 int targetID = requestData->selRecordID;
749 if (targetID == ipmi::sel::firstEntry)
750 {
751 SD_JOURNAL_FOREACH(journal.get())
752 {
753 // Get the record ID from the IPMI_SEL_RECORD_ID field of the first
754 // entry
755 if (getJournalMetadata(journal.get(), "IPMI_SEL_RECORD_ID", 10,
756 targetID) < 0)
757 {
758 return IPMI_CC_UNSPECIFIED_ERROR;
759 }
760 break;
761 }
762 }
763 else if (targetID == ipmi::sel::lastEntry)
764 {
765 SD_JOURNAL_FOREACH_BACKWARDS(journal.get())
766 {
767 // Get the record ID from the IPMI_SEL_RECORD_ID field of the first
768 // entry
769 if (getJournalMetadata(journal.get(), "IPMI_SEL_RECORD_ID", 10,
770 targetID) < 0)
771 {
772 return IPMI_CC_UNSPECIFIED_ERROR;
773 }
774 break;
775 }
776 }
777 // Find the requested ID
778 match = "IPMI_SEL_RECORD_ID=" + std::to_string(targetID);
779 sd_journal_add_match(journal.get(), match.c_str(), 0);
780 // And find the next ID (wrapping to Record ID 1 when necessary)
781 int nextID = targetID + 1;
782 if (nextID == ipmi::sel::lastEntry)
783 {
784 nextID = 1;
785 }
786 match = "IPMI_SEL_RECORD_ID=" + std::to_string(nextID);
787 sd_journal_add_match(journal.get(), match.c_str(), 0);
788 SD_JOURNAL_FOREACH(journal.get())
789 {
790 // Get the record ID from the IPMI_SEL_RECORD_ID field
791 int id = 0;
792 if (getJournalMetadata(journal.get(), "IPMI_SEL_RECORD_ID", 10, id) < 0)
793 {
794 return IPMI_CC_UNSPECIFIED_ERROR;
795 }
796 if (id == targetID)
797 {
798 // Found the desired record, so fill in the data
799 record.recordID = id;
800
801 int recordType = 0;
802 // Get the record type from the IPMI_SEL_RECORD_TYPE field
803 if (getJournalMetadata(journal.get(), "IPMI_SEL_RECORD_TYPE", 16,
804 recordType) < 0)
805 {
806 return IPMI_CC_UNSPECIFIED_ERROR;
807 }
808 record.recordType = recordType;
809 // The rest of the record depends on the record type
810 if (record.recordType == intel_oem::ipmi::sel::systemEvent)
811 {
812 // Get the timestamp
813 uint64_t ts = 0;
814 if (sd_journal_get_realtime_usec(journal.get(), &ts) < 0)
815 {
816 return IPMI_CC_UNSPECIFIED_ERROR;
817 }
818 record.record.system.timestamp = static_cast<uint32_t>(
819 ts / 1000 / 1000); // Convert from us to s
820
821 int generatorID = 0;
822 // Get the generator ID from the IPMI_SEL_GENERATOR_ID field
823 if (getJournalMetadata(journal.get(), "IPMI_SEL_GENERATOR_ID",
824 16, generatorID) < 0)
825 {
826 return IPMI_CC_UNSPECIFIED_ERROR;
827 }
828 record.record.system.generatorID = generatorID;
829
830 // Set the event message revision
831 record.record.system.eventMsgRevision =
832 intel_oem::ipmi::sel::eventMsgRev;
833
834 std::string path;
835 // Get the IPMI_SEL_SENSOR_PATH field
836 if (getJournalMetadata(journal.get(), "IPMI_SEL_SENSOR_PATH",
837 path) < 0)
838 {
839 return IPMI_CC_UNSPECIFIED_ERROR;
840 }
841 record.record.system.sensorType = getSensorTypeFromPath(path);
842 record.record.system.sensorNum = getSensorNumberFromPath(path);
843 record.record.system.eventType =
844 getSensorEventTypeFromPath(path);
845
846 int eventDir = 0;
847 // Get the event direction from the IPMI_SEL_EVENT_DIR field
848 if (getJournalMetadata(journal.get(), "IPMI_SEL_EVENT_DIR", 16,
849 eventDir) < 0)
850 {
851 return IPMI_CC_UNSPECIFIED_ERROR;
852 }
853 // Set the event direction
854 if (eventDir == 0)
855 {
Jason M. Bills4ed6f2c2019-04-02 12:21:25 -0700856 record.record.system.eventType |= deassertionEvent;
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800857 }
858
859 std::vector<uint8_t> evtData;
860 // Get the event data from the IPMI_SEL_DATA field
861 if (getJournalSelData(journal.get(), evtData) < 0)
862 {
863 return IPMI_CC_UNSPECIFIED_ERROR;
864 }
865 record.record.system.eventData[0] = evtData[0];
866 record.record.system.eventData[1] = evtData[1];
867 record.record.system.eventData[2] = evtData[2];
868 }
869 else if (record.recordType >=
870 intel_oem::ipmi::sel::oemTsEventFirst &&
871 record.recordType <= intel_oem::ipmi::sel::oemTsEventLast)
872 {
873 // Get the timestamp
874 uint64_t timestamp = 0;
875 if (sd_journal_get_realtime_usec(journal.get(), &timestamp) < 0)
876 {
877 return IPMI_CC_UNSPECIFIED_ERROR;
878 }
879 record.record.oemTs.timestamp = static_cast<uint32_t>(
880 timestamp / 1000 / 1000); // Convert from us to s
881
882 std::vector<uint8_t> evtData;
883 // Get the OEM data from the IPMI_SEL_DATA field
884 if (getJournalSelData(journal.get(), evtData) < 0)
885 {
886 return IPMI_CC_UNSPECIFIED_ERROR;
887 }
888 // Only keep the bytes that fit in the record
889 std::copy_n(evtData.begin(),
890 std::min(evtData.size(),
891 intel_oem::ipmi::sel::oemTsEventSize),
892 record.record.oemTs.eventData);
893 }
894 else if (record.recordType >= intel_oem::ipmi::sel::oemEventFirst &&
895 record.recordType <= intel_oem::ipmi::sel::oemEventLast)
896 {
897 std::vector<uint8_t> evtData;
898 // Get the OEM data from the IPMI_SEL_DATA field
899 if (getJournalSelData(journal.get(), evtData) < 0)
900 {
901 return IPMI_CC_UNSPECIFIED_ERROR;
902 }
903 // Only keep the bytes that fit in the record
904 std::copy_n(evtData.begin(),
905 std::min(evtData.size(),
906 intel_oem::ipmi::sel::oemEventSize),
907 record.record.oem.eventData);
908 }
909 }
910 else if (id == nextID)
911 {
912 record.nextRecordID = id;
913 }
914 }
915
916 // If we didn't find the requested record, return an error
917 if (record.recordID == 0)
918 {
919 return IPMI_CC_SENSOR_INVALID;
920 }
921
922 if (requestData->readLength == ipmi::sel::entireRecord)
923 {
924 std::copy(&record, &record + 1,
925 static_cast<GetSELEntryResponse*>(response));
926 *data_len = sizeof(record);
927 }
928 else
929 {
930 if (requestData->offset >= ipmi::sel::selRecordSize ||
931 requestData->readLength > ipmi::sel::selRecordSize)
932 {
933 return IPMI_CC_PARM_OUT_OF_RANGE;
934 }
935
936 auto diff = ipmi::sel::selRecordSize - requestData->offset;
937 auto readLength =
938 std::min(diff, static_cast<int>(requestData->readLength));
939
940 *static_cast<uint16_t*>(response) = record.nextRecordID;
941 std::copy_n(
942 reinterpret_cast<uint8_t*>(&record.recordID) + requestData->offset,
943 readLength,
944 static_cast<uint8_t*>(response) + sizeof(record.nextRecordID));
945 *data_len = sizeof(record.nextRecordID) + readLength;
946 }
947
948 return IPMI_CC_OK;
949}
950
951ipmi_ret_t ipmiStorageAddSELEntry(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
952 ipmi_request_t request,
953 ipmi_response_t response,
954 ipmi_data_len_t data_len,
955 ipmi_context_t context)
956{
957 static constexpr char const* ipmiSELObject =
958 "xyz.openbmc_project.Logging.IPMI";
959 static constexpr char const* ipmiSELPath =
960 "/xyz/openbmc_project/Logging/IPMI";
961 static constexpr char const* ipmiSELAddInterface =
962 "xyz.openbmc_project.Logging.IPMI";
963 static const std::string ipmiSELAddMessage =
964 "IPMI SEL entry logged using IPMI Add SEL Entry command.";
965 uint16_t recordID = 0;
966 sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()};
967
968 if (*data_len != sizeof(AddSELRequest))
969 {
970 *data_len = 0;
971 return IPMI_CC_REQ_DATA_LEN_INVALID;
972 }
973 AddSELRequest* req = static_cast<AddSELRequest*>(request);
974
975 // Per the IPMI spec, need to cancel any reservation when a SEL entry is
976 // added
977 cancelSELReservation();
978
979 if (req->recordType == intel_oem::ipmi::sel::systemEvent)
980 {
981 std::string sensorPath =
982 getPathFromSensorNumber(req->record.system.sensorNum);
983 std::vector<uint8_t> eventData(
984 req->record.system.eventData,
985 req->record.system.eventData +
986 intel_oem::ipmi::sel::systemEventSize);
Jason M. Bills4ed6f2c2019-04-02 12:21:25 -0700987 bool assert = !(req->record.system.eventType & deassertionEvent);
Jason M. Billsc04e2e72018-11-28 15:15:56 -0800988 uint16_t genId = req->record.system.generatorID;
989 sdbusplus::message::message writeSEL = bus.new_method_call(
990 ipmiSELObject, ipmiSELPath, ipmiSELAddInterface, "IpmiSelAdd");
991 writeSEL.append(ipmiSELAddMessage, sensorPath, eventData, assert,
992 genId);
993 try
994 {
995 sdbusplus::message::message writeSELResp = bus.call(writeSEL);
996 writeSELResp.read(recordID);
997 }
998 catch (sdbusplus::exception_t& e)
999 {
1000 phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
1001 *data_len = 0;
1002 return IPMI_CC_UNSPECIFIED_ERROR;
1003 }
1004 }
1005 else if (req->recordType >= intel_oem::ipmi::sel::oemTsEventFirst &&
1006 req->recordType <= intel_oem::ipmi::sel::oemEventLast)
1007 {
1008 std::vector<uint8_t> eventData;
1009 if (req->recordType <= intel_oem::ipmi::sel::oemTsEventLast)
1010 {
1011 eventData =
1012 std::vector<uint8_t>(req->record.oemTs.eventData,
1013 req->record.oemTs.eventData +
1014 intel_oem::ipmi::sel::oemTsEventSize);
1015 }
1016 else
1017 {
1018 eventData = std::vector<uint8_t>(
1019 req->record.oem.eventData,
1020 req->record.oem.eventData + intel_oem::ipmi::sel::oemEventSize);
1021 }
1022 sdbusplus::message::message writeSEL = bus.new_method_call(
1023 ipmiSELObject, ipmiSELPath, ipmiSELAddInterface, "IpmiSelAddOem");
1024 writeSEL.append(ipmiSELAddMessage, eventData, req->recordType);
1025 try
1026 {
1027 sdbusplus::message::message writeSELResp = bus.call(writeSEL);
1028 writeSELResp.read(recordID);
1029 }
1030 catch (sdbusplus::exception_t& e)
1031 {
1032 phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
1033 *data_len = 0;
1034 return IPMI_CC_UNSPECIFIED_ERROR;
1035 }
1036 }
1037 else
1038 {
1039 *data_len = 0;
1040 return IPMI_CC_PARM_OUT_OF_RANGE;
1041 }
1042
1043 *static_cast<uint16_t*>(response) = recordID;
1044 *data_len = sizeof(recordID);
1045 return IPMI_CC_OK;
1046}
1047
1048ipmi_ret_t ipmiStorageClearSEL(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
1049 ipmi_request_t request, ipmi_response_t response,
1050 ipmi_data_len_t data_len, ipmi_context_t context)
1051{
1052 if (*data_len != sizeof(ipmi::sel::ClearSELRequest))
1053 {
1054 *data_len = 0;
1055 return IPMI_CC_REQ_DATA_LEN_INVALID;
1056 }
1057 auto requestData = static_cast<const ipmi::sel::ClearSELRequest*>(request);
1058
1059 if (!checkSELReservation(requestData->reservationID))
1060 {
1061 *data_len = 0;
1062 return IPMI_CC_INVALID_RESERVATION_ID;
1063 }
1064
1065 if (requestData->charC != 'C' || requestData->charL != 'L' ||
1066 requestData->charR != 'R')
1067 {
1068 *data_len = 0;
1069 return IPMI_CC_INVALID_FIELD_REQUEST;
1070 }
1071
1072 uint8_t eraseProgress = ipmi::sel::eraseComplete;
1073
1074 /*
1075 * Erasure status cannot be fetched from DBUS, so always return erasure
1076 * status as `erase completed`.
1077 */
1078 if (requestData->eraseOperation == ipmi::sel::getEraseStatus)
1079 {
1080 *static_cast<uint8_t*>(response) = eraseProgress;
1081 *data_len = sizeof(eraseProgress);
1082 return IPMI_CC_OK;
1083 }
1084
1085 // Per the IPMI spec, need to cancel any reservation when the SEL is cleared
1086 cancelSELReservation();
1087
Jason M. Bills7944c302019-03-20 15:24:05 -07001088 // Save the erase time
1089 intel_oem::ipmi::sel::erase_time::save();
1090
Jason M. Billsc04e2e72018-11-28 15:15:56 -08001091 // Clear the SEL by by rotating the journal to start a new file then
1092 // vacuuming to keep only the new file
Jason M. Billsc49adb72019-02-27 11:23:10 -08001093 if (boost::process::system("/bin/journalctl", "--rotate") != 0)
Jason M. Billsc04e2e72018-11-28 15:15:56 -08001094 {
1095 return IPMI_CC_UNSPECIFIED_ERROR;
1096 }
Jason M. Billsc49adb72019-02-27 11:23:10 -08001097 if (boost::process::system("/bin/journalctl", "--vacuum-files=1") != 0)
Jason M. Billsc04e2e72018-11-28 15:15:56 -08001098 {
1099 return IPMI_CC_UNSPECIFIED_ERROR;
1100 }
1101
1102 *static_cast<uint8_t*>(response) = eraseProgress;
1103 *data_len = sizeof(eraseProgress);
1104 return IPMI_CC_OK;
1105}
1106
Jason M. Billscac97a52019-01-30 14:43:46 -08001107ipmi_ret_t ipmiStorageSetSELTime(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
1108 ipmi_request_t request,
1109 ipmi_response_t response,
1110 ipmi_data_len_t data_len,
1111 ipmi_context_t context)
1112{
1113 // Set SEL Time is not supported
1114 *data_len = 0;
1115 return IPMI_CC_INVALID;
1116}
1117
Jason M. Billse2d1aee2018-10-03 15:57:18 -07001118void registerStorageFunctions()
1119{
1120 // <Get FRU Inventory Area Info>
1121 ipmiPrintAndRegister(
1122 NETFUN_STORAGE,
1123 static_cast<ipmi_cmd_t>(IPMINetfnStorageCmds::ipmiCmdGetFRUInvAreaInfo),
1124 NULL, ipmiStorageGetFRUInvAreaInfo, PRIVILEGE_OPERATOR);
1125
Jason M. Billsc04e2e72018-11-28 15:15:56 -08001126 // <READ FRU Data>
Jason M. Billse2d1aee2018-10-03 15:57:18 -07001127 ipmiPrintAndRegister(
1128 NETFUN_STORAGE,
1129 static_cast<ipmi_cmd_t>(IPMINetfnStorageCmds::ipmiCmdReadFRUData), NULL,
1130 ipmiStorageReadFRUData, PRIVILEGE_OPERATOR);
1131
Jason M. Billsc04e2e72018-11-28 15:15:56 -08001132 // <WRITE FRU Data>
Jason M. Billse2d1aee2018-10-03 15:57:18 -07001133 ipmiPrintAndRegister(
1134 NETFUN_STORAGE,
1135 static_cast<ipmi_cmd_t>(IPMINetfnStorageCmds::ipmiCmdWriteFRUData),
1136 NULL, ipmiStorageWriteFRUData, PRIVILEGE_OPERATOR);
Jason M. Billsc04e2e72018-11-28 15:15:56 -08001137
1138 // <Get SEL Info>
1139 ipmiPrintAndRegister(
1140 NETFUN_STORAGE,
1141 static_cast<ipmi_cmd_t>(IPMINetfnStorageCmds::ipmiCmdGetSELInfo), NULL,
1142 ipmiStorageGetSELInfo, PRIVILEGE_OPERATOR);
1143
1144 // <Get SEL Entry>
1145 ipmiPrintAndRegister(
1146 NETFUN_STORAGE,
1147 static_cast<ipmi_cmd_t>(IPMINetfnStorageCmds::ipmiCmdGetSELEntry), NULL,
1148 ipmiStorageGetSELEntry, PRIVILEGE_OPERATOR);
1149
1150 // <Add SEL Entry>
1151 ipmiPrintAndRegister(
1152 NETFUN_STORAGE,
1153 static_cast<ipmi_cmd_t>(IPMINetfnStorageCmds::ipmiCmdAddSEL), NULL,
1154 ipmiStorageAddSELEntry, PRIVILEGE_OPERATOR);
1155
1156 // <Clear SEL>
1157 ipmiPrintAndRegister(
1158 NETFUN_STORAGE,
1159 static_cast<ipmi_cmd_t>(IPMINetfnStorageCmds::ipmiCmdClearSEL), NULL,
1160 ipmiStorageClearSEL, PRIVILEGE_OPERATOR);
Jason M. Billscac97a52019-01-30 14:43:46 -08001161
1162 // <Set SEL Time>
1163 ipmiPrintAndRegister(
1164 NETFUN_STORAGE,
1165 static_cast<ipmi_cmd_t>(IPMINetfnStorageCmds::ipmiCmdSetSELTime), NULL,
1166 ipmiStorageSetSELTime, PRIVILEGE_OPERATOR);
Jason M. Billse2d1aee2018-10-03 15:57:18 -07001167}
Jason M. Bills3f7c5e42018-10-03 14:00:41 -07001168} // namespace storage
Jason M. Billsc04e2e72018-11-28 15:15:56 -08001169} // namespace ipmi