xref: /openbmc/bios-bmc-smm-error-logger/src/buffer.cpp (revision d49db6ffa0013a333a33be2b48f0a422d4054c03)
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