#ifndef __APPLE__ #include #include #include #include #include #include #include #include #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 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); assert(rx_data_len < SPI_BUF_SIZE); // 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