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