blob: 04e9861ef79dc6734240000b45b8b6fb0297dd4b [file] [log] [blame]
Brandon Kimfcbc3db2022-06-06 21:26:18 -07001#include "buffer.hpp"
2
3#include "pci_handler.hpp"
4
5#include <fmt/format.h>
6
Brandon Kim17ee1a92022-06-07 21:04:08 -07007#include <boost/endian/arithmetic.hpp>
8#include <boost/endian/conversion.hpp>
9
10#include <algorithm>
Brandon Kimfcbc3db2022-06-06 21:26:18 -070011#include <array>
Brandon Kimcf0b9752022-06-15 10:32:21 -070012#include <cstddef>
Brandon Kimfcbc3db2022-06-06 21:26:18 -070013#include <cstdint>
14#include <memory>
Brandon Kim40ce08e2022-06-15 16:58:44 -070015#include <numeric>
Brandon Kimfcbc3db2022-06-06 21:26:18 -070016#include <span>
17#include <vector>
18
19namespace bios_bmc_smm_error_logger
20{
21
22BufferImpl::BufferImpl(std::unique_ptr<DataInterface> dataInterface) :
23 dataInterface(std::move(dataInterface)){};
24
25void BufferImpl::initialize(uint32_t bmcInterfaceVersion, uint16_t queueSize,
26 uint16_t ueRegionSize,
27 const std::array<uint32_t, 4>& magicNumber)
28{
Brandon Kimfcbc3db2022-06-06 21:26:18 -070029 const size_t memoryRegionSize = dataInterface->getMemoryRegionSize();
Brandon Kim26660e92022-06-15 16:58:44 -070030 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);
Brandon Kimfcbc3db2022-06-06 21:26:18 -070042 size_t byteWritten = dataInterface->write(0, emptyVector);
Brandon Kim26660e92022-06-15 16:58:44 -070043 if (byteWritten != proposedBufferSize)
Brandon Kimfcbc3db2022-06-06 21:26:18 -070044 {
45 throw std::runtime_error(
Brandon Kim26660e92022-06-15 16:58:44 -070046 fmt::format("[initialize] Only erased '{}'", byteWritten));
Brandon Kimfcbc3db2022-06-06 21:26:18 -070047 }
48
49 // Create an initial buffer header and write to it
50 struct CircularBufferHeader initializationHeader = {};
Brandon Kim17ee1a92022-06-07 21:04:08 -070051 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 });
Brandon Kimfcbc3db2022-06-06 21:26:18 -070061
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(
Brandon Kim26660e92022-06-15 16:58:44 -070072 "[initialize] Only wrote '{}' bytes of the header", byteWritten));
Brandon Kimfcbc3db2022-06-06 21:26:18 -070073 }
Brandon Kim60cab572022-06-15 14:20:05 -070074 cachedBufferHeader = initializationHeader;
Brandon Kimfcbc3db2022-06-06 21:26:18 -070075}
76
Brandon Kim17ee1a92022-06-07 21:04:08 -070077void 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 =
Brandon Kim60cab572022-06-15 14:20:05 -070091 *reinterpret_cast<struct CircularBufferHeader*>(bytesRead.data());
Brandon Kim17ee1a92022-06-07 21:04:08 -070092};
93
94struct CircularBufferHeader BufferImpl::getCachedBufferHeader() const
95{
96 return cachedBufferHeader;
97}
98
Brandon Kimcf0b9752022-06-15 10:32:21 -070099void 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
Brandon Kim9836cfa2022-06-15 11:21:11 -0700122size_t BufferImpl::getQueueOffset()
123{
124 return sizeof(struct CircularBufferHeader) +
125 boost::endian::little_to_native(cachedBufferHeader.ueRegionSize);
126}
127
128std::vector<uint8_t>
129 BufferImpl::wraparoundRead(const uint32_t offset, const uint32_t length,
130 const uint32_t additionalBoundaryCheck)
131{
Brandon Kim26660e92022-06-15 16:58:44 -0700132 const size_t queueSize =
133 boost::endian::little_to_native(cachedBufferHeader.queueSize);
Brandon Kim9836cfa2022-06-15 11:21:11 -0700134
Brandon Kim26660e92022-06-15 16:58:44 -0700135 if (length + additionalBoundaryCheck > queueSize)
Brandon Kim9836cfa2022-06-15 11:21:11 -0700136 {
137 throw std::runtime_error(fmt::format(
Brandon Kim26660e92022-06-15 16:58:44 -0700138 "[wraparoundRead] length '{}' + additionalBoundaryCheck '{}' "
139 "was bigger than queueSize '{}'",
140 length, additionalBoundaryCheck, queueSize));
Brandon Kim9836cfa2022-06-15 11:21:11 -0700141 }
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)
Brandon Kim26660e92022-06-15 16:58:44 -0700151 size_t queueOffset = getQueueOffset();
Brandon Kim9836cfa2022-06-15 11:21:11 -0700152 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());
Brandon Kim26660e92022-06-15 16:58:44 -0700166 updatedReadOffset = wrappedBytesRead.size();
Brandon Kim9836cfa2022-06-15 11:21:11 -0700167 }
168 updateReadPtr(updatedReadOffset);
169
170 return bytesRead;
171}
172
Brandon Kim7bac2d62022-06-07 21:37:51 -0700173struct 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
Brandon Kim40ce08e2022-06-15 16:58:44 -0700183EntryPair 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
Brandon Kim26660e92022-06-15 16:58:44 -0700190 // 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
Brandon Kim40ce08e2022-06-15 16:58:44 -0700193 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
Brandon Kimfcbc3db2022-06-06 21:26:18 -0700211} // namespace bios_bmc_smm_error_logger