#include "buffer.hpp" #include "pci_handler.hpp" #include #include #include #include #include #include #include #include #include #include #include namespace bios_bmc_smm_error_logger { BufferImpl::BufferImpl(std::unique_ptr dataInterface) : dataInterface(std::move(dataInterface)){}; void BufferImpl::initialize(uint32_t bmcInterfaceVersion, uint16_t queueSize, uint16_t ueRegionSize, const std::array& magicNumber) { const size_t memoryRegionSize = dataInterface->getMemoryRegionSize(); const size_t proposedBufferSize = sizeof(struct CircularBufferHeader) + ueRegionSize + queueSize; if (proposedBufferSize > memoryRegionSize) { throw std::runtime_error(fmt::format( "[initialize] Proposed region size '{}' is bigger than the " "BMC's allocated MMIO region of '{}'", proposedBufferSize, memoryRegionSize)); } // Initialize the whole buffer with 0x00 const std::vector emptyVector(proposedBufferSize, 0); size_t byteWritten = dataInterface->write(0, emptyVector); if (byteWritten != proposedBufferSize) { throw std::runtime_error( fmt::format("[initialize] Only erased '{}'", byteWritten)); } // Create an initial buffer header and write to it struct CircularBufferHeader initializationHeader = {}; initializationHeader.bmcInterfaceVersion = boost::endian::native_to_little(bmcInterfaceVersion); initializationHeader.queueSize = boost::endian::native_to_little(queueSize); initializationHeader.ueRegionSize = boost::endian::native_to_little(ueRegionSize); std::transform(magicNumber.begin(), magicNumber.end(), initializationHeader.magicNumber.begin(), [](uint32_t number) -> little_uint32_t { return boost::endian::native_to_little(number); }); uint8_t* initializationHeaderPtr = reinterpret_cast(&initializationHeader); size_t initializationHeaderSize = sizeof(initializationHeader); byteWritten = dataInterface->write( 0, std::span(initializationHeaderPtr, initializationHeaderPtr + initializationHeaderSize)); if (byteWritten != initializationHeaderSize) { throw std::runtime_error(fmt::format( "[initialize] Only wrote '{}' bytes of the header", byteWritten)); } cachedBufferHeader = initializationHeader; } void BufferImpl::readBufferHeader() { size_t headerSize = sizeof(struct CircularBufferHeader); std::vector bytesRead = dataInterface->read(/* offset */ 0, headerSize); if (bytesRead.size() != headerSize) { throw std::runtime_error( fmt::format("Buffer header read only read '{}', expected '{}'", bytesRead.size(), headerSize)); } cachedBufferHeader = *reinterpret_cast(bytesRead.data()); }; struct CircularBufferHeader BufferImpl::getCachedBufferHeader() const { return cachedBufferHeader; } void BufferImpl::updateReadPtr(const uint32_t newReadPtr) { constexpr uint8_t bmcReadPtrOffset = offsetof(struct CircularBufferHeader, bmcReadPtr); little_uint16_t truncatedReadPtr = boost::endian::native_to_little(newReadPtr & 0xffff); uint8_t* truncatedReadPtrPtr = reinterpret_cast(&truncatedReadPtr); size_t writtenSize = dataInterface->write( bmcReadPtrOffset, std::span{ truncatedReadPtrPtr, truncatedReadPtrPtr + sizeof(little_uint16_t)}); if (writtenSize != sizeof(little_uint16_t)) { throw std::runtime_error(fmt::format( "[updateReadPtr] Wrote '{}' bytes, instead of expected '{}'", writtenSize, sizeof(little_uint16_t))); } cachedBufferHeader.bmcReadPtr = truncatedReadPtr; } size_t BufferImpl::getQueueOffset() { return sizeof(struct CircularBufferHeader) + boost::endian::little_to_native(cachedBufferHeader.ueRegionSize); } std::vector BufferImpl::wraparoundRead(const uint32_t offset, const uint32_t length, const uint32_t additionalBoundaryCheck) { const size_t queueSize = boost::endian::little_to_native(cachedBufferHeader.queueSize); if (length + additionalBoundaryCheck > queueSize) { throw std::runtime_error(fmt::format( "[wraparoundRead] length '{}' + additionalBoundaryCheck '{}' " "was bigger than queueSize '{}'", length, additionalBoundaryCheck, queueSize)); } // Do a first read up to the end of the buffer (dataInerface->read should // only read up to the end of the buffer) std::vector bytesRead = dataInterface->read(offset, length); size_t updatedReadOffset = offset + bytesRead.size(); size_t bytesRemaining = length - bytesRead.size(); // If there are any more bytes to be read beyond the buffer, wrap around and // read from the beginning of the buffer (offset by the queueOffset) size_t queueOffset = getQueueOffset(); if (bytesRemaining > 0) { std::vector wrappedBytesRead = dataInterface->read(queueOffset, bytesRemaining); bytesRemaining -= wrappedBytesRead.size(); if (bytesRemaining != 0) { throw std::runtime_error(fmt::format( "[wraparoundRead] Buffer wrapped around but was not able to read " "all of the requested info. Bytes remaining to read '{}' of '{}'", bytesRemaining, length)); } bytesRead.insert(bytesRead.end(), wrappedBytesRead.begin(), wrappedBytesRead.end()); updatedReadOffset = wrappedBytesRead.size(); } updateReadPtr(updatedReadOffset); return bytesRead; } struct QueueEntryHeader BufferImpl::readEntryHeader(size_t offset) { size_t headerSize = sizeof(struct QueueEntryHeader); // wraparonudRead will throw if it did not read all the bytes, let it // propagate up the stack std::vector bytesRead = wraparoundRead(offset, headerSize); return *reinterpret_cast(bytesRead.data()); } EntryPair BufferImpl::readEntry(size_t offset) { struct QueueEntryHeader entryHeader = readEntryHeader(offset); size_t entrySize = entryHeader.entrySize; // wraparonudRead may throw if entrySize was bigger than the buffer or if it // was not able to read all the bytes, let it propagate up the stack // - Use additionalBoundaryCheck parameter to add QueueEntryHeader size to // boundary condition calculation std::vector entry = wraparoundRead(offset + sizeof(struct QueueEntryHeader), entrySize, sizeof(struct QueueEntryHeader)); // Calculate the checksum uint8_t* entryHeaderPtr = reinterpret_cast(&entryHeader); uint8_t checksum = std::accumulate( entryHeaderPtr, entryHeaderPtr + sizeof(struct QueueEntryHeader), 0); checksum = std::accumulate(std::begin(entry), std::end(entry), checksum); if (checksum != 0) { throw std::runtime_error(fmt::format( "[readEntry] Checksum was '{}', expected '0'", checksum)); } return {entryHeader, entry}; } } // namespace bios_bmc_smm_error_logger