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