# include <stdio.h>
# include <stdint.h>
# include <stdlib.h>
# include <string.h>
# include <signal.h>
# include <unistd.h>
# include <sched.h>
# include <sys/time.h>
# include <sys/cdefs.h>
# include <sys/types.h>
# include <sys/time.h>
# include <sys/resource.h>
# include <assert.h>
# include <pthread.h>
# include <zmq.h>
# include <libusb.h>
# include <capnp/serialize.h>
# include "cereal/gen/cpp/log.capnp.h"
# include "common/swaglog.h"
# include "common/timing.h"
int do_exit = 0 ;
libusb_context * ctx = NULL ;
libusb_device_handle * dev_handle ;
pthread_mutex_t usb_lock ;
bool spoofing_started = false ;
bool fake_send = false ;
bool loopback_can = false ;
// double the FIFO size
# define RECV_SIZE (0x1000)
# define TIMEOUT 0
# define SAFETY_NOOUTPUT 0x0000
# define SAFETY_HONDA 0x0001
# define SAFETY_ALLOUTPUT 0x1337
bool usb_connect ( ) {
int err ;
dev_handle = libusb_open_device_with_vid_pid ( ctx , 0xbbaa , 0xddcc ) ;
if ( dev_handle = = NULL ) { return false ; }
err = libusb_set_configuration ( dev_handle , 1 ) ;
if ( err ! = 0 ) { return false ; }
err = libusb_claim_interface ( dev_handle , 0 ) ;
if ( err ! = 0 ) { return false ; }
if ( loopback_can ) {
libusb_control_transfer ( dev_handle , 0xc0 , 0xe5 , 1 , 0 , NULL , 0 , TIMEOUT ) ;
}
// power off ESP
libusb_control_transfer ( dev_handle , 0xc0 , 0xd9 , 0 , 0 , NULL , 0 , TIMEOUT ) ;
// forward CAN1 to CAN3...soon
//libusb_control_transfer(dev_handle, 0xc0, 0xdd, 1, 2, NULL, 0, TIMEOUT);
// set UART modes for Honda Accord
for ( int uart = 2 ; uart < = 3 ; uart + + ) {
// 9600 baud
libusb_control_transfer ( dev_handle , 0x40 , 0xe1 , uart , 9600 , NULL , 0 , TIMEOUT ) ;
// even parity
libusb_control_transfer ( dev_handle , 0x40 , 0xe2 , uart , 1 , NULL , 0 , TIMEOUT ) ;
// callback 1
libusb_control_transfer ( dev_handle , 0x40 , 0xe3 , uart , 1 , NULL , 0 , TIMEOUT ) ;
}
// TODO: Boardd should be able to set the baud rate
int baud = 500000 ;
libusb_control_transfer ( dev_handle , 0x40 , 0xde , 0 , 0 ,
( unsigned char * ) & baud , sizeof ( baud ) , TIMEOUT ) ; // CAN1
libusb_control_transfer ( dev_handle , 0x40 , 0xde , 1 , 0 ,
( unsigned char * ) & baud , sizeof ( baud ) , TIMEOUT ) ; // CAN2
// TODO: Boardd should be able to be told which safety model to use
libusb_control_transfer ( dev_handle , 0x40 , 0xdc , SAFETY_HONDA , 0 , NULL , 0 , TIMEOUT ) ;
return true ;
}
void usb_retry_connect ( ) {
LOG ( " attempting to connect " ) ;
while ( ! usb_connect ( ) ) { usleep ( 100 * 1000 ) ; }
LOGW ( " connected to board " ) ;
}
void handle_usb_issue ( int err , const char func [ ] ) {
LOGE ( " usb error %d \" %s \" in %s " , err , libusb_strerror ( ( enum libusb_error ) err ) , func ) ;
if ( err = = - 4 ) {
LOGE ( " lost connection " ) ;
usb_retry_connect ( ) ;
}
// TODO: check other errors, is simply retrying okay?
}
void can_recv ( void * s ) {
int err ;
uint32_t data [ RECV_SIZE / 4 ] ;
int recv ;
uint32_t f1 , f2 ;
// do recv
pthread_mutex_lock ( & usb_lock ) ;
do {
err = libusb_bulk_transfer ( dev_handle , 0x81 , ( uint8_t * ) data , RECV_SIZE , & recv , TIMEOUT ) ;
if ( err ! = 0 ) { handle_usb_issue ( err , __func__ ) ; }
if ( err = = - 8 ) { LOGE ( " overflow got 0x%x " , recv ) ; } ;
// timeout is okay to exit, recv still happened
if ( err = = - 7 ) { break ; }
} while ( err ! = 0 ) ;
pthread_mutex_unlock ( & usb_lock ) ;
// return if length is 0
if ( recv < = 0 ) {
return ;
}
// create message
capnp : : MallocMessageBuilder msg ;
cereal : : Event : : Builder event = msg . initRoot < cereal : : Event > ( ) ;
event . setLogMonoTime ( nanos_since_boot ( ) ) ;
auto canData = event . initCan ( recv / 0x10 ) ;
// populate message
for ( int i = 0 ; i < ( recv / 0x10 ) ; i + + ) {
if ( data [ i * 4 ] & 4 ) {
// extended
canData [ i ] . setAddress ( data [ i * 4 ] > > 3 ) ;
//printf("got extended: %x\n", data[i*4] >> 3);
} else {
// normal
canData [ i ] . setAddress ( data [ i * 4 ] > > 21 ) ;
}
canData [ i ] . setBusTime ( data [ i * 4 + 1 ] > > 16 ) ;
int len = data [ i * 4 + 1 ] & 0xF ;
canData [ i ] . setDat ( kj : : arrayPtr ( ( uint8_t * ) & data [ i * 4 + 2 ] , len ) ) ;
canData [ i ] . setSrc ( ( data [ i * 4 + 1 ] > > 4 ) & 0xff ) ;
}
// send to can
auto words = capnp : : messageToFlatArray ( msg ) ;
auto bytes = words . asBytes ( ) ;
zmq_send ( s , bytes . begin ( ) , bytes . size ( ) , 0 ) ;
}
void can_health ( void * s ) {
int cnt ;
// copied from board/main.c
struct __attribute__ ( ( packed ) ) health {
uint32_t voltage ;
uint32_t current ;
uint8_t started ;
uint8_t controls_allowed ;
uint8_t gas_interceptor_detected ;
uint8_t started_signal_detected ;
uint8_t started_alt ;
} health ;
// recv from board
pthread_mutex_lock ( & usb_lock ) ;
do {
cnt = libusb_control_transfer ( dev_handle , 0xc0 , 0xd2 , 0 , 0 , ( unsigned char * ) & health , sizeof ( health ) , TIMEOUT ) ;
if ( cnt ! = sizeof ( health ) ) { handle_usb_issue ( cnt , __func__ ) ; }
} while ( cnt ! = sizeof ( health ) ) ;
pthread_mutex_unlock ( & usb_lock ) ;
// create message
capnp : : MallocMessageBuilder msg ;
cereal : : Event : : Builder event = msg . initRoot < cereal : : Event > ( ) ;
event . setLogMonoTime ( nanos_since_boot ( ) ) ;
auto healthData = event . initHealth ( ) ;
// set fields
healthData . setVoltage ( health . voltage ) ;
healthData . setCurrent ( health . current ) ;
if ( spoofing_started ) {
healthData . setStarted ( 1 ) ;
} else {
healthData . setStarted ( health . started ) ;
}
healthData . setControlsAllowed ( health . controls_allowed ) ;
healthData . setGasInterceptorDetected ( health . gas_interceptor_detected ) ;
healthData . setStartedSignalDetected ( health . started_signal_detected ) ;
// send to health
auto words = capnp : : messageToFlatArray ( msg ) ;
auto bytes = words . asBytes ( ) ;
zmq_send ( s , bytes . begin ( ) , bytes . size ( ) , 0 ) ;
}
void can_send ( void * s ) {
int err ;
// recv from sendcan
zmq_msg_t msg ;
zmq_msg_init ( & msg ) ;
err = zmq_msg_recv ( & msg , s , 0 ) ;
assert ( err > = 0 ) ;
// format for board, make copy due to alignment issues, will be freed on out of scope
auto amsg = kj : : heapArray < capnp : : word > ( ( zmq_msg_size ( & msg ) / sizeof ( capnp : : word ) ) + 1 ) ;
memcpy ( amsg . begin ( ) , zmq_msg_data ( & msg ) , zmq_msg_size ( & msg ) ) ;
capnp : : FlatArrayMessageReader cmsg ( amsg ) ;
cereal : : Event : : Reader event = cmsg . getRoot < cereal : : Event > ( ) ;
int msg_count = event . getCan ( ) . size ( ) ;
uint32_t * send = ( uint32_t * ) malloc ( msg_count * 0x10 ) ;
memset ( send , 0 , msg_count * 0x10 ) ;
for ( int i = 0 ; i < msg_count ; i + + ) {
auto cmsg = event . getSendcan ( ) [ i ] ;
if ( cmsg . getAddress ( ) > = 0x800 ) {
// extended
send [ i * 4 ] = ( cmsg . getAddress ( ) < < 3 ) | 5 ;
} else {
// normal
send [ i * 4 ] = ( cmsg . getAddress ( ) < < 21 ) | 1 ;
}
assert ( cmsg . getDat ( ) . size ( ) < = 8 ) ;
send [ i * 4 + 1 ] = cmsg . getDat ( ) . size ( ) | ( cmsg . getSrc ( ) < < 4 ) ;
memcpy ( & send [ i * 4 + 2 ] , cmsg . getDat ( ) . begin ( ) , cmsg . getDat ( ) . size ( ) ) ;
}
// release msg
zmq_msg_close ( & msg ) ;
// send to board
int sent ;
pthread_mutex_lock ( & usb_lock ) ;
if ( ! fake_send ) {
do {
err = libusb_bulk_transfer ( dev_handle , 3 , ( uint8_t * ) send , msg_count * 0x10 , & sent , TIMEOUT ) ;
if ( err ! = 0 | | msg_count * 0x10 ! = sent ) { handle_usb_issue ( err , __func__ ) ; }
} while ( err ! = 0 ) ;
}
pthread_mutex_unlock ( & usb_lock ) ;
// done
free ( send ) ;
}
// **** threads ****
void * can_send_thread ( void * crap ) {
LOGD ( " start send thread " ) ;
// sendcan = 8017
void * context = zmq_ctx_new ( ) ;
void * subscriber = zmq_socket ( context , ZMQ_SUB ) ;
zmq_setsockopt ( subscriber , ZMQ_SUBSCRIBE , " " , 0 ) ;
zmq_connect ( subscriber , " tcp://127.0.0.1:8017 " ) ;
// run as fast as messages come in
while ( ! do_exit ) {
can_send ( subscriber ) ;
}
return NULL ;
}
void * can_recv_thread ( void * crap ) {
LOGD ( " start recv thread " ) ;
// can = 8006
void * context = zmq_ctx_new ( ) ;
void * publisher = zmq_socket ( context , ZMQ_PUB ) ;
zmq_bind ( publisher , " tcp://*:8006 " ) ;
// run at ~200hz
while ( ! do_exit ) {
can_recv ( publisher ) ;
// 5ms
usleep ( 5 * 1000 ) ;
}
return NULL ;
}
void * can_health_thread ( void * crap ) {
LOGD ( " start health thread " ) ;
// health = 8011
void * context = zmq_ctx_new ( ) ;
void * publisher = zmq_socket ( context , ZMQ_PUB ) ;
zmq_bind ( publisher , " tcp://*:8011 " ) ;
// run at 1hz
while ( ! do_exit ) {
can_health ( publisher ) ;
usleep ( 1000 * 1000 ) ;
}
return NULL ;
}
int set_realtime_priority ( int level ) {
// should match python using chrt
struct sched_param sa ;
memset ( & sa , 0 , sizeof ( sa ) ) ;
sa . sched_priority = level ;
return sched_setscheduler ( getpid ( ) , SCHED_FIFO , & sa ) ;
}
int main ( ) {
int err ;
LOGW ( " starting boardd " ) ;
// set process priority
err = set_realtime_priority ( 4 ) ;
LOG ( " setpriority returns %d " , err ) ;
// check the environment
if ( getenv ( " STARTED " ) ) {
spoofing_started = true ;
}
if ( getenv ( " FAKESEND " ) ) {
fake_send = true ;
}
if ( getenv ( " BOARDD_LOOPBACK " ) ) {
loopback_can = true ;
}
// init libusb
err = libusb_init ( & ctx ) ;
assert ( err = = 0 ) ;
libusb_set_debug ( ctx , 3 ) ;
// connect to the board
usb_retry_connect ( ) ;
// create threads
pthread_t can_health_thread_handle ;
err = pthread_create ( & can_health_thread_handle , NULL ,
can_health_thread , NULL ) ;
assert ( err = = 0 ) ;
pthread_t can_send_thread_handle ;
err = pthread_create ( & can_send_thread_handle , NULL ,
can_send_thread , NULL ) ;
assert ( err = = 0 ) ;
pthread_t can_recv_thread_handle ;
err = pthread_create ( & can_recv_thread_handle , NULL ,
can_recv_thread , NULL ) ;
assert ( err = = 0 ) ;
// join threads
err = pthread_join ( can_recv_thread_handle , NULL ) ;
assert ( err = = 0 ) ;
err = pthread_join ( can_send_thread_handle , NULL ) ;
assert ( err = = 0 ) ;
err = pthread_join ( can_health_thread_handle , NULL ) ;
assert ( err = = 0 ) ;
// destruct libusb
libusb_close ( dev_handle ) ;
libusb_exit ( ctx ) ;
}