blob: 5d39f6933bbc12530229ba35dfcbe19f07961717 [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 Kim26660e92022-06-15 16:58:44 -070030 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);
Brandon Kimfcbc3db2022-06-06 21:26:18 -070042 size_t byteWritten = dataInterface->write(0, emptyVector);
Brandon Kim26660e92022-06-15 16:58:44 -070043 if (byteWritten != proposedBufferSize)
Brandon Kimfcbc3db2022-06-06 21:26:18 -070044 {
45 throw std::runtime_error(
Brandon Kim26660e92022-06-15 16:58:44 -070046 fmt::format("[initialize] Only erased '{}'", byteWritten));
Brandon Kimfcbc3db2022-06-06 21:26:18 -070047 }
48
49 // Create an initial buffer header and write to it
50 struct CircularBufferHeader initializationHeader = {};
Brandon Kim17ee1a92022-06-07 21:04:08 -070051 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 });
Brandon Kimfcbc3db2022-06-06 21:26:18 -070061
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(
Brandon Kim26660e92022-06-15 16:58:44 -070072 "[initialize] Only wrote '{}' bytes of the header", byteWritten));
Brandon Kimfcbc3db2022-06-06 21:26:18 -070073 }
Brandon Kim60cab572022-06-15 14:20:05 -070074 cachedBufferHeader = initializationHeader;
Brandon Kimfcbc3db2022-06-06 21:26:18 -070075}
76
Brandon Kim17ee1a92022-06-07 21:04:08 -070077void BufferImpl::readBufferHeader()
78{
79 size_t headerSize = sizeof(struct CircularBufferHeader);
80 std::vector<uint8_t> bytesRead =
Brandon Kim4662b1b2022-06-16 21:38:02 -070081 dataInterface->read(/*offset=*/0, headerSize);
Brandon Kim17ee1a92022-06-07 21:04:08 -070082
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 =
Brandon Kim60cab572022-06-15 14:20:05 -070091 *reinterpret_cast<struct CircularBufferHeader*>(bytesRead.data());
Brandon Kim17ee1a92022-06-07 21:04:08 -070092};
93
94struct CircularBufferHeader BufferImpl::getCachedBufferHeader() const
95{
96 return cachedBufferHeader;
97}
98
Brandon Kimcf0b9752022-06-15 10:32:21 -070099void 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
Brandon Kimc49284b2022-06-17 09:55:26 -0700122void BufferImpl::updateBmcFlags(const uint32_t newBmcFlag)
123{
124 constexpr uint8_t bmcFlagsPtrOffset =
125 offsetof(struct CircularBufferHeader, 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
Brandon Kim9836cfa2022-06-15 11:21:11 -0700145size_t BufferImpl::getQueueOffset()
146{
147 return sizeof(struct CircularBufferHeader) +
148 boost::endian::little_to_native(cachedBufferHeader.ueRegionSize);
149}
150
Brandon Kim35d43352022-06-16 11:13:36 -0700151std::vector<uint8_t> BufferImpl::wraparoundRead(const uint32_t relativeOffset,
152 const uint32_t length)
Brandon Kim9836cfa2022-06-15 11:21:11 -0700153{
Brandon Kim26660e92022-06-15 16:58:44 -0700154 const size_t queueSize =
155 boost::endian::little_to_native(cachedBufferHeader.queueSize);
Brandon Kim9836cfa2022-06-15 11:21:11 -0700156
Brandon Kim35d43352022-06-16 11:13:36 -0700157 if (relativeOffset > queueSize)
158 {
159 throw std::runtime_error(
160 fmt::format("[wraparoundRead] relativeOffset '{}' was bigger "
161 "than queueSize '{}'",
162 relativeOffset, queueSize));
163 }
164 if (length > queueSize)
Brandon Kim9836cfa2022-06-15 11:21:11 -0700165 {
166 throw std::runtime_error(fmt::format(
Brandon Kim35d43352022-06-16 11:13:36 -0700167 "[wraparoundRead] length '{}' was bigger than queueSize '{}'",
168 length, queueSize));
Brandon Kim9836cfa2022-06-15 11:21:11 -0700169 }
170
Brandon Kim35d43352022-06-16 11:13:36 -0700171 // Do a calculation to see if the read will wraparound
172 const size_t queueOffset = getQueueOffset();
173 const size_t queueSizeToQueueEnd = queueSize - relativeOffset;
174 size_t numWraparoundBytesToRead = 0;
175 if (length > queueSizeToQueueEnd)
176 {
177 // This means we will wrap, count the bytes that are left to read
178 numWraparoundBytesToRead = length - queueSizeToQueueEnd;
179 }
180 const size_t numBytesToReadTillQueueEnd = length - numWraparoundBytesToRead;
181
182 std::vector<uint8_t> bytesRead = dataInterface->read(
183 queueOffset + relativeOffset, numBytesToReadTillQueueEnd);
184 if (bytesRead.size() != numBytesToReadTillQueueEnd)
185 {
186 throw std::runtime_error(
187 fmt::format("[wraparoundRead] Read '{}' which was not "
188 "the requested length of '{}'",
189 bytesRead.size(), numBytesToReadTillQueueEnd));
190 }
191 size_t updatedReadPtr = relativeOffset + numBytesToReadTillQueueEnd;
Brandon Kim4662b1b2022-06-16 21:38:02 -0700192 if (updatedReadPtr == queueSize)
193 {
194 // If we read all the way up to the end of the queue, we need to
195 // manually wrap the updateReadPtr around to 0
196 updatedReadPtr = 0;
197 }
Brandon Kim9836cfa2022-06-15 11:21:11 -0700198
199 // If there are any more bytes to be read beyond the buffer, wrap around and
200 // read from the beginning of the buffer (offset by the queueOffset)
Brandon Kim35d43352022-06-16 11:13:36 -0700201 if (numWraparoundBytesToRead > 0)
Brandon Kim9836cfa2022-06-15 11:21:11 -0700202 {
203 std::vector<uint8_t> wrappedBytesRead =
Brandon Kim35d43352022-06-16 11:13:36 -0700204 dataInterface->read(queueOffset, numWraparoundBytesToRead);
205 if (numWraparoundBytesToRead != wrappedBytesRead.size())
Brandon Kim9836cfa2022-06-15 11:21:11 -0700206 {
207 throw std::runtime_error(fmt::format(
Brandon Kim35d43352022-06-16 11:13:36 -0700208 "[wraparoundRead] Buffer wrapped around but read '{}' which "
209 "was not the requested lenght of '{}'",
210 wrappedBytesRead.size(), numWraparoundBytesToRead));
Brandon Kim9836cfa2022-06-15 11:21:11 -0700211 }
212 bytesRead.insert(bytesRead.end(), wrappedBytesRead.begin(),
213 wrappedBytesRead.end());
Brandon Kim35d43352022-06-16 11:13:36 -0700214 updatedReadPtr = numWraparoundBytesToRead;
Brandon Kim9836cfa2022-06-15 11:21:11 -0700215 }
Brandon Kim35d43352022-06-16 11:13:36 -0700216 updateReadPtr(updatedReadPtr);
Brandon Kim9836cfa2022-06-15 11:21:11 -0700217
218 return bytesRead;
219}
220
Brandon Kim35d43352022-06-16 11:13:36 -0700221struct QueueEntryHeader BufferImpl::readEntryHeader(size_t relativeOffset)
Brandon Kim7bac2d62022-06-07 21:37:51 -0700222{
223 size_t headerSize = sizeof(struct QueueEntryHeader);
224 // wraparonudRead will throw if it did not read all the bytes, let it
225 // propagate up the stack
Brandon Kim35d43352022-06-16 11:13:36 -0700226 std::vector<uint8_t> bytesRead = wraparoundRead(relativeOffset, headerSize);
Brandon Kim7bac2d62022-06-07 21:37:51 -0700227
228 return *reinterpret_cast<struct QueueEntryHeader*>(bytesRead.data());
229}
230
Brandon Kim35d43352022-06-16 11:13:36 -0700231EntryPair BufferImpl::readEntry(size_t relativeOffset)
Brandon Kim40ce08e2022-06-15 16:58:44 -0700232{
Brandon Kim35d43352022-06-16 11:13:36 -0700233 struct QueueEntryHeader entryHeader = readEntryHeader(relativeOffset);
234 size_t entrySize = boost::endian::little_to_native(entryHeader.entrySize);
Brandon Kim40ce08e2022-06-15 16:58:44 -0700235
236 // wraparonudRead may throw if entrySize was bigger than the buffer or if it
Brandon Kim26660e92022-06-15 16:58:44 -0700237 // was not able to read all the bytes, let it propagate up the stack
Brandon Kim35d43352022-06-16 11:13:36 -0700238 std::vector<uint8_t> entry = wraparoundRead(
239 relativeOffset + sizeof(struct QueueEntryHeader), 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
263 const size_t queueSize =
264 boost::endian::little_to_native(cachedBufferHeader.queueSize);
265 size_t currentBiosWritePtr =
266 boost::endian::little_to_native(cachedBufferHeader.biosWritePtr);
267 if (currentBiosWritePtr > queueSize)
268 {
269 throw std::runtime_error(fmt::format(
270 "[readErrorLogs] currentBiosWritePtr was '{}' which was bigger "
271 "than queueSize '{}'",
272 currentBiosWritePtr, queueSize));
273 }
274 size_t currentReadPtr =
275 boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr);
276 if (currentReadPtr > queueSize)
277 {
278 throw std::runtime_error(fmt::format(
279 "[readErrorLogs] currentReadPtr was '{}' which was bigger "
280 "than queueSize '{}'",
281 currentReadPtr, queueSize));
282 }
283
284 size_t bytesToRead;
285 if (currentBiosWritePtr == currentReadPtr)
286 {
287 // No new payload was detected, return an empty vector gracefully
288 return {};
289 }
290
291 if (currentBiosWritePtr > currentReadPtr)
292 {
293 // Simply subtract in this case
294 bytesToRead = currentBiosWritePtr - currentReadPtr;
295 }
296 else
297 {
298 // Calculate the bytes to the "end" (QueueSize - ReadPtr) +
299 // bytes to read from the "beginning" (0 + WritePtr)
300 bytesToRead = (queueSize - currentReadPtr) + currentBiosWritePtr;
301 }
302
303 size_t byteRead = 0;
304 std::vector<EntryPair> entryPairs;
305 while (byteRead < bytesToRead)
306 {
307 EntryPair entryPair = readEntry(currentReadPtr);
308 byteRead += sizeof(struct QueueEntryHeader) + entryPair.second.size();
309 entryPairs.push_back(entryPair);
310
311 // Note: readEntry() will update cachedBufferHeader.bmcReadPtr
312 currentReadPtr =
313 boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr);
314 }
315 if (currentBiosWritePtr != currentReadPtr)
316 {
317 throw std::runtime_error(fmt::format(
318 "[readErrorLogs] biosWritePtr '{}' and bmcReaddPtr '{}' "
319 "are not identical after reading through all the logs",
320 currentBiosWritePtr, currentReadPtr));
321 }
322 return entryPairs;
323}
324
Brandon Kimfcbc3db2022-06-06 21:26:18 -0700325} // namespace bios_bmc_smm_error_logger