| Line | Branch | Decision | Exec | Source |
|---|---|---|---|---|
| 1 | #include "pch.h" | |||
| 2 | ||||
| 3 | #include "event_queue.h" | |||
| 4 | ||||
| 5 | 2063 | bool TriggerScheduler::assertNotInList(AngleBasedEvent *head, AngleBasedEvent *element) { | ||
| 6 | /* this code is just to validate state, no functional load*/ | |||
| 7 | decltype(head) current; | |||
| 8 | 2063 | int counter = 0; | ||
| 9 |
2/2✓ Branch 0 taken 18 times.
✓ Branch 1 taken 2060 times.
|
2078 | LL_FOREACH2(head, current, nextToothEvent) { | |
| 10 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 18 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 18 times.
|
18 | if (++counter > QUEUE_LENGTH_LIMIT) { |
| 11 | ✗ | firmwareError(ObdCode::CUSTOM_ERR_LOOPED_QUEUE, "Looped queue?"); | ||
| 12 | ✗ | return false; | ||
| 13 | } | |||
| 14 | ||||
| 15 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 15 times.
|
2/2✓ Decision 'true' taken 3 times.
✓ Decision 'false' taken 15 times.
|
18 | if (current == element) { |
| 16 | /** | |||
| 17 | * for example, this might happen in case of sudden RPM change if event | |||
| 18 | * was not scheduled by angle but was scheduled by time. In case of scheduling | |||
| 19 | * by time with slow RPM the whole next fast revolution might be within the wait | |||
| 20 | */ | |||
| 21 |
1/1✓ Decision 'true' taken 3 times.
|
3 | warning(ObdCode::CUSTOM_RE_ADDING_INTO_EXECUTION_QUEUE, "re-adding element into event_queue"); | |
| 22 | 3 | return true; | ||
| 23 | } | |||
| 24 | } | |||
| 25 | ||||
| 26 | 2060 | return false; | ||
| 27 | } | |||
| 28 | ||||
| 29 | 26 | void TriggerScheduler::schedule(const char *msg, AngleBasedEvent* event, angle_t angle, action_s const& action) { | ||
| 30 | 26 | event->setAngle(angle); | ||
| 31 | ||||
| 32 | 26 | schedule(msg, event, action); | ||
| 33 | 26 | } | ||
| 34 | ||||
| 35 | /** | |||
| 36 | * Schedules 'action' to occur at engine cycle angle 'angle'. | |||
| 37 | * | |||
| 38 | * @return true if event corresponds to current tooth and was time-based scheduler | |||
| 39 | * false if event was put into queue for scheduling at a later tooth | |||
| 40 | */ | |||
| 41 | 9650 | bool TriggerScheduler::scheduleOrQueue(const char *msg, AngleBasedEvent *event, | ||
| 42 | efitick_t edgeTimestamp, | |||
| 43 | angle_t angle, | |||
| 44 | action_s action, | |||
| 45 | float currentPhase, float nextPhase) { | |||
| 46 | 9650 | event->setAngle(angle); | ||
| 47 | ||||
| 48 | // *kludge* naming mess: if (shouldSchedule) { scheduleByAngle } else { schedule } see header for more details | |||
| 49 |
2/2✓ Branch 1 taken 7613 times.
✓ Branch 2 taken 2037 times.
|
2/2✓ Decision 'true' taken 7613 times.
✓ Decision 'false' taken 2037 times.
|
9650 | if (event->shouldSchedule(currentPhase, nextPhase)) { |
| 50 | // if we're due now, just schedule the event | |||
| 51 | 7613 | scheduleByAngle( | ||
| 52 | &event->eventScheduling, | |||
| 53 | edgeTimestamp, | |||
| 54 | event->getAngleFromNow(currentPhase), | |||
| 55 | action | |||
| 56 | ); | |||
| 57 | ||||
| 58 | 7613 | return true; | ||
| 59 | } else { | |||
| 60 | // If not due now, add it to the queue to be scheduled later | |||
| 61 | 2037 | schedule(msg, event, action); | ||
| 62 | ||||
| 63 | 2037 | return false; | ||
| 64 | } | |||
| 65 | } | |||
| 66 | ||||
| 67 | 2063 | void TriggerScheduler::schedule(const char *msg, AngleBasedEvent* event, action_s const& action) { | ||
| 68 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 2063 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 2063 times.
|
2063 | if (event->getAngle() < 0) { |
| 69 | // at the moment we expect API consumer to wrap angle. shall we do the wrapping in the enginePhase setter? | |||
| 70 | // i.e. what is the best level to take care of the range constraint? | |||
| 71 | ✗ | criticalError("Negative angle %s %f", msg, event->getAngle()); | ||
| 72 | } | |||
| 73 | ||||
| 74 | 2063 | event->action = action; | ||
| 75 | ||||
| 76 | { | |||
| 77 | chibios_rt::CriticalSectionLocker csl; | |||
| 78 | ||||
| 79 | // TODO: This is O(n), consider some other way of detecting if in a list, | |||
| 80 | // and consider doubly linked or other list tricks. | |||
| 81 | ||||
| 82 |
2/2✓ Branch 1 taken 2060 times.
✓ Branch 2 taken 3 times.
|
2/2✓ Decision 'true' taken 2060 times.
✓ Decision 'false' taken 3 times.
|
2063 | if (!assertNotInList(m_angleBasedEventsHead, event)) { |
| 83 | // Use Append to retain some semblance of event ordering in case of | |||
| 84 | // time skew. Thus on events are always followed by off events. | |||
| 85 |
4/4✓ Branch 0 taken 7 times.
✓ Branch 1 taken 2053 times.
✓ Branch 2 taken 8 times.
✓ Branch 3 taken 7 times.
|
2068 | LL_APPEND2(m_angleBasedEventsHead, event, nextToothEvent); | |
| 86 | } | |||
| 87 | } | |||
| 88 | 2063 | } | ||
| 89 | ||||
| 90 | 315 | void TriggerScheduler::cancel(AngleBasedEvent* event) { | ||
| 91 | chibios_rt::CriticalSectionLocker csl; | |||
| 92 | ||||
| 93 |
1/8✓ Branch 0 taken 315 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
|
315 | LL_DELETE2(m_angleBasedEventsHead, event, nextToothEvent); | |
| 94 | 315 | } | ||
| 95 | ||||
| 96 | 33525 | void TriggerScheduler::scheduleEventsUntilNextTriggerTooth(float rpm, | ||
| 97 | efitick_t edgeTimestamp, float currentPhase, float nextPhase) { | |||
| 98 | ||||
| 99 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 33525 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 33525 times.
|
33525 | if (rpm == 0) { |
| 100 | // this might happen for instance in case of a single trigger event after a pause | |||
| 101 | ✗ | return; | ||
| 102 | } | |||
| 103 | ||||
| 104 | AngleBasedEvent *current, *tmp, *keephead; | |||
| 105 | 33525 | AngleBasedEvent *keeptail = nullptr; | ||
| 106 | ||||
| 107 | { | |||
| 108 | chibios_rt::CriticalSectionLocker csl; | |||
| 109 | ||||
| 110 | 33525 | keephead = m_angleBasedEventsHead; | ||
| 111 | 33525 | m_angleBasedEventsHead = nullptr; | ||
| 112 | } | |||
| 113 | ||||
| 114 |
4/4✓ Branch 0 taken 2953 times.
✓ Branch 1 taken 33525 times.
✓ Branch 2 taken 2953 times.
✓ Branch 3 taken 33525 times.
|
36478 | LL_FOREACH_SAFE2(keephead, current, tmp, nextToothEvent) | |
| 115 | { | |||
| 116 |
2/2✓ Branch 1 taken 1733 times.
✓ Branch 2 taken 1220 times.
|
2/2✓ Decision 'true' taken 1733 times.
✓ Decision 'false' taken 1220 times.
|
2953 | if (current->shouldSchedule(currentPhase, nextPhase)) { |
| 117 | // time to fire a spark which was scheduled previously | |||
| 118 | ||||
| 119 | // Yes this looks like O(n^2), but that's only over the entire engine | |||
| 120 | // cycle. It's really O(mn + nn) where m = # of teeth and n = # events | |||
| 121 | // fired per cycle. The number of teeth outweigh the number of events, at | |||
| 122 | // least for 60-2.... So odds are we're only firing an event or two per | |||
| 123 | // tooth, which means the outer loop is really only O(n). And if we are | |||
| 124 | // firing many events per teeth, then it's likely the events before this | |||
| 125 | // one also fired and thus the call to LL_DELETE2 is closer to O(1). | |||
| 126 |
1/8✓ Branch 0 taken 1733 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
|
1733 | LL_DELETE2(keephead, current, nextToothEvent); | |
| 127 | ||||
| 128 | 1733 | scheduling_s * sDown = ¤t->eventScheduling; | ||
| 129 | ||||
| 130 | #if SPARK_EXTREME_LOGGING | |||
| 131 | 1733 | efiPrintf("time to invoke [%.1f, %.1f) %d %d", | ||
| 132 | currentPhase, nextPhase, getRevolutionCounter(), time2print(getTimeNowUs())); | |||
| 133 | #endif /* SPARK_EXTREME_LOGGING */ | |||
| 134 | ||||
| 135 | // In case this event was scheduled by overdwell protection, cancel it so | |||
| 136 | // we can re-schedule at the correct time | |||
| 137 | // [tag:overdwell] | |||
| 138 | 1733 | engine->scheduler.cancel(sDown); | ||
| 139 | ||||
| 140 | 1733 | scheduleByAngle( | ||
| 141 | sDown, | |||
| 142 | edgeTimestamp, | |||
| 143 | current->getAngleFromNow(currentPhase), | |||
| 144 | 1733 | current->action | ||
| 145 | ); | |||
| 146 | } else { | |||
| 147 | 1220 | keeptail = current; // Used for fast list concatenation | ||
| 148 | } | |||
| 149 | } | |||
| 150 | ||||
| 151 |
2/2✓ Branch 0 taken 1209 times.
✓ Branch 1 taken 32316 times.
|
2/2✓ Decision 'true' taken 1209 times.
✓ Decision 'false' taken 32316 times.
|
33525 | if (keephead) { |
| 152 | chibios_rt::CriticalSectionLocker csl; | |||
| 153 | ||||
| 154 | // Put any new entries onto the end of the keep list | |||
| 155 | 1209 | keeptail->nextToothEvent = m_angleBasedEventsHead; | ||
| 156 | 1209 | m_angleBasedEventsHead = keephead; | ||
| 157 | } | |||
| 158 | } | |||
| 159 | ||||
| 160 | 12603 | bool AngleBasedEvent::shouldSchedule(float currentPhase, float nextPhase) const { | ||
| 161 | 12603 | return isPhaseInRange(this->enginePhase, currentPhase, nextPhase); | ||
| 162 | } | |||
| 163 | ||||
| 164 | 9346 | float AngleBasedEvent::getAngleFromNow(float currentPhase) const { | ||
| 165 | 9346 | float angleOffset = this->enginePhase - currentPhase; | ||
| 166 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9346 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 9346 times.
|
9346 | if (angleOffset < 0) { |
| 167 | ✗ | angleOffset += engine->engineState.engineCycle; | ||
| 168 | } | |||
| 169 | ||||
| 170 | 9346 | return angleOffset; | ||
| 171 | } | |||
| 172 | ||||
| 173 | #if EFI_UNIT_TEST | |||
| 174 | // todo: reduce code duplication with another 'getElementAtIndexForUnitText' | |||
| 175 | 14 | AngleBasedEvent * TriggerScheduler::getElementAtIndexForUnitTest(int index) { | ||
| 176 | AngleBasedEvent * current; | |||
| 177 | ||||
| 178 |
1/2✓ Branch 0 taken 33 times.
✗ Branch 1 not taken.
|
33 | LL_FOREACH2(m_angleBasedEventsHead, current, nextToothEvent) | |
| 179 | { | |||
| 180 |
2/2✓ Branch 0 taken 14 times.
✓ Branch 1 taken 19 times.
|
2/2✓ Decision 'true' taken 14 times.
✓ Decision 'false' taken 19 times.
|
33 | if (index == 0) |
| 181 | 14 | return current; | ||
| 182 | 19 | index--; | ||
| 183 | } | |||
| 184 | ✗ | criticalError("getElementAtIndexForUnitText: null"); | ||
| 185 | ✗ | return nullptr; | ||
| 186 | } | |||
| 187 | #endif /* EFI_UNIT_TEST */ | |||
| 188 |