GCC Code Coverage Report


Directory: ./
File: firmware/controllers/system/efi_gpio.cpp
Date: 2025-10-24 14:26:41
Coverage Exec Excl Total
Lines: 84.2% 283 0 336
Functions: 88.0% 44 0 50
Branches: 72.4% 113 0 156
Decisions: 76.2% 64 - 84

Line Branch Decision Exec Source
1 /**
2 * @file efi_gpio.cpp
3 * @brief EFI-related GPIO code
4 *
5 * @date Sep 26, 2014
6 * @author Andrey Belomutskiy, (c) 2012-2020
7 */
8
9 #include "pch.h"
10 #include "bench_test.h"
11 #include "engine_sniffer.h"
12
13 #include "drivers/gpio/gpio_ext.h"
14
15 #if HW_HELLEN
16 #include "hellen_all_meta.h"
17 #endif // HW_HELLEN
18
19 #if EFI_ELECTRONIC_THROTTLE_BODY
20 #include "electronic_throttle.h"
21 #endif /* EFI_ELECTRONIC_THROTTLE_BODY */
22
23 // todo: clean this mess, this should become 'static'/private
24 EnginePins enginePins;
25
26 static const char* const sparkNames[] = { "Coil 1", "Coil 2", "Coil 3", "Coil 4", "Coil 5", "Coil 6", "Coil 7", "Coil 8",
27 "Coil 9", "Coil 10", "Coil 11", "Coil 12"};
28
29 static const char* const trailNames[] = { "Trail 1", "Trail 2", "Trail 3", "Trail 4", "Trail 5", "Trail 6", "Trail 7", "Trail 8",
30 "Trail 9", "Trail 10", "Trail 11", "Trail 12"};
31
32 static const char* const trailShortNames[] = { "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "rA", "rB", "rD" };
33
34 const char *vvtNames[] = {
35 "VVT1",
36 "VVT2",
37 "VVT3",
38 "VVT4"};
39
40 const char *laNames[] = {
41 "input1",
42 "input2",
43 "input3",
44 "input4"};
45
46 // these short names are part of engine sniffer protocol
47 static const char* const sparkShortNames[] = { PROTOCOL_COIL_SHORT_PREFIX "1", PROTOCOL_COIL_SHORT_PREFIX "2", "c3", "c4", "c5", "c6", "c7", "c8",
48 "c9", "cA", "cB", "cD"};
49
50 static const char* const injectorNames[] = { "Injector 1", "Injector 2", "Injector 3", "Injector 4", "Injector 5", "Injector 6",
51 "Injector 7", "Injector 8", "Injector 9", "Injector 10", "Injector 11", "Injector 12"};
52
53 static const char* const injectorShortNames[] = { PROTOCOL_INJ_SHORT_PREFIX "1", PROTOCOL_INJ_SHORT_PREFIX "2", "i3", "i4", "i5", "i6", "i7", "i8",
54 "i9", "iA", "iB", "iC"};
55
56 static const char* const injectorStage2Names[] = { "Injector Second Stage 1", "Injector Second Stage 2", "Injector Second Stage 3", "Injector Second Stage 4", "Injector Second Stage 5", "Injector Second Stage 6",
57 "Injector Second Stage 7", "Injector Second Stage 8", "Injector Second Stage 9", "Injector Second Stage 10", "Injector Second Stage 11", "Injector Second Stage 12"};
58
59 static const char* const injectorStage2ShortNames[] = { PROTOCOL_INJ_STAGE2_SHORT_PREFIX "1", PROTOCOL_INJ_STAGE2_SHORT_PREFIX "2", "j3", "j4", "j5", "j6", "j7", "j8",
60 "j9", "jA", "jB", "jC"};
61
62 static const char* const auxValveShortNames[] = { "a1", "a2"};
63
64 static RegisteredOutputPin * registeredOutputHead = nullptr;
65
66 1 RegisteredNamedOutputPin::RegisteredNamedOutputPin(const char *p_name, size_t pinOffset,
67 1 size_t pinModeOffset) : RegisteredOutputPin(p_name, pinOffset, pinModeOffset) {
68 1 }
69
70 RegisteredNamedOutputPin::RegisteredNamedOutputPin(const char *p_name, size_t pinOffset) :
71 RegisteredOutputPin(p_name, pinOffset) {
72 }
73
74 17 RegisteredOutputPin::RegisteredOutputPin(const char *p_registrationName, size_t pinOffset,
75 16 size_t pinModeOffset)
76 17 : next(registeredOutputHead)
77 17 , registrationName(p_registrationName)
78 17 , m_pinOffset(static_cast<uint16_t>(pinOffset))
79 17 , m_hasPinMode(true)
80 17 , m_pinModeOffset(static_cast<uint16_t>(pinModeOffset))
81 {
82 // adding into head of the list is so easy and since we do not care about order that's what we shall do
83 17 registeredOutputHead = this;
84 17 }
85
86 1 RegisteredOutputPin::RegisteredOutputPin(const char *p_registrationName, size_t pinOffset)
87 1 : next(registeredOutputHead)
88 1 , registrationName(p_registrationName)
89 1 , m_pinOffset(static_cast<uint16_t>(pinOffset))
90 1 , m_hasPinMode(false)
91 1 , m_pinModeOffset(-1)
92 {
93 // adding into head of the list is so easy and since we do not care about order that's what we shall do
94 1 registeredOutputHead = this;
95 1 }
96
97 50005 bool RegisteredOutputPin::isPinConfigurationChanged() {
98 50005 brain_pin_e curPin = *(brain_pin_e *) ((void *) (&((char*)&activeConfiguration)[m_pinOffset]));
99 50005 brain_pin_e newPin = *(brain_pin_e *) ((void *) (&((char*) engineConfiguration)[m_pinOffset]));
100 50005 bool pinChanged = curPin != newPin;
101
102
2/2
✓ Branch 0 taken 2778 times.
✓ Branch 1 taken 47227 times.
2/2
✓ Decision 'true' taken 2778 times.
✓ Decision 'false' taken 47227 times.
50005 if (!m_hasPinMode) {
103 2778 return pinChanged;
104 }
105
106 47227 pin_output_mode_e curMode = *(pin_output_mode_e *) ((void *) (&((char*)&activeConfiguration)[m_pinModeOffset]));
107 47227 pin_output_mode_e newMode = *(pin_output_mode_e *) ((void *) (&((char*) engineConfiguration)[m_pinModeOffset]));
108
3/4
✓ Branch 0 taken 47107 times.
✓ Branch 1 taken 120 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 47107 times.
47227 return pinChanged || curMode != newMode;
109 }
110
111 25003 void RegisteredOutputPin::init() {
112 25003 brain_pin_e newPin = *(brain_pin_e *) ((void *) (&((char*) engineConfiguration)[m_pinOffset]));
113
114 pin_output_mode_e newMode;
115
2/2
✓ Branch 0 taken 23614 times.
✓ Branch 1 taken 1389 times.
2/2
✓ Decision 'true' taken 23614 times.
✓ Decision 'false' taken 1389 times.
25003 if (m_hasPinMode) {
116 23614 newMode = *(pin_output_mode_e *) ((void *) (&((char*) engineConfiguration)[m_pinModeOffset]));
117 } else {
118 1389 newMode = OM_DEFAULT;
119 }
120
121
2/2
✓ Branch 1 taken 116 times.
✓ Branch 2 taken 24887 times.
2/2
✓ Decision 'true' taken 116 times.
✓ Decision 'false' taken 24887 times.
25003 if (isPinConfigurationChanged()) {
122 116 this->initPin(registrationName, newPin, newMode);
123 }
124 25003 }
125
126 25002 void RegisteredOutputPin::unregister() {
127
2/2
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 24998 times.
2/2
✓ Decision 'true' taken 4 times.
✓ Decision 'false' taken 24998 times.
25002 if (isPinConfigurationChanged()) {
128 4 OutputPin::deInit();
129 }
130 25002 }
131
132 #define CONFIG_OFFSET(x) (offsetof(engine_configuration_s, x))
133 // todo: pin and pinMode should be combined into a composite entity
134 // todo: one of the impediments is code generator hints handling (we need custom hints and those are not handled nice for fields of structs?)
135 #define CONFIG_PIN_OFFSETS(x) CONFIG_OFFSET(x##Pin), CONFIG_OFFSET(x##PinMode)
136
137 // offset of X within engineConfiguration, plus offset of Y within X
138 // decltype(engine_configuration_s::x) resolves the typename of the struct X inside engineConfiguration
139 #define CONFIG_OFFSET2(x, y) (offsetof(engine_configuration_s, x) + offsetof(decltype(engine_configuration_s::x), y))
140 #define CONFIG_PIN_OFFSETS2(x, y) CONFIG_OFFSET2(x, y##Pin), CONFIG_OFFSET2(x, y##PinMode)
141
142 1 EnginePins::EnginePins() :
143 // [tag:coding_by_convention] 'mainRelay' member here uses 'mainRelayPin' and 'mainRelayPinMode' configuration fields
144 1 mainRelay("Main Relay", CONFIG_PIN_OFFSETS(mainRelay)),
145 1 hpfpValve("HPFP Valve", CONFIG_PIN_OFFSETS(hpfpValve)),
146 1 starterControl("Starter Relay", CONFIG_PIN_OFFSETS(starterControl)),
147 1 starterRelayDisable("Starter Disable Relay", CONFIG_PIN_OFFSETS(starterRelayDisable)),
148 1 fanRelay("Fan Relay", CONFIG_PIN_OFFSETS(fan)),
149 1 fanRelay2("Fan Relay 2", CONFIG_PIN_OFFSETS(fan2)),
150 1 acRelay("A/C Relay", CONFIG_PIN_OFFSETS(acRelay)),
151 1 fuelPumpRelay("Fuel pump Relay", CONFIG_PIN_OFFSETS(fuelPump)),
152 1 nitrousRelay("Nitrous Relay", CONFIG_PIN_OFFSETS(nitrousRelay)),
153 1 vvlRelay("VVL Relay", CONFIG_PIN_OFFSETS(vvlRelay)),
154 #if EFI_HD_ACR
155 harleyAcr("Harley ACR", CONFIG_OFFSET(acrPin)),
156 harleyAcr2("Harley ACR 2", CONFIG_OFFSET(acrPin2)),
157 #endif // EFI_HD_ACR
158 1 boostPin("Boost", CONFIG_PIN_OFFSETS(boostControl)),
159 1 idleSolenoidPin("Idle Valve", CONFIG_OFFSET2(idle, solenoidPin), CONFIG_OFFSET2(idle, solenoidPinMode)),
160 1 secondIdleSolenoidPin("Idle Valve#2", CONFIG_OFFSET(secondSolenoidPin), CONFIG_OFFSET2(idle, solenoidPinMode)),
161 1 alternatorPin("Alternator control", CONFIG_PIN_OFFSETS(alternatorControl)),
162 1 checkEnginePin("checkEnginePin", CONFIG_PIN_OFFSETS(malfunctionIndicator)),
163 1 tachOut("tachOut", CONFIG_PIN_OFFSETS(tachOutput)),
164 1 triggerDecoderErrorPin("led: trigger debug", CONFIG_PIN_OFFSETS(triggerError)),
165
14/14
✓ Branch 1 taken 7 times.
✓ Branch 2 taken 1 time.
✓ Branch 12 taken 12 times.
✓ Branch 13 taken 1 time.
✓ Branch 15 taken 12 times.
✓ Branch 16 taken 1 time.
✓ Branch 18 taken 12 times.
✓ Branch 19 taken 1 time.
✓ Branch 21 taken 12 times.
✓ Branch 22 taken 1 time.
✓ Branch 24 taken 2 times.
✓ Branch 25 taken 1 time.
✓ Branch 26 taken 5 times.
✓ Branch 27 taken 1 time.
64 speedoOut("speedoOut", CONFIG_OFFSET(speedometerOutputPin))
166 {
167 1 hpfpValve.setName("hpfp");
168 #if EFI_HD_ACR
169 harleyAcr.setName("acr");
170 #endif // EFI_HD_ACR
171
172 static_assert(efi::size(sparkNames) >= MAX_CYLINDER_COUNT, "Too many ignition pins");
173 static_assert(efi::size(trailNames) >= MAX_CYLINDER_COUNT, "Too many ignition pins");
174 static_assert(efi::size(injectorNames) >= MAX_CYLINDER_COUNT, "Too many injection pins");
175
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 1 time.
2/2
✓ Decision 'true' taken 12 times.
✓ Decision 'false' taken 1 time.
13 for (int i = 0; i < MAX_CYLINDER_COUNT;i++) {
176 12 enginePins.coils[i].coilIndex = i;
177 12 enginePins.coils[i].setName(sparkNames[i]);
178 12 enginePins.coils[i].shortName = sparkShortNames[i];
179
180 12 enginePins.trailingCoils[i].setName(trailNames[i]);
181 12 enginePins.trailingCoils[i].shortName = trailShortNames[i];
182
183 12 enginePins.injectors[i].injectorIndex = i;
184 12 enginePins.injectors[i].setName(injectorNames[i]);
185 12 enginePins.injectors[i].shortName = injectorShortNames[i];
186
187 12 enginePins.injectorsStage2[i].injectorIndex = i;
188 12 enginePins.injectorsStage2[i].setName(injectorStage2Names[i]);
189 12 enginePins.injectorsStage2[i].shortName = injectorStage2ShortNames[i];
190 }
191
192 static_assert(efi::size(auxValveShortNames) >= AUX_DIGITAL_VALVE_COUNT, "Too many aux valve pins");
193
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 time.
2/2
✓ Decision 'true' taken 2 times.
✓ Decision 'false' taken 1 time.
3 for (int i = 0; i < AUX_DIGITAL_VALVE_COUNT;i++) {
194 2 enginePins.auxValve[i].setName(auxValveShortNames[i]);
195 }
196 1 }
197
198 /**
199 * Sets the value of the pin. On this layer the value is assigned as is, without any conversion.
200 */
201
202 #define unregisterOutputIfPinChanged(output, pin) { \
203 if (isConfigurationChanged(pin)) { \
204 (output).deInit(); \
205 } \
206 }
207
208 #define unregisterOutputIfPinOrModeChanged(output, pin, mode) { \
209 if (isPinOrModeChanged(pin, mode)) { \
210 (output).deInit(); \
211 } \
212 }
213
214 1003 bool EnginePins::stopPins() {
215 1003 bool result = false;
216
2/2
✓ Branch 0 taken 12036 times.
✓ Branch 1 taken 1003 times.
2/2
✓ Decision 'true' taken 12036 times.
✓ Decision 'false' taken 1003 times.
13039 for (int i = 0; i < MAX_CYLINDER_COUNT; i++) {
217 12036 result |= coils[i].stop();
218 12036 result |= injectors[i].stop();
219 12036 result |= injectorsStage2[i].stop();
220 12036 result |= trailingCoils[i].stop();
221 }
222
2/2
✓ Branch 0 taken 2006 times.
✓ Branch 1 taken 1003 times.
2/2
✓ Decision 'true' taken 2006 times.
✓ Decision 'false' taken 1003 times.
3009 for (int i = 0; i < AUX_DIGITAL_VALVE_COUNT; i++) {
223 2006 result |= auxValve[i].stop();
224 }
225 1003 return result;
226 }
227
228 1389 void EnginePins::unregisterPins() {
229 1389 stopInjectionPins();
230 1389 stopIgnitionPins();
231 #if EFI_AUX_VALVES
232 1389 stopAuxValves();
233 #endif
234
235 #if EFI_ELECTRONIC_THROTTLE_BODY
236 1389 unregisterEtbPins();
237 #endif /* EFI_ELECTRONIC_THROTTLE_BODY */
238
239 // todo: add pinMode
240
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1389 times.
1389 unregisterOutputIfPinChanged(sdCsPin, sdCardCsPin);
241
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1389 times.
1389 unregisterOutputIfPinChanged(accelerometerCs, accelerometerCsPin);
242
243 1389 RegisteredOutputPin * pin = registeredOutputHead;
244
2/2
✓ Branch 0 taken 25002 times.
✓ Branch 1 taken 1389 times.
2/2
✓ Decision 'true' taken 25002 times.
✓ Decision 'false' taken 1389 times.
26391 while (pin != nullptr) {
245 25002 pin->unregister();
246 25002 pin = pin->next;
247 }
248 1389 }
249
250 void EnginePins::debug() {
251 RegisteredOutputPin * pin = registeredOutputHead;
252 while (pin != nullptr) {
253 efiPrintf("%s %d", pin->getRegistrationName(), pin->currentLogicValue);
254 pin = pin->next;
255 }
256 }
257
258 1389 void EnginePins::startPins() {
259 #if EFI_ENGINE_CONTROL
260 1389 startInjectionPins();
261 1389 startIgnitionPins();
262 #endif /* EFI_ENGINE_CONTROL */
263
264 #if EFI_AUX_VALVES
265 1389 startAuxValves();
266 #endif // EFI_AUX_VALVES
267
268 1389 RegisteredOutputPin * pin = registeredOutputHead;
269
2/2
✓ Branch 0 taken 25002 times.
✓ Branch 1 taken 1389 times.
2/2
✓ Decision 'true' taken 25002 times.
✓ Decision 'false' taken 1389 times.
26391 while (pin != nullptr) {
270 25002 pin->init();
271 25002 pin = pin->next;
272 }
273 1389 }
274
275 1755 void EnginePins::reset() {
276
2/2
✓ Branch 0 taken 21060 times.
✓ Branch 1 taken 1755 times.
2/2
✓ Decision 'true' taken 21060 times.
✓ Decision 'false' taken 1755 times.
22815 for (int i = 0; i < MAX_CYLINDER_COUNT;i++) {
277 21060 injectors[i].reset();
278 21060 coils[i].reset();
279 21060 trailingCoils[i].reset();
280 }
281 1755 }
282
283 1389 void EnginePins::stopIgnitionPins() {
284
2/2
✓ Branch 0 taken 16668 times.
✓ Branch 1 taken 1389 times.
2/2
✓ Decision 'true' taken 16668 times.
✓ Decision 'false' taken 1389 times.
18057 for (int i = 0; i < MAX_CYLINDER_COUNT; i++) {
285
2/4
✓ Branch 2 taken 16668 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 16668 times.
16668 unregisterOutputIfPinOrModeChanged(enginePins.coils[i], ignitionPins[i], ignitionPinMode);
286
2/4
✓ Branch 2 taken 16668 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 16668 times.
16668 unregisterOutputIfPinOrModeChanged(enginePins.trailingCoils[i], trailingCoilPins[i], ignitionPinMode);
287 }
288 1389 }
289
290 1389 void EnginePins::stopInjectionPins() {
291
2/2
✓ Branch 0 taken 16668 times.
✓ Branch 1 taken 1389 times.
2/2
✓ Decision 'true' taken 16668 times.
✓ Decision 'false' taken 1389 times.
18057 for (int i = 0; i < MAX_CYLINDER_COUNT; i++) {
292
2/4
✓ Branch 2 taken 16668 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 16668 times.
16668 unregisterOutputIfPinOrModeChanged(enginePins.injectors[i], injectionPins[i], injectionPinMode);
293
2/4
✓ Branch 2 taken 16668 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 16668 times.
16668 unregisterOutputIfPinOrModeChanged(enginePins.injectorsStage2[i], injectionPinsStage2[i], injectionPinMode);
294 }
295 1389 }
296
297 #if EFI_AUX_VALVES
298 1389 void EnginePins::stopAuxValves() {
299
2/2
✓ Branch 0 taken 2778 times.
✓ Branch 1 taken 1389 times.
2/2
✓ Decision 'true' taken 2778 times.
✓ Decision 'false' taken 1389 times.
4167 for (int i = 0; i < AUX_DIGITAL_VALVE_COUNT; i++) {
300 2778 NamedOutputPin *output = &enginePins.auxValve[i];
301 // todo: do we need auxValveMode and reuse code?
302
2/2
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 2776 times.
2/2
✓ Decision 'true' taken 2 times.
✓ Decision 'false' taken 2776 times.
2778 if (isConfigurationChanged(auxValves[i])) {
303 2 (output)->deInit();
304 }
305 }
306 1389 }
307
308 1389 void EnginePins::startAuxValves() {
309 #if EFI_PROD_CODE
310 for (int i = 0; i < AUX_DIGITAL_VALVE_COUNT; i++) {
311 NamedOutputPin *output = &enginePins.auxValve[i];
312 // todo: do we need auxValveMode and reuse code?
313 if (isConfigurationChanged(auxValves[i])) {
314 output->initPin(output->getName(), engineConfiguration->auxValves[i]);
315 }
316 }
317 #endif /* EFI_PROD_CODE */
318 1389 }
319 #endif // EFI_AUX_VALVES
320
321 1389 void EnginePins::startIgnitionPins() {
322 #if EFI_PROD_CODE
323 for (size_t i = 0; i < engineConfiguration->cylindersCount; i++) {
324 NamedOutputPin *trailingOutput = &enginePins.trailingCoils[i];
325 if (isPinOrModeChanged(trailingCoilPins[i], ignitionPinMode)) {
326 trailingOutput->initPin(trailingOutput->getName(), engineConfiguration->trailingCoilPins[i], engineConfiguration->ignitionPinMode);
327 }
328
329 NamedOutputPin *output = &enginePins.coils[i];
330 if (isPinOrModeChanged(ignitionPins[i], ignitionPinMode)) {
331 output->initPin(output->getName(), engineConfiguration->ignitionPins[i], engineConfiguration->ignitionPinMode);
332 }
333 }
334 #endif /* EFI_PROD_CODE */
335 1389 }
336
337 1389 void EnginePins::startInjectionPins() {
338 #if EFI_PROD_CODE
339 // todo: should we move this code closer to the injection logic?
340 for (size_t i = 0; i < engineConfiguration->cylindersCount; i++) {
341 NamedOutputPin *output = &enginePins.injectors[i];
342 if (isPinOrModeChanged(injectionPins[i], injectionPinMode)) {
343 output->initPin(output->getName(), engineConfiguration->injectionPins[i],
344 engineConfiguration->injectionPinMode);
345 }
346
347 output = &enginePins.injectorsStage2[i];
348 if (isPinOrModeChanged(injectionPinsStage2[i], injectionPinMode)) {
349 output->initPin(output->getName(), engineConfiguration->injectionPinsStage2[i],
350 engineConfiguration->injectionPinMode);
351 }
352 }
353 #endif /* EFI_PROD_CODE */
354 1389 }
355
356 OutputPin *EnginePins::getOutputPinForBenchMode(bench_mode_e index) {
357 switch(index) {
358 #if EFI_VVT_PID
359 case BENCH_VVT0_VALVE:
360 return getVvtOutputPin(0);
361 case BENCH_VVT1_VALVE:
362 return getVvtOutputPin(1);
363 case BENCH_VVT2_VALVE:
364 return getVvtOutputPin(2);
365 case BENCH_VVT3_VALVE:
366 return getVvtOutputPin(3);
367 #endif // EFI_VVT_PID
368 case BENCH_MAIN_RELAY:
369 return &mainRelay;
370 case BENCH_HPFP_VALVE:
371 return &hpfpValve;
372 case BENCH_FUEL_PUMP:
373 return &fuelPumpRelay;
374 case BENCH_STARTER_ENABLE_RELAY:
375 return &starterControl;
376 case BENCH_CHECK_ENGINE_LIGHT:
377 return &checkEnginePin;
378 case BENCH_AC_COMPRESSOR_RELAY:
379 return &acRelay;
380 case BENCH_FAN_RELAY:
381 return &fanRelay;
382 #if EFI_HD_ACR
383 case HD_ACR:
384 return &harleyAcr;
385 case HD_ACR2:
386 return &harleyAcr2;
387 #endif
388 case BENCH_IDLE_VALVE:
389 return &idleSolenoidPin;
390 case BENCH_FAN_RELAY_2:
391 return &fanRelay;
392 default:
393 criticalError("Unexpected bench pin %d", index);
394 }
395 return nullptr;
396 }
397
398 #if EFI_UNIT_TEST
399 /*
400 * this function goes through the whole pin repository and sets them all to "GPIO::Unassigned",
401 * this is done as a clean-up for testing, since several motor configurations can have conflicting pins
402 * at the same time the productive de-init uses "isPinConfigurationChanged" to reset only the pins that have been changed,
403 * so in order for it to be properly de-initialized as it is done in prod, all pins are re-configured as unassigned,
404 * previously unused pins by tests will not be de-initialized since the configuration on them will be the same (Unassigned => Unassigned)
405 */
406 584 void EnginePins::resetForUnitTest() {
407 584 RegisteredOutputPin * pin = registeredOutputHead;
408
2/2
✓ Branch 0 taken 10512 times.
✓ Branch 1 taken 584 times.
2/2
✓ Decision 'true' taken 10512 times.
✓ Decision 'false' taken 584 times.
11096 while (pin != nullptr) {
409 10512 pin->brainPin = Gpio::Unassigned;
410 10512 pin = pin->next;
411 }
412 584 }
413 #endif
414
415 56 NamedOutputPin::NamedOutputPin() : OutputPin() {
416 56 }
417
418 1 NamedOutputPin::NamedOutputPin(const char *p_name) : OutputPin() {
419 1 name = p_name;
420 1 }
421
422 56204 const char *NamedOutputPin::getName() const {
423 56204 return name;
424 }
425
426 51 void NamedOutputPin::setName(const char* p_name) {
427 51 name = p_name;
428 51 }
429
430 20017 const char *NamedOutputPin::getShortName() const {
431
2/2
✓ Branch 0 taken 97 times.
✓ Branch 1 taken 19920 times.
20017 return shortName == nullptr ? name : shortName;
432 }
433
434 #if EFI_UNIT_TEST
435 extern bool verboseMode;
436 #endif // EFI_UNIT_TEST
437
438 9668 void NamedOutputPin::setHigh() {
439 9668 setHigh(nullptr);
440 9668 }
441
442 9687 void NamedOutputPin::setHigh(const char *msg) {
443 #if EFI_UNIT_TEST
444
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9687 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 9687 times.
9687 if (verboseMode) {
445 efiPrintf("pin %s goes high", name);
446 }
447 #endif // EFI_UNIT_TEST
448 #if EFI_DETAILED_LOGGING
449 // signal->hi_time = hTimeNow();
450 #endif /* EFI_DETAILED_LOGGING */
451
452 // turn the output level ACTIVE
453 9687 setValue(msg, true);
454
455 #if EFI_ENGINE_SNIFFER
456 9687 addEngineSnifferOutputPinEvent(this, FrontDirection::UP);
457 #endif /* EFI_ENGINE_SNIFFER */
458 9687 }
459
460 10312 void NamedOutputPin::setLow() {
461 10312 setLow(nullptr);
462 10312 }
463
464 10330 void NamedOutputPin::setLow(const char *msg) {
465 #if EFI_UNIT_TEST
466
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10330 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 10330 times.
10330 if (verboseMode) {
467 efiPrintf("pin %s goes low", name);
468 }
469 #endif // EFI_UNIT_TEST
470
471 // turn off the output
472 10330 setValue(msg, false);
473
474 #if EFI_ENGINE_SNIFFER
475 10330 addEngineSnifferOutputPinEvent(this, FrontDirection::DOWN);
476 #endif /* EFI_ENGINE_SNIFFER */
477 10330 }
478
479 50150 bool NamedOutputPin::stop() {
480 #if EFI_GPIO_HARDWARE
481
5/6
✓ Branch 1 taken 50150 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 4 times.
✓ Branch 5 taken 50146 times.
✓ Branch 6 taken 4 times.
✓ Branch 7 taken 50146 times.
2/2
✓ Decision 'true' taken 4 times.
✓ Decision 'false' taken 50146 times.
50150 if (isInitialized() && getLogicValue()) {
482 4 setValue("stop", false);
483 4 efiPrintf("turning off %s", name);
484 4 return true;
485 }
486 #endif /* EFI_GPIO_HARDWARE */
487 50146 return false;
488 }
489
490 24173 void InjectorOutputPin::reset() {
491 // If this injector was open, close it and reset state
492
2/2
✓ Branch 0 taken 49 times.
✓ Branch 1 taken 24124 times.
2/2
✓ Decision 'true' taken 49 times.
✓ Decision 'false' taken 24124 times.
24173 if (overlappingCounter != 0) {
493 49 overlappingCounter = 0;
494 49 setValue("reset", 0);
495 }
496
497 // todo: this could be refactored by calling some super-reset method
498 24173 currentLogicValue = 0;
499 24173 }
500
501 24 IgnitionOutputPin::IgnitionOutputPin() {
502 24 reset();
503 24 }
504
505 6283 void IgnitionOutputPin::setHigh() {
506 6283 NamedOutputPin::setHigh();
507 // this is NASTY but what's the better option? bytes? At cost of 22 extra bytes in output status packet?
508
6/7
✓ Branch 0 taken 5672 times.
✓ Branch 1 taken 147 times.
✓ Branch 2 taken 291 times.
✓ Branch 3 taken 67 times.
✓ Branch 4 taken 55 times.
✓ Branch 5 taken 51 times.
✗ Branch 6 not taken.
6283 switch (coilIndex) {
509
1/1
✓ Decision 'true' taken 5672 times.
5672 case 0:
510 5672 engine->outputChannels.coilState1 = true;
511 5672 break;
512
1/1
✓ Decision 'true' taken 147 times.
147 case 1:
513 147 engine->outputChannels.coilState2 = true;
514 147 break;
515
1/1
✓ Decision 'true' taken 291 times.
291 case 2:
516 291 engine->outputChannels.coilState3 = true;
517 291 break;
518
1/1
✓ Decision 'true' taken 67 times.
67 case 3:
519 67 engine->outputChannels.coilState4 = true;
520 67 break;
521
1/1
✓ Decision 'true' taken 55 times.
55 case 4:
522 55 engine->outputChannels.coilState5 = true;
523 55 break;
524
1/1
✓ Decision 'true' taken 51 times.
51 case 5:
525 51 engine->outputChannels.coilState6 = true;
526 51 break;
527 }
528 6283 }
529
530 6945 void IgnitionOutputPin::setLow() {
531 6945 NamedOutputPin::setLow();
532 // this is NASTY but what's the better option? bytes? At cost of 22 extra bytes in output status packet?
533
6/7
✓ Branch 0 taken 6301 times.
✓ Branch 1 taken 152 times.
✓ Branch 2 taken 310 times.
✓ Branch 3 taken 69 times.
✓ Branch 4 taken 57 times.
✓ Branch 5 taken 56 times.
✗ Branch 6 not taken.
6945 switch (coilIndex) {
534
1/1
✓ Decision 'true' taken 6301 times.
6301 case 0:
535 6301 engine->outputChannels.coilState1 = false;
536 6301 break;
537
1/1
✓ Decision 'true' taken 152 times.
152 case 1:
538 152 engine->outputChannels.coilState2 = false;
539 152 break;
540
1/1
✓ Decision 'true' taken 310 times.
310 case 2:
541 310 engine->outputChannels.coilState3 = false;
542 310 break;
543
1/1
✓ Decision 'true' taken 69 times.
69 case 3:
544 69 engine->outputChannels.coilState4 = false;
545 69 break;
546
1/1
✓ Decision 'true' taken 57 times.
57 case 4:
547 57 engine->outputChannels.coilState5 = false;
548 57 break;
549
1/1
✓ Decision 'true' taken 56 times.
56 case 5:
550 56 engine->outputChannels.coilState6 = false;
551 56 break;
552 }
553 6945 }
554
555 42144 void IgnitionOutputPin::reset() {
556 42144 signalFallSparkId = 0;
557 42144 }
558
559 58919 bool OutputPin::isInitialized() const {
560 #if EFI_GPIO_HARDWARE && EFI_PROD_CODE
561 #if (BOARD_EXT_GPIOCHIPS > 0)
562 if (ext)
563 return true;
564 #endif /* (BOARD_EXT_GPIOCHIPS > 0) */
565 return m_port != NULL;
566 #else /* EFI_GPIO_HARDWARE */
567 58919 return true;
568 #endif /* EFI_GPIO_HARDWARE */
569 }
570
571 6321 void OutputPin::toggle() {
572 6321 setValue("toggle", !getLogicValue());
573 6321 }
574
575 2 bool OutputPin::getAndSet(int logicValue) {
576 2 bool oldValue = getLogicValue();
577 2 setValue(logicValue);
578 2 return oldValue;
579 }
580
581 // This function is only used on real hardware
582 #if EFI_PROD_CODE
583 void OutputPin::setOnchipValue(int electricalValue) {
584 if (brainPin == Gpio::Unassigned || brainPin == Gpio::Invalid) {
585 // todo: make 'setOnchipValue' or 'reportsetOnchipValueError' virtual and override for NamedOutputPin?
586 warning(ObdCode::CUSTOM_ERR_6586, "attempting to change unassigned pin");
587 return;
588 }
589 palWritePad(m_port, m_pin, electricalValue);
590 }
591 #endif // EFI_PROD_CODE
592
593 13554 void OutputPin::setValue(int logicValue, bool isForce) {
594 13554 setValue(nullptr, logicValue, isForce);
595 13554 }
596
597 #if EFI_SIMULATOR
598 void OutputPin::resetToggleStats() {
599 durationsInStateMs[0] = durationsInStateMs[1] = 0;
600 pinToggleCounter = 0;
601 }
602 #endif // EFI_SIMULATOR
603
604 42123 void OutputPin::setValue(const char *msg, int logicValue, bool isForce) {
605 UNUSED(msg);
606
3/8
✓ Branch 1 taken 42123 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 42123 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 42123 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 42123 times.
42123 if ((isHwQcMode() || getOutputOnTheBenchTest() == this) && !isForce) {
607 return;
608 }
609
610 #if ENABLE_PERF_TRACE
611 // todo: https://github.com/rusefi/rusefi/issues/1638
612 // ScopePerf perf(PE::OutputPinSetValue);
613 #endif // ENABLE_PERF_TRACE
614
615 #if EFI_UNIT_TEST
616
2/2
✓ Branch 0 taken 26187 times.
✓ Branch 1 taken 15936 times.
2/2
✓ Decision 'true' taken 26187 times.
✓ Decision 'false' taken 15936 times.
42123 if (currentLogicValue != logicValue) {
617 26187 pinToggleCounter++;
618 }
619 #endif // EFI_UNIT_TEST
620
621 #if EFI_SIMULATOR
622 if (currentLogicValue != logicValue) {
623 if (pinToggleCounter > 0) {
624 durationsInStateMs[0] = durationsInStateMs[1];
625 durationsInStateMs[1] = pinToggleTimer.getElapsedUs() / 1000;
626 }
627 pinToggleCounter++;
628 pinToggleTimer.reset();
629 }
630 #endif // EFI_SIMULATOR
631
632 #if EFI_UNIT_TEST
633
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 42123 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 42123 times.
42123 if (verboseMode) {
634 efiPrintf("pin goes %d", logicValue);
635 }
636 #endif // EFI_UNIT_TEST
637
638 // Always store the current logical value of the pin (so it can be
639 // used internally even if not connected to a real hardware pin)
640 42123 currentLogicValue = logicValue;
641
642 // Nothing else to do if not configured
643
2/2
✓ Branch 1 taken 41666 times.
✓ Branch 2 taken 457 times.
2/2
✓ Decision 'true' taken 41666 times.
✓ Decision 'false' taken 457 times.
42123 if (!isBrainPinValid(brainPin)) {
644 41666 return;
645 }
646
647
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 457 times.
457 efiAssertVoid(ObdCode::CUSTOM_ERR_6622, mode <= OM_OPENDRAIN_INVERTED, "invalid pin_output_mode_e");
648
8/10
✓ Branch 0 taken 194 times.
✓ Branch 1 taken 263 times.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 193 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 time.
✓ Branch 6 taken 260 times.
✓ Branch 7 taken 3 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 260 times.
457 int electricalValue = getElectricalValue(logicValue, mode);
649
650 #if EFI_PROD_CODE
651 #if (BOARD_EXT_GPIOCHIPS > 0)
652 if (!this->ext) {
653 setOnchipValue(electricalValue);
654 } else {
655 /* external pin */
656 gpiochips_writePad(this->brainPin, logicValue);
657 /* TODO: check return value */
658 }
659 #else
660 setOnchipValue(electricalValue);
661 #endif
662 #else /* EFI_PROD_CODE */
663 457 setMockState(brainPin, electricalValue);
664 #endif /* EFI_PROD_CODE */
665 }
666
667 1655533 bool OutputPin::getLogicValue() const {
668 // Compare against 1 since it could also be INITIAL_PIN_STATE (which means logical 0, but we haven't initialized the pin yet)
669 1655533 return currentLogicValue == 1;
670 }
671
672 123 void OutputPin::setDefaultPinState(pin_output_mode_e outputMode) {
673
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 123 times.
123 assertOMode(mode);
674 123 this->mode = outputMode;
675 123 setValue(false, /*force*/true); // initial state
676 }
677
678 brain_pin_diag_e OutputPin::getDiag() const {
679 #if EFI_PROD_CODE
680 #if BOARD_EXT_GPIOCHIPS > 0
681 if (!brain_pin_is_onchip(brainPin)) {
682 return gpiochips_getDiag(brainPin);
683 }
684 #endif
685 #endif /* EFI_PROD_CODE */
686 // TODO: add hook to board code for custom diagnostic, like it is done on S105
687 return PIN_UNKNOWN;
688 }
689
690 584 void initMiscOutputPins() {
691 #if EFI_GPIO_HARDWARE
692
693 #if HAL_USE_SPI
694 enginePins.sdCsPin.initPin("SD CS", engineConfiguration->sdCardCsPin);
695 #endif /* HAL_USE_SPI */
696
697 #if EFI_SHAFT_POSITION_INPUT
698 // todo: migrate remaining OutputPin to RegisteredOutputPin in order to get consistent dynamic pin init/deinit
699 584 enginePins.debugTriggerSync.initPin("debug: sync", engineConfiguration->debugTriggerSync);
700 #endif // EFI_SHAFT_POSITION_INPUT
701
702 584 enginePins.o2heater.initPin("O2 heater", engineConfiguration->o2heaterPin);
703
704 #endif /* EFI_GPIO_HARDWARE */
705 584 }
706
707 1179 void OutputPin::initPin(const char *p_msg, brain_pin_e p_brainPin) {
708 1179 initPin(p_msg, p_brainPin, OM_DEFAULT);
709 1178 }
710
711 1299 void OutputPin::initPin(const char *msg, brain_pin_e p_brainPin, pin_output_mode_e outputMode, bool forceInitWithFatalError) {
712 #if EFI_UNIT_TEST
713 1299 pinToggleCounter = 0;
714 #endif
715
716
2/2
✓ Branch 1 taken 1175 times.
✓ Branch 2 taken 124 times.
2/2
✓ Decision 'true' taken 1175 times.
✓ Decision 'false' taken 124 times.
1299 if (!isBrainPinValid(p_brainPin)) {
717 1175 return;
718 }
719
720 // Enter a critical section so that other threads can't change the pin state out from underneath us
721 chibios_rt::CriticalSectionLocker csl;
722
723
2/4
✓ Branch 0 taken 124 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 124 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 124 times.
124 if (!forceInitWithFatalError && hasFirmwareError()) {
724 // Don't allow initializing more pins if we have a fatal error.
725 // Pins should have just been reset, so we shouldn't try to init more.
726 return;
727 }
728
729 // Check that this OutputPin isn't already assigned to another pin (reinit is allowed to change mode)
730 // To avoid this error, call deInit() first
731
6/6
✓ Branch 1 taken 59 times.
✓ Branch 2 taken 65 times.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 58 times.
✓ Branch 5 taken 1 time.
✓ Branch 6 taken 123 times.
2/2
✓ Decision 'true' taken 1 time.
✓ Decision 'false' taken 123 times.
124 if (isBrainPinValid(brainPin) && brainPin != p_brainPin) {
732 1 firmwareError(ObdCode::CUSTOM_OBD_PIN_CONFLICT, "outputPin [%s] already assigned, cannot reassign without unregister first", msg);
733 return;
734 }
735
736
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 123 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 123 times.
123 if (outputMode > OM_OPENDRAIN_INVERTED) {
737 firmwareError(ObdCode::CUSTOM_INVALID_MODE_SETTING, "%s invalid pin_output_mode_e %d %s",
738 msg,
739 outputMode,
740 hwPortname(p_brainPin)
741 );
742 return;
743 }
744
745 #if EFI_GPIO_HARDWARE && EFI_PROD_CODE
746 iomode_t l_mode = (outputMode == OM_DEFAULT || outputMode == OM_INVERTED) ?
747 PAL_MODE_OUTPUT_PUSHPULL : PAL_MODE_OUTPUT_OPENDRAIN;
748
749 #if (BOARD_EXT_GPIOCHIPS > 0)
750 this->ext = false;
751 #endif
752 if (brain_pin_is_onchip(p_brainPin)) {
753 m_port = getHwPort(msg, p_brainPin);
754 m_pin = getHwPin(msg, p_brainPin);
755
756 // Validate port
757 if (!m_port) {
758 criticalError("OutputPin::initPin got invalid port for pin idx %d", static_cast<int>(p_brainPin));
759 return;
760 }
761 }
762 #if (BOARD_EXT_GPIOCHIPS > 0)
763 else {
764 this->ext = true;
765 }
766 #endif
767 #endif // briefly leave the include guard because we need to set default state in tests
768
769 123 brainPin = p_brainPin;
770
771 // The order of the next two calls may look strange, which is a good observation.
772 // We call them in this order so that the pin is set to a known state BEFORE
773 // it's enabled. Enabling the pin then setting it could result in a (brief)
774 // mystery state being driven on the pin (potentially dangerous).
775 123 setDefaultPinState(outputMode);
776
777 #if EFI_GPIO_HARDWARE && EFI_PROD_CODE
778 efiSetPadMode(msg, brainPin, l_mode);
779 if (brain_pin_is_onchip(brainPin)) {
780 // todo: handle OM_OPENDRAIN and OM_OPENDRAIN_INVERTED as well
781 if (outputMode == OM_DEFAULT || outputMode == OM_INVERTED) {
782 #ifndef DISABLE_PIN_STATE_VALIDATION
783 int actualValue = palReadPad(m_port, m_pin);
784 // we had enough drama with pin configuration in board.h and else that we shall self-check
785
786 const int logicalValue =
787 (outputMode == OM_INVERTED)
788 ? !actualValue
789 : actualValue;
790
791 // if the pin was set to logical 1, then set an error and disable the pin so that things don't catch fire
792 if (logicalValue) {
793 criticalError("HARDWARE VALIDATION FAILED %s: unexpected startup pin state %s actual value=%d logical value=%d mode=%s", msg, hwPortname(brainPin), actualValue, logicalValue, getPin_output_mode_e(outputMode));
794 OutputPin::deInit();
795 }
796 #endif
797 }
798 }
799 #endif /* EFI_GPIO_HARDWARE */
800 }
801
802 7 void OutputPin::deInit() {
803 7 efiPrintf("unregistering %s", hwPortname(brainPin));
804
805 // Unregister under lock - we don't want other threads mucking with the pin while we're trying to turn it off
806 chibios_rt::CriticalSectionLocker csl;
807
808 // nothing to do if not registered in the first place
809
2/2
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 2 times.
2/2
✓ Decision 'true' taken 5 times.
✓ Decision 'false' taken 2 times.
7 if (!isBrainPinValid(brainPin)) {
810 5 return;
811 }
812
813 #if (BOARD_EXT_GPIOCHIPS > 0)
814 2 ext = false;
815 #endif // (BOARD_EXT_GPIOCHIPS > 0)
816
817 #if EFI_GPIO_HARDWARE && EFI_PROD_CODE
818 efiSetPadUnused(brainPin);
819 #endif /* EFI_GPIO_HARDWARE */
820
821 // Clear the pin so that it won't get set any more
822 2 brainPin = Gpio::Unassigned;
823 }
824
825 #if EFI_GPIO_HARDWARE
826
827 // questionable trick: we avoid using 'getHwPort' and 'getHwPin' in case of errors in order to increase the changes of turning the LED
828 // by reducing stack requirement
829 ioportid_t criticalErrorLedPort;
830 ioportmask_t criticalErrorLedPin;
831 uint8_t criticalErrorLedState;
832
833 #if EFI_PROD_CODE
834 static void initErrorLed(Gpio led) {
835 enginePins.errorLedPin.initPin("led: CRITICAL status", led, (LED_PIN_MODE));
836 criticalErrorLedPort = getHwPort("CRITICAL", led);
837 criticalErrorLedPin = getHwPin("CRITICAL", led);
838 criticalErrorLedState = (LED_PIN_MODE == OM_INVERTED) ? 0 : 1;
839 }
840 #endif /* EFI_PROD_CODE */
841
842 void initPrimaryPins() {
843 #if EFI_PROD_CODE
844 initErrorLed(LED_CRITICAL_ERROR_BRAIN_PIN);
845
846 addConsoleAction("gpio_pins", EnginePins::debug);
847 #endif /* EFI_PROD_CODE */
848 }
849
850 /**
851 * This method is part of fatal error handling.
852 * The whole method is pretty naive, but that's at least something.
853 */
854 void turnAllPinsOff() {
855 for (int i = 0; i < MAX_CYLINDER_COUNT; i++) {
856 enginePins.injectors[i].setValue(false);
857 enginePins.coils[i].setValue(false);
858 enginePins.trailingCoils[i].setValue(false);
859 }
860 enginePins.mainRelay.setValue(false);
861 enginePins.fuelPumpRelay.setValue(false);
862 enginePins.checkEnginePin.setValue(true); // yes this one can go ON
863 #if EFI_PROD_CODE && HW_HELLEN
864 hellenDisableEnSilently();
865 #endif
866 }
867 #endif /* EFI_GPIO_HARDWARE */
868
869