1 /* 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 34 static 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 45 static 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 68 namespace fb_oem::ipmi::sel 69 { 70 71 class 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 184 } // namespace fb_oem::ipmi::sel 185 186 namespace ipmi 187 { 188 189 namespace storage 190 { 191 192 static void registerSELFunctions() __attribute__((constructor)); 193 static fb_oem::ipmi::sel::SELData selObj __attribute__((init_priority(101))); 194 195 ipmi::RspType<uint8_t, // SEL version 196 uint16_t, // SEL entry count 197 uint16_t, // free space 198 uint32_t, // last add timestamp 199 uint32_t, // last erase timestamp 200 uint8_t> // operation support 201 ipmiStorageGetSELInfo() 202 { 203 204 fb_oem::ipmi::sel::GetSELInfoData info; 205 206 selObj.getInfo(info); 207 return ipmi::responseSuccess(info.selVersion, info.entries, info.freeSpace, 208 info.addTimeStamp, info.eraseTimeStamp, 209 info.operationSupport); 210 } 211 212 ipmi::RspType<uint16_t, std::vector<uint8_t>> 213 ipmiStorageGetSELEntry(std::vector<uint8_t> data) 214 { 215 216 if (data.size() != sizeof(fb_oem::ipmi::sel::GetSELEntryRequest)) 217 { 218 return ipmi::responseReqDataLenInvalid(); 219 } 220 221 fb_oem::ipmi::sel::GetSELEntryRequest *reqData = 222 reinterpret_cast<fb_oem::ipmi::sel::GetSELEntryRequest *>(&data[0]); 223 224 if (reqData->reservID != 0) 225 { 226 if (!checkSELReservation(reqData->reservID)) 227 { 228 return ipmi::responseInvalidReservationId(); 229 } 230 } 231 232 uint16_t selCnt = selObj.getCount(); 233 if (selCnt == 0) 234 { 235 return ipmi::responseSensorInvalid(); 236 } 237 238 /* If it is asked for first entry */ 239 if (reqData->recordID == fb_oem::ipmi::sel::firstEntry) 240 { 241 /* First Entry (0x0000) as per Spec */ 242 reqData->recordID = 1; 243 } 244 else if (reqData->recordID == fb_oem::ipmi::sel::lastEntry) 245 { 246 /* Last entry (0xFFFF) as per Spec */ 247 reqData->recordID = selCnt; 248 } 249 250 std::string ipmiRaw; 251 252 if (selObj.getEntry(reqData->recordID, ipmiRaw) < 0) 253 { 254 return ipmi::responseSensorInvalid(); 255 } 256 257 std::vector<uint8_t> recDataBytes; 258 if (fromHexStr(ipmiRaw, recDataBytes) < 0) 259 { 260 return ipmi::responseUnspecifiedError(); 261 } 262 263 /* Identify the next SEL record ID. If recordID is same as 264 * total SeL count then next id should be last entry else 265 * it should be incremented by 1 to current RecordID 266 */ 267 uint16_t nextRecord; 268 if (reqData->recordID == selCnt) 269 { 270 nextRecord = fb_oem::ipmi::sel::lastEntry; 271 } 272 else 273 { 274 nextRecord = reqData->recordID + 1; 275 } 276 277 if (reqData->readLen == fb_oem::ipmi::sel::entireRecord) 278 { 279 return ipmi::responseSuccess(nextRecord, recDataBytes); 280 } 281 else 282 { 283 if (reqData->offset >= fb_oem::ipmi::sel::selRecordSize || 284 reqData->readLen > fb_oem::ipmi::sel::selRecordSize) 285 { 286 return ipmi::responseUnspecifiedError(); 287 } 288 std::vector<uint8_t> recPartData; 289 290 auto diff = fb_oem::ipmi::sel::selRecordSize - reqData->offset; 291 auto readLength = std::min(diff, static_cast<int>(reqData->readLen)); 292 293 for (int i = 0; i < readLength; i++) 294 { 295 recPartData.push_back(recDataBytes[i + reqData->offset]); 296 } 297 return ipmi::responseSuccess(nextRecord, recPartData); 298 } 299 } 300 301 ipmi::RspType<uint16_t> ipmiStorageAddSELEntry(std::vector<uint8_t> data) 302 { 303 /* Per the IPMI spec, need to cancel any reservation when a 304 * SEL entry is added 305 */ 306 cancelSELReservation(); 307 308 if (data.size() != fb_oem::ipmi::sel::selRecordSize) 309 { 310 return ipmi::responseReqDataLenInvalid(); 311 } 312 313 std::string ipmiRaw, logErr; 314 toHexStr(data, ipmiRaw); 315 316 /* Log the Raw SEL message to the journal */ 317 std::string journalMsg = "SEL Entry Added: " + ipmiRaw; 318 phosphor::logging::log<phosphor::logging::level::INFO>(journalMsg.c_str()); 319 320 int responseID = selObj.addEntry(ipmiRaw.c_str()); 321 if (responseID < 0) 322 { 323 return ipmi::responseUnspecifiedError(); 324 } 325 return ipmi::responseSuccess((uint16_t)responseID); 326 } 327 328 ipmi::RspType<uint8_t> ipmiStorageClearSEL(uint16_t reservationID, 329 const std::array<uint8_t, 3> &clr, 330 uint8_t eraseOperation) 331 { 332 if (!checkSELReservation(reservationID)) 333 { 334 return ipmi::responseInvalidReservationId(); 335 } 336 337 static constexpr std::array<uint8_t, 3> clrExpected = {'C', 'L', 'R'}; 338 if (clr != clrExpected) 339 { 340 return ipmi::responseInvalidFieldRequest(); 341 } 342 343 /* If there is no sel then return erase complete */ 344 if (selObj.getCount() == 0) 345 { 346 return ipmi::responseSuccess(fb_oem::ipmi::sel::eraseComplete); 347 } 348 349 /* Erasure status cannot be fetched, so always return erasure 350 * status as `erase completed`. 351 */ 352 if (eraseOperation == fb_oem::ipmi::sel::getEraseStatus) 353 { 354 return ipmi::responseSuccess(fb_oem::ipmi::sel::eraseComplete); 355 } 356 357 /* Check that initiate erase is correct */ 358 if (eraseOperation != fb_oem::ipmi::sel::initiateErase) 359 { 360 return ipmi::responseInvalidFieldRequest(); 361 } 362 363 /* Per the IPMI spec, need to cancel any reservation when the 364 * SEL is cleared 365 */ 366 cancelSELReservation(); 367 368 /* Clear the complete Sel Json object */ 369 if (selObj.clear() < 0) 370 { 371 return ipmi::responseUnspecifiedError(); 372 } 373 374 return ipmi::responseSuccess(fb_oem::ipmi::sel::eraseComplete); 375 } 376 377 ipmi::RspType<uint32_t> ipmiStorageGetSELTime() 378 { 379 struct timespec selTime = {}; 380 381 if (clock_gettime(CLOCK_REALTIME, &selTime) < 0) 382 { 383 return ipmi::responseUnspecifiedError(); 384 } 385 386 return ipmi::responseSuccess(selTime.tv_sec); 387 } 388 389 ipmi::RspType<> ipmiStorageSetSELTime(uint32_t selTime) 390 { 391 // Set SEL Time is not supported 392 return ipmi::responseInvalidCommand(); 393 } 394 395 ipmi::RspType<uint16_t> ipmiStorageGetSELTimeUtcOffset() 396 { 397 /* TODO: For now, the SEL time stamp is based on UTC time, 398 * so return 0x0000 as offset. Might need to change once 399 * supporting zones in SEL time stamps 400 */ 401 402 uint16_t utcOffset = 0x0000; 403 return ipmi::responseSuccess(utcOffset); 404 } 405 406 void registerSELFunctions() 407 { 408 // <Get SEL Info> 409 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 410 ipmi::storage::cmdGetSelInfo, ipmi::Privilege::User, 411 ipmiStorageGetSELInfo); 412 413 // <Get SEL Entry> 414 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 415 ipmi::storage::cmdGetSelEntry, ipmi::Privilege::User, 416 ipmiStorageGetSELEntry); 417 418 // <Add SEL Entry> 419 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 420 ipmi::storage::cmdAddSelEntry, 421 ipmi::Privilege::Operator, ipmiStorageAddSELEntry); 422 423 // <Clear SEL> 424 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 425 ipmi::storage::cmdClearSel, ipmi::Privilege::Operator, 426 ipmiStorageClearSEL); 427 428 // <Get SEL Time> 429 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 430 ipmi::storage::cmdGetSelTime, ipmi::Privilege::User, 431 ipmiStorageGetSELTime); 432 433 // <Set SEL Time> 434 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 435 ipmi::storage::cmdSetSelTime, 436 ipmi::Privilege::Operator, ipmiStorageSetSELTime); 437 438 // <Get SEL Time UTC Offset> 439 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 440 ipmi::storage::cmdGetSelTimeUtcOffset, 441 ipmi::Privilege::User, 442 ipmiStorageGetSELTimeUtcOffset); 443 444 return; 445 } 446 447 } // namespace storage 448 } // namespace ipmi 449