1 /*
2  * Copyright 2019 Google Inc.
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 
17 #include "helper.hpp"
18 
19 #include "status.hpp"
20 #include "tool_errors.hpp"
21 
22 #include <ipmiblob/blob_errors.hpp>
23 #include <ipmiblob/blob_interface.hpp>
24 
25 #include <algorithm>
26 #include <chrono>
27 #include <optional>
28 #include <thread>
29 #include <utility>
30 
31 namespace host_tool
32 {
33 
34 template <typename Check>
35 static auto pollStat(std::uint16_t session, ipmiblob::BlobInterface* blob,
36                      Check&& check)
37 {
38     using namespace std::chrono_literals;
39 
40     constexpr auto maxSleep = 1s;
41     constexpr auto printInterval = 30s;
42     constexpr auto timeout = 30min;
43 
44     try
45     {
46         auto start = std::chrono::steady_clock::now();
47         auto last_print = start;
48         auto last_check = start;
49         auto check_interval = 50ms;
50 
51         while (true)
52         {
53             ipmiblob::StatResponse resp = blob->getStat(session);
54             auto ret = check(resp);
55             if (ret.has_value())
56             {
57                 std::fprintf(stderr, "success\n");
58                 return std::move(*ret);
59             }
60 
61             auto cur = std::chrono::steady_clock::now();
62             if (cur - last_print >= printInterval)
63             {
64                 std::fprintf(stderr, "running\n");
65                 last_print = cur;
66             }
67 
68             auto sleep = check_interval - (cur - last_check);
69             last_check = cur;
70             // Check that we don't timeout immediately after sleeping
71             // to avoid an extra sleep without checking
72             if (cur - start > timeout - sleep)
73             {
74                 throw ToolException("Stat check timed out");
75             }
76             check_interval = std::min<decltype(check_interval)>(
77                 check_interval * 2, maxSleep);
78             std::this_thread::sleep_for(sleep);
79         }
80     }
81     catch (const ipmiblob::BlobException& b)
82     {
83         throw ToolException("blob exception received: " +
84                             std::string(b.what()));
85     }
86 }
87 
88 /* Poll an open verification session.  Handling closing the session is not yet
89  * owned by this method.
90  */
91 void pollStatus(std::uint16_t session, ipmiblob::BlobInterface* blob)
92 {
93     pollStat(session, blob,
94              [](const ipmiblob::StatResponse& resp) -> std::optional<bool> {
95         if (resp.metadata.size() != 1)
96         {
97             throw ToolException("Invalid stat metadata");
98         }
99         auto result = static_cast<ipmi_flash::ActionStatus>(resp.metadata[0]);
100         switch (result)
101         {
102             case ipmi_flash::ActionStatus::failed:
103                 throw ToolException("BMC reported failure");
104             case ipmi_flash::ActionStatus::unknown:
105             case ipmi_flash::ActionStatus::running:
106                 return std::nullopt;
107             case ipmi_flash::ActionStatus::success:
108                 return true;
109             default:
110                 throw ToolException("Unrecognized action status");
111         }
112     });
113 }
114 
115 /* Poll an open blob session for reading.
116  *
117  * The committing bit indicates that the blob is not available for reading now
118  * and the reader might come back and check the state later.
119  *
120  * Polling finishes under the following conditions:
121  * - The open_read bit set -> stat successful
122  * - The open_read and committing bits unset -> stat failed;
123  * - Blob exception was received;
124  * - Time ran out.
125  * Polling continues when the open_read bit unset and committing bit set.
126  * If the blob is not open_read and not committing, then it is an error to the
127  * reader.
128  */
129 uint32_t pollReadReady(std::uint16_t session, ipmiblob::BlobInterface* blob)
130 {
131     return pollStat(
132         session, blob,
133         [](const ipmiblob::StatResponse& resp) -> std::optional<uint32_t> {
134         if (resp.blob_state & ipmiblob::StateFlags::open_read)
135         {
136             return resp.size;
137         }
138         if (resp.blob_state & ipmiblob::StateFlags::committing)
139         {
140             return std::nullopt;
141         }
142         throw ToolException("BMC blob failed to become ready");
143     });
144 }
145 
146 void* memcpyAligned(void* destination, const void* source, std::size_t size)
147 {
148     std::size_t i = 0;
149     std::size_t bytesCopied = 0;
150 
151     if (!(reinterpret_cast<std::uintptr_t>(destination) %
152           sizeof(std::uint64_t)) &&
153         !(reinterpret_cast<std::uintptr_t>(source) % sizeof(std::uint64_t)))
154     {
155         auto src64 = reinterpret_cast<const volatile std::uint64_t*>(source);
156         auto dest64 = reinterpret_cast<volatile std::uint64_t*>(destination);
157 
158         for (i = 0; i < size / sizeof(std::uint64_t); i++)
159         {
160             *dest64++ = *src64++;
161             bytesCopied += sizeof(std::uint64_t);
162         }
163     }
164 
165     auto srcMem8 = reinterpret_cast<const volatile std::uint8_t*>(source) +
166                    bytesCopied;
167     auto destMem8 = reinterpret_cast<volatile std::uint8_t*>(destination) +
168                     bytesCopied;
169 
170     for (i = bytesCopied; i < size; i++)
171     {
172         *destMem8++ = *srcMem8++;
173     }
174 
175     return destination;
176 }
177 
178 } // namespace host_tool
179