GCC Code Coverage Report


Directory: ./
File: firmware/controllers/system/timer/trigger_scheduler.cpp
Date: 2025-10-24 14:26:41
Coverage Exec Excl Total
Lines: 88.9% 56 0 63
Functions: 100.0% 9 0 9
Branches: 60.4% 29 0 48
Decisions: 81.0% 17 - 21

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 = &current->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