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