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     const size_t proposedBufferSize =
31         sizeof(struct CircularBufferHeader) + ueRegionSize + queueSize;
32     if (proposedBufferSize > memoryRegionSize)
33     {
34         throw std::runtime_error(fmt::format(
35             "[initialize] Proposed region size '{}' is bigger than the "
36             "BMC's allocated MMIO region of '{}'",
37             proposedBufferSize, memoryRegionSize));
38     }
39 
40     // Initialize the whole buffer with 0x00
41     const std::vector<uint8_t> emptyVector(proposedBufferSize, 0);
42     size_t byteWritten = dataInterface->write(0, emptyVector);
43     if (byteWritten != proposedBufferSize)
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 =
81         dataInterface->read(/*offset=*/0, 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 =
102         offsetof(struct CircularBufferHeader, bmcReadPtr);
103 
104     little_uint16_t truncatedReadPtr =
105         boost::endian::native_to_little(newReadPtr & 0xffff);
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(little_uint16_t)});
113     if (writtenSize != sizeof(little_uint16_t))
114     {
115         throw std::runtime_error(fmt::format(
116             "[updateReadPtr] Wrote '{}' bytes, instead of expected '{}'",
117             writtenSize, sizeof(little_uint16_t)));
118     }
119     cachedBufferHeader.bmcReadPtr = truncatedReadPtr;
120 }
121 
122 size_t BufferImpl::getQueueOffset()
123 {
124     return sizeof(struct CircularBufferHeader) +
125            boost::endian::little_to_native(cachedBufferHeader.ueRegionSize);
126 }
127 
128 std::vector<uint8_t> BufferImpl::wraparoundRead(const uint32_t relativeOffset,
129                                                 const uint32_t length)
130 {
131     const size_t queueSize =
132         boost::endian::little_to_native(cachedBufferHeader.queueSize);
133 
134     if (relativeOffset > queueSize)
135     {
136         throw std::runtime_error(
137             fmt::format("[wraparoundRead] relativeOffset '{}' was bigger "
138                         "than queueSize '{}'",
139                         relativeOffset, queueSize));
140     }
141     if (length > queueSize)
142     {
143         throw std::runtime_error(fmt::format(
144             "[wraparoundRead] length '{}' was bigger than queueSize '{}'",
145             length, queueSize));
146     }
147 
148     // Do a calculation to see if the read will wraparound
149     const size_t queueOffset = getQueueOffset();
150     const size_t queueSizeToQueueEnd = queueSize - relativeOffset;
151     size_t numWraparoundBytesToRead = 0;
152     if (length > queueSizeToQueueEnd)
153     {
154         // This means we will wrap, count the bytes that are left to read
155         numWraparoundBytesToRead = length - queueSizeToQueueEnd;
156     }
157     const size_t numBytesToReadTillQueueEnd = length - numWraparoundBytesToRead;
158 
159     std::vector<uint8_t> bytesRead = dataInterface->read(
160         queueOffset + relativeOffset, numBytesToReadTillQueueEnd);
161     if (bytesRead.size() != numBytesToReadTillQueueEnd)
162     {
163         throw std::runtime_error(
164             fmt::format("[wraparoundRead] Read '{}' which was not "
165                         "the requested length of '{}'",
166                         bytesRead.size(), numBytesToReadTillQueueEnd));
167     }
168     size_t updatedReadPtr = relativeOffset + numBytesToReadTillQueueEnd;
169     if (updatedReadPtr == queueSize)
170     {
171         // If we read all the way up to the end of the queue, we need to
172         // manually wrap the updateReadPtr around to 0
173         updatedReadPtr = 0;
174     }
175 
176     // If there are any more bytes to be read beyond the buffer, wrap around and
177     // read from the beginning of the buffer (offset by the queueOffset)
178     if (numWraparoundBytesToRead > 0)
179     {
180         std::vector<uint8_t> wrappedBytesRead =
181             dataInterface->read(queueOffset, numWraparoundBytesToRead);
182         if (numWraparoundBytesToRead != wrappedBytesRead.size())
183         {
184             throw std::runtime_error(fmt::format(
185                 "[wraparoundRead] Buffer wrapped around but read '{}' which "
186                 "was not the requested lenght of '{}'",
187                 wrappedBytesRead.size(), numWraparoundBytesToRead));
188         }
189         bytesRead.insert(bytesRead.end(), wrappedBytesRead.begin(),
190                          wrappedBytesRead.end());
191         updatedReadPtr = numWraparoundBytesToRead;
192     }
193     updateReadPtr(updatedReadPtr);
194 
195     return bytesRead;
196 }
197 
198 struct QueueEntryHeader BufferImpl::readEntryHeader(size_t relativeOffset)
199 {
200     size_t headerSize = sizeof(struct QueueEntryHeader);
201     // wraparonudRead will throw if it did not read all the bytes, let it
202     // propagate up the stack
203     std::vector<uint8_t> bytesRead = wraparoundRead(relativeOffset, headerSize);
204 
205     return *reinterpret_cast<struct QueueEntryHeader*>(bytesRead.data());
206 }
207 
208 EntryPair BufferImpl::readEntry(size_t relativeOffset)
209 {
210     struct QueueEntryHeader entryHeader = readEntryHeader(relativeOffset);
211     size_t entrySize = boost::endian::little_to_native(entryHeader.entrySize);
212 
213     // wraparonudRead may throw if entrySize was bigger than the buffer or if it
214     // was not able to read all the bytes, let it propagate up the stack
215     std::vector<uint8_t> entry = wraparoundRead(
216         relativeOffset + sizeof(struct QueueEntryHeader), entrySize);
217 
218     // Calculate the checksum
219     uint8_t* entryHeaderPtr = reinterpret_cast<uint8_t*>(&entryHeader);
220     uint8_t checksum =
221         std::accumulate(entryHeaderPtr,
222                         entryHeaderPtr + sizeof(struct QueueEntryHeader), 0,
223                         std::bit_xor<void>()) ^
224         std::accumulate(entry.begin(), entry.end(), 0, std::bit_xor<void>());
225 
226     if (checksum != 0)
227     {
228         throw std::runtime_error(fmt::format(
229             "[readEntry] Checksum was '{}', expected '0'", checksum));
230     }
231 
232     return {entryHeader, entry};
233 }
234 
235 std::vector<EntryPair> BufferImpl::readErrorLogs()
236 {
237     // Reading the buffer header will update the cachedBufferHeader
238     readBufferHeader();
239 
240     const size_t queueSize =
241         boost::endian::little_to_native(cachedBufferHeader.queueSize);
242     size_t currentBiosWritePtr =
243         boost::endian::little_to_native(cachedBufferHeader.biosWritePtr);
244     if (currentBiosWritePtr > queueSize)
245     {
246         throw std::runtime_error(fmt::format(
247             "[readErrorLogs] currentBiosWritePtr was '{}' which was bigger "
248             "than queueSize '{}'",
249             currentBiosWritePtr, queueSize));
250     }
251     size_t currentReadPtr =
252         boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr);
253     if (currentReadPtr > queueSize)
254     {
255         throw std::runtime_error(fmt::format(
256             "[readErrorLogs] currentReadPtr was '{}' which was bigger "
257             "than queueSize '{}'",
258             currentReadPtr, queueSize));
259     }
260 
261     size_t bytesToRead;
262     if (currentBiosWritePtr == currentReadPtr)
263     {
264         // No new payload was detected, return an empty vector gracefully
265         return {};
266     }
267 
268     if (currentBiosWritePtr > currentReadPtr)
269     {
270         // Simply subtract in this case
271         bytesToRead = currentBiosWritePtr - currentReadPtr;
272     }
273     else
274     {
275         // Calculate the bytes to the "end" (QueueSize - ReadPtr) +
276         // bytes to read from the "beginning" (0 +  WritePtr)
277         bytesToRead = (queueSize - currentReadPtr) + currentBiosWritePtr;
278     }
279 
280     size_t byteRead = 0;
281     std::vector<EntryPair> entryPairs;
282     while (byteRead < bytesToRead)
283     {
284         EntryPair entryPair = readEntry(currentReadPtr);
285         byteRead += sizeof(struct QueueEntryHeader) + entryPair.second.size();
286         entryPairs.push_back(entryPair);
287 
288         // Note: readEntry() will update cachedBufferHeader.bmcReadPtr
289         currentReadPtr =
290             boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr);
291     }
292     if (currentBiosWritePtr != currentReadPtr)
293     {
294         throw std::runtime_error(fmt::format(
295             "[readErrorLogs] biosWritePtr '{}' and bmcReaddPtr '{}' "
296             "are not identical after reading through all the logs",
297             currentBiosWritePtr, currentReadPtr));
298     }
299     return entryPairs;
300 }
301 
302 } // namespace bios_bmc_smm_error_logger
303