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(); 30*26660e9bSBrandon Kim const size_t proposedBufferSize = 31*26660e9bSBrandon Kim sizeof(struct CircularBufferHeader) + ueRegionSize + queueSize; 32*26660e9bSBrandon Kim if (proposedBufferSize > memoryRegionSize) 33*26660e9bSBrandon Kim { 34*26660e9bSBrandon Kim throw std::runtime_error(fmt::format( 35*26660e9bSBrandon Kim "[initialize] Proposed region size '{}' is bigger than the " 36*26660e9bSBrandon Kim "BMC's allocated MMIO region of '{}'", 37*26660e9bSBrandon Kim proposedBufferSize, memoryRegionSize)); 38*26660e9bSBrandon Kim } 39*26660e9bSBrandon Kim 40*26660e9bSBrandon Kim // Initialize the whole buffer with 0x00 41*26660e9bSBrandon Kim const std::vector<uint8_t> emptyVector(proposedBufferSize, 0); 42fcbc3db1SBrandon Kim size_t byteWritten = dataInterface->write(0, emptyVector); 43*26660e9bSBrandon Kim if (byteWritten != proposedBufferSize) 44fcbc3db1SBrandon Kim { 45fcbc3db1SBrandon Kim throw std::runtime_error( 46*26660e9bSBrandon 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( 72*26660e9bSBrandon 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 = 8117ee1a93SBrandon 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 1289836cfa6SBrandon Kim std::vector<uint8_t> 1299836cfa6SBrandon Kim BufferImpl::wraparoundRead(const uint32_t offset, const uint32_t length, 1309836cfa6SBrandon Kim const uint32_t additionalBoundaryCheck) 1319836cfa6SBrandon Kim { 132*26660e9bSBrandon Kim const size_t queueSize = 133*26660e9bSBrandon Kim boost::endian::little_to_native(cachedBufferHeader.queueSize); 1349836cfa6SBrandon Kim 135*26660e9bSBrandon Kim if (length + additionalBoundaryCheck > queueSize) 1369836cfa6SBrandon Kim { 1379836cfa6SBrandon Kim throw std::runtime_error(fmt::format( 138*26660e9bSBrandon Kim "[wraparoundRead] length '{}' + additionalBoundaryCheck '{}' " 139*26660e9bSBrandon Kim "was bigger than queueSize '{}'", 140*26660e9bSBrandon Kim length, additionalBoundaryCheck, queueSize)); 1419836cfa6SBrandon Kim } 1429836cfa6SBrandon Kim 1439836cfa6SBrandon Kim // Do a first read up to the end of the buffer (dataInerface->read should 1449836cfa6SBrandon Kim // only read up to the end of the buffer) 1459836cfa6SBrandon Kim std::vector<uint8_t> bytesRead = dataInterface->read(offset, length); 1469836cfa6SBrandon Kim size_t updatedReadOffset = offset + bytesRead.size(); 1479836cfa6SBrandon Kim size_t bytesRemaining = length - bytesRead.size(); 1489836cfa6SBrandon Kim 1499836cfa6SBrandon Kim // If there are any more bytes to be read beyond the buffer, wrap around and 1509836cfa6SBrandon Kim // read from the beginning of the buffer (offset by the queueOffset) 151*26660e9bSBrandon Kim size_t queueOffset = getQueueOffset(); 1529836cfa6SBrandon Kim if (bytesRemaining > 0) 1539836cfa6SBrandon Kim { 1549836cfa6SBrandon Kim std::vector<uint8_t> wrappedBytesRead = 1559836cfa6SBrandon Kim dataInterface->read(queueOffset, bytesRemaining); 1569836cfa6SBrandon Kim bytesRemaining -= wrappedBytesRead.size(); 1579836cfa6SBrandon Kim if (bytesRemaining != 0) 1589836cfa6SBrandon Kim { 1599836cfa6SBrandon Kim throw std::runtime_error(fmt::format( 1609836cfa6SBrandon Kim "[wraparoundRead] Buffer wrapped around but was not able to read " 1619836cfa6SBrandon Kim "all of the requested info. Bytes remaining to read '{}' of '{}'", 1629836cfa6SBrandon Kim bytesRemaining, length)); 1639836cfa6SBrandon Kim } 1649836cfa6SBrandon Kim bytesRead.insert(bytesRead.end(), wrappedBytesRead.begin(), 1659836cfa6SBrandon Kim wrappedBytesRead.end()); 166*26660e9bSBrandon Kim updatedReadOffset = wrappedBytesRead.size(); 1679836cfa6SBrandon Kim } 1689836cfa6SBrandon Kim updateReadPtr(updatedReadOffset); 1699836cfa6SBrandon Kim 1709836cfa6SBrandon Kim return bytesRead; 1719836cfa6SBrandon Kim } 1729836cfa6SBrandon Kim 1737bac2d69SBrandon Kim struct QueueEntryHeader BufferImpl::readEntryHeader(size_t offset) 1747bac2d69SBrandon Kim { 1757bac2d69SBrandon Kim size_t headerSize = sizeof(struct QueueEntryHeader); 1767bac2d69SBrandon Kim // wraparonudRead will throw if it did not read all the bytes, let it 1777bac2d69SBrandon Kim // propagate up the stack 1787bac2d69SBrandon Kim std::vector<uint8_t> bytesRead = wraparoundRead(offset, headerSize); 1797bac2d69SBrandon Kim 1807bac2d69SBrandon Kim return *reinterpret_cast<struct QueueEntryHeader*>(bytesRead.data()); 1817bac2d69SBrandon Kim } 1827bac2d69SBrandon Kim 18340ce08e1SBrandon Kim EntryPair BufferImpl::readEntry(size_t offset) 18440ce08e1SBrandon Kim { 18540ce08e1SBrandon Kim struct QueueEntryHeader entryHeader = readEntryHeader(offset); 18640ce08e1SBrandon Kim 18740ce08e1SBrandon Kim size_t entrySize = entryHeader.entrySize; 18840ce08e1SBrandon Kim 18940ce08e1SBrandon Kim // wraparonudRead may throw if entrySize was bigger than the buffer or if it 190*26660e9bSBrandon Kim // was not able to read all the bytes, let it propagate up the stack 191*26660e9bSBrandon Kim // - Use additionalBoundaryCheck parameter to add QueueEntryHeader size to 192*26660e9bSBrandon Kim // boundary condition calculation 19340ce08e1SBrandon Kim std::vector<uint8_t> entry = 19440ce08e1SBrandon Kim wraparoundRead(offset + sizeof(struct QueueEntryHeader), entrySize, 19540ce08e1SBrandon Kim sizeof(struct QueueEntryHeader)); 19640ce08e1SBrandon Kim 19740ce08e1SBrandon Kim // Calculate the checksum 19840ce08e1SBrandon Kim uint8_t* entryHeaderPtr = reinterpret_cast<uint8_t*>(&entryHeader); 19940ce08e1SBrandon Kim uint8_t checksum = std::accumulate( 20040ce08e1SBrandon Kim entryHeaderPtr, entryHeaderPtr + sizeof(struct QueueEntryHeader), 0); 20140ce08e1SBrandon Kim checksum = std::accumulate(std::begin(entry), std::end(entry), checksum); 20240ce08e1SBrandon Kim if (checksum != 0) 20340ce08e1SBrandon Kim { 20440ce08e1SBrandon Kim throw std::runtime_error(fmt::format( 20540ce08e1SBrandon Kim "[readEntry] Checksum was '{}', expected '0'", checksum)); 20640ce08e1SBrandon Kim } 20740ce08e1SBrandon Kim 20840ce08e1SBrandon Kim return {entryHeader, entry}; 20940ce08e1SBrandon Kim } 21040ce08e1SBrandon Kim 211fcbc3db1SBrandon Kim } // namespace bios_bmc_smm_error_logger 212