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 if (queueSize > memoryRegionSize) 31 { 32 throw std::runtime_error(fmt::format( 33 "[initialize] Proposed queue size '{}' is bigger than the " 34 "BMC's allocated MMIO region of '{}'", 35 queueSize, memoryRegionSize)); 36 } 37 38 // Initialize the whole buffer with 0x00 39 const std::vector<uint8_t> emptyVector(queueSize, 0); 40 size_t byteWritten = dataInterface->write(0, emptyVector); 41 if (byteWritten != queueSize) 42 { 43 throw std::runtime_error( 44 fmt::format("[initialize] Only erased '{}'", byteWritten)); 45 } 46 47 // Create an initial buffer header and write to it 48 struct CircularBufferHeader initializationHeader = {}; 49 initializationHeader.bmcInterfaceVersion = 50 boost::endian::native_to_little(bmcInterfaceVersion); 51 initializationHeader.queueSize = boost::endian::native_to_little(queueSize); 52 initializationHeader.ueRegionSize = 53 boost::endian::native_to_little(ueRegionSize); 54 std::transform(magicNumber.begin(), magicNumber.end(), 55 initializationHeader.magicNumber.begin(), 56 [](uint32_t number) -> little_uint32_t { 57 return boost::endian::native_to_little(number); 58 }); 59 60 uint8_t* initializationHeaderPtr = 61 reinterpret_cast<uint8_t*>(&initializationHeader); 62 size_t initializationHeaderSize = sizeof(initializationHeader); 63 byteWritten = dataInterface->write( 64 0, std::span<const uint8_t>(initializationHeaderPtr, 65 initializationHeaderPtr + 66 initializationHeaderSize)); 67 if (byteWritten != initializationHeaderSize) 68 { 69 throw std::runtime_error(fmt::format( 70 "[initialize] Only wrote '{}' bytes of the header", byteWritten)); 71 } 72 cachedBufferHeader = initializationHeader; 73 } 74 75 void BufferImpl::readBufferHeader() 76 { 77 size_t headerSize = sizeof(struct CircularBufferHeader); 78 std::vector<uint8_t> bytesRead = 79 dataInterface->read(/*offset=*/0, headerSize); 80 81 if (bytesRead.size() != headerSize) 82 { 83 throw std::runtime_error( 84 fmt::format("Buffer header read only read '{}', expected '{}'", 85 bytesRead.size(), headerSize)); 86 } 87 88 cachedBufferHeader = 89 *reinterpret_cast<struct CircularBufferHeader*>(bytesRead.data()); 90 }; 91 92 struct CircularBufferHeader BufferImpl::getCachedBufferHeader() const 93 { 94 return cachedBufferHeader; 95 } 96 97 void BufferImpl::updateReadPtr(const uint32_t newReadPtr) 98 { 99 constexpr uint8_t bmcReadPtrOffset = 100 offsetof(struct CircularBufferHeader, bmcReadPtr); 101 102 little_uint24_t truncatedReadPtr = 103 boost::endian::native_to_little(newReadPtr & 0xffffff); 104 uint8_t* truncatedReadPtrPtr = 105 reinterpret_cast<uint8_t*>(&truncatedReadPtr); 106 107 size_t writtenSize = dataInterface->write( 108 bmcReadPtrOffset, std::span<const uint8_t>{ 109 truncatedReadPtrPtr, 110 truncatedReadPtrPtr + sizeof(truncatedReadPtr)}); 111 if (writtenSize != sizeof(truncatedReadPtr)) 112 { 113 throw std::runtime_error(fmt::format( 114 "[updateReadPtr] Wrote '{}' bytes, instead of expected '{}'", 115 writtenSize, sizeof(truncatedReadPtr))); 116 } 117 cachedBufferHeader.bmcReadPtr = truncatedReadPtr; 118 } 119 120 void BufferImpl::updateBmcFlags(const uint32_t newBmcFlag) 121 { 122 constexpr uint8_t bmcFlagsPtrOffset = 123 offsetof(struct CircularBufferHeader, bmcFlags); 124 125 little_uint32_t littleNewBmcFlag = 126 boost::endian::native_to_little(newBmcFlag); 127 uint8_t* littleNewBmcFlagPtr = 128 reinterpret_cast<uint8_t*>(&littleNewBmcFlag); 129 130 size_t writtenSize = dataInterface->write( 131 bmcFlagsPtrOffset, std::span<const uint8_t>{ 132 littleNewBmcFlagPtr, 133 littleNewBmcFlagPtr + sizeof(little_uint32_t)}); 134 if (writtenSize != sizeof(little_uint32_t)) 135 { 136 throw std::runtime_error(fmt::format( 137 "[updateBmcFlags] Wrote '{}' bytes, instead of expected '{}'", 138 writtenSize, sizeof(little_uint32_t))); 139 } 140 cachedBufferHeader.bmcFlags = littleNewBmcFlag; 141 } 142 143 size_t BufferImpl::getQueueOffset() 144 { 145 return sizeof(struct CircularBufferHeader) + 146 boost::endian::little_to_native(cachedBufferHeader.ueRegionSize); 147 } 148 149 std::vector<uint8_t> BufferImpl::wraparoundRead(const uint32_t relativeOffset, 150 const uint32_t length) 151 { 152 const size_t maxOffset = getMaxOffset(); 153 154 if (relativeOffset > maxOffset) 155 { 156 throw std::runtime_error( 157 fmt::format("[wraparoundRead] relativeOffset '{}' was bigger " 158 "than maxOffset '{}'", 159 relativeOffset, maxOffset)); 160 } 161 if (length > maxOffset) 162 { 163 throw std::runtime_error(fmt::format( 164 "[wraparoundRead] length '{}' was bigger than maxOffset '{}'", 165 length, maxOffset)); 166 } 167 168 // Do a calculation to see if the read will wraparound 169 const size_t queueOffset = getQueueOffset(); 170 const size_t writableSpace = maxOffset - relativeOffset; 171 size_t numWraparoundBytesToRead = 0; 172 if (length > writableSpace) 173 { 174 // This means we will wrap, count the bytes that are left to read 175 numWraparoundBytesToRead = length - writableSpace; 176 } 177 const size_t numBytesToReadTillQueueEnd = length - numWraparoundBytesToRead; 178 179 std::vector<uint8_t> bytesRead = dataInterface->read( 180 queueOffset + relativeOffset, numBytesToReadTillQueueEnd); 181 if (bytesRead.size() != numBytesToReadTillQueueEnd) 182 { 183 throw std::runtime_error( 184 fmt::format("[wraparoundRead] Read '{}' which was not " 185 "the requested length of '{}'", 186 bytesRead.size(), numBytesToReadTillQueueEnd)); 187 } 188 size_t updatedReadPtr = relativeOffset + numBytesToReadTillQueueEnd; 189 if (updatedReadPtr == maxOffset) 190 { 191 // If we read all the way up to the end of the queue, we need to 192 // manually wrap the updateReadPtr around to 0 193 updatedReadPtr = 0; 194 } 195 196 // If there are any more bytes to be read beyond the buffer, wrap around and 197 // read from the beginning of the buffer (offset by the queueOffset) 198 if (numWraparoundBytesToRead > 0) 199 { 200 std::vector<uint8_t> wrappedBytesRead = 201 dataInterface->read(queueOffset, numWraparoundBytesToRead); 202 if (numWraparoundBytesToRead != wrappedBytesRead.size()) 203 { 204 throw std::runtime_error(fmt::format( 205 "[wraparoundRead] Buffer wrapped around but read '{}' which " 206 "was not the requested lenght of '{}'", 207 wrappedBytesRead.size(), numWraparoundBytesToRead)); 208 } 209 bytesRead.insert(bytesRead.end(), wrappedBytesRead.begin(), 210 wrappedBytesRead.end()); 211 updatedReadPtr = numWraparoundBytesToRead; 212 } 213 updateReadPtr(updatedReadPtr); 214 215 return bytesRead; 216 } 217 218 struct QueueEntryHeader BufferImpl::readEntryHeader() 219 { 220 size_t headerSize = sizeof(struct QueueEntryHeader); 221 // wraparonudRead will throw if it did not read all the bytes, let it 222 // propagate up the stack 223 std::vector<uint8_t> bytesRead = wraparoundRead( 224 boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr), 225 headerSize); 226 227 return *reinterpret_cast<struct QueueEntryHeader*>(bytesRead.data()); 228 } 229 230 EntryPair BufferImpl::readEntry() 231 { 232 struct QueueEntryHeader entryHeader = readEntryHeader(); 233 size_t entrySize = boost::endian::little_to_native(entryHeader.entrySize); 234 235 // wraparonudRead may throw if entrySize was bigger than the buffer or if it 236 // was not able to read all the bytes, let it propagate up the stack 237 std::vector<uint8_t> entry = wraparoundRead( 238 boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr), 239 entrySize); 240 241 // Calculate the checksum 242 uint8_t* entryHeaderPtr = reinterpret_cast<uint8_t*>(&entryHeader); 243 uint8_t checksum = 244 std::accumulate(entryHeaderPtr, 245 entryHeaderPtr + sizeof(struct QueueEntryHeader), 0, 246 std::bit_xor<void>()) ^ 247 std::accumulate(entry.begin(), entry.end(), 0, std::bit_xor<void>()); 248 249 if (checksum != 0) 250 { 251 throw std::runtime_error(fmt::format( 252 "[readEntry] Checksum was '{}', expected '0'", checksum)); 253 } 254 255 return {entryHeader, entry}; 256 } 257 258 std::vector<EntryPair> BufferImpl::readErrorLogs() 259 { 260 // Reading the buffer header will update the cachedBufferHeader 261 readBufferHeader(); 262 263 const size_t maxOffset = getMaxOffset(); 264 size_t currentBiosWritePtr = 265 boost::endian::little_to_native(cachedBufferHeader.biosWritePtr); 266 if (currentBiosWritePtr > maxOffset) 267 { 268 throw std::runtime_error(fmt::format( 269 "[readErrorLogs] currentBiosWritePtr was '{}' which was bigger " 270 "than maxOffset '{}'", 271 currentBiosWritePtr, maxOffset)); 272 } 273 size_t currentReadPtr = 274 boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr); 275 if (currentReadPtr > maxOffset) 276 { 277 throw std::runtime_error(fmt::format( 278 "[readErrorLogs] currentReadPtr was '{}' which was bigger " 279 "than maxOffset '{}'", 280 currentReadPtr, maxOffset)); 281 } 282 283 size_t bytesToRead; 284 if (currentBiosWritePtr == currentReadPtr) 285 { 286 // No new payload was detected, return an empty vector gracefully 287 return {}; 288 } 289 290 if (currentBiosWritePtr > currentReadPtr) 291 { 292 // Simply subtract in this case 293 bytesToRead = currentBiosWritePtr - currentReadPtr; 294 } 295 else 296 { 297 // Calculate the bytes to the "end" (maxOffset - ReadPtr) + 298 // bytes to read from the "beginning" (0 + WritePtr) 299 bytesToRead = (maxOffset - currentReadPtr) + currentBiosWritePtr; 300 } 301 302 size_t byteRead = 0; 303 std::vector<EntryPair> entryPairs; 304 while (byteRead < bytesToRead) 305 { 306 EntryPair entryPair = readEntry(); 307 byteRead += sizeof(struct QueueEntryHeader) + entryPair.second.size(); 308 entryPairs.push_back(entryPair); 309 310 // Note: readEntry() will update cachedBufferHeader.bmcReadPtr 311 currentReadPtr = 312 boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr); 313 } 314 if (currentBiosWritePtr != currentReadPtr) 315 { 316 throw std::runtime_error(fmt::format( 317 "[readErrorLogs] biosWritePtr '{}' and bmcReaddPtr '{}' " 318 "are not identical after reading through all the logs", 319 currentBiosWritePtr, currentReadPtr)); 320 } 321 return entryPairs; 322 } 323 324 size_t BufferImpl::getMaxOffset() 325 { 326 size_t queueSize = 327 boost::endian::little_to_native(cachedBufferHeader.queueSize); 328 size_t ueRegionSize = 329 boost::endian::little_to_native(cachedBufferHeader.ueRegionSize); 330 331 return queueSize - ueRegionSize - sizeof(struct CircularBufferHeader); 332 } 333 334 } // namespace bios_bmc_smm_error_logger 335