| Line | Branch | Decision | Exec | Source |
|---|---|---|---|---|
| 1 | /** | |||
| 2 | * binary_mlg_logging.cpp | |||
| 3 | * | |||
| 4 | * See also BinarySensorLog.java | |||
| 5 | * See also mlq_file_format.txt | |||
| 6 | * | |||
| 7 | * https://www.efianalytics.com/TunerStudio/docs/MLG_Binary_LogFormat_1.0.pdf | |||
| 8 | * https://www.efianalytics.com/TunerStudio/docs/MLG_Binary_LogFormat_2.0.pdf | |||
| 9 | */ | |||
| 10 | ||||
| 11 | #include "pch.h" | |||
| 12 | ||||
| 13 | #include "binary_mlg_logging.h" | |||
| 14 | #include "mlg_field.h" | |||
| 15 | #include "tunerstudio.h" | |||
| 16 | #include "board_lookup.h" | |||
| 17 | ||||
| 18 | #if EFI_FILE_LOGGING || EFI_UNIT_TEST | |||
| 19 | ||||
| 20 | #define TIME_PRECISION 1000 | |||
| 21 | ||||
| 22 | #if EFI_PROD_CODE | |||
| 23 | extern bool main_loop_started; | |||
| 24 | #endif | |||
| 25 | ||||
| 26 | // floating number of seconds with millisecond precision | |||
| 27 | static scaled_channel<uint32_t, TIME_PRECISION> packedTime; | |||
| 28 | ||||
| 29 | // The list of logged fields lives in a separate file so it can eventually be tool-generated | |||
| 30 | // We use angle brackets instead of quotes because for some boards we want to use header different from the one in this | |||
| 31 | // directory | |||
| 32 | #include <log_fields_generated.h> | |||
| 33 | ||||
| 34 | namespace MLG | |||
| 35 | { | |||
| 36 | ||||
| 37 | ✗ | int getSdCardFieldsCount() { | ||
| 38 | ✗ | return efi::size(fields); | ||
| 39 | } | |||
| 40 | ||||
| 41 | 1 | static constexpr uint16_t computeFieldsRecordLength() { | ||
| 42 | 1 | uint16_t recLength = 0; | ||
| 43 |
2/2✓ Branch 1 taken 729 times.
✓ Branch 2 taken 1 time.
|
2/2✓ Decision 'true' taken 729 times.
✓ Decision 'false' taken 1 time.
|
730 | for (size_t i = 0; i < efi::size(fields); i++) { |
| 44 | 729 | recLength += fields[i].getSize(); | ||
| 45 | } | |||
| 46 | ||||
| 47 | 1 | return recLength; | ||
| 48 | } | |||
| 49 | ||||
| 50 | static uint64_t binaryLogCount = 0; | |||
| 51 | ||||
| 52 | static const uint16_t recordLength = computeFieldsRecordLength(); | |||
| 53 | ||||
| 54 | 558 | static size_t writeFileHeader(Writer& outBuffer) { | ||
| 55 | 558 | size_t writen = 0; | ||
| 56 | 558 | char buffer[Types::Header::Size]; | ||
| 57 | // File format: MLVLG\0 | |||
| 58 | 558 | strncpy(buffer, "MLVLG", 6); | ||
| 59 | ||||
| 60 | // Format version = 02 | |||
| 61 | 558 | buffer[6] = 0; | ||
| 62 | 558 | buffer[7] = 2; | ||
| 63 | ||||
| 64 | // Timestamp | |||
| 65 | 558 | buffer[8] = 0; | ||
| 66 | 558 | buffer[9] = 0; | ||
| 67 | 558 | buffer[10] = 0; | ||
| 68 | 558 | buffer[11] = 0; | ||
| 69 | ||||
| 70 | // Info data start | |||
| 71 | 558 | buffer[12] = 0; | ||
| 72 | 558 | buffer[13] = 0; | ||
| 73 | 558 | buffer[14] = 0; | ||
| 74 | 558 | buffer[15] = 0; | ||
| 75 | ||||
| 76 | 558 | size_t headerSize = Types::Header::Size + efi::size(fields) * Types::Field::DescriptorSize; | ||
| 77 | ||||
| 78 | // Data begin index: begins immediately after the header | |||
| 79 | 558 | buffer[16] = (headerSize >> 24) & 0xFF; | ||
| 80 | 558 | buffer[17] = (headerSize >> 16) & 0xFF; | ||
| 81 | 558 | buffer[18] = (headerSize >> 8) & 0xFF; | ||
| 82 | 558 | buffer[19] = headerSize & 0xFF; | ||
| 83 | ||||
| 84 | // Record length - length of a single data record: sum size of all fields | |||
| 85 | 558 | buffer[20] = recordLength >> 8; | ||
| 86 | 558 | buffer[21] = recordLength & 0xFF; | ||
| 87 | ||||
| 88 | // Number of logger fields | |||
| 89 | 558 | int fieldsCount = efi::size(fields); | ||
| 90 | 558 | buffer[22] = fieldsCount >> 8; | ||
| 91 | 558 | buffer[23] = fieldsCount; | ||
| 92 | ||||
| 93 |
1/1✓ Branch 1 taken 558 times.
|
558 | outBuffer.write(buffer, Types::Header::Size); | |
| 94 | 558 | writen += Types::Header::Size; | ||
| 95 | ||||
| 96 | // Write the actual logger fields, offset by header size (24) | |||
| 97 |
2/2✓ Branch 1 taken 406782 times.
✓ Branch 2 taken 558 times.
|
2/2✓ Decision 'true' taken 406782 times.
✓ Decision 'false' taken 558 times.
|
407340 | for (size_t i = 0; i < efi::size(fields); i++) { |
| 98 |
1/1✓ Branch 2 taken 406782 times.
|
406782 | writen += fields[i].writeHeader(outBuffer); | |
| 99 | } | |||
| 100 | ||||
| 101 | 558 | return writen; | ||
| 102 | } | |||
| 103 | ||||
| 104 | static uint8_t blockRollCounter = 0; | |||
| 105 | ||||
| 106 | 531078 | static size_t writeSdBlock(Writer& outBuffer) { | ||
| 107 | 531078 | size_t writen = 0; | ||
| 108 | static char buffer[16]; | |||
| 109 | ||||
| 110 | // Offset 0 = Block type, standard data block in this case | |||
| 111 | 531078 | buffer[0] = 0; | ||
| 112 | ||||
| 113 | // Offset 1 = rolling counter sequence number | |||
| 114 | 531078 | buffer[1] = blockRollCounter++; | ||
| 115 | ||||
| 116 | // Offset 2, size 2 = Timestamp at 10us resolution | |||
| 117 | 531078 | efitimeus_t nowUs = getTimeNowUs(); | ||
| 118 | 531078 | uint16_t timestamp = nowUs / 10; | ||
| 119 | 531078 | buffer[2] = timestamp >> 8; | ||
| 120 | 531078 | buffer[3] = timestamp & 0xFF; | ||
| 121 | ||||
| 122 | // TODO: check ret value! | |||
| 123 | 531078 | outBuffer.write(buffer, 4); | ||
| 124 | 531078 | writen += 4; | ||
| 125 | ||||
| 126 | // todo: add a log field for SD card period | |||
| 127 | // revSdCardLineTime = nowUs; | |||
| 128 | ||||
| 129 |
1/1✓ Branch 2 taken 531078 times.
|
531078 | packedTime = getTimeNowMs() * 1.0 / TIME_PRECISION; | |
| 130 | ||||
| 131 | 531078 | uint8_t sum = 0; | ||
| 132 |
2/2✓ Branch 1 taken 387155862 times.
✓ Branch 2 taken 531078 times.
|
2/2✓ Decision 'true' taken 387155862 times.
✓ Decision 'false' taken 531078 times.
|
387686940 | for (size_t fieldIndex = 0; fieldIndex < efi::size(fields); fieldIndex++) { |
| 133 | #if EFI_UNIT_TEST | |||
| 134 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 387155862 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 387155862 times.
|
387155862 | if (engine == nullptr) { |
| 135 | ✗ | throw std::runtime_error{"Engine pointer is nullptr in writeSdBlock"}; | ||
| 136 | } | |||
| 137 | ||||
| 138 | // For tests a global Engine pointer is initialised with the nullptr, and tests that do require it | |||
| 139 | // create their own instance and set up the global pointer accordingly. | |||
| 140 | // Global static const array of fields in the generated file log_fields_generated.h has fields with | |||
| 141 | // addresses const-evaluated against 'nullptr' engine, which effectively means offsets in Engine struct, | |||
| 142 | // so if that is the case, we need to account for the offset to whatever | |||
| 143 | // real current engine pointer is set to. | |||
| 144 | // In tests, we are dealing with ELF on linux, and as far as I'm aware, there are no distributions | |||
| 145 | // where the default linker config can map smth before the 4 MB address. | |||
| 146 | // If in doubt, check your system for a min text-segment with "ld --verbose | grep -A20 ENTRY" | |||
| 147 | // Engine struct is lower than 4MB in size, so we can compare field address against Engine size | |||
| 148 | // to find out whether field address was initialised against nullptr engine or not. | |||
| 149 | ||||
| 150 | 387155862 | constexpr auto engineObjectSize{ sizeof(Engine) }; | ||
| 151 | static_assert(engineObjectSize < 0x400000); | |||
| 152 | ||||
| 153 | 387155862 | auto const currentFieldAddress{ reinterpret_cast<uintptr_t>(fields[fieldIndex].getAddr()) }; | ||
| 154 | 387155862 | auto const fieldNeedsOffset{ currentFieldAddress < engineObjectSize }; | ||
| 155 |
2/2✓ Branch 0 taken 386624784 times.
✓ Branch 1 taken 531078 times.
|
387155862 | void* const offset{ fieldNeedsOffset ? engine : nullptr }; | |
| 156 | #else | |||
| 157 | void* const offset{ nullptr }; | |||
| 158 | #endif | |||
| 159 | ||||
| 160 | 387155862 | size_t entrySize = fields[fieldIndex].writeData(buffer, offset); | ||
| 161 | ||||
| 162 |
2/2✓ Branch 0 taken 823701978 times.
✓ Branch 1 taken 387155862 times.
|
2/2✓ Decision 'true' taken 823701978 times.
✓ Decision 'false' taken 387155862 times.
|
1210857840 | for (size_t byteIndex = 0; byteIndex < entrySize; byteIndex++) { |
| 163 | // "CRC" at the end is just the sum of all bytes | |||
| 164 | 823701978 | sum += buffer[byteIndex]; | ||
| 165 | } | |||
| 166 | // TODO: check ret value! | |||
| 167 | 387155862 | outBuffer.write(buffer, entrySize); | ||
| 168 | 387155862 | writen += entrySize; | ||
| 169 | } | |||
| 170 | ||||
| 171 | 531078 | buffer[0] = sum; | ||
| 172 | // 1 byte checksum footer | |||
| 173 | 531078 | outBuffer.write(buffer, 1); | ||
| 174 | 531078 | writen += 1; | ||
| 175 | ||||
| 176 | 531078 | return writen; | ||
| 177 | } | |||
| 178 | ||||
| 179 | 531636 | size_t writeSdLogLine(Writer& bufferedWriter) { | ||
| 180 | #if EFI_PROD_CODE | |||
| 181 | if (!main_loop_started) | |||
| 182 | return 0; | |||
| 183 | #endif //EFI_PROD_CODE | |||
| 184 | ||||
| 185 |
2/2✓ Branch 0 taken 558 times.
✓ Branch 1 taken 531078 times.
|
2/2✓ Decision 'true' taken 558 times.
✓ Decision 'false' taken 531078 times.
|
531636 | if (binaryLogCount == 0) { |
| 186 | 558 | binaryLogCount++; | ||
| 187 | ||||
| 188 | 558 | return writeFileHeader(bufferedWriter); | ||
| 189 | } else { | |||
| 190 | 531078 | binaryLogCount++; | ||
| 191 | ||||
| 192 | 531078 | updateTunerStudioState(); | ||
| 193 | 531078 | return writeSdBlock(bufferedWriter); | ||
| 194 | } | |||
| 195 | } | |||
| 196 | ||||
| 197 | 558 | void resetFileLogging() { | ||
| 198 | 558 | binaryLogCount = 0; | ||
| 199 | 558 | blockRollCounter = 0; | ||
| 200 | 558 | } | ||
| 201 | ||||
| 202 | } /* namespace MLG */ | |||
| 203 | ||||
| 204 | #endif /* EFI_FILE_LOGGING */ | |||
| 205 |