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>
129     BufferImpl::wraparoundRead(const uint32_t offset, const uint32_t length,
130                                const uint32_t additionalBoundaryCheck)
131 {
132     const size_t queueSize =
133         boost::endian::little_to_native(cachedBufferHeader.queueSize);
134 
135     if (length + additionalBoundaryCheck > queueSize)
136     {
137         throw std::runtime_error(fmt::format(
138             "[wraparoundRead] length '{}' + additionalBoundaryCheck '{}' "
139             "was bigger than queueSize '{}'",
140             length, additionalBoundaryCheck, queueSize));
141     }
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)
151     size_t queueOffset = getQueueOffset();
152     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());
166         updatedReadOffset = wrappedBytesRead.size();
167     }
168     updateReadPtr(updatedReadOffset);
169 
170     return bytesRead;
171 }
172 
173 struct 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 
183 EntryPair 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
190     // 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
193     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 
211 } // namespace bios_bmc_smm_error_logger
212