1 #include "config.h"
2 
3 #include "buffer.hpp"
4 
5 #include "pci_handler.hpp"
6 
7 #include <fmt/format.h>
8 
9 #include <boost/endian/arithmetic.hpp>
10 #include <boost/endian/conversion.hpp>
11 
12 #include <algorithm>
13 #include <array>
14 #include <cstddef>
15 #include <cstdint>
16 #include <memory>
17 #include <numeric>
18 #include <span>
19 #include <vector>
20 
21 namespace bios_bmc_smm_error_logger
22 {
23 
24 BufferImpl::BufferImpl(std::unique_ptr<DataInterface> dataInterface) :
25     dataInterface(std::move(dataInterface)){};
26 
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(fmt::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             fmt::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>(initializationHeaderPtr,
67                                     initializationHeaderPtr +
68                                         initializationHeaderSize));
69     if (byteWritten != initializationHeaderSize)
70     {
71         throw std::runtime_error(fmt::format(
72             "[initialize] Only wrote '{}' bytes of the header", byteWritten));
73     }
74     cachedBufferHeader = initializationHeader;
75 }
76 
77 void BufferImpl::readBufferHeader()
78 {
79     size_t headerSize = sizeof(struct CircularBufferHeader);
80     std::vector<uint8_t> bytesRead = dataInterface->read(/*offset=*/0,
81                                                          headerSize);
82 
83     if (bytesRead.size() != headerSize)
84     {
85         throw std::runtime_error(
86             fmt::format("Buffer header read only read '{}', expected '{}'",
87                         bytesRead.size(), headerSize));
88     }
89 
90     cachedBufferHeader =
91         *reinterpret_cast<struct CircularBufferHeader*>(bytesRead.data());
92 };
93 
94 struct CircularBufferHeader BufferImpl::getCachedBufferHeader() const
95 {
96     return cachedBufferHeader;
97 }
98 
99 void BufferImpl::updateReadPtr(const uint32_t newReadPtr)
100 {
101     constexpr uint8_t bmcReadPtrOffset = offsetof(struct CircularBufferHeader,
102                                                   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(fmt::format(
116             "[updateReadPtr] Wrote '{}' bytes, instead of expected '{}'",
117             writtenSize, sizeof(truncatedReadPtr)));
118     }
119     cachedBufferHeader.bmcReadPtr = truncatedReadPtr;
120 }
121 
122 void BufferImpl::updateBmcFlags(const uint32_t newBmcFlag)
123 {
124     constexpr uint8_t bmcFlagsPtrOffset = offsetof(struct CircularBufferHeader,
125                                                    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(fmt::format(
139             "[updateBmcFlags] Wrote '{}' bytes, instead of expected '{}'",
140             writtenSize, sizeof(little_uint32_t)));
141     }
142     cachedBufferHeader.bmcFlags = littleNewBmcFlag;
143 }
144 
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             fmt::format("[wraparoundRead] relativeOffset '{}' was bigger "
154                         "than maxOffset '{}'",
155                         relativeOffset, maxOffset));
156     }
157     if (length > maxOffset)
158     {
159         throw std::runtime_error(fmt::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             fmt::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(fmt::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 
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 
226 EntryPair BufferImpl::readEntry()
227 {
228     struct QueueEntryHeader entryHeader = readEntryHeader();
229     size_t entrySize = boost::endian::little_to_native(entryHeader.entrySize);
230 
231     // wraparonudRead may throw if entrySize was bigger than the buffer or if it
232     // was not able to read all the bytes, let it propagate up the stack
233     std::vector<uint8_t> entry = wraparoundRead(
234         boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr),
235         entrySize);
236 
237     // Calculate the checksum
238     uint8_t* entryHeaderPtr = reinterpret_cast<uint8_t*>(&entryHeader);
239     uint8_t checksum =
240         std::accumulate(entryHeaderPtr,
241                         entryHeaderPtr + sizeof(struct QueueEntryHeader), 0,
242                         std::bit_xor<void>()) ^
243         std::accumulate(entry.begin(), entry.end(), 0, std::bit_xor<void>());
244 
245     if (checksum != 0)
246     {
247         throw std::runtime_error(fmt::format(
248             "[readEntry] Checksum was '{}', expected '0'", checksum));
249     }
250 
251     return {entryHeader, entry};
252 }
253 
254 std::vector<EntryPair> BufferImpl::readErrorLogs()
255 {
256     // Reading the buffer header will update the cachedBufferHeader
257     readBufferHeader();
258 
259     const size_t maxOffset = getMaxOffset();
260     size_t currentBiosWritePtr =
261         boost::endian::little_to_native(cachedBufferHeader.biosWritePtr);
262     if (currentBiosWritePtr > maxOffset)
263     {
264         throw std::runtime_error(fmt::format(
265             "[readErrorLogs] currentBiosWritePtr was '{}' which was bigger "
266             "than maxOffset '{}'",
267             currentBiosWritePtr, maxOffset));
268     }
269     size_t currentReadPtr =
270         boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr);
271     if (currentReadPtr > maxOffset)
272     {
273         throw std::runtime_error(fmt::format(
274             "[readErrorLogs] currentReadPtr was '{}' which was bigger "
275             "than maxOffset '{}'",
276             currentReadPtr, maxOffset));
277     }
278 
279     size_t bytesToRead;
280     if (currentBiosWritePtr == currentReadPtr)
281     {
282         // No new payload was detected, return an empty vector gracefully
283         return {};
284     }
285 
286     if (currentBiosWritePtr > currentReadPtr)
287     {
288         // Simply subtract in this case
289         bytesToRead = currentBiosWritePtr - currentReadPtr;
290     }
291     else
292     {
293         // Calculate the bytes to the "end" (maxOffset - ReadPtr) +
294         // bytes to read from the "beginning" (0 +  WritePtr)
295         bytesToRead = (maxOffset - currentReadPtr) + currentBiosWritePtr;
296     }
297 
298     size_t byteRead = 0;
299     std::vector<EntryPair> entryPairs;
300     while (byteRead < bytesToRead)
301     {
302         EntryPair entryPair = readEntry();
303         byteRead += sizeof(struct QueueEntryHeader) + entryPair.second.size();
304         entryPairs.push_back(entryPair);
305 
306         // Note: readEntry() will update cachedBufferHeader.bmcReadPtr
307         currentReadPtr =
308             boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr);
309     }
310     if (currentBiosWritePtr != currentReadPtr)
311     {
312         throw std::runtime_error(fmt::format(
313             "[readErrorLogs] biosWritePtr '{}' and bmcReaddPtr '{}' "
314             "are not identical after reading through all the logs",
315             currentBiosWritePtr, currentReadPtr));
316     }
317     return entryPairs;
318 }
319 
320 size_t BufferImpl::getMaxOffset()
321 {
322     size_t queueSize =
323         boost::endian::little_to_native(cachedBufferHeader.queueSize);
324     size_t ueRegionSize =
325         boost::endian::little_to_native(cachedBufferHeader.ueRegionSize);
326 
327     if (queueSize != QUEUE_REGION_SIZE)
328     {
329         throw std::runtime_error(fmt::format(
330             "[{}] runtime queueSize '{}' did not match compile-time queueSize "
331             "'{}'. This indicates that the buffer was corrupted",
332             __FUNCTION__, queueSize, QUEUE_REGION_SIZE));
333     }
334     if (ueRegionSize != UE_REGION_SIZE)
335     {
336         throw std::runtime_error(fmt::format(
337             "[{}] runtime ueRegionSize '{}' did not match compile-time "
338             "ueRegionSize '{}'. This indicates that the buffer was corrupted",
339             __FUNCTION__, ueRegionSize, UE_REGION_SIZE));
340     }
341 
342     return queueSize - ueRegionSize - sizeof(struct CircularBufferHeader);
343 }
344 
345 size_t BufferImpl::getQueueOffset()
346 {
347     size_t ueRegionSize =
348         boost::endian::little_to_native(cachedBufferHeader.ueRegionSize);
349 
350     if (ueRegionSize != UE_REGION_SIZE)
351     {
352         throw std::runtime_error(fmt::format(
353             "[{}] runtime ueRegionSize '{}' did not match compile-time "
354             "ueRegionSize '{}'. This indicates that the buffer was corrupted",
355             __FUNCTION__, ueRegionSize, UE_REGION_SIZE));
356     }
357     return sizeof(struct CircularBufferHeader) + ueRegionSize;
358 }
359 
360 } // namespace bios_bmc_smm_error_logger
361