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>(initializationHeaderPtr,
66                                     initializationHeaderPtr +
67                                         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 = dataInterface->read(/*offset=*/0,
80                                                          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 = offsetof(struct CircularBufferHeader,
101                                                   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 = offsetof(struct CircularBufferHeader,
124                                                    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