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 727 times.
✓ Branch 2 taken 1 time.
|
2/2✓ Decision 'true' taken 727 times.
✓ Decision 'false' taken 1 time.
|
728 | for (size_t i = 0; i < efi::size(fields); i++) { |
44 | 727 | 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 | 557 | static size_t writeFileHeader(Writer& outBuffer) { | ||
55 | 557 | size_t writen = 0; | ||
56 | 557 | char buffer[Types::Header::Size]; | ||
57 | // File format: MLVLG\0 | |||
58 | 557 | strncpy(buffer, "MLVLG", 6); | ||
59 | ||||
60 | // Format version = 02 | |||
61 | 557 | buffer[6] = 0; | ||
62 | 557 | buffer[7] = 2; | ||
63 | ||||
64 | // Timestamp | |||
65 | 557 | buffer[8] = 0; | ||
66 | 557 | buffer[9] = 0; | ||
67 | 557 | buffer[10] = 0; | ||
68 | 557 | buffer[11] = 0; | ||
69 | ||||
70 | // Info data start | |||
71 | 557 | buffer[12] = 0; | ||
72 | 557 | buffer[13] = 0; | ||
73 | 557 | buffer[14] = 0; | ||
74 | 557 | buffer[15] = 0; | ||
75 | ||||
76 | 557 | size_t headerSize = Types::Header::Size + efi::size(fields) * Types::Field::DescriptorSize; | ||
77 | ||||
78 | // Data begin index: begins immediately after the header | |||
79 | 557 | buffer[16] = (headerSize >> 24) & 0xFF; | ||
80 | 557 | buffer[17] = (headerSize >> 16) & 0xFF; | ||
81 | 557 | buffer[18] = (headerSize >> 8) & 0xFF; | ||
82 | 557 | buffer[19] = headerSize & 0xFF; | ||
83 | ||||
84 | // Record length - length of a single data record: sum size of all fields | |||
85 | 557 | buffer[20] = recordLength >> 8; | ||
86 | 557 | buffer[21] = recordLength & 0xFF; | ||
87 | ||||
88 | // Number of logger fields | |||
89 | 557 | int fieldsCount = efi::size(fields); | ||
90 | 557 | buffer[22] = fieldsCount >> 8; | ||
91 | 557 | buffer[23] = fieldsCount; | ||
92 | ||||
93 |
1/1✓ Branch 1 taken 557 times.
|
557 | outBuffer.write(buffer, Types::Header::Size); | |
94 | 557 | writen += Types::Header::Size; | ||
95 | ||||
96 | // Write the actual logger fields, offset by header size (24) | |||
97 |
2/2✓ Branch 1 taken 404939 times.
✓ Branch 2 taken 557 times.
|
2/2✓ Decision 'true' taken 404939 times.
✓ Decision 'false' taken 557 times.
|
405496 | for (size_t i = 0; i < efi::size(fields); i++) { |
98 |
1/1✓ Branch 2 taken 404939 times.
|
404939 | writen += fields[i].writeHeader(outBuffer); | |
99 | } | |||
100 | ||||
101 | 557 | return writen; | ||
102 | } | |||
103 | ||||
104 | static uint8_t blockRollCounter = 0; | |||
105 | ||||
106 | 522954 | static size_t writeSdBlock(Writer& outBuffer) { | ||
107 | 522954 | size_t writen = 0; | ||
108 | static char buffer[16]; | |||
109 | ||||
110 | // Offset 0 = Block type, standard data block in this case | |||
111 | 522954 | buffer[0] = 0; | ||
112 | ||||
113 | // Offset 1 = rolling counter sequence number | |||
114 | 522954 | buffer[1] = blockRollCounter++; | ||
115 | ||||
116 | // Offset 2, size 2 = Timestamp at 10us resolution | |||
117 | 522954 | efitimeus_t nowUs = getTimeNowUs(); | ||
118 | 522954 | uint16_t timestamp = nowUs / 10; | ||
119 | 522954 | buffer[2] = timestamp >> 8; | ||
120 | 522954 | buffer[3] = timestamp & 0xFF; | ||
121 | ||||
122 | // TODO: check ret value! | |||
123 | 522954 | outBuffer.write(buffer, 4); | ||
124 | 522954 | writen += 4; | ||
125 | ||||
126 | // todo: add a log field for SD card period | |||
127 | // revSdCardLineTime = nowUs; | |||
128 | ||||
129 |
1/1✓ Branch 2 taken 522954 times.
|
522954 | packedTime = getTimeNowMs() * 1.0 / TIME_PRECISION; | |
130 | ||||
131 | 522954 | uint8_t sum = 0; | ||
132 |
2/2✓ Branch 1 taken 380187558 times.
✓ Branch 2 taken 522954 times.
|
2/2✓ Decision 'true' taken 380187558 times.
✓ Decision 'false' taken 522954 times.
|
380710512 | 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 380187558 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 380187558 times.
|
380187558 | 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 | 380187558 | constexpr auto engineObjectSize{ sizeof(Engine) }; | ||
151 | static_assert(engineObjectSize < 0x400000); | |||
152 | ||||
153 | 380187558 | auto const currentFieldAddress{ reinterpret_cast<uintptr_t>(fields[fieldIndex].getAddr()) }; | ||
154 | 380187558 | auto const fieldNeedsOffset{ currentFieldAddress < engineObjectSize }; | ||
155 |
2/2✓ Branch 0 taken 379664604 times.
✓ Branch 1 taken 522954 times.
|
380187558 | void* const offset{ fieldNeedsOffset ? engine : nullptr }; | |
156 | #else | |||
157 | void* const offset{ nullptr }; | |||
158 | #endif | |||
159 | ||||
160 | 380187558 | size_t entrySize = fields[fieldIndex].writeData(buffer, offset); | ||
161 | ||||
162 |
2/2✓ Branch 0 taken 808486884 times.
✓ Branch 1 taken 380187558 times.
|
2/2✓ Decision 'true' taken 808486884 times.
✓ Decision 'false' taken 380187558 times.
|
1188674442 | for (size_t byteIndex = 0; byteIndex < entrySize; byteIndex++) { |
163 | // "CRC" at the end is just the sum of all bytes | |||
164 | 808486884 | sum += buffer[byteIndex]; | ||
165 | } | |||
166 | // TODO: check ret value! | |||
167 | 380187558 | outBuffer.write(buffer, entrySize); | ||
168 | 380187558 | writen += entrySize; | ||
169 | } | |||
170 | ||||
171 | 522954 | buffer[0] = sum; | ||
172 | // 1 byte checksum footer | |||
173 | 522954 | outBuffer.write(buffer, 1); | ||
174 | 522954 | writen += 1; | ||
175 | ||||
176 | 522954 | return writen; | ||
177 | } | |||
178 | ||||
179 | 523511 | 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 557 times.
✓ Branch 1 taken 522954 times.
|
2/2✓ Decision 'true' taken 557 times.
✓ Decision 'false' taken 522954 times.
|
523511 | if (binaryLogCount == 0) { |
186 | 557 | binaryLogCount++; | ||
187 | ||||
188 | 557 | return writeFileHeader(bufferedWriter); | ||
189 | } else { | |||
190 | 522954 | binaryLogCount++; | ||
191 | ||||
192 | 522954 | updateTunerStudioState(); | ||
193 | 522954 | return writeSdBlock(bufferedWriter); | ||
194 | } | |||
195 | } | |||
196 | ||||
197 | 557 | void resetFileLogging() { | ||
198 | 557 | binaryLogCount = 0; | ||
199 | 557 | blockRollCounter = 0; | ||
200 | 557 | } | ||
201 | ||||
202 | } /* namespace MLG */ | |||
203 | ||||
204 | #endif /* EFI_FILE_LOGGING */ | |||
205 |