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>
pollStat(std::uint16_t session,ipmiblob::BlobInterface * blob,Check && 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(
84 "blob exception received: " + 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 */
pollStatus(std::uint16_t session,ipmiblob::BlobInterface * blob)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 =
100 static_cast<ipmi_flash::ActionStatus>(resp.metadata[0]);
101 switch (result)
102 {
103 case ipmi_flash::ActionStatus::failed:
104 throw ToolException("BMC reported failure");
105 case ipmi_flash::ActionStatus::unknown:
106 case ipmi_flash::ActionStatus::running:
107 return std::nullopt;
108 case ipmi_flash::ActionStatus::success:
109 return true;
110 default:
111 throw ToolException("Unrecognized action status");
112 }
113 });
114 }
115
116 /* Poll an open blob session for reading.
117 *
118 * The committing bit indicates that the blob is not available for reading now
119 * and the reader might come back and check the state later.
120 *
121 * Polling finishes under the following conditions:
122 * - The open_read bit set -> stat successful
123 * - The open_read and committing bits unset -> stat failed;
124 * - Blob exception was received;
125 * - Time ran out.
126 * Polling continues when the open_read bit unset and committing bit set.
127 * If the blob is not open_read and not committing, then it is an error to the
128 * reader.
129 */
pollReadReady(std::uint16_t session,ipmiblob::BlobInterface * blob)130 uint32_t pollReadReady(std::uint16_t session, ipmiblob::BlobInterface* blob)
131 {
132 return pollStat(
133 session, blob,
134 [](const ipmiblob::StatResponse& resp) -> std::optional<uint32_t> {
135 if (resp.blob_state & ipmiblob::StateFlags::open_read)
136 {
137 return resp.size;
138 }
139 if (resp.blob_state & ipmiblob::StateFlags::committing)
140 {
141 return std::nullopt;
142 }
143 throw ToolException("BMC blob failed to become ready");
144 });
145 }
146
memcpyAligned(void * destination,const void * source,std::size_t size)147 void* memcpyAligned(void* destination, const void* source, std::size_t size)
148 {
149 std::size_t i = 0;
150 std::size_t bytesCopied = 0;
151
152 if (!(reinterpret_cast<std::uintptr_t>(destination) %
153 sizeof(std::uint64_t)) &&
154 !(reinterpret_cast<std::uintptr_t>(source) % sizeof(std::uint64_t)))
155 {
156 auto src64 = reinterpret_cast<const volatile std::uint64_t*>(source);
157 auto dest64 = reinterpret_cast<volatile std::uint64_t*>(destination);
158
159 for (i = 0; i < size / sizeof(std::uint64_t); i++)
160 {
161 *dest64++ = *src64++;
162 bytesCopied += sizeof(std::uint64_t);
163 }
164 }
165
166 auto srcMem8 = reinterpret_cast<const volatile std::uint8_t*>(source) +
167 bytesCopied;
168 auto destMem8 = reinterpret_cast<volatile std::uint8_t*>(destination) +
169 bytesCopied;
170
171 for (i = bytesCopied; i < size; i++)
172 {
173 *destMem8++ = *srcMem8++;
174 }
175
176 return destination;
177 }
178
179 } // namespace host_tool
180