GCC Code Coverage Report


Directory: ./
File: firmware/controllers/system/efi_gpio.cpp
Date: 2025-11-16 14:52:24
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 50437 bool RegisteredOutputPin::isPinConfigurationChanged() {
98 50437 brain_pin_e curPin = *(brain_pin_e *) ((void *) (&((char*)&activeConfiguration)[m_pinOffset]));
99 50437 brain_pin_e newPin = *(brain_pin_e *) ((void *) (&((char*) engineConfiguration)[m_pinOffset]));
100 50437 bool pinChanged = curPin != newPin;
101
102
2/2
✓ Branch 0 taken 2802 times.
✓ Branch 1 taken 47635 times.
2/2
✓ Decision 'true' taken 2802 times.
✓ Decision 'false' taken 47635 times.
50437 if (!m_hasPinMode) {
103 2802 return pinChanged;
104 }
105
106 47635 pin_output_mode_e curMode = *(pin_output_mode_e *) ((void *) (&((char*)&activeConfiguration)[m_pinModeOffset]));
107 47635 pin_output_mode_e newMode = *(pin_output_mode_e *) ((void *) (&((char*) engineConfiguration)[m_pinModeOffset]));
108
3/4
✓ Branch 0 taken 47515 times.
✓ Branch 1 taken 120 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 47515 times.
47635 return pinChanged || curMode != newMode;
109 }
110
111 25219 void RegisteredOutputPin::init() {
112 25219 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 23818 times.
✓ Branch 1 taken 1401 times.
2/2
✓ Decision 'true' taken 23818 times.
✓ Decision 'false' taken 1401 times.
25219 if (m_hasPinMode) {
116 23818 newMode = *(pin_output_mode_e *) ((void *) (&((char*) engineConfiguration)[m_pinModeOffset]));
117 } else {
118 1401 newMode = OM_DEFAULT;
119 }
120
121
2/2
✓ Branch 1 taken 116 times.
✓ Branch 2 taken 25103 times.
2/2
✓ Decision 'true' taken 116 times.
✓ Decision 'false' taken 25103 times.
25219 if (isPinConfigurationChanged()) {
122 116 this->initPin(registrationName, newPin, newMode);
123 }
124 25219 }
125
126 25218 void RegisteredOutputPin::unregister() {
127
2/2
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 25214 times.
2/2
✓ Decision 'true' taken 4 times.
✓ Decision 'false' taken 25214 times.
25218 if (isPinConfigurationChanged()) {
128 4 OutputPin::deInit();
129 }
130 25218 }
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 1009 bool EnginePins::stopPins() {
215 1009 bool result = false;
216
2/2
✓ Branch 0 taken 12108 times.
✓ Branch 1 taken 1009 times.
2/2
✓ Decision 'true' taken 12108 times.
✓ Decision 'false' taken 1009 times.
13117 for (int i = 0; i < MAX_CYLINDER_COUNT; i++) {
217 12108 result |= coils[i].stop();
218 12108 result |= injectors[i].stop();
219 12108 result |= injectorsStage2[i].stop();
220 12108 result |= trailingCoils[i].stop();
221 }
222
2/2
✓ Branch 0 taken 2018 times.
✓ Branch 1 taken 1009 times.
2/2
✓ Decision 'true' taken 2018 times.
✓ Decision 'false' taken 1009 times.
3027 for (int i = 0; i < AUX_DIGITAL_VALVE_COUNT; i++) {
223 2018 result |= auxValve[i].stop();
224 }
225 1009 return result;
226 }
227
228 1401 void EnginePins::unregisterPins() {
229 1401 stopInjectionPins();
230 1401 stopIgnitionPins();
231 #if EFI_AUX_VALVES
232 1401 stopAuxValves();
233 #endif
234
235 #if EFI_ELECTRONIC_THROTTLE_BODY
236 1401 unregisterEtbPins();
237 #endif /* EFI_ELECTRONIC_THROTTLE_BODY */
238
239 // todo: add pinMode
240
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1401 times.
1401 unregisterOutputIfPinChanged(sdCsPin, sdCardCsPin);
241
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1401 times.
1401 unregisterOutputIfPinChanged(accelerometerCs, accelerometerCsPin);
242
243 1401 RegisteredOutputPin * pin = registeredOutputHead;
244
2/2
✓ Branch 0 taken 25218 times.
✓ Branch 1 taken 1401 times.
2/2
✓ Decision 'true' taken 25218 times.
✓ Decision 'false' taken 1401 times.
26619 while (pin != nullptr) {
245 25218 pin->unregister();
246 25218 pin = pin->next;
247 }
248 1401 }
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 1401 void EnginePins::startPins() {
259 #if EFI_ENGINE_CONTROL
260 1401 startInjectionPins();
261 1401 startIgnitionPins();
262 #endif /* EFI_ENGINE_CONTROL */
263
264 #if EFI_AUX_VALVES
265 1401 startAuxValves();
266 #endif // EFI_AUX_VALVES
267
268 1401 RegisteredOutputPin * pin = registeredOutputHead;
269
2/2
✓ Branch 0 taken 25218 times.
✓ Branch 1 taken 1401 times.
2/2
✓ Decision 'true' taken 25218 times.
✓ Decision 'false' taken 1401 times.
26619 while (pin != nullptr) {
270 25218 pin->init();
271 25218 pin = pin->next;
272 }
273 1401 }
274
275 1773 void EnginePins::reset() {
276
2/2
✓ Branch 0 taken 21276 times.
✓ Branch 1 taken 1773 times.
2/2
✓ Decision 'true' taken 21276 times.
✓ Decision 'false' taken 1773 times.
23049 for (int i = 0; i < MAX_CYLINDER_COUNT;i++) {
277 21276 injectors[i].reset();
278 21276 coils[i].reset();
279 21276 trailingCoils[i].reset();
280 }
281 1773 }
282
283 1401 void EnginePins::stopIgnitionPins() {
284
2/2
✓ Branch 0 taken 16812 times.
✓ Branch 1 taken 1401 times.
2/2
✓ Decision 'true' taken 16812 times.
✓ Decision 'false' taken 1401 times.
18213 for (int i = 0; i < MAX_CYLINDER_COUNT; i++) {
285
2/4
✓ Branch 2 taken 16812 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 16812 times.
16812 unregisterOutputIfPinOrModeChanged(enginePins.coils[i], ignitionPins[i], ignitionPinMode);
286
2/4
✓ Branch 2 taken 16812 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 16812 times.
16812 unregisterOutputIfPinOrModeChanged(enginePins.trailingCoils[i], trailingCoilPins[i], ignitionPinMode);
287 }
288 1401 }
289
290 1401 void EnginePins::stopInjectionPins() {
291
2/2
✓ Branch 0 taken 16812 times.
✓ Branch 1 taken 1401 times.
2/2
✓ Decision 'true' taken 16812 times.
✓ Decision 'false' taken 1401 times.
18213 for (int i = 0; i < MAX_CYLINDER_COUNT; i++) {
292
2/4
✓ Branch 2 taken 16812 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 16812 times.
16812 unregisterOutputIfPinOrModeChanged(enginePins.injectors[i], injectionPins[i], injectionPinMode);
293
2/4
✓ Branch 2 taken 16812 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 16812 times.
16812 unregisterOutputIfPinOrModeChanged(enginePins.injectorsStage2[i], injectionPinsStage2[i], injectionPinMode);
294 }
295 1401 }
296
297 #if EFI_AUX_VALVES
298 1401 void EnginePins::stopAuxValves() {
299
2/2
✓ Branch 0 taken 2802 times.
✓ Branch 1 taken 1401 times.
2/2
✓ Decision 'true' taken 2802 times.
✓ Decision 'false' taken 1401 times.
4203 for (int i = 0; i < AUX_DIGITAL_VALVE_COUNT; i++) {
300 2802 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 2800 times.
2/2
✓ Decision 'true' taken 2 times.
✓ Decision 'false' taken 2800 times.
2802 if (isConfigurationChanged(auxValves[i])) {
303 2 (output)->deInit();
304 }
305 }
306 1401 }
307
308 1401 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 1401 }
319 #endif // EFI_AUX_VALVES
320
321 1401 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 1401 }
336
337 1401 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 1401 }
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 590 void EnginePins::resetForUnitTest() {
407 590 RegisteredOutputPin * pin = registeredOutputHead;
408
2/2
✓ Branch 0 taken 10620 times.
✓ Branch 1 taken 590 times.
2/2
✓ Decision 'true' taken 10620 times.
✓ Decision 'false' taken 590 times.
11210 while (pin != nullptr) {
409 10620 pin->brainPin = Gpio::Unassigned;
410 10620 pin = pin->next;
411 }
412 590 }
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 56214 const char *NamedOutputPin::getName() const {
423 56214 return name;
424 }
425
426 51 void NamedOutputPin::setName(const char* p_name) {
427 51 name = p_name;
428 51 }
429
430 20849 const char *NamedOutputPin::getShortName() const {
431
2/2
✓ Branch 0 taken 97 times.
✓ Branch 1 taken 20752 times.
20849 return shortName == nullptr ? name : shortName;
432 }
433
434 #if EFI_UNIT_TEST
435 extern bool verboseMode;
436 #endif // EFI_UNIT_TEST
437
438 10121 void NamedOutputPin::setHigh() {
439 10121 setHigh(nullptr);
440 10121 }
441
442 10140 void NamedOutputPin::setHigh(const char *msg) {
443 #if EFI_UNIT_TEST
444
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10140 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 10140 times.
10140 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 10140 setValue(msg, true);
454
455 #if EFI_ENGINE_SNIFFER
456 10140 addEngineSnifferOutputPinEvent(this, FrontDirection::UP);
457 #endif /* EFI_ENGINE_SNIFFER */
458 10140 }
459
460 10691 void NamedOutputPin::setLow() {
461 10691 setLow(nullptr);
462 10691 }
463
464 10709 void NamedOutputPin::setLow(const char *msg) {
465 #if EFI_UNIT_TEST
466
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10709 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 10709 times.
10709 if (verboseMode) {
467 efiPrintf("pin %s goes low", name);
468 }
469 #endif // EFI_UNIT_TEST
470
471 // turn off the output
472 10709 setValue(msg, false);
473
474 #if EFI_ENGINE_SNIFFER
475 10709 addEngineSnifferOutputPinEvent(this, FrontDirection::DOWN);
476 #endif /* EFI_ENGINE_SNIFFER */
477 10709 }
478
479 50450 bool NamedOutputPin::stop() {
480 #if EFI_GPIO_HARDWARE
481
5/6
✓ Branch 1 taken 50450 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 5 times.
✓ Branch 5 taken 50445 times.
✓ Branch 6 taken 5 times.
✓ Branch 7 taken 50445 times.
2/2
✓ Decision 'true' taken 5 times.
✓ Decision 'false' taken 50445 times.
50450 if (isInitialized() && getLogicValue()) {
482 5 setValue("stop", false);
483 5 efiPrintf("turning off %s", name);
484 5 return true;
485 }
486 #endif /* EFI_GPIO_HARDWARE */
487 50445 return false;
488 }
489
490 24365 void InjectorOutputPin::reset() {
491 // If this injector was open, close it and reset state
492
2/2
✓ Branch 0 taken 48 times.
✓ Branch 1 taken 24317 times.
2/2
✓ Decision 'true' taken 48 times.
✓ Decision 'false' taken 24317 times.
24365 if (overlappingCounter != 0) {
493 48 overlappingCounter = 0;
494 48 setValue("reset", 0);
495 }
496
497 // todo: this could be refactored by calling some super-reset method
498 24365 currentLogicValue = 0;
499 24365 }
500
501 24 IgnitionOutputPin::IgnitionOutputPin() {
502 24 reset();
503 24 }
504
505 6323 void IgnitionOutputPin::setHigh() {
506 6323 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 5718 times.
✓ Branch 1 taken 138 times.
✓ Branch 2 taken 293 times.
✓ Branch 3 taken 67 times.
✓ Branch 4 taken 56 times.
✓ Branch 5 taken 51 times.
✗ Branch 6 not taken.
6323 switch (coilIndex) {
509
1/1
✓ Decision 'true' taken 5718 times.
5718 case 0:
510 5718 engine->outputChannels.coilState1 = true;
511 5718 break;
512
1/1
✓ Decision 'true' taken 138 times.
138 case 1:
513 138 engine->outputChannels.coilState2 = true;
514 138 break;
515
1/1
✓ Decision 'true' taken 293 times.
293 case 2:
516 293 engine->outputChannels.coilState3 = true;
517 293 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 56 times.
56 case 4:
522 56 engine->outputChannels.coilState5 = true;
523 56 break;
524
1/1
✓ Decision 'true' taken 51 times.
51 case 5:
525 51 engine->outputChannels.coilState6 = true;
526 51 break;
527 }
528 6323 }
529
530 6910 void IgnitionOutputPin::setLow() {
531 6910 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 6276 times.
✓ Branch 1 taken 141 times.
✓ Branch 2 taken 311 times.
✓ Branch 3 taken 69 times.
✓ Branch 4 taken 57 times.
✓ Branch 5 taken 56 times.
✗ Branch 6 not taken.
6910 switch (coilIndex) {
534
1/1
✓ Decision 'true' taken 6276 times.
6276 case 0:
535 6276 engine->outputChannels.coilState1 = false;
536 6276 break;
537
1/1
✓ Decision 'true' taken 141 times.
141 case 1:
538 141 engine->outputChannels.coilState2 = false;
539 141 break;
540
1/1
✓ Decision 'true' taken 311 times.
311 case 2:
541 311 engine->outputChannels.coilState3 = false;
542 311 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 6910 }
554
555 42576 void IgnitionOutputPin::reset() {
556 42576 signalFallSparkId = 0;
557 42576 }
558
559 59066 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 59066 return true;
568 #endif /* EFI_GPIO_HARDWARE */
569 }
570
571 6311 void OutputPin::toggle() {
572 6311 setValue("toggle", !getLogicValue());
573 6311 }
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 19059 void OutputPin::setValue(int logicValue, bool isForce) {
594 19059 setValue(nullptr, logicValue, isForce);
595 19059 }
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 48462 void OutputPin::setValue(const char *msg, int logicValue, bool isForce) {
605 UNUSED(msg);
606
3/8
✓ Branch 1 taken 48462 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 48462 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 48462 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 48462 times.
48462 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 30121 times.
✓ Branch 1 taken 18341 times.
2/2
✓ Decision 'true' taken 30121 times.
✓ Decision 'false' taken 18341 times.
48462 if (currentLogicValue != logicValue) {
617 30121 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 48462 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 48462 times.
48462 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 48462 currentLogicValue = logicValue;
641
642 // Nothing else to do if not configured
643
2/2
✓ Branch 1 taken 42526 times.
✓ Branch 2 taken 5936 times.
2/2
✓ Decision 'true' taken 42526 times.
✓ Decision 'false' taken 5936 times.
48462 if (!isBrainPinValid(brainPin)) {
644 42526 return;
645 }
646
647
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5936 times.
5936 efiAssertVoid(ObdCode::CUSTOM_ERR_6622, mode <= OM_OPENDRAIN_INVERTED, "invalid pin_output_mode_e");
648
8/10
✓ Branch 0 taken 1711 times.
✓ Branch 1 taken 4225 times.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 1710 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 time.
✓ Branch 6 taken 4222 times.
✓ Branch 7 taken 3 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 4222 times.
5936 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 5936 setMockState(brainPin, electricalValue);
664 #endif /* EFI_PROD_CODE */
665 }
666
667 1965981 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 1965981 return currentLogicValue == 1;
670 }
671
672 139 void OutputPin::setDefaultPinState(pin_output_mode_e outputMode) {
673
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 139 times.
139 assertOMode(mode);
674 139 this->mode = outputMode;
675 139 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 590 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 590 enginePins.debugTriggerSync.initPin("debug: sync", engineConfiguration->debugTriggerSync);
700 #endif // EFI_SHAFT_POSITION_INPUT
701
702 590 enginePins.o2heater.initPin("O2 heater", engineConfiguration->o2heaterPin);
703
704 #endif /* EFI_GPIO_HARDWARE */
705 590 }
706
707 1191 void OutputPin::initPin(const char *p_msg, brain_pin_e p_brainPin) {
708 1191 initPin(p_msg, p_brainPin, OM_DEFAULT);
709 1190 }
710
711 1343 void OutputPin::initPin(const char *msg, brain_pin_e p_brainPin, pin_output_mode_e outputMode, bool forceInitWithFatalError) {
712 #if EFI_UNIT_TEST
713 1343 pinToggleCounter = 0;
714 #endif
715
716
2/2
✓ Branch 1 taken 1203 times.
✓ Branch 2 taken 140 times.
2/2
✓ Decision 'true' taken 1203 times.
✓ Decision 'false' taken 140 times.
1343 if (!isBrainPinValid(p_brainPin)) {
717 1203 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 140 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 140 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 140 times.
140 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 71 times.
✓ Branch 2 taken 69 times.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 70 times.
✓ Branch 5 taken 1 time.
✓ Branch 6 taken 139 times.
2/2
✓ Decision 'true' taken 1 time.
✓ Decision 'false' taken 139 times.
140 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 139 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 139 times.
139 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 139 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 139 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