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 void BufferImpl::updateBmcFlags(const uint32_t newBmcFlag) 123 { 124 constexpr uint8_t bmcFlagsPtrOffset = 125 offsetof(struct CircularBufferHeader, 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 size_t BufferImpl::getQueueOffset() 146 { 147 return sizeof(struct CircularBufferHeader) + 148 boost::endian::little_to_native(cachedBufferHeader.ueRegionSize); 149 } 150 151 std::vector<uint8_t> BufferImpl::wraparoundRead(const uint32_t relativeOffset, 152 const uint32_t length) 153 { 154 const size_t queueSize = 155 boost::endian::little_to_native(cachedBufferHeader.queueSize); 156 157 if (relativeOffset > queueSize) 158 { 159 throw std::runtime_error( 160 fmt::format("[wraparoundRead] relativeOffset '{}' was bigger " 161 "than queueSize '{}'", 162 relativeOffset, queueSize)); 163 } 164 if (length > queueSize) 165 { 166 throw std::runtime_error(fmt::format( 167 "[wraparoundRead] length '{}' was bigger than queueSize '{}'", 168 length, queueSize)); 169 } 170 171 // Do a calculation to see if the read will wraparound 172 const size_t queueOffset = getQueueOffset(); 173 const size_t queueSizeToQueueEnd = queueSize - relativeOffset; 174 size_t numWraparoundBytesToRead = 0; 175 if (length > queueSizeToQueueEnd) 176 { 177 // This means we will wrap, count the bytes that are left to read 178 numWraparoundBytesToRead = length - queueSizeToQueueEnd; 179 } 180 const size_t numBytesToReadTillQueueEnd = length - numWraparoundBytesToRead; 181 182 std::vector<uint8_t> bytesRead = dataInterface->read( 183 queueOffset + relativeOffset, numBytesToReadTillQueueEnd); 184 if (bytesRead.size() != numBytesToReadTillQueueEnd) 185 { 186 throw std::runtime_error( 187 fmt::format("[wraparoundRead] Read '{}' which was not " 188 "the requested length of '{}'", 189 bytesRead.size(), numBytesToReadTillQueueEnd)); 190 } 191 size_t updatedReadPtr = relativeOffset + numBytesToReadTillQueueEnd; 192 if (updatedReadPtr == queueSize) 193 { 194 // If we read all the way up to the end of the queue, we need to 195 // manually wrap the updateReadPtr around to 0 196 updatedReadPtr = 0; 197 } 198 199 // If there are any more bytes to be read beyond the buffer, wrap around and 200 // read from the beginning of the buffer (offset by the queueOffset) 201 if (numWraparoundBytesToRead > 0) 202 { 203 std::vector<uint8_t> wrappedBytesRead = 204 dataInterface->read(queueOffset, numWraparoundBytesToRead); 205 if (numWraparoundBytesToRead != wrappedBytesRead.size()) 206 { 207 throw std::runtime_error(fmt::format( 208 "[wraparoundRead] Buffer wrapped around but read '{}' which " 209 "was not the requested lenght of '{}'", 210 wrappedBytesRead.size(), numWraparoundBytesToRead)); 211 } 212 bytesRead.insert(bytesRead.end(), wrappedBytesRead.begin(), 213 wrappedBytesRead.end()); 214 updatedReadPtr = numWraparoundBytesToRead; 215 } 216 updateReadPtr(updatedReadPtr); 217 218 return bytesRead; 219 } 220 221 struct QueueEntryHeader BufferImpl::readEntryHeader(size_t relativeOffset) 222 { 223 size_t headerSize = sizeof(struct QueueEntryHeader); 224 // wraparonudRead will throw if it did not read all the bytes, let it 225 // propagate up the stack 226 std::vector<uint8_t> bytesRead = wraparoundRead(relativeOffset, headerSize); 227 228 return *reinterpret_cast<struct QueueEntryHeader*>(bytesRead.data()); 229 } 230 231 EntryPair BufferImpl::readEntry(size_t relativeOffset) 232 { 233 struct QueueEntryHeader entryHeader = readEntryHeader(relativeOffset); 234 size_t entrySize = boost::endian::little_to_native(entryHeader.entrySize); 235 236 // wraparonudRead may throw if entrySize was bigger than the buffer or if it 237 // was not able to read all the bytes, let it propagate up the stack 238 std::vector<uint8_t> entry = wraparoundRead( 239 relativeOffset + sizeof(struct QueueEntryHeader), 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 queueSize = 264 boost::endian::little_to_native(cachedBufferHeader.queueSize); 265 size_t currentBiosWritePtr = 266 boost::endian::little_to_native(cachedBufferHeader.biosWritePtr); 267 if (currentBiosWritePtr > queueSize) 268 { 269 throw std::runtime_error(fmt::format( 270 "[readErrorLogs] currentBiosWritePtr was '{}' which was bigger " 271 "than queueSize '{}'", 272 currentBiosWritePtr, queueSize)); 273 } 274 size_t currentReadPtr = 275 boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr); 276 if (currentReadPtr > queueSize) 277 { 278 throw std::runtime_error(fmt::format( 279 "[readErrorLogs] currentReadPtr was '{}' which was bigger " 280 "than queueSize '{}'", 281 currentReadPtr, queueSize)); 282 } 283 284 size_t bytesToRead; 285 if (currentBiosWritePtr == currentReadPtr) 286 { 287 // No new payload was detected, return an empty vector gracefully 288 return {}; 289 } 290 291 if (currentBiosWritePtr > currentReadPtr) 292 { 293 // Simply subtract in this case 294 bytesToRead = currentBiosWritePtr - currentReadPtr; 295 } 296 else 297 { 298 // Calculate the bytes to the "end" (QueueSize - ReadPtr) + 299 // bytes to read from the "beginning" (0 + WritePtr) 300 bytesToRead = (queueSize - currentReadPtr) + currentBiosWritePtr; 301 } 302 303 size_t byteRead = 0; 304 std::vector<EntryPair> entryPairs; 305 while (byteRead < bytesToRead) 306 { 307 EntryPair entryPair = readEntry(currentReadPtr); 308 byteRead += sizeof(struct QueueEntryHeader) + entryPair.second.size(); 309 entryPairs.push_back(entryPair); 310 311 // Note: readEntry() will update cachedBufferHeader.bmcReadPtr 312 currentReadPtr = 313 boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr); 314 } 315 if (currentBiosWritePtr != currentReadPtr) 316 { 317 throw std::runtime_error(fmt::format( 318 "[readErrorLogs] biosWritePtr '{}' and bmcReaddPtr '{}' " 319 "are not identical after reading through all the logs", 320 currentBiosWritePtr, currentReadPtr)); 321 } 322 return entryPairs; 323 } 324 325 } // namespace bios_bmc_smm_error_logger 326