From 88b30e1a8487381b171b9e8f04900d088c3ac5a1 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 30 Nov 2022 22:12:28 -0800 Subject: [PATCH] CAN comms test (#1181) * CAN comms test * remove those * cleanup * little more --- .github/workflows/test.yaml | 4 +- __init__.py | 2 +- board/can_comms.h | 110 +++++++++++++++++ board/fake_stm.h | 5 +- board/main.c | 1 + board/main_comms.h | 111 ------------------ python/__init__.py | 1 + tests/SConscript | 12 ++ tests/libpanda/SConscript | 5 +- tests/libpanda/libpanda_py.py | 21 ++++ tests/libpanda/panda.c | 32 +++++ .../{safety_helpers.c => safety_helpers.h} | 9 -- tests/usbprotocol/test.sh | 8 ++ tests/usbprotocol/test_comms.py | 99 ++++++++++++++++ tests/usbprotocol/test_pandalib.py | 5 +- 15 files changed, 297 insertions(+), 128 deletions(-) create mode 100644 board/can_comms.h create mode 100644 tests/SConscript create mode 100644 tests/libpanda/panda.c rename tests/libpanda/{safety_helpers.c => safety_helpers.h} (94%) create mode 100755 tests/usbprotocol/test.sh create mode 100644 tests/usbprotocol/test_comms.py diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 0b85c7ef3d..79640431ad 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -53,8 +53,10 @@ jobs: - uses: actions/checkout@v2 - name: Build Docker image run: eval "$BUILD" + - name: Build panda + run: $RUN "scons -j4" - name: Test communication protocols - run: $RUN "cd tests/usbprotocol && python -m unittest discover ." + run: $RUN "cd tests/usbprotocol && ./test.sh" safety: name: safety diff --git a/__init__.py b/__init__.py index b8d73caf18..12ca88ef10 100644 --- a/__init__.py +++ b/__init__.py @@ -1,7 +1,7 @@ from .python import (Panda, PandaDFU, # noqa: F401 BASEDIR, pack_can_buffer, unpack_can_buffer, DEFAULT_FW_FN, DEFAULT_H7_FW_FN, MCU_TYPE_H7, MCU_TYPE_F4, DLC_TO_LEN, LEN_TO_DLC, - ALTERNATIVE_EXPERIENCE) + ALTERNATIVE_EXPERIENCE, USBPACKET_MAX_SIZE) from .python.serial import PandaSerial # noqa: F401 diff --git a/board/can_comms.h b/board/can_comms.h new file mode 100644 index 0000000000..623872cb0a --- /dev/null +++ b/board/can_comms.h @@ -0,0 +1,110 @@ +typedef struct { + uint32_t ptr; + uint32_t tail_size; + uint8_t data[72]; + uint8_t counter; +} asm_buffer; + +asm_buffer can_read_buffer = {.ptr = 0U, .tail_size = 0U, .counter = 0U}; +uint32_t total_rx_size = 0U; + +int comms_can_read(uint8_t *data, uint32_t max_len) { + uint32_t pos = 1; + data[0] = can_read_buffer.counter; + // Send tail of previous message if it is in buffer + if (can_read_buffer.ptr > 0U) { + if (can_read_buffer.ptr <= 63U) { + (void)memcpy(&data[pos], can_read_buffer.data, can_read_buffer.ptr); + pos += can_read_buffer.ptr; + can_read_buffer.ptr = 0U; + } else { + (void)memcpy(&data[pos], can_read_buffer.data, 63U); + can_read_buffer.ptr = can_read_buffer.ptr - 63U; + (void)memcpy(can_read_buffer.data, &can_read_buffer.data[63], can_read_buffer.ptr); + pos += 63U; + } + } + + if (total_rx_size > MAX_EP1_CHUNK_PER_BULK_TRANSFER) { + total_rx_size = 0U; + can_read_buffer.counter = 0U; + } else { + CANPacket_t can_packet; + while ((pos < max_len) && can_pop(&can_rx_q, &can_packet)) { + uint32_t pckt_len = CANPACKET_HEAD_SIZE + dlc_to_len[can_packet.data_len_code]; + if ((pos + pckt_len) <= max_len) { + (void)memcpy(&data[pos], &can_packet, pckt_len); + pos += pckt_len; + } else { + (void)memcpy(&data[pos], &can_packet, max_len - pos); + can_read_buffer.ptr = pckt_len - (max_len - pos); + // cppcheck-suppress objectIndex + (void)memcpy(can_read_buffer.data, &((uint8_t*)&can_packet)[(max_len - pos)], can_read_buffer.ptr); + pos = max_len; + } + } + can_read_buffer.counter++; + total_rx_size += pos; + } + if (pos != max_len) { + can_read_buffer.counter = 0U; + total_rx_size = 0U; + } + if (pos <= 1U) { pos = 0U; } + return pos; +} + +asm_buffer can_write_buffer = {.ptr = 0U, .tail_size = 0U, .counter = 0U}; + +// send on CAN +void comms_can_write(uint8_t *data, uint32_t len) { + // Got first packet from a stream, resetting buffer and counter + if (data[0] == 0U) { + can_write_buffer.counter = 0U; + can_write_buffer.ptr = 0U; + can_write_buffer.tail_size = 0U; + } + // Assembling can message with data from buffer + if (data[0] == can_write_buffer.counter) { + uint32_t pos = 1U; + can_write_buffer.counter++; + if (can_write_buffer.ptr != 0U) { + if (can_write_buffer.tail_size <= 63U) { + CANPacket_t to_push; + (void)memcpy(&can_write_buffer.data[can_write_buffer.ptr], &data[pos], can_write_buffer.tail_size); + (void)memcpy(&to_push, can_write_buffer.data, can_write_buffer.ptr + can_write_buffer.tail_size); + can_send(&to_push, to_push.bus, false); + pos += can_write_buffer.tail_size; + can_write_buffer.ptr = 0U; + can_write_buffer.tail_size = 0U; + } else { + (void)memcpy(&can_write_buffer.data[can_write_buffer.ptr], &data[pos], len - pos); + can_write_buffer.tail_size -= 63U; + can_write_buffer.ptr += 63U; + pos += 63U; + } + } + + while (pos < len) { + uint32_t pckt_len = CANPACKET_HEAD_SIZE + dlc_to_len[(data[pos] >> 4U)]; + if ((pos + pckt_len) <= len) { + CANPacket_t to_push; + (void)memcpy(&to_push, &data[pos], pckt_len); + can_send(&to_push, to_push.bus, false); + pos += pckt_len; + } else { + (void)memcpy(can_write_buffer.data, &data[pos], len - pos); + can_write_buffer.ptr = len - pos; + can_write_buffer.tail_size = pckt_len - can_write_buffer.ptr; + pos += can_write_buffer.ptr; + } + } + } +} + +// TODO: make this more general! +void usb_cb_ep3_out_complete(void) { + if (can_tx_check_min_slots_free(MAX_CAN_MSGS_PER_BULK_TRANSFER)) { + usb_outep3_resume_if_paused(); + } +} diff --git a/board/fake_stm.h b/board/fake_stm.h index d1e14ee96f..b73a4e8985 100644 --- a/board/fake_stm.h +++ b/board/fake_stm.h @@ -9,8 +9,11 @@ #define ALLOW_DEBUG #define PANDA +#define ENTER_CRITICAL() 0 +#define EXIT_CRITICAL() 0 + void print(const char *a) { - printf(a); + printf("%s", a); } void puth(unsigned int i) { diff --git a/board/main.c b/board/main.c index e4d34bf1da..52f12eca55 100644 --- a/board/main.c +++ b/board/main.c @@ -24,6 +24,7 @@ #include "obj/gitversion.h" +#include "can_comms.h" #include "main_comms.h" diff --git a/board/main_comms.h b/board/main_comms.h index f1e0fc32d8..72763d8c61 100644 --- a/board/main_comms.h +++ b/board/main_comms.h @@ -47,110 +47,6 @@ int get_rtc_pkt(void *dat) { return sizeof(t); } -typedef struct { - uint32_t ptr; - uint32_t tail_size; - uint8_t data[72]; - uint8_t counter; -} asm_buffer; - -asm_buffer can_read_buffer = {.ptr = 0U, .tail_size = 0U, .counter = 0U}; -uint32_t total_rx_size = 0U; - -int comms_can_read(uint8_t *data, uint32_t max_len) { - uint32_t pos = 1; - data[0] = can_read_buffer.counter; - // Send tail of previous message if it is in buffer - if (can_read_buffer.ptr > 0U) { - if (can_read_buffer.ptr <= 63U) { - (void)memcpy(&data[pos], can_read_buffer.data, can_read_buffer.ptr); - pos += can_read_buffer.ptr; - can_read_buffer.ptr = 0U; - } else { - (void)memcpy(&data[pos], can_read_buffer.data, 63U); - can_read_buffer.ptr = can_read_buffer.ptr - 63U; - (void)memcpy(can_read_buffer.data, &can_read_buffer.data[63], can_read_buffer.ptr); - pos += 63U; - } - } - - if (total_rx_size > MAX_EP1_CHUNK_PER_BULK_TRANSFER) { - total_rx_size = 0U; - can_read_buffer.counter = 0U; - } else { - CANPacket_t can_packet; - while ((pos < max_len) && can_pop(&can_rx_q, &can_packet)) { - uint32_t pckt_len = CANPACKET_HEAD_SIZE + dlc_to_len[can_packet.data_len_code]; - if ((pos + pckt_len) <= max_len) { - (void)memcpy(&data[pos], &can_packet, pckt_len); - pos += pckt_len; - } else { - (void)memcpy(&data[pos], &can_packet, max_len - pos); - can_read_buffer.ptr = pckt_len - (max_len - pos); - // cppcheck-suppress objectIndex - (void)memcpy(can_read_buffer.data, &((uint8_t*)&can_packet)[(max_len - pos)], can_read_buffer.ptr); - pos = max_len; - } - } - can_read_buffer.counter++; - total_rx_size += pos; - } - if (pos != max_len) { - can_read_buffer.counter = 0U; - total_rx_size = 0U; - } - if (pos <= 1U) { pos = 0U; } - return pos; -} - -asm_buffer can_write_buffer = {.ptr = 0U, .tail_size = 0U, .counter = 0U}; - -// send on CAN -void comms_can_write(uint8_t *data, uint32_t len) { - // Got first packet from a stream, resetting buffer and counter - if (data[0] == 0U) { - can_write_buffer.counter = 0U; - can_write_buffer.ptr = 0U; - can_write_buffer.tail_size = 0U; - } - // Assembling can message with data from buffer - if (data[0] == can_write_buffer.counter) { - uint32_t pos = 1U; - can_write_buffer.counter++; - if (can_write_buffer.ptr != 0U) { - if (can_write_buffer.tail_size <= 63U) { - CANPacket_t to_push; - (void)memcpy(&can_write_buffer.data[can_write_buffer.ptr], &data[pos], can_write_buffer.tail_size); - (void)memcpy(&to_push, can_write_buffer.data, can_write_buffer.ptr + can_write_buffer.tail_size); - can_send(&to_push, to_push.bus, false); - pos += can_write_buffer.tail_size; - can_write_buffer.ptr = 0U; - can_write_buffer.tail_size = 0U; - } else { - (void)memcpy(&can_write_buffer.data[can_write_buffer.ptr], &data[pos], len - pos); - can_write_buffer.tail_size -= 63U; - can_write_buffer.ptr += 63U; - pos += 63U; - } - } - - while (pos < len) { - uint32_t pckt_len = CANPACKET_HEAD_SIZE + dlc_to_len[(data[pos] >> 4U)]; - if ((pos + pckt_len) <= len) { - CANPacket_t to_push; - (void)memcpy(&to_push, &data[pos], pckt_len); - can_send(&to_push, to_push.bus, false); - pos += pckt_len; - } else { - (void)memcpy(can_write_buffer.data, &data[pos], len - pos); - can_write_buffer.ptr = len - pos; - can_write_buffer.tail_size = pckt_len - can_write_buffer.ptr; - pos += can_write_buffer.ptr; - } - } - } -} - // send on serial, first byte to select the ring void comms_endpoint2_write(uint8_t *data, uint32_t len) { uart_ring *ur = get_ring_by_number(data[0]); @@ -165,13 +61,6 @@ void comms_endpoint2_write(uint8_t *data, uint32_t len) { } } -// TODO: make this more general! -void usb_cb_ep3_out_complete(void) { - if (can_tx_check_min_slots_free(MAX_CAN_MSGS_PER_BULK_TRANSFER)) { - usb_outep3_resume_if_paused(); - } -} - int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { unsigned int resp_len = 0; uart_ring *ur = NULL; diff --git a/python/__init__.py b/python/__init__.py index 365d2840e3..52bba94f78 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -29,6 +29,7 @@ DEBUG = os.getenv("PANDADEBUG") is not None +USBPACKET_MAX_SIZE = 0x40 CANPACKET_HEAD_SIZE = 0x5 DLC_TO_LEN = [0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 16, 20, 24, 32, 48, 64] LEN_TO_DLC = {length: dlc for (dlc, length) in enumerate(DLC_TO_LEN)} diff --git a/tests/SConscript b/tests/SConscript new file mode 100644 index 0000000000..69f67ecc0f --- /dev/null +++ b/tests/SConscript @@ -0,0 +1,12 @@ +env = Environment( + CC='gcc', + CFLAGS=[ + '-nostdlib', + '-fno-builtin', + '-std=gnu11', + ], + CPPPATH=[".", "../board"], +) + +env.SharedLibrary("safety/libpandasafety.so", ["safety/test.c"]) +env.SharedLibrary("usbprotocol/libpandaprotocol.so", ["usbprotocol/test.c"]) diff --git a/tests/libpanda/SConscript b/tests/libpanda/SConscript index 9ff9102470..6a4d17f084 100644 --- a/tests/libpanda/SConscript +++ b/tests/libpanda/SConscript @@ -4,8 +4,9 @@ env = Environment( '-nostdlib', '-fno-builtin', '-std=gnu11', + '-Wfatal-errors', ], - CPPPATH=[".", "../../board"], + CPPPATH=[".", "#board/"], ) -env.SharedLibrary("libpanda.so", ["safety_helpers.c"]) +env.SharedLibrary("libpanda.so", ["panda.c",]) diff --git a/tests/libpanda/libpanda_py.py b/tests/libpanda/libpanda_py.py index afad693f93..f499e61c1d 100644 --- a/tests/libpanda/libpanda_py.py +++ b/tests/libpanda/libpanda_py.py @@ -30,6 +30,27 @@ int set_safety_hooks(uint16_t mode, uint16_t param); """) +ffi.cdef(""" +typedef struct { + volatile uint32_t w_ptr; + volatile uint32_t r_ptr; + uint32_t fifo_size; + CANPacket_t *elems; +} can_ring; + +extern can_ring *rx_q; +extern can_ring *txgmlan_q; +extern can_ring *tx1_q; +extern can_ring *tx2_q; +extern can_ring *tx3_q; + +bool can_pop(can_ring *q, CANPacket_t *elem); +bool can_push(can_ring *q, CANPacket_t *elem); +int comms_can_read(uint8_t *data, uint32_t max_len); +void comms_can_write(uint8_t *data, uint32_t len); +uint32_t can_slots_empty(can_ring *q); +""") + setup_safety_helpers(ffi) class CANPacket: diff --git a/tests/libpanda/panda.c b/tests/libpanda/panda.c new file mode 100644 index 0000000000..7f1ecdb133 --- /dev/null +++ b/tests/libpanda/panda.c @@ -0,0 +1,32 @@ +#include "fake_stm.h" +#include "config.h" +#include "can_definitions.h" + +bool bitbang_gmlan(CANPacket_t *to_bang) { return true; } +bool can_init(uint8_t can_number) { return true; } +void process_can(uint8_t can_number) { } +//int safety_tx_hook(CANPacket_t *to_send) { return 1; } + +typedef struct harness_configuration harness_configuration; +void usb_cb_ep3_out_complete(void); +void usb_outep3_resume_if_paused(void) { }; + +#include "health.h" +#include "faults.h" +#include "libc.h" +#include "boards/board_declarations.h" +#include "safety.h" +#include "main_declarations.h" +#include "drivers/can_common.h" + +can_ring *rx_q = &can_rx_q; +can_ring *txgmlan_q = &can_txgmlan_q; +can_ring *tx1_q = &can_tx1_q; +can_ring *tx2_q = &can_tx2_q; +can_ring *tx3_q = &can_tx3_q; + +#include "comms_definitions.h" +#include "can_comms.h" + +// libpanda stuff +#include "safety_helpers.h" diff --git a/tests/libpanda/safety_helpers.c b/tests/libpanda/safety_helpers.h similarity index 94% rename from tests/libpanda/safety_helpers.c rename to tests/libpanda/safety_helpers.h index cd5341720f..46b05fc7a0 100644 --- a/tests/libpanda/safety_helpers.c +++ b/tests/libpanda/safety_helpers.h @@ -1,12 +1,3 @@ -#include "config.h" -#include "fake_stm.h" -#include "can_definitions.h" -#include "main_declarations.h" -#include "boards/board_declarations.h" - -#include "faults.h" -#include "safety.h" - void safety_tick_current_rx_checks() { safety_tick(current_rx_checks); } diff --git a/tests/usbprotocol/test.sh b/tests/usbprotocol/test.sh new file mode 100755 index 0000000000..8e3886da7d --- /dev/null +++ b/tests/usbprotocol/test.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -e + +# Loops over all HW_TYPEs, see board/boards/board_declarations.h +for hw_type in {0..7}; do + echo "Testing HW_TYPE: $hw_type" + HW_TYPE=$hw_type python -m unittest discover . +done diff --git a/tests/usbprotocol/test_comms.py b/tests/usbprotocol/test_comms.py new file mode 100644 index 0000000000..0b37c06f6a --- /dev/null +++ b/tests/usbprotocol/test_comms.py @@ -0,0 +1,99 @@ +import random +import unittest + +from panda import Panda, DLC_TO_LEN, USBPACKET_MAX_SIZE, pack_can_buffer, unpack_can_buffer +from panda.tests.libpanda import libpanda_py + +lpp = libpanda_py.libpanda + +CHUNK_SIZE = USBPACKET_MAX_SIZE +TX_QUEUES = (lpp.tx1_q, lpp.tx2_q, lpp.tx3_q, lpp.txgmlan_q) + + +def unpackage_can_msg(pkt): + dat_len = DLC_TO_LEN[pkt[0].data_len_code] + dat = bytes(pkt[0].data[0:dat_len]) + return pkt[0].addr, 0, dat, pkt[0].bus + + +def random_can_messages(n, bus=None): + msgs = [] + for _ in range(n): + if bus is None: + bus = random.randint(0, 3) + address = random.randint(1, (1 << 29) - 1) + data = bytes([random.getrandbits(8) for _ in range(DLC_TO_LEN[random.randrange(0, len(DLC_TO_LEN))])]) + msgs.append((address, 0, data, bus)) + return msgs + + +class TestPandaComms(unittest.TestCase): + def test_tx_queues(self): + for bus in range(4): + message = (0x100, 0, b"test", bus) + + can_pkt_tx = libpanda_py.make_CANPacket(message[0], message[3], message[2]) + can_pkt_rx = libpanda_py.ffi.new('CANPacket_t *') + + assert lpp.can_push(TX_QUEUES[bus], can_pkt_tx), "CAN push failed" + assert lpp.can_pop(TX_QUEUES[bus], can_pkt_rx), "CAN pop failed" + + assert unpackage_can_msg(can_pkt_rx) == message + + def test_can_send_usb(self): + lpp.set_safety_hooks(Panda.SAFETY_ALLOUTPUT, 0) + + for bus in range(3): + with self.subTest(bus=bus): + for _ in range(100): + msgs = random_can_messages(200, bus=bus) + packed = pack_can_buffer(msgs) + + # Simulate USB bulk chunks + for buf in packed: + for i in range(0, len(buf), CHUNK_SIZE): + chunk_len = min(CHUNK_SIZE, len(buf) - i) + lpp.comms_can_write(buf[i:i+chunk_len], chunk_len) + + # Check that they ended up in the right buffers + queue_msgs = [] + pkt = libpanda_py.ffi.new('CANPacket_t *') + while lpp.can_pop(TX_QUEUES[bus], pkt): + queue_msgs.append(unpackage_can_msg(pkt)) + + self.assertEqual(len(queue_msgs), len(msgs)) + self.assertEqual(queue_msgs, msgs) + + @unittest.skip("fails on current implementation") + def test_can_receive_usb(self): + msgs = random_can_messages(50000) + packets = [libpanda_py.make_CANPacket(m[0], m[3], m[2]) for m in msgs] + + rx_msgs = [] + while len(packets) > 0: + # Push into queue + while lpp.can_slots_empty(lpp.rx_q) > 0 and len(packets) > 0: + lpp.can_push(lpp.rx_q, packets.pop(0)) + + # Simulate USB bulk IN chunks + MAX_TRANSFER_SIZE = 16384 + dat = libpanda_py.ffi.new(f"uint8_t[{CHUNK_SIZE}]") + while True: + buf = b"" + while len(buf) < MAX_TRANSFER_SIZE: + max_size = min(CHUNK_SIZE, MAX_TRANSFER_SIZE - len(buf)) + rx_len = lpp.comms_can_read(dat, max_size) + buf += bytes(dat[0:rx_len]) + if rx_len < max_size: + break + + if len(buf) == 0: + break + rx_msgs.extend(unpack_can_buffer(buf)) + + self.assertEqual(len(rx_msgs), len(msgs)) + self.assertEqual(rx_msgs, msgs) + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/tests/usbprotocol/test_pandalib.py b/tests/usbprotocol/test_pandalib.py index 7f2a3b38d5..76d8395b61 100644 --- a/tests/usbprotocol/test_pandalib.py +++ b/tests/usbprotocol/test_pandalib.py @@ -1,15 +1,14 @@ #!/usr/bin/env python3 import random import unittest -from panda import pack_can_buffer, unpack_can_buffer - +from panda import pack_can_buffer, unpack_can_buffer, DLC_TO_LEN class PandaTestPackUnpack(unittest.TestCase): def test_panda_lib_pack_unpack(self): to_pack = [] for _ in range(10000): address = random.randint(1, 0x1FFFFFFF) - data = bytes([random.getrandbits(8) for _ in range(random.randrange(1, 9))]) + data = bytes([random.getrandbits(8) for _ in range(DLC_TO_LEN[random.randrange(0, len(DLC_TO_LEN))])]) to_pack.append((address, 0, data, 0)) packed = pack_can_buffer(to_pack)