GCC Code Coverage Report


Directory: ./
File: firmware/controllers/system/efi_gpio.cpp
Date: 2025-10-03 00:57:22
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 49861 bool RegisteredOutputPin::isPinConfigurationChanged() {
98 49861 brain_pin_e curPin = *(brain_pin_e *) ((void *) (&((char*)&activeConfiguration)[m_pinOffset]));
99 49861 brain_pin_e newPin = *(brain_pin_e *) ((void *) (&((char*) engineConfiguration)[m_pinOffset]));
100 49861 bool pinChanged = curPin != newPin;
101
102
2/2
✓ Branch 0 taken 2770 times.
✓ Branch 1 taken 47091 times.
2/2
✓ Decision 'true' taken 2770 times.
✓ Decision 'false' taken 47091 times.
49861 if (!m_hasPinMode) {
103 2770 return pinChanged;
104 }
105
106 47091 pin_output_mode_e curMode = *(pin_output_mode_e *) ((void *) (&((char*)&activeConfiguration)[m_pinModeOffset]));
107 47091 pin_output_mode_e newMode = *(pin_output_mode_e *) ((void *) (&((char*) engineConfiguration)[m_pinModeOffset]));
108
3/4
✓ Branch 0 taken 46971 times.
✓ Branch 1 taken 120 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 46971 times.
47091 return pinChanged || curMode != newMode;
109 }
110
111 24931 void RegisteredOutputPin::init() {
112 24931 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 23546 times.
✓ Branch 1 taken 1385 times.
2/2
✓ Decision 'true' taken 23546 times.
✓ Decision 'false' taken 1385 times.
24931 if (m_hasPinMode) {
116 23546 newMode = *(pin_output_mode_e *) ((void *) (&((char*) engineConfiguration)[m_pinModeOffset]));
117 } else {
118 1385 newMode = OM_DEFAULT;
119 }
120
121
2/2
✓ Branch 1 taken 116 times.
✓ Branch 2 taken 24815 times.
2/2
✓ Decision 'true' taken 116 times.
✓ Decision 'false' taken 24815 times.
24931 if (isPinConfigurationChanged()) {
122 116 this->initPin(registrationName, newPin, newMode);
123 }
124 24931 }
125
126 24930 void RegisteredOutputPin::unregister() {
127
2/2
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 24926 times.
2/2
✓ Decision 'true' taken 4 times.
✓ Decision 'false' taken 24926 times.
24930 if (isPinConfigurationChanged()) {
128 4 OutputPin::deInit();
129 }
130 24930 }
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 1002 bool EnginePins::stopPins() {
215 1002 bool result = false;
216
2/2
✓ Branch 0 taken 12024 times.
✓ Branch 1 taken 1002 times.
2/2
✓ Decision 'true' taken 12024 times.
✓ Decision 'false' taken 1002 times.
13026 for (int i = 0; i < MAX_CYLINDER_COUNT; i++) {
217 12024 result |= coils[i].stop();
218 12024 result |= injectors[i].stop();
219 12024 result |= injectorsStage2[i].stop();
220 12024 result |= trailingCoils[i].stop();
221 }
222
2/2
✓ Branch 0 taken 2004 times.
✓ Branch 1 taken 1002 times.
2/2
✓ Decision 'true' taken 2004 times.
✓ Decision 'false' taken 1002 times.
3006 for (int i = 0; i < AUX_DIGITAL_VALVE_COUNT; i++) {
223 2004 result |= auxValve[i].stop();
224 }
225 1002 return result;
226 }
227
228 1385 void EnginePins::unregisterPins() {
229 1385 stopInjectionPins();
230 1385 stopIgnitionPins();
231 #if EFI_AUX_VALVES
232 1385 stopAuxValves();
233 #endif
234
235 #if EFI_ELECTRONIC_THROTTLE_BODY
236 1385 unregisterEtbPins();
237 #endif /* EFI_ELECTRONIC_THROTTLE_BODY */
238
239 // todo: add pinMode
240
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1385 times.
1385 unregisterOutputIfPinChanged(sdCsPin, sdCardCsPin);
241
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1385 times.
1385 unregisterOutputIfPinChanged(accelerometerCs, accelerometerCsPin);
242
243 1385 RegisteredOutputPin * pin = registeredOutputHead;
244
2/2
✓ Branch 0 taken 24930 times.
✓ Branch 1 taken 1385 times.
2/2
✓ Decision 'true' taken 24930 times.
✓ Decision 'false' taken 1385 times.
26315 while (pin != nullptr) {
245 24930 pin->unregister();
246 24930 pin = pin->next;
247 }
248 1385 }
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 1385 void EnginePins::startPins() {
259 #if EFI_ENGINE_CONTROL
260 1385 startInjectionPins();
261 1385 startIgnitionPins();
262 #endif /* EFI_ENGINE_CONTROL */
263
264 #if EFI_AUX_VALVES
265 1385 startAuxValves();
266 #endif // EFI_AUX_VALVES
267
268 1385 RegisteredOutputPin * pin = registeredOutputHead;
269
2/2
✓ Branch 0 taken 24930 times.
✓ Branch 1 taken 1385 times.
2/2
✓ Decision 'true' taken 24930 times.
✓ Decision 'false' taken 1385 times.
26315 while (pin != nullptr) {
270 24930 pin->init();
271 24930 pin = pin->next;
272 }
273 1385 }
274
275 1752 void EnginePins::reset() {
276
2/2
✓ Branch 0 taken 21024 times.
✓ Branch 1 taken 1752 times.
2/2
✓ Decision 'true' taken 21024 times.
✓ Decision 'false' taken 1752 times.
22776 for (int i = 0; i < MAX_CYLINDER_COUNT;i++) {
277 21024 injectors[i].reset();
278 21024 coils[i].reset();
279 21024 trailingCoils[i].reset();
280 }
281 1752 }
282
283 1385 void EnginePins::stopIgnitionPins() {
284
2/2
✓ Branch 0 taken 16620 times.
✓ Branch 1 taken 1385 times.
2/2
✓ Decision 'true' taken 16620 times.
✓ Decision 'false' taken 1385 times.
18005 for (int i = 0; i < MAX_CYLINDER_COUNT; i++) {
285
2/4
✓ Branch 2 taken 16620 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 16620 times.
16620 unregisterOutputIfPinOrModeChanged(enginePins.coils[i], ignitionPins[i], ignitionPinMode);
286
2/4
✓ Branch 2 taken 16620 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 16620 times.
16620 unregisterOutputIfPinOrModeChanged(enginePins.trailingCoils[i], trailingCoilPins[i], ignitionPinMode);
287 }
288 1385 }
289
290 1385 void EnginePins::stopInjectionPins() {
291
2/2
✓ Branch 0 taken 16620 times.
✓ Branch 1 taken 1385 times.
2/2
✓ Decision 'true' taken 16620 times.
✓ Decision 'false' taken 1385 times.
18005 for (int i = 0; i < MAX_CYLINDER_COUNT; i++) {
292
2/4
✓ Branch 2 taken 16620 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 16620 times.
16620 unregisterOutputIfPinOrModeChanged(enginePins.injectors[i], injectionPins[i], injectionPinMode);
293
2/4
✓ Branch 2 taken 16620 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 16620 times.
16620 unregisterOutputIfPinOrModeChanged(enginePins.injectorsStage2[i], injectionPinsStage2[i], injectionPinMode);
294 }
295 1385 }
296
297 #if EFI_AUX_VALVES
298 1385 void EnginePins::stopAuxValves() {
299
2/2
✓ Branch 0 taken 2770 times.
✓ Branch 1 taken 1385 times.
2/2
✓ Decision 'true' taken 2770 times.
✓ Decision 'false' taken 1385 times.
4155 for (int i = 0; i < AUX_DIGITAL_VALVE_COUNT; i++) {
300 2770 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 2768 times.
2/2
✓ Decision 'true' taken 2 times.
✓ Decision 'false' taken 2768 times.
2770 if (isConfigurationChanged(auxValves[i])) {
303 2 (output)->deInit();
304 }
305 }
306 1385 }
307
308 1385 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 1385 }
319 #endif // EFI_AUX_VALVES
320
321 1385 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 1385 }
336
337 1385 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 1385 }
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 583 void EnginePins::resetForUnitTest() {
407 583 RegisteredOutputPin * pin = registeredOutputHead;
408
2/2
✓ Branch 0 taken 10494 times.
✓ Branch 1 taken 583 times.
2/2
✓ Decision 'true' taken 10494 times.
✓ Decision 'false' taken 583 times.
11077 while (pin != nullptr) {
409 10494 pin->brainPin = Gpio::Unassigned;
410 10494 pin = pin->next;
411 }
412 583 }
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 41591 const char *NamedOutputPin::getName() const {
423 41591 return name;
424 }
425
426 51 void NamedOutputPin::setName(const char* p_name) {
427 51 name = p_name;
428 51 }
429
430 18607 const char *NamedOutputPin::getShortName() const {
431
2/2
✓ Branch 0 taken 97 times.
✓ Branch 1 taken 18510 times.
18607 return shortName == nullptr ? name : shortName;
432 }
433
434 #if EFI_UNIT_TEST
435 extern bool verboseMode;
436 #endif // EFI_UNIT_TEST
437
438 8899 void NamedOutputPin::setHigh() {
439 8899 setHigh(nullptr);
440 8899 }
441
442 8918 void NamedOutputPin::setHigh(const char *msg) {
443 #if EFI_UNIT_TEST
444
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8918 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 8918 times.
8918 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 8918 setValue(msg, true);
454
455 #if EFI_ENGINE_SNIFFER
456 8918 addEngineSnifferOutputPinEvent(this, FrontDirection::UP);
457 #endif /* EFI_ENGINE_SNIFFER */
458 8918 }
459
460 9671 void NamedOutputPin::setLow() {
461 9671 setLow(nullptr);
462 9671 }
463
464 9689 void NamedOutputPin::setLow(const char *msg) {
465 #if EFI_UNIT_TEST
466
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9689 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 9689 times.
9689 if (verboseMode) {
467 efiPrintf("pin %s goes low", name);
468 }
469 #endif // EFI_UNIT_TEST
470
471 // turn off the output
472 9689 setValue(msg, false);
473
474 #if EFI_ENGINE_SNIFFER
475 9689 addEngineSnifferOutputPinEvent(this, FrontDirection::DOWN);
476 #endif /* EFI_ENGINE_SNIFFER */
477 9689 }
478
479 50100 bool NamedOutputPin::stop() {
480 #if EFI_GPIO_HARDWARE
481
5/6
✓ Branch 1 taken 50100 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 4 times.
✓ Branch 5 taken 50096 times.
✓ Branch 6 taken 4 times.
✓ Branch 7 taken 50096 times.
2/2
✓ Decision 'true' taken 4 times.
✓ Decision 'false' taken 50096 times.
50100 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 50096 return false;
488 }
489
490 24053 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 24004 times.
2/2
✓ Decision 'true' taken 49 times.
✓ Decision 'false' taken 24004 times.
24053 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 24053 currentLogicValue = 0;
499 24053 }
500
501 24 IgnitionOutputPin::IgnitionOutputPin() {
502 24 reset();
503 24 }
504
505 5506 void IgnitionOutputPin::setHigh() {
506 5506 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 4895 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.
5506 switch (coilIndex) {
509
1/1
✓ Decision 'true' taken 4895 times.
4895 case 0:
510 4895 engine->outputChannels.coilState1 = true;
511 4895 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 5506 }
529
530 6296 void IgnitionOutputPin::setLow() {
531 6296 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 5594 times.
✓ Branch 1 taken 157 times.
✓ Branch 2 taken 347 times.
✓ Branch 3 taken 75 times.
✓ Branch 4 taken 62 times.
✓ Branch 5 taken 61 times.
✗ Branch 6 not taken.
6296 switch (coilIndex) {
534
1/1
✓ Decision 'true' taken 5594 times.
5594 case 0:
535 5594 engine->outputChannels.coilState1 = false;
536 5594 break;
537
1/1
✓ Decision 'true' taken 157 times.
157 case 1:
538 157 engine->outputChannels.coilState2 = false;
539 157 break;
540
1/1
✓ Decision 'true' taken 347 times.
347 case 2:
541 347 engine->outputChannels.coilState3 = false;
542 347 break;
543
1/1
✓ Decision 'true' taken 75 times.
75 case 3:
544 75 engine->outputChannels.coilState4 = false;
545 75 break;
546
1/1
✓ Decision 'true' taken 62 times.
62 case 4:
547 62 engine->outputChannels.coilState5 = false;
548 62 break;
549
1/1
✓ Decision 'true' taken 61 times.
61 case 5:
550 61 engine->outputChannels.coilState6 = false;
551 61 break;
552 }
553 6296 }
554
555 42072 void IgnitionOutputPin::reset() {
556 42072 signalFallSparkId = 0;
557 42072 }
558
559 58196 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 58196 return true;
568 #endif /* EFI_GPIO_HARDWARE */
569 }
570
571 5426 void OutputPin::toggle() {
572 5426 setValue("toggle", !getLogicValue());
573 5426 }
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 12767 void OutputPin::setValue(int logicValue, bool isForce) {
594 12767 setValue(nullptr, logicValue, isForce);
595 12767 }
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 39029 void OutputPin::setValue(const char *msg, int logicValue, bool isForce) {
605 UNUSED(msg);
606
3/8
✓ Branch 1 taken 39029 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 39029 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 39029 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 39029 times.
39029 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 23754 times.
✓ Branch 1 taken 15275 times.
2/2
✓ Decision 'true' taken 23754 times.
✓ Decision 'false' taken 15275 times.
39029 if (currentLogicValue != logicValue) {
617 23754 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 39029 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 39029 times.
39029 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 39029 currentLogicValue = logicValue;
641
642 // Nothing else to do if not configured
643
2/2
✓ Branch 1 taken 38572 times.
✓ Branch 2 taken 457 times.
2/2
✓ Decision 'true' taken 38572 times.
✓ Decision 'false' taken 457 times.
39029 if (!isBrainPinValid(brainPin)) {
644 38572 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 1630269 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 1630269 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 583 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 583 enginePins.debugTriggerSync.initPin("debug: sync", engineConfiguration->debugTriggerSync);
700 #endif // EFI_SHAFT_POSITION_INPUT
701
702 583 enginePins.o2heater.initPin("O2 heater", engineConfiguration->o2heaterPin);
703
704 #endif /* EFI_GPIO_HARDWARE */
705 583 }
706
707 1177 void OutputPin::initPin(const char *p_msg, brain_pin_e p_brainPin) {
708 1177 initPin(p_msg, p_brainPin, OM_DEFAULT);
709 1176 }
710
711 1297 void OutputPin::initPin(const char *msg, brain_pin_e p_brainPin, pin_output_mode_e outputMode, bool forceInitWithFatalError) {
712 #if EFI_UNIT_TEST
713 1297 pinToggleCounter = 0;
714 #endif
715
716
2/2
✓ Branch 1 taken 1173 times.
✓ Branch 2 taken 124 times.
2/2
✓ Decision 'true' taken 1173 times.
✓ Decision 'false' taken 124 times.
1297 if (!isBrainPinValid(p_brainPin)) {
717 1173 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