# ifndef __APPLE__
# include <sys/file.h>
# include <sys/ioctl.h>
# include <linux/spi/spidev.h>
# include <cassert>
# include <cmath>
# include <cstring>
# include <iomanip>
# include <sstream>
# include "common/util.h"
# include "common/timing.h"
# include "common/swaglog.h"
# include "panda/board/comms_definitions.h"
# include "selfdrive/pandad/panda_comms.h"
# define SPI_SYNC 0x5AU
# define SPI_HACK 0x79U
# define SPI_DACK 0x85U
# define SPI_NACK 0x1FU
# define SPI_CHECKSUM_START 0xABU
enum SpiError {
NACK = - 2 ,
ACK_TIMEOUT = - 3 ,
} ;
const unsigned int SPI_ACK_TIMEOUT = 500 ; // milliseconds
const std : : string SPI_DEVICE = " /dev/spidev0.0 " ;
class LockEx {
public :
LockEx ( int fd , std : : recursive_mutex & m ) : fd ( fd ) , m ( m ) {
m . lock ( ) ;
flock ( fd , LOCK_EX ) ;
}
~ LockEx ( ) {
flock ( fd , LOCK_UN ) ;
m . unlock ( ) ;
}
private :
int fd ;
std : : recursive_mutex & m ;
} ;
# define SPILOG(fn, fmt, ...) do { \
fn ( fmt , # # __VA_ARGS__ ) ; \
fn ( " %d / 0x%x / %d / %d / tx: %s " , \
xfer_count , header . endpoint , header . tx_len , header . max_rx_len , \
util : : hexdump ( tx_buf , std : : min ( ( int ) header . tx_len , 8 ) ) . c_str ( ) ) ; \
} while ( 0 )
PandaSpiHandle : : PandaSpiHandle ( std : : string serial ) : PandaCommsHandle ( serial ) {
int ret ;
const int uid_len = 12 ;
uint8_t uid [ uid_len ] = { 0 } ;
uint32_t spi_mode = SPI_MODE_0 ;
uint8_t spi_bits_per_word = 8 ;
// 50MHz is the max of the 845. note that some older
// revs of the comma three may not support this speed
uint32_t spi_speed = 50000000 ;
if ( ! util : : file_exists ( SPI_DEVICE ) ) {
goto fail ;
}
spi_fd = open ( SPI_DEVICE . c_str ( ) , O_RDWR ) ;
if ( spi_fd < 0 ) {
LOGE ( " failed opening SPI device %d " , spi_fd ) ;
goto fail ;
}
// SPI settings
ret = util : : safe_ioctl ( spi_fd , SPI_IOC_WR_MODE , & spi_mode ) ;
if ( ret < 0 ) {
LOGE ( " failed setting SPI mode %d " , ret ) ;
goto fail ;
}
ret = util : : safe_ioctl ( spi_fd , SPI_IOC_WR_MAX_SPEED_HZ , & spi_speed ) ;
if ( ret < 0 ) {
LOGE ( " failed setting SPI speed " ) ;
goto fail ;
}
ret = util : : safe_ioctl ( spi_fd , SPI_IOC_WR_BITS_PER_WORD , & spi_bits_per_word ) ;
if ( ret < 0 ) {
LOGE ( " failed setting SPI bits per word " ) ;
goto fail ;
}
// get hw UID/serial
ret = control_read ( 0xc3 , 0 , 0 , uid , uid_len , 100 ) ;
if ( ret = = uid_len ) {
std : : stringstream stream ;
for ( int i = 0 ; i < uid_len ; i + + ) {
stream < < std : : hex < < std : : setw ( 2 ) < < std : : setfill ( ' 0 ' ) < < int ( uid [ i ] ) ;
}
hw_serial = stream . str ( ) ;
} else {
LOGD ( " failed to get serial %d " , ret ) ;
goto fail ;
}
if ( ! serial . empty ( ) & & ( serial ! = hw_serial ) ) {
goto fail ;
}
return ;
fail :
cleanup ( ) ;
throw std : : runtime_error ( " Error connecting to panda " ) ;
}
PandaSpiHandle : : ~ PandaSpiHandle ( ) {
std : : lock_guard lk ( hw_lock ) ;
cleanup ( ) ;
}
void PandaSpiHandle : : cleanup ( ) {
if ( spi_fd ! = - 1 ) {
close ( spi_fd ) ;
spi_fd = - 1 ;
}
}
int PandaSpiHandle : : control_write ( uint8_t request , uint16_t param1 , uint16_t param2 , unsigned int timeout ) {
ControlPacket_t packet = {
. request = request ,
. param1 = param1 ,
. param2 = param2 ,
. length = 0
} ;
return spi_transfer_retry ( 0 , ( uint8_t * ) & packet , sizeof ( packet ) , NULL , 0 , timeout ) ;
}
int PandaSpiHandle : : control_read ( uint8_t request , uint16_t param1 , uint16_t param2 , unsigned char * data , uint16_t length , unsigned int timeout ) {
ControlPacket_t packet = {
. request = request ,
. param1 = param1 ,
. param2 = param2 ,
. length = length
} ;
return spi_transfer_retry ( 0 , ( uint8_t * ) & packet , sizeof ( packet ) , data , length , timeout ) ;
}
int PandaSpiHandle : : bulk_write ( unsigned char endpoint , unsigned char * data , int length , unsigned int timeout ) {
return bulk_transfer ( endpoint , data , length , NULL , 0 , timeout ) ;
}
int PandaSpiHandle : : bulk_read ( unsigned char endpoint , unsigned char * data , int length , unsigned int timeout ) {
return bulk_transfer ( endpoint , NULL , 0 , data , length , timeout ) ;
}
int PandaSpiHandle : : bulk_transfer ( uint8_t endpoint , uint8_t * tx_data , uint16_t tx_len , uint8_t * rx_data , uint16_t rx_len , unsigned int timeout ) {
const int xfer_size = SPI_BUF_SIZE - 0x40 ;
int ret = 0 ;
uint16_t length = ( tx_data ! = NULL ) ? tx_len : rx_len ;
for ( int i = 0 ; i < ( int ) std : : ceil ( ( float ) length / xfer_size ) ; i + + ) {
int d ;
if ( tx_data ! = NULL ) {
int len = std : : min ( xfer_size , tx_len - ( xfer_size * i ) ) ;
d = spi_transfer_retry ( endpoint , tx_data + ( xfer_size * i ) , len , NULL , 0 , timeout ) ;
} else {
uint16_t to_read = std : : min ( xfer_size , rx_len - ret ) ;
d = spi_transfer_retry ( endpoint , NULL , 0 , rx_data + ( xfer_size * i ) , to_read , timeout ) ;
}
if ( d < 0 ) {
SPILOG ( LOGE , " SPI: bulk transfer failed with %d " , d ) ;
comms_healthy = false ;
return d ;
}
ret + = d ;
if ( ( rx_data ! = NULL ) & & d < xfer_size ) {
break ;
}
}
return ret ;
}
std : : vector < std : : string > PandaSpiHandle : : list ( ) {
try {
PandaSpiHandle sh ( " " ) ;
return { sh . hw_serial } ;
} catch ( std : : exception & e ) {
// no panda on SPI
}
return { } ;
}
void add_checksum ( uint8_t * data , int data_len ) {
data [ data_len ] = SPI_CHECKSUM_START ;
for ( int i = 0 ; i < data_len ; i + + ) {
data [ data_len ] ^ = data [ i ] ;
}
}
bool check_checksum ( uint8_t * data , int data_len ) {
uint8_t checksum = SPI_CHECKSUM_START ;
for ( uint16_t i = 0U ; i < data_len ; i + + ) {
checksum ^ = data [ i ] ;
}
return checksum = = 0U ;
}
int PandaSpiHandle : : spi_transfer_retry ( uint8_t endpoint , uint8_t * tx_data , uint16_t tx_len , uint8_t * rx_data , uint16_t max_rx_len , unsigned int timeout ) {
int ret ;
int nack_count = 0 ;
int timeout_count = 0 ;
bool timed_out = false ;
double start_time = millis_since_boot ( ) ;
do {
ret = spi_transfer ( endpoint , tx_data , tx_len , rx_data , max_rx_len , timeout ) ;
if ( ret < 0 ) {
timed_out = ( timeout ! = 0 ) & & ( timeout_count > 5 ) ;
timeout_count + = ret = = SpiError : : ACK_TIMEOUT ;
// give other threads a chance to run
std : : this_thread : : yield ( ) ;
if ( ret = = SpiError : : NACK ) {
// prevent busy waiting while the panda is NACK'ing
// due to full TX buffers
nack_count + = 1 ;
if ( nack_count > 3 ) {
SPILOG ( LOGD , " NACK sleep %d " , nack_count ) ;
usleep ( std : : clamp ( nack_count * 10 , 200 , 2000 ) ) ;
}
}
}
} while ( ret < 0 & & connected & & ! timed_out ) ;
if ( ret < 0 ) {
SPILOG ( LOGE , " transfer failed, after %d tries, %.2fms " , timeout_count , millis_since_boot ( ) - start_time ) ;
}
return ret ;
}
int PandaSpiHandle : : wait_for_ack ( uint8_t ack , uint8_t tx , unsigned int timeout , unsigned int length ) {
double start_millis = millis_since_boot ( ) ;
if ( timeout = = 0 ) {
timeout = SPI_ACK_TIMEOUT ;
}
timeout = std : : clamp ( timeout , 20U , SPI_ACK_TIMEOUT ) ;
spi_ioc_transfer transfer = {
. tx_buf = ( uint64_t ) tx_buf ,
. rx_buf = ( uint64_t ) rx_buf ,
. len = length ,
} ;
memset ( tx_buf , tx , length ) ;
while ( true ) {
int ret = lltransfer ( transfer ) ;
if ( ret < 0 ) {
SPILOG ( LOGE , " SPI: failed to send ACK request " ) ;
return ret ;
}
if ( rx_buf [ 0 ] = = ack ) {
break ;
} else if ( rx_buf [ 0 ] = = SPI_NACK ) {
SPILOG ( LOGD , " SPI: got NACK, waiting for 0x%x " , ack ) ;
return SpiError : : NACK ;
}
// handle timeout
if ( millis_since_boot ( ) - start_millis > timeout ) {
SPILOG ( LOGW , " SPI: timed out waiting for ACK, waiting for 0x%x " , ack ) ;
return SpiError : : ACK_TIMEOUT ;
}
}
return 0 ;
}
int PandaSpiHandle : : lltransfer ( spi_ioc_transfer & t ) {
static const double err_prob = std : : stod ( util : : getenv ( " SPI_ERR_PROB " , " -1 " ) ) ;
if ( err_prob > 0 ) {
if ( ( static_cast < double > ( rand ( ) ) / RAND_MAX ) < err_prob ) {
printf ( " transfer len error \n " ) ;
t . len = rand ( ) % SPI_BUF_SIZE ;
}
if ( ( static_cast < double > ( rand ( ) ) / RAND_MAX ) < err_prob & & t . tx_buf ! = ( uint64_t ) NULL ) {
printf ( " corrupting TX \n " ) ;
for ( int i = 0 ; i < t . len ; i + + ) {
if ( ( static_cast < double > ( rand ( ) ) / RAND_MAX ) > 0.9 ) {
( ( uint8_t * ) t . tx_buf ) [ i ] = ( uint8_t ) ( rand ( ) % 256 ) ;
}
}
}
}
int ret = util : : safe_ioctl ( spi_fd , SPI_IOC_MESSAGE ( 1 ) , & t ) ;
if ( err_prob > 0 ) {
if ( ( static_cast < double > ( rand ( ) ) / RAND_MAX ) < err_prob & & t . rx_buf ! = ( uint64_t ) NULL ) {
printf ( " corrupting RX \n " ) ;
for ( int i = 0 ; i < t . len ; i + + ) {
if ( ( static_cast < double > ( rand ( ) ) / RAND_MAX ) > 0.9 ) {
( ( uint8_t * ) t . rx_buf ) [ i ] = ( uint8_t ) ( rand ( ) % 256 ) ;
}
}
}
}
return ret ;
}
int PandaSpiHandle : : spi_transfer ( uint8_t endpoint , uint8_t * tx_data , uint16_t tx_len , uint8_t * rx_data , uint16_t max_rx_len , unsigned int timeout ) {
int ret ;
uint16_t rx_data_len ;
LockEx lock ( spi_fd , hw_lock ) ;
// needs to be less, since we need to have space for the checksum
assert ( tx_len < SPI_BUF_SIZE ) ;
assert ( max_rx_len < SPI_BUF_SIZE ) ;
xfer_count + + ;
header = {
. sync = SPI_SYNC ,
. endpoint = endpoint ,
. tx_len = tx_len ,
. max_rx_len = max_rx_len
} ;
spi_ioc_transfer transfer = {
. tx_buf = ( uint64_t ) tx_buf ,
. rx_buf = ( uint64_t ) rx_buf
} ;
// Send header
memcpy ( tx_buf , & header , sizeof ( header ) ) ;
add_checksum ( tx_buf , sizeof ( header ) ) ;
transfer . len = sizeof ( header ) + 1 ;
ret = lltransfer ( transfer ) ;
if ( ret < 0 ) {
SPILOG ( LOGE , " SPI: failed to send header " ) ;
goto fail ;
}
// Wait for (N)ACK
ret = wait_for_ack ( SPI_HACK , 0x11 , timeout , 1 ) ;
if ( ret < 0 ) {
goto fail ;
}
// Send data
if ( tx_data ! = NULL ) {
memcpy ( tx_buf , tx_data , tx_len ) ;
}
add_checksum ( tx_buf , tx_len ) ;
transfer . len = tx_len + 1 ;
ret = lltransfer ( transfer ) ;
if ( ret < 0 ) {
SPILOG ( LOGE , " SPI: failed to send data " ) ;
goto fail ;
}
// Wait for (N)ACK
ret = wait_for_ack ( SPI_DACK , 0x13 , timeout , 3 ) ;
if ( ret < 0 ) {
goto fail ;
}
// Read data
rx_data_len = * ( uint16_t * ) ( rx_buf + 1 ) ;
if ( rx_data_len > = SPI_BUF_SIZE ) {
SPILOG ( LOGE , " SPI: RX data len larger than buf size %d " , rx_data_len ) ;
goto fail ;
}
transfer . len = rx_data_len + 1 ;
transfer . rx_buf = ( uint64_t ) ( rx_buf + 2 + 1 ) ;
ret = lltransfer ( transfer ) ;
if ( ret < 0 ) {
SPILOG ( LOGE , " SPI: failed to read rx data " ) ;
goto fail ;
}
if ( ! check_checksum ( rx_buf , rx_data_len + 4 ) ) {
SPILOG ( LOGE , " SPI: bad checksum " ) ;
goto fail ;
}
if ( rx_data ! = NULL ) {
memcpy ( rx_data , rx_buf + 3 , rx_data_len ) ;
}
return rx_data_len ;
fail :
// ensure slave is in a consistent state
// and ready for the next transfer
int nack_cnt = 0 ;
while ( nack_cnt < 3 ) {
if ( wait_for_ack ( SPI_NACK , 0x14 , 1 , SPI_BUF_SIZE / 2 ) = = 0 ) {
nack_cnt + = 1 ;
} else {
nack_cnt = 0 ;
}
}
if ( ret > = 0 ) ret = - 1 ;
return ret ;
}
# endif