1 #include "buffer.hpp" 2 3 #include "pci_handler.hpp" 4 5 #include <fmt/format.h> 6 7 #include <boost/endian/arithmetic.hpp> 8 #include <boost/endian/conversion.hpp> 9 10 #include <algorithm> 11 #include <array> 12 #include <cstddef> 13 #include <cstdint> 14 #include <memory> 15 #include <numeric> 16 #include <span> 17 #include <vector> 18 19 namespace bios_bmc_smm_error_logger 20 { 21 22 BufferImpl::BufferImpl(std::unique_ptr<DataInterface> dataInterface) : 23 dataInterface(std::move(dataInterface)){}; 24 25 void BufferImpl::initialize(uint32_t bmcInterfaceVersion, uint16_t queueSize, 26 uint16_t ueRegionSize, 27 const std::array<uint32_t, 4>& magicNumber) 28 { 29 const size_t memoryRegionSize = dataInterface->getMemoryRegionSize(); 30 const size_t proposedBufferSize = 31 sizeof(struct CircularBufferHeader) + ueRegionSize + queueSize; 32 if (proposedBufferSize > memoryRegionSize) 33 { 34 throw std::runtime_error(fmt::format( 35 "[initialize] Proposed region size '{}' is bigger than the " 36 "BMC's allocated MMIO region of '{}'", 37 proposedBufferSize, memoryRegionSize)); 38 } 39 40 // Initialize the whole buffer with 0x00 41 const std::vector<uint8_t> emptyVector(proposedBufferSize, 0); 42 size_t byteWritten = dataInterface->write(0, emptyVector); 43 if (byteWritten != proposedBufferSize) 44 { 45 throw std::runtime_error( 46 fmt::format("[initialize] Only erased '{}'", byteWritten)); 47 } 48 49 // Create an initial buffer header and write to it 50 struct CircularBufferHeader initializationHeader = {}; 51 initializationHeader.bmcInterfaceVersion = 52 boost::endian::native_to_little(bmcInterfaceVersion); 53 initializationHeader.queueSize = boost::endian::native_to_little(queueSize); 54 initializationHeader.ueRegionSize = 55 boost::endian::native_to_little(ueRegionSize); 56 std::transform(magicNumber.begin(), magicNumber.end(), 57 initializationHeader.magicNumber.begin(), 58 [](uint32_t number) -> little_uint32_t { 59 return boost::endian::native_to_little(number); 60 }); 61 62 uint8_t* initializationHeaderPtr = 63 reinterpret_cast<uint8_t*>(&initializationHeader); 64 size_t initializationHeaderSize = sizeof(initializationHeader); 65 byteWritten = dataInterface->write( 66 0, std::span<const uint8_t>(initializationHeaderPtr, 67 initializationHeaderPtr + 68 initializationHeaderSize)); 69 if (byteWritten != initializationHeaderSize) 70 { 71 throw std::runtime_error(fmt::format( 72 "[initialize] Only wrote '{}' bytes of the header", byteWritten)); 73 } 74 cachedBufferHeader = initializationHeader; 75 } 76 77 void BufferImpl::readBufferHeader() 78 { 79 size_t headerSize = sizeof(struct CircularBufferHeader); 80 std::vector<uint8_t> bytesRead = 81 dataInterface->read(/* offset */ 0, headerSize); 82 83 if (bytesRead.size() != headerSize) 84 { 85 throw std::runtime_error( 86 fmt::format("Buffer header read only read '{}', expected '{}'", 87 bytesRead.size(), headerSize)); 88 } 89 90 cachedBufferHeader = 91 *reinterpret_cast<struct CircularBufferHeader*>(bytesRead.data()); 92 }; 93 94 struct CircularBufferHeader BufferImpl::getCachedBufferHeader() const 95 { 96 return cachedBufferHeader; 97 } 98 99 void BufferImpl::updateReadPtr(const uint32_t newReadPtr) 100 { 101 constexpr uint8_t bmcReadPtrOffset = 102 offsetof(struct CircularBufferHeader, bmcReadPtr); 103 104 little_uint16_t truncatedReadPtr = 105 boost::endian::native_to_little(newReadPtr & 0xffff); 106 uint8_t* truncatedReadPtrPtr = 107 reinterpret_cast<uint8_t*>(&truncatedReadPtr); 108 109 size_t writtenSize = dataInterface->write( 110 bmcReadPtrOffset, std::span<const uint8_t>{ 111 truncatedReadPtrPtr, 112 truncatedReadPtrPtr + sizeof(little_uint16_t)}); 113 if (writtenSize != sizeof(little_uint16_t)) 114 { 115 throw std::runtime_error(fmt::format( 116 "[updateReadPtr] Wrote '{}' bytes, instead of expected '{}'", 117 writtenSize, sizeof(little_uint16_t))); 118 } 119 cachedBufferHeader.bmcReadPtr = truncatedReadPtr; 120 } 121 122 size_t BufferImpl::getQueueOffset() 123 { 124 return sizeof(struct CircularBufferHeader) + 125 boost::endian::little_to_native(cachedBufferHeader.ueRegionSize); 126 } 127 128 std::vector<uint8_t> 129 BufferImpl::wraparoundRead(const uint32_t offset, const uint32_t length, 130 const uint32_t additionalBoundaryCheck) 131 { 132 const size_t queueSize = 133 boost::endian::little_to_native(cachedBufferHeader.queueSize); 134 135 if (length + additionalBoundaryCheck > queueSize) 136 { 137 throw std::runtime_error(fmt::format( 138 "[wraparoundRead] length '{}' + additionalBoundaryCheck '{}' " 139 "was bigger than queueSize '{}'", 140 length, additionalBoundaryCheck, queueSize)); 141 } 142 143 // Do a first read up to the end of the buffer (dataInerface->read should 144 // only read up to the end of the buffer) 145 std::vector<uint8_t> bytesRead = dataInterface->read(offset, length); 146 size_t updatedReadOffset = offset + bytesRead.size(); 147 size_t bytesRemaining = length - bytesRead.size(); 148 149 // If there are any more bytes to be read beyond the buffer, wrap around and 150 // read from the beginning of the buffer (offset by the queueOffset) 151 size_t queueOffset = getQueueOffset(); 152 if (bytesRemaining > 0) 153 { 154 std::vector<uint8_t> wrappedBytesRead = 155 dataInterface->read(queueOffset, bytesRemaining); 156 bytesRemaining -= wrappedBytesRead.size(); 157 if (bytesRemaining != 0) 158 { 159 throw std::runtime_error(fmt::format( 160 "[wraparoundRead] Buffer wrapped around but was not able to read " 161 "all of the requested info. Bytes remaining to read '{}' of '{}'", 162 bytesRemaining, length)); 163 } 164 bytesRead.insert(bytesRead.end(), wrappedBytesRead.begin(), 165 wrappedBytesRead.end()); 166 updatedReadOffset = wrappedBytesRead.size(); 167 } 168 updateReadPtr(updatedReadOffset); 169 170 return bytesRead; 171 } 172 173 struct QueueEntryHeader BufferImpl::readEntryHeader(size_t offset) 174 { 175 size_t headerSize = sizeof(struct QueueEntryHeader); 176 // wraparonudRead will throw if it did not read all the bytes, let it 177 // propagate up the stack 178 std::vector<uint8_t> bytesRead = wraparoundRead(offset, headerSize); 179 180 return *reinterpret_cast<struct QueueEntryHeader*>(bytesRead.data()); 181 } 182 183 EntryPair BufferImpl::readEntry(size_t offset) 184 { 185 struct QueueEntryHeader entryHeader = readEntryHeader(offset); 186 187 size_t entrySize = entryHeader.entrySize; 188 189 // wraparonudRead may throw if entrySize was bigger than the buffer or if it 190 // was not able to read all the bytes, let it propagate up the stack 191 // - Use additionalBoundaryCheck parameter to add QueueEntryHeader size to 192 // boundary condition calculation 193 std::vector<uint8_t> entry = 194 wraparoundRead(offset + sizeof(struct QueueEntryHeader), entrySize, 195 sizeof(struct QueueEntryHeader)); 196 197 // Calculate the checksum 198 uint8_t* entryHeaderPtr = reinterpret_cast<uint8_t*>(&entryHeader); 199 uint8_t checksum = std::accumulate( 200 entryHeaderPtr, entryHeaderPtr + sizeof(struct QueueEntryHeader), 0); 201 checksum = std::accumulate(std::begin(entry), std::end(entry), checksum); 202 if (checksum != 0) 203 { 204 throw std::runtime_error(fmt::format( 205 "[readEntry] Checksum was '{}', expected '0'", checksum)); 206 } 207 208 return {entryHeader, entry}; 209 } 210 211 } // namespace bios_bmc_smm_error_logger 212