# 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/boardd/panda_comms.h"
# define SPI_SYNC 0x5AU
# define SPI_HACK 0x79U
# define SPI_DACK 0x85U
# define SPI_NACK 0x1FU
# define SPI_CHECKSUM_START 0xABU
struct __attribute__ ( ( packed ) ) spi_header {
uint8_t sync ;
uint8_t endpoint ;
uint16_t tx_len ;
uint16_t max_rx_len ;
} ;
const int SPI_MAX_RETRIES = 5 ;
const int SPI_ACK_TIMEOUT = 50 ; // 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 ( ) {
m . unlock ( ) ;
flock ( fd , LOCK_UN ) ;
}
private :
int fd ;
std : : recursive_mutex & m ;
} ;
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 ;
uint32_t spi_speed = 30000000 ;
uint8_t spi_bits_per_word = 8 ;
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 ) ;
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 ) {
LockEx lock ( spi_fd , hw_lock ) ;
ControlPacket_t packet = {
. request = request ,
. param1 = param1 ,
. param2 = param2 ,
. length = 0
} ;
return spi_transfer_retry ( 0 , ( uint8_t * ) & packet , sizeof ( packet ) , NULL , 0 ) ;
}
int PandaSpiHandle : : control_read ( uint8_t request , uint16_t param1 , uint16_t param2 , unsigned char * data , uint16_t length , unsigned int timeout ) {
LockEx lock ( spi_fd , hw_lock ) ;
ControlPacket_t packet = {
. request = request ,
. param1 = param1 ,
. param2 = param2 ,
. length = length
} ;
return spi_transfer_retry ( 0 , ( uint8_t * ) & packet , sizeof ( packet ) , data , length ) ;
}
int PandaSpiHandle : : bulk_write ( unsigned char endpoint , unsigned char * data , int length , unsigned int timeout ) {
LockEx lock ( spi_fd , hw_lock ) ;
return bulk_transfer ( endpoint , data , length , NULL , 0 ) ;
}
int PandaSpiHandle : : bulk_read ( unsigned char endpoint , unsigned char * data , int length , unsigned int timeout ) {
LockEx lock ( spi_fd , hw_lock ) ;
return bulk_transfer ( endpoint , NULL , 0 , data , length ) ;
}
int PandaSpiHandle : : bulk_transfer ( uint8_t endpoint , uint8_t * tx_data , uint16_t tx_len , uint8_t * rx_data , uint16_t rx_len ) {
const int xfer_size = 0x40 * 15 ;
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 ) ;
} 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 ) ;
}
if ( d < 0 ) {
LOGE ( " SPI: bulk transfer failed with %d " , d ) ;
comms_healthy = false ;
return - 1 ;
}
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 ) {
int ret ;
int count = SPI_MAX_RETRIES ;
do {
// TODO: handle error
ret = spi_transfer ( endpoint , tx_data , tx_len , rx_data , max_rx_len ) ;
count - - ;
} while ( ret < 0 & & connected & & count > 0 ) ;
return ret ;
}
int PandaSpiHandle : : wait_for_ack ( spi_ioc_transfer & transfer , uint8_t ack ) {
double start_millis = millis_since_boot ( ) ;
while ( true ) {
int ret = util : : safe_ioctl ( spi_fd , SPI_IOC_MESSAGE ( 1 ) , & transfer ) ;
if ( ret < 0 ) {
LOGE ( " SPI: failed to send ACK request " ) ;
return ret ;
}
if ( rx_buf [ 0 ] = = ack ) {
break ;
} else if ( rx_buf [ 0 ] = = SPI_NACK ) {
LOGW ( " SPI: got NACK " ) ;
return - 1 ;
}
// handle timeout
if ( millis_since_boot ( ) - start_millis > SPI_ACK_TIMEOUT ) {
LOGD ( " SPI: timed out waiting for ACK " ) ;
return - 1 ;
}
}
return 0 ;
}
int PandaSpiHandle : : spi_transfer ( uint8_t endpoint , uint8_t * tx_data , uint16_t tx_len , uint8_t * rx_data , uint16_t max_rx_len ) {
int ret ;
uint16_t rx_data_len ;
// 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 ) ;
spi_header 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 = util : : safe_ioctl ( spi_fd , SPI_IOC_MESSAGE ( 1 ) , & transfer ) ;
if ( ret < 0 ) {
LOGE ( " SPI: failed to send header " ) ;
goto transfer_fail ;
}
// Wait for (N)ACK
tx_buf [ 0 ] = 0x12 ;
transfer . len = 1 ;
ret = wait_for_ack ( transfer , SPI_HACK ) ;
if ( ret < 0 ) {
goto transfer_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 = util : : safe_ioctl ( spi_fd , SPI_IOC_MESSAGE ( 1 ) , & transfer ) ;
if ( ret < 0 ) {
LOGE ( " SPI: failed to send data " ) ;
goto transfer_fail ;
}
// Wait for (N)ACK
tx_buf [ 0 ] = 0xab ;
transfer . len = 1 ;
ret = wait_for_ack ( transfer , SPI_DACK ) ;
if ( ret < 0 ) {
goto transfer_fail ;
}
// Read data len
transfer . len = 2 ;
transfer . rx_buf = ( uint64_t ) ( rx_buf + 1 ) ;
ret = util : : safe_ioctl ( spi_fd , SPI_IOC_MESSAGE ( 1 ) , & transfer ) ;
if ( ret < 0 ) {
LOGE ( " SPI: failed to read rx data len " ) ;
goto transfer_fail ;
}
rx_data_len = * ( uint16_t * ) ( rx_buf + 1 ) ;
if ( rx_data_len > = SPI_BUF_SIZE ) {
LOGE ( " SPI: RX data len larger than buf size %d " , rx_data_len ) ;
goto transfer_fail ;
}
// Read data
transfer . len = rx_data_len + 1 ;
transfer . rx_buf = ( uint64_t ) ( rx_buf + 2 + 1 ) ;
ret = util : : safe_ioctl ( spi_fd , SPI_IOC_MESSAGE ( 1 ) , & transfer ) ;
if ( ret < 0 ) {
LOGE ( " SPI: failed to read rx data " ) ;
goto transfer_fail ;
}
if ( ! check_checksum ( rx_buf , rx_data_len + 4 ) ) {
LOGE ( " SPI: bad checksum " ) ;
goto transfer_fail ;
}
if ( rx_data ! = NULL ) {
memcpy ( rx_data , rx_buf + 3 , rx_data_len ) ;
}
return rx_data_len ;
transfer_fail :
return ret ;
}
# endif