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 <chrono>
26 #include <thread>
27 #include <utility>
28 
29 namespace host_tool
30 {
31 
32 /* Poll an open verification session.  Handling closing the session is not yet
33  * owned by this method.
34  */
35 void pollStatus(std::uint16_t session, ipmiblob::BlobInterface* blob)
36 {
37     using namespace std::chrono_literals;
38 
39     static constexpr auto verificationSleep = 5s;
40     ipmi_flash::ActionStatus result = ipmi_flash::ActionStatus::unknown;
41 
42     try
43     {
44         /* sleep for 5 seconds and check 360 times, for a timeout of: 1800
45          * seconds (30 minutes).
46          * TODO: make this command line configurable and provide smaller
47          * default value.
48          */
49         static constexpr int commandAttempts = 360;
50         int attempts = 0;
51         bool exitLoop = false;
52 
53         /* Reach back the current status from the verification service output.
54          */
55         while (attempts++ < commandAttempts)
56         {
57             ipmiblob::StatResponse resp = blob->getStat(session);
58 
59             if (resp.metadata.size() != sizeof(std::uint8_t))
60             {
61                 /* TODO: How do we want to handle the verification failures,
62                  * because closing the session to the verify blob has a special
63                  * as-of-yet not fully defined behavior.
64                  */
65                 std::fprintf(stderr, "Received invalid metadata response!!!\n");
66             }
67 
68             result = static_cast<ipmi_flash::ActionStatus>(resp.metadata[0]);
69 
70             switch (result)
71             {
72                 case ipmi_flash::ActionStatus::failed:
73                     std::fprintf(stderr, "failed\n");
74                     exitLoop = true;
75                     break;
76                 case ipmi_flash::ActionStatus::unknown:
77                     std::fprintf(stderr, "other\n");
78                     break;
79                 case ipmi_flash::ActionStatus::running:
80                     std::fprintf(stderr, "running\n");
81                     break;
82                 case ipmi_flash::ActionStatus::success:
83                     std::fprintf(stderr, "success\n");
84                     exitLoop = true;
85                     break;
86                 default:
87                     std::fprintf(stderr, "wat\n");
88             }
89 
90             if (exitLoop)
91             {
92                 break;
93             }
94             std::this_thread::sleep_for(verificationSleep);
95         }
96     }
97     catch (const ipmiblob::BlobException& b)
98     {
99         throw ToolException("blob exception received: " +
100                             std::string(b.what()));
101     }
102 
103     /* TODO: If this is reached and it's not success, it may be worth just
104      * throwing a ToolException with a timeout message specifying the final
105      * read's value.
106      *
107      * TODO: Given that excepting from certain points leaves the BMC update
108      * state machine in an inconsistent state, we need to carefully evaluate
109      * which exceptions from the lower layers allow one to try and delete the
110      * blobs to rollback the state and progress.
111      */
112     if (result != ipmi_flash::ActionStatus::success)
113     {
114         throw ToolException("BMC reported failure");
115     }
116 }
117 
118 /* Poll an open blob session for reading.
119  *
120  * The committing bit indicates that the blob is not available for reading now
121  * and the reader might come back and check the state later.
122  *
123  * Polling finishes under the following conditions:
124  * - The open_read bit set -> stat successful
125  * - The open_read and committing bits unset -> stat failed;
126  * - Blob exception was received;
127  * - Time ran out.
128  * Polling continues when the open_read bit unset and committing bit set.
129  * If the blob is not open_read and not committing, then it is an error to the
130  * reader.
131  */
132 uint32_t pollReadReady(std::uint16_t session, ipmiblob::BlobInterface* blob)
133 {
134     using namespace std::chrono_literals;
135     static constexpr auto pollingSleep = 5s;
136     ipmiblob::StatResponse blobStatResp;
137 
138     try
139     {
140         /* Polling lasts 5 minutes. When opening a version blob, the system
141          * unit defined in the version handler will extract the running version
142          * from the image on the flash.
143          */
144         static constexpr int commandAttempts = 60;
145         int attempts = 0;
146 
147         while (attempts++ < commandAttempts)
148         {
149             blobStatResp = blob->getStat(session);
150 
151             if (blobStatResp.blob_state & ipmiblob::StateFlags::open_read)
152             {
153                 std::fprintf(stderr, "success\n");
154                 return blobStatResp.size;
155             }
156             else if (blobStatResp.blob_state & ipmiblob::StateFlags::committing)
157             {
158                 std::fprintf(stderr, "running\n");
159             }
160             else
161             {
162                 std::fprintf(stderr, "failed\n");
163                 throw ToolException("BMC reported failure");
164             }
165 
166             std::this_thread::sleep_for(pollingSleep);
167         }
168     }
169     catch (const ipmiblob::BlobException& b)
170     {
171         throw ToolException("blob exception received: " +
172                             std::string(b.what()));
173     }
174 
175     throw ToolException("Timed out waiting for BMC read ready");
176 }
177 
178 void* memcpyAligned(void* destination, const void* source, std::size_t size)
179 {
180     std::size_t i = 0;
181     std::size_t bytesCopied = 0;
182 
183     if (!(reinterpret_cast<std::uintptr_t>(destination) %
184           sizeof(std::uint64_t)) &&
185         !(reinterpret_cast<std::uintptr_t>(source) % sizeof(std::uint64_t)))
186     {
187         auto src64 = reinterpret_cast<const volatile std::uint64_t*>(source);
188         auto dest64 = reinterpret_cast<volatile std::uint64_t*>(destination);
189 
190         for (i = 0; i < size / sizeof(std::uint64_t); i++)
191         {
192             *dest64++ = *src64++;
193             bytesCopied += sizeof(std::uint64_t);
194         }
195     }
196 
197     auto srcMem8 =
198         reinterpret_cast<const volatile std::uint8_t*>(source) + bytesCopied;
199     auto destMem8 =
200         reinterpret_cast<volatile std::uint8_t*>(destination) + bytesCopied;
201 
202     for (i = bytesCopied; i < size; i++)
203     {
204         *destMem8++ = *srcMem8++;
205     }
206 
207     return destination;
208 }
209 
210 } // namespace host_tool
211