blob: b38862e13618e6e92608d0303e40edf677f7e070 [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)
{
const size_t memoryRegionSize = dataInterface->getMemoryRegionSize();
if (queueSize > memoryRegionSize)
{
throw std::runtime_error(fmt::format(
"[initialize] Proposed queue size '{}' is bigger than the "
"BMC's allocated MMIO region of '{}'",
queueSize, memoryRegionSize));
}
// Initialize the whole buffer with 0x00
const std::vector<uint8_t> emptyVector(queueSize, 0);
size_t byteWritten = dataInterface->write(0, emptyVector);
if (byteWritten != queueSize)
{
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<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(
"[initialize] Only wrote '{}' bytes of the header", 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_uint24_t truncatedReadPtr =
boost::endian::native_to_little(newReadPtr & 0xffffff);
uint8_t* truncatedReadPtrPtr =
reinterpret_cast<uint8_t*>(&truncatedReadPtr);
size_t writtenSize = dataInterface->write(
bmcReadPtrOffset, std::span<const uint8_t>{
truncatedReadPtrPtr,
truncatedReadPtrPtr + sizeof(truncatedReadPtr)});
if (writtenSize != sizeof(truncatedReadPtr))
{
throw std::runtime_error(fmt::format(
"[updateReadPtr] Wrote '{}' bytes, instead of expected '{}'",
writtenSize, sizeof(truncatedReadPtr)));
}
cachedBufferHeader.bmcReadPtr = truncatedReadPtr;
}
void BufferImpl::updateBmcFlags(const uint32_t newBmcFlag)
{
constexpr uint8_t bmcFlagsPtrOffset = offsetof(struct CircularBufferHeader,
bmcFlags);
little_uint32_t littleNewBmcFlag =
boost::endian::native_to_little(newBmcFlag);
uint8_t* littleNewBmcFlagPtr =
reinterpret_cast<uint8_t*>(&littleNewBmcFlag);
size_t writtenSize = dataInterface->write(
bmcFlagsPtrOffset, std::span<const uint8_t>{
littleNewBmcFlagPtr,
littleNewBmcFlagPtr + sizeof(little_uint32_t)});
if (writtenSize != sizeof(little_uint32_t))
{
throw std::runtime_error(fmt::format(
"[updateBmcFlags] Wrote '{}' bytes, instead of expected '{}'",
writtenSize, sizeof(little_uint32_t)));
}
cachedBufferHeader.bmcFlags = littleNewBmcFlag;
}
size_t BufferImpl::getQueueOffset()
{
return sizeof(struct CircularBufferHeader) +
boost::endian::little_to_native(cachedBufferHeader.ueRegionSize);
}
std::vector<uint8_t> BufferImpl::wraparoundRead(const uint32_t relativeOffset,
const uint32_t length)
{
const size_t maxOffset = getMaxOffset();
if (relativeOffset > maxOffset)
{
throw std::runtime_error(
fmt::format("[wraparoundRead] relativeOffset '{}' was bigger "
"than maxOffset '{}'",
relativeOffset, maxOffset));
}
if (length > maxOffset)
{
throw std::runtime_error(fmt::format(
"[wraparoundRead] length '{}' was bigger than maxOffset '{}'",
length, maxOffset));
}
// Do a calculation to see if the read will wraparound
const size_t queueOffset = getQueueOffset();
const size_t writableSpace = maxOffset - relativeOffset;
size_t numWraparoundBytesToRead = 0;
if (length > writableSpace)
{
// This means we will wrap, count the bytes that are left to read
numWraparoundBytesToRead = length - writableSpace;
}
const size_t numBytesToReadTillQueueEnd = length - numWraparoundBytesToRead;
std::vector<uint8_t> bytesRead = dataInterface->read(
queueOffset + relativeOffset, numBytesToReadTillQueueEnd);
if (bytesRead.size() != numBytesToReadTillQueueEnd)
{
throw std::runtime_error(
fmt::format("[wraparoundRead] Read '{}' which was not "
"the requested length of '{}'",
bytesRead.size(), numBytesToReadTillQueueEnd));
}
size_t updatedReadPtr = relativeOffset + numBytesToReadTillQueueEnd;
if (updatedReadPtr == maxOffset)
{
// If we read all the way up to the end of the queue, we need to
// manually wrap the updateReadPtr around to 0
updatedReadPtr = 0;
}
// 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 (numWraparoundBytesToRead > 0)
{
std::vector<uint8_t> wrappedBytesRead =
dataInterface->read(queueOffset, numWraparoundBytesToRead);
if (numWraparoundBytesToRead != wrappedBytesRead.size())
{
throw std::runtime_error(fmt::format(
"[wraparoundRead] Buffer wrapped around but read '{}' which "
"was not the requested lenght of '{}'",
wrappedBytesRead.size(), numWraparoundBytesToRead));
}
bytesRead.insert(bytesRead.end(), wrappedBytesRead.begin(),
wrappedBytesRead.end());
updatedReadPtr = numWraparoundBytesToRead;
}
updateReadPtr(updatedReadPtr);
return bytesRead;
}
struct QueueEntryHeader BufferImpl::readEntryHeader()
{
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(
boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr),
headerSize);
return *reinterpret_cast<struct QueueEntryHeader*>(bytesRead.data());
}
EntryPair BufferImpl::readEntry()
{
struct QueueEntryHeader entryHeader = readEntryHeader();
size_t entrySize = boost::endian::little_to_native(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
std::vector<uint8_t> entry = wraparoundRead(
boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr),
entrySize);
// Calculate the checksum
uint8_t* entryHeaderPtr = reinterpret_cast<uint8_t*>(&entryHeader);
uint8_t checksum =
std::accumulate(entryHeaderPtr,
entryHeaderPtr + sizeof(struct QueueEntryHeader), 0,
std::bit_xor<void>()) ^
std::accumulate(entry.begin(), entry.end(), 0, std::bit_xor<void>());
if (checksum != 0)
{
throw std::runtime_error(fmt::format(
"[readEntry] Checksum was '{}', expected '0'", checksum));
}
return {entryHeader, entry};
}
std::vector<EntryPair> BufferImpl::readErrorLogs()
{
// Reading the buffer header will update the cachedBufferHeader
readBufferHeader();
const size_t maxOffset = getMaxOffset();
size_t currentBiosWritePtr =
boost::endian::little_to_native(cachedBufferHeader.biosWritePtr);
if (currentBiosWritePtr > maxOffset)
{
throw std::runtime_error(fmt::format(
"[readErrorLogs] currentBiosWritePtr was '{}' which was bigger "
"than maxOffset '{}'",
currentBiosWritePtr, maxOffset));
}
size_t currentReadPtr =
boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr);
if (currentReadPtr > maxOffset)
{
throw std::runtime_error(fmt::format(
"[readErrorLogs] currentReadPtr was '{}' which was bigger "
"than maxOffset '{}'",
currentReadPtr, maxOffset));
}
size_t bytesToRead;
if (currentBiosWritePtr == currentReadPtr)
{
// No new payload was detected, return an empty vector gracefully
return {};
}
if (currentBiosWritePtr > currentReadPtr)
{
// Simply subtract in this case
bytesToRead = currentBiosWritePtr - currentReadPtr;
}
else
{
// Calculate the bytes to the "end" (maxOffset - ReadPtr) +
// bytes to read from the "beginning" (0 + WritePtr)
bytesToRead = (maxOffset - currentReadPtr) + currentBiosWritePtr;
}
size_t byteRead = 0;
std::vector<EntryPair> entryPairs;
while (byteRead < bytesToRead)
{
EntryPair entryPair = readEntry();
byteRead += sizeof(struct QueueEntryHeader) + entryPair.second.size();
entryPairs.push_back(entryPair);
// Note: readEntry() will update cachedBufferHeader.bmcReadPtr
currentReadPtr =
boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr);
}
if (currentBiosWritePtr != currentReadPtr)
{
throw std::runtime_error(fmt::format(
"[readErrorLogs] biosWritePtr '{}' and bmcReaddPtr '{}' "
"are not identical after reading through all the logs",
currentBiosWritePtr, currentReadPtr));
}
return entryPairs;
}
size_t BufferImpl::getMaxOffset()
{
size_t queueSize =
boost::endian::little_to_native(cachedBufferHeader.queueSize);
size_t ueRegionSize =
boost::endian::little_to_native(cachedBufferHeader.ueRegionSize);
return queueSize - ueRegionSize - sizeof(struct CircularBufferHeader);
}
} // namespace bios_bmc_smm_error_logger