GCC Code Coverage Report


Directory: ./
File: firmware/console/binary_mlg_log/binary_mlg_logging.cpp
Date: 2025-11-16 14:52:24
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 * 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