blob: fca8837d3f80c203c2f5382e9cd6d155a0a764ce [file] [log] [blame]
#include "buffer.hpp"
#include "pci_handler.hpp"
#include <fmt/format.h>
#include <boost/endian/arithmetic.hpp>
#include <boost/endian/conversion.hpp>
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <numeric>
#include <span>
#include <vector>
namespace bios_bmc_smm_error_logger
{
BufferImpl::BufferImpl(std::unique_ptr<DataInterface> dataInterface) :
dataInterface(std::move(dataInterface)){};
void BufferImpl::initialize(uint32_t bmcInterfaceVersion, uint16_t queueSize,
uint16_t ueRegionSize,
const std::array<uint32_t, 4>& magicNumber)
{
// Initialize the whole buffer with 0x00
const size_t memoryRegionSize = dataInterface->getMemoryRegionSize();
const std::vector<uint8_t> emptyVector(memoryRegionSize, 0);
size_t byteWritten = dataInterface->write(0, emptyVector);
if (byteWritten != memoryRegionSize)
{
throw std::runtime_error(
fmt::format("Buffer initialization 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<uint8_t*>(&initializationHeader);
size_t initializationHeaderSize = sizeof(initializationHeader);
byteWritten = dataInterface->write(
0, std::span<const uint8_t>(initializationHeaderPtr,
initializationHeaderPtr +
initializationHeaderSize));
if (byteWritten != initializationHeaderSize)
{
throw std::runtime_error(fmt::format(
"Buffer initialization buffer header write only wrote '{}'",
byteWritten));
}
cachedBufferHeader = initializationHeader;
}
void BufferImpl::readBufferHeader()
{
size_t headerSize = sizeof(struct CircularBufferHeader);
std::vector<uint8_t> 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<struct CircularBufferHeader*>(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<uint8_t*>(&truncatedReadPtr);
size_t writtenSize = dataInterface->write(
bmcReadPtrOffset, std::span<const uint8_t>{
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<uint8_t>
BufferImpl::wraparoundRead(const uint32_t offset, const uint32_t length,
const uint32_t additionalBoundaryCheck)
{
const size_t memoryRegionSize = dataInterface->getMemoryRegionSize();
size_t queueOffset = getQueueOffset();
if (queueOffset + length + additionalBoundaryCheck > memoryRegionSize)
{
throw std::runtime_error(fmt::format(
"[wraparoundRead] queueOffset '{}' + length '{}' "
"+ additionalBoundaryCheck '{}' + was bigger "
"than memoryRegionSize '{}'",
queueOffset, length, additionalBoundaryCheck, memoryRegionSize));
}
// 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<uint8_t> 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)
if (bytesRemaining > 0)
{
std::vector<uint8_t> 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 = queueOffset + 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<uint8_t> bytesRead = wraparoundRead(offset, headerSize);
return *reinterpret_cast<struct QueueEntryHeader*>(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 bytes, let it propagate up the stack
// - Use additionalBoundaryCheck parameter to tighten the boundary check
std::vector<uint8_t> entry =
wraparoundRead(offset + sizeof(struct QueueEntryHeader), entrySize,
sizeof(struct QueueEntryHeader));
// Calculate the checksum
uint8_t* entryHeaderPtr = reinterpret_cast<uint8_t*>(&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