1 /* 2 // Copyright (c) 2019 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 #include "post_code.hpp" 17 18 #include <cereal/access.hpp> 19 #include <cereal/archives/binary.hpp> 20 #include <cereal/cereal.hpp> 21 #include <cereal/types/map.hpp> 22 #include <cereal/types/tuple.hpp> 23 #include <cereal/types/vector.hpp> 24 #include <phosphor-logging/commit.hpp> 25 #include <sdbusplus/bus.hpp> 26 #include <sdbusplus/exception.hpp> 27 28 #include <iomanip> 29 30 using nlohmann::json; 31 32 const static constexpr auto timeoutMicroSeconds = 1000000; 33 /* systemd service to kick start a target. */ 34 constexpr auto SYSTEMD_SERVICE = "org.freedesktop.systemd1"; 35 constexpr auto SYSTEMD_ROOT = "/org/freedesktop/systemd1"; 36 constexpr auto SYSTEMD_INTERFACE = "org.freedesktop.systemd1.Manager"; 37 38 void PostCodeEvent::raise() const 39 { 40 json j = {{name, args}}; 41 try 42 { 43 sdbusplus::exception::throw_via_json(j); 44 } 45 catch (sdbusplus::exception::generated_event_base& e) 46 { 47 auto path = lg2::commit(std::move(e)); 48 std::cout << path.str << std::endl; 49 } 50 } 51 52 void from_json(const json& j, PostCodeEvent& event) 53 { 54 j.at("name").get_to(event.name); 55 for (const auto& entry : j.at("arguments").items()) 56 { 57 if (entry.value().is_string()) 58 { 59 event.args[entry.key()] = entry.value().get<std::string>(); 60 } 61 else if (entry.value().is_number_integer()) 62 { 63 event.args[entry.key()] = entry.value().get<int>(); 64 } 65 } 66 } 67 68 std::vector<uint8_t> decodeHexString(const std::string& hex) 69 { 70 std::vector<uint8_t> out; 71 // The post-code is at least 1 byte. So, we need at 72 // least a 4 char string. 73 if (hex.size() < 4 || hex.size() % 2 != 0 || hex.substr(0, 2) != "0x") 74 { 75 throw std::runtime_error("Bad Hex String: " + hex); 76 } 77 for (size_t i = 2; i < hex.size(); i += 2) 78 { 79 std::string byteString = hex.substr(i, 2); 80 uint8_t byte = (uint8_t)std::strtol(byteString.c_str(), NULL, 16); 81 out.push_back(byte); 82 } 83 return out; 84 } 85 86 void from_json(const json& j, PostCodeHandler& handler) 87 { 88 std::string primary; 89 j.at("name").get_to(handler.name); 90 j.at("description").get_to(handler.description); 91 j.at("primary").get_to(primary); 92 handler.primary = decodeHexString(primary); 93 if (j.contains("secondary")) 94 { 95 std::string secondary; 96 j.at("secondary").get_to(secondary); 97 handler.secondary = decodeHexString(secondary); 98 } 99 if (j.contains("targets")) 100 { 101 j.at("targets").get_to(handler.targets); 102 } 103 if (j.contains("event")) 104 { 105 PostCodeEvent event; 106 j.at("event").get_to(event); 107 handler.event = event; 108 } 109 } 110 111 const PostCodeHandler* PostCodeHandlers::find(postcode_t code) 112 { 113 for (const auto& handler : handlers) 114 { 115 if (handler.primary == std::get<0>(code) && 116 (!handler.secondary || *handler.secondary == std::get<1>(code))) 117 { 118 return &handler; 119 } 120 } 121 return nullptr; 122 } 123 124 void PostCodeHandlers::handle(postcode_t code) 125 { 126 const PostCodeHandler* handler = find(code); 127 if (!handler) 128 { 129 return; 130 } 131 for (const auto& target : handler->targets) 132 { 133 auto bus = sdbusplus::bus::new_default(); 134 auto method = bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_ROOT, 135 SYSTEMD_INTERFACE, "StartUnit"); 136 method.append(target); 137 method.append("replace"); 138 bus.call_noreply(method); 139 } 140 if (handler->event) 141 { 142 (*(handler->event)).raise(); 143 } 144 } 145 146 void PostCodeHandlers::load(const std::string& path) 147 { 148 std::ifstream ifs(path); 149 handlers = json::parse(ifs).template get<std::vector<PostCodeHandler>>(); 150 ifs.close(); 151 } 152 153 void PostCode::deleteAll() 154 { 155 std::uintmax_t n = fs::remove_all(postCodeListPath); 156 std::cerr << "clearPostCodes deleted " << n << " files in " 157 << postCodeListPath << std::endl; 158 fs::create_directories(postCodeListPath); 159 postCodes.clear(); 160 currentBootCycleIndex = 0; 161 currentBootCycleCount(0); 162 } 163 164 std::vector<postcode_t> PostCode::getPostCodes(uint16_t index) 165 { 166 std::vector<postcode_t> codesVec; 167 if (1 == index && !postCodes.empty()) 168 { 169 std::transform(postCodes.begin(), postCodes.end(), 170 std::back_inserter(codesVec), 171 [](const auto& kv) { return kv.second; }); 172 } 173 else 174 { 175 uint16_t bootNum = getBootNum(index); 176 177 decltype(postCodes) codes; 178 deserializePostCodes(postCodeListPath / std::to_string(bootNum), codes); 179 std::transform(codes.begin(), codes.end(), std::back_inserter(codesVec), 180 [](const auto& kv) { return kv.second; }); 181 } 182 return codesVec; 183 } 184 185 std::map<uint64_t, postcode_t> PostCode::getPostCodesWithTimeStamp( 186 uint16_t index) 187 { 188 if (1 == index && !postCodes.empty()) 189 { 190 return postCodes; 191 } 192 193 uint16_t bootNum = getBootNum(index); 194 decltype(postCodes) codes; 195 deserializePostCodes(postCodeListPath / std::to_string(bootNum), codes); 196 return codes; 197 } 198 199 void PostCode::savePostCodes(postcode_t code) 200 { 201 if (!timer) 202 { 203 timer = std::make_unique<sdbusplus::Timer>(event.get(), [this]() { 204 serialize(postCodeListPath); 205 }); 206 } 207 208 // steady_clock is a monotonic clock that is guaranteed to never be adjusted 209 auto postCodeTimeSteady = std::chrono::steady_clock::now(); 210 uint64_t tsUS = std::chrono::duration_cast<std::chrono::microseconds>( 211 std::chrono::system_clock::now().time_since_epoch()) 212 .count(); 213 214 if (postCodes.empty()) 215 { 216 firstPostCodeTimeSteady = postCodeTimeSteady; 217 firstPostCodeUsSinceEpoch = tsUS; // uS since epoch for 1st post code 218 incrBootCycle(); 219 } 220 else 221 { 222 // calculating tsUS so it is monotonic within the same boot 223 tsUS = firstPostCodeUsSinceEpoch + 224 std::chrono::duration_cast<std::chrono::microseconds>( 225 postCodeTimeSteady - firstPostCodeTimeSteady) 226 .count(); 227 } 228 229 postCodes.insert(std::make_pair(tsUS, code)); 230 if (postCodes.size() > MAX_POST_CODE_SIZE_PER_CYCLE) 231 { 232 postCodes.erase(postCodes.begin()); 233 } 234 235 if (!timer->isRunning()) 236 { 237 timer->start(std::chrono::microseconds(timeoutMicroSeconds)); 238 } 239 240 if (strlen(POSTCODE_DISPLAY_PATH) > 0) 241 { 242 std::string postCodeDisplayPath = 243 POSTCODE_DISPLAY_PATH + std::to_string(node); 244 245 std::ofstream postCodeDisplayFile(postCodeDisplayPath); 246 postCodeDisplayFile << "0x" << std::setfill('0') << std::hex; 247 for (const auto& byte : std::get<0>(code)) 248 { 249 postCodeDisplayFile << std::setw(2) << static_cast<int>(byte); 250 } 251 postCodeDisplayFile.close(); 252 } 253 254 #ifdef ENABLE_BIOS_POST_CODE_LOG 255 uint64_t usTimeOffset = tsUS - firstPostCodeUsSinceEpoch; 256 std::ostringstream hexCode; 257 hexCode << "0x" << std::setfill('0') << std::hex; 258 for (const auto& byte : std::get<0>(code)) 259 { 260 hexCode << std::setw(2) << static_cast<int>(byte); 261 } 262 263 std::ostringstream timeOffsetStr; 264 // Set Fixed-Point Notation 265 timeOffsetStr << std::fixed; 266 // Set precision to 4 digits 267 timeOffsetStr << std::setprecision(4); 268 // Add double to stream 269 timeOffsetStr << static_cast<double>(usTimeOffset) / 1000 / 1000; 270 271 phosphor::logging::log<phosphor::logging::level::INFO>( 272 "BIOS POST Code", 273 phosphor::logging::entry("REDFISH_MESSAGE_ID=%s", 274 "OpenBMC.0.2.BIOSPOSTCode"), 275 phosphor::logging::entry( 276 "REDFISH_MESSAGE_ARGS=%d,%s,%s", currentBootCycleIndex, 277 timeOffsetStr.str().c_str(), hexCode.str().c_str())); 278 #endif 279 postCodeHandlers.handle(code); 280 281 return; 282 } 283 284 fs::path PostCode::serialize(const fs::path& path) 285 { 286 try 287 { 288 std::ofstream osIdx(path / CurrentBootCycleIndexName, std::ios::binary); 289 cereal::BinaryOutputArchive idxArchive(osIdx); 290 idxArchive(currentBootCycleIndex); 291 292 uint16_t count = currentBootCycleCount(); 293 std::ofstream osCnt(path / CurrentBootCycleCountName, std::ios::binary); 294 cereal::BinaryOutputArchive cntArchive(osCnt); 295 cntArchive(count); 296 297 std::ofstream osPostCodes(path / std::to_string(currentBootCycleIndex)); 298 cereal::BinaryOutputArchive oarchivePostCodes(osPostCodes); 299 oarchivePostCodes(postCodes); 300 301 std::ofstream osVersion(path / PostCodeDataVersionName, 302 std::ios::binary); 303 cereal::BinaryOutputArchive versionArchive(osVersion); 304 versionArchive(PostCodeDataVersion); 305 } 306 catch (const cereal::Exception& e) 307 { 308 phosphor::logging::log<phosphor::logging::level::ERR>(e.what()); 309 return ""; 310 } 311 catch (const fs::filesystem_error& e) 312 { 313 phosphor::logging::log<phosphor::logging::level::ERR>(e.what()); 314 return ""; 315 } 316 return path; 317 } 318 319 bool PostCode::deserialize(const fs::path& path, uint16_t& index) 320 { 321 try 322 { 323 if (fs::exists(path)) 324 { 325 std::ifstream is(path, std::ios::in | std::ios::binary); 326 cereal::BinaryInputArchive iarchive(is); 327 iarchive(index); 328 return true; 329 } 330 return false; 331 } 332 catch (const cereal::Exception& e) 333 { 334 phosphor::logging::log<phosphor::logging::level::ERR>(e.what()); 335 return false; 336 } 337 catch (const fs::filesystem_error& e) 338 { 339 return false; 340 } 341 342 return false; 343 } 344 345 bool PostCode::deserializePostCodes(const fs::path& path, 346 std::map<uint64_t, postcode_t>& codes) 347 { 348 try 349 { 350 if (fs::exists(path)) 351 { 352 std::ifstream is(path, std::ios::in | std::ios::binary); 353 cereal::BinaryInputArchive iarchive(is); 354 iarchive(codes); 355 return true; 356 } 357 return false; 358 } 359 catch (const cereal::Exception& e) 360 { 361 phosphor::logging::log<phosphor::logging::level::ERR>(e.what()); 362 return false; 363 } 364 catch (const fs::filesystem_error& e) 365 { 366 return false; 367 } 368 return false; 369 } 370 371 void PostCode::incrBootCycle() 372 { 373 if (currentBootCycleIndex >= maxBootCycleNum()) 374 { 375 currentBootCycleIndex = 1; 376 } 377 else 378 { 379 currentBootCycleIndex++; 380 } 381 currentBootCycleCount(std::min( 382 maxBootCycleNum(), static_cast<uint16_t>(currentBootCycleCount() + 1))); 383 } 384 385 uint16_t PostCode::getBootNum(const uint16_t index) const 386 { 387 // bootNum assumes the oldest archive is boot number 1 388 // and the current boot number equals bootCycleCount 389 // map bootNum back to bootIndex that was used to archive postcode 390 uint16_t bootNum = currentBootCycleIndex; 391 if (index > bootNum) // need to wrap around 392 { 393 bootNum = (maxBootCycleNum() + currentBootCycleIndex) - index + 1; 394 } 395 else 396 { 397 bootNum = currentBootCycleIndex - index + 1; 398 } 399 return bootNum; 400 } 401