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
from_json(const json & j,PostCodeHandler & handler)68 void from_json(const json& j, PostCodeHandler& handler)
69 {
70 j.at("primary").get_to(handler.primary);
71 if (j.contains("secondary"))
72 {
73 secondarycode_t secondary;
74 j.at("secondary").get_to(secondary);
75 handler.secondary = secondary;
76 }
77 if (j.contains("targets"))
78 {
79 j.at("targets").get_to(handler.targets);
80 }
81 if (j.contains("event"))
82 {
83 PostCodeEvent event;
84 j.at("event").get_to(event);
85 handler.event = event;
86 }
87 }
88
find(postcode_t code)89 const PostCodeHandler* PostCodeHandlers::find(postcode_t code)
90 {
91 for (const auto& handler : handlers)
92 {
93 if (handler.primary == std::get<0>(code) &&
94 (!handler.secondary || *handler.secondary == std::get<1>(code)))
95 {
96 return &handler;
97 }
98 }
99 return nullptr;
100 }
101
handle(postcode_t code)102 void PostCodeHandlers::handle(postcode_t code)
103 {
104 const PostCodeHandler* handler = find(code);
105 if (!handler)
106 {
107 return;
108 }
109 for (const auto& target : handler->targets)
110 {
111 auto bus = sdbusplus::bus::new_default();
112 auto method = bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_ROOT,
113 SYSTEMD_INTERFACE, "StartUnit");
114 method.append(target);
115 method.append("replace");
116 bus.call_noreply(method);
117 }
118 if (handler->event)
119 {
120 (*(handler->event)).raise();
121 }
122 }
123
load(const std::string & path)124 void PostCodeHandlers::load(const std::string& path)
125 {
126 std::ifstream ifs(path);
127 handlers = json::parse(ifs).template get<std::vector<PostCodeHandler>>();
128 ifs.close();
129 }
130
deleteAll()131 void PostCode::deleteAll()
132 {
133 std::uintmax_t n = fs::remove_all(postCodeListPath);
134 std::cerr << "clearPostCodes deleted " << n << " files in "
135 << postCodeListPath << std::endl;
136 fs::create_directories(postCodeListPath);
137 postCodes.clear();
138 currentBootCycleIndex = 0;
139 currentBootCycleCount(0);
140 }
141
getPostCodes(uint16_t index)142 std::vector<postcode_t> PostCode::getPostCodes(uint16_t index)
143 {
144 std::vector<postcode_t> codesVec;
145 if (1 == index && !postCodes.empty())
146 {
147 std::transform(postCodes.begin(), postCodes.end(),
148 std::back_inserter(codesVec),
149 [](const auto& kv) { return kv.second; });
150 }
151 else
152 {
153 uint16_t bootNum = getBootNum(index);
154
155 decltype(postCodes) codes;
156 deserializePostCodes(postCodeListPath / std::to_string(bootNum), codes);
157 std::transform(codes.begin(), codes.end(), std::back_inserter(codesVec),
158 [](const auto& kv) { return kv.second; });
159 }
160 return codesVec;
161 }
162
getPostCodesWithTimeStamp(uint16_t index)163 std::map<uint64_t, postcode_t> PostCode::getPostCodesWithTimeStamp(
164 uint16_t index)
165 {
166 if (1 == index && !postCodes.empty())
167 {
168 return postCodes;
169 }
170
171 uint16_t bootNum = getBootNum(index);
172 decltype(postCodes) codes;
173 deserializePostCodes(postCodeListPath / std::to_string(bootNum), codes);
174 return codes;
175 }
176
savePostCodes(postcode_t code)177 void PostCode::savePostCodes(postcode_t code)
178 {
179 if (!timer)
180 {
181 timer = std::make_unique<sdbusplus::Timer>(event.get(), [this]() {
182 serialize(postCodeListPath);
183 });
184 }
185
186 // steady_clock is a monotonic clock that is guaranteed to never be adjusted
187 auto postCodeTimeSteady = std::chrono::steady_clock::now();
188 uint64_t tsUS = std::chrono::duration_cast<std::chrono::microseconds>(
189 std::chrono::system_clock::now().time_since_epoch())
190 .count();
191
192 if (postCodes.empty())
193 {
194 firstPostCodeTimeSteady = postCodeTimeSteady;
195 firstPostCodeUsSinceEpoch = tsUS; // uS since epoch for 1st post code
196 incrBootCycle();
197 }
198 else
199 {
200 // calculating tsUS so it is monotonic within the same boot
201 tsUS = firstPostCodeUsSinceEpoch +
202 std::chrono::duration_cast<std::chrono::microseconds>(
203 postCodeTimeSteady - firstPostCodeTimeSteady)
204 .count();
205 }
206
207 postCodes.insert(std::make_pair(tsUS, code));
208 if (postCodes.size() > MAX_POST_CODE_SIZE_PER_CYCLE)
209 {
210 postCodes.erase(postCodes.begin());
211 }
212
213 if (!timer->isRunning())
214 {
215 timer->start(std::chrono::microseconds(timeoutMicroSeconds));
216 }
217
218 if (strlen(POSTCODE_DISPLAY_PATH) > 0)
219 {
220 std::string postCodeDisplayPath =
221 POSTCODE_DISPLAY_PATH + std::to_string(node);
222
223 std::ofstream postCodeDisplayFile(postCodeDisplayPath);
224 postCodeDisplayFile << "0x" << std::setfill('0') << std::hex;
225 for (const auto& byte : std::get<0>(code))
226 {
227 postCodeDisplayFile << std::setw(2) << static_cast<int>(byte);
228 }
229 postCodeDisplayFile.close();
230 }
231
232 #ifdef ENABLE_BIOS_POST_CODE_LOG
233 uint64_t usTimeOffset = tsUS - firstPostCodeUsSinceEpoch;
234 std::ostringstream hexCode;
235 hexCode << "0x" << std::setfill('0') << std::hex;
236 for (const auto& byte : std::get<0>(code))
237 {
238 hexCode << std::setw(2) << static_cast<int>(byte);
239 }
240
241 std::ostringstream timeOffsetStr;
242 // Set Fixed-Point Notation
243 timeOffsetStr << std::fixed;
244 // Set precision to 4 digits
245 timeOffsetStr << std::setprecision(4);
246 // Add double to stream
247 timeOffsetStr << static_cast<double>(usTimeOffset) / 1000 / 1000;
248
249 phosphor::logging::log<phosphor::logging::level::INFO>(
250 "BIOS POST Code",
251 phosphor::logging::entry("REDFISH_MESSAGE_ID=%s",
252 "OpenBMC.0.2.BIOSPOSTCode"),
253 phosphor::logging::entry(
254 "REDFISH_MESSAGE_ARGS=%d,%s,%s", currentBootCycleIndex,
255 timeOffsetStr.str().c_str(), hexCode.str().c_str()));
256 #endif
257 postCodeHandlers.handle(code);
258
259 return;
260 }
261
serialize(const fs::path & path)262 fs::path PostCode::serialize(const fs::path& path)
263 {
264 try
265 {
266 std::ofstream osIdx(path / CurrentBootCycleIndexName, std::ios::binary);
267 cereal::BinaryOutputArchive idxArchive(osIdx);
268 idxArchive(currentBootCycleIndex);
269
270 uint16_t count = currentBootCycleCount();
271 std::ofstream osCnt(path / CurrentBootCycleCountName, std::ios::binary);
272 cereal::BinaryOutputArchive cntArchive(osCnt);
273 cntArchive(count);
274
275 std::ofstream osPostCodes(path / std::to_string(currentBootCycleIndex));
276 cereal::BinaryOutputArchive oarchivePostCodes(osPostCodes);
277 oarchivePostCodes(postCodes);
278 }
279 catch (const cereal::Exception& e)
280 {
281 phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
282 return "";
283 }
284 catch (const fs::filesystem_error& e)
285 {
286 phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
287 return "";
288 }
289 return path;
290 }
291
deserialize(const fs::path & path,uint16_t & index)292 bool PostCode::deserialize(const fs::path& path, uint16_t& index)
293 {
294 try
295 {
296 if (fs::exists(path))
297 {
298 std::ifstream is(path, std::ios::in | std::ios::binary);
299 cereal::BinaryInputArchive iarchive(is);
300 iarchive(index);
301 return true;
302 }
303 return false;
304 }
305 catch (const cereal::Exception& e)
306 {
307 phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
308 return false;
309 }
310 catch (const fs::filesystem_error& e)
311 {
312 return false;
313 }
314
315 return false;
316 }
317
deserializePostCodes(const fs::path & path,std::map<uint64_t,postcode_t> & codes)318 bool PostCode::deserializePostCodes(const fs::path& path,
319 std::map<uint64_t, postcode_t>& codes)
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(codes);
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 return false;
342 }
343
incrBootCycle()344 void PostCode::incrBootCycle()
345 {
346 if (currentBootCycleIndex >= maxBootCycleNum())
347 {
348 currentBootCycleIndex = 1;
349 }
350 else
351 {
352 currentBootCycleIndex++;
353 }
354 currentBootCycleCount(std::min(
355 maxBootCycleNum(), static_cast<uint16_t>(currentBootCycleCount() + 1)));
356 }
357
getBootNum(const uint16_t index) const358 uint16_t PostCode::getBootNum(const uint16_t index) const
359 {
360 // bootNum assumes the oldest archive is boot number 1
361 // and the current boot number equals bootCycleCount
362 // map bootNum back to bootIndex that was used to archive postcode
363 uint16_t bootNum = currentBootCycleIndex;
364 if (index > bootNum) // need to wrap around
365 {
366 bootNum = (maxBootCycleNum() + currentBootCycleIndex) - index + 1;
367 }
368 else
369 {
370 bootNum = currentBootCycleIndex - index + 1;
371 }
372 return bootNum;
373 }
374