GCC Code Coverage Report


Directory: ./
File: firmware/controllers/system/timer/event_queue.cpp
Date: 2025-11-16 14:52:24
Warnings: 4 unchecked decisions!
Coverage Exec Excl Total
Lines: 93.7% 118 0 126
Functions: 100.0% 13 0 13
Branches: 84.8% 78 0 92
Decisions: 70.8% 34 - 48

Line Branch Decision Exec Source
1 /**
2 * @file event_queue.cpp
3 * This is a data structure which keeps track of all pending events
4 * Implemented as a linked list, which is fine since the number of
5 * pending events is pretty low
6 * todo: MAYBE migrate to a better data structure, but that's low priority
7 *
8 * this data structure is NOT thread safe
9 *
10 * @date Apr 17, 2014
11 * @author Andrey Belomutskiy, (c) 2012-2020
12 */
13
14 #include "pch.h"
15
16 #include "event_queue.h"
17 #include "efitime.h"
18
19 #ifndef EFI_UNIT_TEST_VERBOSE_ACTION
20 #define EFI_UNIT_TEST_VERBOSE_ACTION 0
21 #elif EFI_UNIT_TEST_VERBOSE_ACTION
22 #include <iostream>
23 #endif
24
25 #if EFI_UNIT_TEST
26 extern bool verboseMode;
27 #endif /* EFI_UNIT_TEST */
28
29 701 EventQueue::EventQueue(efidur_t lateDelay)
30 701 : m_lateDelay(lateDelay)
31 {
32
2/2
✓ Branch 1 taken 44864 times.
✓ Branch 2 taken 701 times.
2/2
✓ Decision 'true' taken 44864 times.
✓ Decision 'false' taken 701 times.
45565 for (size_t i = 0; i < efi::size(m_pool); i++) {
33 44864 tryReturnScheduling(&m_pool[i]);
34 }
35
36 #if EFI_PROD_CODE
37 getTunerStudioOutputChannels()->schedulingUsedCount = 0;
38 #endif
39 701 }
40
41 2849 scheduling_s* EventQueue::getFreeScheduling() {
42 2849 auto retVal = m_freelist;
43
44
2/2
✓ Branch 0 taken 2763 times.
✓ Branch 1 taken 86 times.
2/2
✓ Decision 'true' taken 2763 times.
✓ Decision 'false' taken 86 times.
2849 if (retVal) {
45 2763 m_freelist = retVal->next;
46 2763 retVal->next = nullptr;
47
48 #if EFI_PROD_CODE
49 getTunerStudioOutputChannels()->schedulingUsedCount++;
50 #endif
51 }
52
53 2849 return retVal;
54 }
55
56 66838 void EventQueue::tryReturnScheduling(scheduling_s* sched) {
57 // Only return this scheduling to the free list if it's from the correct pool
58
6/6
✓ Branch 0 taken 62525 times.
✓ Branch 1 taken 4313 times.
✓ Branch 3 taken 47291 times.
✓ Branch 4 taken 15234 times.
✓ Branch 5 taken 47291 times.
✓ Branch 6 taken 19547 times.
2/2
✓ Decision 'true' taken 47291 times.
✓ Decision 'false' taken 19547 times.
66838 if (sched >= &m_pool[0] && sched <= &m_pool[efi::size(m_pool) - 1]) {
59 47291 sched->next = m_freelist;
60 47291 m_freelist = sched;
61
62 #if EFI_PROD_CODE
63 getTunerStudioOutputChannels()->schedulingUsedCount--;
64 #endif
65 }
66 66838 }
67
68 /**
69 * @return true if inserted into the head of the list
70 */
71 30429 bool EventQueue::insertTask(scheduling_s *scheduling, efitick_t timeNt, action_s const& action) {
72 30429 ScopePerf perf(PE::EventQueueInsertTask);
73
74
2/2
✓ Branch 0 taken 2849 times.
✓ Branch 1 taken 27580 times.
2/2
✓ Decision 'true' taken 2849 times.
✓ Decision 'false' taken 27580 times.
30429 if (!scheduling) {
75
1/1
✓ Branch 1 taken 2849 times.
2849 scheduling = getFreeScheduling();
76
77 // If still null, the free list is empty and all schedulings in the pool have been expended.
78
2/2
✓ Branch 0 taken 86 times.
✓ Branch 1 taken 2763 times.
2/2
✓ Decision 'true' taken 86 times.
✓ Decision 'false' taken 2763 times.
2849 if (!scheduling) {
79 // TODO: should we warn or error here?
80 // todo: look into why units tests fail here
81 #if EFI_PROD_CODE
82 criticalError("No slots in scheduling pool");
83 #endif
84 86 return false;
85 }
86 }
87
88
1/1
✓ Branch 1 taken 30343 times.
30343 assertListIsSorted();
89
1/3
✗ Branch 1 not taken.
✓ Branch 2 taken 30343 times.
✗ Branch 4 not taken.
30343 efiAssert(ObdCode::CUSTOM_ERR_ASSERT, action.getCallback() != nullptr, "NULL callback", false);
90
91 // please note that simulator does not use this code at all - simulator uses signal_executor_sleep
92
93
2/2
✓ Branch 1 taken 6037 times.
✓ Branch 2 taken 24306 times.
2/2
✓ Decision 'true' taken 6037 times.
✓ Decision 'false' taken 24306 times.
30343 if (scheduling->action) {
94 #if EFI_UNIT_TEST
95
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6037 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 6037 times.
6037 if (verboseMode) {
96 printf("Already scheduled was %d\r\n", (int)scheduling->getMomentNt());
97 printf("Already scheduled now %d\r\n", (int)timeNt);
98 }
99 #endif /* EFI_UNIT_TEST */
100 6037 return false;
101 }
102
103 24306 scheduling->setMomentNt(timeNt);
104 24306 scheduling->action = action;
105
106
6/6
✓ Branch 0 taken 13848 times.
✓ Branch 1 taken 10458 times.
✓ Branch 3 taken 1909 times.
✓ Branch 4 taken 11939 times.
✓ Branch 5 taken 12367 times.
✓ Branch 6 taken 11939 times.
2/2
✓ Decision 'true' taken 12367 times.
✓ Decision 'false' taken 11939 times.
24306 if (!m_head || timeNt < m_head->getMomentNt()) {
107 // here we insert into head of the linked list
108 12367 LL_PREPEND(m_head, scheduling);
109
1/1
✓ Branch 1 taken 12367 times.
12367 assertListIsSorted();
110 12367 return true;
111 } else {
112 // here we know we are not in the head of the list, let's find the position - linear search
113 11939 scheduling_s *insertPosition = m_head;
114
6/6
✓ Branch 0 taken 599062 times.
✓ Branch 1 taken 10051 times.
✓ Branch 3 taken 597174 times.
✓ Branch 4 taken 1888 times.
✓ Branch 5 taken 597174 times.
✓ Branch 6 taken 11939 times.
0/1
? Decision couldn't be analyzed.
609113 while (insertPosition->next != nullptr && insertPosition->next->getMomentNt() < timeNt) {
115 597174 insertPosition = insertPosition->next;
116 }
117
118 11939 scheduling->next = insertPosition->next;
119 11939 insertPosition->next = scheduling;
120
1/1
✓ Branch 1 taken 11939 times.
11939 assertListIsSorted();
121 11939 return false;
122 }
123 }
124
125 1771 void EventQueue::remove(scheduling_s* scheduling) {
126 1771 assertListIsSorted();
127
128 // Special case: event isn't scheduled, so don't cancel it
129
2/2
✓ Branch 1 taken 23 times.
✓ Branch 2 taken 1748 times.
2/2
✓ Decision 'true' taken 23 times.
✓ Decision 'false' taken 1748 times.
1771 if (!scheduling->action) {
130 23 return;
131 }
132
133 // Special case: empty list, nothing to do
134
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1748 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 1748 times.
1748 if (!m_head) {
135 return;
136 }
137
138 // Special case: is the item to remove at the head?
139
2/2
✓ Branch 0 taken 1478 times.
✓ Branch 1 taken 270 times.
2/2
✓ Decision 'true' taken 1478 times.
✓ Decision 'false' taken 270 times.
1748 if (scheduling == m_head) {
140 1478 m_head = m_head->next;
141 1478 scheduling->next = nullptr;
142 1478 scheduling->action = {};
143 } else {
144 270 auto prev = m_head; // keep track of the element before the one to remove, so we can link around it
145 270 auto current = prev->next;
146
147 // Find our element
148
3/4
✓ Branch 0 taken 3211 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2941 times.
✓ Branch 3 taken 270 times.
0/1
? Decision couldn't be analyzed.
3211 while (current && current != scheduling) {
149 2941 prev = current;
150 2941 current = current->next;
151 }
152
153 // Walked off the end, this is an error since this *should* have been scheduled
154
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 270 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 270 times.
270 if (!current) {
155 firmwareError(ObdCode::OBD_PCM_Processor_Fault, "EventQueue::remove didn't find element");
156 return;
157 }
158
159
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 270 times.
270 efiAssertVoid(ObdCode::OBD_PCM_Processor_Fault, current == scheduling, "current not equal to scheduling");
160
161 // Link around the removed item
162 270 prev->next = current->next;
163
164 // Clean the item to remove
165 270 current->next = nullptr;
166 270 current->action = {};
167 }
168
169 1748 assertListIsSorted();
170 }
171
172 /**
173 * On this layer it does not matter which units are used - us, ms ot nt.
174 *
175 * This method is always invoked under a lock
176 * @return Get the timestamp of the soonest pending action, skipping all the actions in the past
177 */
178 5 expected<efitick_t> EventQueue::getNextEventTime(efitick_t nowNt) const {
179
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 2 times.
2/2
✓ Decision 'true' taken 3 times.
✓ Decision 'false' taken 2 times.
5 if (m_head) {
180
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 3 times.
3 if (m_head->getMomentNt() <= nowNt) {
181 /**
182 * We are here if action timestamp is in the past. We should rarely be here since this 'getNextEventTime()' is
183 * always invoked by 'scheduleTimerCallback' which is always invoked right after 'executeAllPendingActions' - but still,
184 * for events which are really close to each other we would end up here.
185 *
186 * looks like we end up here after 'writeconfig' (which freezes the firmware) - we are late
187 * for the next scheduled event
188 */
189 return nowNt + m_lateDelay;
190 } else {
191 3 return m_head->getMomentNt();
192 }
193 }
194
195 2 return unexpected;
196 }
197
198 /**
199 * See also maxPrecisionCallbackDuration for total hw callback time
200 */
201 uint32_t maxEventCallbackDuration = 0;
202
203 /**
204 * Invoke all pending actions prior to specified timestamp
205 * @return number of executed actions
206 */
207 21420 int EventQueue::executeAll(efitick_t now) {
208 21420 ScopePerf perf(PE::EventQueueExecuteAll);
209
210 21420 int executionCounter = 0;
211
212
1/1
✓ Branch 1 taken 21420 times.
21420 assertListIsSorted();
213
214 bool didExecute;
215 43394 do {
216
1/1
✓ Branch 1 taken 43394 times.
43394 didExecute = executeOne(now);
217
4/4
✓ Branch 0 taken 21974 times.
✓ Branch 1 taken 21420 times.
✓ Branch 2 taken 21974 times.
✓ Branch 3 taken 21420 times.
43394 executionCounter += didExecute ? 1 : 0;
218 } while (didExecute);
219
220 21420 return executionCounter;
221 }
222
223 43394 bool EventQueue::executeOne(efitick_t now) {
224 // Read the head every time - a previously executed event could
225 // have inserted something new at the head
226 43394 scheduling_s* current = m_head;
227
228 // Queue is empty - bail
229
2/2
✓ Branch 0 taken 6114 times.
✓ Branch 1 taken 37280 times.
2/2
✓ Decision 'true' taken 6114 times.
✓ Decision 'false' taken 37280 times.
43394 if (!current) {
230 6114 return false;
231 }
232
233 // If the next event is far in the future, we'll reschedule
234 // and execute it next time.
235 // We do this when the next event is close enough that the overhead of
236 // resetting the timer and scheduling an new interrupt is greater than just
237 // waiting for the time to arrive. On current CPUs, this is reasonable to set
238 // around 10 microseconds.
239
2/2
✓ Branch 1 taken 15306 times.
✓ Branch 2 taken 21974 times.
2/2
✓ Decision 'true' taken 15306 times.
✓ Decision 'false' taken 21974 times.
37280 if (current->getMomentNt() > now + m_lateDelay) {
240 15306 return false;
241 }
242
243 #if EFI_UNIT_TEST
244 // efitick_t spinDuration = current->getMomentNt() - getTimeNowNt();
245 // if (spinDuration > 0) {
246 // throw std::runtime_error("Time Spin in unit test");
247 // }
248 #endif
249
250 // near future - spin wait for the event to happen and avoid the
251 // overhead of rescheduling the timer.
252 // yes, that's a busy wait but that's what we need here
253
3/3
✓ Branch 2 taken 21974 times.
✓ Branch 4 taken 27 times.
✓ Branch 5 taken 21947 times.
0/1
? Decision couldn't be analyzed.
21974 while (current->getMomentNt() > getTimeNowNt()) {
254 #if EFI_UNIT_TEST
255 // todo: remove this hack see https://github.com/rusefi/rusefi/issues/6457
256 extern bool unitTestBusyWaitHack;
257
1/2
✓ Branch 0 taken 27 times.
✗ Branch 1 not taken.
1/2
✓ Decision 'true' taken 27 times.
✗ Decision 'false' not taken.
27 if (unitTestBusyWaitHack) {
258 27 break;
259 }
260 #endif
261 UNIT_TEST_BUSY_WAIT_CALLBACK();
262 }
263
264 // step the head forward, unlink this element, clear scheduled flag
265 21974 m_head = current->next;
266 21974 current->next = nullptr;
267
268 // Grab the action but clear it in the event so we can reschedule from the action's execution
269 21974 auto const action{ std::move(current->action) };
270
271
1/1
✓ Branch 1 taken 21974 times.
21974 tryReturnScheduling(current);
272 21974 current = nullptr;
273
274 #if EFI_DETAILED_LOGGING
275 printf("QUEUE: execute current=%d param=%d\r\n", reinterpret_cast<uintptr_t>(current), action.getArgumentRaw());
276 #endif
277
278 // Execute the current element
279 {
280 21974 ScopePerf perf2(PE::EventQueueExecuteCallback);
281 #if EFI_DETAILED_LOGGING && EFI_UNIT_TEST_VERBOSE_ACTION
282 std::cout << "EventQueue::executeOne: " << action.getCallbackName() << "(" << reinterpret_cast<uintptr_t>(action.getCallback()) << ") with raw arg = " << action.getArgumentRaw() << std::endl;
283 #endif
284
1/1
✓ Branch 1 taken 21974 times.
21974 action.execute();
285
286 #if EFI_UNIT_TEST
287 // std::cout << "Executed at " << now << std::endl;
288 #endif
289 }
290
291
1/1
✓ Branch 1 taken 21974 times.
21974 assertListIsSorted();
292 21974 return true;
293 }
294
295 239 int EventQueue::size() const {
296 scheduling_s *tmp;
297 int result;
298
2/2
✓ Branch 0 taken 958 times.
✓ Branch 1 taken 239 times.
1197 LL_COUNT(m_head, tmp, result);
299 239 return result;
300 }
301
302 101562 void EventQueue::assertListIsSorted() const {
303 #if EFI_UNIT_TEST || EFI_SIMULATOR
304 101562 int counter = 0;
305 101562 scheduling_s *current = m_head;
306
4/4
✓ Branch 0 taken 2936403 times.
✓ Branch 1 taken 17788 times.
✓ Branch 2 taken 2852629 times.
✓ Branch 3 taken 83774 times.
0/1
? Decision couldn't be analyzed.
2954191 while (current != nullptr && current->next != nullptr) {
307
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 2852629 times.
2852629 efiAssertVoid(ObdCode::CUSTOM_ERR_6623, current->getMomentNt() <= current->next->getMomentNt(), "list order");
308 2852629 current = current->next;
309
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2852629 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 2852629 times.
2852629 if (counter++ > 1'000'000'000)
310 criticalError("EventQueue: looks like a loop?!");
311 }
312 #endif // EFI_UNIT_TEST || EFI_SIMULATOR
313 }
314
315 620245 scheduling_s * EventQueue::getHead() {
316 620245 return m_head;
317 }
318
319 // todo: reduce code duplication with another 'getElementAtIndexForUnitText'
320 181 scheduling_s *EventQueue::getElementAtIndexForUnitText(int index) {
321 scheduling_s * current;
322
323
2/2
✓ Branch 0 taken 458 times.
✓ Branch 1 taken 8 times.
466 LL_FOREACH(m_head, current)
324 {
325
2/2
✓ Branch 0 taken 173 times.
✓ Branch 1 taken 285 times.
2/2
✓ Decision 'true' taken 173 times.
✓ Decision 'false' taken 285 times.
458 if (index == 0) {
326 173 return current;
327 }
328 285 index--;
329 }
330
331 8 return nullptr;
332 }
333
334 701 void EventQueue::clear() {
335 // Flush the queue, resetting all scheduling_s as though we'd executed them
336
2/2
✓ Branch 0 taken 573 times.
✓ Branch 1 taken 701 times.
2/2
✓ Decision 'true' taken 573 times.
✓ Decision 'false' taken 701 times.
1274 while(m_head) {
337 573 auto x = m_head;
338 // link next element to head
339 573 m_head = x->next;
340
341 // Reset this element
342 573 x->setMomentNt(0);
343 573 x->next = nullptr;
344 573 x->action = {};
345 }
346
347 701 m_head = nullptr;
348 701 }
349