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