Vijay Khemka | 11b9c3b | 2019-08-21 15:21:42 -0700 | [diff] [blame^] | 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 | void registerSELFunctions() |
| 329 | { |
| 330 | // <Get SEL Info> |
| 331 | ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, |
| 332 | ipmi::storage::cmdGetSelInfo, ipmi::Privilege::User, |
| 333 | ipmiStorageGetSELInfo); |
| 334 | |
| 335 | // <Get SEL Entry> |
| 336 | ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, |
| 337 | ipmi::storage::cmdGetSelEntry, ipmi::Privilege::User, |
| 338 | ipmiStorageGetSELEntry); |
| 339 | |
| 340 | // <Add SEL Entry> |
| 341 | ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, |
| 342 | ipmi::storage::cmdAddSelEntry, |
| 343 | ipmi::Privilege::Operator, ipmiStorageAddSELEntry); |
| 344 | |
| 345 | return; |
| 346 | } |
| 347 | |
| 348 | } // namespace storage |
| 349 | } // namespace ipmi |