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 size_t BufferImpl::getQueueOffset()
123 {
124     return sizeof(struct CircularBufferHeader) +
125            boost::endian::little_to_native(cachedBufferHeader.ueRegionSize);
126 }
127 
128 std::vector<uint8_t> BufferImpl::wraparoundRead(const uint32_t relativeOffset,
129                                                 const uint32_t length)
130 {
131     const size_t queueSize =
132         boost::endian::little_to_native(cachedBufferHeader.queueSize);
133 
134     if (relativeOffset > queueSize)
135     {
136         throw std::runtime_error(
137             fmt::format("[wraparoundRead] relativeOffset '{}' was bigger "
138                         "than queueSize '{}'",
139                         relativeOffset, queueSize));
140     }
141     if (length > queueSize)
142     {
143         throw std::runtime_error(fmt::format(
144             "[wraparoundRead] length '{}' was bigger than queueSize '{}'",
145             length, queueSize));
146     }
147 
148     // Do a calculation to see if the read will wraparound
149     const size_t queueOffset = getQueueOffset();
150     const size_t queueSizeToQueueEnd = queueSize - relativeOffset;
151     size_t numWraparoundBytesToRead = 0;
152     if (length > queueSizeToQueueEnd)
153     {
154         // This means we will wrap, count the bytes that are left to read
155         numWraparoundBytesToRead = length - queueSizeToQueueEnd;
156     }
157     const size_t numBytesToReadTillQueueEnd = length - numWraparoundBytesToRead;
158 
159     std::vector<uint8_t> bytesRead = dataInterface->read(
160         queueOffset + relativeOffset, numBytesToReadTillQueueEnd);
161     if (bytesRead.size() != numBytesToReadTillQueueEnd)
162     {
163         throw std::runtime_error(
164             fmt::format("[wraparoundRead] Read '{}' which was not "
165                         "the requested length of '{}'",
166                         bytesRead.size(), numBytesToReadTillQueueEnd));
167     }
168     size_t updatedReadPtr = relativeOffset + numBytesToReadTillQueueEnd;
169 
170     // If there are any more bytes to be read beyond the buffer, wrap around and
171     // read from the beginning of the buffer (offset by the queueOffset)
172     if (numWraparoundBytesToRead > 0)
173     {
174         std::vector<uint8_t> wrappedBytesRead =
175             dataInterface->read(queueOffset, numWraparoundBytesToRead);
176         if (numWraparoundBytesToRead != wrappedBytesRead.size())
177         {
178             throw std::runtime_error(fmt::format(
179                 "[wraparoundRead] Buffer wrapped around but read '{}' which "
180                 "was not the requested lenght of '{}'",
181                 wrappedBytesRead.size(), numWraparoundBytesToRead));
182         }
183         bytesRead.insert(bytesRead.end(), wrappedBytesRead.begin(),
184                          wrappedBytesRead.end());
185         updatedReadPtr = numWraparoundBytesToRead;
186     }
187     updateReadPtr(updatedReadPtr);
188 
189     return bytesRead;
190 }
191 
192 struct QueueEntryHeader BufferImpl::readEntryHeader(size_t relativeOffset)
193 {
194     size_t headerSize = sizeof(struct QueueEntryHeader);
195     // wraparonudRead will throw if it did not read all the bytes, let it
196     // propagate up the stack
197     std::vector<uint8_t> bytesRead = wraparoundRead(relativeOffset, headerSize);
198 
199     return *reinterpret_cast<struct QueueEntryHeader*>(bytesRead.data());
200 }
201 
202 EntryPair BufferImpl::readEntry(size_t relativeOffset)
203 {
204     struct QueueEntryHeader entryHeader = readEntryHeader(relativeOffset);
205     size_t entrySize = boost::endian::little_to_native(entryHeader.entrySize);
206 
207     // wraparonudRead may throw if entrySize was bigger than the buffer or if it
208     // was not able to read all the bytes, let it propagate up the stack
209     std::vector<uint8_t> entry = wraparoundRead(
210         relativeOffset + sizeof(struct QueueEntryHeader), entrySize);
211 
212     // Calculate the checksum
213     uint8_t* entryHeaderPtr = reinterpret_cast<uint8_t*>(&entryHeader);
214     uint8_t checksum = std::accumulate(
215         entryHeaderPtr, entryHeaderPtr + sizeof(struct QueueEntryHeader), 0);
216     checksum = std::accumulate(std::begin(entry), std::end(entry), checksum);
217     if (checksum != 0)
218     {
219         throw std::runtime_error(fmt::format(
220             "[readEntry] Checksum was '{}', expected '0'", checksum));
221     }
222 
223     return {entryHeader, entry};
224 }
225 
226 } // namespace bios_bmc_smm_error_logger
227