blob: b38862e13618e6e92608d0303e40edf677f7e070 [file] [log] [blame]
Brandon Kimfcbc3db2022-06-06 21:26:18 -07001#include "buffer.hpp"
2
3#include "pci_handler.hpp"
4
5#include <fmt/format.h>
6
Brandon Kim17ee1a92022-06-07 21:04:08 -07007#include <boost/endian/arithmetic.hpp>
8#include <boost/endian/conversion.hpp>
9
10#include <algorithm>
Brandon Kimfcbc3db2022-06-06 21:26:18 -070011#include <array>
Brandon Kimcf0b9752022-06-15 10:32:21 -070012#include <cstddef>
Brandon Kimfcbc3db2022-06-06 21:26:18 -070013#include <cstdint>
14#include <memory>
Brandon Kim40ce08e2022-06-15 16:58:44 -070015#include <numeric>
Brandon Kimfcbc3db2022-06-06 21:26:18 -070016#include <span>
17#include <vector>
18
19namespace bios_bmc_smm_error_logger
20{
21
22BufferImpl::BufferImpl(std::unique_ptr<DataInterface> dataInterface) :
23 dataInterface(std::move(dataInterface)){};
24
25void BufferImpl::initialize(uint32_t bmcInterfaceVersion, uint16_t queueSize,
26 uint16_t ueRegionSize,
27 const std::array<uint32_t, 4>& magicNumber)
28{
Brandon Kimfcbc3db2022-06-06 21:26:18 -070029 const size_t memoryRegionSize = dataInterface->getMemoryRegionSize();
Brandon Kim3def3c82022-09-13 05:29:20 +000030 if (queueSize > memoryRegionSize)
Brandon Kim26660e92022-06-15 16:58:44 -070031 {
32 throw std::runtime_error(fmt::format(
Brandon Kim3def3c82022-09-13 05:29:20 +000033 "[initialize] Proposed queue size '{}' is bigger than the "
Brandon Kim26660e92022-06-15 16:58:44 -070034 "BMC's allocated MMIO region of '{}'",
Brandon Kim3def3c82022-09-13 05:29:20 +000035 queueSize, memoryRegionSize));
Brandon Kim26660e92022-06-15 16:58:44 -070036 }
37
38 // Initialize the whole buffer with 0x00
Brandon Kim3def3c82022-09-13 05:29:20 +000039 const std::vector<uint8_t> emptyVector(queueSize, 0);
Brandon Kimfcbc3db2022-06-06 21:26:18 -070040 size_t byteWritten = dataInterface->write(0, emptyVector);
Brandon Kim3def3c82022-09-13 05:29:20 +000041 if (byteWritten != queueSize)
Brandon Kimfcbc3db2022-06-06 21:26:18 -070042 {
43 throw std::runtime_error(
Brandon Kim26660e92022-06-15 16:58:44 -070044 fmt::format("[initialize] Only erased '{}'", byteWritten));
Brandon Kimfcbc3db2022-06-06 21:26:18 -070045 }
46
47 // Create an initial buffer header and write to it
48 struct CircularBufferHeader initializationHeader = {};
Brandon Kim17ee1a92022-06-07 21:04:08 -070049 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 });
Brandon Kimfcbc3db2022-06-06 21:26:18 -070059
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(
Brandon Kim26660e92022-06-15 16:58:44 -070070 "[initialize] Only wrote '{}' bytes of the header", byteWritten));
Brandon Kimfcbc3db2022-06-06 21:26:18 -070071 }
Brandon Kim60cab572022-06-15 14:20:05 -070072 cachedBufferHeader = initializationHeader;
Brandon Kimfcbc3db2022-06-06 21:26:18 -070073}
74
Brandon Kim17ee1a92022-06-07 21:04:08 -070075void BufferImpl::readBufferHeader()
76{
77 size_t headerSize = sizeof(struct CircularBufferHeader);
Patrick Williamsb8125762023-05-10 07:51:26 -050078 std::vector<uint8_t> bytesRead = dataInterface->read(/*offset=*/0,
79 headerSize);
Brandon Kim17ee1a92022-06-07 21:04:08 -070080
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 =
Brandon Kim60cab572022-06-15 14:20:05 -070089 *reinterpret_cast<struct CircularBufferHeader*>(bytesRead.data());
Brandon Kim17ee1a92022-06-07 21:04:08 -070090};
91
92struct CircularBufferHeader BufferImpl::getCachedBufferHeader() const
93{
94 return cachedBufferHeader;
95}
96
Brandon Kimcf0b9752022-06-15 10:32:21 -070097void BufferImpl::updateReadPtr(const uint32_t newReadPtr)
98{
Patrick Williamsb8125762023-05-10 07:51:26 -050099 constexpr uint8_t bmcReadPtrOffset = offsetof(struct CircularBufferHeader,
100 bmcReadPtr);
Brandon Kimcf0b9752022-06-15 10:32:21 -0700101
Brandon Kim271d2312022-09-02 16:34:55 +0000102 little_uint24_t truncatedReadPtr =
103 boost::endian::native_to_little(newReadPtr & 0xffffff);
Brandon Kimcf0b9752022-06-15 10:32:21 -0700104 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,
Brandon Kim271d2312022-09-02 16:34:55 +0000110 truncatedReadPtrPtr + sizeof(truncatedReadPtr)});
111 if (writtenSize != sizeof(truncatedReadPtr))
Brandon Kimcf0b9752022-06-15 10:32:21 -0700112 {
113 throw std::runtime_error(fmt::format(
114 "[updateReadPtr] Wrote '{}' bytes, instead of expected '{}'",
Brandon Kim271d2312022-09-02 16:34:55 +0000115 writtenSize, sizeof(truncatedReadPtr)));
Brandon Kimcf0b9752022-06-15 10:32:21 -0700116 }
117 cachedBufferHeader.bmcReadPtr = truncatedReadPtr;
118}
119
Brandon Kimc49284b2022-06-17 09:55:26 -0700120void BufferImpl::updateBmcFlags(const uint32_t newBmcFlag)
121{
Patrick Williamsb8125762023-05-10 07:51:26 -0500122 constexpr uint8_t bmcFlagsPtrOffset = offsetof(struct CircularBufferHeader,
123 bmcFlags);
Brandon Kimc49284b2022-06-17 09:55:26 -0700124
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
Brandon Kim9836cfa2022-06-15 11:21:11 -0700143size_t BufferImpl::getQueueOffset()
144{
145 return sizeof(struct CircularBufferHeader) +
146 boost::endian::little_to_native(cachedBufferHeader.ueRegionSize);
147}
148
Brandon Kim35d43352022-06-16 11:13:36 -0700149std::vector<uint8_t> BufferImpl::wraparoundRead(const uint32_t relativeOffset,
150 const uint32_t length)
Brandon Kim9836cfa2022-06-15 11:21:11 -0700151{
Brandon Kim3def3c82022-09-13 05:29:20 +0000152 const size_t maxOffset = getMaxOffset();
Brandon Kim9836cfa2022-06-15 11:21:11 -0700153
Brandon Kim3def3c82022-09-13 05:29:20 +0000154 if (relativeOffset > maxOffset)
Brandon Kim35d43352022-06-16 11:13:36 -0700155 {
156 throw std::runtime_error(
157 fmt::format("[wraparoundRead] relativeOffset '{}' was bigger "
Brandon Kim3def3c82022-09-13 05:29:20 +0000158 "than maxOffset '{}'",
159 relativeOffset, maxOffset));
Brandon Kim35d43352022-06-16 11:13:36 -0700160 }
Brandon Kim3def3c82022-09-13 05:29:20 +0000161 if (length > maxOffset)
Brandon Kim9836cfa2022-06-15 11:21:11 -0700162 {
163 throw std::runtime_error(fmt::format(
Brandon Kim3def3c82022-09-13 05:29:20 +0000164 "[wraparoundRead] length '{}' was bigger than maxOffset '{}'",
165 length, maxOffset));
Brandon Kim9836cfa2022-06-15 11:21:11 -0700166 }
167
Brandon Kim35d43352022-06-16 11:13:36 -0700168 // Do a calculation to see if the read will wraparound
169 const size_t queueOffset = getQueueOffset();
Brandon Kim3def3c82022-09-13 05:29:20 +0000170 const size_t writableSpace = maxOffset - relativeOffset;
Brandon Kim35d43352022-06-16 11:13:36 -0700171 size_t numWraparoundBytesToRead = 0;
Brandon Kim3def3c82022-09-13 05:29:20 +0000172 if (length > writableSpace)
Brandon Kim35d43352022-06-16 11:13:36 -0700173 {
174 // This means we will wrap, count the bytes that are left to read
Brandon Kim3def3c82022-09-13 05:29:20 +0000175 numWraparoundBytesToRead = length - writableSpace;
Brandon Kim35d43352022-06-16 11:13:36 -0700176 }
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;
Brandon Kim3def3c82022-09-13 05:29:20 +0000189 if (updatedReadPtr == maxOffset)
Brandon Kim4662b1b2022-06-16 21:38:02 -0700190 {
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 }
Brandon Kim9836cfa2022-06-15 11:21:11 -0700195
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)
Brandon Kim35d43352022-06-16 11:13:36 -0700198 if (numWraparoundBytesToRead > 0)
Brandon Kim9836cfa2022-06-15 11:21:11 -0700199 {
200 std::vector<uint8_t> wrappedBytesRead =
Brandon Kim35d43352022-06-16 11:13:36 -0700201 dataInterface->read(queueOffset, numWraparoundBytesToRead);
202 if (numWraparoundBytesToRead != wrappedBytesRead.size())
Brandon Kim9836cfa2022-06-15 11:21:11 -0700203 {
204 throw std::runtime_error(fmt::format(
Brandon Kim35d43352022-06-16 11:13:36 -0700205 "[wraparoundRead] Buffer wrapped around but read '{}' which "
206 "was not the requested lenght of '{}'",
207 wrappedBytesRead.size(), numWraparoundBytesToRead));
Brandon Kim9836cfa2022-06-15 11:21:11 -0700208 }
209 bytesRead.insert(bytesRead.end(), wrappedBytesRead.begin(),
210 wrappedBytesRead.end());
Brandon Kim35d43352022-06-16 11:13:36 -0700211 updatedReadPtr = numWraparoundBytesToRead;
Brandon Kim9836cfa2022-06-15 11:21:11 -0700212 }
Brandon Kim35d43352022-06-16 11:13:36 -0700213 updateReadPtr(updatedReadPtr);
Brandon Kim9836cfa2022-06-15 11:21:11 -0700214
215 return bytesRead;
216}
217
Brandon Kim613ba532022-09-12 20:55:21 +0000218struct QueueEntryHeader BufferImpl::readEntryHeader()
Brandon Kim7bac2d62022-06-07 21:37:51 -0700219{
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
Brandon Kim613ba532022-09-12 20:55:21 +0000223 std::vector<uint8_t> bytesRead = wraparoundRead(
224 boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr),
225 headerSize);
Brandon Kim7bac2d62022-06-07 21:37:51 -0700226
227 return *reinterpret_cast<struct QueueEntryHeader*>(bytesRead.data());
228}
229
Brandon Kim613ba532022-09-12 20:55:21 +0000230EntryPair BufferImpl::readEntry()
Brandon Kim40ce08e2022-06-15 16:58:44 -0700231{
Brandon Kim613ba532022-09-12 20:55:21 +0000232 struct QueueEntryHeader entryHeader = readEntryHeader();
Brandon Kim35d43352022-06-16 11:13:36 -0700233 size_t entrySize = boost::endian::little_to_native(entryHeader.entrySize);
Brandon Kim40ce08e2022-06-15 16:58:44 -0700234
235 // wraparonudRead may throw if entrySize was bigger than the buffer or if it
Brandon Kim26660e92022-06-15 16:58:44 -0700236 // was not able to read all the bytes, let it propagate up the stack
Brandon Kim35d43352022-06-16 11:13:36 -0700237 std::vector<uint8_t> entry = wraparoundRead(
Brandon Kim613ba532022-09-12 20:55:21 +0000238 boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr),
239 entrySize);
Brandon Kim40ce08e2022-06-15 16:58:44 -0700240
241 // Calculate the checksum
242 uint8_t* entryHeaderPtr = reinterpret_cast<uint8_t*>(&entryHeader);
Brandon Kimf0e4adc2022-06-16 23:14:25 -0700243 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
Brandon Kim40ce08e2022-06-15 16:58:44 -0700249 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
Brandon Kim4662b1b2022-06-16 21:38:02 -0700258std::vector<EntryPair> BufferImpl::readErrorLogs()
259{
260 // Reading the buffer header will update the cachedBufferHeader
261 readBufferHeader();
262
Brandon Kim3def3c82022-09-13 05:29:20 +0000263 const size_t maxOffset = getMaxOffset();
Brandon Kim4662b1b2022-06-16 21:38:02 -0700264 size_t currentBiosWritePtr =
265 boost::endian::little_to_native(cachedBufferHeader.biosWritePtr);
Brandon Kim3def3c82022-09-13 05:29:20 +0000266 if (currentBiosWritePtr > maxOffset)
Brandon Kim4662b1b2022-06-16 21:38:02 -0700267 {
268 throw std::runtime_error(fmt::format(
269 "[readErrorLogs] currentBiosWritePtr was '{}' which was bigger "
Brandon Kim3def3c82022-09-13 05:29:20 +0000270 "than maxOffset '{}'",
271 currentBiosWritePtr, maxOffset));
Brandon Kim4662b1b2022-06-16 21:38:02 -0700272 }
273 size_t currentReadPtr =
274 boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr);
Brandon Kim3def3c82022-09-13 05:29:20 +0000275 if (currentReadPtr > maxOffset)
Brandon Kim4662b1b2022-06-16 21:38:02 -0700276 {
277 throw std::runtime_error(fmt::format(
278 "[readErrorLogs] currentReadPtr was '{}' which was bigger "
Brandon Kim3def3c82022-09-13 05:29:20 +0000279 "than maxOffset '{}'",
280 currentReadPtr, maxOffset));
Brandon Kim4662b1b2022-06-16 21:38:02 -0700281 }
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 {
Brandon Kim3def3c82022-09-13 05:29:20 +0000297 // Calculate the bytes to the "end" (maxOffset - ReadPtr) +
Brandon Kim4662b1b2022-06-16 21:38:02 -0700298 // bytes to read from the "beginning" (0 + WritePtr)
Brandon Kim3def3c82022-09-13 05:29:20 +0000299 bytesToRead = (maxOffset - currentReadPtr) + currentBiosWritePtr;
Brandon Kim4662b1b2022-06-16 21:38:02 -0700300 }
301
302 size_t byteRead = 0;
303 std::vector<EntryPair> entryPairs;
304 while (byteRead < bytesToRead)
305 {
Brandon Kim613ba532022-09-12 20:55:21 +0000306 EntryPair entryPair = readEntry();
Brandon Kim4662b1b2022-06-16 21:38:02 -0700307 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
Brandon Kim3def3c82022-09-13 05:29:20 +0000324size_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
Brandon Kimfcbc3db2022-06-06 21:26:18 -0700334} // namespace bios_bmc_smm_error_logger