3030NACK = 0x1F
3131CHECKSUM_START = 0xAB
3232
33- MIN_ACK_TIMEOUT_MS = 100
33+ MIN_ACK_TIMEOUT_MS = 20
34+ SPI_ACK_TIMEOUT_MS = 500
35+ SPI_CLEANUP_TIMEOUT_MS = 1
3436MAX_XFER_RETRY_COUNT = 5
3537
3638SPI_BUF_SIZE = 4096 # from panda/board/drivers/spi.h
@@ -124,6 +126,8 @@ class PandaSpiHandle(BaseHandle):
124126 def __init__ (self ) -> None :
125127 self .dev = SpiDevice ()
126128 self .no_retry = "NO_RETRY" in os .environ
129+ self .connected = True
130+ self .comms_healthy = True
127131
128132 # helpers
129133 def _calc_checksum (self , data : bytes ) -> int :
@@ -133,10 +137,12 @@ def _calc_checksum(self, data: bytes) -> int:
133137 return cksum
134138
135139 def _wait_for_ack (self , spi , ack_val : int , timeout : int , tx : int , length : int = 1 ) -> bytes :
136- timeout_s = max (MIN_ACK_TIMEOUT_MS , timeout ) * 1e-3
140+ if timeout == 0 :
141+ timeout = SPI_ACK_TIMEOUT_MS
142+ timeout_s = timeout * 1e-3
137143
138144 start = time .monotonic ()
139- while (timeout == 0 ) or (( time .monotonic () - start ) < timeout_s ) :
145+ while (time .monotonic () - start ) < timeout_s :
140146 dat = spi .xfer2 ([tx , ] * length )
141147 if dat [0 ] == ack_val :
142148 return bytes (dat )
@@ -154,7 +160,7 @@ def _transfer_spidev(self, spi, endpoint: int, data, timeout: int, max_rx_len: i
154160 spi .xfer2 (packet )
155161
156162 logger .debug ("- waiting for header ACK" )
157- self ._wait_for_ack (spi , HACK , MIN_ACK_TIMEOUT_MS , 0x11 )
163+ self ._wait_for_ack (spi , HACK , timeout , 0x11 )
158164
159165 logger .debug ("- sending data" )
160166 packet = bytes ([* data , self ._calc_checksum (data )])
@@ -165,18 +171,14 @@ def _transfer_spidev(self, spi, endpoint: int, data, timeout: int, max_rx_len: i
165171 return b""
166172 else :
167173 logger .debug ("- waiting for data ACK" )
168- preread_len = USBPACKET_MAX_SIZE + 1 # read enough for a controlRead
169- dat = self ._wait_for_ack (spi , DACK , timeout , 0x13 , length = 3 + preread_len )
174+ dat = self ._wait_for_ack (spi , DACK , timeout , 0x13 , length = 3 )
170175
171176 # get response length, then response
172177 response_len = struct .unpack ("<H" , dat [1 :3 ])[0 ]
173178 if response_len > max_rx_len :
174179 raise PandaSpiException (f"response length greater than max ({ max_rx_len } { response_len } )" )
175180
176- # read rest
177- remaining = (response_len + 1 ) - preread_len
178- if remaining > 0 :
179- dat += bytes (spi .readbytes (remaining ))
181+ dat += bytes (spi .readbytes (response_len + 1 ))
180182
181183 dat = dat [:3 + response_len + 1 ]
182184 if self ._calc_checksum (dat ) != 0 :
@@ -189,32 +191,46 @@ def _transfer(self, endpoint: int, data, timeout: int, max_rx_len: int = 1000, e
189191 logger .debug ("==============================================" )
190192
191193 n = 0
192- start_time = time .monotonic ()
194+ nack_count = 0
195+ timeout_count = 0
193196 exc = PandaSpiException ()
194- while ( timeout == 0 ) or ( time . monotonic () - start_time ) < timeout * 1e-3 :
197+ while self . connected :
195198 n += 1
196199 logger .debug ("\n try #%d" , n )
197200 with self .dev .acquire () as spi :
198201 try :
199- return self ._transfer_spidev (spi , endpoint , data , timeout , max_rx_len , expect_disconnect )
202+ ret = self ._transfer_spidev (spi , endpoint , data , timeout , max_rx_len , expect_disconnect )
203+ self .comms_healthy = True
204+ return ret
200205 except PandaSpiException as e :
201206 exc = e
202207 logger .debug ("SPI transfer failed, retrying" , exc_info = True )
203208 if self .no_retry :
204209 break
205210
211+ if isinstance (e , PandaSpiMissingAck ):
212+ timeout_count += 1
213+ if timeout != 0 and timeout_count > MAX_XFER_RETRY_COUNT :
214+ break
215+
216+ if isinstance (e , PandaSpiNackResponse ):
217+ nack_count += 1
218+ if nack_count > 3 :
219+ time .sleep (min (max (nack_count * 10 , 200 ), 2000 ) * 1e-6 )
220+
206221 # ensure slave is in a consistent state and ready for the next transfer
207222 # (e.g. slave TX buffer isn't stuck full)
208- nack_cnt = 0
223+ cleanup_nack_count = 0
209224 attempts = 5
210- while (nack_cnt <= 3 ) and (attempts > 0 ):
225+ while (cleanup_nack_count <= 3 ) and (attempts > 0 ):
211226 attempts -= 1
212227 try :
213- self ._wait_for_ack (spi , NACK , MIN_ACK_TIMEOUT_MS , 0x11 , length = XFER_SIZE // 2 )
214- nack_cnt += 1
228+ self ._wait_for_ack (spi , NACK , SPI_CLEANUP_TIMEOUT_MS , 0x14 , length = XFER_SIZE // 2 )
229+ cleanup_nack_count += 1
215230 except PandaSpiException :
216- nack_cnt = 0
231+ cleanup_nack_count = 0
217232
233+ self .comms_healthy = False
218234 raise exc
219235
220236 def get_protocol_version (self ) -> bytes :
@@ -255,6 +271,7 @@ def _get_version(spi) -> bytes:
255271
256272 # libusb1 functions
257273 def close (self ):
274+ self .connected = False
258275 self .dev .close ()
259276
260277 def controlWrite (self , request_type : int , request : int , value : int , index : int , data , timeout : int = TIMEOUT , expect_disconnect : bool = False ):
0 commit comments