GCC Code Coverage Report


Directory: ./
File: firmware/console/binary_mlg_log/binary_mlg_logging.cpp
Date: 2025-10-03 00:57:22
Coverage Exec Excl Total
Lines: 96.0% 72 0 75
Functions: 83.3% 5 0 6
Branches: 88.9% 16 0 18
Decisions: 91.7% 11 - 12

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