blob: f91ee07f1d0747d09b717337fe41f42fb6282d7a [file] [log] [blame]
Vijay Khemka11b9c3b2019-08-21 15:21:42 -07001/*
2 * Copyright (c) 2018 Intel Corporation.
3 * Copyright (c) 2018-present Facebook.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18#include <ipmid/api.hpp>
19
20#include <boost/algorithm/string/join.hpp>
21#include <nlohmann/json.hpp>
22#include <iostream>
23#include <sstream>
24#include <fstream>
25#include <phosphor-logging/log.hpp>
26#include <sdbusplus/message/types.hpp>
27#include <sdbusplus/timer.hpp>
28#include <storagecommands.hpp>
29
30//----------------------------------------------------------------------
31// Platform specific functions for storing app data
32//----------------------------------------------------------------------
33
34static void toHexStr(std::vector<uint8_t> &bytes, std::string &hexStr)
35{
36 std::stringstream stream;
37 stream << std::hex << std::uppercase << std::setfill('0');
38 for (const uint8_t byte : bytes)
39 {
40 stream << std::setw(2) << static_cast<int>(byte);
41 }
42 hexStr = stream.str();
43}
44
45static int fromHexStr(const std::string hexStr, std::vector<uint8_t> &data)
46{
47 for (unsigned int i = 0; i < hexStr.size(); i += 2)
48 {
49 try
50 {
51 data.push_back(static_cast<uint8_t>(
52 std::stoul(hexStr.substr(i, 2), nullptr, 16)));
53 }
54 catch (std::invalid_argument &e)
55 {
56 phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
57 return -1;
58 }
59 catch (std::out_of_range &e)
60 {
61 phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
62 return -1;
63 }
64 }
65 return 0;
66}
67
68namespace fb_oem::ipmi::sel
69{
70
71class SELData
72{
73 private:
74 nlohmann::json selDataObj;
75
76 void flush()
77 {
78 std::ofstream file(SEL_JSON_DATA_FILE);
79 file << selDataObj;
80 file.close();
81 }
82
83 void init()
84 {
85 selDataObj[KEY_SEL_VER] = 0x51;
86 selDataObj[KEY_SEL_COUNT] = 0;
87 selDataObj[KEY_ADD_TIME] = 0xFFFFFFFF;
88 selDataObj[KEY_ERASE_TIME] = 0xFFFFFFFF;
89 selDataObj[KEY_OPER_SUPP] = 0x02;
90 /* Spec indicates that more than 64kB is free */
91 selDataObj[KEY_FREE_SPACE] = 0xFFFF;
92 }
93
94 public:
95 SELData()
96 {
97 /* Get App data stored in json file */
98 std::ifstream file(SEL_JSON_DATA_FILE);
99 if (file)
100 {
101 file >> selDataObj;
102 file.close();
103 }
104
105 /* Initialize SelData object if no entries. */
106 if (selDataObj.find(KEY_SEL_COUNT) == selDataObj.end())
107 {
108 init();
109 }
110 }
111
112 int clear()
113 {
114 /* Clear the complete Sel Json object */
115 selDataObj.clear();
116 /* Reinitialize it with basic data */
117 init();
118 /* Save the erase time */
119 struct timespec selTime = {};
120 if (clock_gettime(CLOCK_REALTIME, &selTime) < 0)
121 {
122 return -1;
123 }
124 selDataObj[KEY_ERASE_TIME] = selTime.tv_sec;
125 flush();
126 return 0;
127 }
128
129 uint32_t getCount()
130 {
131 return selDataObj[KEY_SEL_COUNT];
132 }
133
134 void getInfo(GetSELInfoData &info)
135 {
136 info.selVersion = selDataObj[KEY_SEL_VER];
137 info.entries = selDataObj[KEY_SEL_COUNT];
138 info.freeSpace = selDataObj[KEY_FREE_SPACE];
139 info.addTimeStamp = selDataObj[KEY_ADD_TIME];
140 info.eraseTimeStamp = selDataObj[KEY_ERASE_TIME];
141 info.operationSupport = selDataObj[KEY_OPER_SUPP];
142 }
143
144 int getEntry(uint32_t index, std::string &rawStr)
145 {
146 std::stringstream ss;
147 ss << std::hex;
148 ss << std::setw(2) << std::setfill('0') << index;
149
150 /* Check or the requested SEL Entry, if record is available */
151 if (selDataObj.find(ss.str()) == selDataObj.end())
152 {
153 return -1;
154 }
155
156 rawStr = selDataObj[ss.str()][KEY_SEL_ENTRY_RAW];
157 return 0;
158 }
159
160 int addEntry(std::string keyStr)
161 {
162 struct timespec selTime = {};
163
164 if (clock_gettime(CLOCK_REALTIME, &selTime) < 0)
165 {
166 return -1;
167 }
168
169 selDataObj[KEY_ADD_TIME] = selTime.tv_sec;
170
171 int selCount = selDataObj[KEY_SEL_COUNT];
172 selDataObj[KEY_SEL_COUNT] = ++selCount;
173
174 std::stringstream ss;
175 ss << std::hex;
176 ss << std::setw(2) << std::setfill('0') << selCount;
177
178 selDataObj[ss.str()][KEY_SEL_ENTRY_RAW] = keyStr;
179 flush();
180 return selCount;
181 }
182};
183
Vijay Khemkaf36f3452019-08-09 13:24:45 -0700184static void parseStdSel(StdSELEntry *data, std::string &errStr)
185{
186 std::stringstream tmpStream;
187 tmpStream << std::hex << std::uppercase;
188
189 /* TODO: add pal_add_cri_sel */
190 switch (data->sensorNum)
191 {
192 case memoryEccError:
193 switch (data->eventData1 & 0x0F)
194 {
195 case 0x00:
196 errStr = "Correctable";
197 tmpStream << "DIMM" << std::setw(2) << std::setfill('0')
198 << data->eventData3 << " ECC err";
199 break;
200 case 0x01:
201 errStr = "Uncorrectable";
202 tmpStream << "DIMM" << std::setw(2) << std::setfill('0')
203 << data->eventData3 << " UECC err";
204 break;
205 case 0x02:
206 errStr = "Parity";
207 break;
208 case 0x05:
209 errStr = "Correctable ECC error Logging Limit Reached";
210 break;
211 default:
212 errStr = "Unknown";
213 }
214 break;
215 case memoryErrLogDIS:
216 if ((data->eventData1 & 0x0F) == 0)
217 {
218 errStr = "Correctable Memory Error Logging Disabled";
219 }
220 else
221 {
222 errStr = "Unknown";
223 }
224 break;
225 default:
226
227 /* TODO: parse sel helper */
228 errStr = "Unknown";
229 return;
230 }
231
232 errStr += " (DIMM " + std::to_string(data->eventData3) + ")";
233 errStr += " Logical Rank " + std::to_string(data->eventData2 & 0x03);
234
235 switch ((data->eventData2 & 0x0C) >> 2)
236 {
237 case 0x00:
238 // Ignore when " All info available"
239 break;
240 case 0x01:
241 errStr += " DIMM info not valid";
242 break;
243 case 0x02:
244 errStr += " CHN info not valid";
245 break;
246 case 0x03:
247 errStr += " CPU info not valid";
248 break;
249 default:
250 errStr += " Unknown";
251 }
252
253 if (((data->eventType & 0x80) >> 7) == 0)
254 {
255 errStr += " Assertion";
256 }
257 else
258 {
259 errStr += " Deassertion";
260 }
261
262 return;
263}
264
265static void parseOemSel(TsOemSELEntry *data, std::string &errStr)
266{
267 std::stringstream tmpStream;
268 tmpStream << std::hex << std::uppercase << std::setfill('0');
269
270 switch (data->recordType)
271 {
272 case 0xC0:
273 tmpStream << "VID:0x" << std::setw(2) << (int)data->oemData[1]
274 << std::setw(2) << (int)data->oemData[0] << " DID:0x"
275 << std::setw(2) << (int)data->oemData[3] << std::setw(2)
276 << (int)data->oemData[2] << " Slot:0x" << std::setw(2)
277 << (int)data->oemData[4] << " Error ID:0x" << std::setw(2)
278 << (int)data->oemData[5];
279 break;
280 case 0xC2:
281 tmpStream << "Extra info:0x" << std::setw(2)
282 << (int)data->oemData[1] << " MSCOD:0x" << std::setw(2)
283 << (int)data->oemData[3] << std::setw(2)
284 << (int)data->oemData[2] << " MCACOD:0x" << std::setw(2)
285 << (int)data->oemData[5] << std::setw(2)
286 << (int)data->oemData[4];
287 break;
288 case 0xC3:
289 int bank = (data->oemData[1] & 0xf0) >> 4;
290 int col = ((data->oemData[1] & 0x0f) << 8) | data->oemData[2];
291
292 tmpStream << "Fail Device:0x" << std::setw(2)
293 << (int)data->oemData[0] << " Bank:0x" << std::setw(2)
294 << bank << " Column:0x" << std::setw(2) << col
295 << " Failed Row:0x" << std::setw(2)
296 << (int)data->oemData[3] << std::setw(2)
297 << (int)data->oemData[4] << std::setw(2)
298 << (int)data->oemData[5];
299 }
300
301 errStr = tmpStream.str();
302
303 return;
304}
305
306static void parseSelData(std::vector<uint8_t> &reqData, std::string &msgLog)
307{
308
309 /* Get record type */
310 int recType = reqData[2];
311 std::string errType, errLog;
312
313 uint8_t *ptr = NULL;
314
315 std::stringstream recTypeStream;
316 recTypeStream << std::hex << std::uppercase << std::setfill('0')
317 << std::setw(2) << recType;
318
319 msgLog = "SEL Entry: FRU: 1, Record: ";
320
321 if (recType == stdErrType)
322 {
323 StdSELEntry *data = reinterpret_cast<StdSELEntry *>(&reqData[0]);
324 std::string sensorName;
325
326 errType = stdErr;
327 if (data->sensorType == 0x1F)
328 {
329 sensorName = "OS";
330 }
331 else
332 {
333 auto findSensorName = sensorNameTable.find(data->sensorNum);
334 if (findSensorName == sensorNameTable.end())
335 {
336 sensorName = "Unknown";
337 }
338 else
339 {
340 sensorName = findSensorName->second;
341 }
342 }
343
344 std::tm *ts = localtime((time_t *)(&(data->timeStamp)));
345 std::string timeStr = std::asctime(ts);
346
347 parseStdSel(data, errLog);
348 ptr = &(data->eventData1);
349 std::vector<uint8_t> evtData(ptr, ptr + 3);
350 std::string eventData;
351 toHexStr(evtData, eventData);
352
353 std::stringstream senNumStream;
354 senNumStream << std::hex << std::uppercase << std::setfill('0')
355 << std::setw(2) << (int)(data->sensorNum);
356
357 msgLog += errType + " (0x" + recTypeStream.str() +
358 "), Time: " + timeStr + ", Sensor: " + sensorName + " (0x" +
359 senNumStream.str() + "), Event Data: (" + eventData + ") " +
360 errLog;
361 }
362 else if ((recType >= oemTSErrTypeMin) && (recType <= oemTSErrTypeMax))
363 {
364 /* timestamped OEM SEL records */
365 TsOemSELEntry *data = reinterpret_cast<TsOemSELEntry *>(&reqData[0]);
366 ptr = data->mfrId;
367 std::vector<uint8_t> mfrIdData(ptr, ptr + 3);
368 std::string mfrIdStr;
369 toHexStr(mfrIdData, mfrIdStr);
370
371 ptr = data->oemData;
372 std::vector<uint8_t> oemData(ptr, ptr + 6);
373 std::string oemDataStr;
374 toHexStr(oemData, oemDataStr);
375
376 std::tm *ts = localtime((time_t *)(&(data->timeStamp)));
377 std::string timeStr = std::asctime(ts);
378
379 errType = oemTSErr;
380 parseOemSel(data, errLog);
381
382 msgLog += errType + " (0x" + recTypeStream.str() +
383 "), Time: " + timeStr + ", MFG ID: " + mfrIdStr +
384 ", OEM Data: (" + oemDataStr + ") " + errLog;
385 }
386 else if ((recType >= oemNTSErrTypeMin) && (recType <= oemNTSErrTypeMax))
387 {
388 /* Non timestamped OEM SEL records */
389 NtsOemSELEntry *data = reinterpret_cast<NtsOemSELEntry *>(&reqData[0]);
390 errType = oemNTSErr;
391
392 ptr = data->oemData;
393 std::vector<uint8_t> oemData(ptr, ptr + 13);
394 std::string oemDataStr;
395 toHexStr(oemData, oemDataStr);
396
397 parseOemSel((TsOemSELEntry *)data, errLog);
398 msgLog += errType + " (0x" + recTypeStream.str() + "), OEM Data: (" +
399 oemDataStr + ") " + errLog;
400 }
401 else
402 {
403 errType = unknownErr;
404 toHexStr(reqData, errLog);
405 msgLog +=
406 errType + " (0x" + recTypeStream.str() + ") RawData: " + errLog;
407 }
408}
409
Vijay Khemka11b9c3b2019-08-21 15:21:42 -0700410} // namespace fb_oem::ipmi::sel
411
412namespace ipmi
413{
414
415namespace storage
416{
417
418static void registerSELFunctions() __attribute__((constructor));
419static fb_oem::ipmi::sel::SELData selObj __attribute__((init_priority(101)));
420
421ipmi::RspType<uint8_t, // SEL version
422 uint16_t, // SEL entry count
423 uint16_t, // free space
424 uint32_t, // last add timestamp
425 uint32_t, // last erase timestamp
426 uint8_t> // operation support
427 ipmiStorageGetSELInfo()
428{
429
430 fb_oem::ipmi::sel::GetSELInfoData info;
431
432 selObj.getInfo(info);
433 return ipmi::responseSuccess(info.selVersion, info.entries, info.freeSpace,
434 info.addTimeStamp, info.eraseTimeStamp,
435 info.operationSupport);
436}
437
438ipmi::RspType<uint16_t, std::vector<uint8_t>>
439 ipmiStorageGetSELEntry(std::vector<uint8_t> data)
440{
441
442 if (data.size() != sizeof(fb_oem::ipmi::sel::GetSELEntryRequest))
443 {
444 return ipmi::responseReqDataLenInvalid();
445 }
446
447 fb_oem::ipmi::sel::GetSELEntryRequest *reqData =
448 reinterpret_cast<fb_oem::ipmi::sel::GetSELEntryRequest *>(&data[0]);
449
450 if (reqData->reservID != 0)
451 {
452 if (!checkSELReservation(reqData->reservID))
453 {
454 return ipmi::responseInvalidReservationId();
455 }
456 }
457
458 uint16_t selCnt = selObj.getCount();
459 if (selCnt == 0)
460 {
461 return ipmi::responseSensorInvalid();
462 }
463
464 /* If it is asked for first entry */
465 if (reqData->recordID == fb_oem::ipmi::sel::firstEntry)
466 {
467 /* First Entry (0x0000) as per Spec */
468 reqData->recordID = 1;
469 }
470 else if (reqData->recordID == fb_oem::ipmi::sel::lastEntry)
471 {
472 /* Last entry (0xFFFF) as per Spec */
473 reqData->recordID = selCnt;
474 }
475
476 std::string ipmiRaw;
477
478 if (selObj.getEntry(reqData->recordID, ipmiRaw) < 0)
479 {
480 return ipmi::responseSensorInvalid();
481 }
482
483 std::vector<uint8_t> recDataBytes;
484 if (fromHexStr(ipmiRaw, recDataBytes) < 0)
485 {
486 return ipmi::responseUnspecifiedError();
487 }
488
489 /* Identify the next SEL record ID. If recordID is same as
490 * total SeL count then next id should be last entry else
491 * it should be incremented by 1 to current RecordID
492 */
493 uint16_t nextRecord;
494 if (reqData->recordID == selCnt)
495 {
496 nextRecord = fb_oem::ipmi::sel::lastEntry;
497 }
498 else
499 {
500 nextRecord = reqData->recordID + 1;
501 }
502
503 if (reqData->readLen == fb_oem::ipmi::sel::entireRecord)
504 {
505 return ipmi::responseSuccess(nextRecord, recDataBytes);
506 }
507 else
508 {
509 if (reqData->offset >= fb_oem::ipmi::sel::selRecordSize ||
510 reqData->readLen > fb_oem::ipmi::sel::selRecordSize)
511 {
512 return ipmi::responseUnspecifiedError();
513 }
514 std::vector<uint8_t> recPartData;
515
516 auto diff = fb_oem::ipmi::sel::selRecordSize - reqData->offset;
517 auto readLength = std::min(diff, static_cast<int>(reqData->readLen));
518
519 for (int i = 0; i < readLength; i++)
520 {
521 recPartData.push_back(recDataBytes[i + reqData->offset]);
522 }
523 return ipmi::responseSuccess(nextRecord, recPartData);
524 }
525}
526
527ipmi::RspType<uint16_t> ipmiStorageAddSELEntry(std::vector<uint8_t> data)
528{
529 /* Per the IPMI spec, need to cancel any reservation when a
530 * SEL entry is added
531 */
532 cancelSELReservation();
533
534 if (data.size() != fb_oem::ipmi::sel::selRecordSize)
535 {
536 return ipmi::responseReqDataLenInvalid();
537 }
538
539 std::string ipmiRaw, logErr;
540 toHexStr(data, ipmiRaw);
541
Vijay Khemkaf36f3452019-08-09 13:24:45 -0700542 /* Parse sel data and get an error log to be filed */
543 fb_oem::ipmi::sel::parseSelData(data, logErr);
544
Vijay Khemka11b9c3b2019-08-21 15:21:42 -0700545 /* Log the Raw SEL message to the journal */
546 std::string journalMsg = "SEL Entry Added: " + ipmiRaw;
Vijay Khemkaf36f3452019-08-09 13:24:45 -0700547
Vijay Khemka11b9c3b2019-08-21 15:21:42 -0700548 phosphor::logging::log<phosphor::logging::level::INFO>(journalMsg.c_str());
Vijay Khemkaf36f3452019-08-09 13:24:45 -0700549 phosphor::logging::log<phosphor::logging::level::INFO>(logErr.c_str());
Vijay Khemka11b9c3b2019-08-21 15:21:42 -0700550
551 int responseID = selObj.addEntry(ipmiRaw.c_str());
552 if (responseID < 0)
553 {
554 return ipmi::responseUnspecifiedError();
555 }
556 return ipmi::responseSuccess((uint16_t)responseID);
557}
558
Vijay Khemkac1921c62019-08-09 13:11:31 -0700559ipmi::RspType<uint8_t> ipmiStorageClearSEL(uint16_t reservationID,
560 const std::array<uint8_t, 3> &clr,
561 uint8_t eraseOperation)
562{
563 if (!checkSELReservation(reservationID))
564 {
565 return ipmi::responseInvalidReservationId();
566 }
567
568 static constexpr std::array<uint8_t, 3> clrExpected = {'C', 'L', 'R'};
569 if (clr != clrExpected)
570 {
571 return ipmi::responseInvalidFieldRequest();
572 }
573
574 /* If there is no sel then return erase complete */
575 if (selObj.getCount() == 0)
576 {
577 return ipmi::responseSuccess(fb_oem::ipmi::sel::eraseComplete);
578 }
579
580 /* Erasure status cannot be fetched, so always return erasure
581 * status as `erase completed`.
582 */
583 if (eraseOperation == fb_oem::ipmi::sel::getEraseStatus)
584 {
585 return ipmi::responseSuccess(fb_oem::ipmi::sel::eraseComplete);
586 }
587
588 /* Check that initiate erase is correct */
589 if (eraseOperation != fb_oem::ipmi::sel::initiateErase)
590 {
591 return ipmi::responseInvalidFieldRequest();
592 }
593
594 /* Per the IPMI spec, need to cancel any reservation when the
595 * SEL is cleared
596 */
597 cancelSELReservation();
598
599 /* Clear the complete Sel Json object */
600 if (selObj.clear() < 0)
601 {
602 return ipmi::responseUnspecifiedError();
603 }
604
605 return ipmi::responseSuccess(fb_oem::ipmi::sel::eraseComplete);
606}
607
608ipmi::RspType<uint32_t> ipmiStorageGetSELTime()
609{
610 struct timespec selTime = {};
611
612 if (clock_gettime(CLOCK_REALTIME, &selTime) < 0)
613 {
614 return ipmi::responseUnspecifiedError();
615 }
616
617 return ipmi::responseSuccess(selTime.tv_sec);
618}
619
620ipmi::RspType<> ipmiStorageSetSELTime(uint32_t selTime)
621{
622 // Set SEL Time is not supported
623 return ipmi::responseInvalidCommand();
624}
625
626ipmi::RspType<uint16_t> ipmiStorageGetSELTimeUtcOffset()
627{
628 /* TODO: For now, the SEL time stamp is based on UTC time,
629 * so return 0x0000 as offset. Might need to change once
630 * supporting zones in SEL time stamps
631 */
632
633 uint16_t utcOffset = 0x0000;
634 return ipmi::responseSuccess(utcOffset);
635}
636
Vijay Khemka11b9c3b2019-08-21 15:21:42 -0700637void registerSELFunctions()
638{
639 // <Get SEL Info>
640 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
641 ipmi::storage::cmdGetSelInfo, ipmi::Privilege::User,
642 ipmiStorageGetSELInfo);
643
644 // <Get SEL Entry>
645 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
646 ipmi::storage::cmdGetSelEntry, ipmi::Privilege::User,
647 ipmiStorageGetSELEntry);
648
649 // <Add SEL Entry>
650 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
651 ipmi::storage::cmdAddSelEntry,
652 ipmi::Privilege::Operator, ipmiStorageAddSELEntry);
653
Vijay Khemkac1921c62019-08-09 13:11:31 -0700654 // <Clear SEL>
655 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
656 ipmi::storage::cmdClearSel, ipmi::Privilege::Operator,
657 ipmiStorageClearSEL);
658
659 // <Get SEL Time>
660 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
661 ipmi::storage::cmdGetSelTime, ipmi::Privilege::User,
662 ipmiStorageGetSELTime);
663
664 // <Set SEL Time>
665 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
666 ipmi::storage::cmdSetSelTime,
667 ipmi::Privilege::Operator, ipmiStorageSetSELTime);
668
669 // <Get SEL Time UTC Offset>
670 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
671 ipmi::storage::cmdGetSelTimeUtcOffset,
672 ipmi::Privilege::User,
673 ipmiStorageGetSELTimeUtcOffset);
674
Vijay Khemka11b9c3b2019-08-21 15:21:42 -0700675 return;
676}
677
678} // namespace storage
679} // namespace ipmi