xref: /openbmc/bios-bmc-smm-error-logger/src/buffer.cpp (revision 4662b1bd2d265f605f80709008456aae0d6bb545)
1fcbc3db1SBrandon Kim #include "buffer.hpp"
2fcbc3db1SBrandon Kim 
3fcbc3db1SBrandon Kim #include "pci_handler.hpp"
4fcbc3db1SBrandon Kim 
5fcbc3db1SBrandon Kim #include <fmt/format.h>
6fcbc3db1SBrandon Kim 
717ee1a93SBrandon Kim #include <boost/endian/arithmetic.hpp>
817ee1a93SBrandon Kim #include <boost/endian/conversion.hpp>
917ee1a93SBrandon Kim 
1017ee1a93SBrandon Kim #include <algorithm>
11fcbc3db1SBrandon Kim #include <array>
12cf0b9752SBrandon Kim #include <cstddef>
13fcbc3db1SBrandon Kim #include <cstdint>
14fcbc3db1SBrandon Kim #include <memory>
1540ce08e1SBrandon Kim #include <numeric>
16fcbc3db1SBrandon Kim #include <span>
17fcbc3db1SBrandon Kim #include <vector>
18fcbc3db1SBrandon Kim 
19fcbc3db1SBrandon Kim namespace bios_bmc_smm_error_logger
20fcbc3db1SBrandon Kim {
21fcbc3db1SBrandon Kim 
22fcbc3db1SBrandon Kim BufferImpl::BufferImpl(std::unique_ptr<DataInterface> dataInterface) :
23fcbc3db1SBrandon Kim     dataInterface(std::move(dataInterface)){};
24fcbc3db1SBrandon Kim 
25fcbc3db1SBrandon Kim void BufferImpl::initialize(uint32_t bmcInterfaceVersion, uint16_t queueSize,
26fcbc3db1SBrandon Kim                             uint16_t ueRegionSize,
27fcbc3db1SBrandon Kim                             const std::array<uint32_t, 4>& magicNumber)
28fcbc3db1SBrandon Kim {
29fcbc3db1SBrandon Kim     const size_t memoryRegionSize = dataInterface->getMemoryRegionSize();
3026660e9bSBrandon Kim     const size_t proposedBufferSize =
3126660e9bSBrandon Kim         sizeof(struct CircularBufferHeader) + ueRegionSize + queueSize;
3226660e9bSBrandon Kim     if (proposedBufferSize > memoryRegionSize)
3326660e9bSBrandon Kim     {
3426660e9bSBrandon Kim         throw std::runtime_error(fmt::format(
3526660e9bSBrandon Kim             "[initialize] Proposed region size '{}' is bigger than the "
3626660e9bSBrandon Kim             "BMC's allocated MMIO region of '{}'",
3726660e9bSBrandon Kim             proposedBufferSize, memoryRegionSize));
3826660e9bSBrandon Kim     }
3926660e9bSBrandon Kim 
4026660e9bSBrandon Kim     // Initialize the whole buffer with 0x00
4126660e9bSBrandon Kim     const std::vector<uint8_t> emptyVector(proposedBufferSize, 0);
42fcbc3db1SBrandon Kim     size_t byteWritten = dataInterface->write(0, emptyVector);
4326660e9bSBrandon Kim     if (byteWritten != proposedBufferSize)
44fcbc3db1SBrandon Kim     {
45fcbc3db1SBrandon Kim         throw std::runtime_error(
4626660e9bSBrandon Kim             fmt::format("[initialize] Only erased '{}'", byteWritten));
47fcbc3db1SBrandon Kim     }
48fcbc3db1SBrandon Kim 
49fcbc3db1SBrandon Kim     // Create an initial buffer header and write to it
50fcbc3db1SBrandon Kim     struct CircularBufferHeader initializationHeader = {};
5117ee1a93SBrandon Kim     initializationHeader.bmcInterfaceVersion =
5217ee1a93SBrandon Kim         boost::endian::native_to_little(bmcInterfaceVersion);
5317ee1a93SBrandon Kim     initializationHeader.queueSize = boost::endian::native_to_little(queueSize);
5417ee1a93SBrandon Kim     initializationHeader.ueRegionSize =
5517ee1a93SBrandon Kim         boost::endian::native_to_little(ueRegionSize);
5617ee1a93SBrandon Kim     std::transform(magicNumber.begin(), magicNumber.end(),
5717ee1a93SBrandon Kim                    initializationHeader.magicNumber.begin(),
5817ee1a93SBrandon Kim                    [](uint32_t number) -> little_uint32_t {
5917ee1a93SBrandon Kim                        return boost::endian::native_to_little(number);
6017ee1a93SBrandon Kim                    });
61fcbc3db1SBrandon Kim 
62fcbc3db1SBrandon Kim     uint8_t* initializationHeaderPtr =
63fcbc3db1SBrandon Kim         reinterpret_cast<uint8_t*>(&initializationHeader);
64fcbc3db1SBrandon Kim     size_t initializationHeaderSize = sizeof(initializationHeader);
65fcbc3db1SBrandon Kim     byteWritten = dataInterface->write(
66fcbc3db1SBrandon Kim         0, std::span<const uint8_t>(initializationHeaderPtr,
67fcbc3db1SBrandon Kim                                     initializationHeaderPtr +
68fcbc3db1SBrandon Kim                                         initializationHeaderSize));
69fcbc3db1SBrandon Kim     if (byteWritten != initializationHeaderSize)
70fcbc3db1SBrandon Kim     {
71fcbc3db1SBrandon Kim         throw std::runtime_error(fmt::format(
7226660e9bSBrandon Kim             "[initialize] Only wrote '{}' bytes of the header", byteWritten));
73fcbc3db1SBrandon Kim     }
7460cab57fSBrandon Kim     cachedBufferHeader = initializationHeader;
75fcbc3db1SBrandon Kim }
76fcbc3db1SBrandon Kim 
7717ee1a93SBrandon Kim void BufferImpl::readBufferHeader()
7817ee1a93SBrandon Kim {
7917ee1a93SBrandon Kim     size_t headerSize = sizeof(struct CircularBufferHeader);
8017ee1a93SBrandon Kim     std::vector<uint8_t> bytesRead =
81*4662b1bdSBrandon Kim         dataInterface->read(/*offset=*/0, headerSize);
8217ee1a93SBrandon Kim 
8317ee1a93SBrandon Kim     if (bytesRead.size() != headerSize)
8417ee1a93SBrandon Kim     {
8517ee1a93SBrandon Kim         throw std::runtime_error(
8617ee1a93SBrandon Kim             fmt::format("Buffer header read only read '{}', expected '{}'",
8717ee1a93SBrandon Kim                         bytesRead.size(), headerSize));
8817ee1a93SBrandon Kim     }
8917ee1a93SBrandon Kim 
9017ee1a93SBrandon Kim     cachedBufferHeader =
9160cab57fSBrandon Kim         *reinterpret_cast<struct CircularBufferHeader*>(bytesRead.data());
9217ee1a93SBrandon Kim };
9317ee1a93SBrandon Kim 
9417ee1a93SBrandon Kim struct CircularBufferHeader BufferImpl::getCachedBufferHeader() const
9517ee1a93SBrandon Kim {
9617ee1a93SBrandon Kim     return cachedBufferHeader;
9717ee1a93SBrandon Kim }
9817ee1a93SBrandon Kim 
99cf0b9752SBrandon Kim void BufferImpl::updateReadPtr(const uint32_t newReadPtr)
100cf0b9752SBrandon Kim {
101cf0b9752SBrandon Kim     constexpr uint8_t bmcReadPtrOffset =
102cf0b9752SBrandon Kim         offsetof(struct CircularBufferHeader, bmcReadPtr);
103cf0b9752SBrandon Kim 
104cf0b9752SBrandon Kim     little_uint16_t truncatedReadPtr =
105cf0b9752SBrandon Kim         boost::endian::native_to_little(newReadPtr & 0xffff);
106cf0b9752SBrandon Kim     uint8_t* truncatedReadPtrPtr =
107cf0b9752SBrandon Kim         reinterpret_cast<uint8_t*>(&truncatedReadPtr);
108cf0b9752SBrandon Kim 
109cf0b9752SBrandon Kim     size_t writtenSize = dataInterface->write(
110cf0b9752SBrandon Kim         bmcReadPtrOffset, std::span<const uint8_t>{
111cf0b9752SBrandon Kim                               truncatedReadPtrPtr,
112cf0b9752SBrandon Kim                               truncatedReadPtrPtr + sizeof(little_uint16_t)});
113cf0b9752SBrandon Kim     if (writtenSize != sizeof(little_uint16_t))
114cf0b9752SBrandon Kim     {
115cf0b9752SBrandon Kim         throw std::runtime_error(fmt::format(
116cf0b9752SBrandon Kim             "[updateReadPtr] Wrote '{}' bytes, instead of expected '{}'",
117cf0b9752SBrandon Kim             writtenSize, sizeof(little_uint16_t)));
118cf0b9752SBrandon Kim     }
119cf0b9752SBrandon Kim     cachedBufferHeader.bmcReadPtr = truncatedReadPtr;
120cf0b9752SBrandon Kim }
121cf0b9752SBrandon Kim 
1229836cfa6SBrandon Kim size_t BufferImpl::getQueueOffset()
1239836cfa6SBrandon Kim {
1249836cfa6SBrandon Kim     return sizeof(struct CircularBufferHeader) +
1259836cfa6SBrandon Kim            boost::endian::little_to_native(cachedBufferHeader.ueRegionSize);
1269836cfa6SBrandon Kim }
1279836cfa6SBrandon Kim 
12835d4335eSBrandon Kim std::vector<uint8_t> BufferImpl::wraparoundRead(const uint32_t relativeOffset,
12935d4335eSBrandon Kim                                                 const uint32_t length)
1309836cfa6SBrandon Kim {
13126660e9bSBrandon Kim     const size_t queueSize =
13226660e9bSBrandon Kim         boost::endian::little_to_native(cachedBufferHeader.queueSize);
1339836cfa6SBrandon Kim 
13435d4335eSBrandon Kim     if (relativeOffset > queueSize)
13535d4335eSBrandon Kim     {
13635d4335eSBrandon Kim         throw std::runtime_error(
13735d4335eSBrandon Kim             fmt::format("[wraparoundRead] relativeOffset '{}' was bigger "
13835d4335eSBrandon Kim                         "than queueSize '{}'",
13935d4335eSBrandon Kim                         relativeOffset, queueSize));
14035d4335eSBrandon Kim     }
14135d4335eSBrandon Kim     if (length > queueSize)
1429836cfa6SBrandon Kim     {
1439836cfa6SBrandon Kim         throw std::runtime_error(fmt::format(
14435d4335eSBrandon Kim             "[wraparoundRead] length '{}' was bigger than queueSize '{}'",
14535d4335eSBrandon Kim             length, queueSize));
1469836cfa6SBrandon Kim     }
1479836cfa6SBrandon Kim 
14835d4335eSBrandon Kim     // Do a calculation to see if the read will wraparound
14935d4335eSBrandon Kim     const size_t queueOffset = getQueueOffset();
15035d4335eSBrandon Kim     const size_t queueSizeToQueueEnd = queueSize - relativeOffset;
15135d4335eSBrandon Kim     size_t numWraparoundBytesToRead = 0;
15235d4335eSBrandon Kim     if (length > queueSizeToQueueEnd)
15335d4335eSBrandon Kim     {
15435d4335eSBrandon Kim         // This means we will wrap, count the bytes that are left to read
15535d4335eSBrandon Kim         numWraparoundBytesToRead = length - queueSizeToQueueEnd;
15635d4335eSBrandon Kim     }
15735d4335eSBrandon Kim     const size_t numBytesToReadTillQueueEnd = length - numWraparoundBytesToRead;
15835d4335eSBrandon Kim 
15935d4335eSBrandon Kim     std::vector<uint8_t> bytesRead = dataInterface->read(
16035d4335eSBrandon Kim         queueOffset + relativeOffset, numBytesToReadTillQueueEnd);
16135d4335eSBrandon Kim     if (bytesRead.size() != numBytesToReadTillQueueEnd)
16235d4335eSBrandon Kim     {
16335d4335eSBrandon Kim         throw std::runtime_error(
16435d4335eSBrandon Kim             fmt::format("[wraparoundRead] Read '{}' which was not "
16535d4335eSBrandon Kim                         "the requested length of '{}'",
16635d4335eSBrandon Kim                         bytesRead.size(), numBytesToReadTillQueueEnd));
16735d4335eSBrandon Kim     }
16835d4335eSBrandon Kim     size_t updatedReadPtr = relativeOffset + numBytesToReadTillQueueEnd;
169*4662b1bdSBrandon Kim     if (updatedReadPtr == queueSize)
170*4662b1bdSBrandon Kim     {
171*4662b1bdSBrandon Kim         // If we read all the way up to the end of the queue, we need to
172*4662b1bdSBrandon Kim         // manually wrap the updateReadPtr around to 0
173*4662b1bdSBrandon Kim         updatedReadPtr = 0;
174*4662b1bdSBrandon Kim     }
1759836cfa6SBrandon Kim 
1769836cfa6SBrandon Kim     // If there are any more bytes to be read beyond the buffer, wrap around and
1779836cfa6SBrandon Kim     // read from the beginning of the buffer (offset by the queueOffset)
17835d4335eSBrandon Kim     if (numWraparoundBytesToRead > 0)
1799836cfa6SBrandon Kim     {
1809836cfa6SBrandon Kim         std::vector<uint8_t> wrappedBytesRead =
18135d4335eSBrandon Kim             dataInterface->read(queueOffset, numWraparoundBytesToRead);
18235d4335eSBrandon Kim         if (numWraparoundBytesToRead != wrappedBytesRead.size())
1839836cfa6SBrandon Kim         {
1849836cfa6SBrandon Kim             throw std::runtime_error(fmt::format(
18535d4335eSBrandon Kim                 "[wraparoundRead] Buffer wrapped around but read '{}' which "
18635d4335eSBrandon Kim                 "was not the requested lenght of '{}'",
18735d4335eSBrandon Kim                 wrappedBytesRead.size(), numWraparoundBytesToRead));
1889836cfa6SBrandon Kim         }
1899836cfa6SBrandon Kim         bytesRead.insert(bytesRead.end(), wrappedBytesRead.begin(),
1909836cfa6SBrandon Kim                          wrappedBytesRead.end());
19135d4335eSBrandon Kim         updatedReadPtr = numWraparoundBytesToRead;
1929836cfa6SBrandon Kim     }
19335d4335eSBrandon Kim     updateReadPtr(updatedReadPtr);
1949836cfa6SBrandon Kim 
1959836cfa6SBrandon Kim     return bytesRead;
1969836cfa6SBrandon Kim }
1979836cfa6SBrandon Kim 
19835d4335eSBrandon Kim struct QueueEntryHeader BufferImpl::readEntryHeader(size_t relativeOffset)
1997bac2d69SBrandon Kim {
2007bac2d69SBrandon Kim     size_t headerSize = sizeof(struct QueueEntryHeader);
2017bac2d69SBrandon Kim     // wraparonudRead will throw if it did not read all the bytes, let it
2027bac2d69SBrandon Kim     // propagate up the stack
20335d4335eSBrandon Kim     std::vector<uint8_t> bytesRead = wraparoundRead(relativeOffset, headerSize);
2047bac2d69SBrandon Kim 
2057bac2d69SBrandon Kim     return *reinterpret_cast<struct QueueEntryHeader*>(bytesRead.data());
2067bac2d69SBrandon Kim }
2077bac2d69SBrandon Kim 
20835d4335eSBrandon Kim EntryPair BufferImpl::readEntry(size_t relativeOffset)
20940ce08e1SBrandon Kim {
21035d4335eSBrandon Kim     struct QueueEntryHeader entryHeader = readEntryHeader(relativeOffset);
21135d4335eSBrandon Kim     size_t entrySize = boost::endian::little_to_native(entryHeader.entrySize);
21240ce08e1SBrandon Kim 
21340ce08e1SBrandon Kim     // wraparonudRead may throw if entrySize was bigger than the buffer or if it
21426660e9bSBrandon Kim     // was not able to read all the bytes, let it propagate up the stack
21535d4335eSBrandon Kim     std::vector<uint8_t> entry = wraparoundRead(
21635d4335eSBrandon Kim         relativeOffset + sizeof(struct QueueEntryHeader), entrySize);
21740ce08e1SBrandon Kim 
21840ce08e1SBrandon Kim     // Calculate the checksum
21940ce08e1SBrandon Kim     uint8_t* entryHeaderPtr = reinterpret_cast<uint8_t*>(&entryHeader);
220f0e4adc9SBrandon Kim     uint8_t checksum =
221f0e4adc9SBrandon Kim         std::accumulate(entryHeaderPtr,
222f0e4adc9SBrandon Kim                         entryHeaderPtr + sizeof(struct QueueEntryHeader), 0,
223f0e4adc9SBrandon Kim                         std::bit_xor<void>()) ^
224f0e4adc9SBrandon Kim         std::accumulate(entry.begin(), entry.end(), 0, std::bit_xor<void>());
225f0e4adc9SBrandon Kim 
22640ce08e1SBrandon Kim     if (checksum != 0)
22740ce08e1SBrandon Kim     {
22840ce08e1SBrandon Kim         throw std::runtime_error(fmt::format(
22940ce08e1SBrandon Kim             "[readEntry] Checksum was '{}', expected '0'", checksum));
23040ce08e1SBrandon Kim     }
23140ce08e1SBrandon Kim 
23240ce08e1SBrandon Kim     return {entryHeader, entry};
23340ce08e1SBrandon Kim }
23440ce08e1SBrandon Kim 
235*4662b1bdSBrandon Kim std::vector<EntryPair> BufferImpl::readErrorLogs()
236*4662b1bdSBrandon Kim {
237*4662b1bdSBrandon Kim     // Reading the buffer header will update the cachedBufferHeader
238*4662b1bdSBrandon Kim     readBufferHeader();
239*4662b1bdSBrandon Kim 
240*4662b1bdSBrandon Kim     const size_t queueSize =
241*4662b1bdSBrandon Kim         boost::endian::little_to_native(cachedBufferHeader.queueSize);
242*4662b1bdSBrandon Kim     size_t currentBiosWritePtr =
243*4662b1bdSBrandon Kim         boost::endian::little_to_native(cachedBufferHeader.biosWritePtr);
244*4662b1bdSBrandon Kim     if (currentBiosWritePtr > queueSize)
245*4662b1bdSBrandon Kim     {
246*4662b1bdSBrandon Kim         throw std::runtime_error(fmt::format(
247*4662b1bdSBrandon Kim             "[readErrorLogs] currentBiosWritePtr was '{}' which was bigger "
248*4662b1bdSBrandon Kim             "than queueSize '{}'",
249*4662b1bdSBrandon Kim             currentBiosWritePtr, queueSize));
250*4662b1bdSBrandon Kim     }
251*4662b1bdSBrandon Kim     size_t currentReadPtr =
252*4662b1bdSBrandon Kim         boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr);
253*4662b1bdSBrandon Kim     if (currentReadPtr > queueSize)
254*4662b1bdSBrandon Kim     {
255*4662b1bdSBrandon Kim         throw std::runtime_error(fmt::format(
256*4662b1bdSBrandon Kim             "[readErrorLogs] currentReadPtr was '{}' which was bigger "
257*4662b1bdSBrandon Kim             "than queueSize '{}'",
258*4662b1bdSBrandon Kim             currentReadPtr, queueSize));
259*4662b1bdSBrandon Kim     }
260*4662b1bdSBrandon Kim 
261*4662b1bdSBrandon Kim     size_t bytesToRead;
262*4662b1bdSBrandon Kim     if (currentBiosWritePtr == currentReadPtr)
263*4662b1bdSBrandon Kim     {
264*4662b1bdSBrandon Kim         // No new payload was detected, return an empty vector gracefully
265*4662b1bdSBrandon Kim         return {};
266*4662b1bdSBrandon Kim     }
267*4662b1bdSBrandon Kim 
268*4662b1bdSBrandon Kim     if (currentBiosWritePtr > currentReadPtr)
269*4662b1bdSBrandon Kim     {
270*4662b1bdSBrandon Kim         // Simply subtract in this case
271*4662b1bdSBrandon Kim         bytesToRead = currentBiosWritePtr - currentReadPtr;
272*4662b1bdSBrandon Kim     }
273*4662b1bdSBrandon Kim     else
274*4662b1bdSBrandon Kim     {
275*4662b1bdSBrandon Kim         // Calculate the bytes to the "end" (QueueSize - ReadPtr) +
276*4662b1bdSBrandon Kim         // bytes to read from the "beginning" (0 +  WritePtr)
277*4662b1bdSBrandon Kim         bytesToRead = (queueSize - currentReadPtr) + currentBiosWritePtr;
278*4662b1bdSBrandon Kim     }
279*4662b1bdSBrandon Kim 
280*4662b1bdSBrandon Kim     size_t byteRead = 0;
281*4662b1bdSBrandon Kim     std::vector<EntryPair> entryPairs;
282*4662b1bdSBrandon Kim     while (byteRead < bytesToRead)
283*4662b1bdSBrandon Kim     {
284*4662b1bdSBrandon Kim         EntryPair entryPair = readEntry(currentReadPtr);
285*4662b1bdSBrandon Kim         byteRead += sizeof(struct QueueEntryHeader) + entryPair.second.size();
286*4662b1bdSBrandon Kim         entryPairs.push_back(entryPair);
287*4662b1bdSBrandon Kim 
288*4662b1bdSBrandon Kim         // Note: readEntry() will update cachedBufferHeader.bmcReadPtr
289*4662b1bdSBrandon Kim         currentReadPtr =
290*4662b1bdSBrandon Kim             boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr);
291*4662b1bdSBrandon Kim     }
292*4662b1bdSBrandon Kim     if (currentBiosWritePtr != currentReadPtr)
293*4662b1bdSBrandon Kim     {
294*4662b1bdSBrandon Kim         throw std::runtime_error(fmt::format(
295*4662b1bdSBrandon Kim             "[readErrorLogs] biosWritePtr '{}' and bmcReaddPtr '{}' "
296*4662b1bdSBrandon Kim             "are not identical after reading through all the logs",
297*4662b1bdSBrandon Kim             currentBiosWritePtr, currentReadPtr));
298*4662b1bdSBrandon Kim     }
299*4662b1bdSBrandon Kim     return entryPairs;
300*4662b1bdSBrandon Kim }
301*4662b1bdSBrandon Kim 
302fcbc3db1SBrandon Kim } // namespace bios_bmc_smm_error_logger
303