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