Mbed OS Reference
Loading...
Searching...
No Matches
CacheAlignedBuffer.h
1/* mbed Microcontroller Library
2 * Copyright (c) 2006-2023 ARM Limited
3 * SPDX-License-Identifier: Apache-2.0
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18#ifndef MBED_CACHEALIGNEDBUFFER_H
19#define MBED_CACHEALIGNEDBUFFER_H
20
21#include <cstdlib>
22
23#include "device.h"
24
25namespace mbed {
26
27namespace detail::cab {
28/**
29 * @brief Calculate the needed capacity for a cache aligned buffer's backing buffer based on the
30 * needed capacity and element size.
31 *
32 * @param neededCapacity Capacity needed for the buffer
33 * @param elementSize Size of each element
34 *
35 * @return Needed backing buffer size
36 */
37constexpr inline size_t getNeededBackingBufferSize(size_t neededCapacity, size_t elementSize)
38{
39#if __DCACHE_PRESENT
40 // Allocate enough extra space that we can shift the start of the buffer towards higher addresses to be on a cache line.
41 // The worst case for this is when the first byte is allocated 1 byte past the start of a cache line, so we
42 // will need an additional (cache line size - 1) bytes.
43 // Additionally, since we are going to be invalidating this buffer, we can't allow any other variables to be
44 // in the same cache lines, or they might get corrupted.
45 // So, we need to round up the backing buffer size to the nearest multiple of the cache line size.
46 // The math for rounding up can be found here:
47 // https://community.st.com/t5/stm32-mcus-products/maintaining-cpu-data-cache-coherence-for-dma-buffers/td-p/95746
48 size_t requiredSizeRoundedUp = (neededCapacity * elementSize + __SCB_DCACHE_LINE_SIZE - 1) & ~((__SCB_DCACHE_LINE_SIZE) - 1);
49 return requiredSizeRoundedUp + __SCB_DCACHE_LINE_SIZE - 1;
50#else
51 // No cache on this platform so don't need any extra space.
52 return neededCapacity * elementSize;
53#endif
54}
55}
56
57/** \addtogroup platform-public-api */
58/** @{*/
59/**
60 * \defgroup platform_CacheAlignedBuffer CacheAlignedBuffer class
61 * @{
62 */
63
64/**
65 * @brief CacheAlignedBuffer is used by Mbed in locations where we need a cache-aligned buffer.
66 *
67 * <p>Cache alignment is desirable in several different situations in embedded programming -- one common
68 * use is when working with DMA or other peripherals which write their results back to main memory.
69 * After these peripherals do their work, the data will be correct in main memory, but the CPU cache
70 * might also contain a value cached from that memory which is now incorrect.<p>
71 *
72 * <p>In order to read those results from memory without risk of getting old data from the
73 * CPU cache, one needs to align the buffer so it takes up an integer number of cache lines,
74 * then invalidate the cache lines so that the data gets reread from RAM.</p>
75 *
76 * <p>%CacheAlignedBuffer provides an easy way to allocate the correct amount of space so that
77 * a buffer of any size can be made cache-aligned. To instantiate a %CacheAlignedBuffer, create one of its
78 * subtypes, StaticCacheAlignedBuffer or DynamicCacheAlignedBuffer.</p>
79 *
80 * <h2> Converting Code to use CacheAlignedBuffer </h2>
81 * For code using static arrays, like this:
82 * @code
83 * uint8_t rxBuffer[16];
84 * spi.transfer_and_wait(nullptr, 0, rxBuffer, 16);
85 * @endcode
86 * Use a StaticCacheAlignedBuffer:
87 * @code
88 * StaticCacheAlignedBuffer<uint8_t, 16> rxBuffer;
89 * spi.transfer_and_wait(nullptr, 0, rxBuffer, 16);
90 * @endcode
91 * For code using dynamic allocation to handle unknown buffer sizes, like:
92 * @code
93 * uint16_t * rxBuffer = new uint16_t[bufferSize];
94 * spi.transfer_and_wait(nullptr, 0, rxBuffer, bufferSize);
95 * ...
96 * delete[] rxBuffer;
97 * @endcode
98 * use a DynamicCacheAlignedBuffer:
99 * @code
100 * DynamicCacheAlignedBuffer<uint16_t> rxBuffer(bufferSize);
101 * spi.transfer_and_wait(nullptr, 0, rxBuffer, bufferSize);
102 * @endcode
103 *
104 * @tparam DataT Type of the data to store in the buffer. Note: %CacheAlignedBuffer is not designed for
105 * using class types as DataT, and will not call constructors.
106 */
107template<typename DataT>
109
110protected:
111 /// Pointer to the aligned buffer. Must be set in each constructor of each subclass.
113
114 /// Capacity of the aligned buffer, in terms of number of DataT elements
116
117 // Protected constructor to block instantiation
118 CacheAlignedBuffer() = default;
119
120 /**
121 * Find and return the first location in the given buffer that starts on a cache line.
122 * If this MCU does not use a cache, this function is a no-op.
123 *
124 * @param buffer Pointer to buffer
125 *
126 * @return Pointer to first data item, aligned at the start of a cache line.
127 */
128 static inline DataT *findCacheLineStart(uint8_t *buffer)
129 {
130#if __DCACHE_PRESENT
131 // Use integer division to divide the address down to the cache line size, which
132 // rounds to the cache line before the given address.
133 // So that we always go one cache line back even if the given address is on a cache line,
134 // subtract 1.
135 ptrdiff_t prevCacheLine = ((ptrdiff_t)(buffer - 1)) / __SCB_DCACHE_LINE_SIZE;
136
137 // Now we just have to multiply up again to get an address (adding 1 to go forward by 1 cache line)
138 return reinterpret_cast<DataT *>((prevCacheLine + 1) * __SCB_DCACHE_LINE_SIZE);
139#else
140 return reinterpret_cast<DataT *>(buffer);
141#endif
142 }
143
144public:
145
146 // Iterator types
147 typedef DataT *iterator;
148 typedef DataT const *const_iterator;
149
150 /**
151 * @brief Get a pointer to the aligned data array inside the buffer
152 */
153 DataT *data()
154 {
155 return _alignedBufferPtr;
156 }
157
158 /**
159 * @brief Get a pointer to the aligned data array inside the buffer (const version)
160 */
161 DataT const *data() const
162 {
163 return _alignedBufferPtr;
164 }
165
166 /**
167 * @brief Element access
168 */
169 DataT &operator[](size_t index)
170 {
171 return _alignedBufferPtr[index];
172 }
173
174 /**
175 * @brief Element access (const)
176 */
177 DataT operator[](size_t index) const
178 {
179 return _alignedBufferPtr[index];
180 }
181
182 /**
183 * @brief Get iterator for start of buffer
184 */
185 iterator begin()
186 {
187 return _alignedBufferPtr;
188 }
189
190 /**
191 * @brief Get iterator for start of buffer
192 */
193 const_iterator begin() const
194 {
195 return _alignedBufferPtr;
196 }
197
198 /**
199 * @brief Get iterator for end of buffer
200 */
201 iterator end()
202 {
204 }
205
206 /**
207 * @brief Get iterator for end of buffer
208 */
209 const_iterator end() const
210 {
212 }
213
214 /**
215 * @return The maximum amount of DataT elements that this buffer can hold.
216 */
217 constexpr size_t capacity()
218 {
220 }
221};
222
223/**
224 * @brief CacheAlignedBuffer type designed for static allocation.
225 *
226 * Use a StaticCacheAlignedBuffer when you want to create a cache-aligned buffer with a fixed size
227 * at compile time. %StaticCacheAlignedBuffers can be declared globally, as local variables, or using
228 * new[] and delete[].
229 *
230 * @tparam DataT Type of the data to store in the buffer. Note: %CacheAlignedBuffer is not designed for
231 * using class types as DataT, and will not call constructors.
232 * @tparam BufferSize Buffer size (number of elements) needed by the application for the buffer.
233 */
234template<typename DataT, size_t BufferSize>
236private:
237
238 uint8_t _backingBuffer[detail::cab::getNeededBackingBufferSize(BufferSize, sizeof(DataT))];
239
240public:
241
242 /**
243 * @brief Construct new cache-aligned buffer. Buffer will be zero-initialized.
244 */
246 _backingBuffer{}
247 {
248 this->_alignedBufferPtr = this->findCacheLineStart(_backingBuffer);
249 this->_alignedBufferCapacity = BufferSize;
250 }
251
252 /**
253 * @brief Copy from other cache-aligned buffer. Buffer memory will be copied.
254 */
256 {
257 this->_alignedBufferPtr = this->findCacheLineStart(_backingBuffer);
258 memcpy(this->_alignedBufferPtr, other._alignedBufferPtr, BufferSize * sizeof(DataT));
259 this->_alignedBufferCapacity = BufferSize;
260 }
261
262 /**
263 * @brief Assign from other cache-aligned buffer. Buffer memory will be assigned.
264 *
265 * Only a buffer with the same data type and size can be assigned.
266 */
268 {
269 memmove(this->_alignedBufferPtr, other._alignedBufferPtr, BufferSize * sizeof(DataT));
270 }
271};
272
273/**
274 * @brief CacheAlignedBuffer type which allocates its backing buffer on the heap.
275 *
276 * Use a DynamicCacheAlignedBuffer when you want to create a cache-aligned buffer with a size
277 * known only at runtime. When constructed, %DynamicCacheAlignedBuffers allocate backing memory off the
278 * heap for the provided number of elements. The memory will be released when the buffer object is destroyed.
279 *
280 * @tparam DataT Type of the data to store in the buffer. Note: %CacheAlignedBuffer is not designed for
281 * using class types as DataT, and will not call constructors.
282 */
283template<typename DataT>
285 uint8_t *_heapMem;
286public:
287 /**
288 * @brief Construct new cache-aligned buffer. Buffer will be zero-initialized and allocated from the heap.
289 *
290 * @param capacity Number of elements the buffer shall hold
291 */
293 _heapMem(new uint8_t[detail::cab::getNeededBackingBufferSize(capacity, sizeof(DataT))]())
294 {
295 this->_alignedBufferPtr = this->findCacheLineStart(_heapMem);
297 }
298
299 /**
300 * @brief Copy from other cache-aligned buffer. A new backing buffer will be allocated on the heap and
301 * its data will be copied from the other buffer.
302 */
304 _heapMem(new uint8_t[detail::cab::getNeededBackingBufferSize(other._alignedBufferCapacity, sizeof(DataT))])
305 {
307 this->_alignedBufferPtr = this->findCacheLineStart(_heapMem);
308 memcpy(this->_alignedBufferPtr, other._alignedBufferPtr, this->_alignedBufferCapacity * sizeof(DataT));
309 }
310
311 // Destructor
313 {
314 delete[] this->_heapMem;
315 }
316
317 /**
318 * @brief Assign from other cache-aligned buffer with the same type. A new buffer will be allocated
319 * of the correct size.
320 */
322 {
323 // Check for self assignment
324 if (&other == this) {
325 return *this;
326 }
327
328 delete[] _heapMem;
329 _heapMem = new uint8_t[detail::cab::getNeededBackingBufferSize(other._alignedBufferCapacity, sizeof(DataT))];
330 this->_alignedBufferPtr = this->findCacheLineStart(_heapMem);
331
332 memcpy(this->_alignedBufferPtr, other._alignedBufferPtr, this->_alignedBufferCapacity * sizeof(DataT));
333 }
334};
335
336/// @}
337
338}
339
340#endif //MBED_CACHEALIGNEDBUFFER_H
CacheAlignedBuffer is used by Mbed in locations where we need a cache-aligned buffer.
CacheAlignedBuffer type which allocates its backing buffer on the heap.
CacheAlignedBuffer type designed for static allocation.
DynamicCacheAlignedBuffer(size_t capacity)
Construct new cache-aligned buffer.
const_iterator begin() const
Get iterator for start of buffer.
DataT * data()
Get a pointer to the aligned data array inside the buffer.
DataT operator[](size_t index) const
Element access (const)
StaticCacheAlignedBuffer(StaticCacheAlignedBuffer const &other)
Copy from other cache-aligned buffer.
DataT & operator[](size_t index)
Element access.
constexpr size_t getNeededBackingBufferSize(size_t neededCapacity, size_t elementSize)
Calculate the needed capacity for a cache aligned buffer's backing buffer based on the needed capacit...
static DataT * findCacheLineStart(uint8_t *buffer)
Find and return the first location in the given buffer that starts on a cache line.
StaticCacheAlignedBuffer()
Construct new cache-aligned buffer.
StaticCacheAlignedBuffer & operator=(StaticCacheAlignedBuffer< DataT, BufferSize > const &other)
Assign from other cache-aligned buffer.
iterator end()
Get iterator for end of buffer.
const_iterator end() const
Get iterator for end of buffer.
size_t _alignedBufferCapacity
Capacity of the aligned buffer, in terms of number of DataT elements.
iterator begin()
Get iterator for start of buffer.
constexpr size_t capacity()
DynamicCacheAlignedBuffer(DynamicCacheAlignedBuffer const &other)
Copy from other cache-aligned buffer.
DynamicCacheAlignedBuffer & operator=(DynamicCacheAlignedBuffer const &other)
Assign from other cache-aligned buffer with the same type.
DataT * _alignedBufferPtr
Pointer to the aligned buffer. Must be set in each constructor of each subclass.
DataT const * data() const
Get a pointer to the aligned data array inside the buffer (const version)