xref: /openbmc/phosphor-post-code-manager/src/post_code.cpp (revision 20297dfbd96709af18b6046db76c99c79c149d59)
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