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 #include <stdplus/print.hpp>
10
11 #include <algorithm>
12 #include <array>
13 #include <cstddef>
14 #include <cstdint>
15 #include <format>
16 #include <memory>
17 #include <numeric>
18 #include <span>
19 #include <vector>
20
21 namespace bios_bmc_smm_error_logger
22 {
23
BufferImpl(std::unique_ptr<DataInterface> dataInterface)24 BufferImpl::BufferImpl(std::unique_ptr<DataInterface> dataInterface) :
25 dataInterface(std::move(dataInterface)) {};
26
initialize(uint32_t bmcInterfaceVersion,uint16_t queueSize,uint16_t ueRegionSize,const std::array<uint32_t,4> & magicNumber)27 void BufferImpl::initialize(uint32_t bmcInterfaceVersion, uint16_t queueSize,
28 uint16_t ueRegionSize,
29 const std::array<uint32_t, 4>& magicNumber)
30 {
31 const size_t memoryRegionSize = dataInterface->getMemoryRegionSize();
32 if (queueSize > memoryRegionSize)
33 {
34 throw std::runtime_error(std::format(
35 "[initialize] Proposed queue size '{}' is bigger than the "
36 "BMC's allocated MMIO region of '{}'",
37 queueSize, memoryRegionSize));
38 }
39
40 // Initialize the whole buffer with 0x00
41 const std::vector<uint8_t> emptyVector(queueSize, 0);
42 size_t byteWritten = dataInterface->write(0, emptyVector);
43 if (byteWritten != queueSize)
44 {
45 throw std::runtime_error(
46 std::format("[initialize] Only erased '{}'", byteWritten));
47 }
48
49 // Create an initial buffer header and write to it
50 struct CircularBufferHeader initializationHeader = {};
51 initializationHeader.bmcInterfaceVersion =
52 boost::endian::native_to_little(bmcInterfaceVersion);
53 initializationHeader.queueSize = boost::endian::native_to_little(queueSize);
54 initializationHeader.ueRegionSize =
55 boost::endian::native_to_little(ueRegionSize);
56 std::transform(magicNumber.begin(), magicNumber.end(),
57 initializationHeader.magicNumber.begin(),
58 [](uint32_t number) -> little_uint32_t {
59 return boost::endian::native_to_little(number);
60 });
61
62 uint8_t* initializationHeaderPtr =
63 reinterpret_cast<uint8_t*>(&initializationHeader);
64 size_t initializationHeaderSize = sizeof(initializationHeader);
65 byteWritten = dataInterface->write(
66 0, std::span<const uint8_t>(
67 initializationHeaderPtr,
68 initializationHeaderPtr + initializationHeaderSize));
69 if (byteWritten != initializationHeaderSize)
70 {
71 throw std::runtime_error(std::format(
72 "[initialize] Only wrote '{}' bytes of the header", byteWritten));
73 }
74 cachedBufferHeader = initializationHeader;
75 }
76
readBufferHeader()77 void BufferImpl::readBufferHeader()
78 {
79 size_t headerSize = sizeof(struct CircularBufferHeader);
80 std::vector<uint8_t> bytesRead =
81 dataInterface->read(/*offset=*/0, headerSize);
82
83 if (bytesRead.size() != headerSize)
84 {
85 throw std::runtime_error(
86 std::format("Buffer header read only read '{}', expected '{}'",
87 bytesRead.size(), headerSize));
88 }
89
90 cachedBufferHeader =
91 *reinterpret_cast<struct CircularBufferHeader*>(bytesRead.data());
92 };
93
getCachedBufferHeader() const94 struct CircularBufferHeader BufferImpl::getCachedBufferHeader() const
95 {
96 return cachedBufferHeader;
97 }
98
updateReadPtr(const uint32_t newReadPtr)99 void BufferImpl::updateReadPtr(const uint32_t newReadPtr)
100 {
101 constexpr uint8_t bmcReadPtrOffset =
102 offsetof(struct CircularBufferHeader, bmcReadPtr);
103
104 little_uint24_t truncatedReadPtr =
105 boost::endian::native_to_little(newReadPtr & 0xffffff);
106 uint8_t* truncatedReadPtrPtr =
107 reinterpret_cast<uint8_t*>(&truncatedReadPtr);
108
109 size_t writtenSize = dataInterface->write(
110 bmcReadPtrOffset, std::span<const uint8_t>{
111 truncatedReadPtrPtr,
112 truncatedReadPtrPtr + sizeof(truncatedReadPtr)});
113 if (writtenSize != sizeof(truncatedReadPtr))
114 {
115 throw std::runtime_error(std::format(
116 "[updateReadPtr] Wrote '{}' bytes, instead of expected '{}'",
117 writtenSize, sizeof(truncatedReadPtr)));
118 }
119 cachedBufferHeader.bmcReadPtr = truncatedReadPtr;
120 }
121
updateBmcFlags(const uint32_t newBmcFlag)122 void BufferImpl::updateBmcFlags(const uint32_t newBmcFlag)
123 {
124 constexpr uint8_t bmcFlagsPtrOffset =
125 offsetof(struct CircularBufferHeader, bmcFlags);
126
127 little_uint32_t littleNewBmcFlag =
128 boost::endian::native_to_little(newBmcFlag);
129 uint8_t* littleNewBmcFlagPtr =
130 reinterpret_cast<uint8_t*>(&littleNewBmcFlag);
131
132 size_t writtenSize = dataInterface->write(
133 bmcFlagsPtrOffset, std::span<const uint8_t>{
134 littleNewBmcFlagPtr,
135 littleNewBmcFlagPtr + sizeof(little_uint32_t)});
136 if (writtenSize != sizeof(little_uint32_t))
137 {
138 throw std::runtime_error(std::format(
139 "[updateBmcFlags] Wrote '{}' bytes, instead of expected '{}'",
140 writtenSize, sizeof(little_uint32_t)));
141 }
142 cachedBufferHeader.bmcFlags = littleNewBmcFlag;
143 }
144
wraparoundRead(const uint32_t relativeOffset,const uint32_t length)145 std::vector<uint8_t> BufferImpl::wraparoundRead(const uint32_t relativeOffset,
146 const uint32_t length)
147 {
148 const size_t maxOffset = getMaxOffset();
149
150 if (relativeOffset > maxOffset)
151 {
152 throw std::runtime_error(
153 std::format("[wraparoundRead] relativeOffset '{}' was bigger "
154 "than maxOffset '{}'",
155 relativeOffset, maxOffset));
156 }
157 if (length > maxOffset)
158 {
159 throw std::runtime_error(std::format(
160 "[wraparoundRead] length '{}' was bigger than maxOffset '{}'",
161 length, maxOffset));
162 }
163
164 // Do a calculation to see if the read will wraparound
165 const size_t queueOffset = getQueueOffset();
166 const size_t writableSpace = maxOffset - relativeOffset;
167 size_t numWraparoundBytesToRead = 0;
168 if (length > writableSpace)
169 {
170 // This means we will wrap, count the bytes that are left to read
171 numWraparoundBytesToRead = length - writableSpace;
172 }
173 const size_t numBytesToReadTillQueueEnd = length - numWraparoundBytesToRead;
174
175 std::vector<uint8_t> bytesRead = dataInterface->read(
176 queueOffset + relativeOffset, numBytesToReadTillQueueEnd);
177 if (bytesRead.size() != numBytesToReadTillQueueEnd)
178 {
179 throw std::runtime_error(
180 std::format("[wraparoundRead] Read '{}' which was not "
181 "the requested length of '{}'",
182 bytesRead.size(), numBytesToReadTillQueueEnd));
183 }
184 size_t updatedReadPtr = relativeOffset + numBytesToReadTillQueueEnd;
185 if (updatedReadPtr == maxOffset)
186 {
187 // If we read all the way up to the end of the queue, we need to
188 // manually wrap the updateReadPtr around to 0
189 updatedReadPtr = 0;
190 }
191
192 // If there are any more bytes to be read beyond the buffer, wrap around and
193 // read from the beginning of the buffer (offset by the queueOffset)
194 if (numWraparoundBytesToRead > 0)
195 {
196 std::vector<uint8_t> wrappedBytesRead =
197 dataInterface->read(queueOffset, numWraparoundBytesToRead);
198 if (numWraparoundBytesToRead != wrappedBytesRead.size())
199 {
200 throw std::runtime_error(std::format(
201 "[wraparoundRead] Buffer wrapped around but read '{}' which "
202 "was not the requested lenght of '{}'",
203 wrappedBytesRead.size(), numWraparoundBytesToRead));
204 }
205 bytesRead.insert(bytesRead.end(), wrappedBytesRead.begin(),
206 wrappedBytesRead.end());
207 updatedReadPtr = numWraparoundBytesToRead;
208 }
209 updateReadPtr(updatedReadPtr);
210
211 return bytesRead;
212 }
213
readEntryHeader()214 struct QueueEntryHeader BufferImpl::readEntryHeader()
215 {
216 size_t headerSize = sizeof(struct QueueEntryHeader);
217 // wraparonudRead will throw if it did not read all the bytes, let it
218 // propagate up the stack
219 std::vector<uint8_t> bytesRead = wraparoundRead(
220 boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr),
221 headerSize);
222
223 return *reinterpret_cast<struct QueueEntryHeader*>(bytesRead.data());
224 }
225
readUeLogFromReservedRegion()226 std::vector<uint8_t> BufferImpl::readUeLogFromReservedRegion()
227 {
228 // Ensure cachedBufferHeader is up-to-date
229 readBufferHeader();
230
231 uint16_t currentUeRegionSize =
232 boost::endian::little_to_native(cachedBufferHeader.ueRegionSize);
233 if (currentUeRegionSize == 0)
234 {
235 stdplus::print(stderr,
236 "[readUeLogFromReservedRegion] UE Region size is 0\n");
237 return {};
238 }
239
240 uint32_t biosSideFlags =
241 boost::endian::little_to_native(cachedBufferHeader.biosFlags);
242 uint32_t bmcSideFlags =
243 boost::endian::little_to_native(cachedBufferHeader.bmcFlags);
244
245 // (BIOS_switch ^ BMC_switch) & BIT0 == BIT0 -> unread log
246 // This means if the ueSwitch bit differs, there's an unread log.
247 if (!((biosSideFlags ^ bmcSideFlags) &
248 static_cast<uint32_t>(BufferFlags::ueSwitch)))
249 {
250 return {};
251 }
252 // UE log should be present and unread by BMC, read from end of header
253 // (0x30) to the size of the UE region specified in the header.
254 size_t ueRegionOffset = sizeof(struct CircularBufferHeader);
255 std::vector<uint8_t> ueLogData =
256 dataInterface->read(ueRegionOffset, currentUeRegionSize);
257
258 if (ueLogData.size() == currentUeRegionSize)
259 {
260 return ueLogData;
261 }
262 stdplus::print(stderr,
263 "[readUeLogFromReservedRegion] Failed to read "
264 "full UE log. Expected {}, got {}\n",
265 currentUeRegionSize, ueLogData.size());
266 // Throwing an exception allows main loop to handle re-init.
267 throw std::runtime_error(
268 std::format("Failed to read full UE log. Expected {}, got {}",
269 currentUeRegionSize, ueLogData.size()));
270 }
271
checkForOverflowAndAcknowledge()272 bool BufferImpl::checkForOverflowAndAcknowledge()
273 {
274 // Ensure cachedBufferHeader is up-to-date
275 readBufferHeader();
276
277 uint32_t biosSideFlags =
278 boost::endian::little_to_native(cachedBufferHeader.biosFlags);
279 uint32_t bmcSideFlags =
280 boost::endian::little_to_native(cachedBufferHeader.bmcFlags);
281
282 // Design: (BIOS_switch ^ BMC_switch) & BIT1 == BIT1 -> unlogged overflow
283 // This means if the overflow bit differs, there's an
284 // unacknowledged overflow.
285 if ((biosSideFlags ^ bmcSideFlags) &
286 static_cast<uint32_t>(BufferFlags::overflow))
287 {
288 // Overflow incident has occurred and BMC has not acknowledged it.
289 // Toggle BMC's view of the overflow flag to acknowledge.
290 uint32_t newBmcFlags =
291 bmcSideFlags ^ static_cast<uint32_t>(BufferFlags::overflow);
292 updateBmcFlags(newBmcFlags);
293
294 // Overflow was detected and acknowledged
295 return true;
296 }
297
298 // No new overflow incident or already acknowledged
299 return false;
300 }
301
readEntry()302 EntryPair BufferImpl::readEntry()
303 {
304 struct QueueEntryHeader entryHeader = readEntryHeader();
305 size_t entrySize = boost::endian::little_to_native(entryHeader.entrySize);
306
307 // wraparonudRead may throw if entrySize was bigger than the buffer or if it
308 // was not able to read all the bytes, let it propagate up the stack
309 std::vector<uint8_t> entry = wraparoundRead(
310 boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr),
311 entrySize);
312
313 // Calculate the checksum
314 uint8_t* entryHeaderPtr = reinterpret_cast<uint8_t*>(&entryHeader);
315 uint8_t checksum =
316 std::accumulate(entryHeaderPtr,
317 entryHeaderPtr + sizeof(struct QueueEntryHeader), 0,
318 std::bit_xor<void>()) ^
319 std::accumulate(entry.begin(), entry.end(), 0, std::bit_xor<void>());
320
321 if (checksum != 0)
322 {
323 throw std::runtime_error(std::format(
324 "[readEntry] Checksum was '{}', expected '0'", checksum));
325 }
326
327 return {entryHeader, entry};
328 }
329
readErrorLogs()330 std::vector<EntryPair> BufferImpl::readErrorLogs()
331 {
332 // Reading the buffer header will update the cachedBufferHeader
333 readBufferHeader();
334
335 const size_t maxOffset = getMaxOffset();
336 size_t currentBiosWritePtr =
337 boost::endian::little_to_native(cachedBufferHeader.biosWritePtr);
338 if (currentBiosWritePtr > maxOffset)
339 {
340 throw std::runtime_error(std::format(
341 "[readErrorLogs] currentBiosWritePtr was '{}' which was bigger "
342 "than maxOffset '{}'",
343 currentBiosWritePtr, maxOffset));
344 }
345 size_t currentReadPtr =
346 boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr);
347 if (currentReadPtr > maxOffset)
348 {
349 throw std::runtime_error(std::format(
350 "[readErrorLogs] currentReadPtr was '{}' which was bigger "
351 "than maxOffset '{}'",
352 currentReadPtr, maxOffset));
353 }
354
355 size_t bytesToRead;
356 if (currentBiosWritePtr == currentReadPtr)
357 {
358 // No new payload was detected, return an empty vector gracefully
359 return {};
360 }
361
362 if (currentBiosWritePtr > currentReadPtr)
363 {
364 // Simply subtract in this case
365 bytesToRead = currentBiosWritePtr - currentReadPtr;
366 }
367 else
368 {
369 // Calculate the bytes to the "end" (maxOffset - ReadPtr) +
370 // bytes to read from the "beginning" (0 + WritePtr)
371 bytesToRead = (maxOffset - currentReadPtr) + currentBiosWritePtr;
372 }
373
374 size_t byteRead = 0;
375 std::vector<EntryPair> entryPairs;
376 while (byteRead < bytesToRead)
377 {
378 EntryPair entryPair = readEntry();
379 byteRead += sizeof(struct QueueEntryHeader) + entryPair.second.size();
380 entryPairs.push_back(entryPair);
381
382 // Note: readEntry() will update cachedBufferHeader.bmcReadPtr
383 currentReadPtr =
384 boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr);
385 }
386 if (currentBiosWritePtr != currentReadPtr)
387 {
388 throw std::runtime_error(std::format(
389 "[readErrorLogs] biosWritePtr '{}' and bmcReaddPtr '{}' "
390 "are not identical after reading through all the logs",
391 currentBiosWritePtr, currentReadPtr));
392 }
393 return entryPairs;
394 }
395
getMaxOffset()396 size_t BufferImpl::getMaxOffset()
397 {
398 size_t queueSize =
399 boost::endian::little_to_native(cachedBufferHeader.queueSize);
400 size_t ueRegionSize =
401 boost::endian::little_to_native(cachedBufferHeader.ueRegionSize);
402
403 if (queueSize != QUEUE_REGION_SIZE)
404 {
405 throw std::runtime_error(std::format(
406 "[{}] runtime queueSize '{}' did not match compile-time queueSize "
407 "'{}'. This indicates that the buffer was corrupted",
408 __FUNCTION__, queueSize, QUEUE_REGION_SIZE));
409 }
410 if (ueRegionSize != UE_REGION_SIZE)
411 {
412 throw std::runtime_error(std::format(
413 "[{}] runtime ueRegionSize '{}' did not match compile-time "
414 "ueRegionSize '{}'. This indicates that the buffer was corrupted",
415 __FUNCTION__, ueRegionSize, UE_REGION_SIZE));
416 }
417
418 return queueSize - ueRegionSize - sizeof(struct CircularBufferHeader);
419 }
420
getQueueOffset()421 size_t BufferImpl::getQueueOffset()
422 {
423 size_t ueRegionSize =
424 boost::endian::little_to_native(cachedBufferHeader.ueRegionSize);
425
426 if (ueRegionSize != UE_REGION_SIZE)
427 {
428 throw std::runtime_error(std::format(
429 "[{}] runtime ueRegionSize '{}' did not match compile-time "
430 "ueRegionSize '{}'. This indicates that the buffer was corrupted",
431 __FUNCTION__, ueRegionSize, UE_REGION_SIZE));
432 }
433 return sizeof(struct CircularBufferHeader) + ueRegionSize;
434 }
435
436 } // namespace bios_bmc_smm_error_logger
437