Line | Branch | Decision | Exec | Source |
---|---|---|---|---|
1 | /** | |||
2 | * @file tunerstudio.cpp | |||
3 | * @brief Binary protocol implementation | |||
4 | * | |||
5 | * This implementation would not happen without the documentation | |||
6 | * provided by Jon Zeeff (jon@zeeff.com) | |||
7 | * | |||
8 | * | |||
9 | * @brief Integration with EFI Analytics Tuner Studio software | |||
10 | * | |||
11 | * Tuner Studio has a really simple protocol, a minimal implementation | |||
12 | * capable of displaying current engine state on the gauges would | |||
13 | * require only two commands: queryCommand and ochGetCommand | |||
14 | * | |||
15 | * queryCommand: | |||
16 | * Communication initialization command. TunerStudio sends a single byte H | |||
17 | * ECU response: | |||
18 | * One of the known ECU id strings. | |||
19 | * | |||
20 | * ochGetCommand: | |||
21 | * Request for output channels state.TunerStudio sends a single byte O | |||
22 | * ECU response: | |||
23 | * A snapshot of output channels as described in [OutputChannels] section of the .ini file | |||
24 | * The length of this block is 'ochBlockSize' property of the .ini file | |||
25 | * | |||
26 | * These two commands are enough to get working gauges. In order to start configuring the ECU using | |||
27 | * tuner studio, three more commands should be implemented: | |||
28 | * | |||
29 | * See also https://www.efianalytics.com/TunerStudio/docs/EFI%20Analytics%20ECU%20Definition%20files.pdf | |||
30 | * | |||
31 | * | |||
32 | * @date Oct 22, 2013 | |||
33 | * @author Andrey Belomutskiy, (c) 2012-2020 | |||
34 | * | |||
35 | * This file is part of rusEfi - see http://rusefi.com | |||
36 | * | |||
37 | * rusEfi is free software; you can redistribute it and/or modify it under the terms of | |||
38 | * the GNU General Public License as published by the Free Software Foundation; either | |||
39 | * version 3 of the License, or (at your option) any later version. | |||
40 | * | |||
41 | * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without | |||
42 | * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
43 | * GNU General Public License for more details. | |||
44 | * | |||
45 | * You should have received a copy of the GNU General Public License along with this program. | |||
46 | * If not, see <http://www.gnu.org/licenses/>. | |||
47 | * | |||
48 | * | |||
49 | * This file is part of rusEfi - see http://rusefi.com | |||
50 | * | |||
51 | * rusEfi is free software; you can redistribute it and/or modify it under the terms of | |||
52 | * the GNU General Public License as published by the Free Software Foundation; either | |||
53 | * version 3 of the License, or (at your option) any later version. | |||
54 | * | |||
55 | * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without | |||
56 | * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
57 | * GNU General Public License for more details. | |||
58 | * | |||
59 | * You should have received a copy of the GNU General Public License along with this program. | |||
60 | * If not, see <http://www.gnu.org/licenses/>. | |||
61 | * | |||
62 | */ | |||
63 | ||||
64 | #include "pch.h" | |||
65 | ||||
66 | ||||
67 | #include "tunerstudio.h" | |||
68 | #include "tunerstudio_impl.h" | |||
69 | ||||
70 | #include "main_trigger_callback.h" | |||
71 | #include "flash_main.h" | |||
72 | ||||
73 | #include "tunerstudio_io.h" | |||
74 | #include "malfunction_central.h" | |||
75 | #include "console_io.h" | |||
76 | #include "bluetooth.h" | |||
77 | #include "tunerstudio_io.h" | |||
78 | #include "trigger_scope.h" | |||
79 | #include "electronic_throttle.h" | |||
80 | #include "live_data.h" | |||
81 | #include "efi_quote.h" | |||
82 | ||||
83 | #include <string.h> | |||
84 | #include "bench_test.h" | |||
85 | #include "status_loop.h" | |||
86 | #include "mmc_card.h" | |||
87 | #include "tuner_detector_utils.h" | |||
88 | ||||
89 | #if EFI_SIMULATOR | |||
90 | #include "rusEfiFunctionalTest.h" | |||
91 | #endif /* EFI_SIMULATOR */ | |||
92 | ||||
93 | #include "board_overrides.h" | |||
94 | ||||
95 | #if EFI_TUNER_STUDIO | |||
96 | ||||
97 | // We have TS protocol limitation: offset within one settings page is uin16_t type. | |||
98 | static_assert(sizeof(*config) <= 65536); | |||
99 | ||||
100 | ✗ | static void printErrorCounters() { | ||
101 | ✗ | efiPrintf("TunerStudio size=%d / total=%d / errors=%d / H=%d / O=%d / P=%d / B=%d / 9=%d", | ||
102 | sizeof(engine->outputChannels), tsState.totalCounter, tsState.errorCounter, tsState.queryCommandCounter, | |||
103 | tsState.outputChannelsCommandCounter, tsState.readPageCommandsCounter, tsState.burnCommandCounter, | |||
104 | tsState.readScatterCommandsCounter); | |||
105 | ✗ | efiPrintf("TunerStudio C=%d", | ||
106 | tsState.writeChunkCommandCounter); | |||
107 | ✗ | efiPrintf("TunerStudio errors: underrun=%d / overrun=%d / crc=%d / unrecognized=%d / outofrange=%d / other=%d", | ||
108 | tsState.errorUnderrunCounter, tsState.errorOverrunCounter, tsState.errorCrcCounter, | |||
109 | tsState.errorUnrecognizedCommand, tsState.errorOutOfRange, tsState.errorOther); | |||
110 | ✗ | } | ||
111 | ||||
112 | namespace { | |||
113 | Timer calibrationsVeWriteTimer; | |||
114 | } | |||
115 | ||||
116 | #if 0 | |||
117 | static void printScatterList(TsChannelBase* tsChannel) { | |||
118 | efiPrintf("Scatter list (global)"); | |||
119 | for (size_t i = 0; i < TS_SCATTER_OFFSETS_COUNT; i++) { | |||
120 | uint16_t packed = tsChannel->highSpeedOffsets[i]; | |||
121 | uint16_t type = packed >> 13; | |||
122 | uint16_t offset = packed & 0x1FFF; | |||
123 | ||||
124 | if (type == 0) | |||
125 | continue; | |||
126 | size_t size = 1 << (type - 1); | |||
127 | ||||
128 | efiPrintf("%02d offset 0x%04x size %d", i, offset, size); | |||
129 | } | |||
130 | } | |||
131 | #endif | |||
132 | ||||
133 | /* 1S */ | |||
134 | #define TS_COMMUNICATION_TIMEOUT TIME_MS2I(1000) | |||
135 | /* 10mS when receiving byte by byte */ | |||
136 | #define TS_COMMUNICATION_TIMEOUT_SHORT TIME_MS2I(10) | |||
137 | ||||
138 | ✗ | static void resetTs() { | ||
139 | ✗ | memset(&tsState, 0, sizeof(tsState)); | ||
140 | ✗ | } | ||
141 | ||||
142 | ✗ | static void printTsStats(void) { | ||
143 | #ifdef EFI_CONSOLE_RX_BRAIN_PIN | |||
144 | efiPrintf("Primary UART RX %s", hwPortname(EFI_CONSOLE_RX_BRAIN_PIN)); | |||
145 | efiPrintf("Primary UART TX %s", hwPortname(EFI_CONSOLE_TX_BRAIN_PIN)); | |||
146 | #endif /* EFI_CONSOLE_RX_BRAIN_PIN */ | |||
147 | ||||
148 | #if EFI_USB_SERIAL | |||
149 | printUsbConnectorStats(); | |||
150 | #endif // EFI_USB_SERIAL | |||
151 | ||||
152 | ✗ | printErrorCounters(); | ||
153 | ||||
154 | // TODO: find way to get all tsChannel | |||
155 | //printScatterList(); | |||
156 | ✗ | } | ||
157 | ||||
158 | ✗ | static void setTsSpeed(int value) { | ||
159 | ✗ | engineConfiguration->tunerStudioSerialSpeed = value; | ||
160 | ✗ | printTsStats(); | ||
161 | ✗ | } | ||
162 | ||||
163 | ✗ | void tunerStudioDebug(TsChannelBase* tsChannel, const char *msg) { | ||
164 | #if EFI_TUNER_STUDIO_VERBOSE | |||
165 | efiPrintf("%s: %s", tsChannel->name, msg); | |||
166 | #endif /* EFI_TUNER_STUDIO_VERBOSE */ | |||
167 | ✗ | } | ||
168 | ||||
169 | 1 | static uint8_t* getWorkingPageAddr(TsChannelBase* tsChannel, size_t page, size_t offset) { | ||
170 | // TODO: validate offset? | |||
171 |
1/3✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
|
1 | switch (page) { | |
172 |
1/1✓ Decision 'true' taken 1 time.
|
1 | case TS_PAGE_SETTINGS: | |
173 | // TODO: why engineConfiguration, not config | |||
174 | // TS has access to whole persistent_config_s | |||
175 | 1 | return (uint8_t*)engineConfiguration + offset; | ||
176 | #if EFI_TS_SCATTER | |||
177 | case TS_PAGE_SCATTER_OFFSETS: | |||
178 | return (uint8_t *)tsChannel->page1.highSpeedOffsets + offset; | |||
179 | #endif | |||
180 | #if EFI_LTFT_CONTROL | |||
181 | ✗ | case TS_PAGE_LTFT_TRIMS: | ||
182 | ✗ | return (uint8_t *)ltftGetTsPage() + offset; | ||
183 | #endif | |||
184 | ✗ | default: | ||
185 | // technical dept: TS seems to try to read the 3 pages sequentially, does not look like we properly handle 'EFI_TS_SCATTER=FALSE' | |||
186 | ✗ | tunerStudioError(tsChannel, "ERROR: page address out of range"); | ||
187 | ✗ | return nullptr; | ||
188 | } | |||
189 | } | |||
190 | ||||
191 | 1 | static constexpr size_t getTunerStudioPageSize(size_t page) { | ||
192 |
1/3✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
|
1 | switch (page) { | |
193 |
1/1✓ Decision 'true' taken 1 time.
|
1 | case TS_PAGE_SETTINGS: | |
194 | 1 | return TOTAL_CONFIG_SIZE; | ||
195 | #if EFI_TS_SCATTER | |||
196 | case TS_PAGE_SCATTER_OFFSETS: | |||
197 | return PAGE_SIZE_1; | |||
198 | #endif | |||
199 | #if EFI_LTFT_CONTROL | |||
200 | ✗ | case TS_PAGE_LTFT_TRIMS: | ||
201 | ✗ | return ltftGetTsPageSize(); | ||
202 | #endif | |||
203 | ✗ | default: | ||
204 | ✗ | return 0; | ||
205 | } | |||
206 | } | |||
207 | ||||
208 | // Validate whether the specified offset and count would cause an overrun in the tune. | |||
209 | // Returns true if an overrun would occur. | |||
210 | 1 | static bool validateOffsetCount(size_t page, size_t offset, size_t count, TsChannelBase* tsChannel) { | ||
211 | 1 | size_t allowedSize = getTunerStudioPageSize(page); | ||
212 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 1 time.
|
1 | if (offset + count > allowedSize) { |
213 | ✗ | efiPrintf("TS: Project mismatch? Too much configuration requested %d+%d>%d", offset, count, allowedSize); | ||
214 | ✗ | tunerStudioError(tsChannel, "ERROR: out of range"); | ||
215 | ✗ | sendErrorCode(tsChannel, TS_RESPONSE_OUT_OF_RANGE, "bad_offset"); | ||
216 | ✗ | return true; | ||
217 | } | |||
218 | ||||
219 | 1 | return false; | ||
220 | } | |||
221 | ||||
222 | 1 | static void sendOkResponse(TsChannelBase *tsChannel) { | ||
223 | 1 | tsChannel->sendResponse(TS_CRC, nullptr, 0); | ||
224 | 1 | } | ||
225 | ||||
226 | ✗ | void sendErrorCode(TsChannelBase *tsChannel, uint8_t code, const char *msg) { | ||
227 | //TODO uncomment once I have test it myself | |||
228 | UNUSED(msg); | |||
229 | //if (msg != DO_NOT_LOG) { | |||
230 | // efiPrintf("TS <- Err: %d [%s]", code, msg); | |||
231 | //} | |||
232 | ||||
233 | ✗ | switch (code) { | ||
234 | ✗ | case TS_RESPONSE_UNDERRUN: | ||
235 | ✗ | tsState.errorUnderrunCounter++; | ||
236 | ✗ | break; | ||
237 | ✗ | case TS_RESPONSE_OVERRUN: | ||
238 | ✗ | tsState.errorOverrunCounter++; | ||
239 | ✗ | break; | ||
240 | ✗ | case TS_RESPONSE_CRC_FAILURE: | ||
241 | ✗ | tsState.errorCrcCounter++; | ||
242 | ✗ | break; | ||
243 | ✗ | case TS_RESPONSE_UNRECOGNIZED_COMMAND: | ||
244 | ✗ | tsState.errorUnrecognizedCommand++; | ||
245 | ✗ | break; | ||
246 | ✗ | case TS_RESPONSE_OUT_OF_RANGE: | ||
247 | ✗ | tsState.errorOutOfRange++; | ||
248 | ✗ | break; | ||
249 | ✗ | default: | ||
250 | ✗ | tsState.errorOther++; | ||
251 | ✗ | break; | ||
252 | } | |||
253 | ||||
254 | ✗ | tsChannel->writeCrcResponse(code); | ||
255 | ✗ | } | ||
256 | ||||
257 | ✗ | void TunerStudio::sendErrorCode(TsChannelBase* tsChannel, uint8_t code, const char *msg) { | ||
258 | ✗ | ::sendErrorCode(tsChannel, code, msg); | ||
259 | ✗ | } | ||
260 | ||||
261 | ✗ | PUBLIC_API_WEAK bool isBoardAskingTriggerTsRefresh() { | ||
262 | ✗ | return false; | ||
263 | } | |||
264 | ||||
265 | 522955 | bool needToTriggerTsRefresh() { | ||
266 | 522955 | return !engine->engineTypeChangeTimer.hasElapsedSec(1); | ||
267 | } | |||
268 | ||||
269 | ✗ | void onApplyPreset() { | ||
270 | ✗ | engine->engineTypeChangeTimer.reset(); | ||
271 | ✗ | } | ||
272 | ||||
273 | 1 | PUBLIC_API_WEAK bool isTouchingVe(uint16_t offset, uint16_t count) { | ||
274 | 1 | return isTouchingArea(offset, count, offsetof(persistent_config_s, veTable), sizeof(config->veTable)); | ||
275 | } | |||
276 | ||||
277 | 1 | static void onCalibrationWrite(uint16_t page, uint16_t offset, uint16_t count) { | ||
278 |
3/6✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 time.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 1 time.
|
1 | if ((page == TS_PAGE_SETTINGS) && isTouchingVe(offset, count)) { |
279 | ✗ | calibrationsVeWriteTimer.reset(); | ||
280 | } | |||
281 | 1 | } | ||
282 | ||||
283 | 3 | bool isTouchingArea(uint16_t offset, uint16_t count, int areaStart, int areaSize) { | ||
284 |
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 | if (offset + count < areaStart) { |
285 | // we are touching below for instance VE table | |||
286 | 2 | return false; | ||
287 | } | |||
288 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 1 time.
|
1 | if (offset > areaStart + areaSize) { |
289 | // we are touching after for instance VE table | |||
290 | ✗ | return false; | ||
291 | } | |||
292 | // else - we are touching it! | |||
293 | 1 | return true; | ||
294 | } | |||
295 | ||||
296 | /** | |||
297 | * This command is needed to make the whole transfer a bit faster | |||
298 | */ | |||
299 | 1 | void TunerStudio::handleWriteChunkCommand(TsChannelBase* tsChannel, uint16_t page, uint16_t offset, uint16_t count, | ||
300 | void *content) { | |||
301 | 1 | tsState.writeChunkCommandCounter++; | ||
302 | ||||
303 | 1 | efiPrintf("TS -> Page %d write chunk offset %d count %d (output_count=%d)", | ||
304 | page, offset, count, tsState.outputChannelsCommandCounter); | |||
305 | ||||
306 | ||||
307 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 1 time.
|
1 | if (validateOffsetCount(page, offset, count, tsChannel)) { |
308 | ✗ | tunerStudioError(tsChannel, "ERROR: WR out of range"); | ||
309 | ✗ | sendErrorCode(tsChannel, TS_RESPONSE_OUT_OF_RANGE); | ||
310 | ✗ | return; | ||
311 | } | |||
312 | ||||
313 | 1 | uint8_t * addr = getWorkingPageAddr(tsChannel, page, offset); | ||
314 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 1 time.
|
1 | if (addr == nullptr) { |
315 | ✗ | sendErrorCode(tsChannel, TS_RESPONSE_OUT_OF_RANGE, "ERROR: WR invalid page"); | ||
316 | ✗ | return; | ||
317 | } | |||
318 | ||||
319 | 1 | onCalibrationWrite(page, offset, count); | ||
320 | ||||
321 | // Special case | |||
322 |
1/2✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
|
1/2✓ Decision 'true' taken 1 time.
✗ Decision 'false' not taken.
|
1 | if (page == TS_PAGE_SETTINGS) { |
323 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 1 time.
|
1 | if (isLockedFromUser()) { |
324 | ✗ | sendErrorCode(tsChannel, TS_RESPONSE_UNRECOGNIZED_COMMAND, "locked"); | ||
325 | ✗ | return; | ||
326 | } | |||
327 | ||||
328 | // Skip the write if a preset was just loaded - we don't want to overwrite it | |||
329 | // [tag:popular_vehicle] | |||
330 |
1/2✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
|
1/2✓ Decision 'true' taken 1 time.
✗ Decision 'false' not taken.
|
1 | if (!needToTriggerTsRefresh()) { |
331 | 1 | memcpy(addr, content, count); | ||
332 | } else { | |||
333 | ✗ | efiPrintf("Ignoring TS -> Page %d write chunk offset %d count %d (output_count=%d)", | ||
334 | page, | |||
335 | offset, | |||
336 | count, | |||
337 | tsState.outputChannelsCommandCounter | |||
338 | ); | |||
339 | } | |||
340 | // Force any board configuration options that humans shouldn't be able to change | |||
341 | // huh, why is this NOT within above 'needToTriggerTsRefresh()' condition? | |||
342 | 1 | call_board_override(custom_board_ConfigOverrides); | ||
343 | } else { | |||
344 | ✗ | memcpy(addr, content, count); | ||
345 | } | |||
346 | ||||
347 | 1 | sendOkResponse(tsChannel); | ||
348 | } | |||
349 | ||||
350 | ✗ | void TunerStudio::handleCrc32Check(TsChannelBase *tsChannel, uint16_t page, uint16_t offset, uint16_t count) { | ||
351 | ✗ | tsState.crc32CheckCommandCounter++; | ||
352 | ||||
353 | // Ensure we are reading from in bounds | |||
354 | ✗ | if (validateOffsetCount(page, offset, count, tsChannel)) { | ||
355 | ✗ | tunerStudioError(tsChannel, "ERROR: CRC out of range"); | ||
356 | ✗ | sendErrorCode(tsChannel, TS_RESPONSE_OUT_OF_RANGE); | ||
357 | ✗ | return; | ||
358 | } | |||
359 | ||||
360 | ✗ | const uint8_t* start = getWorkingPageAddr(tsChannel, page, offset); | ||
361 | ✗ | if (start == nullptr) { | ||
362 | ✗ | sendErrorCode(tsChannel, TS_RESPONSE_OUT_OF_RANGE, "ERROR: CRC invalid page"); | ||
363 | ✗ | return; | ||
364 | } | |||
365 | ||||
366 | ✗ | uint32_t crc = SWAP_UINT32(crc32(start, count)); | ||
367 | ✗ | tsChannel->sendResponse(TS_CRC, (const uint8_t *) &crc, 4); | ||
368 | ✗ | efiPrintf("TS <- Get CRC page %d offset %d count %d result %08x", page, offset, count, (unsigned int)crc); | ||
369 | } | |||
370 | ||||
371 | #if EFI_TS_SCATTER | |||
372 | void TunerStudio::handleScatteredReadCommand(TsChannelBase* tsChannel) { | |||
373 | tsState.readScatterCommandsCounter++; | |||
374 | ||||
375 | int totalResponseSize = 0; | |||
376 | for (size_t i = 0; i < TS_SCATTER_OFFSETS_COUNT; i++) { | |||
377 | uint16_t packed = tsChannel->page1.highSpeedOffsets[i]; | |||
378 | uint16_t type = packed >> 13; | |||
379 | ||||
380 | size_t size = type == 0 ? 0 : 1 << (type - 1); | |||
381 | #if EFI_SIMULATOR | |||
382 | // printf("handleScatteredReadCommand 0x%x %d %d\n", packed, size, offset); | |||
383 | #endif /* EFI_SIMULATOR */ | |||
384 | totalResponseSize += size; | |||
385 | } | |||
386 | #if EFI_SIMULATOR | |||
387 | // printf("totalResponseSize %d\n", totalResponseSize); | |||
388 | #endif /* EFI_SIMULATOR */ | |||
389 | ||||
390 | // Command part of CRC | |||
391 | uint32_t crc = tsChannel->writePacketHeader(TS_RESPONSE_OK, totalResponseSize); | |||
392 | ||||
393 | uint8_t dataBuffer[8]; | |||
394 | for (size_t i = 0; i < TS_SCATTER_OFFSETS_COUNT; i++) { | |||
395 | uint16_t packed = tsChannel->page1.highSpeedOffsets[i]; | |||
396 | uint16_t type = packed >> 13; | |||
397 | uint16_t offset = packed & 0x1FFF; | |||
398 | ||||
399 | if (type == 0) | |||
400 | continue; | |||
401 | size_t size = 1 << (type - 1); | |||
402 | ||||
403 | // write each data point and CRC incrementally | |||
404 | copyRange(dataBuffer, getLiveDataFragments(), offset, size); | |||
405 | tsChannel->write(dataBuffer, size, false); | |||
406 | crc = crc32inc((void*)dataBuffer, crc, size); | |||
407 | } | |||
408 | #if EFI_SIMULATOR | |||
409 | // printf("CRC %x\n", crc); | |||
410 | #endif /* EFI_SIMULATOR */ | |||
411 | // now write total CRC | |||
412 | *(uint32_t*)dataBuffer = SWAP_UINT32(crc); | |||
413 | tsChannel->write(dataBuffer, 4, true); | |||
414 | tsChannel->flush(); | |||
415 | } | |||
416 | #endif // EFI_TS_SCATTER | |||
417 | ||||
418 | ✗ | void TunerStudio::handlePageReadCommand(TsChannelBase* tsChannel, uint16_t page, uint16_t offset, uint16_t count) { | ||
419 | ✗ | tsState.readPageCommandsCounter++; | ||
420 | ✗ | efiPrintf("TS <- Page %d read chunk offset %d count %d", page, offset, count); | ||
421 | ||||
422 | ✗ | if (validateOffsetCount(page, offset, count, tsChannel)) { | ||
423 | ✗ | tunerStudioError(tsChannel, "ERROR: RD out of range"); | ||
424 | ✗ | sendErrorCode(tsChannel, TS_RESPONSE_OUT_OF_RANGE); | ||
425 | ✗ | return; | ||
426 | } | |||
427 | ||||
428 | ✗ | uint8_t* addr = getWorkingPageAddr(tsChannel, page, offset); | ||
429 | ✗ | if (page == TS_PAGE_SETTINGS) { | ||
430 | ✗ | if (isLockedFromUser()) { | ||
431 | // to have rusEFI console happy just send all zeros within a valid packet | |||
432 | ✗ | addr = (uint8_t*)&tsChannel->scratchBuffer + TS_PACKET_HEADER_SIZE; | ||
433 | ✗ | memset(addr, 0, count); | ||
434 | } | |||
435 | } | |||
436 | ||||
437 | ✗ | if (addr == nullptr) { | ||
438 | ✗ | sendErrorCode(tsChannel, TS_RESPONSE_OUT_OF_RANGE, "ERROR: RD invalid page"); | ||
439 | ✗ | return; | ||
440 | } | |||
441 | ||||
442 | ✗ | tsChannel->sendResponse(TS_CRC, addr, count); | ||
443 | #if EFI_TUNER_STUDIO_VERBOSE | |||
444 | // efiPrintf("Sending %d done", count); | |||
445 | #endif | |||
446 | } | |||
447 | #endif // EFI_TUNER_STUDIO | |||
448 | ||||
449 | ✗ | void requestBurn() { | ||
450 | #if !EFI_UNIT_TEST | |||
451 | onBurnRequest(); | |||
452 | ||||
453 | #if EFI_CONFIGURATION_STORAGE | |||
454 | setNeedToWriteConfiguration(); | |||
455 | #endif /* EFI_CONFIGURATION_STORAGE */ | |||
456 | #endif // !EFI_UNIT_TEST | |||
457 | ✗ | } | ||
458 | ||||
459 | #if EFI_TUNER_STUDIO | |||
460 | /** | |||
461 | * 'Burn' command is a command to commit the changes | |||
462 | */ | |||
463 | ✗ | static void handleBurnCommand(TsChannelBase* tsChannel, uint16_t page) { | ||
464 | ✗ | if (page == TS_PAGE_SETTINGS) { | ||
465 | ✗ | Timer t; | ||
466 | ✗ | t.reset(); | ||
467 | ||||
468 | ✗ | tsState.burnCommandCounter++; | ||
469 | ||||
470 | ✗ | efiPrintf("TS -> Burn"); | ||
471 | ✗ | validateConfigOnStartUpOrBurn(); | ||
472 | ||||
473 | // problem: 'popular vehicles' dialog has 'Burn' which is very NOT helpful on that dialog | |||
474 | // since users often click both buttons producing a conflict between ECU desire to change settings | |||
475 | // and TS desire to send TS calibration snapshot into ECU | |||
476 | // Skip the burn if a preset was just loaded - we don't want to overwrite it | |||
477 | // [tag:popular_vehicle] | |||
478 | ✗ | if (!needToTriggerTsRefresh()) { | ||
479 | ✗ | requestBurn(); | ||
480 | } | |||
481 | ✗ | efiPrintf("Burned in %.1fms", t.getElapsedSeconds() * 1e3); | ||
482 | #if EFI_TS_SCATTER | |||
483 | } else if (page == TS_PAGE_SCATTER_OFFSETS) { | |||
484 | /* do nothing */ | |||
485 | #endif | |||
486 | } else { | |||
487 | ✗ | sendErrorCode(tsChannel, TS_RESPONSE_OUT_OF_RANGE, "ERROR: Burn invalid page"); | ||
488 | ✗ | return; | ||
489 | } | |||
490 | ||||
491 | ✗ | tsChannel->writeCrcResponse(TS_RESPONSE_BURN_OK); | ||
492 | } | |||
493 | ||||
494 | #if (EFI_PROD_CODE || EFI_SIMULATOR) | |||
495 | ||||
496 | static bool isKnownCommand(char command) { | |||
497 | return command == TS_HELLO_COMMAND || command == TS_READ_COMMAND || command == TS_OUTPUT_COMMAND | |||
498 | || command == TS_BURN_COMMAND | |||
499 | || command == TS_CHUNK_WRITE_COMMAND || command == TS_EXECUTE | |||
500 | || command == TS_IO_TEST_COMMAND | |||
501 | #if EFI_SIMULATOR | |||
502 | || command == TS_SIMULATE_CAN | |||
503 | #endif // EFI_SIMULATOR | |||
504 | #if EFI_TS_SCATTER | |||
505 | || command == TS_GET_SCATTERED_GET_COMMAND | |||
506 | #endif | |||
507 | || command == TS_SET_LOGGER_SWITCH | |||
508 | || command == TS_GET_COMPOSITE_BUFFER_DONE_DIFFERENTLY | |||
509 | || command == TS_GET_TEXT | |||
510 | || command == TS_CRC_CHECK_COMMAND | |||
511 | || command == TS_GET_FIRMWARE_VERSION | |||
512 | || command == TS_PERF_TRACE_BEGIN | |||
513 | || command == TS_PERF_TRACE_GET_BUFFER | |||
514 | || command == TS_GET_CONFIG_ERROR | |||
515 | || command == TS_QUERY_BOOTLOADER; | |||
516 | } | |||
517 | ||||
518 | /** | |||
519 | * rusEfi own test command | |||
520 | */ | |||
521 | static void handleTestCommand(TsChannelBase* tsChannel) { | |||
522 | tsState.testCommandCounter++; | |||
523 | char testOutputBuffer[64]; | |||
524 | /** | |||
525 | * this is NOT a standard TunerStudio command, this is my own | |||
526 | * extension of the protocol to simplify troubleshooting | |||
527 | */ | |||
528 | tunerStudioDebug(tsChannel, "got T (Test)"); | |||
529 | tsChannel->write((const uint8_t*)QUOTE(SIGNATURE_HASH), sizeof(QUOTE(SIGNATURE_HASH))); | |||
530 | ||||
531 | chsnprintf(testOutputBuffer, sizeof(testOutputBuffer), " %d %d", engine->engineState.warnings.lastErrorCode, tsState.testCommandCounter); | |||
532 | tsChannel->write((const uint8_t*)testOutputBuffer, strlen(testOutputBuffer)); | |||
533 | ||||
534 | chsnprintf(testOutputBuffer, sizeof(testOutputBuffer), " uptime=%ds ", (int)getTimeNowS()); | |||
535 | tsChannel->write((const uint8_t*)testOutputBuffer, strlen(testOutputBuffer)); | |||
536 | ||||
537 | chsnprintf(testOutputBuffer, sizeof(testOutputBuffer), __DATE__ " %s\r\n", PROTOCOL_TEST_RESPONSE_TAG); | |||
538 | tsChannel->write((const uint8_t*)testOutputBuffer, strlen(testOutputBuffer)); | |||
539 | ||||
540 | if (hasFirmwareError()) { | |||
541 | const char* error = getCriticalErrorMessage(); | |||
542 | chsnprintf(testOutputBuffer, sizeof(testOutputBuffer), "error=%s\r\n", error); | |||
543 | tsChannel->write((const uint8_t*)testOutputBuffer, strlen(testOutputBuffer)); | |||
544 | } | |||
545 | tsChannel->flush(); | |||
546 | } | |||
547 | ||||
548 | static void handleGetConfigErorr(TsChannelBase* tsChannel) { | |||
549 | const char* errorMessage = hasFirmwareError() ? getCriticalErrorMessage() : getConfigErrorMessage(); | |||
550 | if (strlen(errorMessage) == 0) { | |||
551 | // Check for engine's warning code | |||
552 | errorMessage = engine->engineState.warnings.getWarningMessage(); | |||
553 | } | |||
554 | tsChannel->sendResponse(TS_CRC, reinterpret_cast<const uint8_t*>(errorMessage), strlen(errorMessage), true); | |||
555 | } | |||
556 | ||||
557 | /** | |||
558 | * this command is part of protocol initialization | |||
559 | * | |||
560 | * Query with CRC takes place while re-establishing connection | |||
561 | * Query without CRC takes place on TunerStudio startup | |||
562 | */ | |||
563 | void TunerStudio::handleQueryCommand(TsChannelBase* tsChannel, ts_response_format_e mode) { | |||
564 | tsState.queryCommandCounter++; | |||
565 | const char *signature = getTsSignature(); | |||
566 | ||||
567 | efiPrintf("TS <- Query signature: %s", signature); | |||
568 | tsChannel->sendResponse(mode, (const uint8_t *)signature, strlen(signature) + 1); | |||
569 | } | |||
570 | ||||
571 | /** | |||
572 | * handle non CRC wrapped command | |||
573 | * | |||
574 | * @return true if legacy command was processed, false otherwise | |||
575 | */ | |||
576 | bool TunerStudio::handlePlainCommand(TsChannelBase* tsChannel, uint8_t command) { | |||
577 | // Bail fast if guaranteed not to be a plain command | |||
578 | if (command == 0) { | |||
579 | return false; | |||
580 | } else if (command == TS_HELLO_COMMAND || command == TS_QUERY_COMMAND) { | |||
581 | // We interpret 'Q' as TS_HELLO_COMMAND, since TS uses hardcoded 'Q' during ECU detection (scan all serial ports) | |||
582 | efiPrintf("Got naked Query command"); | |||
583 | handleQueryCommand(tsChannel, TS_PLAIN); | |||
584 | return true; | |||
585 | } else if (command == TS_TEST_COMMAND || command == 'T') { | |||
586 | handleTestCommand(tsChannel); | |||
587 | return true; | |||
588 | } else if (command == TS_COMMAND_F) { | |||
589 | /** | |||
590 | * http://www.msextra.com/forums/viewtopic.php?f=122&t=48327 | |||
591 | * Response from TS support: This is an optional command * | |||
592 | * "The F command is used to find what ini. file needs to be loaded in TunerStudio to match the controller. | |||
593 | * If you are able to just make your firmware ignore the command that would work. | |||
594 | * Currently on some firmware versions the F command is not used and is just ignored by the firmware as a unknown command." | |||
595 | */ | |||
596 | ||||
597 | tunerStudioDebug(tsChannel, "not ignoring F"); | |||
598 | tsChannel->write((const uint8_t *)TS_PROTOCOL, strlen(TS_PROTOCOL)); | |||
599 | tsChannel->flush(); | |||
600 | return true; | |||
601 | } else { | |||
602 | // This wasn't a valid command | |||
603 | return false; | |||
604 | } | |||
605 | } | |||
606 | ||||
607 | TunerStudio tsInstance; | |||
608 | ||||
609 | static int tsProcessOne(TsChannelBase* tsChannel) { | |||
610 | assertStack("communication", ObdCode::STACK_USAGE_COMMUNICATION, EXPECTED_REMAINING_STACK, -1); | |||
611 | ||||
612 | if (!tsChannel->isReady()) { | |||
613 | chThdSleepMilliseconds(10); | |||
614 | return -1; | |||
615 | } | |||
616 | ||||
617 | tsState.totalCounter++; | |||
618 | ||||
619 | uint8_t firstByte; | |||
620 | size_t received = tsChannel->readTimeout(&firstByte, 1, TS_COMMUNICATION_TIMEOUT); | |||
621 | #if EFI_SIMULATOR | |||
622 | logMsg("received %d\r\n", received); | |||
623 | #endif // EFI_SIMULATOR | |||
624 | ||||
625 | if (received != 1) { | |||
626 | //tunerStudioError("ERROR: no command"); | |||
627 | #if EFI_BLUETOOTH_SETUP | |||
628 | if (tsChannel == getBluetoothChannel()) { | |||
629 | // no data in a whole second means time to disconnect BT | |||
630 | // assume there's connection loss and notify the bluetooth init code | |||
631 | bluetoothSoftwareDisconnectNotify(getBluetoothChannel()); | |||
632 | } | |||
633 | #endif /* EFI_BLUETOOTH_SETUP */ | |||
634 | tsChannel->in_sync = false; | |||
635 | return -1; | |||
636 | } | |||
637 | ||||
638 | if (tsInstance.handlePlainCommand(tsChannel, firstByte)) { | |||
639 | return 0; | |||
640 | } | |||
641 | ||||
642 | uint8_t secondByte; | |||
643 | /* second byte should be received within minimal delay */ | |||
644 | received = tsChannel->readTimeout(&secondByte, 1, TS_COMMUNICATION_TIMEOUT_SHORT); | |||
645 | if (received != 1) { | |||
646 | tunerStudioError(tsChannel, "TS: ERROR: no second byte"); | |||
647 | tsChannel->in_sync = false; | |||
648 | return -1; | |||
649 | } | |||
650 | ||||
651 | uint16_t incomingPacketSize = firstByte << 8 | secondByte; | |||
652 | size_t expectedSize = incomingPacketSize + TS_PACKET_TAIL_SIZE; | |||
653 | ||||
654 | if ((incomingPacketSize == 0) || (expectedSize > sizeof(tsChannel->scratchBuffer))) { | |||
655 | if (tsChannel->in_sync) { | |||
656 | efiPrintf("process_ts: channel=%s invalid size: %d", tsChannel->name, incomingPacketSize); | |||
657 | tunerStudioError(tsChannel, "process_ts: ERROR: packet size"); | |||
658 | /* send error only if previously we were in sync */ | |||
659 | sendErrorCode(tsChannel, TS_RESPONSE_OUT_OF_RANGE, "invalid size"); | |||
660 | } | |||
661 | tsChannel->in_sync = false; | |||
662 | return -1; | |||
663 | } | |||
664 | ||||
665 | char command; | |||
666 | if (tsChannel->in_sync) { | |||
667 | /* we are in sync state, packet size should be correct so lets receive full packet and then check if command is supported | |||
668 | * otherwise (if abort reception in middle of packet) it will break synchronization and cause error on next packet */ | |||
669 | received = tsChannel->readTimeout((uint8_t*)(tsChannel->scratchBuffer), expectedSize, TS_COMMUNICATION_TIMEOUT); | |||
670 | command = tsChannel->scratchBuffer[0]; | |||
671 | ||||
672 | if (received != expectedSize) { | |||
673 | /* print and send error as we were in sync */ | |||
674 | efiPrintf("Got only %d bytes while expecting %d for command 0x%02x", received, | |||
675 | expectedSize, command); | |||
676 | tunerStudioError(tsChannel, "ERROR: not enough bytes in stream"); | |||
677 | // MS serial protocol spec: There was a timeout before all data was received. (25ms per character.) | |||
678 | sendErrorCode(tsChannel, TS_RESPONSE_UNDERRUN, "underrun"); | |||
679 | tsChannel->in_sync = false; | |||
680 | return -1; | |||
681 | } | |||
682 | ||||
683 | if (!isKnownCommand(command)) { | |||
684 | /* print and send error as we were in sync */ | |||
685 | efiPrintf("unexpected command %x", command); | |||
686 | sendErrorCode(tsChannel, TS_RESPONSE_UNRECOGNIZED_COMMAND, "unknown"); | |||
687 | tsChannel->in_sync = false; | |||
688 | return -1; | |||
689 | } | |||
690 | } else { | |||
691 | /* receive only command byte to check if it is supported */ | |||
692 | received = tsChannel->readTimeout((uint8_t*)(tsChannel->scratchBuffer), 1, TS_COMMUNICATION_TIMEOUT_SHORT); | |||
693 | command = tsChannel->scratchBuffer[0]; | |||
694 | ||||
695 | if (!isKnownCommand(command)) { | |||
696 | /* do not report any error as we are not in sync */ | |||
697 | return -1; | |||
698 | } | |||
699 | ||||
700 | received = tsChannel->readTimeout((uint8_t*)(tsChannel->scratchBuffer) + 1, expectedSize - 1, TS_COMMUNICATION_TIMEOUT); | |||
701 | if (received != expectedSize - 1) { | |||
702 | /* do not report any error as we are not in sync */ | |||
703 | return -1; | |||
704 | } | |||
705 | } | |||
706 | ||||
707 | #if EFI_SIMULATOR | |||
708 | logMsg("command %c\r\n", command); | |||
709 | #endif | |||
710 | ||||
711 | uint32_t expectedCrc = *(uint32_t*) (tsChannel->scratchBuffer + incomingPacketSize); | |||
712 | ||||
713 | expectedCrc = SWAP_UINT32(expectedCrc); | |||
714 | ||||
715 | uint32_t actualCrc = crc32(tsChannel->scratchBuffer, incomingPacketSize); | |||
716 | if (actualCrc != expectedCrc) { | |||
717 | /* send error only if previously we were in sync */ | |||
718 | if (tsChannel->in_sync) { | |||
719 | efiPrintf("TunerStudio: command %c actual CRC %x/expected %x", tsChannel->scratchBuffer[0], | |||
720 | (unsigned int)actualCrc, (unsigned int)expectedCrc); | |||
721 | tunerStudioError(tsChannel, "ERROR: CRC issue"); | |||
722 | sendErrorCode(tsChannel, TS_RESPONSE_CRC_FAILURE, "crc_issue"); | |||
723 | tsChannel->in_sync = false; | |||
724 | } | |||
725 | return -1; | |||
726 | } | |||
727 | ||||
728 | /* we were able to receive known command with correct crc and size! */ | |||
729 | tsChannel->in_sync = true; | |||
730 | ||||
731 | int success = tsInstance.handleCrcCommand(tsChannel, tsChannel->scratchBuffer, incomingPacketSize); | |||
732 | ||||
733 | if (!success) { | |||
734 | efiPrintf("got unexpected TunerStudio command %x:%c", command, command); | |||
735 | return -1; | |||
736 | } | |||
737 | ||||
738 | return 0; | |||
739 | } | |||
740 | ||||
741 | void TunerstudioThread::ThreadTask() { | |||
742 | auto channel = setupChannel(); | |||
743 | ||||
744 | // No channel configured for this thread, cancel. | |||
745 | if (!channel || !channel->isConfigured()) { | |||
746 | return; | |||
747 | } | |||
748 | ||||
749 | // Until the end of time, process incoming messages. | |||
750 | while (true) { | |||
751 | if (tsProcessOne(channel) == 0) { | |||
752 | onDataArrived(true); | |||
753 | } else { | |||
754 | onDataArrived(false); | |||
755 | } | |||
756 | } | |||
757 | } | |||
758 | ||||
759 | #endif // EFI_PROD_CODE || EFI_SIMULATOR | |||
760 | tunerstudio_counters_s tsState; | |||
761 | ||||
762 | ✗ | void tunerStudioError(TsChannelBase* tsChannel, const char *msg) { | ||
763 | ✗ | tunerStudioDebug(tsChannel, msg); | ||
764 | ✗ | printErrorCounters(); | ||
765 | ✗ | tsState.errorCounter++; | ||
766 | ✗ | } | ||
767 | ||||
768 | #if EFI_PROD_CODE || EFI_SIMULATOR | |||
769 | ||||
770 | extern CommandHandler console_line_callback; | |||
771 | ||||
772 | // see also handleQueryCommand | |||
773 | // see also printVersionForConsole | |||
774 | static void handleGetVersion(TsChannelBase* tsChannel) { | |||
775 | char versionBuffer[32]; | |||
776 | chsnprintf(versionBuffer, sizeof(versionBuffer), "%s v%d@%u", FRONTEND_TITLE_BAR_NAME, getRusEfiVersion(), SIGNATURE_HASH); | |||
777 | tsChannel->sendResponse(TS_CRC, (const uint8_t *) versionBuffer, strlen(versionBuffer) + 1); | |||
778 | } | |||
779 | ||||
780 | #if EFI_TEXT_LOGGING | |||
781 | static void handleGetText(TsChannelBase* tsChannel) { | |||
782 | tsState.textCommandCounter++; | |||
783 | ||||
784 | printOverallStatus(); | |||
785 | ||||
786 | size_t outputSize; | |||
787 | const char* output = swapOutputBuffers(&outputSize); | |||
788 | #if EFI_SIMULATOR | |||
789 | logMsg("get test sending [%d]\r\n", outputSize); | |||
790 | #endif | |||
791 | ||||
792 | tsChannel->writeCrcPacket(TS_RESPONSE_OK, reinterpret_cast<const uint8_t*>(output), outputSize, true); | |||
793 | #if EFI_SIMULATOR | |||
794 | logMsg("sent [%d]\r\n", outputSize); | |||
795 | #endif // EFI_SIMULATOR | |||
796 | } | |||
797 | #endif // EFI_TEXT_LOGGING | |||
798 | ||||
799 | void TunerStudio::handleExecuteCommand(TsChannelBase* tsChannel, char *data, int incomingPacketSize) { | |||
800 | data[incomingPacketSize] = 0; | |||
801 | char *trimmed = efiTrim(data); | |||
802 | #if EFI_SIMULATOR | |||
803 | logMsg("execute [%s]\r\n", trimmed); | |||
804 | #endif // EFI_SIMULATOR | |||
805 | (console_line_callback)(trimmed); | |||
806 | ||||
807 | tsChannel->writeCrcResponse(TS_RESPONSE_OK); | |||
808 | } | |||
809 | ||||
810 | int TunerStudio::handleCrcCommand(TsChannelBase* tsChannel, char *data, int incomingPacketSize) { | |||
811 | ScopePerf perf(PE::TunerStudioHandleCrcCommand); | |||
812 | ||||
813 | char command = data[0]; | |||
814 | data++; | |||
815 | ||||
816 | const uint16_t* data16 = reinterpret_cast<uint16_t*>(data); | |||
817 | ||||
818 | // only few command have page argument, default page is 0 | |||
819 | uint16_t page = 0; | |||
820 | uint16_t offset = 0; | |||
821 | uint16_t count = 0; | |||
822 | ||||
823 | // command may not have offset field - keep safe default value | |||
824 | // not used by .ini at the moment TODO actually use that version of the command in the .ini | |||
825 | if (incomingPacketSize >= 3) { | |||
826 | offset = data16[0]; | |||
827 | } | |||
828 | // command may not have count/size filed - keep safe default value | |||
829 | if (incomingPacketSize >= 5) { | |||
830 | count = data16[1]; | |||
831 | } | |||
832 | ||||
833 | switch(command) | |||
834 | { | |||
835 | case TS_OUTPUT_COMMAND: | |||
836 | if (incomingPacketSize == 1) { | |||
837 | // Read command with no offset and size - read whole livedata | |||
838 | count = TS_TOTAL_OUTPUT_SIZE; | |||
839 | } | |||
840 | cmdOutputChannels(tsChannel, offset, count); | |||
841 | break; | |||
842 | case TS_OUTPUT_ALL_COMMAND: | |||
843 | offset = 0; | |||
844 | count = TS_TOTAL_OUTPUT_SIZE; | |||
845 | // TS will not use this command until ochBlockSize is bigger than blockingFactor and prefer ochGetCommand :( | |||
846 | cmdOutputChannels(tsChannel, offset, count); | |||
847 | break; | |||
848 | case TS_GET_SCATTERED_GET_COMMAND: | |||
849 | #if EFI_TS_SCATTER | |||
850 | handleScatteredReadCommand(tsChannel); | |||
851 | #else | |||
852 | criticalError("Slow/wireless mode not supported"); | |||
853 | #endif // EFI_TS_SCATTER | |||
854 | break; | |||
855 | case TS_HELLO_COMMAND: | |||
856 | tunerStudioDebug(tsChannel, "got Query command"); | |||
857 | handleQueryCommand(tsChannel, TS_CRC); | |||
858 | break; | |||
859 | case TS_GET_FIRMWARE_VERSION: | |||
860 | handleGetVersion(tsChannel); | |||
861 | break; | |||
862 | #if EFI_TEXT_LOGGING | |||
863 | case TS_GET_TEXT: | |||
864 | handleGetText(tsChannel); | |||
865 | break; | |||
866 | #endif // EFI_TEXT_LOGGING | |||
867 | case TS_EXECUTE: | |||
868 | handleExecuteCommand(tsChannel, data, incomingPacketSize - 1); | |||
869 | break; | |||
870 | case TS_CHUNK_WRITE_COMMAND: | |||
871 | /* command with page argument */ | |||
872 | page = data16[0]; | |||
873 | offset = data16[1]; | |||
874 | count = data16[2]; | |||
875 | handleWriteChunkCommand(tsChannel, page, offset, count, data + sizeof(TunerStudioPageRWChunkRequest)); | |||
876 | break; | |||
877 | case TS_CRC_CHECK_COMMAND: | |||
878 | /* command with page argument */ | |||
879 | page = data16[0]; | |||
880 | offset = data16[1]; | |||
881 | count = data16[2]; | |||
882 | handleCrc32Check(tsChannel, page, offset, count); | |||
883 | break; | |||
884 | case TS_BURN_COMMAND: | |||
885 | /* command with page argument */ | |||
886 | page = data16[0]; | |||
887 | handleBurnCommand(tsChannel, page); | |||
888 | break; | |||
889 | case TS_READ_COMMAND: | |||
890 | /* command with page argument */ | |||
891 | page = data16[0]; | |||
892 | offset = data16[1]; | |||
893 | count = data16[2]; | |||
894 | handlePageReadCommand(tsChannel, page, offset, count); | |||
895 | break; | |||
896 | case TS_TEST_COMMAND: | |||
897 | [[fallthrough]]; | |||
898 | case 'T': | |||
899 | handleTestCommand(tsChannel); | |||
900 | break; | |||
901 | case TS_GET_CONFIG_ERROR: | |||
902 | handleGetConfigErorr(tsChannel); | |||
903 | break; | |||
904 | #if EFI_SIMULATOR | |||
905 | case TS_SIMULATE_CAN: | |||
906 | void handleWrapCan(TsChannelBase* tsChannel, char *data, int incomingPacketSize); | |||
907 | handleWrapCan(tsChannel, data, incomingPacketSize - 1); | |||
908 | break; | |||
909 | #endif // EFI_SIMULATOR | |||
910 | case TS_IO_TEST_COMMAND: | |||
911 | #if EFI_SIMULATOR || EFI_PROD_CODE | |||
912 | //TODO: Why did we process `TS_IO_TEST_COMMAND` only in prod code? I've just turned it on for simulator as well, because | |||
913 | // I need test this functionality with simulator as well. We need to review the cases when we really need to turn off | |||
914 | // `TS_IO_TEST_COMMAND` processing. Do we really need guards below? | |||
915 | { | |||
916 | uint16_t subsystem = SWAP_UINT16(data16[0]); | |||
917 | uint16_t index = SWAP_UINT16(data16[1]); | |||
918 | ||||
919 | executeTSCommand(subsystem, index); | |||
920 | } | |||
921 | #endif /* EFI_SIMULATOR || EFI_PROD_CODE */ | |||
922 | sendOkResponse(tsChannel); | |||
923 | break; | |||
924 | #if EFI_TOOTH_LOGGER | |||
925 | case TS_SET_LOGGER_SWITCH: | |||
926 | switch(data[0]) { | |||
927 | case TS_COMPOSITE_ENABLE: | |||
928 | EnableToothLogger(); | |||
929 | break; | |||
930 | case TS_COMPOSITE_DISABLE: | |||
931 | DisableToothLogger(); | |||
932 | break; | |||
933 | case TS_COMPOSITE_READ: | |||
934 | { | |||
935 | auto toothBuffer = GetToothLoggerBufferNonblocking(); | |||
936 | ||||
937 | if (toothBuffer) { | |||
938 | tsChannel->sendResponse(TS_CRC, reinterpret_cast<const uint8_t*>(toothBuffer->buffer), toothBuffer->nextIdx * sizeof(composite_logger_s), true); | |||
939 | ||||
940 | ReturnToothLoggerBuffer(toothBuffer); | |||
941 | } else { | |||
942 | // TS asked for a tooth logger buffer, but we don't have one to give it. | |||
943 | sendErrorCode(tsChannel, TS_RESPONSE_OUT_OF_RANGE, DO_NOT_LOG); | |||
944 | } | |||
945 | } | |||
946 | break; | |||
947 | #ifdef TRIGGER_SCOPE | |||
948 | case TS_TRIGGER_SCOPE_ENABLE: | |||
949 | triggerScopeEnable(); | |||
950 | break; | |||
951 | case TS_TRIGGER_SCOPE_DISABLE: | |||
952 | triggerScopeDisable(); | |||
953 | break; | |||
954 | case TS_TRIGGER_SCOPE_READ: | |||
955 | { | |||
956 | const auto& buffer = triggerScopeGetBuffer(); | |||
957 | ||||
958 | if (buffer) { | |||
959 | tsChannel->sendResponse(TS_CRC, buffer.get<uint8_t>(), buffer.size(), true); | |||
960 | } else { | |||
961 | // TS asked for a tooth logger buffer, but we don't have one to give it. | |||
962 | sendErrorCode(tsChannel, TS_RESPONSE_OUT_OF_RANGE, DO_NOT_LOG); | |||
963 | } | |||
964 | } | |||
965 | break; | |||
966 | #endif // TRIGGER_SCOPE | |||
967 | default: | |||
968 | // dunno what that was, send NAK | |||
969 | return false; | |||
970 | } | |||
971 | ||||
972 | sendOkResponse(tsChannel); | |||
973 | ||||
974 | break; | |||
975 | case TS_GET_COMPOSITE_BUFFER_DONE_DIFFERENTLY: | |||
976 | { | |||
977 | EnableToothLoggerIfNotEnabled(); | |||
978 | ||||
979 | auto toothBuffer = GetToothLoggerBufferNonblocking(); | |||
980 | ||||
981 | if (toothBuffer) { | |||
982 | tsChannel->sendResponse(TS_CRC, reinterpret_cast<const uint8_t*>(toothBuffer->buffer), toothBuffer->nextIdx * sizeof(composite_logger_s), true); | |||
983 | ||||
984 | ReturnToothLoggerBuffer(toothBuffer); | |||
985 | } else { | |||
986 | // TS asked for a tooth logger buffer, but we don't have one to give it. | |||
987 | sendErrorCode(tsChannel, TS_RESPONSE_OUT_OF_RANGE, DO_NOT_LOG); | |||
988 | } | |||
989 | } | |||
990 | ||||
991 | break; | |||
992 | #else // EFI_TOOTH_LOGGER | |||
993 | case TS_GET_COMPOSITE_BUFFER_DONE_DIFFERENTLY: | |||
994 | sendErrorCode(tsChannel, TS_RESPONSE_OUT_OF_RANGE, DO_NOT_LOG); | |||
995 | break; | |||
996 | #endif /* EFI_TOOTH_LOGGER */ | |||
997 | #if ENABLE_PERF_TRACE | |||
998 | case TS_PERF_TRACE_BEGIN: | |||
999 | perfTraceEnable(); | |||
1000 | sendOkResponse(tsChannel); | |||
1001 | break; | |||
1002 | case TS_PERF_TRACE_GET_BUFFER: | |||
1003 | { | |||
1004 | auto trace = perfTraceGetBuffer(); | |||
1005 | tsChannel->sendResponse(TS_CRC, trace.get<uint8_t>(), trace.size(), true); | |||
1006 | } | |||
1007 | ||||
1008 | break; | |||
1009 | #else | |||
1010 | case TS_PERF_TRACE_BEGIN: | |||
1011 | criticalError("TS_PERF_TRACE not supported"); | |||
1012 | break; | |||
1013 | case TS_PERF_TRACE_GET_BUFFER: | |||
1014 | criticalError("TS_PERF_TRACE_GET_BUFFER not supported"); | |||
1015 | break; | |||
1016 | #endif /* ENABLE_PERF_TRACE */ | |||
1017 | case TS_QUERY_BOOTLOADER: { | |||
1018 | uint8_t bldata = TS_QUERY_BOOTLOADER_NONE; | |||
1019 | #if EFI_USE_OPENBLT | |||
1020 | bldata = TS_QUERY_BOOTLOADER_OPENBLT; | |||
1021 | #endif | |||
1022 | ||||
1023 | tsChannel->sendResponse(TS_CRC, &bldata, 1, false); | |||
1024 | break; | |||
1025 | } | |||
1026 | default: | |||
1027 | sendErrorCode(tsChannel, TS_RESPONSE_UNRECOGNIZED_COMMAND, "unknown_command"); | |||
1028 | static char tsErrorBuff[80]; | |||
1029 | chsnprintf(tsErrorBuff, sizeof(tsErrorBuff), "ERROR: ignoring unexpected command %d [%c]", command, command); | |||
1030 | tunerStudioError(tsChannel, tsErrorBuff); | |||
1031 | return false; | |||
1032 | } | |||
1033 | ||||
1034 | return true; | |||
1035 | } | |||
1036 | ||||
1037 | #endif // EFI_PROD_CODE || EFI_SIMULATOR | |||
1038 | ||||
1039 |
1/1✓ Decision 'true' taken 60 times.
|
60 | bool isTuningVeNow() { | |
1040 |
1/2✓ Branch 1 taken 60 times.
✗ Branch 2 not taken.
|
120 | return (!TunerDetectorUtils::isTuningDetectorUndefined()) && | |
1041 |
1/2✗ Branch 2 not taken.
✓ Branch 3 taken 60 times.
|
120 | !calibrationsVeWriteTimer.hasElapsedSec(TunerDetectorUtils::getUserEnteredTuningDetector()); | |
1042 | } | |||
1043 | ||||
1044 | ✗ | void startTunerStudioConnectivity() { | ||
1045 | // Assert tune & output channel struct sizes | |||
1046 | static_assert(sizeof(persistent_config_s) == TOTAL_CONFIG_SIZE, "TS datapage size mismatch"); | |||
1047 | // useful trick if you need to know how far off is the static_assert | |||
1048 | //char (*__kaboom)[sizeof(persistent_config_s)] = 1; | |||
1049 | // another useful trick | |||
1050 | //static_assert(offsetof (engine_configuration_s,HD44780_e) == 700); | |||
1051 | ||||
1052 | ✗ | memset(&tsState, 0, sizeof(tsState)); | ||
1053 | ||||
1054 | ✗ | addConsoleAction("tsinfo", printTsStats); | ||
1055 | ✗ | addConsoleAction("reset_ts", resetTs); | ||
1056 | ✗ | addConsoleActionI("set_ts_speed", setTsSpeed); | ||
1057 | ||||
1058 | #if EFI_BLUETOOTH_SETUP | |||
1059 | // module initialization start (it waits for disconnect and then communicates to the module) | |||
1060 | // Usage: "bluetooth_hc06 <baud> <name> <pincode>" | |||
1061 | // Example: "bluetooth_hc06 38400 rusefi 1234" | |||
1062 | // bluetooth_jdy 115200 alphax 1234 | |||
1063 | addConsoleActionSSS("bluetooth_hc05", [](const char *baudRate, const char *name, const char *pinCode) { | |||
1064 | bluetoothStart(BLUETOOTH_HC_05, baudRate, name, pinCode); | |||
1065 | }); | |||
1066 | addConsoleActionSSS("bluetooth_hc06", [](const char *baudRate, const char *name, const char *pinCode) { | |||
1067 | bluetoothStart(BLUETOOTH_HC_06, baudRate, name, pinCode); | |||
1068 | }); | |||
1069 | addConsoleActionSSS("bluetooth_bk", [](const char *baudRate, const char *name, const char *pinCode) { | |||
1070 | bluetoothStart(BLUETOOTH_BK3231, baudRate, name, pinCode); | |||
1071 | }); | |||
1072 | addConsoleActionSSS("bluetooth_jdy", [](const char *baudRate, const char *name, const char *pinCode) { | |||
1073 | bluetoothStart(BLUETOOTH_JDY_3x, baudRate, name, pinCode); | |||
1074 | }); | |||
1075 | addConsoleActionSSS("bluetooth_jdy31", [](const char *baudRate, const char *name, const char *pinCode) { | |||
1076 | bluetoothStart(BLUETOOTH_JDY_31, baudRate, name, pinCode); | |||
1077 | }); | |||
1078 | #endif /* EFI_BLUETOOTH_SETUP */ | |||
1079 | ✗ | } | ||
1080 | ||||
1081 | #endif // EFI_TUNER_STUDIO | |||
1082 |