Line | Branch | Decision | Exec | Source |
---|---|---|---|---|
1 | #include "pch.h" | |||
2 | ||||
3 | #include "event_queue.h" | |||
4 | ||||
5 | 1285 | bool TriggerScheduler::assertNotInList(AngleBasedEvent *head, AngleBasedEvent *element) { | ||
6 | /* this code is just to validate state, no functional load*/ | |||
7 | decltype(head) current; | |||
8 | 1285 | int counter = 0; | ||
9 |
2/2✓ Branch 0 taken 21 times.
✓ Branch 1 taken 1282 times.
|
1303 | LL_FOREACH2(head, current, nextToothEvent) { | |
10 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 21 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 21 times.
|
21 | 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 18 times.
|
2/2✓ Decision 'true' taken 3 times.
✓ Decision 'false' taken 18 times.
|
21 | 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 | 1282 | 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 | 8872 | 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 | 8872 | 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 1259 times.
|
2/2✓ Decision 'true' taken 7613 times.
✓ Decision 'false' taken 1259 times.
|
8872 | 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 | 1259 | schedule(msg, event, action); | ||
62 | ||||
63 | 1259 | return false; | ||
64 | } | |||
65 | } | |||
66 | ||||
67 | 1285 | void TriggerScheduler::schedule(const char *msg, AngleBasedEvent* event, action_s const& action) { | ||
68 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 1285 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 1285 times.
|
1285 | 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 | 1285 | 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 1282 times.
✓ Branch 2 taken 3 times.
|
2/2✓ Decision 'true' taken 1282 times.
✓ Decision 'false' taken 3 times.
|
1285 | 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 8 times.
✓ Branch 1 taken 1274 times.
✓ Branch 2 taken 9 times.
✓ Branch 3 taken 8 times.
|
1291 | LL_APPEND2(m_angleBasedEventsHead, event, nextToothEvent); | |
86 | } | |||
87 | } | |||
88 | 1285 | } | ||
89 | ||||
90 | 31187 | void TriggerScheduler::scheduleEventsUntilNextTriggerTooth(float rpm, | ||
91 | efitick_t edgeTimestamp, float currentPhase, float nextPhase) { | |||
92 | ||||
93 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 31187 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 31187 times.
|
31187 | if (rpm == 0) { |
94 | // this might happen for instance in case of a single trigger event after a pause | |||
95 | ✗ | return; | ||
96 | } | |||
97 | ||||
98 | AngleBasedEvent *current, *tmp, *keephead; | |||
99 | 31187 | AngleBasedEvent *keeptail = nullptr; | ||
100 | ||||
101 | { | |||
102 | chibios_rt::CriticalSectionLocker csl; | |||
103 | ||||
104 | 31187 | keephead = m_angleBasedEventsHead; | ||
105 | 31187 | m_angleBasedEventsHead = nullptr; | ||
106 | } | |||
107 | ||||
108 |
4/4✓ Branch 0 taken 2007 times.
✓ Branch 1 taken 31187 times.
✓ Branch 2 taken 2007 times.
✓ Branch 3 taken 31187 times.
|
33194 | LL_FOREACH_SAFE2(keephead, current, tmp, nextToothEvent) | |
109 | { | |||
110 |
2/2✓ Branch 1 taken 1268 times.
✓ Branch 2 taken 739 times.
|
2/2✓ Decision 'true' taken 1268 times.
✓ Decision 'false' taken 739 times.
|
2007 | if (current->shouldSchedule(currentPhase, nextPhase)) { |
111 | // time to fire a spark which was scheduled previously | |||
112 | ||||
113 | // Yes this looks like O(n^2), but that's only over the entire engine | |||
114 | // cycle. It's really O(mn + nn) where m = # of teeth and n = # events | |||
115 | // fired per cycle. The number of teeth outweigh the number of events, at | |||
116 | // least for 60-2.... So odds are we're only firing an event or two per | |||
117 | // tooth, which means the outer loop is really only O(n). And if we are | |||
118 | // firing many events per teeth, then it's likely the events before this | |||
119 | // one also fired and thus the call to LL_DELETE2 is closer to O(1). | |||
120 |
1/8✓ Branch 0 taken 1268 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.
|
1268 | LL_DELETE2(keephead, current, nextToothEvent); | |
121 | ||||
122 | 1268 | scheduling_s * sDown = ¤t->eventScheduling; | ||
123 | ||||
124 | #if SPARK_EXTREME_LOGGING | |||
125 | 1268 | efiPrintf("time to invoke [%.1f, %.1f) %d %d", | ||
126 | currentPhase, nextPhase, getRevolutionCounter(), time2print(getTimeNowUs())); | |||
127 | #endif /* SPARK_EXTREME_LOGGING */ | |||
128 | ||||
129 | // In case this event was scheduled by overdwell protection, cancel it so | |||
130 | // we can re-schedule at the correct time | |||
131 | // [tag:overdwell] | |||
132 | 1268 | engine->scheduler.cancel(sDown); | ||
133 | ||||
134 | 1268 | scheduleByAngle( | ||
135 | sDown, | |||
136 | edgeTimestamp, | |||
137 | current->getAngleFromNow(currentPhase), | |||
138 | 1268 | current->action | ||
139 | ); | |||
140 | } else { | |||
141 | 739 | keeptail = current; // Used for fast list concatenation | ||
142 | } | |||
143 | } | |||
144 | ||||
145 |
2/2✓ Branch 0 taken 660 times.
✓ Branch 1 taken 30527 times.
|
2/2✓ Decision 'true' taken 660 times.
✓ Decision 'false' taken 30527 times.
|
31187 | if (keephead) { |
146 | chibios_rt::CriticalSectionLocker csl; | |||
147 | ||||
148 | // Put any new entries onto the end of the keep list | |||
149 | 660 | keeptail->nextToothEvent = m_angleBasedEventsHead; | ||
150 | 660 | m_angleBasedEventsHead = keephead; | ||
151 | } | |||
152 | } | |||
153 | ||||
154 | 10879 | bool AngleBasedEvent::shouldSchedule(float currentPhase, float nextPhase) const { | ||
155 | 10879 | return isPhaseInRange(this->enginePhase, currentPhase, nextPhase); | ||
156 | } | |||
157 | ||||
158 | 8881 | float AngleBasedEvent::getAngleFromNow(float currentPhase) const { | ||
159 | 8881 | float angleOffset = this->enginePhase - currentPhase; | ||
160 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8881 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 8881 times.
|
8881 | if (angleOffset < 0) { |
161 | ✗ | angleOffset += engine->engineState.engineCycle; | ||
162 | } | |||
163 | ||||
164 | 8881 | return angleOffset; | ||
165 | } | |||
166 | ||||
167 | #if EFI_UNIT_TEST | |||
168 | // todo: reduce code duplication with another 'getElementAtIndexForUnitText' | |||
169 | 14 | AngleBasedEvent * TriggerScheduler::getElementAtIndexForUnitTest(int index) { | ||
170 | AngleBasedEvent * current; | |||
171 | ||||
172 |
1/2✓ Branch 0 taken 33 times.
✗ Branch 1 not taken.
|
33 | LL_FOREACH2(m_angleBasedEventsHead, current, nextToothEvent) | |
173 | { | |||
174 |
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) |
175 | 14 | return current; | ||
176 | 19 | index--; | ||
177 | } | |||
178 | ✗ | criticalError("getElementAtIndexForUnitText: null"); | ||
179 | ✗ | return nullptr; | ||
180 | } | |||
181 | #endif /* EFI_UNIT_TEST */ | |||
182 |