GCC Code Coverage Report


Directory: ./
File: firmware/controllers/can/isotp/isotp.cpp
Date: 2025-11-16 14:52:24
Warnings: 1 unchecked decisions!
Coverage Exec Excl Total
Lines: 80.8% 139 0 172
Functions: 87.5% 7 0 8
Branches: 74.2% 66 0 89
Decisions: 61.7% 37 - 60

Line Branch Decision Exec Source
1 /**
2 *
3 * https://en.wikipedia.org/wiki/ISO_15765-2
4 */
5
6 #include "pch.h"
7 #include "isotp.h"
8 #include "can_rx.h"
9
10 #if HAL_USE_CAN || EFI_UNIT_TEST
11
12 52 int CanStreamerState::sendFrame(const IsoTpFrameHeader & header, const uint8_t *data, int num, can_sysinterval_t timeout) {
13 52 int dlc = 8; // standard 8 bytes
14
1/1
✓ Branch 2 taken 52 times.
52 CanTxMessage txmsg(CanCategory::SERIAL, txFrameId, dlc, busIndex, IS_EXT_RANGE_ID(txFrameId));
15
16 // fill the frame data according to the CAN-TP protocol (ISO 15765-2)
17
1/1
✓ Branch 1 taken 52 times.
52 txmsg[isoHeaderByteIndex] = (uint8_t)((header.frameType & 0xf) << 4);
18 int offset, maxNumBytes;
19
4/5
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 31 times.
✓ Branch 3 taken 8 times.
✗ Branch 4 not taken.
52 switch (header.frameType) {
20
1/1
✓ Decision 'true' taken 5 times.
5 case ISO_TP_FRAME_SINGLE:
21 5 offset = isoHeaderByteIndex + 1;
22
1/1
✓ Branch 1 taken 5 times.
5 maxNumBytes = minI(header.numBytes, dlc - offset);
23
1/1
✓ Branch 1 taken 5 times.
5 txmsg[isoHeaderByteIndex] |= maxNumBytes;
24 5 break;
25
1/1
✓ Decision 'true' taken 8 times.
8 case ISO_TP_FRAME_FIRST:
26
1/1
✓ Branch 1 taken 8 times.
8 txmsg[isoHeaderByteIndex] |= (header.numBytes >> 8) & 0xf;
27
1/1
✓ Branch 1 taken 8 times.
8 txmsg[isoHeaderByteIndex + 1] = (uint8_t)(header.numBytes & 0xff);
28 8 offset = isoHeaderByteIndex + 2;
29
1/1
✓ Branch 1 taken 8 times.
8 maxNumBytes = minI(header.numBytes, dlc - offset);
30 8 break;
31
1/1
✓ Decision 'true' taken 31 times.
31 case ISO_TP_FRAME_CONSECUTIVE:
32
1/1
✓ Branch 1 taken 31 times.
31 txmsg[isoHeaderByteIndex] |= header.index & 0xf;
33 31 offset = isoHeaderByteIndex + 1;
34 // todo: is it correct?
35 31 maxNumBytes = dlc - offset;
36 31 break;
37
1/1
✓ Decision 'true' taken 8 times.
8 case ISO_TP_FRAME_FLOW_CONTROL:
38
1/1
✓ Branch 1 taken 8 times.
8 txmsg[isoHeaderByteIndex] |= header.fcFlag & 0xf;
39
1/1
✓ Branch 1 taken 8 times.
8 txmsg[isoHeaderByteIndex + 1] = (uint8_t)(header.blockSize);
40
1/1
✓ Branch 1 taken 8 times.
8 txmsg[isoHeaderByteIndex + 2] = (uint8_t)(header.separationTime);
41 8 offset = isoHeaderByteIndex + 3;
42 8 maxNumBytes = 0; // no data is sent with 'flow control' frame
43 8 break;
44 default:
45 // bad frame type
46 return 0;
47 }
48
49
1/1
✓ Branch 1 taken 52 times.
52 int numBytes = minI(maxNumBytes, num);
50 // copy the contents
51
2/2
✓ Branch 0 taken 44 times.
✓ Branch 1 taken 8 times.
2/2
✓ Decision 'true' taken 44 times.
✓ Decision 'false' taken 8 times.
52 if (data != nullptr) {
52
2/2
✓ Branch 0 taken 258 times.
✓ Branch 1 taken 44 times.
2/2
✓ Decision 'true' taken 258 times.
✓ Decision 'false' taken 44 times.
302 for (int i = 0; i < numBytes; i++) {
53
1/1
✓ Branch 1 taken 258 times.
258 txmsg[i + offset] = data[i];
54 }
55 }
56
57 // send the frame!
58
2/3
✓ Branch 1 taken 52 times.
✓ Branch 3 taken 52 times.
✗ Branch 4 not taken.
1/2
✓ Decision 'true' taken 52 times.
✗ Decision 'false' not taken.
52 if (txTransport->transmit(txmsg, timeout) == CAN_MSG_OK)
59 52 return numBytes;
60 return 0;
61 52 }
62
63 // returns the number of copied bytes
64 44 int CanStreamerState::receiveFrame(const CANRxFrame &rxmsg, uint8_t *destinationBuff, int availableAtBuffer, can_sysinterval_t timeout) {
65
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 44 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 44 times.
44 if (rxmsg.DLC < 1)
66 return 0;
67 44 engine->pauseCANdueToSerial = true;
68 44 int frameType = (rxmsg.data8[isoHeaderByteIndex] >> 4) & 0xf;
69
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 44 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 44 times.
44 if (engineConfiguration->verboseIsoTp) {
70 efiPrintf("receiveFrame frameType=%d", frameType);
71 #if EFI_PROD_CODE
72 printCANRxFrame(-1, rxmsg);
73 #endif // EFI_PROD_CODE
74 }
75 int numBytesAvailable, frameIdx;
76 const uint8_t *srcBuf;
77
3/5
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 31 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
44 switch (frameType) {
78
1/1
✓ Decision 'true' taken 5 times.
5 case ISO_TP_FRAME_SINGLE:
79 5 numBytesAvailable = rxmsg.data8[isoHeaderByteIndex] & 0xf;
80 5 this->waitingForNumBytes = numBytesAvailable;
81 5 srcBuf = rxmsg.data8 + 1 + isoHeaderByteIndex;
82 5 break;
83
1/1
✓ Decision 'true' taken 8 times.
8 case ISO_TP_FRAME_FIRST:
84 8 this->waitingForNumBytes = ((rxmsg.data8[isoHeaderByteIndex] & 0xf) << 8) | rxmsg.data8[isoHeaderByteIndex + 1];
85 8 this->waitingForFrameIndex = 1;
86 8 numBytesAvailable = minI(this->waitingForNumBytes, 6 - isoHeaderByteIndex);
87 8 srcBuf = rxmsg.data8 + 2 + isoHeaderByteIndex;
88 8 rxTransport->onTpFirstFrame(); // used to send flow control message
89 8 break;
90
1/1
✓ Decision 'true' taken 31 times.
31 case ISO_TP_FRAME_CONSECUTIVE:
91 31 frameIdx = rxmsg.data8[isoHeaderByteIndex] & 0xf;
92
2/4
✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 31 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 31 times.
31 if (this->waitingForNumBytes < 0 || this->waitingForFrameIndex != frameIdx) {
93 // todo: that's an abnormal situation, and we probably should react?
94 return 0;
95 }
96 31 numBytesAvailable = minI(this->waitingForNumBytes, 7 - isoHeaderByteIndex);
97 31 srcBuf = rxmsg.data8 + 1 + isoHeaderByteIndex;
98 31 this->waitingForFrameIndex = (this->waitingForFrameIndex + 1) & 0xf;
99 31 break;
100 case ISO_TP_FRAME_FLOW_CONTROL:
101 // todo: currently we just ignore the FC frame
102 return 0;
103 default:
104 // bad frame type
105 return 0;
106 }
107
108 /** performance optimization specific to TS over CAN tunnelling
109 TODO: refactor into child class if we ever choose to revive this logic
110 #if defined(TS_CAN_DEVICE_SHORT_PACKETS_IN_ONE_FRAME)
111 if (frameType == ISO_TP_FRAME_SINGLE) {
112 // restore the CRC on the whole packet
113 uint32_t crc = crc32((void *) srcBuf, numBytesAvailable);
114 // we need a separate buffer for crc because srcBuf may not be word-aligned for direct copy
115 uint8_t crcBuffer[sizeof(uint32_t)];
116 *(uint32_t *) (crcBuffer) = SWAP_UINT32(crc);
117
118 // now set the packet size
119 *(uint16_t *) shortCrcPacketStagingArea = SWAP_UINT16(numBytesAvailable);
120 // copy the data
121 if (numBytesAvailable > 0)
122 memcpy(shortCrcPacketStagingArea + sizeof(uint16_t), srcBuf, numBytesAvailable);
123 // copy the crc to the end
124 memcpy(shortCrcPacketStagingArea + sizeof(uint16_t) + numBytesAvailable, crcBuffer, sizeof(crcBuffer));
125
126 // use the reconstructed tmp buffer as a source buffer
127 srcBuf = shortCrcPacketStagingArea;
128 // we added the 16-bit size & 32-bit crc bytes
129 numBytesAvailable += sizeof(uint16_t) + sizeof(crcBuffer);
130 }
131 #endif *//* TS_CAN_DEVICE_SHORT_PACKETS_IN_ONE_FRAME */
132
133 44 int numBytesToCopy = minI(availableAtBuffer, numBytesAvailable);
134
1/2
✓ Branch 0 taken 44 times.
✗ Branch 1 not taken.
1/2
✓ Decision 'true' taken 44 times.
✗ Decision 'false' not taken.
44 if (destinationBuff != nullptr) {
135 44 memcpy(destinationBuff, srcBuf, numBytesToCopy);
136 }
137 44 srcBuf += numBytesToCopy;
138 44 waitingForNumBytes -= numBytesAvailable;
139 44 isComplete = (waitingForNumBytes == 0);
140 44 numBytesAvailable -= numBytesToCopy;
141 // if there are some more bytes left, we save them for the next time
142
2/2
✓ Branch 0 taken 24 times.
✓ Branch 1 taken 44 times.
2/2
✓ Decision 'true' taken 24 times.
✓ Decision 'false' taken 44 times.
68 for (int i = 0; i < numBytesAvailable; i++) {
143 24 rxFifoBuf.put(srcBuf[i]);
144 }
145
146 // according to the specs, we need to acknowledge the received multi-frame start frame
147
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 36 times.
2/2
✓ Decision 'true' taken 8 times.
✓ Decision 'false' taken 36 times.
44 if (frameType == ISO_TP_FRAME_FIRST) {
148 8 IsoTpFrameHeader header;
149 8 header.frameType = ISO_TP_FRAME_FLOW_CONTROL;
150 8 header.fcFlag = 0; // = "continue to send"
151 8 header.blockSize = 0; // = the remaining "frames" to be sent without flow control or delay
152 8 header.separationTime = 0; // = wait 0 milliseconds, send immediately
153
1/1
✓ Branch 1 taken 8 times.
8 sendFrame(header, nullptr, 0, timeout);
154 }
155
156 44 return numBytesToCopy;
157 }
158
159 void CanStreamerState::reset() {
160 waitingForNumBytes = 0;
161 waitingForFrameIndex = 0;
162 isComplete = false;
163 }
164
165 13 int CanStreamerState::sendDataTimeout(const uint8_t *txbuf, int numBytes, can_sysinterval_t timeout) {
166 13 int offset = 0;
167
168
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 13 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 13 times.
13 if (engineConfiguration->verboseIsoTp) {
169 PRINT("*** INFO: sendDataTimeout %d" PRINT_EOL, numBytes);
170 }
171
172
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 13 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 13 times.
13 if (numBytes < 1)
173 return 0;
174
175 // 1 frame
176
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 8 times.
2/2
✓ Decision 'true' taken 5 times.
✓ Decision 'false' taken 8 times.
13 if (numBytes <= 7 - isoHeaderByteIndex) {
177 5 IsoTpFrameHeader header;
178 5 header.frameType = ISO_TP_FRAME_SINGLE;
179 5 header.numBytes = numBytes;
180
1/1
✓ Branch 1 taken 5 times.
5 return sendFrame(header, txbuf, numBytes, timeout);
181 }
182
183 // multiple frames
184
185 // send the first header frame (FF)
186 8 IsoTpFrameHeader header;
187 8 header.frameType = ISO_TP_FRAME_FIRST;
188 8 header.numBytes = numBytes;
189
1/1
✓ Branch 1 taken 8 times.
8 int numSent = sendFrame(header, txbuf + offset, numBytes, timeout);
190 8 offset += numSent;
191 8 numBytes -= numSent;
192 8 int totalNumSent = numSent;
193
194 // get a flow control (FC) frame
195 #if !EFI_UNIT_TEST // todo: add FC to unit-tests?
196 CANRxFrame rxmsg;
197 for (size_t numFcReceived = 0; ; numFcReceived++) {
198 if (rxTransport->receive(&rxmsg, timeout) != CAN_MSG_OK) {
199 #ifdef SERIAL_CAN_DEBUG
200 PRINT("*** ERROR: CAN Flow Control frame not received" PRINT_EOL);
201 #endif /* SERIAL_CAN_DEBUG */
202 //warning(ObdCode::CUSTOM_ERR_CAN_COMMUNICATION, "CAN Flow Control frame not received");
203 return 0;
204 }
205 receiveFrame(rxmsg, nullptr, 0, timeout);
206 uint8_t frameType = (rxmsg.data8[isoHeaderByteIndex] >> 4) & 0xf;
207 uint8_t flowStatus = rxmsg.data8[isoHeaderByteIndex] & 0xf;
208 // if something is not ok
209 if ((frameType != ISO_TP_FRAME_FLOW_CONTROL) || (flowStatus != CAN_FLOW_STATUS_OK)) {
210 // if the receiver is not ready yet and asks to wait for the next FC frame (give it 3 attempts)
211 if ((frameType == ISO_TP_FRAME_FLOW_CONTROL) && (flowStatus == CAN_FLOW_STATUS_WAIT_MORE) && (numFcReceived < 3)) {
212 continue;
213 }
214 #ifdef SERIAL_CAN_DEBUG
215 efiPrintf("*** ERROR: CAN Flow Control mode not supported");
216 #endif /* SERIAL_CAN_DEBUG */
217 //warning(ObdCode::CUSTOM_ERR_CAN_COMMUNICATION, "CAN Flow Control mode not supported");
218 return 0;
219 }
220 uint8_t blockSize = rxmsg.data8[isoHeaderByteIndex + 1];
221 uint8_t minSeparationTime = rxmsg.data8[isoHeaderByteIndex + 2];
222 if (blockSize != 0 || minSeparationTime != 0) {
223 // todo: process other Flow Control fields (see ISO 15765-2)
224 #ifdef SERIAL_CAN_DEBUG
225 efiPrintf("*** ERROR: CAN Flow Control fields not supported");
226 #endif /* SERIAL_CAN_DEBUG */
227 //warning(ObdCode::CUSTOM_ERR_CAN_COMMUNICATION, "CAN Flow Control fields not supported");
228 }
229 break;
230 }
231 #endif /* EFI_UNIT_TEST */
232
233 // send the rest of the data
234 8 int idx = 1;
235
2/2
✓ Branch 0 taken 31 times.
✓ Branch 1 taken 8 times.
2/2
✓ Decision 'true' taken 31 times.
✓ Decision 'false' taken 8 times.
39 while (numBytes > 0) {
236
1/1
✓ Branch 1 taken 31 times.
31 int len = minI(numBytes, 7);
237 // send the consecutive frames
238 31 header.frameType = ISO_TP_FRAME_CONSECUTIVE;
239 31 header.index = ((idx++) & 0x0f);
240 31 header.numBytes = len;
241
1/1
✓ Branch 1 taken 31 times.
31 numSent = sendFrame(header, txbuf + offset, len, timeout);
242
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 31 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 31 times.
31 if (numSent < 1)
243 break;
244 31 totalNumSent += numSent;
245 31 offset += numSent;
246 31 numBytes -= numSent;
247 }
248 8 return totalNumSent;
249 }
250
251 23 int CanStreamerState::getDataFromFifo(uint8_t *rxbuf, size_t &numBytes) {
252
2/2
✓ Branch 1 taken 13 times.
✓ Branch 2 taken 10 times.
2/2
✓ Decision 'true' taken 13 times.
✓ Decision 'false' taken 10 times.
23 if (rxFifoBuf.isEmpty())
253 13 return 0;
254 10 int numReadFromFifo = minI(numBytes, rxFifoBuf.getCount());
255 // move bytes from the FIFO buffer
256 int i;
257
6/6
✓ Branch 1 taken 29 times.
✓ Branch 2 taken 5 times.
✓ Branch 3 taken 24 times.
✓ Branch 4 taken 5 times.
✓ Branch 5 taken 24 times.
✓ Branch 6 taken 10 times.
0/1
? Decision couldn't be analyzed.
34 for (i = 0; !rxFifoBuf.isEmpty() && i < numReadFromFifo; i++) {
258 24 rxbuf[i] = rxFifoBuf.get();
259 24 numBytes--;
260 }
261 10 return i;
262 }
263
264 23 can_msg_t CanStreamerState::streamAddToTxTimeout(size_t *np, const uint8_t *txbuf, can_sysinterval_t timeout) {
265 23 int numBytes = *np;
266 23 int offset = 0;
267
268
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 23 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 23 times.
23 if (engineConfiguration->verboseIsoTp) {
269 PRINT("*** INFO: streamAddToTxTimeout adding %d, in buffer %d" PRINT_EOL, numBytes, txFifoBuf.getCount());
270 }
271
272 // we send here only if the TX FIFO buffer is getting overflowed
273
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 23 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 23 times.
23 while (numBytes >= txFifoBuf.getSize() - txFifoBuf.getCount()) {
274 int numBytesToAdd = txFifoBuf.getSize() - txFifoBuf.getCount();
275 txFifoBuf.put(txbuf + offset, numBytesToAdd);
276 int numSent = sendDataTimeout((const uint8_t *)txFifoBuf.getElements(), txFifoBuf.getCount(), timeout);
277
278 if (engineConfiguration->verboseIsoTp) {
279 PRINT("*** INFO: streamAddToTxTimeout numBytesToAdd %d / numSent %d / numBytes %d" PRINT_EOL, numBytesToAdd, numSent, numBytes);
280 }
281
282 if (numSent < 1)
283 break;
284 txFifoBuf.clear();
285 offset += numBytesToAdd;
286 numBytes -= numBytesToAdd;
287 }
288
289
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 23 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 23 times.
23 if (engineConfiguration->verboseIsoTp) {
290 PRINT("*** INFO: streamAddToTxTimeout remaining goes to buffer %d" PRINT_EOL, numBytes);
291 }
292
293 // now we put the rest on hold
294 23 txFifoBuf.put(txbuf + offset, numBytes);
295
296
297
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 23 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 23 times.
23 if (engineConfiguration->verboseIsoTp) {
298 PRINT("*** INFO: in buffer %d" PRINT_EOL, txFifoBuf.getCount());
299 }
300
301 23 return CAN_MSG_OK;
302 }
303
304 13 can_msg_t CanStreamerState::streamFlushTx(can_sysinterval_t timeout) {
305 13 int numSent = sendDataTimeout((const uint8_t *)txFifoBuf.getElements(), txFifoBuf.getCount(), timeout);
306 13 if (numSent != txFifoBuf.getCount()) {
307 //warning(ObdCode::CUSTOM_ERR_CAN_COMMUNICATION, "CAN sendDataTimeout() problems");
308 }
309 13 txFifoBuf.clear();
310
311 13 return CAN_MSG_OK;
312 }
313
314 23 can_msg_t CanStreamerState::streamReceiveTimeout(size_t *np, uint8_t *rxbuf, can_sysinterval_t timeout) {
315 23 size_t availableBufferSpace = *np;
316
317 // first, fill the data from the stored buffer (saved from the previous CAN frame)
318
1/1
✓ Branch 1 taken 23 times.
23 int receivedSoFar = getDataFromFifo(rxbuf, availableBufferSpace);
319
320 // if even more data is needed, then we receive more CAN frames
321
2/2
✓ Branch 0 taken 44 times.
✓ Branch 1 taken 23 times.
2/2
✓ Decision 'true' taken 44 times.
✓ Decision 'false' taken 23 times.
67 while (availableBufferSpace > 0) {
322 44 CANRxFrame rxmsg;
323
2/3
✓ Branch 1 taken 44 times.
✓ Branch 3 taken 44 times.
✗ Branch 4 not taken.
1/2
✓ Decision 'true' taken 44 times.
✗ Decision 'false' not taken.
44 if (rxTransport->receive(&rxmsg, timeout) == CAN_MSG_OK) {
324
1/1
✓ Branch 1 taken 44 times.
44 int numReceived = receiveFrame(rxmsg, rxbuf + receivedSoFar, availableBufferSpace, timeout);
325
326
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 44 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 44 times.
44 if (numReceived < 1)
327 break;
328 44 availableBufferSpace -= numReceived;
329 44 receivedSoFar += numReceived;
330 } else {
331 break;
332 }
333 }
334 23 *np -= availableBufferSpace;
335
336 #ifdef SERIAL_CAN_DEBUG
337 efiPrintf("* ret: %d %d (%d)", i, *np, availableBufferSpace);
338 for (int j = 0; j < receivedSoFar; j++) {
339 efiPrintf("* [%d]: %02x", j, rxbuf[j]);
340 }
341 #endif /* SERIAL_CAN_DEBUG */
342
343 23 return CAN_MSG_OK;
344 }
345
346 #endif // HAL_USE_CAN || EFI_UNIT_TEST
347