GCC Code Coverage Report


Directory: ./
File: firmware/console/binary_mlg_log/binary_mlg_logging.cpp
Date: 2025-10-24 14:26:41
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 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