boardd: SPI support (#26374)
* spi handle
* put usb back
* handle eintr
Co-authored-by: Comma Device <device@comma.ai>
old-commit-hash: 8ba9a5107b
taco
parent
45dec4e84f
commit
461206de1c
8 changed files with 295 additions and 3 deletions
@ -1,9 +1,9 @@ |
||||
Import('env', 'envCython', 'common', 'cereal', 'messaging') |
||||
|
||||
libs = ['usb-1.0', common, cereal, messaging, 'pthread', 'zmq', 'capnp', 'kj'] |
||||
env.Program('boardd', ['main.cc', 'boardd.cc', 'panda.cc', 'panda_comms.cc'], LIBS=libs) |
||||
env.Program('boardd', ['main.cc', 'boardd.cc', 'panda.cc', 'panda_comms.cc', 'spi.cc'], LIBS=libs) |
||||
env.Library('libcan_list_to_can_capnp', ['can_list_to_can_capnp.cc']) |
||||
|
||||
envCython.Program('boardd_api_impl.so', 'boardd_api_impl.pyx', LIBS=["can_list_to_can_capnp", 'capnp', 'kj'] + envCython["LIBS"]) |
||||
if GetOption('test'): |
||||
env.Program('tests/test_boardd_usbprotocol', ['tests/test_boardd_usbprotocol.cc', 'panda.cc', 'panda_comms.cc'], LIBS=libs) |
||||
env.Program('tests/test_boardd_usbprotocol', ['tests/test_boardd_usbprotocol.cc', 'panda.cc', 'panda_comms.cc', 'spi.cc'], LIBS=libs) |
||||
|
@ -0,0 +1,252 @@ |
||||
#include <sys/ioctl.h> |
||||
#include <linux/spi/spidev.h> |
||||
|
||||
#include <cassert> |
||||
#include <cstring> |
||||
|
||||
#include "common/util.h" |
||||
#include "common/swaglog.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; |
||||
}; |
||||
|
||||
struct __attribute__((packed)) spi_control_packet { |
||||
uint16_t request; |
||||
uint16_t param1; |
||||
uint16_t param2; |
||||
uint16_t length; |
||||
}; |
||||
|
||||
|
||||
PandaSpiHandle::PandaSpiHandle(std::string serial) : PandaCommsHandle(serial) { |
||||
LOGD("opening SPI panda: %s", serial.c_str()); |
||||
|
||||
int err; |
||||
uint32_t spi_mode = SPI_MODE_0; |
||||
uint32_t spi_speed = 30000000; |
||||
uint8_t spi_bits_per_word = 8; |
||||
|
||||
spi_fd = open(serial.c_str(), O_RDWR); |
||||
if (spi_fd < 0) { |
||||
LOGE("failed setting SPI mode %d", err); |
||||
goto fail; |
||||
} |
||||
|
||||
// SPI settings
|
||||
err = util::safe_ioctl(spi_fd, SPI_IOC_WR_MODE, &spi_mode); |
||||
if (err < 0) { |
||||
LOGE("failed setting SPI mode %d", err); |
||||
goto fail; |
||||
} |
||||
|
||||
err = util::safe_ioctl(spi_fd, SPI_IOC_WR_MAX_SPEED_HZ, &spi_speed); |
||||
if (err < 0) { |
||||
LOGE("failed setting SPI speed"); |
||||
goto fail; |
||||
} |
||||
|
||||
err = util::safe_ioctl(spi_fd, SPI_IOC_WR_BITS_PER_WORD, &spi_bits_per_word); |
||||
if (err < 0) { |
||||
LOGE("failed setting SPI bits per word"); |
||||
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) { |
||||
int err; |
||||
|
||||
std::lock_guard lk(hw_lock); |
||||
do { |
||||
spi_control_packet packet = { |
||||
.request = request, |
||||
.param1 = param1, |
||||
.param2 = param2, |
||||
.length = 0 |
||||
}; |
||||
|
||||
// TODO: handle error
|
||||
err = spi_transfer(0, (uint8_t *) &packet, sizeof(packet), NULL, 0); |
||||
} while (err < 0 && connected); |
||||
|
||||
return err; |
||||
} |
||||
|
||||
int PandaSpiHandle::control_read(uint8_t request, uint16_t param1, uint16_t param2, unsigned char *data, uint16_t length, unsigned int timeout) { |
||||
int err; |
||||
|
||||
std::lock_guard lk(hw_lock); |
||||
do { |
||||
spi_control_packet packet = { |
||||
.request = request, |
||||
.param1 = param1, |
||||
.param2 = param2, |
||||
.length = length |
||||
}; |
||||
|
||||
// TODO: handle error
|
||||
err = spi_transfer(0, (uint8_t *) &packet, sizeof(packet), data, length); |
||||
} while (err < 0 && connected); |
||||
|
||||
return err; |
||||
} |
||||
|
||||
int PandaSpiHandle::bulk_write(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout) { |
||||
return 0; |
||||
} |
||||
|
||||
int PandaSpiHandle::bulk_read(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout) { |
||||
return 0; |
||||
} |
||||
|
||||
std::vector<std::string> PandaSpiHandle::list() { |
||||
// TODO: list all pandas available over 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]; |
||||
} |
||||
} |
||||
|
||||
|
||||
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; |
||||
while (true) { |
||||
ret = util::safe_ioctl(spi_fd, SPI_IOC_MESSAGE(1), &transfer); |
||||
if (ret < 0) { |
||||
LOGE("SPI: failed to send ACK request"); |
||||
goto transfer_fail; |
||||
} |
||||
|
||||
if (rx_buf[0] == SPI_HACK) { |
||||
break; |
||||
} else if (rx_buf[0] == SPI_NACK) { |
||||
LOGW("SPI: got header NACK"); |
||||
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; |
||||
while (true) { |
||||
ret = util::safe_ioctl(spi_fd, SPI_IOC_MESSAGE(1), &transfer); |
||||
if (ret < 0) { |
||||
LOGE("SPI: failed to send ACK request"); |
||||
goto transfer_fail; |
||||
} |
||||
|
||||
if (rx_buf[0] == SPI_DACK) { |
||||
break; |
||||
} else if (rx_buf[0] == SPI_NACK) { |
||||
LOGE("SPI: got data NACK"); |
||||
goto transfer_fail; |
||||
} |
||||
} |
||||
|
||||
// Read data len
|
||||
transfer.len = 2; |
||||
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; |
||||
assert(rx_data_len < SPI_BUF_SIZE); |
||||
|
||||
// Read data
|
||||
transfer.len = rx_data_len + 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; |
||||
} |
||||
// TODO: check checksum
|
||||
|
||||
if (rx_data != NULL) { |
||||
memcpy(rx_data, rx_buf, rx_data_len); |
||||
} |
||||
ret = rx_data_len; |
||||
|
||||
transfer_fail: |
||||
return ret; |
||||
} |
Loading…
Reference in new issue