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