| 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 |