blob: 69f5856cb822f91a0b64e2e780303cdd52a5408b [file] [log] [blame]
Austin Schuh25356e22019-09-11 19:27:07 -07001#ifndef AOS_IPC_LIB_INDEX_H_
2#define AOS_IPC_LIB_INDEX_H_
3
4#include <sys/types.h>
5#include <atomic>
6
7namespace aos {
8namespace ipc_lib {
9
10struct AtomicQueueIndex;
11class AtomicIndex;
12class Index;
13
14namespace testing {
15class QueueIndexTest;
16} // namespace testing
17
18// There are 2 types of indices in the queue. 1 is the index into the overall
19// queue. If we have sent 1,000,005 messages, this is that number.
20//
21// The other is the index into the message list. This is essentially a message
22// pointer. It has some of the lower bits of the queue index encoded into it as
23// a safeguard to detect if someone re-used a message out from under us and we
24// couldn't tell otherwise. It is used to map a queue index to a message index.
25//
26// Each of these index types has an atomic version and a non-atomic. The atomic
27// version is a wrapper around a uint32_t to hang helper functions off of.
28// The non-atomic version contains all the logic. These are all in the header
29// file to encourage the compiler to inline aggressively.
30//
31// The user should very infrequently be manipulating the values of these
32// directly. Use the classes instead to do the heavy lifting.
33
34// Structure for holding the index into the queue.
35class QueueIndex {
36 public:
37 // Returns an invalid queue element which uses a reserved value.
38 static QueueIndex Invalid() { return QueueIndex(0xffffffff, 0); }
39 // Returns a queue element pointing to 0.
40 static QueueIndex Zero(uint32_t count) { return QueueIndex(0, count); }
41
42 // Returns true if the index is valid.
43 bool valid() const { return index_ != 0xffffffff; }
44
45 // Returns the modulo base used to wrap to avoid overlapping with the reserved
46 // number.
47 // max_value is one more than the max value we can store.
48 // count is the the number of elements in the queue.
49 static constexpr uint32_t MaxIndex(uint32_t max_value, uint32_t count) {
50 return (max_value / count) * count;
51 }
52
53 // Gets the next index.
54 QueueIndex Increment() const {
55 return IncrementBy(1u);
56 }
57
58 // Gets the nth next element.
59 QueueIndex IncrementBy(uint32_t amount) const {
60 uint32_t index = index_ + amount;
61 uint32_t max_index = MaxIndex(sentinal_value(), count_);
62
63 if (index < index_) {
64 // We wrapped. We are shifting up by 0x100000000 - MaxIndex(...).
65 // Which is equivalent to subtracting MaxIndex since everything is modular
66 // with a uint32_t.
67 index -= max_index;
68 }
69
70 // Now, wrap the remainder.
71 index = index % max_index;
72 return QueueIndex(index, count_);
73 }
74
75 // Gets the nth previous element.
76 QueueIndex DecrementBy(uint32_t amount) const {
77 uint32_t index = index_ - amount;
78 if (index > index_) {
79 // We wrapped. We are shifting down by 0x100000000 - MaxIndex(...).
80 // Which is equivalent to adding MaxIndex since everything is modular with
81 // a uint32_t.
82 index += MaxIndex(sentinal_value(), count_);
83 }
84 return QueueIndex(index, count_);
85 }
86
87 // Returns true if the lowest 16 bits of the queue index from the Index could
88 // plausibly match this queue index.
89 bool IsPlausible(uint16_t queue_index) const {
90 return valid() && (queue_index == static_cast<uint16_t>(index_ & 0xffff));
91 }
92
93 bool operator==(const QueueIndex other) const {
94 return other.index_ == index_;
95 }
96
97 bool operator!=(const QueueIndex other) const {
98 return other.index_ != index_;
99 }
100
101 // Returns the wrapped index into the queue.
102 uint32_t Wrapped() const { return index_ % count_; }
103
104 // Returns the raw index. This should be used very sparingly.
105 uint32_t index() const { return index_; }
106
107 private:
108 QueueIndex(uint32_t index, uint32_t count) : index_(index), count_(count) {}
109
110 static constexpr uint32_t sentinal_value() { return 0xffffffffu; }
111
112 friend struct AtomicQueueIndex;
113 friend class Index;
114 // For testing.
115 friend class testing::QueueIndexTest;
116
117 // Index and number of elements in the queue.
118 uint32_t index_;
119 // Count is stored here rather than passed in everywhere in the hopes that the
120 // compiler completely optimizes out this class and this variable if it isn't
121 // used.
122 uint32_t count_;
123};
124
125// Atomic storage for setting and getting QueueIndex objects.
126// Count is the number of messages in the queue.
127struct AtomicQueueIndex {
128 public:
129 // Atomically reads the index without any ordering constraints.
130 QueueIndex RelaxedLoad(uint32_t count) {
131 return QueueIndex(index_.load(::std::memory_order_relaxed), count);
132 }
133
134 // Full bidirectional barriers here.
135 QueueIndex Load(uint32_t count) { return QueueIndex(index_.load(), count); }
136 inline void Store(QueueIndex value) { index_.store(value.index_); }
137
138 // Invalidates the element unconditionally.
139 inline void Invalidate() { Store(QueueIndex::Invalid()); }
140
141 // Swaps expected for index atomically. Returns true on success, false
142 // otherwise.
143 inline bool CompareAndExchangeStrong(QueueIndex expected, QueueIndex index) {
144 return index_.compare_exchange_strong(expected.index_, index.index_);
145 }
146
147 private:
148 ::std::atomic<uint32_t> index_;
149};
150
151// Structure holding the queue index and the index into the message list.
152class Index {
153 public:
154 // Constructs an Index. queue_index is the QueueIndex of this message, and
155 // message_index is the index into the messages structure.
156 Index(QueueIndex queue_index, uint16_t message_index)
157 : Index(queue_index.index_, message_index) {}
158 Index(uint32_t queue_index, uint16_t message_index)
159 : index_((queue_index & 0xffff) |
160 (static_cast<uint32_t>(message_index) << 16)) {}
161
162 // Index of this message in the message array.
163 uint16_t message_index() const { return (index_ >> 16) & 0xffff; }
164
165 // Lowest 16 bits of the queue index of this message in the queue.
166 uint16_t queue_index() const { return index_ & 0xffff; }
167
168 // Returns true if the provided queue index plausibly represents this Index.
169 bool IsPlausible(QueueIndex queue_index) const {
170 return queue_index.IsPlausible(this->queue_index());
171 }
172
173 // Returns an invalid Index.
174 static Index Invalid() { return Index(sentinal_value()); }
175 // Checks if this Index is valid or not.
176 bool valid() const { return index_ != sentinal_value(); }
177
178 // Returns the raw Index. This should only be used for debug.
179 uint32_t get() const { return index_; }
180
181 // Returns the maximum number of messages we can store before overflowing.
182 static constexpr uint16_t MaxMessages() { return 0xfffe; }
183
184 bool operator==(const Index other) const { return other.index_ == index_; }
185
186 private:
187 Index(uint32_t index)
188 : index_(index) {}
189
190 friend class AtomicIndex;
191
192 static constexpr uint32_t sentinal_value() { return 0xffffffffu; }
193
194 // Note: a value of 0xffffffff is a sentinal to represent an invalid entry.
195 // This works because we would need to have a queue index of 0x*ffff, *and*
196 // have 0xffff messages in the message list. That constraint is easy to
197 // enforce by limiting the max messages.
198 uint32_t index_;
199};
200
201// Atomic storage for setting and getting Index objects.
202class AtomicIndex {
203 public:
204 // Stores and loads atomically without ordering constraints.
205 Index RelaxedLoad() {
206 return Index(index_.load(::std::memory_order_relaxed));
207 }
208 void RelaxedStore(Index index) {
209 index_.store(index.index_, ::std::memory_order_relaxed);
210 }
211
212 // Invalidates the index atomically, but without any ordering constraints.
213 void RelaxedInvalidate() { RelaxedStore(Index::Invalid()); }
214
215 // Full bidirectional barriers here.
216 void Store(Index index) { index_.store(index.index_); }
217 Index Load() { return Index(index_.load()); }
218
219
220 // Swaps expected for index atomically. Returns true on success, false
221 // otherwise.
222 inline bool CompareAndExchangeStrong(Index expected, Index index) {
223 return index_.compare_exchange_strong(expected.index_, index.index_);
224 }
225
226 private:
227 ::std::atomic<uint32_t> index_;
228};
229
230} // namespace ipc_lib
231} // namespace aos
232
233#endif // AOS_IPC_LIB_INDEX_H_