1 #include "config.h"
2
3 #include "buffer.hpp"
4
5 #include "pci_handler.hpp"
6
7 #include <boost/endian/arithmetic.hpp>
8 #include <boost/endian/conversion.hpp>
9
10 #include <algorithm>
11 #include <array>
12 #include <cstddef>
13 #include <cstdint>
14 #include <format>
15 #include <memory>
16 #include <numeric>
17 #include <span>
18 #include <vector>
19
20 namespace bios_bmc_smm_error_logger
21 {
22
BufferImpl(std::unique_ptr<DataInterface> dataInterface)23 BufferImpl::BufferImpl(std::unique_ptr<DataInterface> dataInterface) :
24 dataInterface(std::move(dataInterface)) {};
25
initialize(uint32_t bmcInterfaceVersion,uint16_t queueSize,uint16_t ueRegionSize,const std::array<uint32_t,4> & magicNumber)26 void BufferImpl::initialize(uint32_t bmcInterfaceVersion, uint16_t queueSize,
27 uint16_t ueRegionSize,
28 const std::array<uint32_t, 4>& magicNumber)
29 {
30 const size_t memoryRegionSize = dataInterface->getMemoryRegionSize();
31 if (queueSize > memoryRegionSize)
32 {
33 throw std::runtime_error(std::format(
34 "[initialize] Proposed queue size '{}' is bigger than the "
35 "BMC's allocated MMIO region of '{}'",
36 queueSize, memoryRegionSize));
37 }
38
39 // Initialize the whole buffer with 0x00
40 const std::vector<uint8_t> emptyVector(queueSize, 0);
41 size_t byteWritten = dataInterface->write(0, emptyVector);
42 if (byteWritten != queueSize)
43 {
44 throw std::runtime_error(
45 std::format("[initialize] Only erased '{}'", byteWritten));
46 }
47
48 // Create an initial buffer header and write to it
49 struct CircularBufferHeader initializationHeader = {};
50 initializationHeader.bmcInterfaceVersion =
51 boost::endian::native_to_little(bmcInterfaceVersion);
52 initializationHeader.queueSize = boost::endian::native_to_little(queueSize);
53 initializationHeader.ueRegionSize =
54 boost::endian::native_to_little(ueRegionSize);
55 std::transform(magicNumber.begin(), magicNumber.end(),
56 initializationHeader.magicNumber.begin(),
57 [](uint32_t number) -> little_uint32_t {
58 return boost::endian::native_to_little(number);
59 });
60
61 uint8_t* initializationHeaderPtr =
62 reinterpret_cast<uint8_t*>(&initializationHeader);
63 size_t initializationHeaderSize = sizeof(initializationHeader);
64 byteWritten = dataInterface->write(
65 0, std::span<const uint8_t>(
66 initializationHeaderPtr,
67 initializationHeaderPtr + initializationHeaderSize));
68 if (byteWritten != initializationHeaderSize)
69 {
70 throw std::runtime_error(std::format(
71 "[initialize] Only wrote '{}' bytes of the header", byteWritten));
72 }
73 cachedBufferHeader = initializationHeader;
74 }
75
readBufferHeader()76 void BufferImpl::readBufferHeader()
77 {
78 size_t headerSize = sizeof(struct CircularBufferHeader);
79 std::vector<uint8_t> bytesRead =
80 dataInterface->read(/*offset=*/0, headerSize);
81
82 if (bytesRead.size() != headerSize)
83 {
84 throw std::runtime_error(
85 std::format("Buffer header read only read '{}', expected '{}'",
86 bytesRead.size(), headerSize));
87 }
88
89 cachedBufferHeader =
90 *reinterpret_cast<struct CircularBufferHeader*>(bytesRead.data());
91 };
92
getCachedBufferHeader() const93 struct CircularBufferHeader BufferImpl::getCachedBufferHeader() const
94 {
95 return cachedBufferHeader;
96 }
97
updateReadPtr(const uint32_t newReadPtr)98 void BufferImpl::updateReadPtr(const uint32_t newReadPtr)
99 {
100 constexpr uint8_t bmcReadPtrOffset =
101 offsetof(struct CircularBufferHeader, bmcReadPtr);
102
103 little_uint24_t truncatedReadPtr =
104 boost::endian::native_to_little(newReadPtr & 0xffffff);
105 uint8_t* truncatedReadPtrPtr =
106 reinterpret_cast<uint8_t*>(&truncatedReadPtr);
107
108 size_t writtenSize = dataInterface->write(
109 bmcReadPtrOffset, std::span<const uint8_t>{
110 truncatedReadPtrPtr,
111 truncatedReadPtrPtr + sizeof(truncatedReadPtr)});
112 if (writtenSize != sizeof(truncatedReadPtr))
113 {
114 throw std::runtime_error(std::format(
115 "[updateReadPtr] Wrote '{}' bytes, instead of expected '{}'",
116 writtenSize, sizeof(truncatedReadPtr)));
117 }
118 cachedBufferHeader.bmcReadPtr = truncatedReadPtr;
119 }
120
updateBmcFlags(const uint32_t newBmcFlag)121 void BufferImpl::updateBmcFlags(const uint32_t newBmcFlag)
122 {
123 constexpr uint8_t bmcFlagsPtrOffset =
124 offsetof(struct CircularBufferHeader, bmcFlags);
125
126 little_uint32_t littleNewBmcFlag =
127 boost::endian::native_to_little(newBmcFlag);
128 uint8_t* littleNewBmcFlagPtr =
129 reinterpret_cast<uint8_t*>(&littleNewBmcFlag);
130
131 size_t writtenSize = dataInterface->write(
132 bmcFlagsPtrOffset, std::span<const uint8_t>{
133 littleNewBmcFlagPtr,
134 littleNewBmcFlagPtr + sizeof(little_uint32_t)});
135 if (writtenSize != sizeof(little_uint32_t))
136 {
137 throw std::runtime_error(std::format(
138 "[updateBmcFlags] Wrote '{}' bytes, instead of expected '{}'",
139 writtenSize, sizeof(little_uint32_t)));
140 }
141 cachedBufferHeader.bmcFlags = littleNewBmcFlag;
142 }
143
wraparoundRead(const uint32_t relativeOffset,const uint32_t length)144 std::vector<uint8_t> BufferImpl::wraparoundRead(const uint32_t relativeOffset,
145 const uint32_t length)
146 {
147 const size_t maxOffset = getMaxOffset();
148
149 if (relativeOffset > maxOffset)
150 {
151 throw std::runtime_error(
152 std::format("[wraparoundRead] relativeOffset '{}' was bigger "
153 "than maxOffset '{}'",
154 relativeOffset, maxOffset));
155 }
156 if (length > maxOffset)
157 {
158 throw std::runtime_error(std::format(
159 "[wraparoundRead] length '{}' was bigger than maxOffset '{}'",
160 length, maxOffset));
161 }
162
163 // Do a calculation to see if the read will wraparound
164 const size_t queueOffset = getQueueOffset();
165 const size_t writableSpace = maxOffset - relativeOffset;
166 size_t numWraparoundBytesToRead = 0;
167 if (length > writableSpace)
168 {
169 // This means we will wrap, count the bytes that are left to read
170 numWraparoundBytesToRead = length - writableSpace;
171 }
172 const size_t numBytesToReadTillQueueEnd = length - numWraparoundBytesToRead;
173
174 std::vector<uint8_t> bytesRead = dataInterface->read(
175 queueOffset + relativeOffset, numBytesToReadTillQueueEnd);
176 if (bytesRead.size() != numBytesToReadTillQueueEnd)
177 {
178 throw std::runtime_error(
179 std::format("[wraparoundRead] Read '{}' which was not "
180 "the requested length of '{}'",
181 bytesRead.size(), numBytesToReadTillQueueEnd));
182 }
183 size_t updatedReadPtr = relativeOffset + numBytesToReadTillQueueEnd;
184 if (updatedReadPtr == maxOffset)
185 {
186 // If we read all the way up to the end of the queue, we need to
187 // manually wrap the updateReadPtr around to 0
188 updatedReadPtr = 0;
189 }
190
191 // If there are any more bytes to be read beyond the buffer, wrap around and
192 // read from the beginning of the buffer (offset by the queueOffset)
193 if (numWraparoundBytesToRead > 0)
194 {
195 std::vector<uint8_t> wrappedBytesRead =
196 dataInterface->read(queueOffset, numWraparoundBytesToRead);
197 if (numWraparoundBytesToRead != wrappedBytesRead.size())
198 {
199 throw std::runtime_error(std::format(
200 "[wraparoundRead] Buffer wrapped around but read '{}' which "
201 "was not the requested lenght of '{}'",
202 wrappedBytesRead.size(), numWraparoundBytesToRead));
203 }
204 bytesRead.insert(bytesRead.end(), wrappedBytesRead.begin(),
205 wrappedBytesRead.end());
206 updatedReadPtr = numWraparoundBytesToRead;
207 }
208 updateReadPtr(updatedReadPtr);
209
210 return bytesRead;
211 }
212
readEntryHeader()213 struct QueueEntryHeader BufferImpl::readEntryHeader()
214 {
215 size_t headerSize = sizeof(struct QueueEntryHeader);
216 // wraparonudRead will throw if it did not read all the bytes, let it
217 // propagate up the stack
218 std::vector<uint8_t> bytesRead = wraparoundRead(
219 boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr),
220 headerSize);
221
222 return *reinterpret_cast<struct QueueEntryHeader*>(bytesRead.data());
223 }
224
readEntry()225 EntryPair BufferImpl::readEntry()
226 {
227 struct QueueEntryHeader entryHeader = readEntryHeader();
228 size_t entrySize = boost::endian::little_to_native(entryHeader.entrySize);
229
230 // wraparonudRead may throw if entrySize was bigger than the buffer or if it
231 // was not able to read all the bytes, let it propagate up the stack
232 std::vector<uint8_t> entry = wraparoundRead(
233 boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr),
234 entrySize);
235
236 // Calculate the checksum
237 uint8_t* entryHeaderPtr = reinterpret_cast<uint8_t*>(&entryHeader);
238 uint8_t checksum =
239 std::accumulate(entryHeaderPtr,
240 entryHeaderPtr + sizeof(struct QueueEntryHeader), 0,
241 std::bit_xor<void>()) ^
242 std::accumulate(entry.begin(), entry.end(), 0, std::bit_xor<void>());
243
244 if (checksum != 0)
245 {
246 throw std::runtime_error(std::format(
247 "[readEntry] Checksum was '{}', expected '0'", checksum));
248 }
249
250 return {entryHeader, entry};
251 }
252
readErrorLogs()253 std::vector<EntryPair> BufferImpl::readErrorLogs()
254 {
255 // Reading the buffer header will update the cachedBufferHeader
256 readBufferHeader();
257
258 const size_t maxOffset = getMaxOffset();
259 size_t currentBiosWritePtr =
260 boost::endian::little_to_native(cachedBufferHeader.biosWritePtr);
261 if (currentBiosWritePtr > maxOffset)
262 {
263 throw std::runtime_error(std::format(
264 "[readErrorLogs] currentBiosWritePtr was '{}' which was bigger "
265 "than maxOffset '{}'",
266 currentBiosWritePtr, maxOffset));
267 }
268 size_t currentReadPtr =
269 boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr);
270 if (currentReadPtr > maxOffset)
271 {
272 throw std::runtime_error(std::format(
273 "[readErrorLogs] currentReadPtr was '{}' which was bigger "
274 "than maxOffset '{}'",
275 currentReadPtr, maxOffset));
276 }
277
278 size_t bytesToRead;
279 if (currentBiosWritePtr == currentReadPtr)
280 {
281 // No new payload was detected, return an empty vector gracefully
282 return {};
283 }
284
285 if (currentBiosWritePtr > currentReadPtr)
286 {
287 // Simply subtract in this case
288 bytesToRead = currentBiosWritePtr - currentReadPtr;
289 }
290 else
291 {
292 // Calculate the bytes to the "end" (maxOffset - ReadPtr) +
293 // bytes to read from the "beginning" (0 + WritePtr)
294 bytesToRead = (maxOffset - currentReadPtr) + currentBiosWritePtr;
295 }
296
297 size_t byteRead = 0;
298 std::vector<EntryPair> entryPairs;
299 while (byteRead < bytesToRead)
300 {
301 EntryPair entryPair = readEntry();
302 byteRead += sizeof(struct QueueEntryHeader) + entryPair.second.size();
303 entryPairs.push_back(entryPair);
304
305 // Note: readEntry() will update cachedBufferHeader.bmcReadPtr
306 currentReadPtr =
307 boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr);
308 }
309 if (currentBiosWritePtr != currentReadPtr)
310 {
311 throw std::runtime_error(std::format(
312 "[readErrorLogs] biosWritePtr '{}' and bmcReaddPtr '{}' "
313 "are not identical after reading through all the logs",
314 currentBiosWritePtr, currentReadPtr));
315 }
316 return entryPairs;
317 }
318
getMaxOffset()319 size_t BufferImpl::getMaxOffset()
320 {
321 size_t queueSize =
322 boost::endian::little_to_native(cachedBufferHeader.queueSize);
323 size_t ueRegionSize =
324 boost::endian::little_to_native(cachedBufferHeader.ueRegionSize);
325
326 if (queueSize != QUEUE_REGION_SIZE)
327 {
328 throw std::runtime_error(std::format(
329 "[{}] runtime queueSize '{}' did not match compile-time queueSize "
330 "'{}'. This indicates that the buffer was corrupted",
331 __FUNCTION__, queueSize, QUEUE_REGION_SIZE));
332 }
333 if (ueRegionSize != UE_REGION_SIZE)
334 {
335 throw std::runtime_error(std::format(
336 "[{}] runtime ueRegionSize '{}' did not match compile-time "
337 "ueRegionSize '{}'. This indicates that the buffer was corrupted",
338 __FUNCTION__, ueRegionSize, UE_REGION_SIZE));
339 }
340
341 return queueSize - ueRegionSize - sizeof(struct CircularBufferHeader);
342 }
343
getQueueOffset()344 size_t BufferImpl::getQueueOffset()
345 {
346 size_t ueRegionSize =
347 boost::endian::little_to_native(cachedBufferHeader.ueRegionSize);
348
349 if (ueRegionSize != UE_REGION_SIZE)
350 {
351 throw std::runtime_error(std::format(
352 "[{}] runtime ueRegionSize '{}' did not match compile-time "
353 "ueRegionSize '{}'. This indicates that the buffer was corrupted",
354 __FUNCTION__, ueRegionSize, UE_REGION_SIZE));
355 }
356 return sizeof(struct CircularBufferHeader) + ueRegionSize;
357 }
358
359 } // namespace bios_bmc_smm_error_logger
360