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
raise() const38 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
from_json(const json & j,PostCodeEvent & event)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
decodeHexString(const std::string & hex)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
from_json(const json & j,PostCodeHandler & handler)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
find(postcode_t code)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
handle(postcode_t code)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
load(const std::string & path)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
deleteAll()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
getPostCodes(uint16_t index)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
getPostCodesWithTimeStamp(uint16_t index)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
savePostCodes(postcode_t code)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
serialize(const fs::path & path)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
deserialize(const fs::path & path,uint16_t & index)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
deserializePostCodes(const fs::path & path,std::map<uint64_t,postcode_t> & codes)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
incrBootCycle()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
getBootNum(const uint16_t index) const385 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