GCC Code Coverage Report


Directory: ./
File: firmware/controllers/system/efi_gpio.cpp
Date: 2025-12-30 16:19:43
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 50365 bool RegisteredOutputPin::isPinConfigurationChanged() {
98 50365 brain_pin_e curPin = *(brain_pin_e *) ((void *) (&((char*)&activeConfiguration)[m_pinOffset]));
99 50365 brain_pin_e newPin = *(brain_pin_e *) ((void *) (&((char*) engineConfiguration)[m_pinOffset]));
100 50365 bool pinChanged = curPin != newPin;
101
102
2/2
✓ Branch 0 taken 2798 times.
✓ Branch 1 taken 47567 times.
2/2
✓ Decision 'true' taken 2798 times.
✓ Decision 'false' taken 47567 times.
50365 if (!m_hasPinMode) {
103 2798 return pinChanged;
104 }
105
106 47567 pin_output_mode_e curMode = *(pin_output_mode_e *) ((void *) (&((char*)&activeConfiguration)[m_pinModeOffset]));
107 47567 pin_output_mode_e newMode = *(pin_output_mode_e *) ((void *) (&((char*) engineConfiguration)[m_pinModeOffset]));
108
3/4
✓ Branch 0 taken 47447 times.
✓ Branch 1 taken 120 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 47447 times.
47567 return pinChanged || curMode != newMode;
109 }
110
111 25183 void RegisteredOutputPin::init() {
112 25183 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 23784 times.
✓ Branch 1 taken 1399 times.
2/2
✓ Decision 'true' taken 23784 times.
✓ Decision 'false' taken 1399 times.
25183 if (m_hasPinMode) {
116 23784 newMode = *(pin_output_mode_e *) ((void *) (&((char*) engineConfiguration)[m_pinModeOffset]));
117 } else {
118 1399 newMode = OM_DEFAULT;
119 }
120
121
2/2
✓ Branch 1 taken 116 times.
✓ Branch 2 taken 25067 times.
2/2
✓ Decision 'true' taken 116 times.
✓ Decision 'false' taken 25067 times.
25183 if (isPinConfigurationChanged()) {
122 116 this->initPin(registrationName, newPin, newMode);
123 }
124 25183 }
125
126 25182 void RegisteredOutputPin::unregister() {
127
2/2
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 25178 times.
2/2
✓ Decision 'true' taken 4 times.
✓ Decision 'false' taken 25178 times.
25182 if (isPinConfigurationChanged()) {
128 4 OutputPin::deInit();
129 }
130 25182 }
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 1008 bool EnginePins::stopPins() {
215 1008 bool result = false;
216
2/2
✓ Branch 0 taken 12096 times.
✓ Branch 1 taken 1008 times.
2/2
✓ Decision 'true' taken 12096 times.
✓ Decision 'false' taken 1008 times.
13104 for (int i = 0; i < MAX_CYLINDER_COUNT; i++) {
217 12096 result |= coils[i].stop();
218 12096 result |= injectors[i].stop();
219 12096 result |= injectorsStage2[i].stop();
220 12096 result |= trailingCoils[i].stop();
221 }
222
2/2
✓ Branch 0 taken 2016 times.
✓ Branch 1 taken 1008 times.
2/2
✓ Decision 'true' taken 2016 times.
✓ Decision 'false' taken 1008 times.
3024 for (int i = 0; i < AUX_DIGITAL_VALVE_COUNT; i++) {
223 2016 result |= auxValve[i].stop();
224 }
225 1008 return result;
226 }
227
228 1399 void EnginePins::unregisterPins() {
229 1399 stopInjectionPins();
230 1399 stopIgnitionPins();
231 #if EFI_AUX_VALVES
232 1399 stopAuxValves();
233 #endif
234
235 #if EFI_ELECTRONIC_THROTTLE_BODY
236 1399 unregisterEtbPins();
237 #endif /* EFI_ELECTRONIC_THROTTLE_BODY */
238
239 // todo: add pinMode
240
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1399 times.
1399 unregisterOutputIfPinChanged(sdCsPin, sdCardCsPin);
241
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1399 times.
1399 unregisterOutputIfPinChanged(accelerometerCs, accelerometerCsPin);
242
243 1399 RegisteredOutputPin * pin = registeredOutputHead;
244
2/2
✓ Branch 0 taken 25182 times.
✓ Branch 1 taken 1399 times.
2/2
✓ Decision 'true' taken 25182 times.
✓ Decision 'false' taken 1399 times.
26581 while (pin != nullptr) {
245 25182 pin->unregister();
246 25182 pin = pin->next;
247 }
248 1399 }
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 1399 void EnginePins::startPins() {
259 #if EFI_ENGINE_CONTROL
260 1399 startInjectionPins();
261 1399 startIgnitionPins();
262 #endif /* EFI_ENGINE_CONTROL */
263
264 #if EFI_AUX_VALVES
265 1399 startAuxValves();
266 #endif // EFI_AUX_VALVES
267
268 1399 RegisteredOutputPin * pin = registeredOutputHead;
269
2/2
✓ Branch 0 taken 25182 times.
✓ Branch 1 taken 1399 times.
2/2
✓ Decision 'true' taken 25182 times.
✓ Decision 'false' taken 1399 times.
26581 while (pin != nullptr) {
270 25182 pin->init();
271 25182 pin = pin->next;
272 }
273 1399 }
274
275 1770 void EnginePins::reset() {
276
2/2
✓ Branch 0 taken 21240 times.
✓ Branch 1 taken 1770 times.
2/2
✓ Decision 'true' taken 21240 times.
✓ Decision 'false' taken 1770 times.
23010 for (int i = 0; i < MAX_CYLINDER_COUNT;i++) {
277 21240 injectors[i].reset();
278 21240 coils[i].reset();
279 21240 trailingCoils[i].reset();
280 }
281 1770 }
282
283 1399 void EnginePins::stopIgnitionPins() {
284
2/2
✓ Branch 0 taken 16788 times.
✓ Branch 1 taken 1399 times.
2/2
✓ Decision 'true' taken 16788 times.
✓ Decision 'false' taken 1399 times.
18187 for (int i = 0; i < MAX_CYLINDER_COUNT; i++) {
285
2/4
✓ Branch 2 taken 16788 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 16788 times.
16788 unregisterOutputIfPinOrModeChanged(enginePins.coils[i], ignitionPins[i], ignitionPinMode);
286
2/4
✓ Branch 2 taken 16788 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 16788 times.
16788 unregisterOutputIfPinOrModeChanged(enginePins.trailingCoils[i], trailingCoilPins[i], ignitionPinMode);
287 }
288 1399 }
289
290 1399 void EnginePins::stopInjectionPins() {
291
2/2
✓ Branch 0 taken 16788 times.
✓ Branch 1 taken 1399 times.
2/2
✓ Decision 'true' taken 16788 times.
✓ Decision 'false' taken 1399 times.
18187 for (int i = 0; i < MAX_CYLINDER_COUNT; i++) {
292
2/4
✓ Branch 2 taken 16788 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 16788 times.
16788 unregisterOutputIfPinOrModeChanged(enginePins.injectors[i], injectionPins[i], injectionPinMode);
293
2/4
✓ Branch 2 taken 16788 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 16788 times.
16788 unregisterOutputIfPinOrModeChanged(enginePins.injectorsStage2[i], injectionPinsStage2[i], injectionPinMode);
294 }
295 1399 }
296
297 #if EFI_AUX_VALVES
298 1399 void EnginePins::stopAuxValves() {
299
2/2
✓ Branch 0 taken 2798 times.
✓ Branch 1 taken 1399 times.
2/2
✓ Decision 'true' taken 2798 times.
✓ Decision 'false' taken 1399 times.
4197 for (int i = 0; i < AUX_DIGITAL_VALVE_COUNT; i++) {
300 2798 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 2796 times.
2/2
✓ Decision 'true' taken 2 times.
✓ Decision 'false' taken 2796 times.
2798 if (isConfigurationChanged(auxValves[i])) {
303 2 (output)->deInit();
304 }
305 }
306 1399 }
307
308 1399 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 1399 }
319 #endif // EFI_AUX_VALVES
320
321 1399 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 1399 }
336
337 1399 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 1399 }
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 589 void EnginePins::resetForUnitTest() {
407 589 RegisteredOutputPin * pin = registeredOutputHead;
408
2/2
✓ Branch 0 taken 10602 times.
✓ Branch 1 taken 589 times.
2/2
✓ Decision 'true' taken 10602 times.
✓ Decision 'false' taken 589 times.
11191 while (pin != nullptr) {
409 10602 pin->brainPin = Gpio::Unassigned;
410 10602 pin = pin->next;
411 }
412 589 }
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 50400 bool NamedOutputPin::stop() {
480 #if EFI_GPIO_HARDWARE
481
5/6
✓ Branch 1 taken 50400 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 5 times.
✓ Branch 5 taken 50395 times.
✓ Branch 6 taken 5 times.
✓ Branch 7 taken 50395 times.
2/2
✓ Decision 'true' taken 5 times.
✓ Decision 'false' taken 50395 times.
50400 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 50395 return false;
488 }
489
490 24329 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 24281 times.
2/2
✓ Decision 'true' taken 48 times.
✓ Decision 'false' taken 24281 times.
24329 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 24329 currentLogicValue = 0;
499 24329 }
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 42504 void IgnitionOutputPin::reset() {
556 42504 signalFallSparkId = 0;
557 42504 }
558
559 59016 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 59016 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 19052 void OutputPin::setValue(int logicValue, bool isForce) {
594 19052 setValue(nullptr, logicValue, isForce);
595 19052 }
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 48453 void OutputPin::setValue(const char *msg, int logicValue, bool isForce) {
605 UNUSED(msg);
606
3/8
✓ Branch 1 taken 48453 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 48453 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 48453 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 48453 times.
48453 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 18332 times.
2/2
✓ Decision 'true' taken 30121 times.
✓ Decision 'false' taken 18332 times.
48453 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 48453 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 48453 times.
48453 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 48453 currentLogicValue = logicValue;
641
642 // Nothing else to do if not configured
643
2/2
✓ Branch 1 taken 42517 times.
✓ Branch 2 taken 5936 times.
2/2
✓ Decision 'true' taken 42517 times.
✓ Decision 'false' taken 5936 times.
48453 if (!isBrainPinValid(brainPin)) {
644 42517 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 1965929 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 1965929 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 589 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 589 enginePins.debugTriggerSync.initPin("debug: sync", engineConfiguration->debugTriggerSync);
700 #endif // EFI_SHAFT_POSITION_INPUT
701
702 589 enginePins.o2heater.initPin("O2 heater", engineConfiguration->o2heaterPin);
703
704 #endif /* EFI_GPIO_HARDWARE */
705 589 }
706
707 1189 void OutputPin::initPin(const char *p_msg, brain_pin_e p_brainPin) {
708 1189 initPin(p_msg, p_brainPin, OM_DEFAULT);
709 1188 }
710
711 1341 void OutputPin::initPin(const char *msg, brain_pin_e p_brainPin, pin_output_mode_e outputMode, bool forceInitWithFatalError) {
712 #if EFI_UNIT_TEST
713 1341 pinToggleCounter = 0;
714 #endif
715
716
2/2
✓ Branch 1 taken 1201 times.
✓ Branch 2 taken 140 times.
2/2
✓ Decision 'true' taken 1201 times.
✓ Decision 'false' taken 140 times.
1341 if (!isBrainPinValid(p_brainPin)) {
717 1201 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