xref: /openbmc/bios-bmc-smm-error-logger/src/buffer.cpp (revision 40ce08e165579e1bc791cdf9278a44190f4d4014)
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>
15*40ce08e1SBrandon 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     // Initialize the whole buffer with 0x00
30fcbc3db1SBrandon Kim     const size_t memoryRegionSize = dataInterface->getMemoryRegionSize();
31fcbc3db1SBrandon Kim     const std::vector<uint8_t> emptyVector(memoryRegionSize, 0);
32fcbc3db1SBrandon Kim     size_t byteWritten = dataInterface->write(0, emptyVector);
33fcbc3db1SBrandon Kim     if (byteWritten != memoryRegionSize)
34fcbc3db1SBrandon Kim     {
35fcbc3db1SBrandon Kim         throw std::runtime_error(
36fcbc3db1SBrandon Kim             fmt::format("Buffer initialization only erased '{}'", byteWritten));
37fcbc3db1SBrandon Kim     }
38fcbc3db1SBrandon Kim 
39fcbc3db1SBrandon Kim     // Create an initial buffer header and write to it
40fcbc3db1SBrandon Kim     struct CircularBufferHeader initializationHeader = {};
4117ee1a93SBrandon Kim     initializationHeader.bmcInterfaceVersion =
4217ee1a93SBrandon Kim         boost::endian::native_to_little(bmcInterfaceVersion);
4317ee1a93SBrandon Kim     initializationHeader.queueSize = boost::endian::native_to_little(queueSize);
4417ee1a93SBrandon Kim     initializationHeader.ueRegionSize =
4517ee1a93SBrandon Kim         boost::endian::native_to_little(ueRegionSize);
4617ee1a93SBrandon Kim     std::transform(magicNumber.begin(), magicNumber.end(),
4717ee1a93SBrandon Kim                    initializationHeader.magicNumber.begin(),
4817ee1a93SBrandon Kim                    [](uint32_t number) -> little_uint32_t {
4917ee1a93SBrandon Kim                        return boost::endian::native_to_little(number);
5017ee1a93SBrandon Kim                    });
51fcbc3db1SBrandon Kim 
52fcbc3db1SBrandon Kim     uint8_t* initializationHeaderPtr =
53fcbc3db1SBrandon Kim         reinterpret_cast<uint8_t*>(&initializationHeader);
54fcbc3db1SBrandon Kim     size_t initializationHeaderSize = sizeof(initializationHeader);
55fcbc3db1SBrandon Kim     byteWritten = dataInterface->write(
56fcbc3db1SBrandon Kim         0, std::span<const uint8_t>(initializationHeaderPtr,
57fcbc3db1SBrandon Kim                                     initializationHeaderPtr +
58fcbc3db1SBrandon Kim                                         initializationHeaderSize));
59fcbc3db1SBrandon Kim     if (byteWritten != initializationHeaderSize)
60fcbc3db1SBrandon Kim     {
61fcbc3db1SBrandon Kim         throw std::runtime_error(fmt::format(
62fcbc3db1SBrandon Kim             "Buffer initialization buffer header write only wrote '{}'",
63fcbc3db1SBrandon Kim             byteWritten));
64fcbc3db1SBrandon Kim     }
6560cab57fSBrandon Kim     cachedBufferHeader = initializationHeader;
66fcbc3db1SBrandon Kim }
67fcbc3db1SBrandon Kim 
6817ee1a93SBrandon Kim void BufferImpl::readBufferHeader()
6917ee1a93SBrandon Kim {
7017ee1a93SBrandon Kim     size_t headerSize = sizeof(struct CircularBufferHeader);
7117ee1a93SBrandon Kim     std::vector<uint8_t> bytesRead =
7217ee1a93SBrandon Kim         dataInterface->read(/* offset */ 0, headerSize);
7317ee1a93SBrandon Kim 
7417ee1a93SBrandon Kim     if (bytesRead.size() != headerSize)
7517ee1a93SBrandon Kim     {
7617ee1a93SBrandon Kim         throw std::runtime_error(
7717ee1a93SBrandon Kim             fmt::format("Buffer header read only read '{}', expected '{}'",
7817ee1a93SBrandon Kim                         bytesRead.size(), headerSize));
7917ee1a93SBrandon Kim     }
8017ee1a93SBrandon Kim 
8117ee1a93SBrandon Kim     cachedBufferHeader =
8260cab57fSBrandon Kim         *reinterpret_cast<struct CircularBufferHeader*>(bytesRead.data());
8317ee1a93SBrandon Kim };
8417ee1a93SBrandon Kim 
8517ee1a93SBrandon Kim struct CircularBufferHeader BufferImpl::getCachedBufferHeader() const
8617ee1a93SBrandon Kim {
8717ee1a93SBrandon Kim     return cachedBufferHeader;
8817ee1a93SBrandon Kim }
8917ee1a93SBrandon Kim 
90cf0b9752SBrandon Kim void BufferImpl::updateReadPtr(const uint32_t newReadPtr)
91cf0b9752SBrandon Kim {
92cf0b9752SBrandon Kim     constexpr uint8_t bmcReadPtrOffset =
93cf0b9752SBrandon Kim         offsetof(struct CircularBufferHeader, bmcReadPtr);
94cf0b9752SBrandon Kim 
95cf0b9752SBrandon Kim     little_uint16_t truncatedReadPtr =
96cf0b9752SBrandon Kim         boost::endian::native_to_little(newReadPtr & 0xffff);
97cf0b9752SBrandon Kim     uint8_t* truncatedReadPtrPtr =
98cf0b9752SBrandon Kim         reinterpret_cast<uint8_t*>(&truncatedReadPtr);
99cf0b9752SBrandon Kim 
100cf0b9752SBrandon Kim     size_t writtenSize = dataInterface->write(
101cf0b9752SBrandon Kim         bmcReadPtrOffset, std::span<const uint8_t>{
102cf0b9752SBrandon Kim                               truncatedReadPtrPtr,
103cf0b9752SBrandon Kim                               truncatedReadPtrPtr + sizeof(little_uint16_t)});
104cf0b9752SBrandon Kim     if (writtenSize != sizeof(little_uint16_t))
105cf0b9752SBrandon Kim     {
106cf0b9752SBrandon Kim         throw std::runtime_error(fmt::format(
107cf0b9752SBrandon Kim             "[updateReadPtr] Wrote '{}' bytes, instead of expected '{}'",
108cf0b9752SBrandon Kim             writtenSize, sizeof(little_uint16_t)));
109cf0b9752SBrandon Kim     }
110cf0b9752SBrandon Kim     cachedBufferHeader.bmcReadPtr = truncatedReadPtr;
111cf0b9752SBrandon Kim }
112cf0b9752SBrandon Kim 
1139836cfa6SBrandon Kim size_t BufferImpl::getQueueOffset()
1149836cfa6SBrandon Kim {
1159836cfa6SBrandon Kim     return sizeof(struct CircularBufferHeader) +
1169836cfa6SBrandon Kim            boost::endian::little_to_native(cachedBufferHeader.ueRegionSize);
1179836cfa6SBrandon Kim }
1189836cfa6SBrandon Kim 
1199836cfa6SBrandon Kim std::vector<uint8_t>
1209836cfa6SBrandon Kim     BufferImpl::wraparoundRead(const uint32_t offset, const uint32_t length,
1219836cfa6SBrandon Kim                                const uint32_t additionalBoundaryCheck)
1229836cfa6SBrandon Kim {
1239836cfa6SBrandon Kim     const size_t memoryRegionSize = dataInterface->getMemoryRegionSize();
1249836cfa6SBrandon Kim 
1259836cfa6SBrandon Kim     size_t queueOffset = getQueueOffset();
1269836cfa6SBrandon Kim     if (queueOffset + length + additionalBoundaryCheck > memoryRegionSize)
1279836cfa6SBrandon Kim     {
1289836cfa6SBrandon Kim         throw std::runtime_error(fmt::format(
1299836cfa6SBrandon Kim             "[wraparoundRead] queueOffset '{}' + length '{}' "
1309836cfa6SBrandon Kim             "+ additionalBoundaryCheck '{}' + was bigger "
1319836cfa6SBrandon Kim             "than memoryRegionSize '{}'",
1329836cfa6SBrandon Kim             queueOffset, length, additionalBoundaryCheck, memoryRegionSize));
1339836cfa6SBrandon Kim     }
1349836cfa6SBrandon Kim 
1359836cfa6SBrandon Kim     // Do a first read up to the end of the buffer (dataInerface->read should
1369836cfa6SBrandon Kim     // only read up to the end of the buffer)
1379836cfa6SBrandon Kim     std::vector<uint8_t> bytesRead = dataInterface->read(offset, length);
1389836cfa6SBrandon Kim     size_t updatedReadOffset = offset + bytesRead.size();
1399836cfa6SBrandon Kim     size_t bytesRemaining = length - bytesRead.size();
1409836cfa6SBrandon Kim 
1419836cfa6SBrandon Kim     // If there are any more bytes to be read beyond the buffer, wrap around and
1429836cfa6SBrandon Kim     // read from the beginning of the buffer (offset by the queueOffset)
1439836cfa6SBrandon Kim     if (bytesRemaining > 0)
1449836cfa6SBrandon Kim     {
1459836cfa6SBrandon Kim         std::vector<uint8_t> wrappedBytesRead =
1469836cfa6SBrandon Kim             dataInterface->read(queueOffset, bytesRemaining);
1479836cfa6SBrandon Kim         bytesRemaining -= wrappedBytesRead.size();
1489836cfa6SBrandon Kim         if (bytesRemaining != 0)
1499836cfa6SBrandon Kim         {
1509836cfa6SBrandon Kim             throw std::runtime_error(fmt::format(
1519836cfa6SBrandon Kim                 "[wraparoundRead] Buffer wrapped around but was not able to read "
1529836cfa6SBrandon Kim                 "all of the requested info. Bytes remaining to read '{}' of '{}'",
1539836cfa6SBrandon Kim                 bytesRemaining, length));
1549836cfa6SBrandon Kim         }
1559836cfa6SBrandon Kim         bytesRead.insert(bytesRead.end(), wrappedBytesRead.begin(),
1569836cfa6SBrandon Kim                          wrappedBytesRead.end());
1579836cfa6SBrandon Kim         updatedReadOffset = queueOffset + wrappedBytesRead.size();
1589836cfa6SBrandon Kim     }
1599836cfa6SBrandon Kim     updateReadPtr(updatedReadOffset);
1609836cfa6SBrandon Kim 
1619836cfa6SBrandon Kim     return bytesRead;
1629836cfa6SBrandon Kim }
1639836cfa6SBrandon Kim 
1647bac2d69SBrandon Kim struct QueueEntryHeader BufferImpl::readEntryHeader(size_t offset)
1657bac2d69SBrandon Kim {
1667bac2d69SBrandon Kim     size_t headerSize = sizeof(struct QueueEntryHeader);
1677bac2d69SBrandon Kim     // wraparonudRead will throw if it did not read all the bytes, let it
1687bac2d69SBrandon Kim     // propagate up the stack
1697bac2d69SBrandon Kim     std::vector<uint8_t> bytesRead = wraparoundRead(offset, headerSize);
1707bac2d69SBrandon Kim 
1717bac2d69SBrandon Kim     return *reinterpret_cast<struct QueueEntryHeader*>(bytesRead.data());
1727bac2d69SBrandon Kim }
1737bac2d69SBrandon Kim 
174*40ce08e1SBrandon Kim EntryPair BufferImpl::readEntry(size_t offset)
175*40ce08e1SBrandon Kim {
176*40ce08e1SBrandon Kim     struct QueueEntryHeader entryHeader = readEntryHeader(offset);
177*40ce08e1SBrandon Kim 
178*40ce08e1SBrandon Kim     size_t entrySize = entryHeader.entrySize;
179*40ce08e1SBrandon Kim 
180*40ce08e1SBrandon Kim     // wraparonudRead may throw if entrySize was bigger than the buffer or if it
181*40ce08e1SBrandon Kim     // was not able to read all bytes, let it propagate up the stack
182*40ce08e1SBrandon Kim     // - Use additionalBoundaryCheck parameter to tighten the boundary check
183*40ce08e1SBrandon Kim     std::vector<uint8_t> entry =
184*40ce08e1SBrandon Kim         wraparoundRead(offset + sizeof(struct QueueEntryHeader), entrySize,
185*40ce08e1SBrandon Kim                        sizeof(struct QueueEntryHeader));
186*40ce08e1SBrandon Kim 
187*40ce08e1SBrandon Kim     // Calculate the checksum
188*40ce08e1SBrandon Kim     uint8_t* entryHeaderPtr = reinterpret_cast<uint8_t*>(&entryHeader);
189*40ce08e1SBrandon Kim     uint8_t checksum = std::accumulate(
190*40ce08e1SBrandon Kim         entryHeaderPtr, entryHeaderPtr + sizeof(struct QueueEntryHeader), 0);
191*40ce08e1SBrandon Kim     checksum = std::accumulate(std::begin(entry), std::end(entry), checksum);
192*40ce08e1SBrandon Kim     if (checksum != 0)
193*40ce08e1SBrandon Kim     {
194*40ce08e1SBrandon Kim         throw std::runtime_error(fmt::format(
195*40ce08e1SBrandon Kim             "[readEntry] Checksum was '{}', expected '0'", checksum));
196*40ce08e1SBrandon Kim     }
197*40ce08e1SBrandon Kim 
198*40ce08e1SBrandon Kim     return {entryHeader, entry};
199*40ce08e1SBrandon Kim }
200*40ce08e1SBrandon Kim 
201fcbc3db1SBrandon Kim } // namespace bios_bmc_smm_error_logger
202