diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 465e4ddb80..79640431ad 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -6,11 +6,11 @@ concurrency: cancel-in-progress: true env: - RUN: docker run -v $GITHUB_WORKSPACE:/tmp/openpilot/panda -w /tmp/openpilot/panda --rm panda /bin/bash -c - PERSIST: docker run -v $GITHUB_WORKSPACE:/tmp/openpilot/panda -w /tmp/openpilot/panda --name panda panda /bin/bash -c + RUN: docker run -v ${{ github.workspace }}:/tmp/openpilot/panda -w /tmp/openpilot/panda --rm panda /bin/bash -c + PERSIST: docker run -v ${{ github.workspace }}:/tmp/openpilot/panda -w /tmp/openpilot/panda --name panda panda /bin/bash -c BUILD: | export DOCKER_BUILDKIT=1 - docker build --pull --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from ghcr.io/commaai/panda:latest -t panda -f Dockerfile.panda . + docker build --pull --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from ghcr.io/commaai/panda:latest -t panda -f Dockerfile . jobs: docker_push: @@ -39,11 +39,11 @@ jobs: - name: Build Docker image run: eval "$BUILD" - name: Test python package installer - run: ${{ env.RUN }} "cd /tmp/openpilot/panda && python setup.py install" - - name: Build panda STM image and bootstub - run: ${{ env.RUN }} "cd /tmp/openpilot/panda && scons" - - name: Build pedal STM image and bootstub - run: ${{ env.RUN }} "cd /tmp/openpilot/panda && PEDAL=1 scons" + run: ${{ env.RUN }} "python setup.py install" + - name: Build panda + pedal images and bootstub + run: ${{ env.RUN }} "scons -j4" + - name: Build panda with SPI support + run: ${{ env.RUN }} "ENABLE_SPI=1 scons -j4" unit_tests: name: unit tests @@ -53,9 +53,10 @@ jobs: - uses: actions/checkout@v2 - name: Build Docker image run: eval "$BUILD" - - name: Test pack/unpack for USB protocol - run: ${{ env.RUN }} "cd /tmp/openpilot/panda/tests/usbprotocol && - python -m unittest discover ." + - name: Build panda + run: $RUN "scons -j4" + - name: Test communication protocols + run: $RUN "cd tests/usbprotocol && ./test.sh" safety: name: safety @@ -66,13 +67,14 @@ jobs: - name: Build Docker image run: eval "$BUILD" - name: Run safety tests - timeout-minutes: 3 + timeout-minutes: 4 run: | - ${{ env.RUN }} "cd /tmp/openpilot && \ + ${{ env.RUN }} "cd .. && \ scons -c && \ scons -j$(nproc) opendbc/ cereal/ && \ - cd panda/tests/safety && \ - ./test.sh" + cd panda && \ + scons -j$(nproc) && \ + tests/safety/test.sh" safety_replay: name: safety replay @@ -84,7 +86,7 @@ jobs: run: eval "$BUILD" - name: Run safety replay run: ${{ env.RUN }} "cd tests/safety_replay && - scons -u --test .. && + scons -u .. && ./test_safety_replay.py" misra: diff --git a/.gitignore b/.gitignore index 3a8ead1e0f..403d034ee1 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,6 @@ examples/output.csv nosetests.xml .mypy_cache/ .sconsign.dblite + +# CTU info files generated by Cppcheck +*.*.ctu-info diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fb84825144..55b89e6075 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,14 +10,12 @@ repos: rev: v0.910-1 hooks: - id: mypy - exclude: '^(tests/automated)/' additional_dependencies: ['git+https://github.com/numpy/numpy-stubs', 'types-requests', 'types-atomicwrites', 'types-pycurl'] - repo: https://github.com/PyCQA/flake8 rev: 4.0.1 hooks: - id: flake8 - exclude: '^(tests/automated)/' args: - --select=F,E112,E113,E304,E501,E502,E701,E702,E703,E71,E72,E731,W191,W6 - --exclude=tests/gmbitbang/* @@ -30,7 +28,9 @@ repos: entry: pylint language: system types: [python] - exclude: '^(tests/automated)/' args: - - --disable=C,R,W0613,W0511,W0212,W0201,W0311,W0106,W0603,W0621,W0703,E1136 + - -rn + - -sn + - -j0 + - --disable=C,R,W0613,W0511,W0212,W0201,W0311,W0106,W0603,W0621,W0703,W1203,W1514,E1136 - --generated-members="usb1.*" diff --git a/Dockerfile b/Dockerfile index 6fda9c1485..8757c4fac8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,50 +1,45 @@ FROM ubuntu:20.04 ENV PYTHONUNBUFFERED 1 +ENV PYTHONPATH /tmp/openpilot:$PYTHONPATH ENV DEBIAN_FRONTEND=noninteractive -RUN apt-get update && apt-get install -y \ +RUN apt-get update && apt-get install -y --no-install-recommends \ autoconf \ automake \ - bash \ - bison \ bzip2 \ ca-certificates \ + capnproto \ + clang \ curl \ - dfu-util \ - flex \ g++ \ - gawk \ - gcc \ + gcc-arm-none-eabi libnewlib-arm-none-eabi \ git \ - gperf \ - help2man \ - iputils-ping \ + libarchive-dev \ + libavformat-dev libavcodec-dev libavdevice-dev libavutil-dev libswscale-dev libavresample-dev libavfilter-dev \ libbz2-dev \ - libexpat-dev \ + libcapnp-dev \ + libcurl4-openssl-dev \ libffi-dev \ - libssl-dev \ - libstdc++-arm-none-eabi-newlib \ libtool \ - libtool-bin \ + libssl-dev \ + libsqlite3-dev \ libusb-1.0-0 \ - locales \ + libzmq3-dev \ + locales \ + opencl-headers \ + ocl-icd-opencl-dev \ make \ - ncurses-dev \ - network-manager \ + patch \ + pkg-config \ + python \ python-dev \ - python3-serial \ - sed \ - texinfo \ - unrar-free \ unzip \ wget \ - build-essential \ - python-dev \ - screen \ - vim \ - wget \ - wireless-tools \ - zlib1g-dev + zlib1g-dev \ + && rm -rf /var/lib/apt/lists/* && \ + cd /usr/lib/gcc/arm-none-eabi/9.2.1 && \ + rm -rf arm/ && \ + rm -rf thumb/nofp thumb/v6* thumb/v8* thumb/v7+fp thumb/v7-r+fp.sp RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen ENV LANG en_US.UTF-8 @@ -52,22 +47,51 @@ ENV LANGUAGE en_US:en ENV LC_ALL en_US.UTF-8 RUN curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bash - ENV PATH="/root/.pyenv/bin:/root/.pyenv/shims:${PATH}" -RUN pyenv install 3.8.10 -RUN pyenv global 3.8.10 -RUN pyenv rehash -RUN pip install --upgrade pip==18.0 +ENV PANDA_PATH=/tmp/openpilot/panda +ENV OPENPILOT_REF="ee0dd36a3c775dbd82493c84f4e7272c1eb3fcbd" +ENV OPENDBC_REF="e8e97fcf00be9a696be009aa37ca13c55b9f632c" COPY requirements.txt /tmp/ -RUN pip install -r /tmp/requirements.txt +RUN pyenv install 3.8.10 && \ + pyenv global 3.8.10 && \ + pyenv rehash && \ + pip install --no-cache-dir -r /tmp/requirements.txt + +ENV CPPCHECK_DIR=/tmp/cppcheck +COPY tests/misra/install.sh /tmp/ +RUN /tmp/install.sh -ENV PYTHONPATH /tmp:$PYTHONPATH +RUN git config --global --add safe.directory /tmp/openpilot/panda +RUN cd /tmp && \ + git clone https://github.com/commaai/openpilot.git tmppilot || true && \ + cd /tmp/tmppilot && \ + git fetch origin $OPENPILOT_REF && \ + git checkout $OPENPILOT_REF && \ + git submodule update --init cereal opendbc rednose_repo && \ + git -C opendbc fetch && \ + git -C opendbc checkout $OPENDBC_REF && \ + git -C opendbc reset --hard HEAD && \ + git -C opendbc clean -xfd && \ + mkdir /tmp/openpilot && \ + cp -pR SConstruct site_scons/ tools/ selfdrive/ system/ common/ cereal/ opendbc/ rednose/ third_party/ /tmp/openpilot && \ + rm -rf /tmp/openpilot/panda && \ + rm -rf /tmp/tmppilot -RUN cd /tmp && git clone https://github.com/commaai/panda_jungle.git && \ +RUN cd /tmp/openpilot && \ + git clone https://github.com/commaai/panda_jungle.git && \ cd panda_jungle && \ git fetch && \ - git checkout 7b7197c605915ac34f3d62f314edd84e2e78a759 + git checkout 7b7197c605915ac34f3d62f314edd84e2e78a759 && \ + rm -rf .git/ + +RUN cd /tmp/openpilot && \ + pip install --no-cache-dir -r opendbc/requirements.txt && \ + pip install --no-cache-dir --upgrade aenum lru-dict pycurl tenacity atomicwrites serial smbus2 + -ADD ./panda.tar.gz /tmp/panda +# for Jenkins +COPY README.md panda.tar.* /tmp/ +RUN mkdir /tmp/openpilot/panda && \ + tar -xvf /tmp/panda.tar.gz -C /tmp/openpilot/panda/ || true diff --git a/Dockerfile.panda b/Dockerfile.panda deleted file mode 100644 index 3719a7cb30..0000000000 --- a/Dockerfile.panda +++ /dev/null @@ -1,84 +0,0 @@ -FROM ubuntu:20.04 -ENV PYTHONUNBUFFERED 1 -ENV PYTHONPATH /tmp/openpilot:$PYTHONPATH - -ENV DEBIAN_FRONTEND=noninteractive -RUN apt-get update && apt-get install -y --no-install-recommends \ - autoconf \ - automake \ - bzip2 \ - ca-certificates \ - capnproto \ - clang \ - curl \ - g++ \ - gcc-arm-none-eabi libnewlib-arm-none-eabi \ - git \ - libarchive-dev \ - libavformat-dev libavcodec-dev libavdevice-dev libavutil-dev libswscale-dev libavresample-dev libavfilter-dev \ - libbz2-dev \ - libcapnp-dev \ - libcurl4-openssl-dev \ - libffi-dev \ - libtool \ - libssl-dev \ - libsqlite3-dev \ - libusb-1.0-0 \ - libzmq3-dev \ - locales \ - opencl-headers \ - ocl-icd-opencl-dev \ - make \ - patch \ - pkg-config \ - python \ - python-dev \ - unzip \ - wget \ - zlib1g-dev \ - && rm -rf /var/lib/apt/lists/* && \ - cd /usr/lib/gcc/arm-none-eabi/9.2.1 && \ - rm -rf arm/ && \ - rm -rf thumb/nofp thumb/v6* thumb/v8* thumb/v7+fp thumb/v7-r+fp.sp - -RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen -ENV LANG en_US.UTF-8 -ENV LANGUAGE en_US:en -ENV LC_ALL en_US.UTF-8 - -RUN curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bash -ENV PATH="/root/.pyenv/bin:/root/.pyenv/shims:${PATH}" - -ENV PANDA_PATH=/tmp/openpilot/panda -ENV OPENPILOT_REF="96e8d5c9fe1a8084dfa5d97c78d4ea2037272420" -ENV OPENDBC_REF="04cc54d5e662aaf708f72cabb65507c7dbb5136d" - -COPY requirements.txt /tmp/ -RUN pyenv install 3.8.10 && \ - pyenv global 3.8.10 && \ - pyenv rehash && \ - pip install --no-cache-dir -r /tmp/requirements.txt - -ENV CPPCHECK_DIR=/tmp/cppcheck -COPY tests/misra/install.sh /tmp/ -RUN /tmp/install.sh - -RUN git config --global --add safe.directory /tmp/openpilot/panda -RUN cd /tmp && \ - git clone https://github.com/commaai/openpilot.git tmppilot || true && \ - cd /tmp/tmppilot && \ - git fetch origin $OPENPILOT_REF && \ - git checkout $OPENPILOT_REF && \ - git submodule update --init cereal opendbc rednose_repo && \ - git -C opendbc fetch && \ - git -C opendbc checkout $OPENDBC_REF && \ - git -C opendbc reset --hard HEAD && \ - git -C opendbc clean -xfd && \ - mkdir /tmp/openpilot && \ - cp -pR SConstruct site_scons/ tools/ selfdrive/ system/ common/ cereal/ opendbc/ rednose/ third_party/ /tmp/openpilot && \ - rm -rf /tmp/openpilot/panda && \ - rm -rf /tmp/tmppilot - -RUN cd /tmp/openpilot && \ - pip install --no-cache-dir -r opendbc/requirements.txt && \ - pip install --no-cache-dir --upgrade aenum lru-dict pycurl tenacity atomicwrites serial smbus2 scons diff --git a/Jenkinsfile b/Jenkinsfile index 80a028869a..4d850e0b08 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,8 +1,28 @@ +def docker_run(String step_label, int timeout_mins, String cmd) { + timeout(time: timeout_mins, unit: 'MINUTES') { + sh script: "docker run --rm --privileged \ + --env PARTIAL_TESTS=${env.PARTIAL_TESTS} \ + --volume /dev/bus/usb:/dev/bus/usb \ + --volume /var/run/dbus:/var/run/dbus \ + --workdir /tmp/openpilot/panda \ + --net host \ + ${env.DOCKER_IMAGE_TAG} \ + bash -c 'scons -j8 && ${cmd}'", \ + label: step_label + } +} + pipeline { agent any environment { + PARTIAL_TESTS = "${env.BRANCH_NAME == 'master' ? ' ' : '1'}" DOCKER_IMAGE_TAG = "panda:build-${env.GIT_COMMIT}" } + options { + timeout(time: 3, unit: 'HOURS') + disableConcurrentBuilds(abortPrevious: env.BRANCH_NAME != 'master') + } + stages { stage ('Acquire resource locks') { options { @@ -11,7 +31,7 @@ pipeline { stages { stage('Build Docker Image') { steps { - timeout(time: 60, unit: 'MINUTES') { + timeout(time: 20, unit: 'MINUTES') { script { sh 'git archive -v -o panda.tar.gz --format=tar.gz HEAD' dockerImage = docker.build("${env.DOCKER_IMAGE_TAG}") @@ -19,59 +39,31 @@ pipeline { } } } - stage('reset hardware') { + stage('prep') { steps { - timeout(time: 10, unit: 'MINUTES') { - script { - sh "docker run --rm --privileged \ - --volume /dev/bus/usb:/dev/bus/usb \ - --volume /var/run/dbus:/var/run/dbus \ - --net host \ - ${env.DOCKER_IMAGE_TAG} \ - bash -c 'cd /tmp/panda && scons -j8 && python ./tests/ci_reset_hw.py'" - } + script { + docker_run("reset hardware", 3, "python ./tests/ci_reset_hw.py") } } } stage('pedal tests') { steps { - timeout(time: 10, unit: 'MINUTES') { - script { - sh "docker run --rm --privileged \ - --volume /dev/bus/usb:/dev/bus/usb \ - --volume /var/run/dbus:/var/run/dbus \ - --net host \ - ${env.DOCKER_IMAGE_TAG} \ - bash -c 'cd /tmp/panda && PEDAL_JUNGLE=058010800f51363038363036 python ./tests/pedal/test_pedal.py'" - } + script { + docker_run("test pedal", 1, "PEDAL_JUNGLE=058010800f51363038363036 python ./tests/pedal/test_pedal.py") } } } stage('HITL tests') { steps { - timeout(time: 30, unit: 'MINUTES') { - script { - sh "docker run --rm --privileged \ - --volume /dev/bus/usb:/dev/bus/usb \ - --volume /var/run/dbus:/var/run/dbus \ - --net host \ - ${env.DOCKER_IMAGE_TAG} \ - bash -c 'cd /tmp/panda && scons -j8 && PANDAS_JUNGLE=23002d000851393038373731 PANDAS_EXCLUDE=\"1d0002000c51303136383232 2f002e000c51303136383232\" ./tests/automated/test.sh'" - } + script { + docker_run("HITL tests", 35, 'PANDAS_JUNGLE=23002d000851393038373731 PANDAS_EXCLUDE="1d0002000c51303136383232 2f002e000c51303136383232" ./tests/hitl/test.sh') } } } stage('CANFD tests') { steps { - timeout(time: 10, unit: 'MINUTES') { - script { - sh "docker run --rm --privileged \ - --volume /dev/bus/usb:/dev/bus/usb \ - --volume /var/run/dbus:/var/run/dbus \ - --net host \ - ${env.DOCKER_IMAGE_TAG} \ - bash -c 'cd /tmp/panda && scons -j8 && JUNGLE=058010800f51363038363036 H7_PANDAS_EXCLUDE=\"080021000c51303136383232 33000e001051393133353939\" ./tests/canfd/test_canfd.py'" - } + script { + docker_run("CANFD tets", 6, 'JUNGLE=058010800f51363038363036 H7_PANDAS_EXCLUDE="080021000c51303136383232 33000e001051393133353939" ./tests/canfd/test_canfd.py') } } } diff --git a/README.md b/README.md index e2956ddd35..522b010ca5 100644 --- a/README.md +++ b/README.md @@ -8,13 +8,24 @@ It supports 3x CAN, 2x LIN, and 1x GMLAN. It also charges a phone. On the comput It uses an [STM32F413](http://www.st.com/en/microcontrollers/stm32f413-423.html?querycriteria=productId=LN2004). -It is 2nd gen hardware, reusing code and parts from the [NEO](https://github.com/commaai/neo) interface board. - ![panda tests](https://github.com/commaai/panda/workflows/tests/badge.svg) ![panda drivers](https://github.com/commaai/panda/workflows/drivers/badge.svg) ## Usage +Setup your dependencies: + +```bash +# Ubuntu +sudo apt-get install dfu-util gcc-arm-none-eabi python3-pip +pip install -r requirements.txt + +# macOS +brew tap ArmMbed/homebrew-formulae +brew install python dfu-util arm-none-eabi-gcc gcc@12 +pip install -r requirements.txt +``` + ### Python To install the library: @@ -47,16 +58,12 @@ sudo udevadm control --reload-rules && sudo udevadm trigger The panda jungle uses different udev rules. See [the repo](https://github.com/commaai/panda_jungle#udev-rules) for instructions. -### JavaScript - -See [PandaJS](https://github.com/commaai/pandajs) - - ## Software interface support As a universal car interface, it should support every reasonable software interface. -- [User space](https://github.com/commaai/panda/tree/master/python) +- [Python library](https://github.com/commaai/panda/tree/master/python) +- [C++ library](https://github.com/commaai/openpilot/tree/master/selfdrive/boardd) - [socketcan in kernel](https://github.com/commaai/panda/tree/master/drivers/linux) (alpha) - [Windows J2534](https://github.com/commaai/panda/tree/master/drivers/windows) diff --git a/SConscript b/SConscript new file mode 100644 index 0000000000..ed3aff754d --- /dev/null +++ b/SConscript @@ -0,0 +1,6 @@ +# panda fw +SConscript('board/SConscript') + +# test files +if GetOption('test'): + SConscript('tests/libpanda/SConscript') diff --git a/SConstruct b/SConstruct index ba4b18dcd0..fd83d87523 100644 --- a/SConstruct +++ b/SConstruct @@ -1,9 +1,7 @@ AddOption('--test', action='store_true', + default=True, help='build test files') -# panda fw -SConscript('board/SConscript') - -# test files -SConscript('tests/safety/SConscript') +# panda fw & test files +SConscript('SConscript') diff --git a/UPDATING.md b/UPDATING.md deleted file mode 100644 index ab60f88ac8..0000000000 --- a/UPDATING.md +++ /dev/null @@ -1,9 +0,0 @@ -# Updating your panda - -Panda should update automatically via the [openpilot](http://openpilot.comma.ai/). - -On Linux or Mac OSX, you can manually update it using: -``` -sudo pip install --upgrade pandacan` -PYTHONPATH="" sudo python -c "import panda; panda.flash_release()"` -``` diff --git a/__init__.py b/__init__.py index 6b2d73a87e..8b43e99380 100644 --- a/__init__.py +++ b/__init__.py @@ -1,10 +1,5 @@ -# flake8: noqa -# pylint: skip-file -from .python import Panda, PandaDFU, flash_release, \ - BASEDIR, ensure_st_up_to_date, PandaSerial, 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 - -from .python.config import BOOTSTUB_ADDRESS, BLOCK_SIZE_FX, APP_ADDRESS_FX, \ - BLOCK_SIZE_H7, APP_ADDRESS_H7, DEVICE_SERIAL_NUMBER_ADDR_H7, \ - DEVICE_SERIAL_NUMBER_ADDR_FX +from .python.constants import McuType, BASEDIR # noqa: F401 +from .python.serial import PandaSerial # noqa: F401 +from .python import (Panda, PandaDFU, # noqa: F401 + pack_can_buffer, unpack_can_buffer, calculate_checksum, + DLC_TO_LEN, LEN_TO_DLC, ALTERNATIVE_EXPERIENCE, USBPACKET_MAX_SIZE, CANPACKET_HEAD_SIZE) diff --git a/board/README.md b/board/README.md index 4bd4b89a21..4434d28afc 100644 --- a/board/README.md +++ b/board/README.md @@ -1,20 +1,3 @@ -Dependencies --------- - -**Mac** - -``` -xcode-select --install -./get_sdk_mac.sh -``` - -**Debian / Ubuntu** - -``` -./get_sdk.sh -``` - - Programming ---- diff --git a/board/SConscript b/board/SConscript index ebdca1301f..7c91279047 100644 --- a/board/SConscript +++ b/board/SConscript @@ -11,7 +11,7 @@ build_projects = {} build_projects["pedal"] = { "MAIN": "pedal/main.c", "STARTUP_FILE": "stm32fx/startup_stm32f205xx.s", - "LINKER_SCRIPT": "stm32fx/stm32fx_flash.ld", + "LINKER_SCRIPT": "stm32fx/stm32f2_flash.ld", "APP_START_ADDRESS": "0x8004000", "PROJECT_FLAGS": [ "-mcpu=cortex-m3", @@ -29,7 +29,7 @@ build_projects["pedal_usb"]["PROJECT_FLAGS"].append("-DPEDAL_USB") build_projects["panda"] = { "MAIN": "main.c", "STARTUP_FILE": "stm32fx/startup_stm32f413xx.s", - "LINKER_SCRIPT": "stm32fx/stm32fx_flash.ld", + "LINKER_SCRIPT": "stm32fx/stm32f4_flash.ld", "APP_START_ADDRESS": "0x8004000", "PROJECT_FLAGS": [ "-mcpu=cortex-m4", @@ -152,6 +152,9 @@ for project_name in build_projects: "-std=gnu11", ] + project["PROJECT_FLAGS"] + common_flags + if ("ENABLE_SPI" in os.environ or "h7" in project_name) and not project_name.startswith('pedal'): + flags.append('-DENABLE_SPI') + project_env = Environment( ENV=os.environ, CC=PREFIX + 'gcc', diff --git a/board/boards/black.h b/board/boards/black.h index 27fc3d5974..e4d5bb8e58 100644 --- a/board/boards/black.h +++ b/board/boards/black.h @@ -17,7 +17,7 @@ void black_enable_can_transceiver(uint8_t transceiver, bool enabled) { set_gpio_output(GPIOB, 10, !enabled); break; default: - puts("Invalid CAN transceiver ("); puth(transceiver); puts("): enabling failed\n"); + print("Invalid CAN transceiver ("); puth(transceiver); print("): enabling failed\n"); break; } } @@ -74,7 +74,7 @@ void black_set_gps_mode(uint8_t mode) { set_gpio_output(GPIOC, 5, 0); break; default: - puts("Invalid GPS mode\n"); + print("Invalid GPS mode\n"); break; } } @@ -102,7 +102,7 @@ void black_set_can_mode(uint8_t mode){ } break; default: - puts("Tried to set unsupported CAN mode: "); puth(mode); puts("\n"); + print("Tried to set unsupported CAN mode: "); puth(mode); print("\n"); break; } } @@ -187,9 +187,13 @@ const board board_black = { .has_hw_gmlan = false, .has_obd = true, .has_lin = false, + .has_spi = false, .has_canfd = false, .has_rtc_battery = false, .fan_max_rpm = 0U, + .adc_scale = 8862U, + .fan_stall_recovery = false, + .fan_enable_cooldown_time = 0U, .init = black_init, .enable_can_transceiver = black_enable_can_transceiver, .enable_can_transceivers = black_enable_can_transceivers, @@ -201,6 +205,6 @@ const board board_black = { .set_fan_enabled = unused_set_fan_enabled, .set_ir_power = unused_set_ir_power, .set_phone_power = unused_set_phone_power, - .set_clock_source_mode = unused_set_clock_source_mode, - .set_siren = unused_set_siren + .set_siren = unused_set_siren, + .read_som_gpio = unused_read_som_gpio }; diff --git a/board/boards/board_declarations.h b/board/boards/board_declarations.h index b6216da8de..db5c742295 100644 --- a/board/boards/board_declarations.h +++ b/board/boards/board_declarations.h @@ -10,9 +10,9 @@ typedef uint32_t (*board_read_current)(void); typedef void (*board_set_ir_power)(uint8_t percentage); typedef void (*board_set_fan_enabled)(bool enabled); typedef void (*board_set_phone_power)(bool enabled); -typedef void (*board_set_clock_source_mode)(uint8_t mode); typedef void (*board_set_siren)(bool enabled); typedef void (*board_board_tick)(bool ignition, bool usb_enum, bool heartbeat_seen); +typedef bool (*board_read_som_gpio)(void); struct board { const char *board_type; @@ -21,9 +21,13 @@ struct board { const bool has_hw_gmlan; const bool has_obd; const bool has_lin; + const bool has_spi; const bool has_canfd; const bool has_rtc_battery; const uint16_t fan_max_rpm; + const uint16_t adc_scale; + const bool fan_stall_recovery; + const uint8_t fan_enable_cooldown_time; board_init init; board_enable_can_transceiver enable_can_transceiver; board_enable_can_transceivers enable_can_transceivers; @@ -35,9 +39,9 @@ struct board { board_set_ir_power set_ir_power; board_set_fan_enabled set_fan_enabled; board_set_phone_power set_phone_power; - board_set_clock_source_mode set_clock_source_mode; board_set_siren set_siren; board_board_tick board_tick; + board_read_som_gpio read_som_gpio; }; // ******************* Definitions ******************** @@ -51,6 +55,7 @@ struct board { #define HW_TYPE_DOS 6U #define HW_TYPE_RED_PANDA 7U #define HW_TYPE_RED_PANDA_V2 8U +#define HW_TYPE_TRES 9U // LED colors #define LED_RED 0U diff --git a/board/boards/dos.h b/board/boards/dos.h index d0b00ef045..b6451bd6a6 100644 --- a/board/boards/dos.h +++ b/board/boards/dos.h @@ -17,7 +17,7 @@ void dos_enable_can_transceiver(uint8_t transceiver, bool enabled) { set_gpio_output(GPIOB, 10, !enabled); break; default: - puts("Invalid CAN transceiver ("); puth(transceiver); puts("): enabling failed\n"); + print("Invalid CAN transceiver ("); puth(transceiver); print("): enabling failed\n"); break; } } @@ -88,7 +88,7 @@ void dos_set_can_mode(uint8_t mode){ } break; default: - puts("Tried to set unsupported CAN mode: "); puth(mode); puts("\n"); + print("Tried to set unsupported CAN mode: "); puth(mode); print("\n"); break; } } @@ -110,14 +110,14 @@ void dos_set_fan_enabled(bool enabled){ set_gpio_output(GPIOA, 1, enabled); } -void dos_set_clock_source_mode(uint8_t mode){ - clock_source_init(mode); -} - void dos_set_siren(bool enabled){ set_gpio_output(GPIOC, 12, enabled); } +bool dos_read_som_gpio (void){ + return (get_gpio_input(GPIOC, 2) != 0); +} + void dos_init(void) { common_init_gpio(); @@ -139,18 +139,23 @@ void dos_init(void) { set_gpio_output(GPIOC, 10, 1); set_gpio_output(GPIOC, 11, 1); +#ifdef ENABLE_SPI + // SPI init + gpio_spi_init(); +#endif + // C8: FAN PWM aka TIM3_CH3 set_gpio_alternate(GPIOC, 8, GPIO_AF2_TIM3); + // C2: SOM GPIO used as input (fan control at boot) + set_gpio_mode(GPIOC, 2, MODE_INPUT); + set_gpio_pullup(GPIOC, 2, PULL_DOWN); + // Initialize IR PWM and set to 0% set_gpio_alternate(GPIOB, 7, GPIO_AF2_TIM4); pwm_init(TIM4, 2); dos_set_ir_power(0U); - // Initialize fan and set to 0% - fan_init(); - dos_set_fan_enabled(false); - // Initialize harness harness_init(); @@ -177,7 +182,7 @@ void dos_init(void) { } // Init clock source (camera strobe) using PWM - dos_set_clock_source_mode(CLOCK_SOURCE_MODE_PWM); + clock_source_init(); } const harness_configuration dos_harness_config = { @@ -202,9 +207,17 @@ const board board_dos = { .has_hw_gmlan = false, .has_obd = true, .has_lin = false, +#ifdef ENABLE_SPI + .has_spi = true, +#else + .has_spi = false, +#endif .has_canfd = false, .has_rtc_battery = true, .fan_max_rpm = 6500U, + .adc_scale = 8862U, + .fan_stall_recovery = true, + .fan_enable_cooldown_time = 0U, .init = dos_init, .enable_can_transceiver = dos_enable_can_transceiver, .enable_can_transceivers = dos_enable_can_transceivers, @@ -216,6 +229,6 @@ const board board_dos = { .set_fan_enabled = dos_set_fan_enabled, .set_ir_power = dos_set_ir_power, .set_phone_power = unused_set_phone_power, - .set_clock_source_mode = dos_set_clock_source_mode, - .set_siren = dos_set_siren + .set_siren = dos_set_siren, + .read_som_gpio = dos_read_som_gpio }; diff --git a/board/boards/grey.h b/board/boards/grey.h index 29deb99136..ccc7787203 100644 --- a/board/boards/grey.h +++ b/board/boards/grey.h @@ -28,7 +28,7 @@ void grey_set_gps_mode(uint8_t mode) { set_gpio_output(GPIOC, 5, 0); break; default: - puts("Invalid ESP/GPS mode\n"); + print("Invalid ESP/GPS mode\n"); break; } } @@ -41,9 +41,13 @@ const board board_grey = { .has_hw_gmlan = true, .has_obd = false, .has_lin = true, + .has_spi = false, .has_canfd = false, .has_rtc_battery = false, .fan_max_rpm = 0U, + .adc_scale = 8862U, + .fan_stall_recovery = false, + .fan_enable_cooldown_time = 0U, .init = grey_init, .enable_can_transceiver = white_enable_can_transceiver, .enable_can_transceivers = white_enable_can_transceivers, @@ -55,6 +59,6 @@ const board board_grey = { .set_fan_enabled = unused_set_fan_enabled, .set_ir_power = unused_set_ir_power, .set_phone_power = unused_set_phone_power, - .set_clock_source_mode = unused_set_clock_source_mode, - .set_siren = unused_set_siren + .set_siren = unused_set_siren, + .read_som_gpio = unused_read_som_gpio }; diff --git a/board/boards/pedal.h b/board/boards/pedal.h index 62830cfbaf..509a4bb4b0 100644 --- a/board/boards/pedal.h +++ b/board/boards/pedal.h @@ -8,7 +8,7 @@ void pedal_enable_can_transceiver(uint8_t transceiver, bool enabled) { set_gpio_output(GPIOB, 3, !enabled); break; default: - puts("Invalid CAN transceiver ("); puth(transceiver); puts("): enabling failed\n"); + print("Invalid CAN transceiver ("); puth(transceiver); print("): enabling failed\n"); break; } } @@ -32,7 +32,7 @@ void pedal_set_led(uint8_t color, bool enabled) { void pedal_set_gps_mode(uint8_t mode) { UNUSED(mode); - puts("Trying to set ESP/GPS mode on pedal. This is not supported.\n"); + print("Trying to set ESP/GPS mode on pedal. This is not supported.\n"); } void pedal_set_can_mode(uint8_t mode){ @@ -40,7 +40,7 @@ void pedal_set_can_mode(uint8_t mode){ case CAN_MODE_NORMAL: break; default: - puts("Tried to set unsupported CAN mode: "); puth(mode); puts("\n"); + print("Tried to set unsupported CAN mode: "); puth(mode); print("\n"); break; } } @@ -79,9 +79,13 @@ const board board_pedal = { .has_hw_gmlan = false, .has_obd = false, .has_lin = false, + .has_spi = false, .has_canfd = false, .has_rtc_battery = false, .fan_max_rpm = 0U, + .adc_scale = 8862U, + .fan_stall_recovery = false, + .fan_enable_cooldown_time = 0U, .init = pedal_init, .enable_can_transceiver = pedal_enable_can_transceiver, .enable_can_transceivers = pedal_enable_can_transceivers, @@ -93,6 +97,6 @@ const board board_pedal = { .set_fan_enabled = unused_set_fan_enabled, .set_ir_power = unused_set_ir_power, .set_phone_power = unused_set_phone_power, - .set_clock_source_mode = unused_set_clock_source_mode, - .set_siren = unused_set_siren + .set_siren = unused_set_siren, + .read_som_gpio = unused_read_som_gpio }; diff --git a/board/boards/red.h b/board/boards/red.h index 9691ed5c6c..2be859d9f6 100644 --- a/board/boards/red.h +++ b/board/boards/red.h @@ -49,10 +49,6 @@ void red_set_led(uint8_t color, bool enabled) { } } -void red_set_usb_load_switch(bool enabled) { - set_gpio_output(GPIOB, 14, !enabled); -} - void red_set_can_mode(uint8_t mode) { switch (mode) { case CAN_MODE_NORMAL: @@ -123,16 +119,15 @@ void red_init(void) { set_gpio_pullup(GPIOB, 4, PULL_NONE); set_gpio_mode(GPIOB, 4, MODE_OUTPUT); - // B14: usb load switch - set_gpio_pullup(GPIOB, 14, PULL_NONE); - set_gpio_mode(GPIOB, 14, MODE_OUTPUT); - //B1: 5VOUT_S set_gpio_pullup(GPIOB, 1, PULL_NONE); set_gpio_mode(GPIOB, 1, MODE_ANALOG); - // Turn on USB load switch. - red_set_usb_load_switch(true); + // B14: usb load switch, enabled by pull resistor on board, obsolete for red panda + set_gpio_output_type(GPIOB, 14, OUTPUT_TYPE_OPEN_DRAIN); + set_gpio_pullup(GPIOB, 14, PULL_UP); + set_gpio_mode(GPIOB, 14, MODE_OUTPUT); + set_gpio_output(GPIOB, 14, 1); // Initialize harness harness_init(); @@ -179,9 +174,13 @@ const board board_red = { .has_hw_gmlan = false, .has_obd = true, .has_lin = false, + .has_spi = false, .has_canfd = true, .has_rtc_battery = false, .fan_max_rpm = 0U, + .adc_scale = 5539U, + .fan_stall_recovery = false, + .fan_enable_cooldown_time = 0U, .init = red_init, .enable_can_transceiver = red_enable_can_transceiver, .enable_can_transceivers = red_enable_can_transceivers, @@ -193,6 +192,6 @@ const board board_red = { .set_fan_enabled = unused_set_fan_enabled, .set_ir_power = unused_set_ir_power, .set_phone_power = unused_set_phone_power, - .set_clock_source_mode = unused_set_clock_source_mode, - .set_siren = unused_set_siren + .set_siren = unused_set_siren, + .read_som_gpio = unused_read_som_gpio }; diff --git a/board/boards/red_chiplet.h b/board/boards/red_chiplet.h new file mode 100644 index 0000000000..c4816a9e90 --- /dev/null +++ b/board/boards/red_chiplet.h @@ -0,0 +1,112 @@ +// ///////////////////// // +// Red Panda chiplet + Harness // +// ///////////////////// // + +// Most hardware functionality is similar to red panda + +void red_chiplet_enable_can_transceiver(uint8_t transceiver, bool enabled) { + switch (transceiver) { + case 1U: + set_gpio_output(GPIOG, 11, !enabled); + break; + case 2U: + set_gpio_output(GPIOB, 10, !enabled); + break; + case 3U: + set_gpio_output(GPIOD, 7, !enabled); + break; + case 4U: + set_gpio_output(GPIOB, 11, !enabled); + break; + default: + break; + } +} + +void red_chiplet_enable_can_transceivers(bool enabled) { + uint8_t main_bus = (car_harness_status == HARNESS_STATUS_FLIPPED) ? 3U : 1U; + for (uint8_t i=1U; i<=4U; i++) { + // Leave main CAN always on for CAN-based ignition detection + if (i == main_bus) { + red_chiplet_enable_can_transceiver(i, true); + } else { + red_chiplet_enable_can_transceiver(i, enabled); + } + } +} + +void red_chiplet_set_fan_or_usb_load_switch(bool enabled) { + set_gpio_output(GPIOD, 3, enabled); +} + +void red_chiplet_init(void) { + common_init_gpio(); + + // A8, A3: OBD_SBU1_RELAY, OBD_SBU2_RELAY + set_gpio_output_type(GPIOA, 8, OUTPUT_TYPE_OPEN_DRAIN); + set_gpio_pullup(GPIOA, 8, PULL_NONE); + set_gpio_output(GPIOA, 8, 1); + set_gpio_mode(GPIOA, 8, MODE_OUTPUT); + + set_gpio_output_type(GPIOA, 3, OUTPUT_TYPE_OPEN_DRAIN); + set_gpio_pullup(GPIOA, 3, PULL_NONE); + set_gpio_output(GPIOA, 3, 1); + set_gpio_mode(GPIOA, 3, MODE_OUTPUT); + + // G11,B10,D7,B11: transceiver enable + set_gpio_pullup(GPIOG, 11, PULL_NONE); + set_gpio_mode(GPIOG, 11, MODE_OUTPUT); + + set_gpio_pullup(GPIOB, 10, PULL_NONE); + set_gpio_mode(GPIOB, 10, MODE_OUTPUT); + + set_gpio_pullup(GPIOD, 7, PULL_NONE); + set_gpio_mode(GPIOD, 7, MODE_OUTPUT); + + set_gpio_pullup(GPIOB, 11, PULL_NONE); + set_gpio_mode(GPIOB, 11, MODE_OUTPUT); + + // D3: usb load switch + set_gpio_pullup(GPIOD, 3, PULL_NONE); + set_gpio_mode(GPIOD, 3, MODE_OUTPUT); + + // B0: 5VOUT_S + set_gpio_pullup(GPIOB, 0, PULL_NONE); + set_gpio_mode(GPIOB, 0, MODE_ANALOG); + + // Initialize harness + harness_init(); + + // Initialize RTC + rtc_init(); + + // Enable CAN transceivers + red_chiplet_enable_can_transceivers(true); + + // Disable LEDs + red_set_led(LED_RED, false); + red_set_led(LED_GREEN, false); + red_set_led(LED_BLUE, false); + + // Set normal CAN mode + red_set_can_mode(CAN_MODE_NORMAL); + + // flip CAN0 and CAN2 if we are flipped + if (car_harness_status == HARNESS_STATUS_FLIPPED) { + can_flip_buses(0, 2); + } +} + +const harness_configuration red_chiplet_harness_config = { + .has_harness = true, + .GPIO_SBU1 = GPIOC, + .GPIO_SBU2 = GPIOA, + .GPIO_relay_SBU1 = GPIOA, + .GPIO_relay_SBU2 = GPIOA, + .pin_SBU1 = 4, + .pin_SBU2 = 1, + .pin_relay_SBU1 = 8, + .pin_relay_SBU2 = 3, + .adc_channel_SBU1 = 4, // ADC12_INP4 + .adc_channel_SBU2 = 17 // ADC1_INP17 +}; diff --git a/board/boards/red_v2.h b/board/boards/red_v2.h index 3eef77e0f6..3e8f2f2e0e 100644 --- a/board/boards/red_v2.h +++ b/board/boards/red_v2.h @@ -2,132 +2,32 @@ // Red Panda V2 with chiplet + Harness // // ///////////////////// // -// Most hardware functionality is similar to red panda +void red_panda_v2_init(void) { + // common chiplet init + red_chiplet_init(); -void red_v2_enable_can_transceiver(uint8_t transceiver, bool enabled) { - switch (transceiver) { - case 1U: - set_gpio_output(GPIOG, 11, !enabled); - break; - case 2U: - set_gpio_output(GPIOB, 10, !enabled); - break; - case 3U: - set_gpio_output(GPIOD, 7, !enabled); - break; - case 4U: - set_gpio_output(GPIOB, 11, !enabled); - break; - default: - break; - } + // Turn on USB load switch + red_chiplet_set_fan_or_usb_load_switch(true); } -void red_v2_enable_can_transceivers(bool enabled) { - uint8_t main_bus = (car_harness_status == HARNESS_STATUS_FLIPPED) ? 3U : 1U; - for (uint8_t i=1U; i<=4U; i++) { - // Leave main CAN always on for CAN-based ignition detection - if (i == main_bus) { - red_v2_enable_can_transceiver(i, true); - } else { - red_v2_enable_can_transceiver(i, enabled); - } - } -} - -void red_v2_set_usb_load_switch(bool enabled) { - set_gpio_output(GPIOD, 3, enabled); -} - -void red_v2_init(void) { - common_init_gpio(); - - //A8, A9 : OBD_SBU1_RELAY, OBD_SBU2_RELAY - set_gpio_output_type(GPIOA, 8, OUTPUT_TYPE_OPEN_DRAIN); - set_gpio_pullup(GPIOA, 8, PULL_NONE); - set_gpio_mode(GPIOA, 8, MODE_OUTPUT); - set_gpio_output(GPIOA, 8, 1); - - set_gpio_output_type(GPIOA, 9, OUTPUT_TYPE_OPEN_DRAIN); - set_gpio_pullup(GPIOA, 9, PULL_NONE); - set_gpio_mode(GPIOA, 9, MODE_OUTPUT); - set_gpio_output(GPIOA, 9, 1); - - // G11,B10,D7,B11: transceiver enable - set_gpio_pullup(GPIOG, 11, PULL_NONE); - set_gpio_mode(GPIOG, 11, MODE_OUTPUT); - - set_gpio_pullup(GPIOB, 10, PULL_NONE); - set_gpio_mode(GPIOB, 10, MODE_OUTPUT); - - set_gpio_pullup(GPIOD, 7, PULL_NONE); - set_gpio_mode(GPIOD, 7, MODE_OUTPUT); - - set_gpio_pullup(GPIOB, 11, PULL_NONE); - set_gpio_mode(GPIOB, 11, MODE_OUTPUT); - - // D3: usb load switch - set_gpio_pullup(GPIOD, 3, PULL_NONE); - set_gpio_mode(GPIOD, 3, MODE_OUTPUT); - - //B0: 5VOUT_S - set_gpio_pullup(GPIOB, 0, PULL_NONE); - set_gpio_mode(GPIOB, 0, MODE_ANALOG); - - // Turn on USB load switch. - red_v2_set_usb_load_switch(true); - - // Initialize harness - harness_init(); - - // Initialize RTC - rtc_init(); - - // Enable CAN transceivers - red_v2_enable_can_transceivers(true); - - // Disable LEDs - red_set_led(LED_RED, false); - red_set_led(LED_GREEN, false); - red_set_led(LED_BLUE, false); - - // Set normal CAN mode - red_set_can_mode(CAN_MODE_NORMAL); - - // flip CAN0 and CAN2 if we are flipped - if (car_harness_status == HARNESS_STATUS_FLIPPED) { - can_flip_buses(0, 2); - } -} - -const harness_configuration red_v2_harness_config = { - .has_harness = true, - .GPIO_SBU1 = GPIOC, - .GPIO_SBU2 = GPIOA, - .GPIO_relay_SBU1 = GPIOA, - .GPIO_relay_SBU2 = GPIOA, - .pin_SBU1 = 4, - .pin_SBU2 = 1, - .pin_relay_SBU1 = 8, - .pin_relay_SBU2 = 9, - .adc_channel_SBU1 = 4, //ADC12_INP4 - .adc_channel_SBU2 = 17 //ADC1_INP17 -}; - const board board_red_v2 = { .board_type = "Red_v2", .board_tick = unused_board_tick, - .harness_config = &red_v2_harness_config, + .harness_config = &red_chiplet_harness_config, .has_gps = false, .has_hw_gmlan = false, .has_obd = true, .has_lin = false, + .has_spi = false, .has_canfd = true, .has_rtc_battery = true, .fan_max_rpm = 0U, - .init = red_v2_init, - .enable_can_transceiver = red_v2_enable_can_transceiver, - .enable_can_transceivers = red_v2_enable_can_transceivers, + .adc_scale = 5539U, + .fan_stall_recovery = false, + .fan_enable_cooldown_time = 0U, + .init = red_panda_v2_init, + .enable_can_transceiver = red_chiplet_enable_can_transceiver, + .enable_can_transceivers = red_chiplet_enable_can_transceivers, .set_led = red_set_led, .set_gps_mode = unused_set_gps_mode, .set_can_mode = red_set_can_mode, @@ -136,6 +36,5 @@ const board board_red_v2 = { .set_fan_enabled = unused_set_fan_enabled, .set_ir_power = unused_set_ir_power, .set_phone_power = unused_set_phone_power, - .set_clock_source_mode = unused_set_clock_source_mode, .set_siren = unused_set_siren }; diff --git a/board/boards/tres.h b/board/boards/tres.h new file mode 100644 index 0000000000..c7c954ab5a --- /dev/null +++ b/board/boards/tres.h @@ -0,0 +1,113 @@ +// ///////////////// +// Tres + Harness // +// ///////////////// + +bool tres_ir_enabled; +bool tres_fan_enabled; +void tres_update_fan_ir_power(void) { + red_chiplet_set_fan_or_usb_load_switch(tres_ir_enabled || tres_fan_enabled); +} + +void tres_set_ir_power(uint8_t percentage){ + tres_ir_enabled = (percentage > 0U); + tres_update_fan_ir_power(); + pwm_set(TIM3, 4, percentage); +} + +void tres_set_bootkick(bool enabled){ + set_gpio_output(GPIOA, 0, !enabled); +} + +bool tres_ignition_prev = false; +void tres_board_tick(bool ignition, bool usb_enum, bool heartbeat_seen) { + UNUSED(usb_enum); + if (ignition && !tres_ignition_prev) { + // enable bootkick on rising edge of ignition + tres_set_bootkick(true); + } else if (heartbeat_seen) { + // disable once openpilot is up + tres_set_bootkick(false); + } else { + + } + tres_ignition_prev = ignition; +} + +void tres_set_fan_enabled(bool enabled) { + // NOTE: fan controller reset doesn't work on a tres if IR is enabled + tres_fan_enabled = enabled; + tres_update_fan_ir_power(); +} + +bool tres_read_som_gpio (void){ + return (get_gpio_input(GPIOC, 2) != 0); +} + +void tres_init(void) { + // Enable USB 3.3V LDO for USB block + register_set_bits(&(PWR->CR3), PWR_CR3_USBREGEN); + register_set_bits(&(PWR->CR3), PWR_CR3_USB33DEN); + while ((PWR->CR3 & PWR_CR3_USB33RDY) == 0); + + red_chiplet_init(); + + // C2: SOM GPIO used as input (fan control at boot) + set_gpio_mode(GPIOC, 2, MODE_INPUT); + set_gpio_pullup(GPIOC, 2, PULL_DOWN); + + tres_set_bootkick(true); + + // SOM debugging UART + gpio_uart7_init(); + uart_init(&uart_ring_som_debug, 115200); + + // SPI init + gpio_spi_init(); + + // fan setup + set_gpio_alternate(GPIOC, 8, GPIO_AF2_TIM3); + + // Initialize IR PWM and set to 0% + set_gpio_alternate(GPIOC, 9, GPIO_AF2_TIM3); + pwm_init(TIM3, 4); + tres_set_ir_power(0U); + + // Fake siren + set_gpio_alternate(GPIOC, 10, GPIO_AF4_I2C5); + set_gpio_alternate(GPIOC, 11, GPIO_AF4_I2C5); + register_set_bits(&(GPIOC->OTYPER), GPIO_OTYPER_OT10 | GPIO_OTYPER_OT11); // open drain + fake_siren_init(); + + // Clock source + clock_source_init(); +} + +const board board_tres = { + .board_type = "Tres", + .board_tick = tres_board_tick, + .harness_config = &red_chiplet_harness_config, + .has_gps = false, + .has_hw_gmlan = false, + .has_obd = true, + .has_lin = false, + .has_spi = true, + .has_canfd = true, + .has_rtc_battery = true, + .fan_max_rpm = 6600U, + .adc_scale = 3021U, + .fan_stall_recovery = false, + .fan_enable_cooldown_time = 3U, + .init = tres_init, + .enable_can_transceiver = red_chiplet_enable_can_transceiver, + .enable_can_transceivers = red_chiplet_enable_can_transceivers, + .set_led = red_set_led, + .set_gps_mode = unused_set_gps_mode, + .set_can_mode = red_set_can_mode, + .check_ignition = red_check_ignition, + .read_current = unused_read_current, + .set_fan_enabled = tres_set_fan_enabled, + .set_ir_power = tres_set_ir_power, + .set_phone_power = unused_set_phone_power, + .set_siren = fake_siren_set, + .read_som_gpio = tres_read_som_gpio +}; diff --git a/board/boards/uno.h b/board/boards/uno.h index c2573681e8..abf8331c6f 100644 --- a/board/boards/uno.h +++ b/board/boards/uno.h @@ -19,7 +19,7 @@ void uno_enable_can_transceiver(uint8_t transceiver, bool enabled) { set_gpio_output(GPIOB, 10, !enabled); break; default: - puts("Invalid CAN transceiver ("); puth(transceiver); puts("): enabling failed\n"); + print("Invalid CAN transceiver ("); puth(transceiver); print("): enabling failed\n"); break; } } @@ -93,7 +93,7 @@ void uno_set_gps_mode(uint8_t mode) { uno_set_gps_load_switch(true); break; default: - puts("Invalid ESP/GPS mode\n"); + print("Invalid ESP/GPS mode\n"); break; } } @@ -121,7 +121,7 @@ void uno_set_can_mode(uint8_t mode){ } break; default: - puts("Tried to set unsupported CAN mode: "); puth(mode); puts("\n"); + print("Tried to set unsupported CAN mode: "); puth(mode); print("\n"); break; } } @@ -192,10 +192,6 @@ void uno_init(void) { pwm_init(TIM4, 2); uno_set_ir_power(0U); - // Initialize fan and set to 0% - fan_init(); - uno_set_fan_enabled(false); - // Initialize harness harness_init(); @@ -219,7 +215,7 @@ void uno_init(void) { } // Switch to phone usb mode if harness connection is powered by less than 7V - if(adc_get_voltage() < 7000U){ + if(adc_get_voltage(current_board->adc_scale) < 7000U){ uno_set_usb_switch(true); } else { uno_set_usb_switch(false); @@ -251,9 +247,13 @@ const board board_uno = { .has_hw_gmlan = false, .has_obd = true, .has_lin = false, + .has_spi = false, .has_canfd = false, .has_rtc_battery = true, .fan_max_rpm = 5100U, + .adc_scale = 8862U, + .fan_stall_recovery = false, + .fan_enable_cooldown_time = 0U, .init = uno_init, .enable_can_transceiver = uno_enable_can_transceiver, .enable_can_transceivers = uno_enable_can_transceivers, @@ -265,6 +265,6 @@ const board board_uno = { .set_fan_enabled = uno_set_fan_enabled, .set_ir_power = uno_set_ir_power, .set_phone_power = uno_set_phone_power, - .set_clock_source_mode = unused_set_clock_source_mode, - .set_siren = unused_set_siren + .set_siren = unused_set_siren, + .read_som_gpio = unused_read_som_gpio }; diff --git a/board/boards/unused_funcs.h b/board/boards/unused_funcs.h index be675d89fd..d3b4a4c6c8 100644 --- a/board/boards/unused_funcs.h +++ b/board/boards/unused_funcs.h @@ -14,10 +14,6 @@ void unused_set_phone_power(bool enabled) { UNUSED(enabled); } -void unused_set_clock_source_mode(uint8_t mode) { - UNUSED(mode); -} - void unused_set_siren(bool enabled) { UNUSED(enabled); } @@ -30,4 +26,8 @@ void unused_board_tick(bool ignition, bool usb_enum, bool heartbeat_seen) { UNUSED(ignition); UNUSED(usb_enum); UNUSED(heartbeat_seen); +} + +bool unused_read_som_gpio(void) { + return false; } \ No newline at end of file diff --git a/board/boards/white.h b/board/boards/white.h index 961e0eaa16..ebca3b2233 100644 --- a/board/boards/white.h +++ b/board/boards/white.h @@ -14,7 +14,7 @@ void white_enable_can_transceiver(uint8_t transceiver, bool enabled) { set_gpio_output(GPIOA, 0, !enabled); break; default: - puts("Invalid CAN transceiver ("); puth(transceiver); puts("): enabling failed\n"); + print("Invalid CAN transceiver ("); puth(transceiver); print("): enabling failed\n"); break; } } @@ -60,7 +60,7 @@ void white_set_usb_power_mode(uint8_t mode){ set_gpio_output(GPIOA, 13, 0); break; default: - puts("Invalid usb power mode\n"); + print("Invalid usb power mode\n"); break; } } @@ -77,7 +77,7 @@ void white_set_gps_mode(uint8_t mode) { set_gpio_output(GPIOC, 5, 0); break; default: - puts("Invalid ESP/GPS mode\n"); + print("Invalid ESP/GPS mode\n"); break; } } @@ -136,7 +136,7 @@ void white_set_can_mode(uint8_t mode){ set_gpio_alternate(GPIOB, 6, GPIO_AF9_CAN2); break; default: - puts("Tried to set unsupported CAN mode: "); puth(mode); puts("\n"); + print("Tried to set unsupported CAN mode: "); puth(mode); print("\n"); break; } } @@ -214,7 +214,7 @@ void white_grey_common_init(void) { white_set_can_mode(CAN_MODE_NORMAL); // Init usb power mode - uint32_t voltage = adc_get_voltage(); + uint32_t voltage = adc_get_voltage(current_board->adc_scale); // Init in CDP mode only if panda is powered by 12V. // Otherwise a PC would not be able to flash a standalone panda if (voltage > 8000U) { // 8V threshold @@ -243,9 +243,13 @@ const board board_white = { .has_hw_gmlan = true, .has_obd = false, .has_lin = true, + .has_spi = false, .has_canfd = false, .has_rtc_battery = false, .fan_max_rpm = 0U, + .adc_scale = 8862U, + .fan_stall_recovery = false, + .fan_enable_cooldown_time = 0U, .init = white_init, .enable_can_transceiver = white_enable_can_transceiver, .enable_can_transceivers = white_enable_can_transceivers, @@ -257,6 +261,6 @@ const board board_white = { .set_fan_enabled = unused_set_fan_enabled, .set_ir_power = unused_set_ir_power, .set_phone_power = unused_set_phone_power, - .set_clock_source_mode = unused_set_clock_source_mode, - .set_siren = unused_set_siren + .set_siren = unused_set_siren, + .read_som_gpio = unused_read_som_gpio }; diff --git a/board/bootstub.c b/board/bootstub.c index a31721232e..a2bf7dad7a 100644 --- a/board/bootstub.c +++ b/board/bootstub.c @@ -39,7 +39,6 @@ int main(void) { disable_interrupts(); clock_init(); - detect_external_debug_serial(); detect_board_type(); if (enter_bootloader_mode == ENTER_SOFTLOADER_MAGIC) { diff --git a/board/bootstub_declarations.h b/board/bootstub_declarations.h index 8e9f07312f..ae115d2eb3 100644 --- a/board/bootstub_declarations.h +++ b/board/bootstub_declarations.h @@ -1,14 +1,19 @@ // ******************** Prototypes ******************** -void puts(const char *a){ UNUSED(a); } +void print(const char *a){ UNUSED(a); } void puth(uint8_t i){ UNUSED(i); } void puth2(uint8_t i){ UNUSED(i); } void puth4(uint8_t i){ UNUSED(i); } +void hexdump(const void *a, int l){ UNUSED(a); UNUSED(l); } typedef struct board board; typedef struct harness_configuration harness_configuration; // No CAN support on bootloader void can_flip_buses(uint8_t bus1, uint8_t bus2){UNUSED(bus1); UNUSED(bus2);} void pwm_init(TIM_TypeDef *TIM, uint8_t channel); void pwm_set(TIM_TypeDef *TIM, uint8_t channel, uint8_t percentage); +// No UART support in bootloader +typedef struct uart_ring {} uart_ring; +uart_ring uart_ring_som_debug; +void uart_init(uart_ring *q, int baud) { UNUSED(q); UNUSED(baud); } // ********************* Globals ********************** uint8_t hw_type = 0; diff --git a/board/can_comms.h b/board/can_comms.h new file mode 100644 index 0000000000..02bc0aef31 --- /dev/null +++ b/board/can_comms.h @@ -0,0 +1,117 @@ +/* + CAN transactions to and from the host come in the form of + a certain number of CANPacket_t. The transaction is split + into multiple transfers or chunks. + + * comms_can_read outputs this buffer in chunks of a specified length. + chunks are always the given length, except the last one. + * comms_can_write reads in this buffer in chunks. + * both functions maintain an overflow buffer for a partial CANPacket_t that + spans multiple transfers/chunks. + * the overflow buffers are reset by a dedicated control transfer handler, + which is sent by the host on each start of a connection. +*/ + +typedef struct { + uint32_t ptr; + uint32_t tail_size; + uint8_t data[72]; +} asm_buffer; + +asm_buffer can_read_buffer = {.ptr = 0U, .tail_size = 0U}; + +int comms_can_read(uint8_t *data, uint32_t max_len) { + uint32_t pos = 0U; + + // Send tail of previous message if it is in buffer + if (can_read_buffer.ptr > 0U) { + uint32_t overflow_len = MIN(max_len - pos, can_read_buffer.ptr); + (void)memcpy(&data[pos], can_read_buffer.data, overflow_len); + pos += overflow_len; + (void)memcpy(can_read_buffer.data, &can_read_buffer.data[overflow_len], can_read_buffer.ptr - overflow_len); + can_read_buffer.ptr -= overflow_len; + } + + if (can_read_buffer.ptr == 0U) { + // Fill rest of buffer with new data + 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; + } + } + } + + return pos; +} + +asm_buffer can_write_buffer = {.ptr = 0U, .tail_size = 0U}; + +// send on CAN +void comms_can_write(uint8_t *data, uint32_t len) { + uint32_t pos = 0U; + + // Assembling can message with data from buffer + if (can_write_buffer.ptr != 0U) { + if (can_write_buffer.tail_size <= (len - pos)) { + // we have enough data to complete the buffer + CANPacket_t to_push; + (void)memcpy(&can_write_buffer.data[can_write_buffer.ptr], &data[pos], can_write_buffer.tail_size); + can_write_buffer.ptr += can_write_buffer.tail_size; + pos += can_write_buffer.tail_size; + + // send out + (void)memcpy(&to_push, can_write_buffer.data, can_write_buffer.ptr); + can_send(&to_push, to_push.bus, false); + + // reset overflow buffer + can_write_buffer.ptr = 0U; + can_write_buffer.tail_size = 0U; + } else { + // maybe next time + uint32_t data_size = len - pos; + (void) memcpy(&can_write_buffer.data[can_write_buffer.ptr], &data[pos], data_size); + can_write_buffer.tail_size -= data_size; + can_write_buffer.ptr += data_size; + pos += data_size; + } + } + + // rest of the message + 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; + } + } +} + +void comms_can_reset(void) { + can_write_buffer.ptr = 0U; + can_write_buffer.tail_size = 0U; + can_read_buffer.ptr = 0U; + can_read_buffer.tail_size = 0U; +} + +// 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/can_definitions.h b/board/can_definitions.h index 0de2da8a31..daae4d0443 100644 --- a/board/can_definitions.h +++ b/board/can_definitions.h @@ -1,25 +1,34 @@ -#include "dlc_to_len.h" +#pragma once + +const uint8_t PANDA_CAN_CNT = 3U; +const uint8_t PANDA_BUS_CNT = 4U; + +// bump this when changing the CAN packet +#define CAN_PACKET_VERSION 4 + +#define CANPACKET_HEAD_SIZE 6U + +#if !defined(STM32F4) && !defined(STM32F2) + #define CANFD + #define CANPACKET_DATA_SIZE_MAX 64U +#else + #define CANPACKET_DATA_SIZE_MAX 8U +#endif -#define CAN_PACKET_VERSION 2 typedef struct { unsigned char reserved : 1; unsigned char bus : 3; - unsigned char data_len_code : 4; + unsigned char data_len_code : 4; // lookup length with dlc_to_len unsigned char rejected : 1; unsigned char returned : 1; unsigned char extended : 1; unsigned int addr : 29; + unsigned char checksum; unsigned char data[CANPACKET_DATA_SIZE_MAX]; } __attribute__((packed, aligned(4))) CANPacket_t; +const unsigned char dlc_to_len[] = {0U, 1U, 2U, 3U, 4U, 5U, 6U, 7U, 8U, 12U, 16U, 20U, 24U, 32U, 48U, 64U}; + #define GET_BUS(msg) ((msg)->bus) #define GET_LEN(msg) (dlc_to_len[(msg)->data_len_code]) #define GET_ADDR(msg) ((msg)->addr) - -// Flasher and pedal use raw mailbox access -#define GET_MAILBOX_BYTE(msg, b) (((int)(b) > 3) ? (((msg)->RDHR >> (8U * ((unsigned int)(b) % 4U))) & 0xFFU) : (((msg)->RDLR >> (8U * (unsigned int)(b))) & 0xFFU)) -#define GET_MAILBOX_BYTES_04(msg) ((msg)->RDLR) -#define GET_MAILBOX_BYTES_48(msg) ((msg)->RDHR) - -#define WORD_TO_BYTE_ARRAY(dst8, src32) 0[dst8] = ((src32) & 0xFFU); 1[dst8] = (((src32) >> 8U) & 0xFFU); 2[dst8] = (((src32) >> 16U) & 0xFFU); 3[dst8] = (((src32) >> 24U) & 0xFFU) -#define BYTE_ARRAY_TO_WORD(dst32, src8) ((dst32) = 0[src8] | (1[src8] << 8U) | (2[src8] << 16U) | (3[src8] << 24U)) diff --git a/board/comms_definitions.h b/board/comms_definitions.h index 66ffe7e558..d074514de4 100644 --- a/board/comms_definitions.h +++ b/board/comms_definitions.h @@ -9,3 +9,4 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp); void comms_endpoint2_write(uint8_t *data, uint32_t len); void comms_can_write(uint8_t *data, uint32_t len); int comms_can_read(uint8_t *data, uint32_t max_len); +void comms_can_reset(void); diff --git a/board/config.h b/board/config.h index 6a25a398de..1eb7de69b5 100644 --- a/board/config.h +++ b/board/config.h @@ -1,37 +1,35 @@ -#ifndef PANDA_CONFIG_H -#define PANDA_CONFIG_H +#pragma once + +#include //#define DEBUG //#define DEBUG_UART //#define DEBUG_USB //#define DEBUG_SPI //#define DEBUG_FAULTS +//#define DEBUG_COMMS +#define CAN_INIT_TIMEOUT_MS 500U #define DEEPSLEEP_WAKEUP_DELAY 3U +#define USBPACKET_MAX_SIZE 0x40U +#define MAX_CAN_MSGS_PER_BULK_TRANSFER 51U -#define NULL ((void*)0) -#define COMPILE_TIME_ASSERT(pred) ((void)sizeof(char[1 - (2 * ((int)(!(pred))))])) - -#define MIN(a,b) \ - ({ __typeof__ (a) _a = (a); \ - __typeof__ (b) _b = (b); \ - (_a < _b) ? _a : _b; }) +// USB definitions +#define USB_VID 0xBBAAU -#define MAX(a,b) \ - ({ __typeof__ (a) _a = (a); \ - __typeof__ (b) _b = (b); \ - (_a > _b) ? _a : _b; }) - -#define ABS(a) \ - ({ __typeof__ (a) _a = (a); \ - (_a > 0) ? _a : (-_a); }) +#ifdef BOOTSTUB + #define USB_PID 0xDDEEU +#else + #define USB_PID 0xDDCCU +#endif -#include -#include "panda.h" +// platform includes #ifdef STM32H7 #include "stm32h7/stm32h7_config.h" -#else +#elif defined(STM32F2) || defined(STM32F4) #include "stm32fx/stm32fx_config.h" -#endif - +#else + // TODO: uncomment this, cppcheck complains + // building for tests + //#include "fake_stm.h" #endif diff --git a/board/dlc_to_len.h b/board/dlc_to_len.h deleted file mode 100644 index 82d743ffb8..0000000000 --- a/board/dlc_to_len.h +++ /dev/null @@ -1 +0,0 @@ -unsigned char dlc_to_len[] = {0U, 1U, 2U, 3U, 4U, 5U, 6U, 7U, 8U, 12U, 16U, 20U, 24U, 32U, 48U, 64U}; diff --git a/board/drivers/bxcan.h b/board/drivers/bxcan.h index c54f1f9cda..8c4a3fc46d 100644 --- a/board/drivers/bxcan.h +++ b/board/drivers/bxcan.h @@ -27,9 +27,9 @@ void can_set_gmlan(uint8_t bus) { switch (prev_bus) { case 1: case 2: - puts("Disable GMLAN on CAN"); + print("Disable GMLAN on CAN"); puth(prev_bus + 1U); - puts("\n"); + print("\n"); current_board->set_can_mode(CAN_MODE_NORMAL); bus_config[prev_bus].bus_lookup = prev_bus; bus_config[prev_bus].can_num_lookup = prev_bus; @@ -47,9 +47,9 @@ void can_set_gmlan(uint8_t bus) { switch (bus) { case 1: case 2: - puts("Enable GMLAN on CAN"); + print("Enable GMLAN on CAN"); puth(bus + 1U); - puts("\n"); + print("\n"); current_board->set_can_mode((bus == 1U) ? CAN_MODE_GMLAN_CAN2 : CAN_MODE_GMLAN_CAN3); bus_config[bus].bus_lookup = 3; bus_config[bus].can_num_lookup = -1; @@ -60,11 +60,11 @@ void can_set_gmlan(uint8_t bus) { case 0xFF: //-1 unsigned break; default: - puts("GMLAN can only be set on CAN2 or CAN3\n"); + print("GMLAN can only be set on CAN2 or CAN3\n"); break; } } else { - puts("GMLAN not available on black panda\n"); + print("GMLAN not available on black panda\n"); } } @@ -72,6 +72,12 @@ void update_can_health_pkt(uint8_t can_number, bool error_irq) { CAN_TypeDef *CAN = CANIF_FROM_CAN_NUM(can_number); uint32_t esr_reg = CAN->ESR; + if (error_irq) { + can_health[can_number].total_error_cnt += 1U; + CAN->MSR = CAN_MSR_ERRI; + llcan_clear_send(CAN); + } + can_health[can_number].bus_off = ((esr_reg & CAN_ESR_BOFF) >> CAN_ESR_BOFF_Pos); can_health[can_number].bus_off_cnt += can_health[can_number].bus_off; can_health[can_number].error_warning = ((esr_reg & CAN_ESR_EWGF) >> CAN_ESR_EWGF_Pos); @@ -84,11 +90,6 @@ void update_can_health_pkt(uint8_t can_number, bool error_irq) { can_health[can_number].receive_error_cnt = ((esr_reg & CAN_ESR_REC) >> CAN_ESR_REC_Pos); can_health[can_number].transmit_error_cnt = ((esr_reg & CAN_ESR_TEC) >> CAN_ESR_TEC_Pos); - - if (error_irq) { - can_health[can_number].total_error_cnt += 1U; - llcan_clear_send(CAN); - } } // CAN error @@ -126,6 +127,7 @@ void process_can(uint8_t can_number) { to_push.bus = bus_number; WORD_TO_BYTE_ARRAY(&to_push.data[0], CAN->sTxMailBox[0].TDLR); WORD_TO_BYTE_ARRAY(&to_push.data[4], CAN->sTxMailBox[0].TDHR); + can_set_checksum(&to_push); rx_buffer_overflow += can_push(&can_rx_q, &to_push) ? 0U : 1U; } @@ -136,14 +138,18 @@ void process_can(uint8_t can_number) { } if (can_pop(can_queues[bus_number], &to_send)) { - can_health[can_number].total_tx_cnt += 1U; - // only send if we have received a packet - CAN->sTxMailBox[0].TIR = ((to_send.extended != 0U) ? (to_send.addr << 3) : (to_send.addr << 21)) | (to_send.extended << 2); - CAN->sTxMailBox[0].TDTR = to_send.data_len_code; - BYTE_ARRAY_TO_WORD(CAN->sTxMailBox[0].TDLR, &to_send.data[0]); - BYTE_ARRAY_TO_WORD(CAN->sTxMailBox[0].TDHR, &to_send.data[4]); - // Send request TXRQ - CAN->sTxMailBox[0].TIR |= 0x1U; + if (can_check_checksum(&to_send)) { + can_health[can_number].total_tx_cnt += 1U; + // only send if we have received a packet + CAN->sTxMailBox[0].TIR = ((to_send.extended != 0U) ? (to_send.addr << 3) : (to_send.addr << 21)) | (to_send.extended << 2); + CAN->sTxMailBox[0].TDTR = to_send.data_len_code; + BYTE_ARRAY_TO_WORD(CAN->sTxMailBox[0].TDLR, &to_send.data[0]); + BYTE_ARRAY_TO_WORD(CAN->sTxMailBox[0].TDHR, &to_send.data[4]); + // Send request TXRQ + CAN->sTxMailBox[0].TIR |= 0x1U; + } else { + can_health[can_number].total_tx_checksum_error_cnt += 1U; + } usb_cb_ep3_out_complete(); } @@ -182,6 +188,7 @@ void can_rx(uint8_t can_number) { to_push.bus = bus_number; WORD_TO_BYTE_ARRAY(&to_push.data[0], CAN->sFIFOMailBox[0].RDLR); WORD_TO_BYTE_ARRAY(&to_push.data[4], CAN->sFIFOMailBox[0].RDHR); + can_set_checksum(&to_push); // forwarding (panda only) int bus_fwd_num = safety_fwd_hook(bus_number, &to_push); @@ -195,6 +202,8 @@ void can_rx(uint8_t can_number) { to_send.bus = to_push.bus; to_send.data_len_code = to_push.data_len_code; (void)memcpy(to_send.data, to_push.data, dlc_to_len[to_push.data_len_code]); + can_set_checksum(&to_send); + can_send(&to_send, bus_fwd_num, true); can_health[can_number].total_fwd_cnt += 1U; } diff --git a/board/drivers/can_common.h b/board/drivers/can_common.h index af2fcd6f31..9fd2743d6c 100644 --- a/board/drivers/can_common.h +++ b/board/drivers/can_common.h @@ -53,18 +53,23 @@ void process_can(uint8_t can_number); #ifdef STM32H7 __attribute__((section(".ram_d1"))) can_buffer(rx_q, 0x1000) -__attribute__((section(".ram_d1"))) can_buffer(txgmlan_q, 0x1A0) +__attribute__((section(".ram_d1"))) can_buffer(tx2_q, 0x1A0) +__attribute__((section(".ram_d2"))) can_buffer(txgmlan_q, 0x1A0) #else can_buffer(rx_q, 0x1000) +can_buffer(tx2_q, 0x1A0) can_buffer(txgmlan_q, 0x1A0) #endif can_buffer(tx1_q, 0x1A0) -can_buffer(tx2_q, 0x1A0) can_buffer(tx3_q, 0x1A0) // FIXME: // cppcheck-suppress misra-c2012-9.3 can_ring *can_queues[] = {&can_tx1_q, &can_tx2_q, &can_tx3_q, &can_txgmlan_q}; +// helpers +#define WORD_TO_BYTE_ARRAY(dst8, src32) 0[dst8] = ((src32) & 0xFFU); 1[dst8] = (((src32) >> 8U) & 0xFFU); 2[dst8] = (((src32) >> 16U) & 0xFFU); 3[dst8] = (((src32) >> 24U) & 0xFFU) +#define BYTE_ARRAY_TO_WORD(dst32, src8) ((dst32) = 0[src8] | (1[src8] << 8U) | (2[src8] << 16U) | (3[src8] << 24U)) + // ********************* interrupt safe queue ********************* bool can_pop(can_ring *q, CANPacket_t *elem) { bool ret = 0; @@ -102,21 +107,21 @@ bool can_push(can_ring *q, CANPacket_t *elem) { EXIT_CRITICAL(); if (!ret) { #ifdef DEBUG - puts("can_push to "); + print("can_push to "); if (q == &can_rx_q) { - puts("can_rx_q"); + print("can_rx_q"); } else if (q == &can_tx1_q) { - puts("can_tx1_q"); + print("can_tx1_q"); } else if (q == &can_tx2_q) { - puts("can_tx2_q"); + print("can_tx2_q"); } else if (q == &can_tx3_q) { - puts("can_tx3_q"); + print("can_tx3_q"); } else if (q == &can_txgmlan_q) { - puts("can_txgmlan_q"); + print("can_txgmlan_q"); } else { - puts("unknown"); + print("unknown"); } - puts(" failed!\n"); + print(" failed!\n"); #endif } return ret; @@ -168,7 +173,7 @@ bus_config_t bus_config[] = { void can_init_all(void) { bool ret = true; - for (uint8_t i=0U; i < CAN_CNT; i++) { + for (uint8_t i=0U; i < PANDA_CAN_CNT; i++) { if (!current_board->has_canfd) { bus_config[i].can_data_speed = 0U; } @@ -190,24 +195,25 @@ void ignition_can_hook(CANPacket_t *to_push) { int addr = GET_ADDR(to_push); int len = GET_LEN(to_push); - ignition_can_cnt = 0U; // reset counter - if (bus == 0) { // GM exception if ((addr == 0x160) && (len == 5)) { // this message isn't all zeros when ignition is on ignition_can = GET_BYTES_04(to_push) != 0U; + ignition_can_cnt = 0U; } // Tesla exception if ((addr == 0x348) && (len == 8)) { // GTW_status ignition_can = (GET_BYTE(to_push, 0) & 0x1U) != 0U; + ignition_can_cnt = 0U; } // Mazda exception if ((addr == 0x9E) && (len == 8)) { ignition_can = (GET_BYTE(to_push, 0) >> 5) == 0x6U; + ignition_can_cnt = 0U; } } @@ -221,9 +227,26 @@ bool can_tx_check_min_slots_free(uint32_t min) { (can_slots_empty(&can_txgmlan_q) >= min); } +uint8_t calculate_checksum(uint8_t *dat, uint32_t len) { + uint8_t checksum = 0U; + for (uint32_t i = 0U; i < len; i++) { + checksum ^= dat[i]; + } + return checksum; +} + +void can_set_checksum(CANPacket_t *packet) { + packet->checksum = 0U; + packet->checksum = calculate_checksum((uint8_t *) packet, CANPACKET_HEAD_SIZE + GET_LEN(packet)); +} + +bool can_check_checksum(CANPacket_t *packet) { + return (calculate_checksum((uint8_t *) packet, CANPACKET_HEAD_SIZE + GET_LEN(packet)) == 0U); +} + void can_send(CANPacket_t *to_push, uint8_t bus_number, bool skip_tx_hook) { if (skip_tx_hook || safety_tx_hook(to_push) != 0) { - if (bus_number < BUS_CNT) { + if (bus_number < PANDA_BUS_CNT) { // add CAN packet to send queue if ((bus_number == 3U) && (bus_config[3].can_num_lookup == 0xFFU)) { gmlan_send_errs += bitbang_gmlan(to_push) ? 0U : 1U; @@ -235,6 +258,9 @@ void can_send(CANPacket_t *to_push, uint8_t bus_number, bool skip_tx_hook) { } else { safety_tx_blocked += 1U; to_push->rejected = 1U; + + // data changed + can_set_checksum(to_push); rx_buffer_overflow += can_push(&can_rx_q, to_push) ? 0U : 1U; } } diff --git a/board/drivers/clock_source.h b/board/drivers/clock_source.h new file mode 100644 index 0000000000..d1b5724f16 --- /dev/null +++ b/board/drivers/clock_source.h @@ -0,0 +1,33 @@ +#define CLOCK_SOURCE_PERIOD_MS 50U +#define CLOCK_SOURCE_PULSE_LEN_MS 2U + +void clock_source_init(void) { + // Setup timer + register_set(&(TIM1->PSC), ((APB2_TIMER_FREQ*100U)-1U), 0xFFFFU); // Tick on 0.1 ms + register_set(&(TIM1->ARR), ((CLOCK_SOURCE_PERIOD_MS*10U) - 1U), 0xFFFFU); // Period + register_set(&(TIM1->CCMR1), 0U, 0xFFFFU); // No output on compare + register_set(&(TIM1->CCER), TIM_CCER_CC1E, 0xFFFFU); // Enable compare 1 + register_set(&(TIM1->CCR1), (CLOCK_SOURCE_PULSE_LEN_MS*10U), 0xFFFFU); // Compare 1 value + register_set(&(TIM1->CCR2), (CLOCK_SOURCE_PULSE_LEN_MS*10U), 0xFFFFU); // Compare 1 value + register_set(&(TIM1->CCR3), (CLOCK_SOURCE_PULSE_LEN_MS*10U), 0xFFFFU); // Compare 1 value + register_set_bits(&(TIM1->DIER), TIM_DIER_UIE | TIM_DIER_CC1IE); // Enable interrupts + register_set(&(TIM1->CR1), TIM_CR1_CEN, 0x3FU); // Enable timer + + // No interrupts + NVIC_DisableIRQ(TIM1_UP_TIM10_IRQn); + NVIC_DisableIRQ(TIM1_CC_IRQn); + + // Set GPIO as timer channels + set_gpio_alternate(GPIOB, 14, GPIO_AF1_TIM1); + set_gpio_alternate(GPIOB, 15, GPIO_AF1_TIM1); + + // Set PWM mode + register_set(&(TIM1->CCMR1), (0b110 << TIM_CCMR1_OC2M_Pos), 0xFFFFU); + register_set(&(TIM1->CCMR2), (0b110 << TIM_CCMR2_OC3M_Pos), 0xFFFFU); + + // Enable output + register_set(&(TIM1->BDTR), TIM_BDTR_MOE, 0xFFFFU); + + // Enable complementary compares + register_set_bits(&(TIM1->CCER), TIM_CCER_CC2NE | TIM_CCER_CC3NE); +} diff --git a/board/drivers/fake_siren.h b/board/drivers/fake_siren.h new file mode 100644 index 0000000000..c139cb2abc --- /dev/null +++ b/board/drivers/fake_siren.h @@ -0,0 +1,74 @@ +#include "stm32h7/lli2c.h" + +#define CODEC_I2C_ADDR 0x10 +// 1Vpp sine wave with 1V offset +const uint8_t fake_siren_lut[360] = { 134U, 135U, 137U, 138U, 139U, 140U, 141U, 143U, 144U, 145U, 146U, 148U, 149U, 150U, 151U, 152U, 154U, 155U, 156U, 157U, 158U, 159U, 160U, 162U, 163U, 164U, 165U, 166U, 167U, 168U, 169U, 170U, 171U, 172U, 174U, 175U, 176U, 177U, 177U, 178U, 179U, 180U, 181U, 182U, 183U, 184U, 185U, 186U, 186U, 187U, 188U, 189U, 190U, 190U, 191U, 192U, 193U, 193U, 194U, 195U, 195U, 196U, 196U, 197U, 197U, 198U, 199U, 199U, 199U, 200U, 200U, 201U, 201U, 202U, 202U, 202U, 203U, 203U, 203U, 203U, 204U, 204U, 204U, 204U, 204U, 204U, 204U, 205U, 205U, 205U, 205U, 205U, 205U, 205U, 204U, 204U, 204U, 204U, 204U, 204U, 204U, 203U, 203U, 203U, 203U, 202U, 202U, 202U, 201U, 201U, 200U, 200U, 199U, 199U, 199U, 198U, 197U, 197U, 196U, 196U, 195U, 195U, 194U, 193U, 193U, 192U, 191U, 190U, 190U, 189U, 188U, 187U, 186U, 186U, 185U, 184U, 183U, 182U, 181U, 180U, 179U, 178U, 177U, 177U, 176U, 175U, 174U, 172U, 171U, 170U, 169U, 168U, 167U, 166U, 165U, 164U, 163U, 162U, 160U, 159U, 158U, 157U, 156U, 155U, 154U, 152U, 151U, 150U, 149U, 148U, 146U, 145U, 144U, 143U, 141U, 140U, 139U, 138U, 137U, 135U, 134U, 133U, 132U, 130U, 129U, 128U, 127U, 125U, 124U, 123U, 122U, 121U, 119U, 118U, 117U, 116U, 115U, 113U, 112U, 111U, 110U, 109U, 108U, 106U, 105U, 104U, 103U, 102U, 101U, 100U, 99U, 98U, 97U, 96U, 95U, 94U, 93U, 92U, 91U, 90U, 89U, 88U, 87U, 86U, 85U, 84U, 83U, 82U, 82U, 81U, 80U, 79U, 78U, 78U, 77U, 76U, 76U, 75U, 74U, 74U, 73U, 72U, 72U, 71U, 71U, 70U, 70U, 69U, 69U, 68U, 68U, 67U, 67U, 67U, 66U, 66U, 66U, 65U, 65U, 65U, 65U, 64U, 64U, 64U, 64U, 64U, 64U, 64U, 64U, 64U, 63U, 64U, 64U, 64U, 64U, 64U, 64U, 64U, 64U, 64U, 65U, 65U, 65U, 65U, 66U, 66U, 66U, 67U, 67U, 67U, 68U, 68U, 69U, 69U, 70U, 70U, 71U, 71U, 72U, 72U, 73U, 74U, 74U, 75U, 76U, 76U, 77U, 78U, 78U, 79U, 80U, 81U, 82U, 82U, 83U, 84U, 85U, 86U, 87U, 88U, 89U, 90U, 91U, 92U, 93U, 94U, 95U, 96U, 97U, 98U, 99U, 100U, 101U, 102U, 103U, 104U, 105U, 106U, 108U, 109U, 110U, 111U, 112U, 113U, 115U, 116U, 117U, 118U, 119U, 121U, 122U, 123U, 124U, 125U, 127U, 128U, 129U, 130U, 132U, 133U }; + +bool fake_siren_enabled = false; + +void fake_siren_codec_enable(bool enabled) { + if(enabled) { + bool success = false; + success &= i2c_set_reg_bits(I2C5, CODEC_I2C_ADDR, 0x2B, (1U << 1)); // Left speaker mix from INA1 + success &= i2c_set_reg_bits(I2C5, CODEC_I2C_ADDR, 0x2C, (1U << 1)); // Right speaker mix from INA1 + success &= i2c_set_reg_mask(I2C5, CODEC_I2C_ADDR, 0x3D, 0x17, 0b11111); // Left speaker volume + success &= i2c_set_reg_mask(I2C5, CODEC_I2C_ADDR, 0x3E, 0x17, 0b11111); // Right speaker volume + success &= i2c_set_reg_mask(I2C5, CODEC_I2C_ADDR, 0x37, 0b101, 0b111); // INA gain + success &= i2c_set_reg_bits(I2C5, CODEC_I2C_ADDR, 0x4C, (1U << 7)); // Enable INA + success &= i2c_set_reg_bits(I2C5, CODEC_I2C_ADDR, 0x51, (1U << 7)); // Disable global shutdown + if (!success) { + print("Failed to setup the codec for fake siren\n"); + } + } else { + // Disable INA input. Make sure to retry a few times if the I2C bus is busy. + for(uint8_t i=0U; i<10U; i++) { + if (i2c_clear_reg_bits(I2C5, CODEC_I2C_ADDR, 0x4C, (1U << 7))) { + break; + } + } + } +} + + +void fake_siren_set(bool enabled) { + if (enabled != fake_siren_enabled) { + fake_siren_codec_enable(enabled); + } + + if (enabled) { + register_set_bits(&DMA1_Stream1->CR, DMA_SxCR_EN); + } else { + register_clear_bits(&DMA1_Stream1->CR, DMA_SxCR_EN); + } + fake_siren_enabled = enabled; +} + +void fake_siren_init(void) { + // Init DAC + register_set(&DAC1->MCR, 0U, 0xFFFFFFFFU); + register_set(&DAC1->CR, DAC_CR_TEN1 | (6U << DAC_CR_TSEL1_Pos) | DAC_CR_DMAEN1, 0xFFFFFFFFU); + register_set_bits(&DAC1->CR, DAC_CR_EN1); + + // Setup DMAMUX (DAC_CH1_DMA as input) + register_set(&DMAMUX1_Channel1->CCR, 67U, DMAMUX_CxCR_DMAREQ_ID_Msk); + + // Setup DMA + register_set(&DMA1_Stream1->M0AR, (uint32_t) fake_siren_lut, 0xFFFFFFFFU); + register_set(&DMA1_Stream1->PAR, (uint32_t) &(DAC1->DHR8R1), 0xFFFFFFFFU); + DMA1_Stream1->NDTR = sizeof(fake_siren_lut); + register_set(&DMA1_Stream1->FCR, 0U, 0x00000083U); + DMA1_Stream1->CR = (0b11 << DMA_SxCR_PL_Pos); + DMA1_Stream1->CR |= DMA_SxCR_MINC | DMA_SxCR_CIRC | (1 << DMA_SxCR_DIR_Pos); + + // Init trigger timer (around 2.5kHz) + register_set(&TIM7->PSC, 0U, 0xFFFFU); + register_set(&TIM7->ARR, 133U, 0xFFFFU); + register_set(&TIM7->CR2, (0b10 << TIM_CR2_MMS_Pos), TIM_CR2_MMS_Msk); + register_set(&TIM7->CR1, TIM_CR1_ARPE | TIM_CR1_URS, 0x088EU); + TIM7->SR = 0U; + TIM7->CR1 |= TIM_CR1_CEN; + + // Enable the I2C to the codec + i2c_init(I2C5); + fake_siren_codec_enable(false); +} diff --git a/board/drivers/fan.h b/board/drivers/fan.h index 5fc2c52779..cf4faa09be 100644 --- a/board/drivers/fan.h +++ b/board/drivers/fan.h @@ -5,6 +5,7 @@ struct fan_state_t { uint8_t power; float error_integral; uint8_t stall_counter; + uint8_t cooldown_counter; } fan_state_t; struct fan_state_t fan_state; @@ -15,6 +16,10 @@ void fan_set_power(uint8_t percentage){ fan_state.target_rpm = ((current_board->fan_max_rpm * MIN(100U, MAX(0U, percentage))) / 100U); } +void fan_reset_cooldown(void){ + fan_state.cooldown_counter = current_board->fan_enable_cooldown_time * 8U; +} + // Call this at 8Hz void fan_tick(void){ if (current_board->fan_max_rpm > 0U) { @@ -24,25 +29,31 @@ void fan_tick(void){ fan_state.rpm = (fan_rpm_fast + (3U * fan_state.rpm)) / 4U; // Enable fan if we want it to spin - current_board->set_fan_enabled(fan_state.target_rpm > 0U); + current_board->set_fan_enabled((fan_state.target_rpm > 0U) || (fan_state.cooldown_counter > 0U)); + if (fan_state.target_rpm > 0U) { + fan_reset_cooldown(); + } // Stall detection - if(fan_state.power > 0U) { - if (fan_rpm_fast == 0U) { - fan_state.stall_counter = MIN(fan_state.stall_counter + 1U, 255U); - } else { - fan_state.stall_counter = 0U; - } + if (current_board->fan_stall_recovery) { + if (fan_state.power > 0U) { + if (fan_rpm_fast == 0U) { + fan_state.stall_counter = MIN(fan_state.stall_counter + 1U, 255U); + } else { + fan_state.stall_counter = 0U; + } - if (fan_state.stall_counter > FAN_STALL_THRESHOLD) { - // Stall detected, power cycling fan controller - current_board->set_fan_enabled(false); + if (fan_state.stall_counter > FAN_STALL_THRESHOLD) { + // Stall detected, power cycling fan controller + current_board->set_fan_enabled(false); - // clip integral, can't fully reset otherwise we may always be stuck in stall detection - fan_state.error_integral = MIN(50.0f, MAX(0.0f, fan_state.error_integral)); + // clip integral, can't fully reset otherwise we may always be stuck in stall detection + fan_state.error_integral = MIN(50.0f, MAX(0.0f, fan_state.error_integral)); + } + } else { + fan_state.stall_counter = 0U; + fan_state.error_integral = 0.0f; } - } else { - fan_state.stall_counter = 0U; } // Update controller @@ -54,5 +65,10 @@ void fan_tick(void){ fan_state.power = MIN(100U, MAX(0U, feedforward + fan_state.error_integral)); pwm_set(TIM3, 3, fan_state.power); + + // Cooldown counter + if (fan_state.cooldown_counter > 0U) { + fan_state.cooldown_counter--; + } } } diff --git a/board/drivers/fdcan.h b/board/drivers/fdcan.h index 0d3aa16878..d8793fd66a 100644 --- a/board/drivers/fdcan.h +++ b/board/drivers/fdcan.h @@ -29,7 +29,7 @@ bool can_set_speed(uint8_t can_number) { void can_set_gmlan(uint8_t bus) { UNUSED(bus); - puts("GMLAN not available on red panda\n"); + print("GMLAN not available on red panda\n"); } // ***************************** CAN ***************************** @@ -84,37 +84,43 @@ void process_can(uint8_t can_number) { if ((CANx->TXFQS & FDCAN_TXFQS_TFQF) == 0) { CANPacket_t to_send; if (can_pop(can_queues[bus_number], &to_send)) { - can_health[can_number].total_tx_cnt += 1U; - - uint32_t TxFIFOSA = FDCAN_START_ADDRESS + (can_number * FDCAN_OFFSET) + (FDCAN_RX_FIFO_0_EL_CNT * FDCAN_RX_FIFO_0_EL_SIZE); - uint8_t tx_index = (CANx->TXFQS >> FDCAN_TXFQS_TFQPI_Pos) & 0x1F; - // only send if we have received a packet - canfd_fifo *fifo; - fifo = (canfd_fifo *)(TxFIFOSA + (tx_index * FDCAN_TX_FIFO_EL_SIZE)); - - fifo->header[0] = (to_send.extended << 30) | ((to_send.extended != 0U) ? (to_send.addr) : (to_send.addr << 18)); - fifo->header[1] = (to_send.data_len_code << 16) | (bus_config[can_number].canfd_enabled << 21) | (bus_config[can_number].brs_enabled << 20); - - uint8_t data_len_w = (dlc_to_len[to_send.data_len_code] / 4U); - data_len_w += ((dlc_to_len[to_send.data_len_code] % 4U) > 0U) ? 1U : 0U; - for (unsigned int i = 0; i < data_len_w; i++) { - BYTE_ARRAY_TO_WORD(fifo->data_word[i], &to_send.data[i*4U]); + if (can_check_checksum(&to_send)) { + can_health[can_number].total_tx_cnt += 1U; + + uint32_t TxFIFOSA = FDCAN_START_ADDRESS + (can_number * FDCAN_OFFSET) + (FDCAN_RX_FIFO_0_EL_CNT * FDCAN_RX_FIFO_0_EL_SIZE); + uint8_t tx_index = (CANx->TXFQS >> FDCAN_TXFQS_TFQPI_Pos) & 0x1F; + // only send if we have received a packet + canfd_fifo *fifo; + fifo = (canfd_fifo *)(TxFIFOSA + (tx_index * FDCAN_TX_FIFO_EL_SIZE)); + + fifo->header[0] = (to_send.extended << 30) | ((to_send.extended != 0U) ? (to_send.addr) : (to_send.addr << 18)); + fifo->header[1] = (to_send.data_len_code << 16) | (bus_config[can_number].canfd_enabled << 21) | (bus_config[can_number].brs_enabled << 20); + + uint8_t data_len_w = (dlc_to_len[to_send.data_len_code] / 4U); + data_len_w += ((dlc_to_len[to_send.data_len_code] % 4U) > 0U) ? 1U : 0U; + for (unsigned int i = 0; i < data_len_w; i++) { + BYTE_ARRAY_TO_WORD(fifo->data_word[i], &to_send.data[i*4U]); + } + + CANx->TXBAR = (1UL << tx_index); + + // Send back to USB + CANPacket_t to_push; + + to_push.returned = 1U; + to_push.rejected = 0U; + to_push.extended = to_send.extended; + to_push.addr = to_send.addr; + to_push.bus = to_send.bus; + to_push.data_len_code = to_send.data_len_code; + (void)memcpy(to_push.data, to_send.data, dlc_to_len[to_push.data_len_code]); + can_set_checksum(&to_push); + + rx_buffer_overflow += can_push(&can_rx_q, &to_push) ? 0U : 1U; + } else { + can_health[can_number].total_tx_checksum_error_cnt += 1U; } - CANx->TXBAR = (1UL << tx_index); - - // Send back to USB - CANPacket_t to_push; - - to_push.returned = 1U; - to_push.rejected = 0U; - to_push.extended = to_send.extended; - to_push.addr = to_send.addr; - to_push.bus = to_send.bus; - to_push.data_len_code = to_send.data_len_code; - (void)memcpy(to_push.data, to_send.data, dlc_to_len[to_push.data_len_code]); - rx_buffer_overflow += can_push(&can_rx_q, &to_push) ? 0U : 1U; - usb_cb_ep3_out_complete(); } } @@ -164,6 +170,7 @@ void can_rx(uint8_t can_number) { for (unsigned int i = 0; i < data_len_w; i++) { WORD_TO_BYTE_ARRAY(&to_push.data[i*4U], fifo->data_word[i]); } + can_set_checksum(&to_push); // forwarding (panda only) int bus_fwd_num = safety_fwd_hook(bus_number, &to_push); @@ -177,6 +184,8 @@ void can_rx(uint8_t can_number) { to_send.bus = to_push.bus; to_send.data_len_code = to_push.data_len_code; (void)memcpy(to_send.data, to_push.data, dlc_to_len[to_push.data_len_code]); + can_set_checksum(&to_send); + can_send(&to_send, bus_fwd_num, true); can_health[can_number].total_fwd_cnt += 1U; } diff --git a/board/drivers/gmlan_alt.h b/board/drivers/gmlan_alt.h index 2e52256911..266a683e6e 100644 --- a/board/drivers/gmlan_alt.h +++ b/board/drivers/gmlan_alt.h @@ -161,9 +161,9 @@ void gmlan_switch_init(int timeout_enable) { void set_gmlan_digital_output(int to_set) { inverted_bit_to_send = to_set; /* - puts("Writing "); + print("Writing "); puth(inverted_bit_to_send); - puts("\n"); + print("\n"); */ } @@ -207,12 +207,12 @@ void TIM12_IRQ_Handler(void) { if ((gmlan_sending > 0) && // not first bit ((read == 0) && (pkt_stuffed[gmlan_sending-1] == 1)) && // bus wrongly dominant (gmlan_sending != (gmlan_sendmax - 11))) { //not ack bit - puts("GMLAN ERR: bus driven at "); + print("GMLAN ERR: bus driven at "); puth(gmlan_sending); - puts("\n"); + print("\n"); retry = 1; } else if ((read == 1) && (gmlan_sending == (gmlan_sendmax - 11))) { // recessive during ACK - puts("GMLAN ERR: didn't recv ACK\n"); + print("GMLAN ERR: didn't recv ACK\n"); retry = 1; } else { // do not retry @@ -224,7 +224,7 @@ void TIM12_IRQ_Handler(void) { gmlan_sending = 0; gmlan_fail_count++; if (gmlan_fail_count == MAX_FAIL_COUNT) { - puts("GMLAN ERR: giving up send\n"); + print("GMLAN ERR: giving up send\n"); gmlan_send_ok = false; } } else { diff --git a/board/drivers/harness.h b/board/drivers/harness.h index 7d0f0ae3e7..c2b5ac95f8 100644 --- a/board/drivers/harness.h +++ b/board/drivers/harness.h @@ -21,9 +21,9 @@ struct harness_configuration { void set_intercept_relay(bool intercept) { if (car_harness_status != HARNESS_STATUS_NC) { if (intercept) { - puts("switching harness to intercept (relay on)\n"); + print("switching harness to intercept (relay on)\n"); } else { - puts("switching harness to passthrough (relay off)\n"); + print("switching harness to passthrough (relay off)\n"); } if(car_harness_status == HARNESS_STATUS_NORMAL){ @@ -80,7 +80,7 @@ void harness_init(void) { // try to detect orientation uint8_t ret = harness_detect_orientation(); if (ret != HARNESS_STATUS_NC) { - puts("detected car harness with orientation "); puth2(ret); puts("\n"); + print("detected car harness with orientation "); puth2(ret); print("\n"); car_harness_status = ret; // set the SBU lines to be inputs before using the relay. The lines are not 5V tolerant in ADC mode! @@ -90,6 +90,6 @@ void harness_init(void) { // keep busses connected by default set_intercept_relay(false); } else { - puts("failed to detect car harness!\n"); + print("failed to detect car harness!\n"); } } diff --git a/board/drivers/interrupts.h b/board/drivers/interrupts.h index a394985626..ad1ec5c020 100644 --- a/board/drivers/interrupts.h +++ b/board/drivers/interrupts.h @@ -11,7 +11,7 @@ uint32_t microsecond_timer_get(void); void unused_interrupt_handler(void) { // Something is wrong if this handler is called! - puts("Unused interrupt handler called!\n"); + print("Unused interrupt handler called!\n"); fault_occurred(FAULT_UNUSED_INTERRUPT_HANDLED); } @@ -47,7 +47,7 @@ void handle_interrupt(IRQn_Type irq_type){ // Check that the interrupts don't fire too often if(check_interrupt_rate && (interrupts[irq_type].call_counter > interrupts[irq_type].max_call_rate)){ - puts("Interrupt 0x"); puth(irq_type); puts(" fired too often (0x"); puth(interrupts[irq_type].call_counter); puts("/s)!\n"); + print("Interrupt 0x"); puth(irq_type); print(" fired too often (0x"); puth(interrupts[irq_type].call_counter); print("/s)!\n"); fault_occurred(interrupts[irq_type].call_rate_fault); } diff --git a/board/drivers/registers.h b/board/drivers/registers.h index 4595a56867..a5f8b0280c 100644 --- a/board/drivers/registers.h +++ b/board/drivers/registers.h @@ -35,7 +35,7 @@ void register_set(volatile uint32_t *addr, uint32_t val, uint32_t mask){ register_map[hash].check_mask |= mask; } else { #ifdef DEBUG_FAULTS - puts("Hash collision: address 0x"); puth((uint32_t) addr); puts("!\n"); + print("Hash collision: address 0x"); puth((uint32_t) addr); print("!\n"); #endif } EXIT_CRITICAL() @@ -60,11 +60,11 @@ void check_registers(void){ ENTER_CRITICAL() if((*(register_map[i].address) & register_map[i].check_mask) != (register_map[i].value & register_map[i].check_mask)){ #ifdef DEBUG_FAULTS - puts("Register at address 0x"); puth((uint32_t) register_map[i].address); puts(" is divergent!"); - puts(" Map: 0x"); puth(register_map[i].value); - puts(" Register: 0x"); puth(*(register_map[i].address)); - puts(" Mask: 0x"); puth(register_map[i].check_mask); - puts("\n"); + print("Register at address 0x"); puth((uint32_t) register_map[i].address); print(" is divergent!"); + print(" Map: 0x"); puth(register_map[i].value); + print(" Register: 0x"); puth(*(register_map[i].address)); + print(" Mask: 0x"); puth(register_map[i].check_mask); + print("\n"); #endif fault_occurred(FAULT_REGISTER_DIVERGENT); } diff --git a/board/drivers/rtc.h b/board/drivers/rtc.h index 7f18543744..df121e3e89 100644 --- a/board/drivers/rtc.h +++ b/board/drivers/rtc.h @@ -1,6 +1,3 @@ -#define RCC_BDCR_OPTIONS_LSE (RCC_BDCR_RTCEN | RCC_BDCR_RTCSEL_0 | RCC_BDCR_LSEON) -#define RCC_BDCR_OPTIONS_LSI (RCC_BDCR_RTCEN | RCC_BDCR_RTCSEL_1) - #define YEAR_OFFSET 2000U typedef struct __attribute__((packed)) timestamp_t { @@ -21,41 +18,8 @@ uint16_t from_bcd(uint8_t value){ return (((value & 0xF0U) >> 4U) * 10U) + (value & 0x0FU); } -void rtc_init(void){ - uint32_t bdcr_opts = 0U; - uint32_t bdcr_mask = 0U; - if (current_board->has_rtc_battery) { - bdcr_opts = RCC_BDCR_OPTIONS_LSE; - bdcr_mask = RCC_BDCR_MASK_LSE; - } else { - bdcr_opts = RCC_BDCR_OPTIONS_LSI; - bdcr_mask = RCC_BDCR_MASK_LSI; - RCC->CSR |= RCC_CSR_LSION; - while((RCC->CSR & RCC_CSR_LSIRDY) == 0){} - } - - // Initialize RTC module and clock if not done already. - if((RCC->BDCR & bdcr_mask) != bdcr_opts){ - puts("Initializing RTC\n"); - // Reset backup domain - register_set_bits(&(RCC->BDCR), RCC_BDCR_BDRST); - - // Disable write protection - disable_bdomain_protection(); - - // Clear backup domain reset - register_clear_bits(&(RCC->BDCR), RCC_BDCR_BDRST); - - // Set RTC options - register_set(&(RCC->BDCR), bdcr_opts, bdcr_mask); - - // Enable write protection - enable_bdomain_protection(); - } -} - void rtc_set_time(timestamp_t time){ - puts("Setting RTC time\n"); + print("Setting RTC time\n"); // Disable write protection disable_bdomain_protection(); diff --git a/board/drivers/spi.h b/board/drivers/spi.h new file mode 100644 index 0000000000..f7382e469b --- /dev/null +++ b/board/drivers/spi.h @@ -0,0 +1,165 @@ +#pragma once + +#define SPI_BUF_SIZE 1024U +#define SPI_TIMEOUT_US 10000U + +#ifdef STM32H7 +__attribute__((section(".ram_d1"))) uint8_t spi_buf_rx[SPI_BUF_SIZE]; +__attribute__((section(".ram_d1"))) uint8_t spi_buf_tx[SPI_BUF_SIZE]; +#else +uint8_t spi_buf_rx[SPI_BUF_SIZE]; +uint8_t spi_buf_tx[SPI_BUF_SIZE]; +#endif + +#define SPI_CHECKSUM_START 0xABU +#define SPI_SYNC_BYTE 0x5AU +#define SPI_HACK 0x79U +#define SPI_DACK 0x85U +#define SPI_NACK 0x1FU + +// SPI states +enum { + SPI_STATE_HEADER, + SPI_STATE_HEADER_ACK, + SPI_STATE_HEADER_NACK, + SPI_STATE_DATA_RX, + SPI_STATE_DATA_RX_ACK, + SPI_STATE_DATA_TX +}; + +uint8_t spi_state = SPI_STATE_HEADER; +uint8_t spi_endpoint; +uint16_t spi_data_len_mosi; +uint16_t spi_data_len_miso; + +#define SPI_HEADER_SIZE 7U + +// low level SPI prototypes +void llspi_init(void); +void llspi_mosi_dma(uint8_t *addr, int len); +void llspi_miso_dma(uint8_t *addr, int len); + +void spi_init(void) { + // clear buffers (for debugging) + (void)memset(spi_buf_rx, 0, SPI_BUF_SIZE); + (void)memset(spi_buf_tx, 0, SPI_BUF_SIZE); + + // platform init + llspi_init(); + + // Start the first packet! + spi_state = SPI_STATE_HEADER; + llspi_mosi_dma(spi_buf_rx, SPI_HEADER_SIZE); +} + +bool check_checksum(uint8_t *data, uint16_t len) { + // TODO: can speed this up by casting the bulk to uint32_t and xor-ing the bytes afterwards + uint8_t checksum = SPI_CHECKSUM_START; + for(uint16_t i = 0U; i < len; i++){ + checksum ^= data[i]; + } + return checksum == 0U; +} + +void spi_handle_rx(void) { + uint8_t next_rx_state = SPI_STATE_HEADER; + + // parse header + spi_endpoint = spi_buf_rx[1]; + spi_data_len_mosi = (spi_buf_rx[3] << 8) | spi_buf_rx[2]; + spi_data_len_miso = (spi_buf_rx[5] << 8) | spi_buf_rx[4]; + + if (spi_state == SPI_STATE_HEADER) { + if ((spi_buf_rx[0] == SPI_SYNC_BYTE) && check_checksum(spi_buf_rx, SPI_HEADER_SIZE)) { + // response: ACK and start receiving data portion + spi_buf_tx[0] = SPI_HACK; + next_rx_state = SPI_STATE_HEADER_ACK; + } else { + // response: NACK and reset state machine + print("- incorrect header sync or checksum "); hexdump(spi_buf_rx, SPI_HEADER_SIZE); + spi_buf_tx[0] = SPI_NACK; + next_rx_state = SPI_STATE_HEADER_NACK; + } + llspi_miso_dma(spi_buf_tx, 1); + } else if (spi_state == SPI_STATE_DATA_RX) { + // We got everything! Based on the endpoint specified, call the appropriate handler + uint16_t response_len = 0U; + bool reponse_ack = false; + if (check_checksum(&(spi_buf_rx[SPI_HEADER_SIZE]), spi_data_len_mosi + 1U)) { + if (spi_endpoint == 0U) { + if (spi_data_len_mosi >= sizeof(ControlPacket_t)) { + ControlPacket_t ctrl; + (void)memcpy(&ctrl, &spi_buf_rx[SPI_HEADER_SIZE], sizeof(ControlPacket_t)); + response_len = comms_control_handler(&ctrl, &spi_buf_tx[3]); + reponse_ack = true; + } else { + print("SPI: insufficient data for control handler\n"); + } + } else if ((spi_endpoint == 1U) || (spi_endpoint == 0x81U)) { + if (spi_data_len_mosi == 0U) { + response_len = comms_can_read(&(spi_buf_tx[3]), spi_data_len_miso); + reponse_ack = true; + } else { + print("SPI: did not expect data for can_read\n"); + } + } else if (spi_endpoint == 2U) { + comms_endpoint2_write(&spi_buf_rx[SPI_HEADER_SIZE], spi_data_len_mosi); + reponse_ack = true; + } else if (spi_endpoint == 3U) { + if (spi_data_len_mosi > 0U) { + comms_can_write(&spi_buf_rx[SPI_HEADER_SIZE], spi_data_len_mosi); + reponse_ack = true; + } else { + print("SPI: did expect data for can_write\n"); + } + } else { + print("SPI: unexpected endpoint"); puth(spi_endpoint); print("\n"); + } + } else { + // Checksum was incorrect + reponse_ack = false; + print("- incorrect data checksum\n"); + } + + // Setup response header + spi_buf_tx[0] = reponse_ack ? SPI_DACK : SPI_NACK; + spi_buf_tx[1] = response_len & 0xFFU; + spi_buf_tx[2] = (response_len >> 8) & 0xFFU; + + // Add checksum + uint8_t checksum = SPI_CHECKSUM_START; + for(uint16_t i = 0U; i < (response_len + 3U); i++) { + checksum ^= spi_buf_tx[i]; + } + spi_buf_tx[response_len + 3U] = checksum; + + // Write response + llspi_miso_dma(spi_buf_tx, response_len + 4U); + + next_rx_state = SPI_STATE_DATA_TX; + } else { + print("SPI: RX unexpected state: "); puth(spi_state); print("\n"); + } + + spi_state = next_rx_state; +} + +void spi_handle_tx(bool timed_out) { + if ((spi_state == SPI_STATE_HEADER_NACK) || timed_out) { + // Reset state + spi_state = SPI_STATE_HEADER; + llspi_mosi_dma(spi_buf_rx, SPI_HEADER_SIZE); + } else if (spi_state == SPI_STATE_HEADER_ACK) { + // ACK was sent, queue up the RX buf for the data + checksum + spi_state = SPI_STATE_DATA_RX; + llspi_mosi_dma(&spi_buf_rx[SPI_HEADER_SIZE], spi_data_len_mosi + 1U); + } else if (spi_state == SPI_STATE_DATA_TX) { + // Reset state + spi_state = SPI_STATE_HEADER; + llspi_mosi_dma(spi_buf_rx, SPI_HEADER_SIZE); + } else { + spi_state = SPI_STATE_HEADER; + llspi_mosi_dma(spi_buf_rx, SPI_HEADER_SIZE); + print("SPI: TX unexpected state: "); puth(spi_state); print("\n"); + } +} diff --git a/board/drivers/timers.h b/board/drivers/timers.h index eb05398863..d7f3f01fd0 100644 --- a/board/drivers/timers.h +++ b/board/drivers/timers.h @@ -6,7 +6,7 @@ void timer_init(TIM_TypeDef *TIM, int psc) { } void microsecond_timer_init(void) { - MICROSECOND_TIMER->PSC = (APB1_FREQ)-1U; + MICROSECOND_TIMER->PSC = (APB1_TIMER_FREQ - 1U); MICROSECOND_TIMER->CR1 = TIM_CR1_CEN; MICROSECOND_TIMER->EGR = TIM_EGR_UG; } @@ -18,7 +18,7 @@ uint32_t microsecond_timer_get(void) { void interrupt_timer_init(void) { enable_interrupt_timer(); REGISTER_INTERRUPT(INTERRUPT_TIMER_IRQ, interrupt_timer_handler, 1, FAULT_INTERRUPT_RATE_INTERRUPTS) - register_set(&(INTERRUPT_TIMER->PSC), ((uint16_t)(15.25*APB1_FREQ)-1U), 0xFFFFU); + register_set(&(INTERRUPT_TIMER->PSC), ((uint16_t)(15.25*APB1_TIMER_FREQ)-1U), 0xFFFFU); register_set(&(INTERRUPT_TIMER->DIER), TIM_DIER_UIE, 0x5F5FU); register_set(&(INTERRUPT_TIMER->CR1), TIM_CR1_CEN, 0x3FU); INTERRUPT_TIMER->SR = 0; @@ -26,6 +26,6 @@ void interrupt_timer_init(void) { } void tick_timer_init(void) { - timer_init(TICK_TIMER, (uint16_t)((15.25*APB2_FREQ)/8U)); + timer_init(TICK_TIMER, (uint16_t)((15.25*APB2_TIMER_FREQ)/8U)); NVIC_EnableIRQ(TICK_TIMER_IRQ); } diff --git a/board/drivers/uart.h b/board/drivers/uart.h index d6107a6c3f..b0fc2ed1be 100644 --- a/board/drivers/uart.h +++ b/board/drivers/uart.h @@ -53,6 +53,14 @@ UART_BUFFER(lin2, FIFO_SIZE_INT, FIFO_SIZE_INT, USART3, NULL, false) // debug = USART2 UART_BUFFER(debug, FIFO_SIZE_INT, FIFO_SIZE_INT, USART2, debug_ring_callback, false) +// SOM debug = UART7 +#ifdef STM32H7 + UART_BUFFER(som_debug, FIFO_SIZE_INT, FIFO_SIZE_INT, UART7, NULL, false) +#else + // UART7 is not available on F4 + UART_BUFFER(som_debug, 1U, 1U, NULL, NULL, false) +#endif + uart_ring *get_ring_by_number(int a) { uart_ring *ring = NULL; switch(a) { @@ -68,6 +76,9 @@ uart_ring *get_ring_by_number(int a) { case 3: ring = &uart_ring_lin2; break; + case 4: + ring = &uart_ring_som_debug; + break; default: ring = NULL; break; @@ -150,17 +161,11 @@ void clear_uart_buff(uart_ring *q) { // ************************ High-level debug functions ********************** void putch(const char a) { - if (has_external_debug_serial) { - // assuming debugging is important if there's external serial connected - while (!putc(&uart_ring_debug, a)); - - } else { - // misra-c2012-17.7: serial debug function, ok to ignore output - (void)injectc(&uart_ring_debug, a); - } + // misra-c2012-17.7: serial debug function, ok to ignore output + (void)injectc(&uart_ring_debug, a); } -void puts(const char *a) { +void print(const char *a) { for (const char *in = a; *in; in++) { if (*in == '\n') putch('\r'); putch(*in); @@ -178,7 +183,7 @@ void putui(uint32_t i) { idx--; i_copy /= 10; } while (i_copy != 0U); - puts(&str[idx + 1U]); + print(&str[idx + 1U]); } void puthx(uint32_t i, uint8_t len) { @@ -203,10 +208,10 @@ void puth4(unsigned int i) { void hexdump(const void *a, int l) { if (a != NULL) { for (int i=0; i < l; i++) { - if ((i != 0) && ((i & 0xf) == 0)) puts("\n"); + if ((i != 0) && ((i & 0xf) == 0)) print("\n"); puth2(((const unsigned char*)a)[i]); - puts(" "); + print(" "); } } - puts("\n"); + print("\n"); } diff --git a/board/drivers/usb.h b/board/drivers/usb.h index f8017addeb..1ac5b21b3d 100644 --- a/board/drivers/usb.h +++ b/board/drivers/usb.h @@ -352,7 +352,7 @@ uint8_t winusb_20_desc[WINUSB_PLATFORM_DESCRIPTOR_LENGTH] = { // current packet USB_Setup_TypeDef setup; -uint8_t usbdata[0x100]; +uint8_t usbdata[0x100] __attribute__((aligned(4))); uint8_t* ep0_txdata = NULL; uint16_t ep0_txlen = 0; bool outep3_processing = false; @@ -375,7 +375,7 @@ void *USB_ReadPacket(void *dest, uint16_t len) { void USB_WritePacket(const void *src, uint16_t len, uint32_t ep) { #ifdef DEBUG_USB - puts("writing "); + print("writing "); hexdump(src, len); #endif @@ -402,7 +402,7 @@ void USB_WritePacket(const void *src, uint16_t len, uint32_t ep) { // so use TX FIFO empty interrupt to send larger amounts of data void USB_WritePacket_EP0(uint8_t *src, uint16_t len) { #ifdef DEBUG_USB - puts("writing "); + print("writing "); hexdump(src, len); #endif @@ -505,7 +505,7 @@ void usb_setup(void) { USBx_DEVICE->DCFG |= ((setup.b.wValue.w & 0x7fU) << 4); #ifdef DEBUG_USB - puts(" set address\n"); + print(" set address\n"); #endif USB_WritePacket(0, 0, 0); @@ -515,7 +515,7 @@ void usb_setup(void) { case USB_REQ_GET_DESCRIPTOR: switch (setup.b.wValue.bw.lsb) { case USB_DESC_TYPE_DEVICE: - //puts(" writing device descriptor\n"); + //print(" writing device descriptor\n"); // set bcdDevice to hardware type device_desc[13] = hw_type; @@ -523,7 +523,7 @@ void usb_setup(void) { USB_WritePacket(device_desc, MIN(sizeof(device_desc), setup.b.wLength.w), 0); USBx_OUTEP(0)->DOEPCTL |= USB_OTG_DOEPCTL_CNAK; - //puts("D"); + //print("D"); break; case USB_DESC_TYPE_CONFIGURATION: USB_WritePacket(configuration_desc, MIN(sizeof(configuration_desc), setup.b.wLength.w), 0); @@ -660,21 +660,21 @@ void usb_irqhandler(void) { // gintsts SUSPEND? 04008428 #ifdef DEBUG_USB puth(gintsts); - puts(" "); + print(" "); /*puth(USBx->GCCFG); - puts(" ");*/ + print(" ");*/ puth(gotgint); - puts(" ep "); + print(" ep "); puth(daint); - puts(" USB interrupt!\n"); + print(" USB interrupt!\n"); #endif if ((gintsts & USB_OTG_GINTSTS_CIDSCHG) != 0) { - puts("connector ID status change\n"); + print("connector ID status change\n"); } if ((gintsts & USB_OTG_GINTSTS_ESUSP) != 0) { - puts("ESUSP detected\n"); + print("ESUSP detected\n"); } if ((gintsts & USB_OTG_GINTSTS_EOPF) != 0) { @@ -682,7 +682,7 @@ void usb_irqhandler(void) { } if ((gintsts & USB_OTG_GINTSTS_USBRST) != 0) { - puts("USB reset\n"); + print("USB reset\n"); usb_enumerated = false; usb_reset(); } @@ -692,16 +692,16 @@ void usb_irqhandler(void) { } if ((gintsts & USB_OTG_GINTSTS_ENUMDNE) != 0) { - puts("enumeration done"); + print("enumeration done"); // Full speed, ENUMSPD //puth(USBx_DEVICE->DSTS); - puts("\n"); + print("\n"); } if ((gintsts & USB_OTG_GINTSTS_OTGINT) != 0) { - puts("OTG int:"); + print("OTG int:"); puth(USBx->GOTGINT); - puts("\n"); + print("\n"); // getting ADTOCHG //USBx->GOTGINT = USBx->GOTGINT; @@ -714,13 +714,13 @@ void usb_irqhandler(void) { int status = (rxst & USB_OTG_GRXSTSP_PKTSTS) >> 17; #ifdef DEBUG_USB - puts(" RX FIFO:"); + print(" RX FIFO:"); puth(rxst); - puts(" status: "); + print(" status: "); puth(status); - puts(" len: "); + print(" len: "); puth((rxst & USB_OTG_GRXSTSP_BCNT) >> 4); - puts("\n"); + print("\n"); #endif if (status == STS_DATA_UPDT) { @@ -728,9 +728,9 @@ void usb_irqhandler(void) { int len = (rxst & USB_OTG_GRXSTSP_BCNT) >> 4; (void)USB_ReadPacket(&usbdata, len); #ifdef DEBUG_USB - puts(" data "); + print(" data "); puth(len); - puts("\n"); + print("\n"); hexdump(&usbdata, len); #endif @@ -745,9 +745,9 @@ void usb_irqhandler(void) { } else if (status == STS_SETUP_UPDT) { (void)USB_ReadPacket(&setup, 8); #ifdef DEBUG_USB - puts(" setup "); + print(" setup "); hexdump(&setup, 8); - puts("\n"); + print("\n"); #endif } else { // status is neither STS_DATA_UPDT or STS_SETUP_UPDT, skip @@ -756,9 +756,9 @@ void usb_irqhandler(void) { /*if (gintsts & USB_OTG_GINTSTS_HPRTINT) { // host - puts("HPRT:"); + print("HPRT:"); puth(USBx_HOST_PORT->HPRT); - puts("\n"); + print("\n"); if (USBx_HOST_PORT->HPRT & USB_OTG_HPRT_PCDET) { USBx_HOST_PORT->HPRT |= USB_OTG_HPRT_PRST; USBx_HOST_PORT->HPRT |= USB_OTG_HPRT_PCDET; @@ -769,16 +769,16 @@ void usb_irqhandler(void) { if ((gintsts & USB_OTG_GINTSTS_BOUTNAKEFF) || (gintsts & USB_OTG_GINTSTS_GINAKEFF)) { // no global NAK, why is this getting set? #ifdef DEBUG_USB - puts("GLOBAL NAK\n"); + print("GLOBAL NAK\n"); #endif USBx_DEVICE->DCTL |= USB_OTG_DCTL_CGONAK | USB_OTG_DCTL_CGINAK; } if ((gintsts & USB_OTG_GINTSTS_SRQINT) != 0) { // we want to do "A-device host negotiation protocol" since we are the A-device - /*puts("start request\n"); + /*print("start request\n"); puth(USBx->GOTGCTL); - puts("\n");*/ + print("\n");*/ //USBx->GUSBCFG |= USB_OTG_GUSBCFG_FDMOD; //USBx_HOST_PORT->HPRT = USB_OTG_HPRT_PPWR | USB_OTG_HPRT_PENA; //USBx->GOTGCTL |= USB_OTG_GOTGCTL_SRQ; @@ -787,22 +787,22 @@ void usb_irqhandler(void) { // out endpoint hit if ((gintsts & USB_OTG_GINTSTS_OEPINT) != 0) { #ifdef DEBUG_USB - puts(" 0:"); + print(" 0:"); puth(USBx_OUTEP(0)->DOEPINT); - puts(" 2:"); + print(" 2:"); puth(USBx_OUTEP(2)->DOEPINT); - puts(" 3:"); + print(" 3:"); puth(USBx_OUTEP(3)->DOEPINT); - puts(" "); + print(" "); puth(USBx_OUTEP(3)->DOEPCTL); - puts(" 4:"); + print(" 4:"); puth(USBx_OUTEP(4)->DOEPINT); - puts(" OUT ENDPOINT\n"); + print(" OUT ENDPOINT\n"); #endif if ((USBx_OUTEP(2)->DOEPINT & USB_OTG_DOEPINT_XFRC) != 0) { #ifdef DEBUG_USB - puts(" OUT2 PACKET XFRC\n"); + print(" OUT2 PACKET XFRC\n"); #endif USBx_OUTEP(2)->DOEPTSIZ = (1U << 19) | 0x40U; USBx_OUTEP(2)->DOEPCTL |= USB_OTG_DOEPCTL_EPENA | USB_OTG_DOEPCTL_CNAK; @@ -810,14 +810,14 @@ void usb_irqhandler(void) { if ((USBx_OUTEP(3)->DOEPINT & USB_OTG_DOEPINT_XFRC) != 0) { #ifdef DEBUG_USB - puts(" OUT3 PACKET XFRC\n"); + print(" OUT3 PACKET XFRC\n"); #endif // NAK cleared by process_can (if tx buffers have room) outep3_processing = false; usb_cb_ep3_out_complete(); } else if ((USBx_OUTEP(3)->DOEPINT & 0x2000) != 0) { #ifdef DEBUG_USB - puts(" OUT3 PACKET WTF\n"); + print(" OUT3 PACKET WTF\n"); #endif // if NAK was set trigger this, unknown interrupt // TODO: why was this here? fires when TX buffers when we can't clear NAK @@ -825,9 +825,9 @@ void usb_irqhandler(void) { // USBx_OUTEP(3)->DOEPCTL |= USB_OTG_DOEPCTL_CNAK; } else if ((USBx_OUTEP(3)->DOEPINT) != 0) { #ifdef DEBUG_USB - puts("OUTEP3 error "); + print("OUTEP3 error "); puth(USBx_OUTEP(3)->DOEPINT); - puts("\n"); + print("\n"); #endif } else { // USBx_OUTEP(3)->DOEPINT is 0, ok to skip @@ -851,11 +851,11 @@ void usb_irqhandler(void) { // interrupt endpoint hit (Page 1221) if ((gintsts & USB_OTG_GINTSTS_IEPINT) != 0) { #ifdef DEBUG_USB - puts(" "); + print(" "); puth(USBx_INEP(0)->DIEPINT); - puts(" "); + print(" "); puth(USBx_INEP(1)->DIEPINT); - puts(" IN ENDPOINT\n"); + print(" IN ENDPOINT\n"); #endif // Should likely check the EP of the IN request even if there is @@ -876,7 +876,7 @@ void usb_irqhandler(void) { // *** IN token received when TxFIFO is empty if ((USBx_INEP(1)->DIEPINT & USB_OTG_DIEPMSK_ITTXFEMSK) != 0) { #ifdef DEBUG_USB - puts(" IN PACKET QUEUE\n"); + print(" IN PACKET QUEUE\n"); #endif // TODO: always assuming max len, can we get the length? USB_WritePacket((void *)resp, comms_can_read(resp, 0x40), 1); @@ -887,7 +887,7 @@ void usb_irqhandler(void) { // *** IN token received when TxFIFO is empty if ((USBx_INEP(1)->DIEPINT & USB_OTG_DIEPMSK_ITTXFEMSK) != 0) { #ifdef DEBUG_USB - puts(" IN PACKET QUEUE\n"); + print(" IN PACKET QUEUE\n"); #endif // TODO: always assuming max len, can we get the length? int len = comms_can_read(resp, 0x40); @@ -897,13 +897,13 @@ void usb_irqhandler(void) { } break; default: - puts("current_int0_alt_setting value invalid\n"); + print("current_int0_alt_setting value invalid\n"); break; } if ((USBx_INEP(0)->DIEPINT & USB_OTG_DIEPMSK_ITTXFEMSK) != 0) { #ifdef DEBUG_USB - puts(" IN PACKET QUEUE\n"); + print(" IN PACKET QUEUE\n"); #endif if ((ep0_txlen != 0U) && ((USBx_INEP(0)->DTXFSTS & USB_OTG_DTXFSTS_INEPTFSAV) >= 0x40U)) { diff --git a/board/early_init.h b/board/early_init.h index ae9a903628..6c60b20fbb 100644 --- a/board/early_init.h +++ b/board/early_init.h @@ -48,7 +48,6 @@ void early_initialization(void) { // early GPIOs float everything early_gpio_float(); - detect_external_debug_serial(); detect_board_type(); if (enter_bootloader_mode == ENTER_BOOTLOADER_MAGIC) { diff --git a/board/fake_stm.h b/board/fake_stm.h new file mode 100644 index 0000000000..b73a4e8985 --- /dev/null +++ b/board/fake_stm.h @@ -0,0 +1,33 @@ +// minimal code to fake a panda for tests +#include +#include +#include + +#include "utils.h" + +#define CANFD +#define ALLOW_DEBUG +#define PANDA + +#define ENTER_CRITICAL() 0 +#define EXIT_CRITICAL() 0 + +void print(const char *a) { + printf("%s", a); +} + +void puth(unsigned int i) { + printf("%u", i); +} + +typedef struct { + uint32_t CNT; +} TIM_TypeDef; + +TIM_TypeDef timer; +TIM_TypeDef *MICROSECOND_TIMER = &timer; +uint32_t microsecond_timer_get(void); + +uint32_t microsecond_timer_get(void) { + return MICROSECOND_TIMER->CNT; +} diff --git a/board/faults.h b/board/faults.h index 8f6ee28fac..c5df1131b1 100644 --- a/board/faults.h +++ b/board/faults.h @@ -26,6 +26,8 @@ #define FAULT_INTERRUPT_RATE_CLOCK_SOURCE (1U << 20) #define FAULT_INTERRUPT_RATE_TICK (1U << 21) #define FAULT_INTERRUPT_RATE_EXTI (1U << 22) +#define FAULT_INTERRUPT_RATE_SPI (1U << 23) +#define FAULT_INTERRUPT_RATE_UART_7 (1U << 24) // Permanent faults #define PERMANENT_FAULTS 0U @@ -36,10 +38,10 @@ uint32_t faults = 0U; void fault_occurred(uint32_t fault) { faults |= fault; if((PERMANENT_FAULTS & fault) != 0U){ - puts("Permanent fault occurred: 0x"); puth(fault); puts("\n"); + print("Permanent fault occurred: 0x"); puth(fault); print("\n"); fault_status = FAULT_STATUS_PERMANENT; } else { - puts("Temporary fault occurred: 0x"); puth(fault); puts("\n"); + print("Temporary fault occurred: 0x"); puth(fault); print("\n"); fault_status = FAULT_STATUS_TEMPORARY; } } @@ -48,6 +50,6 @@ void fault_recovered(uint32_t fault) { if((PERMANENT_FAULTS & fault) == 0U){ faults &= ~fault; } else { - puts("Cannot recover from a permanent fault!\n"); + print("Cannot recover from a permanent fault!\n"); } } diff --git a/board/flasher.h b/board/flasher.h index 45b9d123db..97d81096e5 100644 --- a/board/flasher.h +++ b/board/flasher.h @@ -2,6 +2,8 @@ uint32_t *prog_ptr = NULL; bool unlocked = false; +void spi_init(void); + #ifdef uart_ring void debug_ring_callback(uart_ring *ring) {} #endif @@ -41,6 +43,18 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { resp[1] = 0xff; } break; + // **** 0xc1: get hardware type + case 0xc1: + resp[0] = hw_type; + resp_len = 1; + break; + // **** 0xc3: fetch MCU UID + case 0xc3: + #ifdef UID_BASE + (void)memcpy(resp, ((uint8_t *)UID_BASE), 12); + resp_len = 12; + #endif + break; // **** 0xd0: fetch serial number case 0xd0: #ifndef STM32F2 @@ -59,12 +73,12 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { // this allows reflashing of the bootstub switch (req->param1) { case 0: - puts("-> entering bootloader\n"); + print("-> entering bootloader\n"); enter_bootloader_mode = ENTER_BOOTLOADER_MAGIC; NVIC_SystemReset(); break; case 1: - puts("-> entering softloader\n"); + print("-> entering softloader\n"); enter_bootloader_mode = ENTER_SOFTLOADER_MAGIC; NVIC_SystemReset(); break; @@ -113,11 +127,11 @@ void comms_endpoint2_write(uint8_t *data, uint32_t len) { int spi_cb_rx(uint8_t *data, int len, uint8_t *data_out) { UNUSED(len); ControlPacket_t control_req; - + int resp_len = 0; switch (data[0]) { case 0: - // control transfer + // control transfer control_req.request = ((USB_Setup_TypeDef *)(data+4))->b.bRequest; control_req.param1 = ((USB_Setup_TypeDef *)(data+4))->b.wValue.w; control_req.param2 = ((USB_Setup_TypeDef *)(data+4))->b.wIndex.w; @@ -256,7 +270,7 @@ void soft_flasher_start(void) { REGISTER_INTERRUPT(CAN1_SCE_IRQn, CAN1_SCE_IRQ_Handler, CAN_INTERRUPT_RATE, FAULT_INTERRUPT_RATE_CAN_1) #endif - puts("\n\n\n************************ FLASHER START ************************\n"); + print("\n\n\n************************ FLASHER START ************************\n"); enter_bootloader_mode = 0; @@ -282,6 +296,12 @@ void soft_flasher_start(void) { // enable USB usb_init(); + // enable SPI + if (current_board->has_spi) { + gpio_spi_init(); + spi_init(); + } + // green LED on for flashing current_board->set_led(LED_GREEN, 1); diff --git a/board/get_sdk.sh b/board/get_sdk.sh deleted file mode 100755 index c0d05651a9..0000000000 --- a/board/get_sdk.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -sudo apt-get install dfu-util gcc-arm-none-eabi python3-pip -sudo pip install libusb1 pycryptodome requests diff --git a/board/get_sdk_mac.sh b/board/get_sdk_mac.sh deleted file mode 100755 index 9e4b71590f..0000000000 --- a/board/get_sdk_mac.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -# Need formula for gcc -sudo easy_install pip -/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" -brew tap ArmMbed/homebrew-formulae -brew install python dfu-util arm-none-eabi-gcc -pip install --user libusb1 pycryptodome requests diff --git a/board/health.h b/board/health.h index 32b9bdd149..afe4f7254e 100644 --- a/board/health.h +++ b/board/health.h @@ -28,7 +28,7 @@ struct __attribute__((packed)) health_t { uint8_t safety_rx_checks_invalid; }; -#define CAN_HEALTH_PACKET_VERSION 3 +#define CAN_HEALTH_PACKET_VERSION 4 typedef struct __attribute__((packed)) { uint8_t bus_off; uint32_t bus_off_cnt; @@ -46,6 +46,7 @@ typedef struct __attribute__((packed)) { uint32_t total_tx_cnt; uint32_t total_rx_cnt; uint32_t total_fwd_cnt; // Messages forwarded from one bus to another + uint32_t total_tx_checksum_error_cnt; uint16_t can_speed; uint16_t can_data_speed; uint8_t canfd_enabled; diff --git a/board/main.c b/board/main.c index 48ed4c7fe6..4620a61573 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" @@ -63,11 +64,11 @@ void set_safety_mode(uint16_t mode, uint16_t param) { uint16_t mode_copy = mode; int err = set_safety_hooks(mode_copy, param); if (err == -1) { - puts("Error: safety set mode failed. Falling back to SILENT\n"); + print("Error: safety set mode failed. Falling back to SILENT\n"); mode_copy = SAFETY_SILENT; err = set_safety_hooks(mode_copy, 0U); if (err == -1) { - puts("Error: Failed setting SILENT mode. Hanging\n"); + print("Error: Failed setting SILENT mode. Hanging\n"); while (true) { // TERMINAL ERROR: we can't continue if SILENT safety mode isn't succesfully set } @@ -154,18 +155,18 @@ void tick_handler(void) { if (loop_counter == 0U) { can_live = pending_can_live; - //puth(usart1_dma); puts(" "); puth(DMA2_Stream5->M0AR); puts(" "); puth(DMA2_Stream5->NDTR); puts("\n"); + //puth(usart1_dma); print(" "); puth(DMA2_Stream5->M0AR); print(" "); puth(DMA2_Stream5->NDTR); print("\n"); // reset this every 16th pass if ((uptime_cnt & 0xFU) == 0U) { pending_can_live = 0; } #ifdef DEBUG - puts("** blink "); - puts("rx:"); puth4(can_rx_q.r_ptr); puts("-"); puth4(can_rx_q.w_ptr); puts(" "); - puts("tx1:"); puth4(can_tx1_q.r_ptr); puts("-"); puth4(can_tx1_q.w_ptr); puts(" "); - puts("tx2:"); puth4(can_tx2_q.r_ptr); puts("-"); puth4(can_tx2_q.w_ptr); puts(" "); - puts("tx3:"); puth4(can_tx3_q.r_ptr); puts("-"); puth4(can_tx3_q.w_ptr); puts("\n"); + print("** blink "); + print("rx:"); puth4(can_rx_q.r_ptr); print("-"); puth4(can_rx_q.w_ptr); print(" "); + print("tx1:"); puth4(can_tx1_q.r_ptr); print("-"); puth4(can_tx1_q.w_ptr); print(" "); + print("tx2:"); puth4(can_tx2_q.r_ptr); print("-"); puth4(can_tx2_q.w_ptr); print(" "); + print("tx3:"); puth4(can_tx3_q.r_ptr); print("-"); puth4(can_tx3_q.w_ptr); print("\n"); #endif // set green LED to be controls allowed @@ -214,9 +215,9 @@ void tick_handler(void) { if (!heartbeat_disabled) { // if the heartbeat has been gone for a while, go to SILENT safety mode and enter power save if (heartbeat_counter >= (check_started() ? HEARTBEAT_IGNITION_CNT_ON : HEARTBEAT_IGNITION_CNT_OFF)) { - puts("device hasn't sent a heartbeat for 0x"); + print("device hasn't sent a heartbeat for 0x"); puth(heartbeat_counter); - puts(" seconds. Safety is set to SILENT mode.\n"); + print(" seconds. Safety is set to SILENT mode.\n"); if (controls_allowed_countdown > 0U) { siren_countdown = 5U; @@ -231,6 +232,7 @@ void tick_handler(void) { if (current_safety_mode != SAFETY_SILENT) { set_safety_mode(SAFETY_SILENT, 0U); } + if (power_save_status != POWER_SAVE_STATUS_ENABLED) { set_power_save_state(POWER_SAVE_STATUS_ENABLED); } @@ -238,8 +240,10 @@ void tick_handler(void) { // Also disable IR when the heartbeat goes missing current_board->set_ir_power(0U); - // If enumerated but no heartbeat (phone up, boardd not running), turn the fan on to cool the device - if(usb_enumerated){ + // TODO: need a SPI equivalent + // If enumerated but no heartbeat (phone up, boardd not running), or when the SOM GPIO is pulled high by the ABL, + // turn the fan on to cool the device + if(usb_enumerated || current_board->read_som_gpio()){ fan_set_power(50U); } else { fan_set_power(0U); @@ -312,22 +316,20 @@ int main(void) { // init early devices clock_init(); peripherals_init(); - detect_external_debug_serial(); detect_board_type(); adc_init(); // print hello - puts("\n\n\n************************ MAIN START ************************\n"); + print("\n\n\n************************ MAIN START ************************\n"); // check for non-supported board types if(hw_type == HW_TYPE_UNKNOWN){ - puts("Unsupported board type\n"); + print("Unsupported board type\n"); while (1) { /* hang */ } } - puts("Config:\n"); - puts(" Board type: "); puts(current_board->board_type); puts("\n"); - puts(has_external_debug_serial ? " Real serial\n" : " USB serial\n"); + print("Config:\n"); + print(" Board type: "); print(current_board->board_type); print("\n"); // init board current_board->init(); @@ -335,13 +337,6 @@ int main(void) { // panda has an FPU, let's use it! enable_fpu(); - // enable main uart if it's connected - if (has_external_debug_serial) { - // WEIRDNESS: without this gate around the UART, it would "crash", but only if the ESP is enabled - // assuming it's because the lines were left floating and spurious noise was on them - uart_init(&uart_ring_debug, 115200); - } - if (current_board->has_gps) { uart_init(&uart_ring_gps, 9600); } else { @@ -349,7 +344,7 @@ int main(void) { uart_init(&uart_ring_gps, 115200); } - if(current_board->has_lin){ + if (current_board->has_lin) { // enable LIN uart_init(&uart_ring_lin1, 10400); UART5->CR2 |= USART_CR2_LINEN; @@ -357,6 +352,10 @@ int main(void) { USART3->CR2 |= USART_CR2_LINEN; } + if (current_board->fan_max_rpm > 0U) { + llfan_init(); + } + microsecond_timer_init(); // init to SILENT and can silent @@ -370,12 +369,18 @@ int main(void) { tick_timer_init(); #ifdef DEBUG - puts("DEBUG ENABLED\n"); + print("DEBUG ENABLED\n"); #endif // enable USB (right before interrupts or enum can fail!) usb_init(); - puts("**** INTERRUPTS ON ****\n"); +#ifdef ENABLE_SPI + if (current_board->has_spi) { + spi_init(); + } +#endif + + print("**** INTERRUPTS ON ****\n"); enable_interrupts(); // LED should keep on blinking all the time diff --git a/board/main_comms.h b/board/main_comms.h index 640685ba07..4b7b3c5c94 100644 --- a/board/main_comms.h +++ b/board/main_comms.h @@ -9,7 +9,7 @@ int get_health_pkt(void *dat) { struct health_t * health = (struct health_t*)dat; health->uptime_pkt = uptime_cnt; - health->voltage_pkt = adc_get_voltage(); + health->voltage_pkt = adc_get_voltage(current_board->adc_scale); health->current_pkt = current_board->read_current(); //Use the GPIO pin to determine ignition or use a CAN based logic @@ -47,115 +47,11 @@ 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]); if ((len != 0U) && (ur != NULL)) { - if ((data[0] < 2U) || safety_tx_lin_hook(data[0] - 2U, &data[1], len - 1U)) { + if ((data[0] < 2U) || (data[0] >= 4U) || safety_tx_lin_hook(data[0] - 2U, &data[1], len - 1U)) { for (uint32_t i = 1; i < len; i++) { while (!putc(ur, data[i])) { // wait @@ -165,17 +61,19 @@ 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; timestamp_t t; + uint32_t time; + +#ifdef DEBUG_COMMS + print("raw control request: "); hexdump(req, sizeof(ControlPacket_t)); print("\n"); + print("- request "); puth(req->request); print("\n"); + print("- param1 "); puth(req->param1); print("\n"); + print("- param2 "); puth(req->param2); print("\n"); +#endif + switch (req->request) { // **** 0xa0: get rtc time case 0xa0: @@ -223,6 +121,15 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { t.second = req->param1; rtc_set_time(t); break; + // **** 0xa8: get microsecond timer + case 0xa8: + time = microsecond_timer_get(); + resp[0] = (time & 0x000000FFU); + resp[1] = ((time & 0x0000FF00U) >> 8U); + resp[2] = ((time & 0x00FF0000U) >> 16U); + resp[3] = ((time & 0xFF000000U) >> 24U); + resp_len = 4U; + break; // **** 0xb0: set IR power case 0xb0: current_board->set_ir_power(req->param1); @@ -241,12 +148,16 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { case 0xb3: current_board->set_phone_power(req->param1 > 0U); break; + // **** 0xc0: reset communications + case 0xc0: + comms_can_reset(); + break; // **** 0xc1: get hardware type case 0xc1: resp[0] = hw_type; resp_len = 1; break; - // **** 0xd0: fetch serial number + // **** 0xc2: CAN health stats case 0xc2: COMPILE_TIME_ASSERT(sizeof(can_health_t) <= USBPACKET_MAX_SIZE); if (req->param1 < 3U) { @@ -259,6 +170,12 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { (void)memcpy(resp, &can_health[req->param1], resp_len); } break; + // **** 0xc3: fetch MCU UID + case 0xc3: + (void)memcpy(resp, ((uint8_t *)UID_BASE), 12); + resp_len = 12; + break; + // **** 0xd0: fetch serial (aka the provisioned dongle ID) case 0xd0: // addresses are OTP if (req->param1 == 1U) { @@ -276,18 +193,18 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { case 0: // only allow bootloader entry on debug builds #ifdef ALLOW_DEBUG - puts("-> entering bootloader\n"); + print("-> entering bootloader\n"); enter_bootloader_mode = ENTER_BOOTLOADER_MAGIC; NVIC_SystemReset(); #endif break; case 1: - puts("-> entering softloader\n"); + print("-> entering softloader\n"); enter_bootloader_mode = ENTER_SOFTLOADER_MAGIC; NVIC_SystemReset(); break; default: - puts("Bootloader mode invalid\n"); + print("Bootloader mode invalid\n"); break; } break; @@ -363,7 +280,7 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { } else if (req->param2 == 2U) { can_set_gmlan(2); } else { - puts("Invalid bus num for GMLAN CAN set\n"); + print("Invalid bus num for GMLAN CAN set\n"); } } else { can_set_gmlan(-1); @@ -384,7 +301,7 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { break; // **** 0xde: set can bitrate case 0xde: - if ((req->param1 < BUS_CNT) && is_speed_valid(req->param2, speeds, sizeof(speeds)/sizeof(speeds[0]))) { + if ((req->param1 < PANDA_BUS_CNT) && is_speed_valid(req->param2, speeds, sizeof(speeds)/sizeof(speeds[0]))) { bus_config[req->param1].can_speed = req->param2; bool ret = can_init(CAN_NUM_FROM_BUS_NUM(req->param1)); UNUSED(ret); @@ -478,13 +395,13 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { // **** 0xf1: Clear CAN ring buffer. case 0xf1: if (req->param1 == 0xFFFFU) { - puts("Clearing CAN Rx queue\n"); + print("Clearing CAN Rx queue\n"); can_clear(&can_rx_q); - } else if (req->param1 < BUS_CNT) { - puts("Clearing CAN Tx queue\n"); + } else if (req->param1 < PANDA_BUS_CNT) { + print("Clearing CAN Tx queue\n"); can_clear(can_queues[req->param1]); } else { - puts("Clearing CAN CAN ring buffer failed: wrong bus number\n"); + print("Clearing CAN CAN ring buffer failed: wrong bus number\n"); } break; // **** 0xf2: Clear UART ring buffer. @@ -492,7 +409,7 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { { uart_ring * rb = get_ring_by_number(req->param1); if (rb != NULL) { - puts("Clearing UART queue.\n"); + print("Clearing UART queue.\n"); clear_uart_buff(rb); } break; @@ -517,10 +434,6 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { } } break; - // **** 0xf5: set clock source mode - case 0xf5: - current_board->set_clock_source_mode(req->param1); - break; // **** 0xf6: set siren enabled case 0xf6: siren_enabled = (req->param1 != 0U); @@ -537,7 +450,7 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { break; // **** 0xf9: set CAN FD data bitrate case 0xf9: - if ((req->param1 < CAN_CNT) && + if ((req->param1 < PANDA_CAN_CNT) && current_board->has_canfd && is_speed_valid(req->param2, data_speeds, sizeof(data_speeds)/sizeof(data_speeds[0]))) { bus_config[req->param1].can_data_speed = req->param2; @@ -553,16 +466,16 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { break; // **** 0xfc: set CAN FD non-ISO mode case 0xfc: - if ((req->param1 < CAN_CNT) && current_board->has_canfd) { + if ((req->param1 < PANDA_CAN_CNT) && current_board->has_canfd) { bus_config[req->param1].canfd_non_iso = (req->param2 != 0U); bool ret = can_init(CAN_NUM_FROM_BUS_NUM(req->param1)); UNUSED(ret); } break; default: - puts("NO HANDLER "); + print("NO HANDLER "); puth(req->request); - puts("\n"); + print("\n"); break; } return resp_len; diff --git a/board/main_declarations.h b/board/main_declarations.h index c1fc87035d..4570c6e67d 100644 --- a/board/main_declarations.h +++ b/board/main_declarations.h @@ -1,8 +1,9 @@ // ******************** Prototypes ******************** -void puts(const char *a); +void print(const char *a); void puth(unsigned int i); void puth2(unsigned int i); void puth4(unsigned int i); +void hexdump(const void *a, int l); typedef struct board board; typedef struct harness_configuration harness_configuration; void can_flip_buses(uint8_t bus1, uint8_t bus2); diff --git a/board/panda.h b/board/panda.h deleted file mode 100644 index b6d611b2ca..0000000000 --- a/board/panda.h +++ /dev/null @@ -1,26 +0,0 @@ -// USB definitions -#define USB_VID 0xBBAAU - -#ifdef BOOTSTUB - #define USB_PID 0xDDEEU -#else - #define USB_PID 0xDDCCU -#endif - -#define USBPACKET_MAX_SIZE 0x40U - -#define MAX_CAN_MSGS_PER_BULK_TRANSFER 51U -#define MAX_EP1_CHUNK_PER_BULK_TRANSFER 16256U // max data stream chunk in bytes, shouldn't be higher than 16320 or counter will overflow - -// CAN definitions -#define CANPACKET_HEAD_SIZE 5U - -#if !defined(STM32F4) && !defined(STM32F2) - #define CANPACKET_DATA_SIZE_MAX 64U -#else - #define CANPACKET_DATA_SIZE_MAX 8U -#endif - -#define CAN_CNT 3U -#define BUS_CNT 4U -#define CAN_INIT_TIMEOUT_MS 500U diff --git a/board/pedal/main.c b/board/pedal/main.c index 757361bb5d..71df7fe4e2 100644 --- a/board/pedal/main.c +++ b/board/pedal/main.c @@ -11,7 +11,7 @@ #include "drivers/usb.h" #else // no serial either - void puts(const char *a) { + void print(const char *a) { UNUSED(a); } void puth(unsigned int i) { @@ -78,9 +78,9 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { } break; default: - puts("NO HANDLER "); + print("NO HANDLER "); puth(req->request); - puts("\n"); + print("\n"); break; } return resp_len; @@ -122,7 +122,7 @@ const uint8_t crc_poly = 0xD5U; // standard crc8 void CAN1_RX0_IRQ_Handler(void) { while ((CAN->RF0R & CAN_RF0R_FMP0) != 0) { #ifdef DEBUG - puts("CAN RX\n"); + print("CAN RX\n"); #endif int address = CAN->sFIFOMailBox[0].RIR >> 21; if (address == CAN_GAS_INPUT) { @@ -135,7 +135,7 @@ void CAN1_RX0_IRQ_Handler(void) { enter_bootloader_mode = ENTER_BOOTLOADER_MAGIC; NVIC_SystemReset(); } else { - puts("Failed entering Softloader or Bootloader\n"); + print("Failed entering Softloader or Bootloader\n"); } } @@ -151,9 +151,9 @@ void CAN1_RX0_IRQ_Handler(void) { if (crc_checksum(dat, CAN_GAS_SIZE - 1, crc_poly) == dat[5]) { if (((current_index + 1U) & COUNTER_CYCLE) == index) { #ifdef DEBUG - puts("setting gas "); + print("setting gas "); puth(value_0); - puts("\n"); + print("\n"); #endif if (enable) { gas_set_0 = value_0; @@ -196,11 +196,11 @@ int led_value = 0; void TIM3_IRQ_Handler(void) { #ifdef DEBUG puth(TIM3->CNT); - puts(" "); + print(" "); puth(pdl0); - puts(" "); + print(" "); puth(pdl1); - puts("\n"); + print("\n"); #endif // check timer for sending the user pedal and clearing the CAN @@ -222,7 +222,7 @@ void TIM3_IRQ_Handler(void) { // old can packet hasn't sent! state = FAULT_SEND; #ifdef DEBUG - puts("CAN MISS\n"); + print("CAN MISS\n"); #endif } @@ -275,7 +275,6 @@ int main(void) { // init devices clock_init(); peripherals_init(); - detect_external_debug_serial(); detect_board_type(); // init board @@ -293,7 +292,7 @@ int main(void) { // init can bool llcan_speed_set = llcan_set_speed(CAN1, 5000, false, false); if (!llcan_speed_set) { - puts("Failed to set llcan speed"); + print("Failed to set llcan speed"); } bool ret = llcan_init(CAN1); @@ -305,7 +304,7 @@ int main(void) { watchdog_init(); - puts("**** INTERRUPTS ON ****\n"); + print("**** INTERRUPTS ON ****\n"); enable_interrupts(); // main pedal loop diff --git a/board/pedal/main_declarations.h b/board/pedal/main_declarations.h index 339a67d359..33e4227357 100644 --- a/board/pedal/main_declarations.h +++ b/board/pedal/main_declarations.h @@ -1,5 +1,5 @@ // ******************** Prototypes ******************** -void puts(const char *a); +void print(const char *a); void puth(unsigned int i); void puth2(unsigned int i); void puth4(unsigned int i); diff --git a/board/power_saving.h b/board/power_saving.h index 52c65028a4..cc271e5da5 100644 --- a/board/power_saving.h +++ b/board/power_saving.h @@ -12,14 +12,14 @@ void set_power_save_state(int state) { if (is_valid_state && (state != power_save_status)) { bool enable = false; if (state == POWER_SAVE_STATUS_ENABLED) { - puts("enable power savings\n"); + print("enable power savings\n"); if (current_board->has_gps) { const char UBLOX_SLEEP_MSG[] = "\xb5\x62\x06\x04\x04\x00\x01\x00\x08\x00\x17\x78"; uart_ring *ur = get_ring_by_number(1); for (unsigned int i = 0; i < sizeof(UBLOX_SLEEP_MSG) - 1U; i++) while (!putc(ur, UBLOX_SLEEP_MSG[i])); } } else { - puts("disable power savings\n"); + print("disable power savings\n"); if (current_board->has_gps) { const char UBLOX_WAKE_MSG[] = "\xb5\x62\x06\x04\x04\x00\x01\x00\x09\x00\x18\x7a"; uart_ring *ur = get_ring_by_number(1); diff --git a/board/provision.h b/board/provision.h index 8421794423..e56205bffe 100644 --- a/board/provision.h +++ b/board/provision.h @@ -1,3 +1,6 @@ +// this is where we manage the dongle ID assigned during our +// manufacturing. aside from this, there's a UID for the MCU + #define PROVISION_CHUNK_LEN 0x20 void get_provision_chunk(uint8_t *resp) { diff --git a/board/safety.h b/board/safety.h index d9f0753e89..45f76a3332 100644 --- a/board/safety.h +++ b/board/safety.h @@ -1,4 +1,5 @@ #include "safety_declarations.h" +#include "can_definitions.h" // include the safety policies. #include "safety/safety_defaults.h" @@ -18,10 +19,6 @@ #include "safety/safety_elm327.h" #include "safety/safety_body.h" -#ifdef STM32H7 -#define CANFD -#endif - // CAN-FD only safety modes #ifdef CANFD #include "safety/safety_hyundai_canfd.h" @@ -73,7 +70,7 @@ int safety_rx_hook(CANPacket_t *to_push) { } int safety_tx_hook(CANPacket_t *to_send) { - return (relay_malfunction ? -1 : current_hooks->tx(to_send, get_longitudinal_allowed())); + return (relay_malfunction ? -1 : current_hooks->tx(to_send)); } int safety_tx_lin_hook(int lin_num, uint8_t *data, int len) { @@ -199,7 +196,7 @@ void update_counter(AddrCheckStruct addr_list[], int index, uint8_t counter) { bool is_msg_valid(AddrCheckStruct addr_list[], int index) { bool valid = true; if (index != -1) { - if ((!addr_list[index].valid_checksum) || (addr_list[index].wrong_counters >= MAX_WRONG_COUNTERS)) { + if (!addr_list[index].valid_checksum || !addr_list[index].valid_quality_flag || (addr_list[index].wrong_counters >= MAX_WRONG_COUNTERS)) { valid = false; controls_allowed = 0; } @@ -218,7 +215,8 @@ bool addr_safety_check(CANPacket_t *to_push, const addr_checks *rx_checks, uint32_t (*get_checksum)(CANPacket_t *to_push), uint32_t (*compute_checksum)(CANPacket_t *to_push), - uint8_t (*get_counter)(CANPacket_t *to_push)) { + uint8_t (*get_counter)(CANPacket_t *to_push), + bool (*get_quality_flag_valid)(CANPacket_t *to_push)) { int index = get_addr_check_index(to_push, rx_checks->check, rx_checks->len); update_addr_timestamp(rx_checks->check, index); @@ -240,6 +238,13 @@ bool addr_safety_check(CANPacket_t *to_push, } else { rx_checks->check[index].wrong_counters = 0U; } + + // quality flag check + if ((get_quality_flag_valid != NULL) && rx_checks->check[index].msg[rx_checks->check[index].index].quality_flag) { + rx_checks->check[index].valid_quality_flag = get_quality_flag_valid(to_push); + } else { + rx_checks->check[index].valid_quality_flag = true; + } } return is_msg_valid(rx_checks->check, index); } @@ -486,6 +491,41 @@ float interpolate(struct lookup_t xy, float x) { return ret; } +// Safety checks for longitudinal actuation +bool longitudinal_accel_checks(int desired_accel, const LongitudinalLimits limits) { + bool violation = false; + if (!get_longitudinal_allowed()) { + violation |= desired_accel != limits.inactive_accel; + } else { + violation |= max_limit_check(desired_accel, limits.max_accel, limits.min_accel); + } + return violation; +} + +bool longitudinal_speed_checks(int desired_speed, const LongitudinalLimits limits) { + return !get_longitudinal_allowed() && (desired_speed != limits.inactive_speed); +} + +bool longitudinal_gas_checks(int desired_gas, const LongitudinalLimits limits) { + bool violation = false; + if (!get_longitudinal_allowed()) { + violation |= desired_gas != limits.inactive_gas; + } else { + violation |= max_limit_check(desired_gas, limits.max_gas, limits.min_gas); + } + return violation; +} + +bool longitudinal_brake_checks(int desired_brake, const LongitudinalLimits limits) { + bool violation = false; + violation |= !get_longitudinal_allowed() && (desired_brake != 0); + violation |= desired_brake > limits.max_brake; + return violation; +} + +bool longitudinal_interceptor_checks(CANPacket_t *to_send) { + return !get_longitudinal_allowed() && (GET_BYTE(to_send, 0) || GET_BYTE(to_send, 1)); +} // Safety checks for torque-based steering commands bool steer_torque_cmd_checks(int desired_torque, int steer_req, const SteeringLimits limits) { @@ -573,6 +613,37 @@ bool steer_torque_cmd_checks(int desired_torque, int steer_req, const SteeringLi return violation; } +// Safety checks for angle-based steering commands +bool steer_angle_cmd_checks(int desired_angle, bool steer_control_enabled, const SteeringLimits limits) { + bool violation = false; + + if (controls_allowed && steer_control_enabled) { + // convert floating point angle rate limits to integers in the scale of the desired angle on CAN, + // add 1 to not false trigger the violation. also fudge the speed by 1 m/s so rate limits are + // always slightly above openpilot's in case we read an updated speed in between angle commands + // TODO: this speed fudge can be much lower, look at data to determine the lowest reasonable offset + int delta_angle_up = (interpolate(limits.angle_rate_up_lookup, vehicle_speed - 1.) * limits.angle_deg_to_can) + 1.; + int delta_angle_down = (interpolate(limits.angle_rate_down_lookup, vehicle_speed - 1.) * limits.angle_deg_to_can) + 1.; + + int highest_desired_angle = desired_angle_last + ((desired_angle_last > 0) ? delta_angle_up : delta_angle_down); + int lowest_desired_angle = desired_angle_last - ((desired_angle_last >= 0) ? delta_angle_down : delta_angle_up); + + // check for violation; + violation |= max_limit_check(desired_angle, highest_desired_angle, lowest_desired_angle); + } + desired_angle_last = desired_angle; + + // Angle should be the same as current angle while not steering + violation |= (!controls_allowed && + ((desired_angle < (angle_meas.min - 1)) || + (desired_angle > (angle_meas.max + 1)))); + + // No angle control allowed when controls are not allowed + violation |= !controls_allowed && steer_control_enabled; + + return violation; +} + void pcm_cruise_check(bool cruise_engaged) { // Enter controls on rising edge of stock ACC, exit controls if stock ACC disengages if (!cruise_engaged) { diff --git a/board/safety/safety_body.h b/board/safety/safety_body.h index 473946a6da..a4d21ef011 100644 --- a/board/safety/safety_body.h +++ b/board/safety/safety_body.h @@ -9,15 +9,14 @@ addr_checks body_rx_checks = {body_addr_checks, BODY_ADDR_CHECK_LEN}; static int body_rx_hook(CANPacket_t *to_push) { - bool valid = addr_safety_check(to_push, &body_rx_checks, NULL, NULL, NULL); + bool valid = addr_safety_check(to_push, &body_rx_checks, NULL, NULL, NULL, NULL); controls_allowed = valid; return valid; } -static int body_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { - UNUSED(longitudinal_allowed); +static int body_tx_hook(CANPacket_t *to_send) { int tx = 0; int addr = GET_ADDR(to_send); @@ -30,8 +29,8 @@ static int body_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { if (msg_allowed(to_send, BODY_TX_MSGS, sizeof(BODY_TX_MSGS)/sizeof(BODY_TX_MSGS[0])) && controls_allowed) { tx = 1; } - - // Allow going into CAN flashing mode even if controls are not allowed + + // Allow going into CAN flashing mode even if controls are not allowed if (!controls_allowed && ((uint32_t)GET_BYTES_04(to_send) == 0xdeadfaceU) && ((uint32_t)GET_BYTES_48(to_send) == 0x0ab00b1eU)) { tx = 1; } diff --git a/board/safety/safety_chrysler.h b/board/safety/safety_chrysler.h index b158cf8a4b..694d034501 100644 --- a/board/safety/safety_chrysler.h +++ b/board/safety/safety_chrysler.h @@ -9,7 +9,7 @@ const SteeringLimits CHRYSLER_STEERING_LIMITS = { }; const SteeringLimits CHRYSLER_RAM_DT_STEERING_LIMITS = { - .max_steer = 261, + .max_steer = 350, .max_rt_delta = 112, .max_rt_interval = 250000, .max_rate_up = 6, @@ -180,7 +180,7 @@ static int chrysler_rx_hook(CANPacket_t *to_push) { bool valid = addr_safety_check(to_push, &chrysler_rx_checks, chrysler_get_checksum, chrysler_compute_checksum, - chrysler_get_counter); + chrysler_get_counter, NULL); const int bus = GET_BUS(to_push); const int addr = GET_ADDR(to_push); @@ -226,8 +226,7 @@ static int chrysler_rx_hook(CANPacket_t *to_push) { return valid; } -static int chrysler_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { - UNUSED(longitudinal_allowed); +static int chrysler_tx_hook(CANPacket_t *to_send) { int tx = 1; int addr = GET_ADDR(to_send); diff --git a/board/safety/safety_defaults.h b/board/safety/safety_defaults.h index e10f369d34..41c8862953 100644 --- a/board/safety/safety_defaults.h +++ b/board/safety/safety_defaults.h @@ -15,9 +15,8 @@ static const addr_checks* nooutput_init(uint16_t param) { return &default_rx_checks; } -static int nooutput_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { +static int nooutput_tx_hook(CANPacket_t *to_send) { UNUSED(to_send); - UNUSED(longitudinal_allowed); return false; } @@ -54,9 +53,8 @@ static const addr_checks* alloutput_init(uint16_t param) { return &default_rx_checks; } -static int alloutput_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { +static int alloutput_tx_hook(CANPacket_t *to_send) { UNUSED(to_send); - UNUSED(longitudinal_allowed); return true; } diff --git a/board/safety/safety_elm327.h b/board/safety/safety_elm327.h index 5535471eac..6b1e72eac3 100644 --- a/board/safety/safety_elm327.h +++ b/board/safety/safety_elm327.h @@ -1,5 +1,4 @@ -static int elm327_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { - UNUSED(longitudinal_allowed); +static int elm327_tx_hook(CANPacket_t *to_send) { int tx = 1; int addr = GET_ADDR(to_send); diff --git a/board/safety/safety_ford.h b/board/safety/safety_ford.h index 6556d957da..69bada7c90 100644 --- a/board/safety/safety_ford.h +++ b/board/safety/safety_ford.h @@ -2,6 +2,8 @@ #define MSG_EngBrakeData 0x165 // RX from PCM, for driver brake pedal and cruise state #define MSG_EngVehicleSpThrottle 0x204 // RX from PCM, for driver throttle input #define MSG_DesiredTorqBrk 0x213 // RX from ABS, for standstill state +#define MSG_BrakeSysFeatures 0x415 // RX from ABS, for vehicle speed +#define MSG_Yaw_Data_FD1 0x91 // RX from RCM, for yaw rate #define MSG_Steering_Data_FD1 0x083 // TX by OP, various driver switches and LKAS/CC buttons #define MSG_ACCDATA_3 0x18A // TX by OP, ACC/TJA user interface #define MSG_Lane_Assist_Data1 0x3CA // TX by OP, Lane Keep Assist @@ -22,7 +24,12 @@ const CanMsg FORD_TX_MSGS[] = { }; #define FORD_TX_LEN (sizeof(FORD_TX_MSGS) / sizeof(FORD_TX_MSGS[0])) +// warning: quality flags are not yet checked in openpilot's CAN parser, +// this may be the cause of blocked messages AddrCheckStruct ford_addr_checks[] = { + {.msg = {{MSG_BrakeSysFeatures, 0, 8, .check_checksum = true, .max_counter = 15U, .quality_flag=true, .expected_timestep = 20000U}, { 0 }, { 0 }}}, + {.msg = {{MSG_Yaw_Data_FD1, 0, 8, .check_checksum = true, .max_counter = 255U, .quality_flag=true, .expected_timestep = 10000U}, { 0 }, { 0 }}}, + // These messages have no counter or checksum {.msg = {{MSG_EngBrakeData, 0, 8, .expected_timestep = 100000U}, { 0 }, { 0 }}}, {.msg = {{MSG_EngVehicleSpThrottle, 0, 8, .expected_timestep = 10000U}, { 0 }, { 0 }}}, {.msg = {{MSG_DesiredTorqBrk, 0, 8, .expected_timestep = 20000U}, { 0 }, { 0 }}}, @@ -30,6 +37,77 @@ AddrCheckStruct ford_addr_checks[] = { #define FORD_ADDR_CHECK_LEN (sizeof(ford_addr_checks) / sizeof(ford_addr_checks[0])) addr_checks ford_rx_checks = {ford_addr_checks, FORD_ADDR_CHECK_LEN}; +static uint8_t ford_get_counter(CANPacket_t *to_push) { + int addr = GET_ADDR(to_push); + + uint8_t cnt; + if (addr == MSG_BrakeSysFeatures) { + // Signal: VehVActlBrk_No_Cnt + cnt = (GET_BYTE(to_push, 2) >> 2) & 0xFU; + } else if (addr == MSG_Yaw_Data_FD1) { + // Signal: VehRollYaw_No_Cnt + cnt = GET_BYTE(to_push, 5); + } else { + cnt = 0; + } + return cnt; +} + +static uint32_t ford_get_checksum(CANPacket_t *to_push) { + int addr = GET_ADDR(to_push); + + uint8_t chksum; + if (addr == MSG_BrakeSysFeatures) { + // Signal: VehVActlBrk_No_Cs + chksum = GET_BYTE(to_push, 3); + } else if (addr == MSG_Yaw_Data_FD1) { + // Signal: VehRollYawW_No_Cs + chksum = GET_BYTE(to_push, 4); + } else { + chksum = 0; + } + return chksum; +} + +static uint32_t ford_compute_checksum(CANPacket_t *to_push) { + int addr = GET_ADDR(to_push); + + uint8_t chksum = 0; + if (addr == MSG_BrakeSysFeatures) { + chksum += GET_BYTE(to_push, 0) + GET_BYTE(to_push, 1); // Veh_V_ActlBrk + chksum += GET_BYTE(to_push, 2) >> 6; // VehVActlBrk_D_Qf + chksum += (GET_BYTE(to_push, 2) >> 2) & 0xFU; // VehVActlBrk_No_Cnt + chksum = 0xFFU - chksum; + } else if (addr == MSG_Yaw_Data_FD1) { + chksum += GET_BYTE(to_push, 0) + GET_BYTE(to_push, 1); // VehRol_W_Actl + chksum += GET_BYTE(to_push, 2) + GET_BYTE(to_push, 3); // VehYaw_W_Actl + chksum += GET_BYTE(to_push, 5); // VehRollYaw_No_Cnt + chksum += GET_BYTE(to_push, 6) >> 6; // VehRolWActl_D_Qf + chksum += (GET_BYTE(to_push, 6) >> 4) & 0x3U; // VehYawWActl_D_Qf + chksum = 0xFFU - chksum; + } else { + } + + return chksum; +} + +static bool ford_get_quality_flag_valid(CANPacket_t *to_push) { + int addr = GET_ADDR(to_push); + + bool valid = false; + if (addr == MSG_BrakeSysFeatures) { + valid = (GET_BYTE(to_push, 2) >> 6) == 0x3U; // VehVActlBrk_D_Qf + } else if (addr == MSG_Yaw_Data_FD1) { + valid = (GET_BYTE(to_push, 6) >> 4) == 0xFU; // VehRolWActl_D_Qf & VehYawWActl_D_Qf + } else { + } + return valid; +} + +#define INACTIVE_CURVATURE 1000U +#define INACTIVE_CURVATURE_RATE 4096U +#define INACTIVE_PATH_OFFSET 512U +#define INACTIVE_PATH_ANGLE 1000U static bool ford_lkas_msg_check(int addr) { return (addr == MSG_ACCDATA_3) @@ -39,7 +117,8 @@ static bool ford_lkas_msg_check(int addr) { } static int ford_rx_hook(CANPacket_t *to_push) { - bool valid = addr_safety_check(to_push, &ford_rx_checks, NULL, NULL, NULL); + bool valid = addr_safety_check(to_push, &ford_rx_checks, + ford_get_checksum, ford_compute_checksum, ford_get_counter, ford_get_quality_flag_valid); if (valid && (GET_BUS(to_push) == FORD_MAIN_BUS)) { int addr = GET_ADDR(to_push); @@ -50,6 +129,12 @@ static int ford_rx_hook(CANPacket_t *to_push) { vehicle_moving = ((GET_BYTE(to_push, 3) >> 3) & 0x3U) == 0U; } + // Update vehicle speed + if (addr == MSG_BrakeSysFeatures) { + // Signal: Veh_V_ActlBrk + vehicle_speed = ((GET_BYTE(to_push, 0) << 8) | GET_BYTE(to_push, 1)) * 0.01 / 3.6; + } + // Update gas pedal if (addr == MSG_EngVehicleSpThrottle) { // Pedal position: (0.1 * val) in percent @@ -76,8 +161,7 @@ static int ford_rx_hook(CANPacket_t *to_push) { return valid; } -static int ford_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { - UNUSED(longitudinal_allowed); +static int ford_tx_hook(CANPacket_t *to_send) { int tx = 1; int addr = GET_ADDR(to_send); @@ -117,9 +201,18 @@ static int ford_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { if (addr == MSG_LateralMotionControl) { // Signal: LatCtl_D_Rq unsigned int steer_control_type = (GET_BYTE(to_send, 4) >> 2) & 0x7U; - bool steer_control_enabled = steer_control_type != 0U; + unsigned int curvature = (GET_BYTE(to_send, 0) << 3) | (GET_BYTE(to_send, 1) >> 5); + unsigned int curvature_rate = ((GET_BYTE(to_send, 1) & 0x1FU) << 8) | GET_BYTE(to_send, 2); + unsigned int path_angle = (GET_BYTE(to_send, 3) << 3) | (GET_BYTE(to_send, 4) >> 5); + unsigned int path_offset = (GET_BYTE(to_send, 5) << 2) | (GET_BYTE(to_send, 6) >> 6); + + // These signals are not yet tested with the current safety limits + if ((curvature_rate != INACTIVE_CURVATURE_RATE) || (path_angle != INACTIVE_PATH_ANGLE) || (path_offset != INACTIVE_PATH_OFFSET)) { + tx = 0; + } // No steer control allowed when controls are not allowed + bool steer_control_enabled = (steer_control_type != 0U) || (curvature != INACTIVE_CURVATURE); if (!controls_allowed && steer_control_enabled) { tx = 0; } diff --git a/board/safety/safety_gm.h b/board/safety/safety_gm.h index 80aafef428..176a4e1c63 100644 --- a/board/safety/safety_gm.h +++ b/board/safety/safety_gm.h @@ -1,26 +1,31 @@ -// board enforces -// in-state -// accel set/resume -// out-state -// cancel button -// regen paddle -// accel rising edge -// brake rising edge -// brake > 0mph - -const int GM_MAX_STEER = 300; -const int GM_MAX_RT_DELTA = 128; // max delta torque allowed for real time checks -const uint32_t GM_RT_INTERVAL = 250000; // 250ms between real time checks -const int GM_MAX_RATE_UP = 7; -const int GM_MAX_RATE_DOWN = 17; -const int GM_DRIVER_TORQUE_ALLOWANCE = 50; -const int GM_DRIVER_TORQUE_FACTOR = 4; +const SteeringLimits GM_STEERING_LIMITS = { + .max_steer = 300, + .max_rate_up = 10, + .max_rate_down = 25, + .driver_torque_allowance = 50, + .driver_torque_factor = 4, + .max_rt_delta = 128, + .max_rt_interval = 250000, + .type = TorqueDriverLimited, +}; -const int GM_STANDSTILL_THRSLD = 10; // 0.311kph +const LongitudinalLimits GM_ASCM_LONG_LIMITS = { + .max_gas = 3072, + .min_gas = 1404, + .inactive_gas = 1404, + .max_brake = 400, +}; -const int GM_MAX_GAS = 3072; -const int GM_MAX_REGEN = 1404; -const int GM_MAX_BRAKE = 400; +const LongitudinalLimits GM_CAM_LONG_LIMITS = { + .max_gas = 3400, + .min_gas = 1514, + .inactive_gas = 1554, + .max_brake = 400, +}; + +const LongitudinalLimits *gm_long_limits; + +const int GM_STANDSTILL_THRSLD = 10; // 0.311kph const CanMsg GM_ASCM_TX_MSGS[] = {{384, 0, 4}, {1033, 0, 7}, {1034, 0, 7}, {715, 0, 8}, {880, 0, 6}, // pt bus {161, 1, 7}, {774, 1, 8}, {776, 1, 7}, {784, 1, 2}, // obs bus @@ -28,7 +33,10 @@ const CanMsg GM_ASCM_TX_MSGS[] = {{384, 0, 4}, {1033, 0, 7}, {1034, 0, 7}, {715, {0x104c006c, 3, 3}, {0x10400060, 3, 5}}; // gmlan const CanMsg GM_CAM_TX_MSGS[] = {{384, 0, 4}, // pt bus - {481, 2, 7}}; // camera bus + {481, 2, 7}, {388, 2, 8}}; // camera bus + +const CanMsg GM_CAM_LONG_TX_MSGS[] = {{384, 0, 4}, {789, 0, 5}, {715, 0, 8}, {880, 0, 6}, // pt bus + {388, 2, 8}}; // camera bus // TODO: do checksum and counter checks. Add correct timestep, 0.1s for now. AddrCheckStruct gm_addr_checks[] = { @@ -39,11 +47,13 @@ AddrCheckStruct gm_addr_checks[] = { {190, 0, 7, .expected_timestep = 100000U}, // Bolt EUV {190, 0, 8, .expected_timestep = 100000U}}}, // Escalade {.msg = {{452, 0, 8, .expected_timestep = 100000U}, { 0 }, { 0 }}}, + {.msg = {{201, 0, 8, .expected_timestep = 100000U}, { 0 }, { 0 }}}, }; #define GM_RX_CHECK_LEN (sizeof(gm_addr_checks) / sizeof(gm_addr_checks[0])) addr_checks gm_rx_checks = {gm_addr_checks, GM_RX_CHECK_LEN}; const uint16_t GM_PARAM_HW_CAM = 1; +const uint16_t GM_PARAM_HW_CAM_LONG = 2; enum { GM_BTN_UNPRESS = 1, @@ -53,11 +63,12 @@ enum { }; enum {GM_ASCM, GM_CAM} gm_hw = GM_ASCM; +bool gm_cam_long = false; bool gm_pcm_cruise = false; static int gm_rx_hook(CANPacket_t *to_push) { - bool valid = addr_safety_check(to_push, &gm_rx_checks, NULL, NULL, NULL); + bool valid = addr_safety_check(to_push, &gm_rx_checks, NULL, NULL, NULL, NULL); if (valid && (GET_BUS(to_push) == 0U)) { int addr = GET_ADDR(to_push); @@ -80,29 +91,29 @@ static int gm_rx_hook(CANPacket_t *to_push) { if ((addr == 481) && !gm_pcm_cruise) { int button = (GET_BYTE(to_push, 5) & 0x70U) >> 4; + // enter controls on falling edge of set or rising edge of resume (avoids fault) + bool set = (button != GM_BTN_SET) && (cruise_button_prev == GM_BTN_SET); + bool res = (button == GM_BTN_RESUME) && (cruise_button_prev != GM_BTN_RESUME); + if (set || res) { + controls_allowed = 1; + } + // exit controls on cancel press if (button == GM_BTN_CANCEL) { controls_allowed = 0; } - // enter controls on falling edge of set or resume - bool set = (button == GM_BTN_UNPRESS) && (cruise_button_prev == GM_BTN_SET); - bool res = (button == GM_BTN_UNPRESS) && (cruise_button_prev == GM_BTN_RESUME); - if (set || res) { - controls_allowed = 1; - } - cruise_button_prev = button; } - if (addr == 190) { - // Reference for signal and thresholds: - // https://github.com/commaai/openpilot/blob/master/selfdrive/car/gm/carstate.py - if (gm_hw == GM_ASCM) { - brake_pressed = GET_BYTE(to_push, 1) >= 8U; - } else { - brake_pressed = GET_BYTE(to_push, 1) >= 20U; - } + // Reference for brake pressed signals: + // https://github.com/commaai/openpilot/blob/master/selfdrive/car/gm/carstate.py + if ((addr == 190) && (gm_hw == GM_ASCM)) { + brake_pressed = GET_BYTE(to_push, 1) >= 8U; + } + + if ((addr == 201) && (gm_hw == GM_CAM)) { + brake_pressed = GET_BIT(to_push, 40U) != 0U; } if (addr == 452) { @@ -121,8 +132,8 @@ static int gm_rx_hook(CANPacket_t *to_push) { bool stock_ecu_detected = (addr == 384); // ASCMLKASteeringCmd - // Only check ASCMGasRegenCmd if ASCM, GM_CAM uses stock longitudinal - if ((gm_hw == GM_ASCM) && (addr == 715)) { + // Check ASCMGasRegenCmd only if we're blocking it + if (!gm_pcm_cruise && (addr == 715)) { stock_ecu_detected = true; } generic_rx_checks(stock_ecu_detected); @@ -136,36 +147,26 @@ static int gm_rx_hook(CANPacket_t *to_push) { // else // block all commands that produce actuation -static int gm_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { +static int gm_tx_hook(CANPacket_t *to_send) { int tx = 1; int addr = GET_ADDR(to_send); if (gm_hw == GM_CAM) { - tx = msg_allowed(to_send, GM_CAM_TX_MSGS, sizeof(GM_CAM_TX_MSGS)/sizeof(GM_CAM_TX_MSGS[0])); + if (gm_cam_long) { + tx = msg_allowed(to_send, GM_CAM_LONG_TX_MSGS, sizeof(GM_CAM_LONG_TX_MSGS)/sizeof(GM_CAM_LONG_TX_MSGS[0])); + } else { + tx = msg_allowed(to_send, GM_CAM_TX_MSGS, sizeof(GM_CAM_TX_MSGS)/sizeof(GM_CAM_TX_MSGS[0])); + } } else { tx = msg_allowed(to_send, GM_ASCM_TX_MSGS, sizeof(GM_ASCM_TX_MSGS)/sizeof(GM_ASCM_TX_MSGS[0])); } - // disallow actuator commands if gas or brake (with vehicle moving) are pressed - // and the the latching controls_allowed flag is True - int pedal_pressed = brake_pressed_prev && vehicle_moving; - bool alt_exp_allow_gas = alternative_experience & ALT_EXP_DISABLE_DISENGAGE_ON_GAS; - if (!alt_exp_allow_gas) { - pedal_pressed = pedal_pressed || gas_pressed_prev; - } - bool current_controls_allowed = controls_allowed && !pedal_pressed; - // BRAKE: safety check if (addr == 789) { int brake = ((GET_BYTE(to_send, 0) & 0xFU) << 8) + GET_BYTE(to_send, 1); brake = (0x1000 - brake) & 0xFFF; - if (!current_controls_allowed || !longitudinal_allowed) { - if (brake != 0) { - tx = 0; - } - } - if (brake > GM_MAX_BRAKE) { + if (longitudinal_brake_checks(brake, *gm_long_limits)) { tx = 0; } } @@ -173,70 +174,24 @@ static int gm_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { // LKA STEER: safety check if (addr == 384) { int desired_torque = ((GET_BYTE(to_send, 0) & 0x7U) << 8) + GET_BYTE(to_send, 1); - uint32_t ts = microsecond_timer_get(); - bool violation = 0; desired_torque = to_signed(desired_torque, 11); - if (current_controls_allowed) { - - // *** global torque limit check *** - violation |= max_limit_check(desired_torque, GM_MAX_STEER, -GM_MAX_STEER); - - // *** torque rate limit check *** - violation |= driver_limit_check(desired_torque, desired_torque_last, &torque_driver, - GM_MAX_STEER, GM_MAX_RATE_UP, GM_MAX_RATE_DOWN, - GM_DRIVER_TORQUE_ALLOWANCE, GM_DRIVER_TORQUE_FACTOR); - - // used next time - desired_torque_last = desired_torque; - - // *** torque real time rate limit check *** - violation |= rt_rate_limit_check(desired_torque, rt_torque_last, GM_MAX_RT_DELTA); - - // every RT_INTERVAL set the new limits - uint32_t ts_elapsed = get_ts_elapsed(ts, ts_torque_check_last); - if (ts_elapsed > GM_RT_INTERVAL) { - rt_torque_last = desired_torque; - ts_torque_check_last = ts; - } - } - - // no torque if controls is not allowed - if (!current_controls_allowed && (desired_torque != 0)) { - violation = 1; - } - - // reset to 0 if either controls is not allowed or there's a violation - if (violation || !current_controls_allowed) { - desired_torque_last = 0; - rt_torque_last = 0; - ts_torque_check_last = ts; - } - - if (violation) { + if (steer_torque_cmd_checks(desired_torque, -1, GM_STEERING_LIMITS)) { tx = 0; } } // GAS/REGEN: safety check if (addr == 715) { + bool apply = GET_BIT(to_send, 0U) != 0U; int gas_regen = ((GET_BYTE(to_send, 2) & 0x7FU) << 5) + ((GET_BYTE(to_send, 3) & 0xF8U) >> 3); - // Disabled message is !engaged with gas - // value that corresponds to max regen. - if (!current_controls_allowed || !longitudinal_allowed) { - // Stock ECU sends max regen when not enabled - if (gas_regen != GM_MAX_REGEN) { - tx = 0; - } - } - // Need to allow apply bit in pre-enabled and overriding states - if (!controls_allowed) { - bool apply = GET_BIT(to_send, 0U) != 0U; - if (apply) { - tx = 0; - } - } - if (gas_regen > GM_MAX_GAS) { + + bool violation = false; + // Allow apply bit in pre-enabled and overriding states + violation |= !controls_allowed && apply; + violation |= longitudinal_gas_checks(gas_regen, *gm_long_limits); + + if (violation) { tx = 0; } } @@ -260,15 +215,21 @@ static int gm_fwd_hook(int bus_num, CANPacket_t *to_fwd) { int bus_fwd = -1; if (gm_hw == GM_CAM) { + int addr = GET_ADDR(to_fwd); if (bus_num == 0) { - bus_fwd = 2; + // block PSCMStatus; forwarded through openpilot to hide an alert from the camera + bool is_pscm_msg = (addr == 388); + if (!is_pscm_msg) { + bus_fwd = 2; + } } if (bus_num == 2) { - // block lkas message, forward all others - int addr = GET_ADDR(to_fwd); + // block lkas message and acc messages if gm_cam_long, forward all others bool is_lkas_msg = (addr == 384); - if (!is_lkas_msg) { + bool is_acc_msg = (addr == 789) || (addr == 715) || (addr == 880); + int block_msg = is_lkas_msg || (is_acc_msg && gm_cam_long); + if (!block_msg) { bus_fwd = 0; } } @@ -279,7 +240,18 @@ static int gm_fwd_hook(int bus_num, CANPacket_t *to_fwd) { static const addr_checks* gm_init(uint16_t param) { gm_hw = GET_FLAG(param, GM_PARAM_HW_CAM) ? GM_CAM : GM_ASCM; - gm_pcm_cruise = gm_hw == GM_CAM; + + if (gm_hw == GM_ASCM) { + gm_long_limits = &GM_ASCM_LONG_LIMITS; + } else if (gm_hw == GM_CAM) { + gm_long_limits = &GM_CAM_LONG_LIMITS; + } else { + } + +#ifdef ALLOW_DEBUG + gm_cam_long = GET_FLAG(param, GM_PARAM_HW_CAM_LONG); +#endif + gm_pcm_cruise = (gm_hw == GM_CAM) && !gm_cam_long; return &gm_rx_checks; } diff --git a/board/safety/safety_honda.h b/board/safety/safety_honda.h index 1d20cfe80b..51a908b853 100644 --- a/board/safety/safety_honda.h +++ b/board/safety/safety_honda.h @@ -16,9 +16,22 @@ const CanMsg HONDA_BOSCH_LONG_TX_MSGS[] = {{0xE4, 1, 5}, {0x1DF, 1, 8}, {0x1EF, // Threshold calculated from DBC gains: round(((83.3 / 0.253984064) + (83.3 / 0.126992032)) / 2) = 492 const int HONDA_GAS_INTERCEPTOR_THRESHOLD = 492; #define HONDA_GET_INTERCEPTOR(msg) (((GET_BYTE((msg), 0) << 8) + GET_BYTE((msg), 1) + (GET_BYTE((msg), 2) << 8) + GET_BYTE((msg), 3)) / 2U) // avg between 2 tracks -const int HONDA_BOSCH_NO_GAS_VALUE = -30000; // value sent when not requesting gas -const int HONDA_BOSCH_GAS_MAX = 2000; -const int HONDA_BOSCH_ACCEL_MIN = -350; // max braking == -3.5m/s2 + +const LongitudinalLimits HONDA_BOSCH_LONG_LIMITS = { + .max_accel = 200, // accel is used for brakes + .min_accel = -350, + + .max_gas = 2000, + .min_gas = -30000, + .inactive_gas = -30000, +}; + +const LongitudinalLimits HONDA_NIDEC_LONG_LIMITS = { + .max_gas = 198, // 0xc6 + .max_brake = 255, + + .inactive_speed = 0, +}; // Nidec and bosch radarless has the powertrain bus on bus 0 AddrCheckStruct honda_common_addr_checks[] = { @@ -106,7 +119,7 @@ static uint8_t honda_get_counter(CANPacket_t *to_push) { static int honda_rx_hook(CANPacket_t *to_push) { bool valid = addr_safety_check(to_push, &honda_rx_checks, - honda_get_checksum, honda_compute_checksum, honda_get_counter); + honda_get_checksum, honda_compute_checksum, honda_get_counter, NULL); if (valid) { const bool pcm_cruise = ((honda_hw == HONDA_BOSCH) && !honda_bosch_long) || \ @@ -247,7 +260,7 @@ static int honda_rx_hook(CANPacket_t *to_push) { // else // block all commands that produce actuation -static int honda_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { +static int honda_tx_hook(CANPacket_t *to_send) { int tx = 1; int addr = GET_ADDR(to_send); @@ -263,14 +276,6 @@ static int honda_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { tx = msg_allowed(to_send, HONDA_N_TX_MSGS, sizeof(HONDA_N_TX_MSGS)/sizeof(HONDA_N_TX_MSGS[0])); } - // disallow actuator commands if gas or brake (with vehicle moving) are pressed - // and the latching controls_allowed flag is True - int pedal_pressed = brake_pressed_prev && vehicle_moving; - bool alt_exp_allow_gas = alternative_experience & ALT_EXP_DISABLE_DISENGAGE_ON_GAS; - if (!alt_exp_allow_gas) { - pedal_pressed = pedal_pressed || gas_pressed_prev; - } - bool current_controls_allowed = controls_allowed && !(pedal_pressed); int bus_pt = honda_get_pt_bus(); int bus_buttons = (honda_bosch_radarless) ? 2 : bus_pt; // the camera controls ACC on radarless Bosch cars @@ -278,22 +283,19 @@ static int honda_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { if ((addr == 0x30C) && (bus == bus_pt)) { int pcm_speed = (GET_BYTE(to_send, 0) << 8) | GET_BYTE(to_send, 1); int pcm_gas = GET_BYTE(to_send, 2); - if (!current_controls_allowed || !longitudinal_allowed) { - if ((pcm_speed != 0) || (pcm_gas != 0)) { - tx = 0; - } + + bool violation = false; + violation |= longitudinal_speed_checks(pcm_speed, HONDA_NIDEC_LONG_LIMITS); + violation |= longitudinal_gas_checks(pcm_gas, HONDA_NIDEC_LONG_LIMITS); + if (violation) { + tx = 0; } } // BRAKE: safety check (nidec) if ((addr == 0x1FA) && (bus == bus_pt)) { honda_brake = (GET_BYTE(to_send, 0) << 2) + ((GET_BYTE(to_send, 1) >> 6) & 0x3U); - if (!current_controls_allowed || !longitudinal_allowed) { - if (honda_brake != 0) { - tx = 0; - } - } - if (honda_brake > 255) { + if (longitudinal_brake_checks(honda_brake, HONDA_NIDEC_LONG_LIMITS)) { tx = 0; } if (honda_fwd_brake) { @@ -305,30 +307,21 @@ static int honda_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { if ((addr == 0x1DF) && (bus == bus_pt)) { int accel = (GET_BYTE(to_send, 3) << 3) | ((GET_BYTE(to_send, 4) >> 5) & 0x7U); accel = to_signed(accel, 11); - if (!current_controls_allowed || !longitudinal_allowed) { - if (accel != 0) { - tx = 0; - } - } - if (accel < HONDA_BOSCH_ACCEL_MIN) { - tx = 0; - } int gas = (GET_BYTE(to_send, 0) << 8) | GET_BYTE(to_send, 1); gas = to_signed(gas, 16); - if (!current_controls_allowed || !longitudinal_allowed) { - if (gas != HONDA_BOSCH_NO_GAS_VALUE) { - tx = 0; - } - } - if (gas > HONDA_BOSCH_GAS_MAX) { + + bool violation = false; + violation |= longitudinal_accel_checks(accel, HONDA_BOSCH_LONG_LIMITS); + violation |= longitudinal_gas_checks(gas, HONDA_BOSCH_LONG_LIMITS); + if (violation) { tx = 0; } } // STEER: safety check if ((addr == 0xE4) || (addr == 0x194)) { - if (!current_controls_allowed) { + if (!controls_allowed) { bool steer_applied = GET_BYTE(to_send, 0) | GET_BYTE(to_send, 1); if (steer_applied) { tx = 0; @@ -345,17 +338,15 @@ static int honda_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { // GAS: safety check (interceptor) if (addr == 0x200) { - if (!current_controls_allowed || !longitudinal_allowed) { - if (GET_BYTE(to_send, 0) || GET_BYTE(to_send, 1)) { - tx = 0; - } + if (longitudinal_interceptor_checks(to_send)) { + tx = 0; } } // FORCE CANCEL: safety check only relevant when spamming the cancel button in Bosch HW // ensuring that only the cancel button press is sent (VAL 2) when controls are off. // This avoids unintended engagements while still allowing resume spam - if ((addr == 0x296) && !current_controls_allowed && (bus == bus_buttons)) { + if ((addr == 0x296) && !controls_allowed && (bus == bus_buttons)) { if (((GET_BYTE(to_send, 0) >> 5) & 0x7U) != 2U) { tx = 0; } diff --git a/board/safety/safety_hyundai.h b/board/safety/safety_hyundai.h index dbff3f19e0..cfd020543b 100644 --- a/board/safety/safety_hyundai.h +++ b/board/safety/safety_hyundai.h @@ -1,25 +1,29 @@ #include "safety_hyundai_common.h" -const SteeringLimits HYUNDAI_STEERING_LIMITS = { - .max_steer = 384, - .max_rt_delta = 112, - .max_rt_interval = 250000, - .max_rate_up = 3, - .max_rate_down = 7, - .driver_torque_allowance = 50, - .driver_torque_factor = 2, - .type = TorqueDriverLimited, - - // the EPS faults when the steering angle is above a certain threshold for too long. to prevent this, - // we allow setting CF_Lkas_ActToi bit to 0 while maintaining the requested torque value for two consecutive frames - .min_valid_request_frames = 89, - .max_invalid_request_frames = 2, - .min_valid_request_rt_interval = 810000, // 810ms; a ~10% buffer on cutting every 90 frames - .has_steer_req_tolerance = true, -}; +#define HYUNDAI_LIMITS(steer, rate_up, rate_down) { \ + .max_steer = (steer), \ + .max_rate_up = (rate_up), \ + .max_rate_down = (rate_down), \ + .max_rt_delta = 112, \ + .max_rt_interval = 250000, \ + .driver_torque_allowance = 50, \ + .driver_torque_factor = 2, \ + .type = TorqueDriverLimited, \ + /* the EPS faults when the steering angle is above a certain threshold for too long. to prevent this, */ \ + /* we allow setting CF_Lkas_ActToi bit to 0 while maintaining the requested torque value for two consecutive frames */ \ + .min_valid_request_frames = 89, \ + .max_invalid_request_frames = 2, \ + .min_valid_request_rt_interval = 810000, /* 810ms; a ~10% buffer on cutting every 90 frames */ \ + .has_steer_req_tolerance = true, \ +} -const int HYUNDAI_MAX_ACCEL = 200; // 1/100 m/s2 -const int HYUNDAI_MIN_ACCEL = -350; // 1/100 m/s2 +const SteeringLimits HYUNDAI_STEERING_LIMITS = HYUNDAI_LIMITS(384, 3, 7); +const SteeringLimits HYUNDAI_STEERING_LIMITS_ALT = HYUNDAI_LIMITS(270, 2, 3); + +const LongitudinalLimits HYUNDAI_LONG_LIMITS = { + .max_accel = 200, // 1/100 m/s2 + .min_accel = -350, // 1/100 m/s2 +}; const CanMsg HYUNDAI_TX_MSGS[] = { {832, 0, 8}, // LKAS11 Bus 0 @@ -96,10 +100,7 @@ AddrCheckStruct hyundai_legacy_addr_checks[] = { }; #define HYUNDAI_LEGACY_ADDR_CHECK_LEN (sizeof(hyundai_legacy_addr_checks) / sizeof(hyundai_legacy_addr_checks[0])) -const int HYUNDAI_PARAM_CAMERA_SCC = 8; - bool hyundai_legacy = false; -bool hyundai_camera_scc = false; addr_checks hyundai_rx_checks = {hyundai_addr_checks, HYUNDAI_ADDR_CHECK_LEN}; @@ -181,7 +182,7 @@ static int hyundai_rx_hook(CANPacket_t *to_push) { bool valid = addr_safety_check(to_push, &hyundai_rx_checks, hyundai_get_checksum, hyundai_compute_checksum, - hyundai_get_counter); + hyundai_get_counter, NULL); int bus = GET_BUS(to_push); int addr = GET_ADDR(to_push); @@ -240,7 +241,7 @@ static int hyundai_rx_hook(CANPacket_t *to_push) { return valid; } -static int hyundai_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { +static int hyundai_tx_hook(CANPacket_t *to_send) { int tx = 1; int addr = GET_ADDR(to_send); @@ -274,16 +275,10 @@ static int hyundai_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { int aeb_decel_cmd = GET_BYTE(to_send, 2); int aeb_req = GET_BIT(to_send, 54U); - bool violation = 0; - - if (!longitudinal_allowed) { - if ((desired_accel_raw != 0) || (desired_accel_val != 0)) { - violation = 1; - } - } - violation |= max_limit_check(desired_accel_raw, HYUNDAI_MAX_ACCEL, HYUNDAI_MIN_ACCEL); - violation |= max_limit_check(desired_accel_val, HYUNDAI_MAX_ACCEL, HYUNDAI_MIN_ACCEL); + bool violation = false; + violation |= longitudinal_accel_checks(desired_accel_raw, HYUNDAI_LONG_LIMITS); + violation |= longitudinal_accel_checks(desired_accel_val, HYUNDAI_LONG_LIMITS); violation |= (aeb_decel_cmd != 0); violation |= (aeb_req != 0); @@ -297,7 +292,8 @@ static int hyundai_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { int desired_torque = ((GET_BYTES_04(to_send) >> 16) & 0x7ffU) - 1024U; bool steer_req = GET_BIT(to_send, 27U) != 0U; - if (steer_torque_cmd_checks(desired_torque, steer_req, HYUNDAI_STEERING_LIMITS)) { + const SteeringLimits limits = hyundai_alt_limits ? HYUNDAI_STEERING_LIMITS_ALT : HYUNDAI_STEERING_LIMITS; + if (steer_torque_cmd_checks(desired_torque, steer_req, limits)) { tx = 0; } } @@ -350,7 +346,6 @@ static int hyundai_fwd_hook(int bus_num, CANPacket_t *to_fwd) { static const addr_checks* hyundai_init(uint16_t param) { hyundai_common_init(param); hyundai_legacy = false; - hyundai_camera_scc = GET_FLAG(param, HYUNDAI_PARAM_CAMERA_SCC); if (hyundai_longitudinal) { hyundai_rx_checks = (addr_checks){hyundai_long_addr_checks, HYUNDAI_LONG_ADDR_CHECK_LEN}; diff --git a/board/safety/safety_hyundai_canfd.h b/board/safety/safety_hyundai_canfd.h index 750b94dbca..0481273444 100644 --- a/board/safety/safety_hyundai_canfd.h +++ b/board/safety/safety_hyundai_canfd.h @@ -43,7 +43,7 @@ const CanMsg HYUNDAI_CANFD_HDA2_LONG_TX_MSGS[] = { const CanMsg HYUNDAI_CANFD_HDA1_TX_MSGS[] = { {0x12A, 0, 16}, // LFA {0x1A0, 0, 32}, // CRUISE_INFO - {0x1CF, 0, 8}, // CRUISE_BUTTON + {0x1CF, 2, 8}, // CRUISE_BUTTON {0x1E0, 0, 16}, // LFAHDA_CLUSTER }; @@ -51,28 +51,71 @@ AddrCheckStruct hyundai_canfd_addr_checks[] = { {.msg = {{0x35, 1, 32, .check_checksum = true, .max_counter = 0xffU, .expected_timestep = 10000U}, {0x35, 0, 32, .check_checksum = true, .max_counter = 0xffU, .expected_timestep = 10000U}, {0x105, 0, 32, .check_checksum = true, .max_counter = 0xffU, .expected_timestep = 10000U}}}, - {.msg = {{0x65, 1, 32, .check_checksum = true, .max_counter = 0xffU, .expected_timestep = 10000U}, - {0x65, 0, 32, .check_checksum = true, .max_counter = 0xffU, .expected_timestep = 10000U}, { 0 }}}, + {.msg = {{0x175, 1, 24, .check_checksum = true, .max_counter = 0xffU, .expected_timestep = 20000U}, + {0x175, 0, 24, .check_checksum = true, .max_counter = 0xffU, .expected_timestep = 20000U}, { 0 }}}, {.msg = {{0xa0, 1, 24, .check_checksum = true, .max_counter = 0xffU, .expected_timestep = 10000U}, {0xa0, 0, 24, .check_checksum = true, .max_counter = 0xffU, .expected_timestep = 10000U}, { 0 }}}, {.msg = {{0xea, 1, 24, .check_checksum = true, .max_counter = 0xffU, .expected_timestep = 10000U}, {0xea, 0, 24, .check_checksum = true, .max_counter = 0xffU, .expected_timestep = 10000U}, { 0 }}}, - {.msg = {{0x175, 1, 24, .check_checksum = true, .max_counter = 0xffU, .expected_timestep = 10000U}, - {0x175, 0, 24, .check_checksum = true, .max_counter = 0xffU, .expected_timestep = 10000U}, { 0 }}}, + {.msg = {{0x1a0, 1, 32, .check_checksum = true, .max_counter = 0xffU, .expected_timestep = 20000U}, + {0x1a0, 2, 32, .check_checksum = true, .max_counter = 0xffU, .expected_timestep = 20000U}, { 0 }}}, {.msg = {{0x1cf, 1, 8, .check_checksum = false, .max_counter = 0xfU, .expected_timestep = 20000U}, {0x1cf, 0, 8, .check_checksum = false, .max_counter = 0xfU, .expected_timestep = 20000U}, {0x1aa, 0, 16, .check_checksum = false, .max_counter = 0xffU, .expected_timestep = 20000U}}}, }; #define HYUNDAI_CANFD_ADDR_CHECK_LEN (sizeof(hyundai_canfd_addr_checks) / sizeof(hyundai_canfd_addr_checks[0])) +// 0x1a0 is on bus 0 +AddrCheckStruct hyundai_canfd_radar_scc_addr_checks[] = { + {.msg = {{0x35, 1, 32, .check_checksum = true, .max_counter = 0xffU, .expected_timestep = 10000U}, + {0x35, 0, 32, .check_checksum = true, .max_counter = 0xffU, .expected_timestep = 10000U}, + {0x105, 0, 32, .check_checksum = true, .max_counter = 0xffU, .expected_timestep = 10000U}}}, + {.msg = {{0x175, 1, 24, .check_checksum = true, .max_counter = 0xffU, .expected_timestep = 20000U}, + {0x175, 0, 24, .check_checksum = true, .max_counter = 0xffU, .expected_timestep = 20000U}, { 0 }}}, + {.msg = {{0xa0, 1, 24, .check_checksum = true, .max_counter = 0xffU, .expected_timestep = 10000U}, + {0xa0, 0, 24, .check_checksum = true, .max_counter = 0xffU, .expected_timestep = 10000U}, { 0 }}}, + {.msg = {{0xea, 1, 24, .check_checksum = true, .max_counter = 0xffU, .expected_timestep = 10000U}, + {0xea, 0, 24, .check_checksum = true, .max_counter = 0xffU, .expected_timestep = 10000U}, { 0 }}}, + {.msg = {{0x1a0, 0, 32, .check_checksum = true, .max_counter = 0xffU, .expected_timestep = 20000U}, { 0 }, { 0 }}}, + {.msg = {{0x1cf, 1, 8, .check_checksum = false, .max_counter = 0xfU, .expected_timestep = 20000U}, + {0x1cf, 0, 8, .check_checksum = false, .max_counter = 0xfU, .expected_timestep = 20000U}, + {0x1aa, 0, 16, .check_checksum = false, .max_counter = 0xffU, .expected_timestep = 20000U}}}, +}; +#define HYUNDAI_CANFD_RADAR_SCC_ADDR_CHECK_LEN (sizeof(hyundai_canfd_radar_scc_addr_checks) / sizeof(hyundai_canfd_radar_scc_addr_checks[0])) + +AddrCheckStruct hyundai_canfd_long_addr_checks[] = { + {.msg = {{0x35, 1, 32, .check_checksum = true, .max_counter = 0xffU, .expected_timestep = 10000U}, + {0x35, 0, 32, .check_checksum = true, .max_counter = 0xffU, .expected_timestep = 10000U}, + {0x105, 0, 32, .check_checksum = true, .max_counter = 0xffU, .expected_timestep = 10000U}}}, + {.msg = {{0x175, 1, 24, .check_checksum = true, .max_counter = 0xffU, .expected_timestep = 20000U}, + {0x175, 0, 24, .check_checksum = true, .max_counter = 0xffU, .expected_timestep = 20000U}, { 0 }}}, + {.msg = {{0xa0, 1, 24, .check_checksum = true, .max_counter = 0xffU, .expected_timestep = 10000U}, + {0xa0, 0, 24, .check_checksum = true, .max_counter = 0xffU, .expected_timestep = 10000U}, { 0 }}}, + {.msg = {{0xea, 1, 24, .check_checksum = true, .max_counter = 0xffU, .expected_timestep = 10000U}, + {0xea, 0, 24, .check_checksum = true, .max_counter = 0xffU, .expected_timestep = 10000U}, { 0 }}}, + {.msg = {{0x1cf, 1, 8, .check_checksum = false, .max_counter = 0xfU, .expected_timestep = 20000U}, + {0x1cf, 0, 8, .check_checksum = false, .max_counter = 0xfU, .expected_timestep = 20000U}, + {0x1aa, 0, 16, .check_checksum = false, .max_counter = 0xffU, .expected_timestep = 20000U}}}, +}; +#define HYUNDAI_CANFD_LONG_ADDR_CHECK_LEN (sizeof(hyundai_canfd_long_addr_checks) / sizeof(hyundai_canfd_long_addr_checks[0])) + +AddrCheckStruct hyundai_canfd_ice_addr_checks[] = { + {.msg = {{0x100, 0, 32, .check_checksum = true, .max_counter = 0xffU, .expected_timestep = 10000U}, { 0 }, { 0 }}}, + {.msg = {{0xa0, 0, 24, .check_checksum = true, .max_counter = 0xffU, .expected_timestep = 10000U}, { 0 }, { 0 }}}, + {.msg = {{0xea, 0, 24, .check_checksum = true, .max_counter = 0xffU, .expected_timestep = 10000U}, { 0 }, { 0 }}}, + {.msg = {{0x175, 0, 24, .check_checksum = true, .max_counter = 0xffU, .expected_timestep = 20000U}, { 0 }, { 0 }}}, + {.msg = {{0x1aa, 0, 16, .check_checksum = false, .max_counter = 0xffU, .expected_timestep = 20000U}, { 0 }, { 0 }}}, +}; +#define HYUNDAI_CANFD_ICE_ADDR_CHECK_LEN (sizeof(hyundai_canfd_ice_addr_checks) / sizeof(hyundai_canfd_ice_addr_checks[0])) + addr_checks hyundai_canfd_rx_checks = {hyundai_canfd_addr_checks, HYUNDAI_CANFD_ADDR_CHECK_LEN}; uint16_t hyundai_canfd_crc_lut[256]; -const int HYUNDAI_PARAM_CANFD_HDA2 = 8; -const int HYUNDAI_PARAM_CANFD_ALT_BUTTONS = 16; +const int HYUNDAI_PARAM_CANFD_HDA2 = 16; +const int HYUNDAI_PARAM_CANFD_ALT_BUTTONS = 32; bool hyundai_canfd_hda2 = false; bool hyundai_canfd_alt_buttons = false; @@ -124,12 +167,13 @@ static uint32_t hyundai_canfd_compute_checksum(CANPacket_t *to_push) { static int hyundai_canfd_rx_hook(CANPacket_t *to_push) { bool valid = addr_safety_check(to_push, &hyundai_canfd_rx_checks, - hyundai_canfd_get_checksum, hyundai_canfd_compute_checksum, hyundai_canfd_get_counter); + hyundai_canfd_get_checksum, hyundai_canfd_compute_checksum, hyundai_canfd_get_counter, NULL); int bus = GET_BUS(to_push); int addr = GET_ADDR(to_push); const int pt_bus = hyundai_canfd_hda2 ? 1 : 0; + const int scc_bus = hyundai_camera_scc ? 2 : pt_bus; if (valid && (bus == pt_bus)) { // driver torque @@ -154,23 +198,19 @@ static int hyundai_canfd_rx_hook(CANPacket_t *to_push) { hyundai_common_cruise_buttons_check(cruise_button, main_button); } - // cruise state - if (addr == 0x175) { - bool cruise_engaged = GET_BIT(to_push, 68U); - hyundai_common_cruise_state_check(cruise_engaged); - } - // gas press, different for EV, hybrid, and ICE models if ((addr == 0x35) && hyundai_ev_gas_signal) { gas_pressed = GET_BYTE(to_push, 5) != 0U; } else if ((addr == 0x105) && hyundai_hybrid_gas_signal) { gas_pressed = (GET_BIT(to_push, 103U) != 0U) || (GET_BYTE(to_push, 13) != 0U) || (GET_BIT(to_push, 112U) != 0U); + } else if ((addr == 0x100) && !hyundai_ev_gas_signal && !hyundai_hybrid_gas_signal) { + gas_pressed = GET_BIT(to_push, 176U) != 0U; } else { } // brake press - if (addr == 0x65) { - brake_pressed = GET_BIT(to_push, 57U) != 0U; + if (addr == 0x175) { + brake_pressed = GET_BIT(to_push, 81U) != 0U; } // vehicle moving @@ -183,23 +223,31 @@ static int hyundai_canfd_rx_hook(CANPacket_t *to_push) { } } + if (valid && (bus == scc_bus)) { + // cruise state + if ((addr == 0x1a0) && !hyundai_longitudinal) { + bool cruise_engaged = ((GET_BYTE(to_push, 8) >> 4) & 0x3U) != 0U; + hyundai_common_cruise_state_check(cruise_engaged); + } + } + const int steer_addr = hyundai_canfd_hda2 ? 0x50 : 0x12a; bool stock_ecu_detected = (addr == steer_addr) && (bus == 0); if (hyundai_longitudinal) { - // ensure ADRV ECU is still knocked out - stock_ecu_detected = stock_ecu_detected || ((addr == 0x1a0) && (bus == 1)); + // on HDA2, ensure ADRV ECU is still knocked out + // on others, ensure accel msg is blocked from camera + const int stock_scc_bus = hyundai_canfd_hda2 ? 1 : 0; + stock_ecu_detected = stock_ecu_detected || ((addr == 0x1a0) && (bus == stock_scc_bus)); } generic_rx_checks(stock_ecu_detected); return valid; } -static int hyundai_canfd_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { - UNUSED(longitudinal_allowed); +static int hyundai_canfd_tx_hook(CANPacket_t *to_send) { int tx = 0; int addr = GET_ADDR(to_send); - int bus = GET_BUS(to_send); if (hyundai_canfd_hda2 && !hyundai_longitudinal) { tx = msg_allowed(to_send, HYUNDAI_CANFD_HDA2_TX_MSGS, sizeof(HYUNDAI_CANFD_HDA2_TX_MSGS)/sizeof(HYUNDAI_CANFD_HDA2_TX_MSGS[0])); @@ -221,8 +269,7 @@ static int hyundai_canfd_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed } // cruise buttons check - const int buttons_bus = hyundai_canfd_hda2 ? 1 : 0; - if ((addr == 0x1cf) && (bus == buttons_bus)) { + if (addr == 0x1cf) { int button = GET_BYTE(to_send, 2) & 0x7U; bool is_cancel = (button == HYUNDAI_BTN_CANCEL); bool is_resume = (button == HYUNDAI_BTN_RESUME); @@ -234,7 +281,7 @@ static int hyundai_canfd_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed } // UDS: only tester present ("\x02\x3E\x80\x00\x00\x00\x00\x00") allowed on diagnostics address - if (addr == 0x730) { + if ((addr == 0x730) && hyundai_canfd_hda2) { if ((GET_BYTES_04(to_send) != 0x00803E02U) || (GET_BYTES_48(to_send) != 0x0U)) { tx = 0; } @@ -248,13 +295,8 @@ static int hyundai_canfd_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed bool violation = false; if (hyundai_longitudinal) { - if (!longitudinal_allowed) { - if ((desired_accel_raw != 0) || (desired_accel_val != 0)) { - violation = true; - } - } - violation |= max_limit_check(desired_accel_raw, HYUNDAI_MAX_ACCEL, HYUNDAI_MIN_ACCEL); - violation |= max_limit_check(desired_accel_val, HYUNDAI_MAX_ACCEL, HYUNDAI_MIN_ACCEL); + violation |= longitudinal_accel_checks(desired_accel_raw, HYUNDAI_LONG_LIMITS); + violation |= longitudinal_accel_checks(desired_accel_val, HYUNDAI_LONG_LIMITS); } else { // only used to cancel on here if ((desired_accel_raw != 0) || (desired_accel_val != 0)) { @@ -285,7 +327,10 @@ static int hyundai_canfd_fwd_hook(int bus_num, CANPacket_t *to_fwd) { // HUD icons int is_lfahda_msg = ((addr == 0x1e0) && !hyundai_canfd_hda2); - int block_msg = is_lkas_msg || is_lfa_msg || is_lfahda_msg; + // CRUISE_INFO for non-HDA2, we send our own longitudinal commands + int is_scc_msg = ((addr == 0x1a0) && hyundai_longitudinal && !hyundai_canfd_hda2); + + int block_msg = is_lkas_msg || is_lfa_msg || is_lfahda_msg || is_scc_msg; if (!block_msg) { bus_fwd = 0; } @@ -301,10 +346,23 @@ static const addr_checks* hyundai_canfd_init(uint16_t param) { hyundai_canfd_hda2 = GET_FLAG(param, HYUNDAI_PARAM_CANFD_HDA2); hyundai_canfd_alt_buttons = GET_FLAG(param, HYUNDAI_PARAM_CANFD_ALT_BUTTONS); - if (!hyundai_canfd_hda2) { + // no long for ICE yet + if (!hyundai_ev_gas_signal && !hyundai_hybrid_gas_signal) { hyundai_longitudinal = false; } + if (hyundai_longitudinal) { + hyundai_canfd_rx_checks = (addr_checks){hyundai_canfd_long_addr_checks, HYUNDAI_CANFD_LONG_ADDR_CHECK_LEN}; + } else { + if (!hyundai_ev_gas_signal && !hyundai_hybrid_gas_signal) { + hyundai_canfd_rx_checks = (addr_checks){hyundai_canfd_ice_addr_checks, HYUNDAI_CANFD_ICE_ADDR_CHECK_LEN}; + } else if (!hyundai_camera_scc && !hyundai_canfd_hda2) { + hyundai_canfd_rx_checks = (addr_checks){hyundai_canfd_radar_scc_addr_checks, HYUNDAI_CANFD_RADAR_SCC_ADDR_CHECK_LEN}; + } else { + hyundai_canfd_rx_checks = (addr_checks){hyundai_canfd_addr_checks, HYUNDAI_CANFD_ADDR_CHECK_LEN}; + } + } + return &hyundai_canfd_rx_checks; } diff --git a/board/safety/safety_hyundai_common.h b/board/safety/safety_hyundai_common.h index e56f6f465b..8bd84feacf 100644 --- a/board/safety/safety_hyundai_common.h +++ b/board/safety/safety_hyundai_common.h @@ -4,6 +4,8 @@ const int HYUNDAI_PARAM_EV_GAS = 1; const int HYUNDAI_PARAM_HYBRID_GAS = 2; const int HYUNDAI_PARAM_LONGITUDINAL = 4; +const int HYUNDAI_PARAM_CAMERA_SCC = 8; +const int HYUNDAI_PARAM_ALT_LIMITS = 64; // TODO: shift this down with the rest of the common flags const uint8_t HYUNDAI_PREV_BUTTON_SAMPLES = 8; // roughly 160 ms const uint32_t HYUNDAI_STANDSTILL_THRSLD = 30; // ~1kph @@ -19,11 +21,15 @@ enum { bool hyundai_ev_gas_signal = false; bool hyundai_hybrid_gas_signal = false; bool hyundai_longitudinal = false; +bool hyundai_camera_scc = false; +bool hyundai_alt_limits = false; uint8_t hyundai_last_button_interaction; // button messages since the user pressed an enable button void hyundai_common_init(uint16_t param) { hyundai_ev_gas_signal = GET_FLAG(param, HYUNDAI_PARAM_EV_GAS); hyundai_hybrid_gas_signal = !hyundai_ev_gas_signal && GET_FLAG(param, HYUNDAI_PARAM_HYBRID_GAS); + hyundai_camera_scc = GET_FLAG(param, HYUNDAI_PARAM_CAMERA_SCC); + hyundai_alt_limits = GET_FLAG(param, HYUNDAI_PARAM_ALT_LIMITS); hyundai_last_button_interaction = HYUNDAI_PREV_BUTTON_SAMPLES; @@ -60,18 +66,18 @@ void hyundai_common_cruise_buttons_check(const int cruise_button, const int main } if (hyundai_longitudinal) { - // exit controls on cancel press - if (cruise_button == HYUNDAI_BTN_CANCEL) { - controls_allowed = 0; - } - // enter controls on falling edge of resume or set - bool set = (cruise_button == HYUNDAI_BTN_NONE) && (cruise_button_prev == HYUNDAI_BTN_SET); - bool res = (cruise_button == HYUNDAI_BTN_NONE) && (cruise_button_prev == HYUNDAI_BTN_RESUME); + bool set = (cruise_button != HYUNDAI_BTN_SET) && (cruise_button_prev == HYUNDAI_BTN_SET); + bool res = (cruise_button != HYUNDAI_BTN_RESUME) && (cruise_button_prev == HYUNDAI_BTN_RESUME); if (set || res) { controls_allowed = 1; } + // exit controls on cancel press + if (cruise_button == HYUNDAI_BTN_CANCEL) { + controls_allowed = 0; + } + cruise_button_prev = cruise_button; } } diff --git a/board/safety/safety_mazda.h b/board/safety/safety_mazda.h index 423c096dd3..70866fe608 100644 --- a/board/safety/safety_mazda.h +++ b/board/safety/safety_mazda.h @@ -37,7 +37,7 @@ addr_checks mazda_rx_checks = {mazda_addr_checks, MAZDA_ADDR_CHECKS_LEN}; // track msgs coming from OP so that we know what CAM msgs to drop and what to forward static int mazda_rx_hook(CANPacket_t *to_push) { - bool valid = addr_safety_check(to_push, &mazda_rx_checks, NULL, NULL, NULL); + bool valid = addr_safety_check(to_push, &mazda_rx_checks, NULL, NULL, NULL, NULL); if (valid && ((int)GET_BUS(to_push) == MAZDA_MAIN)) { int addr = GET_ADDR(to_push); @@ -72,8 +72,7 @@ static int mazda_rx_hook(CANPacket_t *to_push) { return valid; } -static int mazda_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { - UNUSED(longitudinal_allowed); +static int mazda_tx_hook(CANPacket_t *to_send) { int tx = 1; int addr = GET_ADDR(to_send); diff --git a/board/safety/safety_nissan.h b/board/safety/safety_nissan.h index b97bd1fb68..cc8048aa40 100644 --- a/board/safety/safety_nissan.h +++ b/board/safety/safety_nissan.h @@ -1,15 +1,14 @@ - -const uint32_t NISSAN_RT_INTERVAL = 250000; // 250ms between real time checks - -const struct lookup_t NISSAN_LOOKUP_ANGLE_RATE_UP = { - {2., 7., 17.}, - {5., .8, .15}}; - -const struct lookup_t NISSAN_LOOKUP_ANGLE_RATE_DOWN = { - {2., 7., 17.}, - {5., 3.5, .5}}; - -const int NISSAN_DEG_TO_CAN = 100; +const SteeringLimits NISSAN_STEERING_LIMITS = { + .angle_deg_to_can = 100, + .angle_rate_up_lookup = { + {0., 5., 15.}, + {5., .8, .15} + }, + .angle_rate_down_lookup = { + {0., 5., 15.}, + {5., 3.5, .4} + }, +}; const CanMsg NISSAN_TX_MSGS[] = { {0x169, 0, 8}, // LKAS @@ -43,7 +42,7 @@ bool nissan_alt_eps = false; static int nissan_rx_hook(CANPacket_t *to_push) { - bool valid = addr_safety_check(to_push, &nissan_rx_checks, NULL, NULL, NULL); + bool valid = addr_safety_check(to_push, &nissan_rx_checks, NULL, NULL, NULL, NULL); if (valid) { int bus = GET_BUS(to_push); @@ -62,10 +61,11 @@ static int nissan_rx_hook(CANPacket_t *to_push) { } if (addr == 0x285) { - // Get current speed - // Factor 0.005 - vehicle_speed = ((GET_BYTE(to_push, 2) << 8) | (GET_BYTE(to_push, 3))) * 0.005 / 3.6; - vehicle_moving = vehicle_speed > 0.; + // Get current speed and standstill + uint16_t right_rear = (GET_BYTE(to_push, 0) << 8) | (GET_BYTE(to_push, 1)); + uint16_t left_rear = (GET_BYTE(to_push, 2) << 8) | (GET_BYTE(to_push, 3)); + vehicle_moving = (right_rear | left_rear) != 0U; + vehicle_speed = (right_rear + left_rear) / 2.0 * 0.005 / 3.6; } // X-Trail 0x15c, Leaf 0x239 @@ -99,12 +99,11 @@ static int nissan_rx_hook(CANPacket_t *to_push) { } -static int nissan_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { - UNUSED(longitudinal_allowed); +static int nissan_tx_hook(CANPacket_t *to_send) { int tx = 1; int addr = GET_ADDR(to_send); - bool violation = 0; + bool violation = false; if (!msg_allowed(to_send, NISSAN_TX_MSGS, sizeof(NISSAN_TX_MSGS) / sizeof(NISSAN_TX_MSGS[0]))) { tx = 0; @@ -115,34 +114,11 @@ static int nissan_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { int desired_angle = ((GET_BYTE(to_send, 0) << 10) | (GET_BYTE(to_send, 1) << 2) | ((GET_BYTE(to_send, 2) >> 6) & 0x3U)); bool lka_active = (GET_BYTE(to_send, 6) >> 4) & 1U; - // offeset 1310 * NISSAN_DEG_TO_CAN + // offeset 1310 * NISSAN_STEERING_LIMITS.angle_deg_to_can desired_angle = desired_angle - 131000; - if (controls_allowed && lka_active) { - // add 1 to not false trigger the violation - float delta_angle_float; - delta_angle_float = (interpolate(NISSAN_LOOKUP_ANGLE_RATE_UP, vehicle_speed) * NISSAN_DEG_TO_CAN) + 1.; - int delta_angle_up = (int)(delta_angle_float); - delta_angle_float = (interpolate(NISSAN_LOOKUP_ANGLE_RATE_DOWN, vehicle_speed) * NISSAN_DEG_TO_CAN) + 1.; - int delta_angle_down = (int)(delta_angle_float); - int highest_desired_angle = desired_angle_last + ((desired_angle_last > 0) ? delta_angle_up : delta_angle_down); - int lowest_desired_angle = desired_angle_last - ((desired_angle_last >= 0) ? delta_angle_down : delta_angle_up); - - // check for violation; - violation |= max_limit_check(desired_angle, highest_desired_angle, lowest_desired_angle); - } - desired_angle_last = desired_angle; - - // desired steer angle should be the same as steer angle measured when controls are off - if ((!controls_allowed) && - ((desired_angle < (angle_meas.min - 1)) || - (desired_angle > (angle_meas.max + 1)))) { - violation = 1; - } - - // no lka_enabled bit if controls not allowed - if (!controls_allowed && lka_active) { - violation = 1; + if (steer_angle_cmd_checks(desired_angle, lka_active, NISSAN_STEERING_LIMITS)) { + violation = true; } } diff --git a/board/safety/safety_subaru.h b/board/safety/safety_subaru.h index a1a98a15f2..538c5a7ce1 100644 --- a/board/safety/safety_subaru.h +++ b/board/safety/safety_subaru.h @@ -82,7 +82,7 @@ static uint32_t subaru_compute_checksum(CANPacket_t *to_push) { static int subaru_rx_hook(CANPacket_t *to_push) { bool valid = addr_safety_check(to_push, &subaru_rx_checks, - subaru_get_checksum, subaru_compute_checksum, subaru_get_counter); + subaru_get_checksum, subaru_compute_checksum, subaru_get_counter, NULL); if (valid) { const int bus = GET_BUS(to_push); @@ -120,8 +120,7 @@ static int subaru_rx_hook(CANPacket_t *to_push) { return valid; } -static int subaru_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { - UNUSED(longitudinal_allowed); +static int subaru_tx_hook(CANPacket_t *to_send) { int tx = 1; int addr = GET_ADDR(to_send); diff --git a/board/safety/safety_subaru_legacy.h b/board/safety/safety_subaru_legacy.h index 7e58e20fa9..d84d4a6491 100644 --- a/board/safety/safety_subaru_legacy.h +++ b/board/safety/safety_subaru_legacy.h @@ -26,7 +26,7 @@ addr_checks subaru_l_rx_checks = {subaru_l_addr_checks, SUBARU_L_ADDR_CHECK_LEN} static int subaru_legacy_rx_hook(CANPacket_t *to_push) { - bool valid = addr_safety_check(to_push, &subaru_l_rx_checks, NULL, NULL, NULL); + bool valid = addr_safety_check(to_push, &subaru_l_rx_checks, NULL, NULL, NULL, NULL); if (valid && (GET_BUS(to_push) == 0U)) { int addr = GET_ADDR(to_push); @@ -61,8 +61,7 @@ static int subaru_legacy_rx_hook(CANPacket_t *to_push) { return valid; } -static int subaru_legacy_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { - UNUSED(longitudinal_allowed); +static int subaru_legacy_tx_hook(CANPacket_t *to_send) { int tx = 1; int addr = GET_ADDR(to_send); diff --git a/board/safety/safety_tesla.h b/board/safety/safety_tesla.h index 312074dec5..d862d8d314 100644 --- a/board/safety/safety_tesla.h +++ b/board/safety/safety_tesla.h @@ -1,14 +1,21 @@ -const struct lookup_t TESLA_LOOKUP_ANGLE_RATE_UP = { - {2., 7., 17.}, - {5., .8, .25}}; +const SteeringLimits TESLA_STEERING_LIMITS = { + .angle_deg_to_can = 10, + .angle_rate_up_lookup = { + {0., 5., 15.}, + {5., .8, .15} + }, + .angle_rate_down_lookup = { + {0., 5., 15.}, + {5., 3.5, .4} + }, +}; -const struct lookup_t TESLA_LOOKUP_ANGLE_RATE_DOWN = { - {2., 7., 17.}, - {5., 3.5, .8}}; +const LongitudinalLimits TESLA_LONG_LIMITS = { + .max_accel = 425, // 2. m/s^2 + .min_accel = 287, // -3.52 m/s^2 // TODO: limit to -3.48 + .inactive_accel = 375, // 0. m/s^2 +}; -const int TESLA_DEG_TO_CAN = 10; -const float TESLA_MAX_ACCEL = 2.0; // m/s^2 -const float TESLA_MIN_ACCEL = -3.5; // m/s^2 const int TESLA_FLAG_POWERTRAIN = 1; const int TESLA_FLAG_LONGITUDINAL_CONTROL = 2; @@ -26,8 +33,6 @@ const CanMsg TESLA_PT_TX_MSGS[] = { }; #define TESLA_PT_TX_LEN (sizeof(TESLA_PT_TX_MSGS) / sizeof(TESLA_PT_TX_MSGS[0])) -const int TESLA_NO_ACCEL_VALUE = 375; // value sent when not requesting acceleration - AddrCheckStruct tesla_addr_checks[] = { {.msg = {{0x370, 0, 8, .expected_timestep = 40000U}, { 0 }, { 0 }}}, // EPAS_sysStatus (25Hz) {.msg = {{0x108, 0, 8, .expected_timestep = 10000U}, { 0 }, { 0 }}}, // DI_torque1 (100Hz) @@ -55,7 +60,7 @@ bool tesla_stock_aeb = false; static int tesla_rx_hook(CANPacket_t *to_push) { bool valid = addr_safety_check(to_push, tesla_powertrain ? (&tesla_pt_rx_checks) : (&tesla_rx_checks), - NULL, NULL, NULL); + NULL, NULL, NULL, NULL); if(valid) { int bus = GET_BUS(to_push); @@ -112,7 +117,7 @@ static int tesla_rx_hook(CANPacket_t *to_push) { } -static int tesla_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { +static int tesla_tx_hook(CANPacket_t *to_send) { int tx = 1; int addr = GET_ADDR(to_send); @@ -133,29 +138,7 @@ static int tesla_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { bool steer_control_enabled = (steer_control_type != 0) && // NONE (steer_control_type != 3); // DISABLED - // Rate limit while steering - if(controls_allowed && steer_control_enabled) { - // Add 1 to not false trigger the violation - float delta_angle_float; - delta_angle_float = (interpolate(TESLA_LOOKUP_ANGLE_RATE_UP, vehicle_speed) * TESLA_DEG_TO_CAN); - int delta_angle_up = (int)(delta_angle_float) + 1; - delta_angle_float = (interpolate(TESLA_LOOKUP_ANGLE_RATE_DOWN, vehicle_speed) * TESLA_DEG_TO_CAN); - int delta_angle_down = (int)(delta_angle_float) + 1; - int highest_desired_angle = desired_angle_last + ((desired_angle_last > 0) ? delta_angle_up : delta_angle_down); - int lowest_desired_angle = desired_angle_last - ((desired_angle_last >= 0) ? delta_angle_down : delta_angle_up); - - // Check for violation; - violation |= max_limit_check(desired_angle, highest_desired_angle, lowest_desired_angle); - } - desired_angle_last = desired_angle; - - // Angle should be the same as current angle while not steering - if(!controls_allowed && ((desired_angle < (angle_meas.min - 1)) || (desired_angle > (angle_meas.max + 1)))) { - violation = true; - } - - // No angle control allowed when controls are not allowed - if(!controls_allowed && steer_control_enabled) { + if (steer_angle_cmd_checks(desired_angle, steer_control_enabled, TESLA_STEERING_LIMITS)) { violation = true; } } @@ -185,23 +168,8 @@ static int tesla_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { // Don't allow any acceleration limits above the safety limits int raw_accel_max = ((GET_BYTE(to_send, 6) & 0x1FU) << 4) | (GET_BYTE(to_send, 5) >> 4); int raw_accel_min = ((GET_BYTE(to_send, 5) & 0x0FU) << 5) | (GET_BYTE(to_send, 4) >> 3); - float accel_max = (0.04 * raw_accel_max) - 15; - float accel_min = (0.04 * raw_accel_min) - 15; - - if ((accel_max > TESLA_MAX_ACCEL) || (accel_min > TESLA_MAX_ACCEL)){ - violation = true; - } - - if ((accel_max < TESLA_MIN_ACCEL) || (accel_min < TESLA_MIN_ACCEL)){ - violation = true; - } - - // Don't allow longitudinal actuation if controls aren't allowed - if (!longitudinal_allowed) { - if ((raw_accel_max != TESLA_NO_ACCEL_VALUE) || (raw_accel_min != TESLA_NO_ACCEL_VALUE)) { - violation = true; - } - } + violation |= longitudinal_accel_checks(raw_accel_max, TESLA_LONG_LIMITS); + violation |= longitudinal_accel_checks(raw_accel_min, TESLA_LONG_LIMITS); } else { violation = true; } diff --git a/board/safety/safety_toyota.h b/board/safety/safety_toyota.h index 510bc10e3f..6206130844 100644 --- a/board/safety/safety_toyota.h +++ b/board/safety/safety_toyota.h @@ -16,8 +16,10 @@ const SteeringLimits TOYOTA_STEERING_LIMITS = { }; // longitudinal limits -const int TOYOTA_MAX_ACCEL = 2000; // 2.0 m/s2 -const int TOYOTA_MIN_ACCEL = -3500; // -3.5 m/s2 +const LongitudinalLimits TOYOTA_LONG_LIMITS = { + .max_accel = 2000, // 2.0 m/s2 + .min_accel = -3500, // -3.5 m/s2 +}; // panda interceptor threshold needs to be equivalent to openpilot threshold to avoid controls mismatches // If thresholds are mismatched then it is possible for panda to see the gas fall and rise while openpilot is in the pre-enabled state @@ -46,9 +48,11 @@ const uint32_t TOYOTA_PARAM_OFFSET = 8U; const uint32_t TOYOTA_EPS_FACTOR = (1U << TOYOTA_PARAM_OFFSET) - 1U; const uint32_t TOYOTA_PARAM_ALT_BRAKE = 1U << TOYOTA_PARAM_OFFSET; const uint32_t TOYOTA_PARAM_STOCK_LONGITUDINAL = 2U << TOYOTA_PARAM_OFFSET; +const uint32_t TOYOTA_PARAM_LTA = 4U << TOYOTA_PARAM_OFFSET; bool toyota_alt_brake = false; bool toyota_stock_longitudinal = false; +bool toyota_lta = false; int toyota_dbc_eps_torque_factor = 100; // conversion factor for STEER_TORQUE_EPS in %: see dbc file static uint32_t toyota_compute_checksum(CANPacket_t *to_push) { @@ -69,7 +73,7 @@ static uint32_t toyota_get_checksum(CANPacket_t *to_push) { static int toyota_rx_hook(CANPacket_t *to_push) { bool valid = addr_safety_check(to_push, &toyota_rx_checks, - toyota_get_checksum, toyota_compute_checksum, NULL); + toyota_get_checksum, toyota_compute_checksum, NULL, NULL); if (valid && (GET_BUS(to_push) == 0U)) { int addr = GET_ADDR(to_push); @@ -130,7 +134,7 @@ static int toyota_rx_hook(CANPacket_t *to_push) { return valid; } -static int toyota_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { +static int toyota_tx_hook(CANPacket_t *to_send) { int tx = 1; int addr = GET_ADDR(to_send); @@ -145,10 +149,8 @@ static int toyota_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { // GAS PEDAL: safety check if (addr == 0x200) { - if (!longitudinal_allowed) { - if (GET_BYTE(to_send, 0) || GET_BYTE(to_send, 1)) { - tx = 0; - } + if (longitudinal_interceptor_checks(to_send)) { + tx = 0; } } @@ -156,22 +158,21 @@ static int toyota_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { if (addr == 0x343) { int desired_accel = (GET_BYTE(to_send, 0) << 8) | GET_BYTE(to_send, 1); desired_accel = to_signed(desired_accel, 16); - if (!longitudinal_allowed || toyota_stock_longitudinal) { - if (desired_accel != 0) { - tx = 0; - } - } + + bool violation = false; + violation |= longitudinal_accel_checks(desired_accel, TOYOTA_LONG_LIMITS); // only ACC messages that cancel are allowed when openpilot is not controlling longitudinal if (toyota_stock_longitudinal) { bool cancel_req = GET_BIT(to_send, 24U) != 0U; if (!cancel_req) { - tx = 0; + violation = true; + } + if (desired_accel != TOYOTA_LONG_LIMITS.inactive_accel) { + violation = true; } } - bool violation = max_limit_check(desired_accel, TOYOTA_MAX_ACCEL, TOYOTA_MIN_ACCEL); - if (violation) { tx = 0; } @@ -189,14 +190,15 @@ static int toyota_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { // LTA steering check // only sent to prevent dash errors, no actuation is accepted if (addr == 0x191) { - // check the STEER_REQUEST, STEER_REQUEST_2, and STEER_ANGLE_CMD signals + // check the STEER_REQUEST, STEER_REQUEST_2, SETME_X64 STEER_ANGLE_CMD signals bool lta_request = (GET_BYTE(to_send, 0) & 1U) != 0U; bool lta_request2 = ((GET_BYTE(to_send, 3) >> 1) & 1U) != 0U; + int setme_x64 = GET_BYTE(to_send, 5); int lta_angle = (GET_BYTE(to_send, 1) << 8) | GET_BYTE(to_send, 2); lta_angle = to_signed(lta_angle, 16); // block LTA msgs with actuation requests - if (lta_request || lta_request2 || (lta_angle != 0)) { + if (lta_request || lta_request2 || (lta_angle != 0) || (setme_x64 != 0)) { tx = 0; } } @@ -209,6 +211,10 @@ static int toyota_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { if (steer_torque_cmd_checks(desired_torque, steer_req, TOYOTA_STEERING_LIMITS)) { tx = 0; } + // When using LTA (angle control), assert no actuation on LKA message + if (toyota_lta && ((desired_torque != 0) || steer_req)) { + tx = 0; + } } } @@ -220,6 +226,12 @@ static const addr_checks* toyota_init(uint16_t param) { toyota_alt_brake = GET_FLAG(param, TOYOTA_PARAM_ALT_BRAKE); toyota_stock_longitudinal = GET_FLAG(param, TOYOTA_PARAM_STOCK_LONGITUDINAL); toyota_dbc_eps_torque_factor = param & TOYOTA_EPS_FACTOR; + +#ifdef ALLOW_DEBUG + toyota_lta = GET_FLAG(param, TOYOTA_PARAM_LTA); +#else + toyota_lta = false; +#endif return &toyota_rx_checks; } diff --git a/board/safety/safety_volkswagen_common.h b/board/safety/safety_volkswagen_common.h new file mode 100644 index 0000000000..ce2bf580fe --- /dev/null +++ b/board/safety/safety_volkswagen_common.h @@ -0,0 +1,10 @@ +#ifndef SAFETY_VOLKSWAGEN_COMMON_H +#define SAFETY_VOLKSWAGEN_COMMON_H + +const uint16_t FLAG_VOLKSWAGEN_LONG_CONTROL = 1; + +bool volkswagen_longitudinal = false; +bool volkswagen_set_button_prev = false; +bool volkswagen_resume_button_prev = false; + +#endif diff --git a/board/safety/safety_volkswagen_mqb.h b/board/safety/safety_volkswagen_mqb.h index da288e6cb5..afedc20a18 100644 --- a/board/safety/safety_volkswagen_mqb.h +++ b/board/safety/safety_volkswagen_mqb.h @@ -1,4 +1,6 @@ +#include "safety_volkswagen_common.h" +// lateral limits const SteeringLimits VOLKSWAGEN_MQB_STEERING_LIMITS = { .max_steer = 300, // 3.0 Nm (EPS side max of 3.0Nm with fault if violated) .max_rt_delta = 75, // 4 max rate up * 50Hz send rate * 250000 RT interval / 1000000 = 50 ; 50 * 1.5 for safety pad = 75 @@ -10,31 +12,46 @@ const SteeringLimits VOLKSWAGEN_MQB_STEERING_LIMITS = { .type = TorqueDriverLimited, }; +// longitudinal limits +// acceleration in m/s2 * 1000 to avoid floating point math +const LongitudinalLimits VOLKSWAGEN_MQB_LONG_LIMITS = { + .max_accel = 2000, + .min_accel = -3500, + .inactive_accel = 3010, // VW sends one increment above the max range when inactive +}; + #define MSG_ESP_19 0x0B2 // RX from ABS, for wheel speeds #define MSG_LH_EPS_03 0x09F // RX from EPS, for driver steering torque #define MSG_ESP_05 0x106 // RX from ABS, for brake switch state #define MSG_TSK_06 0x120 // RX from ECU, for ACC status from drivetrain coordinator #define MSG_MOTOR_20 0x121 // RX from ECU, for driver throttle input +#define MSG_ACC_06 0x122 // TX by OP, ACC control instructions to the drivetrain coordinator #define MSG_HCA_01 0x126 // TX by OP, Heading Control Assist steering torque #define MSG_GRA_ACC_01 0x12B // TX by OP, ACC control buttons for cancel/resume +#define MSG_ACC_07 0x12E // TX by OP, ACC control instructions to the drivetrain coordinator +#define MSG_ACC_02 0x30C // TX by OP, ACC HUD data to the instrument cluster +#define MSG_MOTOR_14 0x3BE // RX from ECU, for brake switch status #define MSG_LDW_02 0x397 // TX by OP, Lane line recognition and text alerts // Transmit of GRA_ACC_01 is allowed on bus 0 and 2 to keep compatibility with gateway and camera integration -const CanMsg VOLKSWAGEN_MQB_TX_MSGS[] = {{MSG_HCA_01, 0, 8}, {MSG_GRA_ACC_01, 0, 8}, {MSG_GRA_ACC_01, 2, 8}, {MSG_LDW_02, 0, 8}}; -#define VOLKSWAGEN_MQB_TX_MSGS_LEN (sizeof(VOLKSWAGEN_MQB_TX_MSGS) / sizeof(VOLKSWAGEN_MQB_TX_MSGS[0])) +const CanMsg VOLKSWAGEN_MQB_STOCK_TX_MSGS[] = {{MSG_HCA_01, 0, 8}, {MSG_GRA_ACC_01, 0, 8}, {MSG_GRA_ACC_01, 2, 8}, {MSG_LDW_02, 0, 8}}; +const CanMsg VOLKSWAGEN_MQB_LONG_TX_MSGS[] = {{MSG_HCA_01, 0, 8}, {MSG_LDW_02, 0, 8}, + {MSG_ACC_02, 0, 8}, {MSG_ACC_06, 0, 8}, {MSG_ACC_07, 0, 8}}; AddrCheckStruct volkswagen_mqb_addr_checks[] = { - {.msg = {{MSG_ESP_19, 0, 8, .check_checksum = false, .max_counter = 0U, .expected_timestep = 10000U}, { 0 }, { 0 }}}, - {.msg = {{MSG_LH_EPS_03, 0, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 10000U}, { 0 }, { 0 }}}, - {.msg = {{MSG_ESP_05, 0, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 20000U}, { 0 }, { 0 }}}, - {.msg = {{MSG_TSK_06, 0, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 20000U}, { 0 }, { 0 }}}, - {.msg = {{MSG_MOTOR_20, 0, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 20000U}, { 0 }, { 0 }}}, + {.msg = {{MSG_ESP_19, 0, 8, .check_checksum = false, .max_counter = 0U, .expected_timestep = 10000U}, { 0 }, { 0 }}}, + {.msg = {{MSG_LH_EPS_03, 0, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 10000U}, { 0 }, { 0 }}}, + {.msg = {{MSG_ESP_05, 0, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 20000U}, { 0 }, { 0 }}}, + {.msg = {{MSG_TSK_06, 0, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 20000U}, { 0 }, { 0 }}}, + {.msg = {{MSG_MOTOR_20, 0, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 20000U}, { 0 }, { 0 }}}, + {.msg = {{MSG_MOTOR_14, 0, 8, .check_checksum = false, .max_counter = 0U, .expected_timestep = 100000U}, { 0 }, { 0 }}}, }; #define VOLKSWAGEN_MQB_ADDR_CHECKS_LEN (sizeof(volkswagen_mqb_addr_checks) / sizeof(volkswagen_mqb_addr_checks[0])) addr_checks volkswagen_mqb_rx_checks = {volkswagen_mqb_addr_checks, VOLKSWAGEN_MQB_ADDR_CHECKS_LEN}; uint8_t volkswagen_crc8_lut_8h2f[256]; // Static lookup table for CRC8 poly 0x2F, aka 8H2F/AUTOSAR - +bool volkswagen_mqb_brake_pedal_switch = false; +bool volkswagen_mqb_brake_pressure_detected = false; static uint32_t volkswagen_mqb_get_checksum(CANPacket_t *to_push) { return (uint8_t)GET_BYTE(to_push, 0); @@ -83,6 +100,14 @@ static uint32_t volkswagen_mqb_compute_crc(CANPacket_t *to_push) { static const addr_checks* volkswagen_mqb_init(uint16_t param) { UNUSED(param); + volkswagen_set_button_prev = false; + volkswagen_resume_button_prev = false; + volkswagen_mqb_brake_pedal_switch = false; + volkswagen_mqb_brake_pressure_detected = false; + +#ifdef ALLOW_DEBUG + volkswagen_longitudinal = GET_FLAG(param, FLAG_VOLKSWAGEN_LONG_CONTROL); +#endif gen_crc_lookup_table_8(0x2F, volkswagen_crc8_lut_8h2f); return &volkswagen_mqb_rx_checks; } @@ -90,20 +115,21 @@ static const addr_checks* volkswagen_mqb_init(uint16_t param) { static int volkswagen_mqb_rx_hook(CANPacket_t *to_push) { bool valid = addr_safety_check(to_push, &volkswagen_mqb_rx_checks, - volkswagen_mqb_get_checksum, volkswagen_mqb_compute_crc, volkswagen_mqb_get_counter); + volkswagen_mqb_get_checksum, volkswagen_mqb_compute_crc, volkswagen_mqb_get_counter, NULL); if (valid && (GET_BUS(to_push) == 0U)) { int addr = GET_ADDR(to_push); - // Update in-motion state by sampling front wheel speeds - // Signal: ESP_19.ESP_VL_Radgeschw_02 (front left) in scaled km/h - // Signal: ESP_19.ESP_VR_Radgeschw_02 (front right) in scaled km/h + // Update in-motion state by sampling wheel speeds if (addr == MSG_ESP_19) { - int wheel_speed_fl = GET_BYTE(to_push, 4) | (GET_BYTE(to_push, 5) << 8); - int wheel_speed_fr = GET_BYTE(to_push, 6) | (GET_BYTE(to_push, 7) << 8); - // Check for average front speed in excess of 0.3m/s, 1.08km/h - // DBC speed scale 0.0075: 0.3m/s = 144, sum both wheels to compare - vehicle_moving = (wheel_speed_fl + wheel_speed_fr) > 288; + // sum 4 wheel speeds + int speed = 0; + for (uint8_t i = 0U; i < 8U; i += 2U) { + int wheel_speed = GET_BYTE(to_push, i) | (GET_BYTE(to_push, i + 1U) << 8); + speed += wheel_speed; + } + // Check all wheel speeds for any movement + vehicle_moving = speed > 0; } // Update driver input torque samples @@ -118,12 +144,41 @@ static int volkswagen_mqb_rx_hook(CANPacket_t *to_push) { update_sample(&torque_driver, torque_driver_new); } - // Enter controls on rising edge of stock ACC, exit controls if stock ACC disengages - // Signal: TSK_06.TSK_Status if (addr == MSG_TSK_06) { + // When using stock ACC, enter controls on rising edge of stock ACC engage, exit on disengage + // Always exit controls on main switch off + // Signal: TSK_06.TSK_Status int acc_status = (GET_BYTE(to_push, 3) & 0x7U); bool cruise_engaged = (acc_status == 3) || (acc_status == 4) || (acc_status == 5); - pcm_cruise_check(cruise_engaged); + acc_main_on = cruise_engaged || (acc_status == 2); + + if (!volkswagen_longitudinal) { + pcm_cruise_check(cruise_engaged); + } + + if (!acc_main_on) { + controls_allowed = false; + } + } + + if (addr == MSG_GRA_ACC_01) { + // If using openpilot longitudinal, enter controls on falling edge of Set or Resume with main switch on + // Signal: GRA_ACC_01.GRA_Tip_Setzen + // Signal: GRA_ACC_01.GRA_Tip_Wiederaufnahme + if (volkswagen_longitudinal) { + bool set_button = GET_BIT(to_push, 16U); + bool resume_button = GET_BIT(to_push, 19U); + if ((volkswagen_set_button_prev && !set_button) || (volkswagen_resume_button_prev && !resume_button)) { + controls_allowed = acc_main_on; + } + volkswagen_set_button_prev = set_button; + volkswagen_resume_button_prev = resume_button; + } + // Always exit controls on rising edge of Cancel + // Signal: GRA_ACC_01.GRA_Abbrechen + if (GET_BIT(to_push, 13U) == 1U) { + controls_allowed = false; + } } // Signal: Motor_20.MO_Fahrpedalrohwert_01 @@ -131,24 +186,31 @@ static int volkswagen_mqb_rx_hook(CANPacket_t *to_push) { gas_pressed = ((GET_BYTES_04(to_push) >> 12) & 0xFFU) != 0U; } - // Signal: ESP_05.ESP_Fahrer_bremst + // Signal: Motor_14.MO_Fahrer_bremst (ECU detected brake pedal switch F63) + if (addr == MSG_MOTOR_14) { + volkswagen_mqb_brake_pedal_switch = (GET_BYTE(to_push, 3) & 0x10U) >> 4; + } + + // Signal: ESP_05.ESP_Fahrer_bremst (ESP detected driver brake pressure above platform specified threshold) if (addr == MSG_ESP_05) { - brake_pressed = (GET_BYTE(to_push, 3) & 0x4U) >> 2; + volkswagen_mqb_brake_pressure_detected = (GET_BYTE(to_push, 3) & 0x4U) >> 2; } + brake_pressed = volkswagen_mqb_brake_pedal_switch || volkswagen_mqb_brake_pressure_detected; + generic_rx_checks((addr == MSG_HCA_01)); } return valid; } -static int volkswagen_mqb_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { - UNUSED(longitudinal_allowed); - +static int volkswagen_mqb_tx_hook(CANPacket_t *to_send) { int addr = GET_ADDR(to_send); int tx = 1; - if (!msg_allowed(to_send, VOLKSWAGEN_MQB_TX_MSGS, VOLKSWAGEN_MQB_TX_MSGS_LEN)) { - tx = 0; + if (volkswagen_longitudinal) { + tx = msg_allowed(to_send, VOLKSWAGEN_MQB_LONG_TX_MSGS, sizeof(VOLKSWAGEN_MQB_LONG_TX_MSGS) / sizeof(VOLKSWAGEN_MQB_LONG_TX_MSGS[0])); + } else { + tx = msg_allowed(to_send, VOLKSWAGEN_MQB_STOCK_TX_MSGS, sizeof(VOLKSWAGEN_MQB_STOCK_TX_MSGS) / sizeof(VOLKSWAGEN_MQB_STOCK_TX_MSGS[0])); } // Safety check for HCA_01 Heading Control Assist torque @@ -166,6 +228,30 @@ static int volkswagen_mqb_tx_hook(CANPacket_t *to_send, bool longitudinal_allowe } } + // Safety check for both ACC_06 and ACC_07 acceleration requests + // To avoid floating point math, scale upward and compare to pre-scaled safety m/s2 boundaries + if ((addr == MSG_ACC_06) || (addr == MSG_ACC_07)) { + bool violation = false; + int desired_accel = 0; + + if (addr == MSG_ACC_06) { + // Signal: ACC_06.ACC_Sollbeschleunigung_02 (acceleration in m/s2, scale 0.005, offset -7.22) + desired_accel = ((((GET_BYTE(to_send, 4) & 0x7U) << 8) | GET_BYTE(to_send, 3)) * 5U) - 7220U; + } else { + // Signal: ACC_07.ACC_Folgebeschl (acceleration in m/s2, scale 0.03, offset -4.6) + int secondary_accel = (GET_BYTE(to_send, 4) * 30U) - 4600U; + violation |= (secondary_accel != 3020); // enforce always inactive (one increment above max range) at this time + // Signal: ACC_07.ACC_Sollbeschleunigung_02 (acceleration in m/s2, scale 0.005, offset -7.22) + desired_accel = (((GET_BYTE(to_send, 7) << 3) | ((GET_BYTE(to_send, 6) & 0xE0U) >> 5)) * 5U) - 7220U; + } + + violation |= longitudinal_accel_checks(desired_accel, VOLKSWAGEN_MQB_LONG_LIMITS); + + if (violation) { + tx = 0; + } + } + // FORCE CANCEL: ensuring that only the cancel button press is sent when controls are off. // This avoids unintended engagements while still allowing resume spam if ((addr == MSG_GRA_ACC_01) && !controls_allowed) { @@ -190,7 +276,10 @@ static int volkswagen_mqb_fwd_hook(int bus_num, CANPacket_t *to_fwd) { break; case 2: if ((addr == MSG_HCA_01) || (addr == MSG_LDW_02)) { - // OP takes control of the Heading Control Assist and Lane Departure Warning messages from the camera + // openpilot takes over LKAS steering control and related HUD messages from the camera + bus_fwd = -1; + } else if (volkswagen_longitudinal && ((addr == MSG_ACC_02) || (addr == MSG_ACC_06) || (addr == MSG_ACC_07))) { + // openpilot takes over acceleration/braking control and related HUD messages from the stock ACC radar bus_fwd = -1; } else { // Forward all remaining traffic from Extended CAN devices to J533 gateway diff --git a/board/safety/safety_volkswagen_pq.h b/board/safety/safety_volkswagen_pq.h index e3657a125a..2cacb1227f 100644 --- a/board/safety/safety_volkswagen_pq.h +++ b/board/safety/safety_volkswagen_pq.h @@ -1,3 +1,5 @@ +#include "safety_volkswagen_common.h" + // lateral limits const SteeringLimits VOLKSWAGEN_PQ_STEERING_LIMITS = { .max_steer = 300, // 3.0 Nm (EPS side max of 3.0Nm with fault if violated) @@ -11,8 +13,12 @@ const SteeringLimits VOLKSWAGEN_PQ_STEERING_LIMITS = { }; // longitudinal limits -const int VOLKSWAGEN_PQ_MAX_ACCEL = 2000; // 2.0 m/s2 -const int VOLKSWAGEN_PQ_MIN_ACCEL = -3500; // -3.5 m/s2 +// acceleration in m/s2 * 1000 to avoid floating point math +const LongitudinalLimits VOLKSWAGEN_PQ_LONG_LIMITS = { + .max_accel = 2000, + .min_accel = -3500, + .inactive_accel = 3010, // VW sends one increment above the max range when inactive +}; #define MSG_LENKHILFE_3 0x0D0 // RX from EPS, for steering angle and driver steering torque #define MSG_HCA_1 0x0D2 // TX by OP, Heading Control Assist steering torque @@ -22,14 +28,14 @@ const int VOLKSWAGEN_PQ_MIN_ACCEL = -3500; // -3.5 m/s2 #define MSG_MOTOR_3 0x380 // RX from ECU, for driver throttle input #define MSG_GRA_NEU 0x38A // TX by OP, ACC control buttons for cancel/resume #define MSG_MOTOR_5 0x480 // RX from ECU, for ACC main switch state -#define MSG_ACC_GRA_ANZIEGE 0x56A // TX by OP, ACC HUD +#define MSG_ACC_GRA_ANZEIGE 0x56A // TX by OP, ACC HUD #define MSG_LDW_1 0x5BE // TX by OP, Lane line recognition and text alerts // Transmit of GRA_Neu is allowed on bus 0 and 2 to keep compatibility with gateway and camera integration const CanMsg VOLKSWAGEN_PQ_STOCK_TX_MSGS[] = {{MSG_HCA_1, 0, 5}, {MSG_LDW_1, 0, 8}, {MSG_GRA_NEU, 0, 4}, {MSG_GRA_NEU, 2, 4}}; const CanMsg VOLKSWAGEN_PQ_LONG_TX_MSGS[] = {{MSG_HCA_1, 0, 5}, {MSG_LDW_1, 0, 8}, - {MSG_ACC_SYSTEM, 0, 8}, {MSG_ACC_GRA_ANZIEGE, 0, 8}}; + {MSG_ACC_SYSTEM, 0, 8}, {MSG_ACC_GRA_ANZEIGE, 0, 8}}; AddrCheckStruct volkswagen_pq_addr_checks[] = { {.msg = {{MSG_LENKHILFE_3, 0, 6, .check_checksum = true, .max_counter = 15U, .expected_timestep = 10000U}, { 0 }, { 0 }}}, @@ -42,11 +48,6 @@ AddrCheckStruct volkswagen_pq_addr_checks[] = { #define VOLKSWAGEN_PQ_ADDR_CHECKS_LEN (sizeof(volkswagen_pq_addr_checks) / sizeof(volkswagen_pq_addr_checks[0])) addr_checks volkswagen_pq_rx_checks = {volkswagen_pq_addr_checks, VOLKSWAGEN_PQ_ADDR_CHECKS_LEN}; -const uint16_t FLAG_VOLKSWAGEN_LONG_CONTROL = 1; -bool volkswagen_pq_longitudinal = false; -bool volkswagen_pq_set_prev = false; -bool volkswagen_pq_resume_prev = false; - static uint32_t volkswagen_pq_get_checksum(CANPacket_t *to_push) { int addr = GET_ADDR(to_push); @@ -87,8 +88,11 @@ static uint32_t volkswagen_pq_compute_checksum(CANPacket_t *to_push) { static const addr_checks* volkswagen_pq_init(uint16_t param) { UNUSED(param); + volkswagen_set_button_prev = false; + volkswagen_resume_button_prev = false; + #ifdef ALLOW_DEBUG - volkswagen_pq_longitudinal = GET_FLAG(param, FLAG_VOLKSWAGEN_LONG_CONTROL); + volkswagen_longitudinal = GET_FLAG(param, FLAG_VOLKSWAGEN_LONG_CONTROL); #endif return &volkswagen_pq_rx_checks; } @@ -96,7 +100,7 @@ static const addr_checks* volkswagen_pq_init(uint16_t param) { static int volkswagen_pq_rx_hook(CANPacket_t *to_push) { bool valid = addr_safety_check(to_push, &volkswagen_pq_rx_checks, - volkswagen_pq_get_checksum, volkswagen_pq_compute_checksum, volkswagen_pq_get_counter); + volkswagen_pq_get_checksum, volkswagen_pq_compute_checksum, volkswagen_pq_get_counter, NULL); if (valid && (GET_BUS(to_push) == 0U)) { int addr = GET_ADDR(to_push); @@ -105,8 +109,7 @@ static int volkswagen_pq_rx_hook(CANPacket_t *to_push) { // Signal: Bremse_1.Geschwindigkeit_neu__Bremse_1_ if (addr == MSG_BREMSE_1) { int speed = ((GET_BYTE(to_push, 2) & 0xFEU) >> 1) | (GET_BYTE(to_push, 3) << 7); - // DBC speed scale 0.01: 0.3m/s = 108. - vehicle_moving = speed > 108; + vehicle_moving = speed > 0; } // Update driver input torque samples @@ -121,7 +124,7 @@ static int volkswagen_pq_rx_hook(CANPacket_t *to_push) { update_sample(&torque_driver, torque_driver_new); } - if (volkswagen_pq_longitudinal) { + if (volkswagen_longitudinal) { if (addr == MSG_MOTOR_5) { // ACC main switch on is a prerequisite to enter controls, exit controls immediately on main switch off // Signal: Motor_5.GRA_Hauptschalter @@ -137,11 +140,11 @@ static int volkswagen_pq_rx_hook(CANPacket_t *to_push) { // Signal: GRA_Neu.GRA_Neu_Recall bool set_button = GET_BIT(to_push, 16U); bool resume_button = GET_BIT(to_push, 17U); - if ((volkswagen_pq_set_prev && !set_button) || (volkswagen_pq_resume_prev && !resume_button)) { + if ((volkswagen_set_button_prev && !set_button) || (volkswagen_resume_button_prev && !resume_button)) { controls_allowed = acc_main_on; } - volkswagen_pq_set_prev = set_button; - volkswagen_pq_resume_prev = resume_button; + volkswagen_set_button_prev = set_button; + volkswagen_resume_button_prev = resume_button; // Exit controls on rising edge of Cancel, override Set/Resume if present simultaneously // Signal: GRA_ACC_01.GRA_Abbrechen if (GET_BIT(to_push, 9U) == 1U) { @@ -173,11 +176,11 @@ static int volkswagen_pq_rx_hook(CANPacket_t *to_push) { return valid; } -static int volkswagen_pq_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { +static int volkswagen_pq_tx_hook(CANPacket_t *to_send) { int addr = GET_ADDR(to_send); int tx = 1; - if (volkswagen_pq_longitudinal) { + if (volkswagen_longitudinal) { tx = msg_allowed(to_send, VOLKSWAGEN_PQ_LONG_TX_MSGS, sizeof(VOLKSWAGEN_PQ_LONG_TX_MSGS) / sizeof(VOLKSWAGEN_PQ_LONG_TX_MSGS[0])); } else { tx = msg_allowed(to_send, VOLKSWAGEN_PQ_STOCK_TX_MSGS, sizeof(VOLKSWAGEN_PQ_STOCK_TX_MSGS) / sizeof(VOLKSWAGEN_PQ_STOCK_TX_MSGS[0])); @@ -202,24 +205,10 @@ static int volkswagen_pq_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed // Safety check for acceleration commands // To avoid floating point math, scale upward and compare to pre-scaled safety m/s2 boundaries if (addr == MSG_ACC_SYSTEM) { - bool violation = 0; - int desired_accel = 0; - // Signal: ACC_System.ACS_Sollbeschl (acceleration in m/s2, scale 0.005, offset -7.22) - desired_accel = ((((GET_BYTE(to_send, 4) & 0x7U) << 8) | GET_BYTE(to_send, 3)) * 5U) - 7220U; - - // VW send one increment above the max range when inactive - if (desired_accel == 3010) { - desired_accel = 0; - } - - if (!longitudinal_allowed && (desired_accel != 0)) { - violation = 1; - } - - violation |= max_limit_check(desired_accel, VOLKSWAGEN_PQ_MAX_ACCEL, VOLKSWAGEN_PQ_MIN_ACCEL); + int desired_accel = ((((GET_BYTE(to_send, 4) & 0x7U) << 8) | GET_BYTE(to_send, 3)) * 5U) - 7220U; - if (violation) { + if (longitudinal_accel_checks(desired_accel, VOLKSWAGEN_PQ_LONG_LIMITS)) { tx = 0; } } @@ -251,7 +240,7 @@ static int volkswagen_pq_fwd_hook(int bus_num, CANPacket_t *to_fwd) { if ((addr == MSG_HCA_1) || (addr == MSG_LDW_1)) { // openpilot takes over LKAS steering control and related HUD messages from the camera bus_fwd = -1; - } else if (volkswagen_pq_longitudinal && ((addr == MSG_ACC_SYSTEM) || (addr == MSG_ACC_GRA_ANZIEGE))) { + } else if (volkswagen_longitudinal && ((addr == MSG_ACC_SYSTEM) || (addr == MSG_ACC_GRA_ANZEIGE))) { // openpilot takes over acceleration/braking control and related HUD messages from the stock ACC radar } else { // Forward all remaining traffic from Extended CAN devices to J533 gateway diff --git a/board/safety_declarations.h b/board/safety_declarations.h index d50d4d1fe0..49c8db03d4 100644 --- a/board/safety_declarations.h +++ b/board/safety_declarations.h @@ -1,3 +1,5 @@ +#pragma once + #define GET_BIT(msg, b) (((msg)->data[((b) / 8U)] >> ((b) % 8U)) & 0x1U) #define GET_BYTE(msg, b) ((msg)->data[(b)]) #define GET_BYTES_04(msg) ((msg)->data[0] | ((msg)->data[1] << 8U) | ((msg)->data[2] << 16U) | ((msg)->data[3] << 24U)) @@ -32,6 +34,7 @@ typedef enum { } SteeringControlType; typedef struct { + // torque cmd limits const int max_steer; const int max_rate_up; const int max_rate_down; @@ -52,14 +55,37 @@ typedef struct { const int max_invalid_request_frames; const uint32_t min_valid_request_rt_interval; const bool has_steer_req_tolerance; + + // angle cmd limits + const int angle_deg_to_can; + const struct lookup_t angle_rate_up_lookup; + const struct lookup_t angle_rate_down_lookup; } SteeringLimits; +typedef struct { + // acceleration cmd limits + const int max_accel; + const int min_accel; + const int inactive_accel; + + // gas & brake cmd limits + // inactive and min gas are 0 on most safety modes + const int max_gas; + const int min_gas; + const int inactive_gas; + const int max_brake; + + // speed cmd limits + const int inactive_speed; +} LongitudinalLimits; + typedef struct { const int addr; const int bus; const int len; const bool check_checksum; // true is checksum check is performed const uint8_t max_counter; // maximum value of the counter. 0 means that the counter check is skipped + const bool quality_flag; // true is quality flag check is performed const uint32_t expected_timestep; // expected time between message updates [us] } CanMsgCheck; @@ -72,6 +98,7 @@ typedef struct { int index; // if multiple messages are allowed to be checked, this stores the index of the first one seen. only msg[msg_index] will be used bool valid_checksum; // true if and only if checksum check is passed int wrong_counters; // counter of wrong counters, saturated between 0 and MAX_WRONG_COUNTERS + bool valid_quality_flag; // true if the message's quality/health/status signals are valid uint8_t last_counter; // last counter value uint32_t last_timestamp; // micro-s bool lagging; // true if and only if the time between updates is excessive @@ -108,16 +135,23 @@ bool addr_safety_check(CANPacket_t *to_push, const addr_checks *rx_checks, uint32_t (*get_checksum)(CANPacket_t *to_push), uint32_t (*compute_checksum)(CANPacket_t *to_push), - uint8_t (*get_counter)(CANPacket_t *to_push)); + uint8_t (*get_counter)(CANPacket_t *to_push), + bool (*get_quality_flag_valid)(CANPacket_t *to_push)); void generic_rx_checks(bool stock_ecu_detected); void relay_malfunction_set(void); void relay_malfunction_reset(void); bool steer_torque_cmd_checks(int desired_torque, int steer_req, const SteeringLimits limits); +bool steer_angle_cmd_checks(int desired_angle, bool steer_control_enabled, const SteeringLimits limits); +bool longitudinal_accel_checks(int desired_accel, const LongitudinalLimits limits); +bool longitudinal_speed_checks(int desired_speed, const LongitudinalLimits limits); +bool longitudinal_gas_checks(int desired_gas, const LongitudinalLimits limits); +bool longitudinal_brake_checks(int desired_brake, const LongitudinalLimits limits); +bool longitudinal_interceptor_checks(CANPacket_t *to_send); void pcm_cruise_check(bool cruise_engaged); typedef const addr_checks* (*safety_hook_init)(uint16_t param); typedef int (*rx_hook)(CANPacket_t *to_push); -typedef int (*tx_hook)(CANPacket_t *to_send, bool longitudinal_allowed); +typedef int (*tx_hook)(CANPacket_t *to_send); typedef int (*tx_lin_hook)(int lin_num, uint8_t *data, int len); typedef int (*fwd_hook)(int bus_num, CANPacket_t *to_fwd); diff --git a/board/stm32fx/board.h b/board/stm32fx/board.h index 90aa739fda..878c1be9ec 100644 --- a/board/stm32fx/board.h +++ b/board/stm32fx/board.h @@ -11,7 +11,7 @@ #include "stm32fx/llfan.h" #include "stm32fx/llrtc.h" #include "drivers/rtc.h" - #include "stm32fx/clock_source.h" + #include "drivers/clock_source.h" #include "boards/white.h" #include "boards/grey.h" #include "boards/black.h" @@ -48,14 +48,7 @@ void detect_board_type(void) { current_board = &board_pedal; #else hw_type = HW_TYPE_UNKNOWN; - puts("Hardware type is UNKNOWN!\n"); + print("Hardware type is UNKNOWN!\n"); #endif #endif } - -bool has_external_debug_serial = 0; - -void detect_external_debug_serial(void) { - // detect if external serial debugging is present - has_external_debug_serial = detect_with_pull(GPIOA, 3, PULL_DOWN); -} diff --git a/board/stm32fx/clock.h b/board/stm32fx/clock.h index f2386328d1..fb918d3125 100644 --- a/board/stm32fx/clock.h +++ b/board/stm32fx/clock.h @@ -4,9 +4,18 @@ void clock_init(void) { while ((RCC->CR & RCC_CR_HSERDY) == 0); // divide things + // AHB = 96MHz + // APB1 = 48MHz + // APB2 = 48MHz register_set(&(RCC->CFGR), RCC_CFGR_HPRE_DIV1 | RCC_CFGR_PPRE2_DIV2 | RCC_CFGR_PPRE1_DIV2, 0xFF7FFCF3U); - // 16mhz crystal + // 16MHz crystal + // PLLM: 8 + // PLLN: 96 + // PLLP: 2 + // PLLQ: 4 + // P output: 96MHz + // Q output: 48MHz register_set(&(RCC->PLLCFGR), RCC_PLLCFGR_PLLQ_2 | RCC_PLLCFGR_PLLM_3 | RCC_PLLCFGR_PLLN_6 | RCC_PLLCFGR_PLLN_5 | RCC_PLLCFGR_PLLSRC_HSE, 0x7F437FFFU); // start PLL diff --git a/board/stm32fx/clock_source.h b/board/stm32fx/clock_source.h deleted file mode 100644 index f8fea5fca0..0000000000 --- a/board/stm32fx/clock_source.h +++ /dev/null @@ -1,98 +0,0 @@ - -#define CLOCK_SOURCE_MODE_DISABLED 0U -#define CLOCK_SOURCE_MODE_FREE_RUNNING 1U -#define CLOCK_SOURCE_MODE_PWM 2U - -#define CLOCK_SOURCE_PERIOD_MS 50U -#define CLOCK_SOURCE_PULSE_LEN_MS 2U - -uint8_t clock_source_mode = CLOCK_SOURCE_MODE_DISABLED; - -void TIM1_UP_TIM10_IRQ_Handler(void) { - if((TIM1->SR & TIM_SR_UIF) != 0) { - if(clock_source_mode == CLOCK_SOURCE_MODE_FREE_RUNNING) { - // Start clock pulse - set_gpio_output(GPIOB, 14, true); - set_gpio_output(GPIOB, 15, true); - set_gpio_output(GPIOC, 5, true); - } - - // Reset interrupt - TIM1->SR &= ~(TIM_SR_UIF); - } -} - -void TIM1_CC_IRQ_Handler(void) { - if((TIM1->SR & TIM_SR_CC1IF) != 0) { - if(clock_source_mode == CLOCK_SOURCE_MODE_FREE_RUNNING) { - // End clock pulse - set_gpio_output(GPIOB, 14, false); - set_gpio_output(GPIOB, 15, false); - set_gpio_output(GPIOC, 5, false); - } - - // Reset interrupt - TIM1->SR &= ~(TIM_SR_CC1IF); - } -} - -void clock_source_init(uint8_t mode){ - // Setup timer - REGISTER_INTERRUPT(TIM1_UP_TIM10_IRQn, TIM1_UP_TIM10_IRQ_Handler, (1200U / CLOCK_SOURCE_PERIOD_MS) , FAULT_INTERRUPT_RATE_TIM1) - REGISTER_INTERRUPT(TIM1_CC_IRQn, TIM1_CC_IRQ_Handler, (1200U / CLOCK_SOURCE_PERIOD_MS) , FAULT_INTERRUPT_RATE_TIM1) - register_set(&(TIM1->PSC), ((APB2_FREQ*100U)-1U), 0xFFFFU); // Tick on 0.1 ms - register_set(&(TIM1->ARR), ((CLOCK_SOURCE_PERIOD_MS*10U) - 1U), 0xFFFFU); // Period - register_set(&(TIM1->CCMR1), 0U, 0xFFFFU); // No output on compare - register_set(&(TIM1->CCER), TIM_CCER_CC1E, 0xFFFFU); // Enable compare 1 - register_set(&(TIM1->CCR1), (CLOCK_SOURCE_PULSE_LEN_MS*10U), 0xFFFFU); // Compare 1 value - register_set(&(TIM1->CCR2), (CLOCK_SOURCE_PULSE_LEN_MS*10U), 0xFFFFU); // Compare 1 value - register_set(&(TIM1->CCR3), (CLOCK_SOURCE_PULSE_LEN_MS*10U), 0xFFFFU); // Compare 1 value - register_set_bits(&(TIM1->DIER), TIM_DIER_UIE | TIM_DIER_CC1IE); // Enable interrupts - register_set(&(TIM1->CR1), TIM_CR1_CEN, 0x3FU); // Enable timer - - // Set mode - switch(mode) { - case CLOCK_SOURCE_MODE_DISABLED: - // No clock signal - NVIC_DisableIRQ(TIM1_UP_TIM10_IRQn); - NVIC_DisableIRQ(TIM1_CC_IRQn); - - // Disable pulse if we were in the middle of it - set_gpio_output(GPIOB, 14, false); - set_gpio_output(GPIOB, 15, false); - - clock_source_mode = CLOCK_SOURCE_MODE_DISABLED; - break; - case CLOCK_SOURCE_MODE_FREE_RUNNING: - // Clock signal is based on internal timer - NVIC_EnableIRQ(TIM1_UP_TIM10_IRQn); - NVIC_EnableIRQ(TIM1_CC_IRQn); - - clock_source_mode = CLOCK_SOURCE_MODE_FREE_RUNNING; - break; - case CLOCK_SOURCE_MODE_PWM: - // No interrupts - NVIC_DisableIRQ(TIM1_UP_TIM10_IRQn); - NVIC_DisableIRQ(TIM1_CC_IRQn); - - // Set GPIO as timer channels - set_gpio_alternate(GPIOB, 14, GPIO_AF1_TIM1); - set_gpio_alternate(GPIOB, 15, GPIO_AF1_TIM1); - - // Set PWM mode - register_set(&(TIM1->CCMR1), (0b110 << TIM_CCMR1_OC2M_Pos), 0xFFFFU); - register_set(&(TIM1->CCMR2), (0b110 << TIM_CCMR2_OC3M_Pos), 0xFFFFU); - - // Enable output - register_set(&(TIM1->BDTR), TIM_BDTR_MOE, 0xFFFFU); - - // Enable complementary compares - register_set_bits(&(TIM1->CCER), TIM_CCER_CC2NE | TIM_CCER_CC3NE); - - clock_source_mode = CLOCK_SOURCE_MODE_PWM; - break; - default: - puts("Unknown clock source mode: "); puth(mode); puts("\n"); - break; - } -} diff --git a/board/stm32fx/lladc.h b/board/stm32fx/lladc.h index 2ae14a2dcb..0d3e8e028a 100644 --- a/board/stm32fx/lladc.h +++ b/board/stm32fx/lladc.h @@ -28,7 +28,7 @@ uint32_t adc_get(unsigned int channel) { return ADC1->JDR1; } -uint32_t adc_get_voltage(void) { +uint32_t adc_get_voltage(uint16_t scale) { // REVC has a 10, 1 (1/11) voltage divider // Here is the calculation for the scale (s) // ADCV = VIN_S * (1/11) * (4095/3.3) @@ -36,5 +36,5 @@ uint32_t adc_get_voltage(void) { // s = 1000/((4095/3.3)*(1/11)) = 8.8623046875 // Avoid needing floating point math, so output in mV - return (adc_get(ADCCHAN_VOLTAGE) * 8862U) / 1000U; + return (adc_get(ADCCHAN_VOLTAGE) * scale) / 1000U; } diff --git a/board/stm32fx/llbxcan.h b/board/stm32fx/llbxcan.h index a1f0182bee..0fdfd398a9 100644 --- a/board/stm32fx/llbxcan.h +++ b/board/stm32fx/llbxcan.h @@ -1,3 +1,8 @@ +// Flasher and pedal use raw mailbox access +#define GET_MAILBOX_BYTE(msg, b) (((int)(b) > 3) ? (((msg)->RDHR >> (8U * ((unsigned int)(b) % 4U))) & 0xFFU) : (((msg)->RDLR >> (8U * (unsigned int)(b))) & 0xFFU)) +#define GET_MAILBOX_BYTES_04(msg) ((msg)->RDLR) +#define GET_MAILBOX_BYTES_48(msg) ((msg)->RDHR) + // SAE 2284-3 : minimum 16 tq, SJW 3, sample point at 81.3% #define CAN_QUANTA 16U #define CAN_SEQ1 12U @@ -11,7 +16,7 @@ #define CAN_NAME_FROM_CANIF(CAN_DEV) (((CAN_DEV)==CAN1) ? "CAN1" : (((CAN_DEV) == CAN2) ? "CAN2" : "CAN3")) -void puts(const char *a); +void print(const char *a); // kbps multiplied by 10 const uint32_t speeds[] = {100U, 200U, 500U, 1000U, 1250U, 2500U, 5000U, 10000U}; @@ -29,7 +34,7 @@ bool llcan_set_speed(CAN_TypeDef *CAN_obj, uint32_t speed, bool loopback, bool s timeout_counter++; if(timeout_counter >= CAN_INIT_TIMEOUT_MS){ - puts(CAN_NAME_FROM_CANIF(CAN_obj)); puts(" set_speed timed out (1)!\n"); + print(CAN_NAME_FROM_CANIF(CAN_obj)); print(" set_speed timed out (1)!\n"); ret = false; break; } @@ -60,7 +65,7 @@ bool llcan_set_speed(CAN_TypeDef *CAN_obj, uint32_t speed, bool loopback, bool s timeout_counter++; if(timeout_counter >= CAN_INIT_TIMEOUT_MS){ - puts(CAN_NAME_FROM_CANIF(CAN_obj)); puts(" set_speed timed out (2)!\n"); + print(CAN_NAME_FROM_CANIF(CAN_obj)); print(" set_speed timed out (2)!\n"); ret = false; break; } @@ -84,7 +89,7 @@ bool llcan_init(CAN_TypeDef *CAN_obj) { timeout_counter++; if(timeout_counter >= CAN_INIT_TIMEOUT_MS){ - puts(CAN_NAME_FROM_CANIF(CAN_obj)); puts(" initialization timed out!\n"); + print(CAN_NAME_FROM_CANIF(CAN_obj)); print(" initialization timed out!\n"); ret = false; break; } @@ -120,7 +125,7 @@ bool llcan_init(CAN_TypeDef *CAN_obj) { NVIC_EnableIRQ(CAN3_SCE_IRQn); #endif } else { - puts("Invalid CAN: initialization failed\n"); + print("Invalid CAN: initialization failed\n"); } } return ret; diff --git a/board/stm32fx/lldac.h b/board/stm32fx/lldac.h index 7b2e112bee..6cd2f8ca2c 100644 --- a/board/stm32fx/lldac.h +++ b/board/stm32fx/lldac.h @@ -11,6 +11,6 @@ void dac_set(int channel, uint32_t value) { } else if (channel == 1) { register_set(&(DAC->DHR12R2), value, 0xFFFU); } else { - puts("Failed to set DAC: invalid channel value: 0x"); puth(value); puts("\n"); + print("Failed to set DAC: invalid channel value: 0x"); puth(value); print("\n"); } } diff --git a/board/stm32fx/llfan.h b/board/stm32fx/llfan.h index 403314153d..f66fc187ff 100644 --- a/board/stm32fx/llfan.h +++ b/board/stm32fx/llfan.h @@ -1,23 +1,25 @@ // TACH interrupt handler void EXTI2_IRQ_Handler(void) { - volatile unsigned int pr = EXTI->PR & (1U << 2); - if ((pr & (1U << 2)) != 0U) { - fan_state.tach_counter++; - } - EXTI->PR = (1U << 2); + volatile unsigned int pr = EXTI->PR & (1U << 2); + if ((pr & (1U << 2)) != 0U) { + fan_state.tach_counter++; + } + EXTI->PR = (1U << 2); } -void fan_init(void){ - // 5000RPM * 4 tach edges / 60 seconds - REGISTER_INTERRUPT(EXTI2_IRQn, EXTI2_IRQ_Handler, 700U, FAULT_INTERRUPT_RATE_TACH) +void llfan_init(void) { + fan_reset_cooldown(); - // Init PWM speed control - pwm_init(TIM3, 3); + // 5000RPM * 4 tach edges / 60 seconds + REGISTER_INTERRUPT(EXTI2_IRQn, EXTI2_IRQ_Handler, 700U, FAULT_INTERRUPT_RATE_TACH) - // Init TACH interrupt - register_set(&(SYSCFG->EXTICR[0]), SYSCFG_EXTICR1_EXTI2_PD, 0xF00U); - register_set_bits(&(EXTI->IMR), (1U << 2)); - register_set_bits(&(EXTI->RTSR), (1U << 2)); - register_set_bits(&(EXTI->FTSR), (1U << 2)); - NVIC_EnableIRQ(EXTI2_IRQn); + // Init PWM speed control + pwm_init(TIM3, 3); + + // Init TACH interrupt + register_set(&(SYSCFG->EXTICR[0]), SYSCFG_EXTICR1_EXTI2_PD, 0xF00U); + register_set_bits(&(EXTI->IMR), (1U << 2)); + register_set_bits(&(EXTI->RTSR), (1U << 2)); + register_set_bits(&(EXTI->FTSR), (1U << 2)); + NVIC_EnableIRQ(EXTI2_IRQn); } diff --git a/board/stm32fx/llrtc.h b/board/stm32fx/llrtc.h index b286ce72bf..a9b61917e7 100644 --- a/board/stm32fx/llrtc.h +++ b/board/stm32fx/llrtc.h @@ -1,6 +1,3 @@ -#define RCC_BDCR_MASK_LSE (RCC_BDCR_RTCEN | RCC_BDCR_RTCSEL | RCC_BDCR_LSEMOD | RCC_BDCR_LSEBYP | RCC_BDCR_LSEON) -#define RCC_BDCR_MASK_LSI (RCC_BDCR_RTCEN | RCC_BDCR_RTCSEL) - void enable_bdomain_protection(void) { register_clear_bits(&(PWR->CR), PWR_CR_DBP); } @@ -9,6 +6,38 @@ void disable_bdomain_protection(void) { register_set_bits(&(PWR->CR), PWR_CR_DBP); } +void rtc_init(void){ + uint32_t bdcr_opts = RCC_BDCR_RTCEN; + uint32_t bdcr_mask = (RCC_BDCR_RTCEN | RCC_BDCR_RTCSEL); + if (current_board->has_rtc_battery) { + bdcr_opts |= (RCC_BDCR_RTCSEL_0 | RCC_BDCR_LSEON); + bdcr_mask |= (RCC_BDCR_LSEMOD | RCC_BDCR_LSEBYP | RCC_BDCR_LSEON); + } else { + bdcr_opts |= RCC_BDCR_RTCSEL_1; + RCC->CSR |= RCC_CSR_LSION; + while((RCC->CSR & RCC_CSR_LSIRDY) == 0){} + } + + // Initialize RTC module and clock if not done already. + if((RCC->BDCR & bdcr_mask) != bdcr_opts){ + print("Initializing RTC\n"); + // Reset backup domain + register_set_bits(&(RCC->BDCR), RCC_BDCR_BDRST); + + // Disable write protection + disable_bdomain_protection(); + + // Clear backup domain reset + register_clear_bits(&(RCC->BDCR), RCC_BDCR_BDRST); + + // Set RTC options + register_set(&(RCC->BDCR), bdcr_opts, bdcr_mask); + + // Enable write protection + enable_bdomain_protection(); + } +} + void rtc_wakeup_init(void) { EXTI->IMR |= EXTI_IMR_MR22; EXTI->RTSR |= EXTI_RTSR_TR22; // rising edge diff --git a/board/stm32fx/llspi.h b/board/stm32fx/llspi.h index d07bce352f..3aa7236bd6 100644 --- a/board/stm32fx/llspi.h +++ b/board/stm32fx/llspi.h @@ -1,131 +1,87 @@ -// IRQs: DMA2_Stream2, DMA2_Stream3, EXTI4 - -void spi_init(void); -int spi_cb_rx(uint8_t *data, int len, uint8_t *data_out); - -// end API - -#define SPI_BUF_SIZE 256 -uint8_t spi_buf[SPI_BUF_SIZE]; -int spi_buf_count = 0; -int spi_total_count = 0; - -void spi_tx_dma(void *addr, int len) { +void llspi_miso_dma(uint8_t *addr, int len) { // disable DMA + DMA2_Stream3->CR &= ~DMA_SxCR_EN; register_clear_bits(&(SPI1->CR2), SPI_CR2_TXDMAEN); - register_clear_bits(&(DMA2_Stream3->CR), DMA_SxCR_EN); - // DMA2, stream 3, channel 3 + // setup source and length register_set(&(DMA2_Stream3->M0AR), (uint32_t)addr, 0xFFFFFFFFU); DMA2_Stream3->NDTR = len; - register_set(&(DMA2_Stream3->PAR), (uint32_t)&(SPI1->DR), 0xFFFFFFFFU); - - // channel3, increment memory, memory -> periph, enable - register_set(&(DMA2_Stream3->CR), (DMA_SxCR_CHSEL_1 | DMA_SxCR_CHSEL_0 | DMA_SxCR_MINC | DMA_SxCR_DIR_0 | DMA_SxCR_EN), 0x1E077EFEU); - delay(0); - register_set_bits(&(DMA2_Stream3->CR), DMA_SxCR_TCIE); + // enable DMA register_set_bits(&(SPI1->CR2), SPI_CR2_TXDMAEN); - - // signal data is ready by driving low - // esp must be configured as input by this point - set_gpio_output(GPIOB, 0, 0); + DMA2_Stream3->CR |= DMA_SxCR_EN; } -void spi_rx_dma(void *addr, int len) { +void llspi_mosi_dma(uint8_t *addr, int len) { // disable DMA register_clear_bits(&(SPI1->CR2), SPI_CR2_RXDMAEN); - register_clear_bits(&(DMA2_Stream2->CR), DMA_SxCR_EN); + DMA2_Stream2->CR &= ~DMA_SxCR_EN; // drain the bus volatile uint8_t dat = SPI1->DR; (void)dat; - // DMA2, stream 2, channel 3 + // setup destination and length register_set(&(DMA2_Stream2->M0AR), (uint32_t)addr, 0xFFFFFFFFU); DMA2_Stream2->NDTR = len; - register_set(&(DMA2_Stream2->PAR), (uint32_t)&(SPI1->DR), 0xFFFFFFFFU); - - // channel3, increment memory, periph -> memory, enable - register_set(&(DMA2_Stream2->CR), (DMA_SxCR_CHSEL_1 | DMA_SxCR_CHSEL_0 | DMA_SxCR_MINC | DMA_SxCR_EN), 0x1E077EFEU); - delay(0); - register_set_bits(&(DMA2_Stream2->CR), DMA_SxCR_TCIE); + // enable DMA + DMA2_Stream2->CR |= DMA_SxCR_EN; register_set_bits(&(SPI1->CR2), SPI_CR2_RXDMAEN); } -// ***************************** SPI IRQs ***************************** -// can't go on the stack cause it's DMAed -uint8_t spi_tx_buf[0x44]; - -// SPI RX +// SPI MOSI DMA FINISHED void DMA2_Stream2_IRQ_Handler(void) { - int *resp_len = (int*)spi_tx_buf; - (void)memset(spi_tx_buf, 0xaa, 0x44); - *resp_len = spi_cb_rx(spi_buf, 0x14, spi_tx_buf+4); - #ifdef DEBUG_SPI - puts("SPI write: "); - puth(*resp_len); - puts("\n"); - #endif - spi_tx_dma(spi_tx_buf, *resp_len + 4); - - // ack + // Clear interrupt flag + ENTER_CRITICAL(); DMA2->LIFCR = DMA_LIFCR_CTCIF2; -} -// SPI TX -void DMA2_Stream3_IRQ_Handler(void) { - #ifdef DEBUG_SPI - puts("SPI handshake\n"); - #endif + spi_handle_rx(); - // reset handshake back to pull up - set_gpio_mode(GPIOB, 0, MODE_INPUT); - set_gpio_pullup(GPIOB, 0, PULL_UP); + EXIT_CRITICAL(); +} - // ack +// SPI MISO DMA FINISHED +void DMA2_Stream3_IRQ_Handler(void) { + // Clear interrupt flag DMA2->LIFCR = DMA_LIFCR_CTCIF3; -} -void EXTI4_IRQ_Handler(void) { - volatile unsigned int pr = EXTI->PR & (1U << 4); - #ifdef DEBUG_SPI - puts("exti4\n"); - #endif - // SPI CS falling - if ((pr & (1U << 4)) != 0U) { - spi_total_count = 0; - spi_rx_dma(spi_buf, 0x14); + // Wait until the transaction is actually finished and clear the DR + // Timeout to prevent hang when the master clock stops. + bool timed_out = false; + uint32_t start_time = microsecond_timer_get(); + while (!(SPI1->SR & SPI_SR_TXE)) { + if (get_ts_elapsed(microsecond_timer_get(), start_time) > SPI_TIMEOUT_US) { + timed_out = true; + break; + } } - EXTI->PR = pr; + volatile uint8_t dat = SPI1->DR; + (void)dat; + SPI1->DR = 0U; + + spi_handle_tx(timed_out); } // ***************************** SPI init ***************************** -void spi_init(void) { - // Max SPI clock the ESP can produce is 80MHz. At buffer size of 256 bytes, that's a max of about 40k buffers per second - REGISTER_INTERRUPT(DMA2_Stream2_IRQn, DMA2_Stream2_IRQ_Handler, 50000U, FAULT_INTERRUPT_RATE_SPI_DMA) - REGISTER_INTERRUPT(DMA2_Stream3_IRQn, DMA2_Stream3_IRQ_Handler, 50000U, FAULT_INTERRUPT_RATE_SPI_DMA) - REGISTER_INTERRUPT(EXTI4_IRQn, EXTI4_IRQ_Handler, 50000U, FAULT_INTERRUPT_RATE_SPI_CS) // TODO: Figure out if this is a reasonable limit +void llspi_init(void) { + // We expect less than 50 transactions (including control messages and CAN buffers) at the 100Hz boardd interval. Can be raised if needed. + REGISTER_INTERRUPT(DMA2_Stream2_IRQn, DMA2_Stream2_IRQ_Handler, 5000U, FAULT_INTERRUPT_RATE_SPI_DMA) + REGISTER_INTERRUPT(DMA2_Stream3_IRQn, DMA2_Stream3_IRQ_Handler, 5000U, FAULT_INTERRUPT_RATE_SPI_DMA) - //puts("SPI init\n"); - register_set(&(SPI1->CR1), SPI_CR1_SPE, 0xFFFFU); + // Setup MOSI DMA + register_set(&(DMA2_Stream2->CR), (DMA_SxCR_CHSEL_1 | DMA_SxCR_CHSEL_0 | DMA_SxCR_MINC | DMA_SxCR_TCIE), 0x1E077EFEU); + register_set(&(DMA2_Stream2->PAR), (uint32_t)&(SPI1->DR), 0xFFFFFFFFU); + + // Setup MISO DMA + register_set(&(DMA2_Stream3->CR), (DMA_SxCR_CHSEL_1 | DMA_SxCR_CHSEL_0 | DMA_SxCR_MINC | DMA_SxCR_DIR_0 | DMA_SxCR_TCIE), 0x1E077EFEU); + register_set(&(DMA2_Stream3->PAR), (uint32_t)&(SPI1->DR), 0xFFFFFFFFU); - // enable SPI interrupts - //SPI1->CR2 = SPI_CR2_RXNEIE | SPI_CR2_ERRIE | SPI_CR2_TXEIE; - register_set(&(SPI1->CR2), SPI_CR2_RXNEIE, 0xF7U); + // Enable SPI and the error interrupts + // TODO: verify clock phase and polarity + register_set(&(SPI1->CR1), SPI_CR1_SPE, 0xFFFFU); + register_set(&(SPI1->CR2), 0U, 0xF7U); NVIC_EnableIRQ(DMA2_Stream2_IRQn); NVIC_EnableIRQ(DMA2_Stream3_IRQn); - //NVIC_EnableIRQ(SPI1_IRQn); - - // reset handshake back to pull up - set_gpio_mode(GPIOB, 0, MODE_INPUT); - set_gpio_pullup(GPIOB, 0, PULL_UP); - - // setup interrupt on falling edge of SPI enable (on PA4) - register_set(&(SYSCFG->EXTICR[2]), SYSCFG_EXTICR2_EXTI4_PA, 0xFFFFU); - register_set_bits(&(EXTI->IMR), (1U << 4)); - register_set_bits(&(EXTI->FTSR), (1U << 4)); - NVIC_EnableIRQ(EXTI4_IRQn); } diff --git a/board/stm32fx/lluart.h b/board/stm32fx/lluart.h index 69959a76f5..cc5b731349 100644 --- a/board/stm32fx/lluart.h +++ b/board/stm32fx/lluart.h @@ -93,7 +93,7 @@ void uart_interrupt_handler(uart_ring *q) { uint32_t err = (status & USART_SR_ORE) | (status & USART_SR_NE) | (status & USART_SR_FE) | (status & USART_SR_PE); if(err != 0U){ #ifdef DEBUG_UART - puts("Encountered UART error: "); puth(err); puts("\n"); + print("Encountered UART error: "); puth(err); print("\n"); #endif UART_READ_DR(q->uart) } @@ -109,7 +109,7 @@ void uart_interrupt_handler(uart_ring *q) { dma_pointer_handler(&uart_ring_gps, DMA2_Stream5->NDTR); } else { #ifdef DEBUG_UART - puts("No IDLE dma_pointer_handler implemented for this UART."); + print("No IDLE dma_pointer_handler implemented for this UART."); #endif } } @@ -128,7 +128,7 @@ void DMA2_Stream5_IRQ_Handler(void) { // Handle errors if((DMA2->HISR & DMA_HISR_TEIF5) || (DMA2->HISR & DMA_HISR_DMEIF5) || (DMA2->HISR & DMA_HISR_FEIF5)){ #ifdef DEBUG_UART - puts("Encountered UART DMA error. Clearing and restarting DMA...\n"); + print("Encountered UART DMA error. Clearing and restarting DMA...\n"); #endif // Clear flags @@ -173,7 +173,7 @@ void dma_rx_init(uart_ring *q) { // Enable interrupt NVIC_EnableIRQ(DMA2_Stream5_IRQn); } else { - puts("Tried to initialize RX DMA for an unsupported UART\n"); + print("Tried to initialize RX DMA for an unsupported UART\n"); } } @@ -192,44 +192,46 @@ void uart_set_baud(USART_TypeDef *u, unsigned int baud) { } void uart_init(uart_ring *q, int baud) { - // Register interrupts (max data rate: 115200 baud) - if(q->uart == USART1){ - REGISTER_INTERRUPT(USART1_IRQn, USART1_IRQ_Handler, 150000U, FAULT_INTERRUPT_RATE_UART_1) - } else if (q->uart == USART2){ - REGISTER_INTERRUPT(USART2_IRQn, USART2_IRQ_Handler, 150000U, FAULT_INTERRUPT_RATE_UART_2) - } else if (q->uart == USART3){ - REGISTER_INTERRUPT(USART3_IRQn, USART3_IRQ_Handler, 150000U, FAULT_INTERRUPT_RATE_UART_3) - } else if (q->uart == UART5){ - REGISTER_INTERRUPT(UART5_IRQn, UART5_IRQ_Handler, 150000U, FAULT_INTERRUPT_RATE_UART_5) - } else { - // UART not used. Skip registering interrupts - } - if(q->dma_rx){ - REGISTER_INTERRUPT(DMA2_Stream5_IRQn, DMA2_Stream5_IRQ_Handler, 100U, FAULT_INTERRUPT_RATE_UART_DMA) // Called twice per buffer - } + if(q->uart != NULL){ + // Register interrupts (max data rate: 115200 baud) + if(q->uart == USART1){ + REGISTER_INTERRUPT(USART1_IRQn, USART1_IRQ_Handler, 150000U, FAULT_INTERRUPT_RATE_UART_1) + } else if (q->uart == USART2){ + REGISTER_INTERRUPT(USART2_IRQn, USART2_IRQ_Handler, 150000U, FAULT_INTERRUPT_RATE_UART_2) + } else if (q->uart == USART3){ + REGISTER_INTERRUPT(USART3_IRQn, USART3_IRQ_Handler, 150000U, FAULT_INTERRUPT_RATE_UART_3) + } else if (q->uart == UART5){ + REGISTER_INTERRUPT(UART5_IRQn, UART5_IRQ_Handler, 150000U, FAULT_INTERRUPT_RATE_UART_5) + } else { + // UART not used. Skip registering interrupts + } + if(q->dma_rx){ + REGISTER_INTERRUPT(DMA2_Stream5_IRQn, DMA2_Stream5_IRQ_Handler, 100U, FAULT_INTERRUPT_RATE_UART_DMA) // Called twice per buffer + } - // Set baud and enable peripheral with TX and RX mode - uart_set_baud(q->uart, baud); - q->uart->CR1 = USART_CR1_UE | USART_CR1_TE | USART_CR1_RE; - if ((q->uart == USART2) || (q->uart == USART3) || (q->uart == UART5)) { - q->uart->CR1 |= USART_CR1_RXNEIE; - } + // Set baud and enable peripheral with TX and RX mode + uart_set_baud(q->uart, baud); + q->uart->CR1 = USART_CR1_UE | USART_CR1_TE | USART_CR1_RE; + if ((q->uart == USART2) || (q->uart == USART3) || (q->uart == UART5)) { + q->uart->CR1 |= USART_CR1_RXNEIE; + } - // Enable UART interrupts - if(q->uart == USART1){ - NVIC_EnableIRQ(USART1_IRQn); - } else if (q->uart == USART2){ - NVIC_EnableIRQ(USART2_IRQn); - } else if (q->uart == USART3){ - NVIC_EnableIRQ(USART3_IRQn); - } else if (q->uart == UART5){ - NVIC_EnableIRQ(UART5_IRQn); - } else { - // UART not used. Skip enabling interrupts - } + // Enable UART interrupts + if(q->uart == USART1){ + NVIC_EnableIRQ(USART1_IRQn); + } else if (q->uart == USART2){ + NVIC_EnableIRQ(USART2_IRQn); + } else if (q->uart == USART3){ + NVIC_EnableIRQ(USART3_IRQn); + } else if (q->uart == UART5){ + NVIC_EnableIRQ(UART5_IRQn); + } else { + // UART not used. Skip enabling interrupts + } - // Initialise RX DMA if used - if(q->dma_rx){ - dma_rx_init(q); + // Initialise RX DMA if used + if(q->dma_rx){ + dma_rx_init(q); + } } } diff --git a/board/stm32fx/llusb.h b/board/stm32fx/llusb.h index df4747312e..ab34672aa4 100644 --- a/board/stm32fx/llusb.h +++ b/board/stm32fx/llusb.h @@ -33,14 +33,14 @@ void usb_init(void) { // full speed PHY, do reset and remove power down /*puth(USBx->GRSTCTL); - puts(" resetting PHY\n");*/ + print(" resetting PHY\n");*/ while ((USBx->GRSTCTL & USB_OTG_GRSTCTL_AHBIDL) == 0); - //puts("AHB idle\n"); + //print("AHB idle\n"); // reset PHY here USBx->GRSTCTL |= USB_OTG_GRSTCTL_CSRST; while ((USBx->GRSTCTL & USB_OTG_GRSTCTL_CSRST) == USB_OTG_GRSTCTL_CSRST); - //puts("reset done\n"); + //print("reset done\n"); // internal PHY, force device mode USBx->GUSBCFG = USB_OTG_GUSBCFG_PHYSEL | USB_OTG_GUSBCFG_FDMOD; diff --git a/board/stm32fx/peripherals.h b/board/stm32fx/peripherals.h index 40ef6cb573..3eeebf0eb6 100644 --- a/board/stm32fx/peripherals.h +++ b/board/stm32fx/peripherals.h @@ -5,6 +5,15 @@ void gpio_usb_init(void) { GPIOA->OSPEEDR = GPIO_OSPEEDER_OSPEEDR11 | GPIO_OSPEEDER_OSPEEDR12; } +void gpio_spi_init(void) { + // A4-A7: SPI + set_gpio_alternate(GPIOA, 4, GPIO_AF5_SPI1); + set_gpio_alternate(GPIOA, 5, GPIO_AF5_SPI1); + set_gpio_alternate(GPIOA, 6, GPIO_AF5_SPI1); + set_gpio_alternate(GPIOA, 7, GPIO_AF5_SPI1); + register_set_bits(&(GPIOA->OSPEEDR), GPIO_OSPEEDER_OSPEEDR4 | GPIO_OSPEEDER_OSPEEDR5 | GPIO_OSPEEDER_OSPEEDR6 | GPIO_OSPEEDER_OSPEEDR7); +} + void gpio_usart2_init(void) { // A2,A3: USART 2 for debugging set_gpio_alternate(GPIOA, 2, GPIO_AF7_USART2); diff --git a/board/stm32fx/stm32fx_flash.ld b/board/stm32fx/stm32f2_flash.ld similarity index 100% rename from board/stm32fx/stm32fx_flash.ld rename to board/stm32fx/stm32f2_flash.ld diff --git a/board/stm32fx/stm32f4_flash.ld b/board/stm32fx/stm32f4_flash.ld new file mode 100644 index 0000000000..a601c1f60a --- /dev/null +++ b/board/stm32fx/stm32f4_flash.ld @@ -0,0 +1,166 @@ +/* +***************************************************************************** +** +** File : stm32f4_flash.ld +** +** Abstract : Linker script for STM32F407VG Device with +** 1024KByte FLASH, 192KByte RAM +** +** Set heap size, stack size and stack location according +** to application requirements. +** +** Set memory bank area and size if external memory is used. +** +** Target : STMicroelectronics STM32 +** +** Environment : Atollic TrueSTUDIO(R) +** +** Distribution: The file is distributed "as is," without any warranty +** of any kind. +** +** (c)Copyright Atollic AB. +** You may use this file as-is or modify it according to the needs of your +** project. Distribution of this file (unmodified or modified) is not +** permitted. Atollic AB permit registered Atollic TrueSTUDIO(R) users the +** rights to distribute the assembled, compiled & linked contents of this +** file as part of an application binary file, provided that it is built +** using the Atollic TrueSTUDIO(R) toolchain. +** +***************************************************************************** +*/ + +/* Entry Point */ +ENTRY(Reset_Handler) + +/* Highest address of the user mode stack */ +enter_bootloader_mode = 0x2001FFFC; +_estack = 0x2001FFFC; /* end of 128K RAM on AHB bus*/ +_app_start = 0x08004000; /* Reserve Sector 0(16K) for bootloader */ + +/* Generate a link error if heap and stack don't fit into RAM */ +_Min_Heap_Size = 0; /* required amount of heap */ +_Min_Stack_Size = 0x400; /* required amount of stack */ + +/* Specify the memory areas */ +MEMORY +{ + FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K + RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 256K + RAM2 (xrw) : ORIGIN = 0x20040000, LENGTH = 64K + MEMORY_B1 (rx) : ORIGIN = 0x60000000, LENGTH = 0K +} + +/* Define output sections */ +SECTIONS +{ + /* The startup code goes first into FLASH */ + .isr_vector : + { + . = ALIGN(4); + KEEP(*(.isr_vector)) /* Startup code */ + . = ALIGN(4); + } >FLASH + + /* The program code and other data goes into FLASH */ + .text : + { + . = ALIGN(4); + *(.text) /* .text sections (code) */ + *(.text*) /* .text* sections (code) */ + *(.rodata) /* .rodata sections (constants, strings, etc.) */ + *(.rodata*) /* .rodata* sections (constants, strings, etc.) */ + *(.glue_7) /* glue arm to thumb code */ + *(.glue_7t) /* glue thumb to arm code */ + *(.eh_frame) + + KEEP (*(.init)) + KEEP (*(.fini)) + + . = ALIGN(4); + _etext = .; /* define a global symbols at end of code */ + _exit = .; + } >FLASH + + + .ARM.extab : { *(.ARM.extab* .gnu.linkonce.armextab.*) } >FLASH + .ARM : { + __exidx_start = .; + *(.ARM.exidx*) + __exidx_end = .; + } >FLASH + + .preinit_array : + { + PROVIDE_HIDDEN (__preinit_array_start = .); + KEEP (*(.preinit_array*)) + PROVIDE_HIDDEN (__preinit_array_end = .); + } >FLASH + .init_array : + { + PROVIDE_HIDDEN (__init_array_start = .); + KEEP (*(SORT(.init_array.*))) + KEEP (*(.init_array*)) + PROVIDE_HIDDEN (__init_array_end = .); + } >FLASH + .fini_array : + { + PROVIDE_HIDDEN (__fini_array_start = .); + KEEP (*(.fini_array*)) + KEEP (*(SORT(.fini_array.*))) + PROVIDE_HIDDEN (__fini_array_end = .); + } >FLASH + + /* used by the startup to initialize data */ + _sidata = .; + + /* Initialized data sections goes into RAM, load LMA copy after code */ + .data : AT ( _sidata ) + { + . = ALIGN(4); + _sdata = .; /* create a global symbol at data start */ + *(.data) /* .data sections */ + *(.data*) /* .data* sections */ + + . = ALIGN(4); + _edata = .; /* define a global symbol at data end */ + } >RAM + + /* Uninitialized data section */ + . = ALIGN(4); + .bss : + { + /* This is used by the startup in order to initialize the .bss secion */ + _sbss = .; /* define a global symbol at bss start */ + __bss_start__ = _sbss; + *(.bss) + *(.bss*) + *(COMMON) + + . = ALIGN(4); + _ebss = .; /* define a global symbol at bss end */ + __bss_end__ = _ebss; + } >RAM + + /* User_heap_stack section, used to check that there is enough RAM left */ + ._user_heap_stack : + { + . = ALIGN(4); + PROVIDE ( end = . ); + PROVIDE ( _end = . ); + . = . + _Min_Heap_Size; + . = . + _Min_Stack_Size; + . = ALIGN(4); + } >RAM + + /* MEMORY_bank1 section, code must be located here explicitly */ + /* Example: extern int foo(void) __attribute__ ((section (".mb1text"))); */ + .memory_b1_text : + { + *(.mb1text) /* .mb1text sections (code) */ + *(.mb1text*) /* .mb1text* sections (code) */ + *(.mb1rodata) /* read-only data (constants) */ + *(.mb1rodata*) + } >MEMORY_B1 + + .ARM.attributes 0 : { *(.ARM.attributes) } +} diff --git a/board/stm32fx/stm32fx_config.h b/board/stm32fx/stm32fx_config.h index cbdff1dae4..073cb07774 100644 --- a/board/stm32fx/stm32fx_config.h +++ b/board/stm32fx/stm32fx_config.h @@ -10,10 +10,11 @@ // from the linker script #define APP_START_ADDRESS 0x8004000U -#define CORE_FREQ 96U // in Mhz -//APB1 - 48Mhz, APB2 - 96Mhz -#define APB1_FREQ CORE_FREQ/2U -#define APB2_FREQ CORE_FREQ/1U +#define CORE_FREQ 96U // in MHz +#define APB1_FREQ (CORE_FREQ/2U) +#define APB1_TIMER_FREQ (APB1_FREQ*2U) // APB1 is multiplied by 2 for the timer peripherals +#define APB2_FREQ (CORE_FREQ/2U) +#define APB2_TIMER_FREQ (APB2_FREQ*2U) // APB2 is multiplied by 2 for the timer peripherals #define BOOTLOADER_ADDRESS 0x1FFF0004U @@ -66,6 +67,11 @@ #include "stm32fx/board.h" #include "stm32fx/clock.h" +#if defined(PANDA) || defined(BOOTSTUB) + #include "drivers/spi.h" + #include "stm32fx/llspi.h" +#endif + #if !defined(BOOTSTUB) && (defined(PANDA) || defined(PEDAL_USB)) #include "drivers/uart.h" #include "stm32fx/lluart.h" diff --git a/board/stm32h7/board.h b/board/stm32h7/board.h index 108dbfd8d7..086db86b3c 100644 --- a/board/stm32h7/board.h +++ b/board/stm32h7/board.h @@ -9,33 +9,37 @@ #include "drivers/fan.h" #include "stm32h7/llfan.h" #include "stm32h7/llrtc.h" +#include "stm32h7/lldac.h" +#include "drivers/fake_siren.h" #include "drivers/rtc.h" +#include "drivers/clock_source.h" #include "boards/red.h" +#include "boards/red_chiplet.h" #include "boards/red_v2.h" +#include "boards/tres.h" -uint8_t board_id(void) { +uint8_t get_board_id(void) { return detect_with_pull(GPIOF, 7, PULL_UP) | - (detect_with_pull(GPIOF, 8, PULL_UP) << 1U) | - (detect_with_pull(GPIOF, 9, PULL_UP) << 2U) | - (detect_with_pull(GPIOF, 10, PULL_UP) << 3U); + (detect_with_pull(GPIOF, 8, PULL_UP) << 1U) | + (detect_with_pull(GPIOF, 9, PULL_UP) << 2U) | + (detect_with_pull(GPIOF, 10, PULL_UP) << 3U); } void detect_board_type(void) { - if(board_id() == 0U){ + const uint8_t board_id = get_board_id(); + + if (board_id == 0U) { hw_type = HW_TYPE_RED_PANDA; current_board = &board_red; - } else if(board_id() == 1U){ + } else if (board_id == 1U) { hw_type = HW_TYPE_RED_PANDA_V2; current_board = &board_red_v2; + } else if (board_id == 2U) { + hw_type = HW_TYPE_TRES; + current_board = &board_tres; } else { hw_type = HW_TYPE_UNKNOWN; - puts("Hardware type is UNKNOWN!\n"); + print("Hardware type is UNKNOWN!\n"); } } - -bool has_external_debug_serial = 0; -void detect_external_debug_serial(void) { - // detect if external serial debugging is present - has_external_debug_serial = detect_with_pull(GPIOA, 3, PULL_DOWN) || detect_with_pull(GPIOE, 7, PULL_DOWN); -} diff --git a/board/stm32h7/clock.h b/board/stm32h7/clock.h index 10febe15a6..b1846da261 100644 --- a/board/stm32h7/clock.h +++ b/board/stm32h7/clock.h @@ -1,3 +1,22 @@ +/* +HSE: 25MHz +PLL1Q: 80MHz (for FDCAN) +HSI48 enabled (for USB) +CPU: 240MHz +CPU Systick: 240MHz +AXI: 120MHz +HCLK3: 60MHz +APB3 per: 60MHz +AHB1,2 per: 120MHz +APB1 per: 60MHz +APB1 tim: 120MHz +APB2 per: 60MHz +APB2 tim: 120MHz +AHB4 per: 120MHz +APB4 per: 60MHz +PCLK1: 60MHz (for USART2,3,4,5,7,8) +*/ + void clock_init(void) { //Set power mode to direct SMPS power supply(depends on the board layout) register_set(&(PWR->CR3), PWR_CR3_SMPSEN, 0xFU); // powered only by SMPS @@ -42,7 +61,7 @@ void clock_init(void) { register_set_bits(&(RCC->AHB4ENR), RCC_APB4ENR_SYSCFGEN); //////////////END OTHER CLOCKS//////////////////// - // Configure clock source for USB (HSI at 48Mhz) + // Configure clock source for USB (HSI48) register_set(&(RCC->D2CCIP2R), RCC_D2CCIP2R_USBSEL_1 | RCC_D2CCIP2R_USBSEL_0, RCC_D2CCIP2R_USBSEL); // Configure clock source for FDCAN (PLL1Q at 80Mhz) register_set(&(RCC->D2CCIP1R), RCC_D2CCIP1R_FDCANSEL_0, RCC_D2CCIP1R_FDCANSEL); @@ -52,4 +71,8 @@ void clock_init(void) { register_set_bits(&(RCC->CR), RCC_CR_CSSHSEON); //Enable Vdd33usb supply level detector register_set_bits(&(PWR->CR3), PWR_CR3_USB33DEN); + + // Enable CPU access to SRAM1 and SRAM2 (in domain D2) + register_set_bits(&(RCC->AHB2ENR), RCC_AHB2ENR_SRAM1EN); + register_set_bits(&(RCC->AHB2ENR), RCC_AHB2ENR_SRAM2EN); } diff --git a/board/stm32h7/inc/stm32h725xx.h b/board/stm32h7/inc/stm32h725xx.h index 0631daac0d..0e0d4a9d62 100644 --- a/board/stm32h7/inc/stm32h725xx.h +++ b/board/stm32h7/inc/stm32h725xx.h @@ -24478,7 +24478,7 @@ typedef struct ((INSTANCE) == TIM23) || \ ((INSTANCE) == TIM24)) -/****************** TIM Instances : supporting internal trigger inputs(ITRX) *******/ +/****************** TIM Instances : supporting internal trigger inputstr(ITRX) *******/ #define IS_TIM_CLOCKSOURCE_ITRX_INSTANCE(INSTANCE)\ (((INSTANCE) == TIM1) || \ ((INSTANCE) == TIM2) || \ diff --git a/board/stm32h7/inc/stm32h735xx.h b/board/stm32h7/inc/stm32h735xx.h index 0631daac0d..0e0d4a9d62 100644 --- a/board/stm32h7/inc/stm32h735xx.h +++ b/board/stm32h7/inc/stm32h735xx.h @@ -24478,7 +24478,7 @@ typedef struct ((INSTANCE) == TIM23) || \ ((INSTANCE) == TIM24)) -/****************** TIM Instances : supporting internal trigger inputs(ITRX) *******/ +/****************** TIM Instances : supporting internal trigger inputstr(ITRX) *******/ #define IS_TIM_CLOCKSOURCE_ITRX_INSTANCE(INSTANCE)\ (((INSTANCE) == TIM1) || \ ((INSTANCE) == TIM2) || \ diff --git a/board/stm32h7/interrupt_handlers.h b/board/stm32h7/interrupt_handlers.h index 2b0a2b428a..0811ffe523 100644 --- a/board/stm32h7/interrupt_handlers.h +++ b/board/stm32h7/interrupt_handlers.h @@ -72,3 +72,4 @@ void OTG_HS_EP1_OUT_IRQHandler(void) {handle_interrupt(OTG_HS_EP1_OUT_IRQn);} void OTG_HS_EP1_IN_IRQHandler(void) {handle_interrupt(OTG_HS_EP1_IN_IRQn);} void OTG_HS_WKUP_IRQHandler(void) {handle_interrupt(OTG_HS_WKUP_IRQn);} void OTG_HS_IRQHandler(void) {handle_interrupt(OTG_HS_IRQn);} +void UART7_IRQHandler(void) {handle_interrupt(UART7_IRQn);} diff --git a/board/stm32h7/lladc.h b/board/stm32h7/lladc.h index 795ca3533f..3abfef96b7 100644 --- a/board/stm32h7/lladc.h +++ b/board/stm32h7/lladc.h @@ -21,7 +21,7 @@ uint32_t adc_get(unsigned int channel) { ADC1->SQR1 &= ~(ADC_SQR1_L); ADC1->SQR1 = (channel << 6U); - + ADC1->SMPR1 = (0x7U << (channel * 3U) ); ADC1->PCSEL_RES0 = (0x1U << channel); @@ -36,13 +36,14 @@ uint32_t adc_get(unsigned int channel) { return res; } -uint32_t adc_get_voltage(void) { +uint32_t adc_get_voltage(uint16_t scale) { // REVC has a 10, 1 (1/11) voltage divider // Here is the calculation for the scale (s) // ADCV = VIN_S * (1/11) * (65535/3.3) // RETVAL = ADCV * s = VIN_S*1000 // s = 1000/((65535/3.3)*(1/11)) = 0.553902494 + // s = 1000/((65535/1.8)*(1/11)) = 0.3021 // Avoid needing floating point math, so output in mV - return (adc_get(ADCCHAN_VOLTAGE) * 5539U) / 10000U; + return (adc_get(ADCCHAN_VOLTAGE) * scale) / 10000U; } diff --git a/board/stm32h7/lldac.h b/board/stm32h7/lldac.h new file mode 100644 index 0000000000..77e333e5c8 --- /dev/null +++ b/board/stm32h7/lldac.h @@ -0,0 +1,42 @@ +void dac_init(DAC_TypeDef *dac, uint8_t channel, bool dma) { + register_set(&dac->CR, 0U, 0xFFFFU); + register_set(&dac->MCR, 0U, 0xFFFFU); + + switch(channel) { + case 1: + if (dma) { + register_set_bits(&dac->CR, DAC_CR_DMAEN1); + // register_set(&DAC->CR, (6U << DAC_CR_TSEL1_Pos), DAC_CR_TSEL1); + register_set_bits(&dac->CR, DAC_CR_TEN1); + } else { + register_clear_bits(&dac->CR, DAC_CR_DMAEN1); + } + register_set_bits(&dac->CR, DAC_CR_EN1); + break; + case 2: + if (dma) { + register_set_bits(&dac->CR, DAC_CR_DMAEN2); + } else { + register_clear_bits(&dac->CR, DAC_CR_DMAEN2); + } + register_set_bits(&dac->CR, DAC_CR_EN2); + break; + default: + break; + } +} + +// Set channel 1 value, in mV +void dac_set(DAC_TypeDef *dac, uint8_t channel, uint32_t value) { + uint32_t raw_val = MAX(MIN(value * (1U << 8U) / 3300U, (1U << 8U)), 0U); + switch(channel) { + case 1: + register_set(&dac->DHR8R1, raw_val, 0xFFU); + break; + case 2: + register_set(&dac->DHR8R2, raw_val, 0xFFU); + break; + default: + break; + } +} diff --git a/board/stm32h7/llfan.h b/board/stm32h7/llfan.h index fc529dcbe2..bbcda63e09 100644 --- a/board/stm32h7/llfan.h +++ b/board/stm32h7/llfan.h @@ -1,2 +1,25 @@ -void EXTI2_IRQ_Handler(void) { } -void fan_init(void){ } +// TACH interrupt handler +void EXTI2_IRQ_Handler(void) { + volatile unsigned int pr = EXTI->PR1 & (1U << 2); + if ((pr & (1U << 2)) != 0U) { + fan_state.tach_counter++; + } + EXTI->PR1 = (1U << 2); +} + +void llfan_init(void) { + fan_reset_cooldown(); + + // 5000RPM * 4 tach edges / 60 seconds + REGISTER_INTERRUPT(EXTI2_IRQn, EXTI2_IRQ_Handler, 700U, FAULT_INTERRUPT_RATE_TACH) + + // Init PWM speed control + pwm_init(TIM3, 3); + + // Init TACH interrupt + register_set(&(SYSCFG->EXTICR[0]), SYSCFG_EXTICR1_EXTI2_PD, 0xF00U); + register_set_bits(&(EXTI->IMR1), (1U << 2)); + register_set_bits(&(EXTI->RTSR1), (1U << 2)); + register_set_bits(&(EXTI->FTSR1), (1U << 2)); + NVIC_EnableIRQ(EXTI2_IRQn); +} diff --git a/board/stm32h7/llfdcan.h b/board/stm32h7/llfdcan.h index d21f415ce0..b2bb885b43 100644 --- a/board/stm32h7/llfdcan.h +++ b/board/stm32h7/llfdcan.h @@ -37,7 +37,7 @@ #define CAN_NUM_FROM_CANIF(CAN_DEV) (((CAN_DEV)==FDCAN1) ? 0UL : (((CAN_DEV) == FDCAN2) ? 1UL : 2UL)) -void puts(const char *a); +void print(const char *a); // kbps multiplied by 10 const uint32_t speeds[] = {100U, 200U, 500U, 1000U, 1250U, 2500U, 5000U, 10000U}; @@ -148,10 +148,10 @@ bool llcan_set_speed(FDCAN_GlobalTypeDef *CANx, uint32_t speed, uint32_t data_sp } ret = fdcan_exit_init(CANx); if (!ret) { - puts(CAN_NAME_FROM_CANIF(CANx)); puts(" set_speed timed out! (2)\n"); + print(CAN_NAME_FROM_CANIF(CANx)); print(" set_speed timed out! (2)\n"); } } else { - puts(CAN_NAME_FROM_CANIF(CANx)); puts(" set_speed timed out! (1)\n"); + print(CAN_NAME_FROM_CANIF(CANx)); print(" set_speed timed out! (1)\n"); } return ret; } @@ -219,7 +219,7 @@ bool llcan_init(FDCAN_GlobalTypeDef *CANx) { ret = fdcan_exit_init(CANx); if(!ret) { - puts(CAN_NAME_FROM_CANIF(CANx)); puts(" llcan_init timed out (2)!\n"); + print(CAN_NAME_FROM_CANIF(CANx)); print(" llcan_init timed out (2)!\n"); } if (CANx == FDCAN1) { @@ -232,11 +232,11 @@ bool llcan_init(FDCAN_GlobalTypeDef *CANx) { NVIC_EnableIRQ(FDCAN3_IT0_IRQn); NVIC_EnableIRQ(FDCAN3_IT1_IRQn); } else { - puts("Invalid CAN: initialization failed\n"); + print("Invalid CAN: initialization failed\n"); } } else { - puts(CAN_NAME_FROM_CANIF(CANx)); puts(" llcan_init timed out (1)!\n"); + print(CAN_NAME_FROM_CANIF(CANx)); print(" llcan_init timed out (1)!\n"); } return ret; } diff --git a/board/stm32h7/lli2c.h b/board/stm32h7/lli2c.h new file mode 100644 index 0000000000..74e8ac8308 --- /dev/null +++ b/board/stm32h7/lli2c.h @@ -0,0 +1,153 @@ + +// TODO: this driver relies heavily on polling, +// if we want it to be more async, we should use interrupts + +#define I2C_TIMEOUT_US 100000U + +// cppcheck-suppress misra-c2012-2.7; not sure why it triggers here? +bool i2c_status_wait(volatile uint32_t *reg, uint32_t mask, uint32_t val) { + uint32_t start_time = microsecond_timer_get(); + while(((*reg & mask) != val) && (get_ts_elapsed(microsecond_timer_get(), start_time) < I2C_TIMEOUT_US)); + return ((*reg & mask) == val); +} + +bool i2c_write_reg(I2C_TypeDef *I2C, uint8_t addr, uint8_t reg, uint8_t value) { + // Setup transfer and send START + addr + bool ret = false; + for(uint32_t i=0U; i<10U; i++) { + register_clear_bits(&I2C->CR2, I2C_CR2_ADD10); + I2C->CR2 = ((addr << 1U) & I2C_CR2_SADD_Msk); + register_clear_bits(&I2C->CR2, I2C_CR2_RD_WRN); + register_set_bits(&I2C->CR2, I2C_CR2_AUTOEND); + I2C->CR2 |= (2 << I2C_CR2_NBYTES_Pos); + + I2C->CR2 |= I2C_CR2_START; + if(!i2c_status_wait(&I2C->CR2, I2C_CR2_START, 0U)) { + continue; + } + + // check if we lost arbitration + if ((I2C->ISR & I2C_ISR_ARLO) != 0U) { + register_set_bits(&I2C->ICR, I2C_ICR_ARLOCF); + } else { + ret = true; + break; + } + } + + if (!ret) { + goto end; + } + + // Send data + ret = i2c_status_wait(&I2C->ISR, I2C_ISR_TXIS, I2C_ISR_TXIS); + if(!ret) { + goto end; + } + I2C->TXDR = reg; + + ret = i2c_status_wait(&I2C->ISR, I2C_ISR_TXIS, I2C_ISR_TXIS); + if(!ret) { + goto end; + } + I2C->TXDR = value; + +end: + return ret; +} + +bool i2c_read_reg(I2C_TypeDef *I2C, uint8_t addr, uint8_t reg, uint8_t *value) { + // Setup transfer and send START + addr + bool ret = false; + for(uint32_t i=0U; i<10U; i++) { + register_clear_bits(&I2C->CR2, I2C_CR2_ADD10); + I2C->CR2 = ((addr << 1U) & I2C_CR2_SADD_Msk); + register_clear_bits(&I2C->CR2, I2C_CR2_RD_WRN); + register_clear_bits(&I2C->CR2, I2C_CR2_AUTOEND); + I2C->CR2 |= (1 << I2C_CR2_NBYTES_Pos); + + I2C->CR2 |= I2C_CR2_START; + if(!i2c_status_wait(&I2C->CR2, I2C_CR2_START, 0U)) { + continue; + } + + // check if we lost arbitration + if ((I2C->ISR & I2C_ISR_ARLO) != 0U) { + register_set_bits(&I2C->ICR, I2C_ICR_ARLOCF); + } else { + ret = true; + break; + } + } + + if (!ret) { + goto end; + } + + // Send data + ret = i2c_status_wait(&I2C->ISR, I2C_ISR_TXIS, I2C_ISR_TXIS); + if(!ret) { + goto end; + } + I2C->TXDR = reg; + + // Restart + I2C->CR2 = (((addr << 1) | 0x1U) & I2C_CR2_SADD_Msk) | (1U << I2C_CR2_NBYTES_Pos) | I2C_CR2_RD_WRN | I2C_CR2_START; + ret = i2c_status_wait(&I2C->CR2, I2C_CR2_START, 0U); + if(!ret) { + goto end; + } + + // check if we lost arbitration + if ((I2C->ISR & I2C_ISR_ARLO) != 0U) { + register_set_bits(&I2C->ICR, I2C_ICR_ARLOCF); + ret = false; + goto end; + } + + // Read data + ret = i2c_status_wait(&I2C->ISR, I2C_ISR_RXNE, I2C_ISR_RXNE); + if(!ret) { + goto end; + } + *value = I2C->RXDR; + + // Stop + I2C->CR2 |= I2C_CR2_STOP; + +end: + return ret; +} + +bool i2c_set_reg_bits(I2C_TypeDef *I2C, uint8_t addr, uint8_t reg, uint8_t bits) { + uint8_t value; + bool ret = i2c_read_reg(I2C, addr, reg, &value); + if(ret) { + ret = i2c_write_reg(I2C, addr, reg, value | bits); + } + return ret; +} + +bool i2c_clear_reg_bits(I2C_TypeDef *I2C, uint8_t addr, uint8_t reg, uint8_t bits) { + uint8_t value; + bool ret = i2c_read_reg(I2C, addr, reg, &value); + if(ret) { + ret = i2c_write_reg(I2C, addr, reg, value & (uint8_t) (~bits)); + } + return ret; +} + +bool i2c_set_reg_mask(I2C_TypeDef *I2C, uint8_t addr, uint8_t reg, uint8_t value, uint8_t mask) { + uint8_t old_value; + bool ret = i2c_read_reg(I2C, addr, reg, &old_value); + if(ret) { + ret = i2c_write_reg(I2C, addr, reg, (old_value & (uint8_t) (~mask)) | (value & mask)); + } + return ret; +} + +void i2c_init(I2C_TypeDef *I2C) { + // 100kHz clock speed + I2C->TIMINGR = 0x107075B0; + I2C->CR1 = I2C_CR1_PE; +} \ No newline at end of file diff --git a/board/stm32h7/llrtc.h b/board/stm32h7/llrtc.h index d78a514b5f..03787d0db2 100644 --- a/board/stm32h7/llrtc.h +++ b/board/stm32h7/llrtc.h @@ -1,6 +1,3 @@ -#define RCC_BDCR_MASK_LSE (RCC_BDCR_RTCEN | RCC_BDCR_RTCSEL | RCC_BDCR_LSEDRV | RCC_BDCR_LSEBYP | RCC_BDCR_LSEON) -#define RCC_BDCR_MASK_LSI (RCC_BDCR_RTCEN | RCC_BDCR_RTCSEL) - void enable_bdomain_protection(void) { register_clear_bits(&(PWR->CR1), PWR_CR1_DBP); } @@ -9,6 +6,38 @@ void disable_bdomain_protection(void) { register_set_bits(&(PWR->CR1), PWR_CR1_DBP); } +void rtc_init(void){ + uint32_t bdcr_opts = RCC_BDCR_RTCEN; + uint32_t bdcr_mask = (RCC_BDCR_RTCEN | RCC_BDCR_RTCSEL); + if (current_board->has_rtc_battery) { + bdcr_opts |= (RCC_BDCR_LSEDRV_1 | RCC_BDCR_RTCSEL_0 | RCC_BDCR_LSEON); + bdcr_mask |= (RCC_BDCR_LSEDRV | RCC_BDCR_LSEBYP | RCC_BDCR_LSEON); + } else { + bdcr_opts |= RCC_BDCR_RTCSEL_1; + RCC->CSR |= RCC_CSR_LSION; + while((RCC->CSR & RCC_CSR_LSIRDY) == 0){} + } + + // Initialize RTC module and clock if not done already. + if((RCC->BDCR & bdcr_mask) != bdcr_opts){ + print("Initializing RTC\n"); + // Reset backup domain + register_set_bits(&(RCC->BDCR), RCC_BDCR_BDRST); + + // Disable write protection + disable_bdomain_protection(); + + // Clear backup domain reset + register_clear_bits(&(RCC->BDCR), RCC_BDCR_BDRST); + + // Set RTC options + register_set(&(RCC->BDCR), bdcr_opts, bdcr_mask); + + // Enable write protection + enable_bdomain_protection(); + } +} + void rtc_wakeup_init(void) { EXTI->IMR1 |= EXTI_IMR1_IM19; EXTI->RTSR1 |= EXTI_RTSR1_TR19; // rising edge diff --git a/board/stm32h7/llspi.h b/board/stm32h7/llspi.h new file mode 100644 index 0000000000..9e21659b83 --- /dev/null +++ b/board/stm32h7/llspi.h @@ -0,0 +1,98 @@ +// master -> panda DMA start +void llspi_mosi_dma(uint8_t *addr, int len) { + // disable DMA + SPI + register_clear_bits(&(SPI4->CFG1), SPI_CFG1_RXDMAEN); + DMA2_Stream2->CR &= ~DMA_SxCR_EN; + register_clear_bits(&(SPI4->CR1), SPI_CR1_SPE); + + // drain the bus + while ((SPI4->SR & SPI_SR_RXP) != 0U) { + volatile uint8_t dat = SPI4->RXDR; + (void)dat; + } + + // setup destination and length + register_set(&(DMA2_Stream2->M0AR), (uint32_t)addr, 0xFFFFFFFFU); + DMA2_Stream2->NDTR = len; + + // enable DMA + SPI + DMA2_Stream2->CR |= DMA_SxCR_EN; + register_set_bits(&(SPI4->CFG1), SPI_CFG1_RXDMAEN); + register_set_bits(&(SPI4->CR1), SPI_CR1_SPE); +} + +// panda -> master DMA start +void llspi_miso_dma(uint8_t *addr, int len) { + // disable DMA + DMA2_Stream3->CR &= ~DMA_SxCR_EN; + register_clear_bits(&(SPI4->CFG1), SPI_CFG1_TXDMAEN); + + // setup source and length + register_set(&(DMA2_Stream3->M0AR), (uint32_t)addr, 0xFFFFFFFFU); + DMA2_Stream3->NDTR = len; + + // clear under-run while we were reading + SPI4->IFCR |= SPI_IFCR_UDRC; + + // enable DMA + register_set_bits(&(SPI4->CFG1), SPI_CFG1_TXDMAEN); + DMA2_Stream3->CR |= DMA_SxCR_EN; +} + +// master -> panda DMA finished +void DMA2_Stream2_IRQ_Handler(void) { + // Clear interrupt flag + ENTER_CRITICAL(); + DMA2->LIFCR = DMA_LIFCR_CTCIF2; + + spi_handle_rx(); + + EXIT_CRITICAL(); +} + +// panda -> master DMA finished +void DMA2_Stream3_IRQ_Handler(void) { + // Clear interrupt flag + DMA2->LIFCR = DMA_LIFCR_CTCIF3; + + // Wait until the transaction is actually finished and clear the DR. + // Timeout to prevent hang when the master clock stops. + bool timed_out = false; + uint32_t start_time = microsecond_timer_get(); + while (!(SPI4->SR & SPI_SR_TXC)) { + if (get_ts_elapsed(microsecond_timer_get(), start_time) > SPI_TIMEOUT_US) { + timed_out = true; + break; + } + } + volatile uint8_t dat = SPI4->TXDR; + (void)dat; + + spi_handle_tx(timed_out); +} + + +void llspi_init(void) { + // We expect less than 50 transactions (including control messages and CAN buffers) at the 100Hz boardd interval. Can be raised if needed. + REGISTER_INTERRUPT(DMA2_Stream2_IRQn, DMA2_Stream2_IRQ_Handler, 5000U, FAULT_INTERRUPT_RATE_SPI_DMA) + REGISTER_INTERRUPT(DMA2_Stream3_IRQn, DMA2_Stream3_IRQ_Handler, 5000U, FAULT_INTERRUPT_RATE_SPI_DMA) + + // Setup MOSI DMA + register_set(&(DMAMUX1_Channel10->CCR), 83U, 0xFFFFFFFFU); + register_set(&(DMA2_Stream2->CR), (DMA_SxCR_MINC | DMA_SxCR_TCIE), 0x1E077EFEU); + register_set(&(DMA2_Stream2->PAR), (uint32_t)&(SPI4->RXDR), 0xFFFFFFFFU); + + // Setup MISO DMA, memory -> peripheral + register_set(&(DMAMUX1_Channel11->CCR), 84U, 0xFFFFFFFFU); + register_set(&(DMA2_Stream3->CR), (DMA_SxCR_MINC | DMA_SxCR_DIR_0 | DMA_SxCR_TCIE), 0x1E077EFEU); + register_set(&(DMA2_Stream3->PAR), (uint32_t)&(SPI4->TXDR), 0xFFFFFFFFU); + + // Enable SPI + register_set(&(SPI4->CFG1), (7U << SPI_CFG1_DSIZE_Pos), SPI_CFG1_DSIZE_Msk); + register_set(&(SPI4->UDRDR), 0xcd, 0xFFFFU); // set under-run value for debugging + register_set(&(SPI4->CR1), SPI_CR1_SPE, 0xFFFFU); + register_set(&(SPI4->CR2), 0, 0xFFFFU); + + NVIC_EnableIRQ(DMA2_Stream2_IRQn); + NVIC_EnableIRQ(DMA2_Stream3_IRQn); +} diff --git a/board/stm32h7/lluart.h b/board/stm32h7/lluart.h index aa44f7bb84..89e47606c8 100644 --- a/board/stm32h7/lluart.h +++ b/board/stm32h7/lluart.h @@ -1,5 +1,130 @@ -void uart_init(uart_ring *q, int baud) { UNUSED(q); UNUSED(baud); } -void uart_set_baud(USART_TypeDef *u, unsigned int baud) { UNUSED(u); UNUSED(baud); } + void dma_pointer_handler(uart_ring *q, uint32_t dma_ndtr) { UNUSED(q); UNUSED(dma_ndtr); } -void uart_rx_ring(uart_ring *q) { UNUSED(q); } -void uart_tx_ring(uart_ring *q) { UNUSED(q); } +void dma_rx_init(uart_ring *q) { UNUSED(q); } + +#define __DIV(_PCLK_, _BAUD_) (((_PCLK_) * 25U) / (4U * (_BAUD_))) +#define __DIVMANT(_PCLK_, _BAUD_) (__DIV((_PCLK_), (_BAUD_)) / 100U) +#define __DIVFRAQ(_PCLK_, _BAUD_) ((((__DIV((_PCLK_), (_BAUD_)) - (__DIVMANT((_PCLK_), (_BAUD_)) * 100U)) * 16U) + 50U) / 100U) +#define __USART_BRR(_PCLK_, _BAUD_) ((__DIVMANT((_PCLK_), (_BAUD_)) << 4) | (__DIVFRAQ((_PCLK_), (_BAUD_)) & 0x0FU)) + +void uart_rx_ring(uart_ring *q){ + // Do not read out directly if DMA enabled + if (q->dma_rx == false) { + ENTER_CRITICAL(); + + // Read out RX buffer + uint8_t c = q->uart->RDR; // This read after reading SR clears a bunch of interrupts + + uint16_t next_w_ptr = (q->w_ptr_rx + 1U) % q->rx_fifo_size; + // Do not overwrite buffer data + if (next_w_ptr != q->r_ptr_rx) { + q->elems_rx[q->w_ptr_rx] = c; + q->w_ptr_rx = next_w_ptr; + if (q->callback != NULL) { + q->callback(q); + } + } + + EXIT_CRITICAL(); + } +} + +void uart_tx_ring(uart_ring *q){ + ENTER_CRITICAL(); + // Send out next byte of TX buffer + if (q->w_ptr_tx != q->r_ptr_tx) { + // Only send if transmit register is empty (aka last byte has been sent) + if ((q->uart->ISR & USART_ISR_TXE_TXFNF) != 0) { + q->uart->TDR = q->elems_tx[q->r_ptr_tx]; // This clears TXE + q->r_ptr_tx = (q->r_ptr_tx + 1U) % q->tx_fifo_size; + } + + // Enable TXE interrupt if there is still data to be sent + if(q->r_ptr_tx != q->w_ptr_tx){ + q->uart->CR1 |= USART_CR1_TXEIE; + } else { + q->uart->CR1 &= ~USART_CR1_TXEIE; + } + } + EXIT_CRITICAL(); +} + +void uart_set_baud(USART_TypeDef *u, unsigned int baud) { + // UART7 is connected to APB1 at 60MHz + u->BRR = 60000000U / baud; +} + +// This read after reading ISR clears all error interrupts. We don't want compiler warnings, nor optimizations +#define UART_READ_RDR(uart) volatile uint8_t t = (uart)->RDR; UNUSED(t); + +void uart_interrupt_handler(uart_ring *q) { + ENTER_CRITICAL(); + + // Read UART status. This is also the first step necessary in clearing most interrupts + uint32_t status = q->uart->ISR; + + // If RXFNE is set, perform a read. This clears RXFNE, ORE, IDLE, NF and FE + if((status & USART_ISR_RXNE_RXFNE) != 0U){ + uart_rx_ring(q); + } + + // Detect errors and clear them + uint32_t err = (status & USART_ISR_ORE) | (status & USART_ISR_NE) | (status & USART_ISR_FE) | (status & USART_ISR_PE); + if(err != 0U){ + #ifdef DEBUG_UART + print("Encountered UART error: "); puth(err); print("\n"); + #endif + UART_READ_RDR(q->uart) + } + + if ((err & USART_ISR_ORE) != 0U) { + q->uart->ICR |= USART_ICR_ORECF; + } else if ((err & USART_ISR_NE) != 0U) { + q->uart->ICR |= USART_ICR_NECF; + } else if ((err & USART_ISR_FE) != 0U) { + q->uart->ICR |= USART_ICR_FECF; + } else if ((err & USART_ISR_PE) != 0U) { + q->uart->ICR |= USART_ICR_PECF; + } else {} + + // Send if necessary + uart_tx_ring(q); + + // Run DMA pointer handler if the line is idle + if(q->dma_rx && (status & USART_ISR_IDLE)){ + // Reset IDLE flag + UART_READ_RDR(q->uart) + + #ifdef DEBUG_UART + print("No IDLE dma_pointer_handler implemented for this UART."); + #endif + } + + EXIT_CRITICAL(); +} + +void UART7_IRQ_Handler(void) { uart_interrupt_handler(&uart_ring_som_debug); } + +void uart_init(uart_ring *q, int baud) { + if (q->uart == UART7) { + REGISTER_INTERRUPT(UART7_IRQn, UART7_IRQ_Handler, 150000U, FAULT_INTERRUPT_RATE_UART_7) + + if (q->dma_rx) { + // TODO + } + + uart_set_baud(q->uart, baud); + q->uart->CR1 = USART_CR1_UE | USART_CR1_TE | USART_CR1_RE; + + // Enable interrupt on RX not empty + q->uart->CR1 |= USART_CR1_RXNEIE; + + // Enable UART interrupts + NVIC_EnableIRQ(UART7_IRQn); + + // Initialise RX DMA if used + if (q->dma_rx) { + dma_rx_init(q); + } + } +} diff --git a/board/stm32h7/peripherals.h b/board/stm32h7/peripherals.h index 00ef6bfa05..7e3b2e5b16 100644 --- a/board/stm32h7/peripherals.h +++ b/board/stm32h7/peripherals.h @@ -1,10 +1,18 @@ void gpio_usb_init(void) { - // A11,A12: USB: + // A11,A12: USB set_gpio_alternate(GPIOA, 11, GPIO_AF10_OTG1_FS); set_gpio_alternate(GPIOA, 12, GPIO_AF10_OTG1_FS); GPIOA->OSPEEDR = GPIO_OSPEEDR_OSPEED11 | GPIO_OSPEEDR_OSPEED12; } +void gpio_spi_init(void) { + set_gpio_alternate(GPIOE, 11, GPIO_AF5_SPI4); + set_gpio_alternate(GPIOE, 12, GPIO_AF5_SPI4); + set_gpio_alternate(GPIOE, 13, GPIO_AF5_SPI4); + set_gpio_alternate(GPIOE, 14, GPIO_AF5_SPI4); + register_set_bits(&(GPIOE->OSPEEDR), GPIO_OSPEEDR_OSPEED11 | GPIO_OSPEEDR_OSPEED12 | GPIO_OSPEEDR_OSPEED13 | GPIO_OSPEEDR_OSPEED14); +} + void gpio_usart2_init(void) { // A2,A3: USART 2 for debugging set_gpio_alternate(GPIOA, 2, GPIO_AF7_USART2); @@ -22,12 +30,15 @@ void common_init_gpio(void) { /// E2,E3,E4: RGB LED set_gpio_pullup(GPIOE, 2, PULL_NONE); set_gpio_mode(GPIOE, 2, MODE_OUTPUT); + set_gpio_output_type(GPIOE, 2, OUTPUT_TYPE_OPEN_DRAIN); set_gpio_pullup(GPIOE, 3, PULL_NONE); set_gpio_mode(GPIOE, 3, MODE_OUTPUT); + set_gpio_output_type(GPIOE, 3, OUTPUT_TYPE_OPEN_DRAIN); set_gpio_pullup(GPIOE, 4, PULL_NONE); set_gpio_mode(GPIOE, 4, MODE_OUTPUT); + set_gpio_output_type(GPIOE, 4, OUTPUT_TYPE_OPEN_DRAIN); // F7,F8,F9,F10: BOARD ID set_gpio_pullup(GPIOF, 7, PULL_NONE); @@ -82,6 +93,10 @@ void common_init_gpio(void) { void flasher_peripherals_init(void) { RCC->AHB1ENR |= RCC_AHB1ENR_USB1OTGHSEN; + + // SPI + DMA + RCC->APB2ENR |= RCC_APB2ENR_SPI4EN; + RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN; } // Peripheral initialization @@ -95,10 +110,19 @@ void peripherals_init(void) { RCC->AHB4ENR |= RCC_AHB4ENR_GPIOFEN; RCC->AHB4ENR |= RCC_AHB4ENR_GPIOGEN; + RCC->APB2ENR |= RCC_APB2ENR_SPI4EN; // SPI + RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN; // DAC DMA + RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN; // SPI DMA RCC->APB1LENR |= RCC_APB1LENR_TIM2EN; // main counter + RCC->APB1LENR |= RCC_APB1LENR_TIM3EN; // fan pwm RCC->APB1LENR |= RCC_APB1LENR_TIM6EN; // interrupt timer - RCC->APB2ENR |= RCC_APB2ENR_TIM8EN; // clock source timer + RCC->APB1LENR |= RCC_APB1LENR_TIM7EN; // DMA trigger timer + RCC->APB1LENR |= RCC_APB1LENR_UART7EN; // SOM uart + RCC->APB1LENR |= RCC_APB1LENR_DAC12EN; // DAC + RCC->APB2ENR |= RCC_APB2ENR_TIM8EN; // tick timer RCC->APB1LENR |= RCC_APB1LENR_TIM12EN; // slow loop + RCC->APB1LENR |= RCC_APB1LENR_I2C5EN; // codec I2C + RCC->APB2ENR |= RCC_APB2ENR_TIM1EN; // clock source timer RCC->APB1HENR |= RCC_APB1HENR_FDCANEN; // FDCAN core enable RCC->AHB1ENR |= RCC_AHB1ENR_ADC12EN; // Enable ADC clocks diff --git a/board/stm32h7/stm32h7_config.h b/board/stm32h7/stm32h7_config.h index abaed68155..d1b19fa34d 100644 --- a/board/stm32h7/stm32h7_config.h +++ b/board/stm32h7/stm32h7_config.h @@ -7,8 +7,10 @@ #define CORE_FREQ 240U // in Mhz //APB1 - 120Mhz, APB2 - 120Mhz -#define APB1_FREQ CORE_FREQ/2U -#define APB2_FREQ CORE_FREQ/2U +#define APB1_FREQ (CORE_FREQ/4U) +#define APB1_TIMER_FREQ (APB1_FREQ*2U) // APB1 is multiplied by 2 for the timer peripherals +#define APB2_FREQ (CORE_FREQ/4U) +#define APB2_TIMER_FREQ (APB2_FREQ*2U) // APB2 is multiplied by 2 for the timer peripherals #define BOOTLOADER_ADDRESS 0x1FF09804U @@ -55,12 +57,16 @@ #include "stm32h7/interrupt_handlers.h" #include "drivers/timers.h" #include "stm32h7/lladc.h" -#include "stm32h7/board.h" -#include "stm32h7/clock.h" #if !defined(BOOTSTUB) && defined(PANDA) #include "drivers/uart.h" #include "stm32h7/lluart.h" +#endif + +#include "stm32h7/board.h" +#include "stm32h7/clock.h" + +#if !defined(BOOTSTUB) && defined(PANDA) #include "stm32h7/llexti.h" #endif @@ -72,6 +78,9 @@ #include "stm32h7/llusb.h" +#include "drivers/spi.h" +#include "stm32h7/llspi.h" + void early_gpio_float(void) { RCC->AHB4ENR = RCC_AHB4ENR_GPIOAEN | RCC_AHB4ENR_GPIOBEN | RCC_AHB4ENR_GPIOCEN | RCC_AHB4ENR_GPIODEN | RCC_AHB4ENR_GPIOEEN | RCC_AHB4ENR_GPIOFEN | RCC_AHB4ENR_GPIOGEN | RCC_AHB4ENR_GPIOHEN; GPIOA->MODER = 0; GPIOB->MODER = 0; GPIOC->MODER = 0; GPIOD->MODER = 0; GPIOE->MODER = 0; GPIOF->MODER = 0; GPIOG->MODER = 0; GPIOH->MODER = 0; diff --git a/board/stm32h7/stm32h7x5_flash.ld b/board/stm32h7/stm32h7x5_flash.ld index aeaa4e3bee..5aef663743 100644 --- a/board/stm32h7/stm32h7x5_flash.ld +++ b/board/stm32h7/stm32h7x5_flash.ld @@ -64,10 +64,10 @@ _Min_Stack_Size = 0x400; /* required amount of stack */ /* Specify the memory areas */ MEMORY { -DTCMRAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K -RAM_D1 (xrw) : ORIGIN = 0x24000000, LENGTH = 320K -RAM_D2 (xrw) : ORIGIN = 0x30000000, LENGTH = 32K -RAM_D3 (xrw) : ORIGIN = 0x38000000, LENGTH = 16K +DTCMRAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K /* DTCM */ +RAM_D1 (xrw) : ORIGIN = 0x24000000, LENGTH = 320K /* AXI SRAM */ +RAM_D2 (xrw) : ORIGIN = 0x30000000, LENGTH = 32K /* SRAM1(16kb) + SRAM2(16kb) */ +RAM_D3 (xrw) : ORIGIN = 0x38000000, LENGTH = 16K /* SRAM4 */ ITCMRAM (xrw) : ORIGIN = 0x00000000, LENGTH = 64K FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 1024K } @@ -152,7 +152,7 @@ SECTIONS _edata = .; /* define a global symbol at data end */ } >DTCMRAM AT> FLASH - + /* Uninitialized data section */ . = ALIGN(4); .bss : @@ -186,6 +186,12 @@ SECTIONS *(.ram_d1*) } >RAM_D1 + .ram_d2 (NOLOAD) : + { + . = ALIGN(4); + *(.ram_d2*) + } >RAM_D2 + .ARM.attributes 0 : { *(.ARM.attributes) } } diff --git a/board/utils.h b/board/utils.h index d767738b1b..4232e0a979 100644 --- a/board/utils.h +++ b/board/utils.h @@ -1,3 +1,28 @@ +#define MIN(a,b) \ + ({ __typeof__ (a) _a = (a); \ + __typeof__ (b) _b = (b); \ + (_a < _b) ? _a : _b; }) + +#define MAX(a,b) \ + ({ __typeof__ (a) _a = (a); \ + __typeof__ (b) _b = (b); \ + (_a > _b) ? _a : _b; }) + +#define ABS(a) \ + ({ __typeof__ (a) _a = (a); \ + (_a > 0) ? _a : (-_a); }) + +#ifndef NULL +#define NULL ((void*)0) +#endif + +// STM32 HAL defines this +#ifndef UNUSED +#define UNUSED(x) ((void)(x)) +#endif + +#define COMPILE_TIME_ASSERT(pred) ((void)sizeof(char[1 - (2 * ((int)(!(pred))))])) + // compute the time elapsed (in microseconds) from 2 counter samples // case where ts < ts_last is ok: overflow is properly re-casted into uint32_t uint32_t get_ts_elapsed(uint32_t ts, uint32_t ts_last) { diff --git a/examples/query_fw_versions.py b/examples/query_fw_versions.py index 2d4509745c..82a204185e 100755 --- a/examples/query_fw_versions.py +++ b/examples/query_fw_versions.py @@ -4,14 +4,15 @@ from panda import Panda from panda.python.uds import UdsClient, MessageTimeoutError, NegativeResponseError, SESSION_TYPE, DATA_IDENTIFIER_TYPE - if __name__ == "__main__": parser = argparse.ArgumentParser() - parser.add_argument('--rxoffset', default="") - parser.add_argument('--nonstandard', action='store_true') - parser.add_argument('--debug', action='store_true') - parser.add_argument('--addr') - parser.add_argument('--bus') + parser.add_argument("--rxoffset", default="") + parser.add_argument("--nonstandard", action="store_true") + parser.add_argument("--no-obd", action="store_true", help="Bus 1 will not be multiplexed to the OBD-II port") + parser.add_argument("--debug", action="store_true") + parser.add_argument("--addr") + parser.add_argument("--bus") + parser.add_argument('-s', '--serial', help="Serial number of panda to use") args = parser.parse_args() if args.addr: @@ -25,15 +26,25 @@ for std_id in DATA_IDENTIFIER_TYPE: uds_data_ids[std_id.value] = std_id.name if args.nonstandard: - for uds_id in range(0xf100,0xf180): + for uds_id in range(0xf100, 0xf180): uds_data_ids[uds_id] = "IDENTIFICATION_OPTION_VEHICLE_MANUFACTURER_SPECIFIC_DATA_IDENTIFIER" - for uds_id in range(0xf1a0,0xf1f0): + for uds_id in range(0xf1a0, 0xf1f0): uds_data_ids[uds_id] = "IDENTIFICATION_OPTION_VEHICLE_MANUFACTURER_SPECIFIC" - for uds_id in range(0xf1f0,0xf200): + for uds_id in range(0xf1f0, 0xf200): uds_data_ids[uds_id] = "IDENTIFICATION_OPTION_SYSTEM_SUPPLIER_SPECIFIC" - panda = Panda() - panda.set_safety_mode(Panda.SAFETY_ELM327) + panda_serials = Panda.list() + if args.serial is None and len(panda_serials) > 1: + print("\nMultiple pandas found, choose one:") + for serial in panda_serials: + with Panda(serial) as panda: + print(f" {serial}: internal={panda.is_internal()}") + print() + parser.print_help() + exit() + + panda = Panda(serial=args.serial) + panda.set_safety_mode(Panda.SAFETY_ELM327, 1 if args.no_obd else 0) print("querying addresses ...") with tqdm(addrs) as t: for addr in t: diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 0000000000..0b6de30d80 --- /dev/null +++ b/mypy.ini @@ -0,0 +1,11 @@ +[mypy] +; third-party packages +ignore_missing_imports = True + +; helpful warnings +warn_redundant_casts = True +warn_unreachable = True +warn_unused_ignores = True + +; restrict dynamic typing +warn_return_any = True diff --git a/python/__init__.py b/python/__init__.py index 0024efdbaf..1b8495f40d 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -5,97 +5,92 @@ import usb1 import struct import hashlib +import binascii import datetime -import traceback import warnings +import logging from functools import wraps from typing import Optional from itertools import accumulate -from .dfu import PandaDFU, MCU_TYPE_F2, MCU_TYPE_F4, MCU_TYPE_H7 # pylint: disable=import-error -from .flash_release import flash_release # noqa pylint: disable=import-error -from .update import ensure_st_up_to_date # noqa pylint: disable=import-error -from .serial import PandaSerial # noqa pylint: disable=import-error -from .isotp import isotp_send, isotp_recv # pylint: disable=import-error -from .config import DEFAULT_FW_FN, DEFAULT_H7_FW_FN, SECTOR_SIZES_FX, SECTOR_SIZES_H7 # noqa pylint: disable=import-error -__version__ = '0.0.10' +from .base import BaseHandle +from .constants import McuType +from .dfu import PandaDFU +from .isotp import isotp_send, isotp_recv +from .spi import PandaSpiHandle, PandaSpiException +from .usb import PandaUsbHandle -BASEDIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../") +__version__ = '0.0.10' -DEBUG = os.getenv("PANDADEBUG") is not None +# setup logging +LOGLEVEL = os.environ.get('LOGLEVEL', 'INFO').upper() +logging.basicConfig(level=LOGLEVEL, format='%(message)s') -CANPACKET_HEAD_SIZE = 0x5 +USBPACKET_MAX_SIZE = 0x40 +CANPACKET_HEAD_SIZE = 0x6 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)} + +def calculate_checksum(data): + res = 0 + for b in data: + res ^= b + return res + def pack_can_buffer(arr): snds = [b''] - idx = 0 for address, _, dat, bus in arr: assert len(dat) in LEN_TO_DLC - if DEBUG: - print(f" W 0x{address:x}: 0x{dat.hex()}") + #logging.debug(" W 0x%x: 0x%s", address, dat.hex()) + extended = 1 if address >= 0x800 else 0 data_len_code = LEN_TO_DLC[len(dat)] - header = bytearray(5) + header = bytearray(CANPACKET_HEAD_SIZE) word_4b = address << 3 | extended << 2 header[0] = (data_len_code << 4) | (bus << 1) header[1] = word_4b & 0xFF header[2] = (word_4b >> 8) & 0xFF header[3] = (word_4b >> 16) & 0xFF header[4] = (word_4b >> 24) & 0xFF - snds[idx] += header + dat - if len(snds[idx]) > 256: # Limit chunks to 256 bytes + header[5] = calculate_checksum(header[:5] + dat) + + snds[-1] += header + dat + if len(snds[-1]) > 256: # Limit chunks to 256 bytes snds.append(b'') - idx += 1 - - #Apply counter to each 64 byte packet - for idx in range(len(snds)): - tx = b'' - counter = 0 - for i in range (0, len(snds[idx]), 63): - tx += bytes([counter]) + snds[idx][i:i+63] - counter += 1 - snds[idx] = tx + return snds def unpack_can_buffer(dat): ret = [] - counter = 0 - tail = bytearray() - for i in range(0, len(dat), 64): - if counter != dat[i]: - print("CAN: LOST RECV PACKET COUNTER") + + while len(dat) >= CANPACKET_HEAD_SIZE: + data_len = DLC_TO_LEN[(dat[0]>>4)] + + header = dat[:CANPACKET_HEAD_SIZE] + + bus = (header[0] >> 1) & 0x7 + address = (header[4] << 24 | header[3] << 16 | header[2] << 8 | header[1]) >> 3 + + if (header[1] >> 1) & 0x1: + # returned + bus += 128 + if header[1] & 0x1: + # rejected + bus += 192 + + # we need more from the next transfer + if data_len > len(dat) - CANPACKET_HEAD_SIZE: break - counter+=1 - chunk = tail + dat[i+1:i+64] - tail = bytearray() - pos = 0 - while pos>4)] - pckt_len = CANPACKET_HEAD_SIZE + data_len - if pckt_len <= len(chunk[pos:]): - header = chunk[pos:pos+CANPACKET_HEAD_SIZE] - if len(header) < 5: - print("CAN: MALFORMED USB RECV PACKET") - break - bus = (header[0] >> 1) & 0x7 - address = (header[4] << 24 | header[3] << 16 | header[2] << 8 | header[1]) >> 3 - returned = (header[1] >> 1) & 0x1 - rejected = header[1] & 0x1 - data = chunk[pos + CANPACKET_HEAD_SIZE:pos + CANPACKET_HEAD_SIZE + data_len] - if returned: - bus += 128 - if rejected: - bus += 192 - if DEBUG: - print(f" R 0x{address:x}: 0x{data.hex()}") - ret.append((address, 0, data, bus)) - pos += pckt_len - else: - tail = chunk[pos:] - break - return ret + + assert calculate_checksum(dat[:(CANPACKET_HEAD_SIZE+data_len)]) == 0, "CAN packet checksum incorrect" + + data = dat[CANPACKET_HEAD_SIZE:(CANPACKET_HEAD_SIZE+data_len)] + dat = dat[(CANPACKET_HEAD_SIZE+data_len):] + + ret.append((address, 0, data, bus)) + + return (ret, dat) def ensure_health_packet_version(fn): @wraps(fn) @@ -167,6 +162,7 @@ class Panda: SERIAL_ESP = 1 SERIAL_LIN1 = 2 SERIAL_LIN2 = 3 + SERIAL_SOM_DEBUG = 4 GMLAN_CAN2 = 1 GMLAN_CAN3 = 2 @@ -183,26 +179,25 @@ class Panda: HW_TYPE_DOS = b'\x06' HW_TYPE_RED_PANDA = b'\x07' HW_TYPE_RED_PANDA_V2 = b'\x08' + HW_TYPE_TRES = b'\x09' - CAN_PACKET_VERSION = 2 + CAN_PACKET_VERSION = 4 HEALTH_PACKET_VERSION = 11 - CAN_HEALTH_PACKET_VERSION = 3 + CAN_HEALTH_PACKET_VERSION = 4 HEALTH_STRUCT = struct.Struct("> 8, ]) + this_bcd = device.getbcdDevice() + if this_bcd is not None and this_bcd != 0x2300: + bcd = bytearray([this_bcd >> 8, ]) break - except Exception as e: - print("exception", e) - traceback.print_exc() - if not wait or self._handle is not None: + except Exception: + logging.exception("USB connect error") + if not wait or handle is not None: break context = usb1.USBContext() # New context needed so new devices show up - assert self._handle is not None - self._mcu_type = self.get_mcu_type() - self.health_version, self.can_version, self.can_health_version = self.get_packets_versions() - print("connected") + usb_handle = None + if handle is not None: + usb_handle = PandaUsbHandle(handle) - # disable openpilot's heartbeat checks - if self._disable_checks: - self.set_heartbeat_disabled() - self.set_power_save(0) + return usb_handle, usb_serial, bootstub, bcd + + @staticmethod + def list(): + ret = Panda.usb_list() + ret += Panda.spi_list() + return list(set(ret)) + + @staticmethod + def usb_list(): + context = usb1.USBContext() + ret = [] + try: + for device in context.getDeviceList(skip_on_error=True): + if device.getVendorID() == 0xbbaa and device.getProductID() in (0xddcc, 0xddee): + try: + serial = device.getSerialNumber() + if len(serial) == 24: + ret.append(serial) + else: + warnings.warn(f"found device with panda descriptors but invalid serial: {serial}", RuntimeWarning) + except Exception: + continue + except Exception: + pass + return ret + + @staticmethod + def spi_list(): + _, serial, _, _ = Panda.spi_connect(None) + if serial is not None: + return [serial, ] + return [] def reset(self, enter_bootstub=False, enter_bootloader=False, reconnect=True): try: @@ -307,7 +402,7 @@ def reset(self, enter_bootstub=False, enter_bootloader=False, reconnect=True): self.reconnect() def reconnect(self): - if self._handle is not None: + if self._handle_open: self.close() time.sleep(1.0) @@ -319,7 +414,7 @@ def reconnect(self): success = True break except Exception: - print("reconnecting is taking %d seconds..." % (i + 1)) + logging.debug("reconnecting is taking %d seconds...", i + 1) try: dfu = PandaDFU(PandaDFU.st_serial_to_dfu_serial(self._serial, self._mcu_type)) dfu.recover() @@ -329,39 +424,41 @@ def reconnect(self): if not success: raise Exception("reconnect failed") - + @staticmethod + def flasher_present(handle: BaseHandle) -> bool: + fr = handle.controlRead(Panda.REQUEST_IN, 0xb0, 0, 0, 0xc) + return fr[4:8] == b"\xde\xad\xd0\x0d" @staticmethod def flash_static(handle, code, mcu_type): assert mcu_type is not None, "must set valid mcu_type to flash" # confirm flasher is present - fr = handle.controlRead(Panda.REQUEST_IN, 0xb0, 0, 0, 0xc) - assert fr[4:8] == b"\xde\xad\xd0\x0d" + assert Panda.flasher_present(handle) # determine sectors to erase - apps_sectors_cumsum = accumulate(SECTOR_SIZES_H7[1:] if mcu_type == MCU_TYPE_H7 else SECTOR_SIZES_FX[1:]) + apps_sectors_cumsum = accumulate(mcu_type.config.sector_sizes[1:]) last_sector = next((i + 1 for i, v in enumerate(apps_sectors_cumsum) if v > len(code)), -1) assert last_sector >= 1, "Binary too small? No sector to erase." assert last_sector < 7, "Binary too large! Risk of overwriting provisioning chunk." # unlock flash - print("flash: unlocking") + logging.warning("flash: unlocking") handle.controlWrite(Panda.REQUEST_IN, 0xb1, 0, 0, b'') # erase sectors - print(f"flash: erasing sectors 1 - {last_sector}") + logging.warning(f"flash: erasing sectors 1 - {last_sector}") for i in range(1, last_sector + 1): handle.controlWrite(Panda.REQUEST_IN, 0xb2, i, 0, b'') # flash over EP2 STEP = 0x10 - print("flash: flashing") + logging.warning("flash: flashing") for i in range(0, len(code), STEP): handle.bulkWrite(2, code[i:i + STEP]) # reset - print("flash: resetting") + logging.warning("flash: resetting") try: handle.controlWrite(Panda.REQUEST_IN, 0xd8, 0, 0, b'') except Exception: @@ -369,9 +466,9 @@ def flash_static(handle, code, mcu_type): def flash(self, fn=None, code=None, reconnect=True): if not fn: - fn = DEFAULT_H7_FW_FN if self._mcu_type == MCU_TYPE_H7 else DEFAULT_FW_FN + fn = self._mcu_type.config.app_path assert os.path.isfile(fn) - print("flash: main version is " + self.get_version()) + logging.debug("flash: main version is %s", self.get_version()) if not self.bootstub: self.reset(enter_bootstub=True) assert(self.bootstub) @@ -381,7 +478,7 @@ def flash(self, fn=None, code=None, reconnect=True): code = f.read() # get version - print("flash: bootstub version is " + self.get_version()) + logging.debug("flash: bootstub version is %s", self.get_version()) # do flash Panda.flash_static(self._handle, code, mcu_type=self._mcu_type) @@ -412,30 +509,16 @@ def recover(self, timeout: Optional[int] = None, reset: bool = True) -> bool: def wait_for_dfu(dfu_serial: str, timeout: Optional[int] = None) -> bool: t_start = time.monotonic() while dfu_serial not in PandaDFU.list(): - print("waiting for DFU...") + logging.debug("waiting for DFU...") time.sleep(0.1) if timeout is not None and (time.monotonic() - t_start) > timeout: return False return True - @staticmethod - def list(): - context = usb1.USBContext() - ret = [] - try: - for device in context.getDeviceList(skip_on_error=True): - if device.getVendorID() == 0xbbaa and device.getProductID() in (0xddcc, 0xddee): - try: - serial = device.getSerialNumber() - if len(serial) == 24: - ret.append(serial) - else: - warnings.warn(f"found device with panda descriptors but invalid serial: {serial}", RuntimeWarning) - except Exception: - continue - except Exception: - pass - return ret + def up_to_date(self) -> bool: + current = self.get_signature() + expected = Panda.get_signature_from_firmware(self.get_mcu_type().config.app_path) + return (current == expected) def call_control_api(self, msg): self._handle.controlWrite(Panda.REQUEST_OUT, msg, 0, 0, b'') @@ -503,11 +586,12 @@ def can_health(self, can_number): "total_tx_cnt": a[13], "total_rx_cnt": a[14], "total_fwd_cnt": a[15], - "can_speed": a[16], - "can_data_speed": a[17], - "canfd_enabled": a[18], - "brs_enabled": a[19], - "canfd_non_iso": a[20], + "total_tx_checksum_error_cnt": a[16], + "can_speed": a[17], + "can_data_speed": a[18], + "canfd_enabled": a[19], + "brs_enabled": a[20], + "canfd_non_iso": a[21], } # ******************* control ******************* @@ -515,8 +599,8 @@ def can_health(self, can_number): def enter_bootloader(self): try: self._handle.controlWrite(Panda.REQUEST_OUT, 0xd1, 0, 0, b'') - except Exception as e: - print(e) + except Exception: + logging.exception("exception while entering bootloader") def get_version(self): return self._handle.controlRead(Panda.REQUEST_IN, 0xd6, 0, 0, 0x40).decode('utf8') @@ -527,7 +611,7 @@ def get_signature_from_firmware(fn) -> bytes: f.seek(-128, 2) # Seek from end of file return f.read(128) - def get_signature(self): + def get_signature(self) -> bytes: part_1 = self._handle.controlRead(Panda.REQUEST_IN, 0xd3, 0, 0, 0x40) part_2 = self._handle.controlRead(Panda.REQUEST_IN, 0xd4, 0, 0, 0x40) return bytes(part_1 + part_2) @@ -535,10 +619,9 @@ def get_signature(self): def get_type(self): ret = self._handle.controlRead(Panda.REQUEST_IN, 0xc1, 0, 0, 0x40) - # bootstub doesn't implement this call, so fallback to bcdDevice - invalid_type = self.bootstub and (ret is None or len(ret) != 1) - if invalid_type and self._bcd_device is not None: - ret = self._bcd_device + # old bootstubs don't implement this endpoint, see comment in Panda.device + if self._bcd_hw_type is not None and (ret is None or len(ret) != 1): + ret = self._bcd_hw_type return ret @@ -551,15 +634,20 @@ def get_packets_versions(self): else: return (0, 0, 0) - def get_mcu_type(self): + def get_mcu_type(self) -> McuType: hw_type = self.get_type() if hw_type in Panda.F2_DEVICES: - return MCU_TYPE_F2 + return McuType.F2 elif hw_type in Panda.F4_DEVICES: - return MCU_TYPE_F4 + return McuType.F4 elif hw_type in Panda.H7_DEVICES: - return MCU_TYPE_H7 - return None + return McuType.H7 + else: + # have to assume F4, see comment in Panda.connect + if self._assume_f4_mcu: + return McuType.F4 + + raise ValueError(f"unknown HW type: {hw_type}") def has_obd(self): return self.get_type() in Panda.HAS_OBD @@ -568,22 +656,33 @@ def is_internal(self): return self.get_type() in Panda.INTERNAL_DEVICES def get_serial(self): + """ + Returns the comma-issued dongle ID from our provisioning + """ dat = self._handle.controlRead(Panda.REQUEST_IN, 0xd0, 0, 0, 0x20) hashsig, calc_hash = dat[0x1c:], hashlib.sha1(dat[0:0x1c]).digest()[0:4] assert(hashsig == calc_hash) return [dat[0:0x10].decode("utf8"), dat[0x10:0x10 + 10].decode("utf8")] def get_usb_serial(self): + """ + Returns the serial number reported from the USB descriptor; + matches the MCU UID + """ return self._serial + def get_uid(self): + """ + Returns the UID from the MCU + """ + dat = self._handle.controlRead(Panda.REQUEST_IN, 0xc3, 0, 0, 12) + return binascii.hexlify(dat).decode() + def get_secret(self): return self._handle.controlRead(Panda.REQUEST_IN, 0xd0, 1, 0, 0x10) # ******************* configuration ******************* - def set_usb_power(self, on): - self._handle.controlWrite(Panda.REQUEST_OUT, 0xe6, int(on), 0, b'') - def set_power_save(self, power_save_enabled=0): self._handle.controlWrite(Panda.REQUEST_OUT, 0xe7, int(power_save_enabled), 0, b'') @@ -645,6 +744,9 @@ def set_uart_callback(self, uart, install): # Timeout is in ms. If set to 0, the timeout is infinite. CAN_SEND_TIMEOUT_MS = 10 + def can_reset_communications(self): + self._handle.controlWrite(Panda.REQUEST_OUT, 0xc0, 0, 0, b'') + @ensure_can_packet_version def can_send_many(self, arr, timeout=CAN_SEND_TIMEOUT_MS): snds = pack_can_buffer(arr) @@ -656,10 +758,10 @@ def can_send_many(self, arr, timeout=CAN_SEND_TIMEOUT_MS): tx = tx[bs:] if len(tx) == 0: break - print("CAN: PARTIAL SEND MANY, RETRYING") + logging.error("CAN: PARTIAL SEND MANY, RETRYING") break except (usb1.USBErrorIO, usb1.USBErrorOverflow): - print("CAN: BAD SEND MANY, RETRYING") + logging.error("CAN: BAD SEND MANY, RETRYING") def can_send(self, addr, dat, bus, timeout=CAN_SEND_TIMEOUT_MS): self.can_send_many([[addr, None, dat, bus]], timeout=timeout) @@ -672,9 +774,10 @@ def can_recv(self): dat = self._handle.bulkRead(1, 16384) # Max receive batch size + 2 extra reserve frames break except (usb1.USBErrorIO, usb1.USBErrorOverflow): - print("CAN: BAD RECV, RETRYING") + logging.error("CAN: BAD RECV, RETRYING") time.sleep(0.1) - return unpack_can_buffer(dat) + msgs, self.can_rx_overflow_buffer = unpack_can_buffer(self.can_rx_overflow_buffer + dat) + return msgs def can_clear(self, bus): """Clears all messages from the specified internal CAN ringbuffer as @@ -708,6 +811,8 @@ def serial_read(self, port_number): def serial_write(self, port_number, ln): ret = 0 + if type(ln) == str: + ln = bytes(ln, 'utf-8') for i in range(0, len(ln), 0x20): ret += self._handle.bulkWrite(2, struct.pack("B", port_number) + ln[i:i + 0x20]) return ret @@ -727,19 +832,15 @@ def serial_clear(self, port_number): # pulse low for wakeup def kline_wakeup(self, k=True, l=True): assert k or l, "must specify k-line, l-line, or both" - if DEBUG: - print("kline wakeup...") + logging.debug("kline wakeup...") self._handle.controlWrite(Panda.REQUEST_OUT, 0xf0, 2 if k and l else int(l), 0, b'') - if DEBUG: - print("kline wakeup done") + logging.debug("kline wakeup done") def kline_5baud(self, addr, k=True, l=True): assert k or l, "must specify k-line, l-line, or both" - if DEBUG: - print("kline 5 baud...") + logging.debug("kline 5 baud...") self._handle.controlWrite(Panda.REQUEST_OUT, 0xf4, 2 if k and l else int(l), addr, b'') - if DEBUG: - print("kline 5 baud done") + logging.debug("kline 5 baud done") def kline_drain(self, bus=2): # drain buffer @@ -748,8 +849,7 @@ def kline_drain(self, bus=2): ret = self._handle.controlRead(Panda.REQUEST_IN, 0xe0, bus, 0, 0x40) if len(ret) == 0: break - elif DEBUG: - print(f"kline drain: 0x{ret.hex()}") + logging.debug(f"kline drain: 0x{ret.hex()}") bret += ret return bytes(bret) @@ -757,8 +857,8 @@ def kline_ll_recv(self, cnt, bus=2): echo = bytearray() while len(echo) != cnt: ret = self._handle.controlRead(Panda.REQUEST_OUT, 0xe0, bus, 0, cnt - len(echo)) - if DEBUG and len(ret) > 0: - print(f"kline recv: 0x{ret.hex()}") + if len(ret) > 0: + logging.debug(f"kline recv: 0x{ret.hex()}") echo += ret return bytes(echo) @@ -768,14 +868,13 @@ def kline_send(self, x, bus=2, checksum=True): x += bytes([sum(x) % 0x100]) for i in range(0, len(x), 0xf): ts = x[i:i + 0xf] - if DEBUG: - print(f"kline send: 0x{ts.hex()}") + logging.debug(f"kline send: 0x{ts.hex()}") self._handle.bulkWrite(2, bytes([bus]) + ts) echo = self.kline_ll_recv(len(ts), bus=bus) if echo != ts: - print(f"**** ECHO ERROR {i} ****") - print(f"0x{echo.hex()}") - print(f"0x{ts.hex()}") + logging.error(f"**** ECHO ERROR {i} ****") + logging.error(f"0x{echo.hex()}") + logging.error(f"0x{ts.hex()}") assert echo == ts def kline_recv(self, bus=2, header_len=4): @@ -808,6 +907,11 @@ def get_datetime(self): a = struct.unpack("HBBBBBB", dat) return datetime.datetime(a[0], a[1], a[2], a[4], a[5], a[6]) + # ****************** Timer ***************** + def get_microsecond_timer(self): + dat = self._handle.controlRead(Panda.REQUEST_IN, 0xa8, 0, 0, 4) + return struct.unpack("I", dat)[0] + # ******************* IR ******************* def set_ir_power(self, percentage): self._handle.controlWrite(Panda.REQUEST_OUT, 0xb0, int(percentage), 0, b'') @@ -825,10 +929,6 @@ def get_fan_rpm(self): def set_phone_power(self, enabled): self._handle.controlWrite(Panda.REQUEST_OUT, 0xb3, int(enabled), 0, b'') - # ************** Clock Source ************** - def set_clock_source_mode(self, mode): - self._handle.controlWrite(Panda.REQUEST_OUT, 0xf5, int(mode), 0, b'') - # ****************** Siren ***************** def set_siren(self, enabled): self._handle.controlWrite(Panda.REQUEST_OUT, 0xf6, int(enabled), 0, b'') diff --git a/python/base.py b/python/base.py new file mode 100644 index 0000000000..5bfa564892 --- /dev/null +++ b/python/base.py @@ -0,0 +1,67 @@ +from abc import ABC, abstractmethod +from typing import List + +from .constants import McuType + +TIMEOUT = int(15 * 1e3) # default timeout, in milliseconds + +class BaseHandle(ABC): + """ + A handle to talk to a panda. + Borrows heavily from the libusb1 handle API. + """ + + @abstractmethod + def close(self) -> None: + ... + + @abstractmethod + def controlWrite(self, request_type: int, request: int, value: int, index: int, data, timeout: int = TIMEOUT) -> int: + ... + + @abstractmethod + def controlRead(self, request_type: int, request: int, value: int, index: int, length: int, timeout: int = TIMEOUT) -> bytes: + ... + + @abstractmethod + def bulkWrite(self, endpoint: int, data: List[int], timeout: int = TIMEOUT) -> int: + ... + + @abstractmethod + def bulkRead(self, endpoint: int, length: int, timeout: int = TIMEOUT) -> bytes: + ... + + +class BaseSTBootloaderHandle(ABC): + """ + A handle to talk to a panda while it's in the STM32 bootloader. + """ + + @abstractmethod + def get_mcu_type(self) -> McuType: + ... + + @abstractmethod + def close(self) -> None: + ... + + @abstractmethod + def clear_status(self) -> None: + ... + + @abstractmethod + def program(self, address: int, dat: bytes) -> None: + ... + + @abstractmethod + def erase_app(self) -> None: + ... + + @abstractmethod + def erase_bootstub(self) -> None: + ... + + @abstractmethod + def jump(self, address: int) -> None: + ... + diff --git a/python/ccp.py b/python/ccp.py index a8c00169fc..9183120021 100644 --- a/python/ccp.py +++ b/python/ccp.py @@ -100,9 +100,9 @@ def _recv_dto(self, timeout: float) -> bytes: msgs = self._panda.can_recv() or [] if len(msgs) >= 256: print("CAN RX buffer overflow!!!", file=sys.stderr) - for rx_addr, _, rx_data, rx_bus in msgs: + for rx_addr, _, rx_data_bytearray, rx_bus in msgs: if rx_bus == self.can_bus and rx_addr == self.rx_addr: - rx_data = bytes(rx_data) # convert bytearray to bytes + rx_data = bytes(rx_data_bytearray) if self.debug: print(f"CAN-RX: {hex(rx_addr)} - 0x{bytes.hex(rx_data)}") assert len(rx_data) == 8, f"message length not 8: {len(rx_data)}" @@ -183,7 +183,7 @@ def download(self, data: bytes) -> int: resp = self._recv_dto(0.025) # mta_addr_ext = resp[0] mta_addr = struct.unpack(f"{self.byte_order.value}I", resp[1:5])[0] - return mta_addr + return mta_addr # type: ignore def download_6_bytes(self, data: bytes) -> int: if len(data) != 6: @@ -192,7 +192,7 @@ def download_6_bytes(self, data: bytes) -> int: resp = self._recv_dto(0.025) # mta_addr_ext = resp[0] mta_addr = struct.unpack(f"{self.byte_order.value}I", resp[1:5])[0] - return mta_addr + return mta_addr # type: ignore def upload(self, size: int) -> bytes: if size > 5: @@ -296,7 +296,7 @@ def program(self, size: int, data: bytes) -> int: resp = self._recv_dto(0.1) # mta_addr_ext = resp[0] mta_addr = struct.unpack(f"{self.byte_order.value}I", resp[1:5])[0] - return mta_addr + return mta_addr # type: ignore def program_6_bytes(self, data: bytes) -> int: if len(data) != 6: @@ -305,7 +305,7 @@ def program_6_bytes(self, data: bytes) -> int: resp = self._recv_dto(0.1) # mta_addr_ext = resp[0] mta_addr = struct.unpack(f"{self.byte_order.value}I", resp[1:5])[0] - return mta_addr + return mta_addr # type: ignore def move_memory_block(self, size: int) -> None: self._send_cro(COMMAND_CODE.MOVE, struct.pack(f"{self.byte_order.value}I", size)) diff --git a/python/config.py b/python/config.py deleted file mode 100644 index 56940ed720..0000000000 --- a/python/config.py +++ /dev/null @@ -1,19 +0,0 @@ -import os - -BASEDIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../") - -BOOTSTUB_ADDRESS = 0x8000000 - -BLOCK_SIZE_FX = 0x800 -APP_ADDRESS_FX = 0x8004000 -SECTOR_SIZES_FX = [0x4000 for _ in range(4)] + [0x10000] + [0x20000 for _ in range(11)] -DEVICE_SERIAL_NUMBER_ADDR_FX = 0x1FFF79C0 -DEFAULT_FW_FN = os.path.join(BASEDIR, "board", "obj", "panda.bin.signed") -DEFAULT_BOOTSTUB_FN = os.path.join(BASEDIR, "board", "obj", "bootstub.panda.bin") - -BLOCK_SIZE_H7 = 0x400 -APP_ADDRESS_H7 = 0x8020000 -SECTOR_SIZES_H7 = [0x20000 for _ in range(7)] # there is an 8th sector, but we use that for the provisioning chunk, so don't program over that! -DEVICE_SERIAL_NUMBER_ADDR_H7 = 0x080FFFC0 -DEFAULT_H7_FW_FN = os.path.join(BASEDIR, "board", "obj", "panda_h7.bin.signed") -DEFAULT_H7_BOOTSTUB_FN = os.path.join(BASEDIR, "board", "obj", "bootstub.panda_h7.bin") diff --git a/python/constants.py b/python/constants.py new file mode 100644 index 0000000000..c55fd2c9b8 --- /dev/null +++ b/python/constants.py @@ -0,0 +1,57 @@ +import os +import enum +from typing import List, NamedTuple + +BASEDIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../") + + +class McuConfig(NamedTuple): + mcu: str + mcu_idcode: int + uid_address: int + block_size: int + sector_sizes: List[int] + serial_number_address: int + app_address: int + app_path: str + bootstub_address: int + bootstub_path: str + +Fx = ( + 0x1FFF7A10, + 0x800, + [0x4000 for _ in range(4)] + [0x10000] + [0x20000 for _ in range(11)], + 0x1FFF79C0, + 0x8004000, + os.path.join(BASEDIR, "board", "obj", "panda.bin.signed"), + 0x8000000, + os.path.join(BASEDIR, "board", "obj", "bootstub.panda.bin"), +) +F2Config = McuConfig("STM32F2", 0x411, *Fx) +F4Config = McuConfig("STM32F4", 0x463, *Fx) + +H7Config = McuConfig( + "STM32H7", + 0x483, + 0x1FF1E800, + 0x400, + # there is an 8th sector, but we use that for the provisioning chunk, so don't program over that! + [0x20000 for _ in range(7)], + 0x080FFFC0, + 0x8020000, + os.path.join(BASEDIR, "board", "obj", "panda_h7.bin.signed"), + 0x8000000, + os.path.join(BASEDIR, "board", "obj", "bootstub.panda_h7.bin"), +) + +@enum.unique +class McuType(enum.Enum): + F2 = F2Config + F4 = F4Config + H7 = H7Config + + @property + def config(self): + return self.value + +MCU_TYPE_BY_IDCODE = {m.config.mcu_idcode: m for m in McuType} diff --git a/python/dfu.py b/python/dfu.py index 317780ca8d..15e018c5ba 100644 --- a/python/dfu.py +++ b/python/dfu.py @@ -1,22 +1,31 @@ import usb1 import struct import binascii -from .config import BOOTSTUB_ADDRESS, APP_ADDRESS_H7, APP_ADDRESS_FX, BLOCK_SIZE_H7, BLOCK_SIZE_FX, DEFAULT_H7_BOOTSTUB_FN, DEFAULT_BOOTSTUB_FN +from typing import List, Optional +from .base import BaseSTBootloaderHandle +from .spi import STBootloaderSPIHandle, PandaSpiException +from .usb import STBootloaderUSBHandle +from .constants import McuType -MCU_TYPE_F2 = 0 -MCU_TYPE_F4 = 1 -MCU_TYPE_H7 = 2 -# *** DFU mode *** -DFU_DNLOAD = 1 -DFU_UPLOAD = 2 -DFU_GETSTATUS = 3 -DFU_CLRSTATUS = 4 -DFU_ABORT = 6 +class PandaDFU: + def __init__(self, dfu_serial: Optional[str]): + # try USB, then SPI + handle: Optional[BaseSTBootloaderHandle] + handle = PandaDFU.usb_connect(dfu_serial) + if handle is None: + handle = PandaDFU.spi_connect(dfu_serial) -class PandaDFU(object): - def __init__(self, dfu_serial): + if handle is None: + raise Exception(f"failed to open DFU device {dfu_serial}") + + self._handle: BaseSTBootloaderHandle = handle + self._mcu_type: McuType = self._handle.get_mcu_type() + + @staticmethod + def usb_connect(dfu_serial: Optional[str]) -> Optional[STBootloaderUSBHandle]: + handle = None context = usb1.USBContext() for device in context.getDeviceList(skip_on_error=True): if device.getVendorID() == 0x0483 and device.getProductID() == 0xdf11: @@ -24,14 +33,37 @@ def __init__(self, dfu_serial): this_dfu_serial = device.open().getASCIIStringDescriptor(3) except Exception: continue + if this_dfu_serial == dfu_serial or dfu_serial is None: - self._mcu_type = self.get_mcu_type(device) - self._handle = device.open() - return - raise Exception("failed to open " + dfu_serial if dfu_serial is not None else "DFU device") + handle = STBootloaderUSBHandle(device, device.open()) + break + + return handle + + @staticmethod + def spi_connect(dfu_serial: Optional[str]) -> Optional[STBootloaderSPIHandle]: + handle = None + this_dfu_serial = None + + try: + handle = STBootloaderSPIHandle() + this_dfu_serial = PandaDFU.st_serial_to_dfu_serial(handle.get_uid(), handle.get_mcu_type()) + except PandaSpiException: + handle = None + + if dfu_serial is not None and dfu_serial != this_dfu_serial: + handle = None + + return handle + + @staticmethod + def list() -> List[str]: + ret = PandaDFU.usb_list() + ret += PandaDFU.spi_list() + return list(set(ret)) @staticmethod - def list(): + def usb_list() -> List[str]: context = usb1.USBContext() dfu_serials = [] try: @@ -46,80 +78,41 @@ def list(): return dfu_serials @staticmethod - def st_serial_to_dfu_serial(st, mcu_type=MCU_TYPE_F4): + def spi_list() -> List[str]: + try: + h = PandaDFU.spi_connect(None) + if h is not None: + dfu_serial = PandaDFU.st_serial_to_dfu_serial(h.get_uid(), h.get_mcu_type()) + return [dfu_serial, ] + except PandaSpiException: + pass + return [] + + @staticmethod + def st_serial_to_dfu_serial(st: str, mcu_type: McuType = McuType.F4): if st is None or st == "none": return None uid_base = struct.unpack("H" * 6, bytes.fromhex(st)) - if mcu_type == MCU_TYPE_H7: + if mcu_type == McuType.H7: return binascii.hexlify(struct.pack("!HHH", uid_base[1] + uid_base[5], uid_base[0] + uid_base[4], uid_base[3])).upper().decode("utf-8") else: return binascii.hexlify(struct.pack("!HHH", uid_base[1] + uid_base[5], uid_base[0] + uid_base[4] + 0xA, uid_base[3])).upper().decode("utf-8") - # TODO: Find a way to detect F4 vs F2 - def get_mcu_type(self, dev): - return MCU_TYPE_H7 if dev.getbcdDevice() == 512 else MCU_TYPE_F4 - - def status(self): - while 1: - dat = self._handle.controlRead(0x21, DFU_GETSTATUS, 0, 0, 6) - if dat[1] == 0: - break - - def clear_status(self): - # Clear status - stat = self._handle.controlRead(0x21, DFU_GETSTATUS, 0, 0, 6) - if stat[4] == 0xa: - self._handle.controlRead(0x21, DFU_CLRSTATUS, 0, 0, 0) - elif stat[4] == 0x9: - self._handle.controlWrite(0x21, DFU_ABORT, 0, 0, b"") - self.status() - stat = str(self._handle.controlRead(0x21, DFU_GETSTATUS, 0, 0, 6)) - - def erase(self, address): - self._handle.controlWrite(0x21, DFU_DNLOAD, 0, 0, b"\x41" + struct.pack("I", address)) - self.status() - - def program(self, address, dat, block_size=None): - if block_size is None: - block_size = len(dat) - - # Set Address Pointer - self._handle.controlWrite(0x21, DFU_DNLOAD, 0, 0, b"\x21" + struct.pack("I", address)) - self.status() - - # Program - dat += b"\xFF" * ((block_size - len(dat)) % block_size) - for i in range(0, len(dat) // block_size): - ldat = dat[i * block_size:(i + 1) * block_size] - print("programming %d with length %d" % (i, len(ldat))) - self._handle.controlWrite(0x21, DFU_DNLOAD, 2 + i, 0, ldat) - self.status() + def get_mcu_type(self) -> McuType: + return self._mcu_type + + def reset(self): + self._handle.jump(self._mcu_type.config.bootstub_address) def program_bootstub(self, code_bootstub): - self.clear_status() - self.erase(BOOTSTUB_ADDRESS) - if self._mcu_type == MCU_TYPE_H7: - self.erase(APP_ADDRESS_H7) - self.program(BOOTSTUB_ADDRESS, code_bootstub, BLOCK_SIZE_H7) - else: - self.erase(APP_ADDRESS_FX) - self.program(BOOTSTUB_ADDRESS, code_bootstub, BLOCK_SIZE_FX) + self._handle.clear_status() + self._handle.erase_bootstub() + self._handle.erase_app() + self._handle.program(self._mcu_type.config.bootstub_address, code_bootstub) self.reset() def recover(self): - fn = DEFAULT_H7_BOOTSTUB_FN if self._mcu_type == MCU_TYPE_H7 else DEFAULT_BOOTSTUB_FN - - with open(fn, "rb") as f: + with open(self._mcu_type.config.bootstub_path, "rb") as f: code = f.read() - self.program_bootstub(code) - def reset(self): - # **** Reset **** - self._handle.controlWrite(0x21, DFU_DNLOAD, 0, 0, b"\x21" + struct.pack("I", BOOTSTUB_ADDRESS)) - self.status() - try: - self._handle.controlWrite(0x21, DFU_DNLOAD, 2, 0, b"") - _ = str(self._handle.controlRead(0x21, DFU_GETSTATUS, 0, 0, 6)) - except Exception: - pass diff --git a/python/flash_release.py b/python/flash_release.py deleted file mode 100755 index ae50e6dc02..0000000000 --- a/python/flash_release.py +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/env python3 - -import sys -import time -import requests -import json -import io - -def flash_release(path=None, st_serial=None): - from panda import Panda, PandaDFU - from zipfile import ZipFile - - def status(x): - print("\033[1;32;40m" + x + "\033[00m") - - if st_serial is not None: - # look for Panda - panda_list = Panda.list() - if len(panda_list) == 0: - raise Exception("panda not found, make sure it's connected and your user can access it") - elif len(panda_list) > 1: - raise Exception("Please only connect one panda") - st_serial = panda_list[0] - print("Using panda with serial %s" % st_serial) - - if path is None: - print("Fetching latest firmware from github.com/commaai/panda-artifacts") - r = requests.get("https://raw.githubusercontent.com/commaai/panda-artifacts/master/latest.json") - url = json.loads(r.text)['url'] - r = requests.get(url) - print("Fetching firmware from %s" % url) - path = io.BytesIO(r.content) - - zf = ZipFile(path) - zf.printdir() - - version = zf.read("version").decode().strip() - status("0. Preparing to flash " + str(version)) - - code_bootstub = zf.read("bootstub.panda.bin") - code_panda = zf.read("panda.bin") - - # enter DFU mode - status("1. Entering DFU mode") - panda = Panda(st_serial) - panda.reset(enter_bootstub=True) - panda.reset(enter_bootloader=True) - time.sleep(1) - - # program bootstub - status("2. Programming bootstub") - dfu = PandaDFU(PandaDFU.st_serial_to_dfu_serial(st_serial)) - dfu.program_bootstub(code_bootstub) - time.sleep(1) - - # flash main code - status("3. Flashing main code") - panda = Panda(st_serial) - panda.flash(code=code_panda) - panda.close() - - # check for connection - status("4. Verifying version") - panda = Panda(st_serial) - my_version = panda.get_version() - print("dongle id: %s" % panda.get_serial()[0]) - print(my_version, "should be", version) - assert(str(version) == str(my_version)) - - # done! - status("6. Success!") - -if __name__ == "__main__": - flash_release(*sys.argv[1:]) diff --git a/python/spi.py b/python/spi.py new file mode 100644 index 0000000000..0ff15c3bc3 --- /dev/null +++ b/python/spi.py @@ -0,0 +1,313 @@ +import binascii +import os +import fcntl +import math +import time +import struct +import logging +import threading +from contextlib import contextmanager +from functools import reduce +from typing import List, Optional + +from .base import BaseHandle, BaseSTBootloaderHandle, TIMEOUT +from .constants import McuType, MCU_TYPE_BY_IDCODE + +try: + import spidev +except ImportError: + spidev = None + +# Constants +SYNC = 0x5A +HACK = 0x79 +DACK = 0x85 +NACK = 0x1F +CHECKSUM_START = 0xAB + +MIN_ACK_TIMEOUT_MS = 100 +MAX_XFER_RETRY_COUNT = 5 + +USB_MAX_SIZE = 0x40 + +DEV_PATH = "/dev/spidev0.0" + + +class PandaSpiException(Exception): + pass + +class PandaSpiUnavailable(PandaSpiException): + pass + +class PandaSpiNackResponse(PandaSpiException): + pass + +class PandaSpiMissingAck(PandaSpiException): + pass + +class PandaSpiBadChecksum(PandaSpiException): + pass + +class PandaSpiTransferFailed(PandaSpiException): + pass + + +SPI_LOCK = threading.Lock() + +class SpiDevice: + """ + Provides locked, thread-safe access to a panda's SPI interface. + """ + def __init__(self, speed=30000000): + if not os.path.exists(DEV_PATH): + raise PandaSpiUnavailable(f"SPI device not found: {DEV_PATH}") + if spidev is None: + raise PandaSpiUnavailable("spidev is not installed") + + self._spidev = spidev.SpiDev() # pylint: disable=c-extension-no-member + self._spidev.open(0, 0) + self._spidev.max_speed_hz = speed + + @contextmanager + def acquire(self): + try: + SPI_LOCK.acquire() + fcntl.flock(self._spidev, fcntl.LOCK_EX) + yield self._spidev + finally: + fcntl.flock(self._spidev, fcntl.LOCK_UN) + SPI_LOCK.release() + + def close(self): + self._spidev.close() + + +class PandaSpiHandle(BaseHandle): + """ + A class that mimics a libusb1 handle for panda SPI communications. + """ + def __init__(self): + self.dev = SpiDevice() + + # helpers + def _calc_checksum(self, data: List[int]) -> int: + cksum = CHECKSUM_START + for b in data: + cksum ^= b + return cksum + + def _wait_for_ack(self, spi, ack_val: int, timeout: int) -> None: + timeout_s = max(MIN_ACK_TIMEOUT_MS, timeout) * 1e-3 + + start = time.monotonic() + while (timeout == 0) or ((time.monotonic() - start) < timeout_s): + dat = spi.xfer2(b"\x12")[0] + if dat == NACK: + raise PandaSpiNackResponse + elif dat == ack_val: + return + + raise PandaSpiMissingAck + + def _transfer(self, spi, endpoint: int, data, timeout: int, max_rx_len: int = 1000) -> bytes: + logging.debug("starting transfer: endpoint=%d, max_rx_len=%d", endpoint, max_rx_len) + logging.debug("==============================================") + + exc = PandaSpiException() + for n in range(MAX_XFER_RETRY_COUNT): + logging.debug("\ntry #%d", n+1) + try: + logging.debug("- send header") + packet = struct.pack(" max_rx_len: + raise PandaSpiException("response length greater than max") + + logging.debug("- receiving response") + dat = bytes(spi.xfer2(b"\x00" * (response_len + 1))) + if self._calc_checksum([DACK, *response_len_bytes, *dat]) != 0: + raise PandaSpiBadChecksum + + return dat[:-1] + except PandaSpiException as e: + exc = e + logging.debug("SPI transfer failed, %d retries left", n, exc_info=True) + raise exc + + # libusb1 functions + def close(self): + self.dev.close() + + def controlWrite(self, request_type: int, request: int, value: int, index: int, data, timeout: int = TIMEOUT): + with self.dev.acquire() as spi: + return self._transfer(spi, 0, struct.pack(" int: + with self.dev.acquire() as spi: + for x in range(math.ceil(len(data) / USB_MAX_SIZE)): + self._transfer(spi, endpoint, data[USB_MAX_SIZE*x:USB_MAX_SIZE*(x+1)], timeout) + return len(data) + + def bulkRead(self, endpoint: int, length: int, timeout: int = TIMEOUT) -> bytes: + ret: List[int] = [] + with self.dev.acquire() as spi: + for _ in range(math.ceil(length / USB_MAX_SIZE)): + d = self._transfer(spi, endpoint, [], timeout, max_rx_len=USB_MAX_SIZE) + ret += d + if len(d) < USB_MAX_SIZE: + break + return bytes(ret) + + +class STBootloaderSPIHandle(BaseSTBootloaderHandle): + """ + Implementation of the STM32 SPI bootloader protocol described in: + https://www.st.com/resource/en/application_note/an4286-spi-protocol-used-in-the-stm32-bootloader-stmicroelectronics.pdf + """ + + SYNC = 0x5A + ACK = 0x79 + NACK = 0x1F + + def __init__(self): + self.dev = SpiDevice(speed=1000000) + + # say hello + try: + with self.dev.acquire() as spi: + spi.xfer([self.SYNC, ]) + try: + self._get_ack(spi) + except PandaSpiNackResponse: + # NACK ok here, will only ACK the first time + pass + + self._mcu_type = MCU_TYPE_BY_IDCODE[self.get_chip_id()] + except PandaSpiException: + raise PandaSpiException("failed to connect to panda") # pylint: disable=W0707 + + def _get_ack(self, spi, timeout=1.0): + data = 0x00 + start_time = time.monotonic() + while data not in (self.ACK, self.NACK) and (time.monotonic() - start_time < timeout): + data = spi.xfer([0x00, ])[0] + time.sleep(0.001) + spi.xfer([self.ACK, ]) + + if data == self.NACK: + raise PandaSpiNackResponse + elif data != self.ACK: + raise PandaSpiMissingAck + + def _cmd(self, cmd: int, data: Optional[List[bytes]] = None, read_bytes: int = 0, predata=None) -> bytes: + ret = b"" + with self.dev.acquire() as spi: + # sync + command + spi.xfer([self.SYNC, ]) + spi.xfer([cmd, cmd ^ 0xFF]) + self._get_ack(spi) + + # "predata" - for commands that send the first data without a checksum + if predata is not None: + spi.xfer(predata) + self._get_ack(spi) + + # send data + if data is not None: + for d in data: + if predata is not None: + spi.xfer(d + self._checksum(predata + d)) + else: + spi.xfer(d + self._checksum(d)) + self._get_ack(spi, timeout=20) + + # receive + if read_bytes > 0: + ret = spi.xfer([0x00, ]*(read_bytes + 1))[1:] + if data is None or len(data) == 0: + self._get_ack(spi) + + return bytes(ret) + + def _checksum(self, data: bytes) -> bytes: + if len(data) == 1: + ret = data[0] ^ 0xFF + else: + ret = reduce(lambda a, b: a ^ b, data) + return bytes([ret, ]) + + # *** Bootloader commands *** + + def read(self, address: int, length: int): + data = [struct.pack('>I', address), struct.pack('B', length - 1)] + return self._cmd(0x11, data=data, read_bytes=length) + + def get_chip_id(self) -> int: + r = self._cmd(0x02, read_bytes=3) + assert r[0] == 1 # response length - 1 + return ((r[1] << 8) + r[2]) + + def go_cmd(self, address: int) -> None: + self._cmd(0x21, data=[struct.pack('>I', address), ]) + + # *** helpers *** + + def get_uid(self): + dat = self.read(McuType.H7.config.uid_address, 12) + return binascii.hexlify(dat).decode() + + def erase_sector(self, sector: int): + p = struct.pack('>H', 0) # number of sectors to erase + d = struct.pack('>H', sector) + self._cmd(0x44, data=[d, ], predata=p) + + # *** PandaDFU API *** + + def erase_app(self): + self.erase_sector(1) + + def erase_bootstub(self): + self.erase_sector(0) + + def get_mcu_type(self): + return self._mcu_type + + def clear_status(self): + pass + + def close(self): + self.dev.close() + + def program(self, address, dat): + bs = 256 # max block size for writing to flash over SPI + dat += b"\xFF" * ((bs - len(dat)) % bs) + for i in range(0, len(dat) // bs): + block = dat[i * bs:(i + 1) * bs] + self._cmd(0x31, data=[ + struct.pack('>I', address + i*bs), + bytes([len(block) - 1]) + block, + ]) + + def jump(self, address): + self.go_cmd(self._mcu_type.config.bootstub_address) diff --git a/python/update.py b/python/update.py deleted file mode 100755 index f8e3280444..0000000000 --- a/python/update.py +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env python3 -import os -import time - -def ensure_st_up_to_date(): - from panda import Panda, PandaDFU, BASEDIR - - with open(os.path.join(BASEDIR, "VERSION")) as f: - repo_version = f.read() - - repo_version += "-EON" if os.path.isfile('/EON') else "-DEV" - - panda = None - panda_dfu = None - - while 1: - # break on normal mode Panda - panda_list = Panda.list() - if len(panda_list) > 0: - panda = Panda(panda_list[0]) - break - - # flash on DFU mode Panda - panda_dfu = PandaDFU.list() - if len(panda_dfu) > 0: - panda_dfu = PandaDFU(panda_dfu[0]) - panda_dfu.recover() - - print("waiting for board...") - time.sleep(1) - - if panda.bootstub or not panda.get_version().startswith(repo_version): - panda.flash() - - if panda.bootstub: - panda.recover() - - assert(not panda.bootstub) - version = str(panda.get_version()) - print("%s should be %s" % (version, repo_version)) - assert(version.startswith(repo_version)) - -if __name__ == "__main__": - ensure_st_up_to_date() diff --git a/python/usb.py b/python/usb.py new file mode 100644 index 0000000000..2e236a9999 --- /dev/null +++ b/python/usb.py @@ -0,0 +1,95 @@ +import struct +from typing import List + +from .base import BaseHandle, BaseSTBootloaderHandle, TIMEOUT +from .constants import McuType + +class PandaUsbHandle(BaseHandle): + def __init__(self, libusb_handle): + self._libusb_handle = libusb_handle + + def close(self): + self._libusb_handle.close() + + def controlWrite(self, request_type: int, request: int, value: int, index: int, data, timeout: int = TIMEOUT): + return self._libusb_handle.controlWrite(request_type, request, value, index, data, timeout) + + def controlRead(self, request_type: int, request: int, value: int, index: int, length: int, timeout: int = TIMEOUT): + return self._libusb_handle.controlRead(request_type, request, value, index, length, timeout) + + def bulkWrite(self, endpoint: int, data: List[int], timeout: int = TIMEOUT) -> int: + return self._libusb_handle.bulkWrite(endpoint, data, timeout) # type: ignore + + def bulkRead(self, endpoint: int, length: int, timeout: int = TIMEOUT) -> bytes: + return self._libusb_handle.bulkRead(endpoint, length, timeout) # type: ignore + + + +class STBootloaderUSBHandle(BaseSTBootloaderHandle): + DFU_DNLOAD = 1 + DFU_UPLOAD = 2 + DFU_GETSTATUS = 3 + DFU_CLRSTATUS = 4 + DFU_ABORT = 6 + + def __init__(self, libusb_device, libusb_handle): + self._libusb_handle = libusb_handle + + # TODO: Find a way to detect F4 vs F2 + # TODO: also check F4 BCD, don't assume in else + self._mcu_type = McuType.H7 if libusb_device.getbcdDevice() == 512 else McuType.F4 + + def _status(self) -> None: + while 1: + dat = self._libusb_handle.controlRead(0x21, self.DFU_GETSTATUS, 0, 0, 6) + if dat[1] == 0: + break + + def _erase_page_address(self, address: int) -> None: + self._libusb_handle.controlWrite(0x21, self.DFU_DNLOAD, 0, 0, b"\x41" + struct.pack("I", address)) + self._status() + + def get_mcu_type(self): + return self._mcu_type + + def erase_app(self): + self._erase_page_address(self._mcu_type.config.app_address) + + def erase_bootstub(self): + self._erase_page_address(self._mcu_type.config.bootstub_address) + + def clear_status(self): + # Clear status + stat = self._libusb_handle.controlRead(0x21, self.DFU_GETSTATUS, 0, 0, 6) + if stat[4] == 0xa: + self._libusb_handle.controlRead(0x21, self.DFU_CLRSTATUS, 0, 0, 0) + elif stat[4] == 0x9: + self._libusb_handle.controlWrite(0x21, self.DFU_ABORT, 0, 0, b"") + self._status() + stat = str(self._libusb_handle.controlRead(0x21, self.DFU_GETSTATUS, 0, 0, 6)) + + def close(self): + self._libusb_handle.close() + + def program(self, address, dat): + # Set Address Pointer + self._libusb_handle.controlWrite(0x21, self.DFU_DNLOAD, 0, 0, b"\x21" + struct.pack("I", address)) + self._status() + + # Program + bs = self._mcu_type.config.block_size + dat += b"\xFF" * ((bs - len(dat)) % bs) + for i in range(0, len(dat) // bs): + ldat = dat[i * bs:(i + 1) * bs] + print("programming %d with length %d" % (i, len(ldat))) + self._libusb_handle.controlWrite(0x21, self.DFU_DNLOAD, 2 + i, 0, ldat) + self._status() + + def jump(self, address): + self._libusb_handle.controlWrite(0x21, self.DFU_DNLOAD, 0, 0, b"\x21" + struct.pack("I", address)) + self._status() + try: + self._libusb_handle.controlWrite(0x21, self.DFU_DNLOAD, 2, 0, b"") + _ = str(self._libusb_handle.controlRead(0x21, self.DFU_GETSTATUS, 0, 0, 6)) + except Exception: + pass diff --git a/requirements.txt b/requirements.txt index fdf4e48a62..600be4b2bf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,5 +11,7 @@ flake8==3.7.9 cffi==1.14.3 crcmod pre-commit==2.13.0 -pylint==2.5.2 -scons==4.1.0.post1 +pylint==2.15.4 +scons==4.4.0 +flaky +spidev diff --git a/tests/SConscript b/tests/SConscript new file mode 100644 index 0000000000..86e9f933d4 --- /dev/null +++ b/tests/SConscript @@ -0,0 +1,13 @@ +env = Environment( + CC='gcc', + CFLAGS=[ + '-nostdlib', + '-fno-builtin', + '-std=gnu11', + ], + CPPPATH=[".", "../board"], +) + +env.PrependENVPath('PATH', '/opt/homebrew/bin') +env.SharedLibrary("safety/libpandasafety.so", ["safety/test.c"]) +env.SharedLibrary("usbprotocol/libpandaprotocol.so", ["usbprotocol/test.c"]) diff --git a/tests/automated/1_program.py b/tests/automated/1_program.py deleted file mode 100644 index a81a0b8832..0000000000 --- a/tests/automated/1_program.py +++ /dev/null @@ -1,59 +0,0 @@ -import os -import time - -from panda import Panda, PandaDFU, MCU_TYPE_H7, BASEDIR -from .helpers import test_all_pandas, panda_connect_and_init, check_signature - - -@test_all_pandas -@panda_connect_and_init(full_reset=False) -def test_a_known_bootstub(p): - # Test that compiled app can work with known production bootstub - KNOWN_H7_BOOTSTUB_FN = os.path.join(BASEDIR, "tests", "automated", "known_bootstub", "bootstub.panda_h7.bin") - KNOWN_BOOTSTUB_FN = os.path.join(BASEDIR, "tests", "automated", "known_bootstub", "bootstub.panda.bin") - - p.reset(enter_bootstub=True) - p.reset(enter_bootloader=True) - - dfu_serial = PandaDFU.st_serial_to_dfu_serial(p._serial, p._mcu_type) - assert Panda.wait_for_dfu(dfu_serial, timeout=30) - - dfu = PandaDFU(dfu_serial) - fn = KNOWN_H7_BOOTSTUB_FN if p._mcu_type == MCU_TYPE_H7 else KNOWN_BOOTSTUB_FN - with open(fn, "rb") as f: - code = f.read() - - dfu.program_bootstub(code) - p.connect(True, True) - p.flash() - check_signature(p) - -@test_all_pandas -@panda_connect_and_init(full_reset=False) -def test_b_recover(p): - assert p.recover(timeout=30) - check_signature(p) - -@test_all_pandas -@panda_connect_and_init(full_reset=False) -def test_c_flash(p): - # test flash from bootstub - serial = p._serial - assert serial != None - p.reset(enter_bootstub=True) - p.close() - time.sleep(2) - - np = Panda(serial) - assert np.bootstub - assert np._serial == serial - np.flash() - np.close() - - p.reconnect() - p.reset() - check_signature(p) - - # test flash from app - p.flash() - check_signature(p) diff --git a/tests/automated/known_bootstub/bootstub.panda.bin b/tests/automated/known_bootstub/bootstub.panda.bin deleted file mode 100755 index 6f658e2de2..0000000000 Binary files a/tests/automated/known_bootstub/bootstub.panda.bin and /dev/null differ diff --git a/tests/black_white_loopback_test.py b/tests/black_white_loopback_test.py index 9ef9f06551..f5c6170fe4 100755 --- a/tests/black_white_loopback_test.py +++ b/tests/black_white_loopback_test.py @@ -23,7 +23,7 @@ def get_test_string(): content_errors = 0 def run_test(sleep_duration): - global counter, nonzero_bus_errors, zero_bus_errors, content_errors + global counter pandas = Panda.list() print(pandas) diff --git a/tests/black_white_relay_endurance.py b/tests/black_white_relay_endurance.py index 32c77f2edb..f44eafd207 100755 --- a/tests/black_white_relay_endurance.py +++ b/tests/black_white_relay_endurance.py @@ -23,7 +23,7 @@ def get_test_string(): content_errors = 0 def run_test(sleep_duration): - global counter, nonzero_bus_errors, zero_bus_errors, content_errors + global counter pandas = Panda.list() print(pandas) diff --git a/tests/black_white_relay_test.py b/tests/black_white_relay_test.py index 3480d0e47b..4de02ec849 100755 --- a/tests/black_white_relay_test.py +++ b/tests/black_white_relay_test.py @@ -22,7 +22,7 @@ def get_test_string(): content_errors = 0 def run_test(sleep_duration): - global counter, open_errors, closed_errors, content_errors + global counter, open_errors, closed_errors pandas = Panda.list() print(pandas) diff --git a/tests/bulk_write_test.py b/tests/bulk_write_test.py index 708258ba66..047e870ce8 100755 --- a/tests/bulk_write_test.py +++ b/tests/bulk_write_test.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +# type: ignore # for jungle stuff import os import time import threading diff --git a/tests/canfd/test_canfd.py b/tests/canfd/test_canfd.py index 4c6cefd206..5a1511ef45 100755 --- a/tests/canfd/test_canfd.py +++ b/tests/canfd/test_canfd.py @@ -18,25 +18,22 @@ def panda_reset(): panda_serials = [] - if JUNGLE_SERIAL: - panda_jungle = PandaJungle(JUNGLE_SERIAL) - panda_jungle.set_panda_power(False) - time.sleep(2) - panda_jungle.set_panda_power(True) - time.sleep(4) + panda_jungle = PandaJungle(JUNGLE_SERIAL) + panda_jungle.set_panda_power(False) + time.sleep(1) + panda_jungle.set_panda_power(True) + time.sleep(4) for serial in Panda.list(): if serial not in H7_PANDAS_EXCLUDE: - p = Panda(serial=serial) - if p.get_type() in H7_HW_TYPES: - assert p.recover(timeout=30) - panda_serials.append(serial) - p.close() - - if len(panda_serials) < 2: - print("Minimum two H7 type pandas should be connected.") - assert False - + with Panda(serial=serial) as p: + if p.get_type() in H7_HW_TYPES: + p.reset() + panda_serials.append(serial) + + print("test pandas", panda_serials) + assert len(panda_serials) == 2, "Two H7 pandas required" + return panda_serials def panda_init(serial, enable_canfd=False, enable_non_iso=False): @@ -51,7 +48,7 @@ def panda_init(serial, enable_canfd=False, enable_non_iso=False): return p def canfd_test(p_send, p_recv): - for _ in range(100): + for n in range(100): sent_msgs = defaultdict(set) to_send = [] for _ in range(200): @@ -64,8 +61,8 @@ def canfd_test(p_send, p_recv): p_send.can_send_many(to_send, timeout=0) - start_time = time.time() - while time.time() - start_time < 1: + start_time = time.monotonic() + while (time.monotonic() - start_time < 1) and any(len(x) > 0 for x in sent_msgs.values()): incoming = p_recv.can_recv() for msg in incoming: address, _, data, bus = msg @@ -74,15 +71,8 @@ def canfd_test(p_send, p_recv): sent_msgs[bus].discard(k) for bus in range(3): - assert not len(sent_msgs[bus]), f"loop : bus {bus} missing {len(sent_msgs[bus])} messages" - - # Set back to silent mode - p_send.set_safety_mode(Panda.SAFETY_SILENT) - p_recv.set_safety_mode(Panda.SAFETY_SILENT) - p_send.set_power_save(True) - p_recv.set_power_save(True) - p_send.close() - p_recv.close() + assert not len(sent_msgs[bus]), f"loop {n}: bus {bus} missing {len(sent_msgs[bus])} messages" + print("Got all messages intact") diff --git a/tests/ci_reset_hw.py b/tests/ci_reset_hw.py old mode 100644 new mode 100755 index bee012566e..6a5427318f --- a/tests/ci_reset_hw.py +++ b/tests/ci_reset_hw.py @@ -1,35 +1,40 @@ +#!/usr/bin/env python3 +import concurrent.futures + from panda import Panda, PandaDFU from panda.tests.libs.resetter import Resetter - -# resets power for both jungles(ports 1 and 2) and USB hubs(port 3) -# puts pandas into DFU mode and flashes bootstub + app +# Reset + flash all CI hardware to get it into a consistent state +# * ports 1-2 are jungles +# * port 3 is for the USB hubs if __name__ == "__main__": r = Resetter() r.enable_boot(True) - r.cycle_power(delay=5, ports=[1,2,3]) + r.cycle_power(delay=7, ports=[1, 2, 3]) r.enable_boot(False) pandas = PandaDFU.list() - print(pandas) - assert len(pandas) == 8 + print("DFU pandas:", pandas) + assert len(pandas) == 7 - for serial in pandas: - p = PandaDFU(serial) - p.recover() + with concurrent.futures.ProcessPoolExecutor(max_workers=len(pandas)) as exc: + def recover(serial): + PandaDFU(serial).recover() + list(exc.map(recover, pandas, timeout=20)) - r.cycle_power(delay=5, ports=[1,2]) + r.cycle_power(delay=7, ports=[1, 2]) pandas = Panda.list() print(pandas) - assert len(pandas) == 8 + assert len(pandas) == 7 - for serial in pandas: - pf = Panda(serial) - if pf.bootstub: - pf.flash() - pf.close() + with concurrent.futures.ProcessPoolExecutor(max_workers=len(pandas)) as exc: + def flash(serial): + with Panda(serial) as pf: + if pf.bootstub: + pf.flash() + list(exc.map(flash, pandas, timeout=20)) - r.cycle_power(delay=0, ports=[1,2]) + r.cycle_power(delay=0, ports=[1, 2]) r.close() diff --git a/tests/ci_shell.sh b/tests/ci_shell.sh index f0e2dc1230..92c0f96e8a 100755 --- a/tests/ci_shell.sh +++ b/tests/ci_shell.sh @@ -2,11 +2,12 @@ DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" OP_ROOT="$DIR/../../" +PANDA_ROOT="$DIR/../" if [ -z "$BUILD" ]; then docker pull docker.io/commaai/panda:latest else - docker build --cache-from docker.io/commaai/panda:latest -t docker.io/commaai/panda:latest -f $OP_ROOT/Dockerfile.openpilot_base . + docker build --cache-from docker.io/commaai/panda:latest -t docker.io/commaai/panda:latest -f $PANDA_ROOT/Dockerfile $PANDA_ROOT fi docker run \ diff --git a/tests/debug_console.py b/tests/debug_console.py index d0c9628aaf..a74cd846e8 100755 --- a/tests/debug_console.py +++ b/tests/debug_console.py @@ -4,6 +4,7 @@ import sys import time import select +import codecs sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")) from panda import Panda # noqa: E402 @@ -11,20 +12,27 @@ setcolor = ["\033[1;32;40m", "\033[1;31;40m"] unsetcolor = "\033[00m" +port_number = int(os.getenv("PORT", "0")) +claim = os.getenv("CLAIM") is not None +no_color = os.getenv("NO_COLOR") is not None +no_reconnect = os.getenv("NO_RECONNECT") is not None + if __name__ == "__main__": while True: try: - port_number = int(os.getenv("PORT", "0")) - claim = os.getenv("CLAIM") is not None - serials = Panda.list() if os.getenv("SERIAL"): serials = [x for x in serials if x == os.getenv("SERIAL")] pandas = list([Panda(x, claim=claim) for x in serials]) + decoders = [codecs.getincrementaldecoder('utf-8')() for _ in pandas] if not len(pandas): - sys.exit("no pandas found") + print("no pandas found") + if no_reconnect: + sys.exit(0) + time.sleep(1) + continue if os.getenv("BAUD") is not None: for panda in pandas: @@ -35,7 +43,11 @@ while True: ret = panda.serial_read(port_number) if len(ret) > 0: - sys.stdout.write(setcolor[i] + ret.decode('ascii') + unsetcolor) + decoded = decoders[i].decode(ret) + if no_color: + sys.stdout.write(decoded) + else: + sys.stdout.write(setcolor[i] + decoded + unsetcolor) sys.stdout.flush() else: break diff --git a/tests/gmbitbang/recv.py b/tests/gmbitbang/recv.py index 3949c424d9..73285e7e59 100755 --- a/tests/gmbitbang/recv.py +++ b/tests/gmbitbang/recv.py @@ -1,16 +1,19 @@ #!/usr/bin/env python3 +from typing import Optional + from panda import Panda -p = Panda() -p.set_safety_mode(Panda.SAFETY_ALLOUTPUT) -p.set_gmlan(bus=2) -#p.can_send(0xaaa, b"\x00\x00", bus=3) -last_add = None -while 1: - ret = p.can_recv() - if len(ret) > 0: - add = ret[0][0] - if last_add is not None and add != last_add + 1: - print("MISS: ", last_add, add) - last_add = add - print(ret) +if __name__ == "__main__": + p = Panda() + p.set_safety_mode(Panda.SAFETY_ALLOUTPUT) + p.set_gmlan(bus=2) + #p.can_send(0xaaa, b"\x00\x00", bus=3) + last_add: Optional[int] = None + while True: + ret = p.can_recv() + if len(ret) > 0: + add = ret[0][0] + if last_add is not None and add != last_add + 1: + print("MISS: ", last_add, add) + last_add = add + print(ret) diff --git a/tests/health_test.py b/tests/health_test.py index 86e56f945e..1195c2d7f2 100755 --- a/tests/health_test.py +++ b/tests/health_test.py @@ -3,16 +3,16 @@ from panda import Panda if __name__ == "__main__": - panda_serials = Panda.list() - pandas = [] - for ps in panda_serials: - pandas.append(Panda(serial=ps)) - if len(pandas) == 0: - print("No pandas connected") - assert False + i = 0 + pi = 0 + panda = Panda() while True: - for panda in pandas: - print(panda.health()) - print("\n") - time.sleep(0.5) + st = time.monotonic() + while time.monotonic() - st < 1: + panda.health() + i += 1 + print(i, panda.health(), "\n") + print(f"Speed: {i - pi}Hz") + pi = i + diff --git a/tests/hitl/0_dfu.py b/tests/hitl/0_dfu.py new file mode 100644 index 0000000000..ff697ba545 --- /dev/null +++ b/tests/hitl/0_dfu.py @@ -0,0 +1,21 @@ +from panda import Panda, PandaDFU +from .helpers import test_all_pandas, panda_connect_and_init + +@test_all_pandas +@panda_connect_and_init +def test_dfu(p): + app_mcu_type = p.get_mcu_type() + dfu_serial = PandaDFU.st_serial_to_dfu_serial(p.get_usb_serial(), p.get_mcu_type()) + + p.reset(enter_bootstub=True) + p.reset(enter_bootloader=True) + assert Panda.wait_for_dfu(dfu_serial, timeout=20), "failed to enter DFU" + + dfu = PandaDFU(dfu_serial) + assert dfu.get_mcu_type() == app_mcu_type + + assert dfu_serial in PandaDFU.list() + + dfu._handle.clear_status() + dfu.reset() + p.reconnect() diff --git a/tests/hitl/1_program.py b/tests/hitl/1_program.py new file mode 100644 index 0000000000..c129c99b5c --- /dev/null +++ b/tests/hitl/1_program.py @@ -0,0 +1,83 @@ +import os +import time + +from panda import Panda, PandaDFU, McuType, BASEDIR +from .helpers import test_all_pandas, panda_connect_and_init, check_signature + + +# TODO: make more comprehensive bootstub tests and run on a few production ones + current +# TODO: also test release-signed app +@test_all_pandas +@panda_connect_and_init +def test_a_known_bootstub(p): + """ + Test that compiled app can work with known production bootstub + """ + known_bootstubs = { + # covers the two cases listed in Panda.connect + McuType.F4: [ + # case A - no bcdDevice or panda type, has to assume F4 + "bootstub_f4_first_dos_production.panda.bin", + + # case B - just bcdDevice + "bootstub_f4_only_bcd.panda.bin", + ], + McuType.H7: ["bootstub.panda_h7.bin"], + } + + for kb in known_bootstubs[p.get_mcu_type()]: + app_ids = (p.get_mcu_type(), p.get_usb_serial()) + assert None not in app_ids + + p.reset(enter_bootstub=True) + p.reset(enter_bootloader=True) + + dfu_serial = PandaDFU.st_serial_to_dfu_serial(p._serial, p._mcu_type) + assert Panda.wait_for_dfu(dfu_serial, timeout=30) + + dfu = PandaDFU(dfu_serial) + with open(os.path.join(BASEDIR, "tests/hitl/known_bootstub", kb), "rb") as f: + code = f.read() + + dfu.program_bootstub(code) + + p.connect(claim=False, wait=True) + + # check for MCU or serial mismatch + with Panda(p._serial, claim=False) as np: + bootstub_ids = (np.get_mcu_type(), np.get_usb_serial()) + assert app_ids == bootstub_ids + + # ensure we can flash app and it jumps to app + p.flash() + check_signature(p) + assert not p.bootstub + +@test_all_pandas +@panda_connect_and_init +def test_b_recover(p): + assert p.recover(timeout=30) + check_signature(p) + +@test_all_pandas +@panda_connect_and_init +def test_c_flash(p): + # test flash from bootstub + serial = p._serial + assert serial is not None + p.reset(enter_bootstub=True) + p.close() + time.sleep(2) + + with Panda(serial) as np: + assert np.bootstub + assert np._serial == serial + np.flash() + + p.reconnect() + p.reset() + check_signature(p) + + # test flash from app + p.flash() + check_signature(p) diff --git a/tests/automated/2_health.py b/tests/hitl/2_health.py similarity index 72% rename from tests/automated/2_health.py rename to tests/hitl/2_health.py index d77449af08..3a7f1e74a2 100644 --- a/tests/automated/2_health.py +++ b/tests/hitl/2_health.py @@ -35,11 +35,11 @@ def test_orientation_detection(p): seen_orientations.append(detected_harness_orientation) @test_all_pandas -@panda_connect_and_init(full_reset=False) +@panda_connect_and_init def test_voltage(p): for _ in range(10): voltage = p.health()['voltage'] - assert ((voltage > 10000) and (voltage < 14000)) + assert ((voltage > 11000) and (voltage < 13000)) time.sleep(0.1) @test_all_pandas @@ -53,14 +53,18 @@ def test_hw_type(p): mcu_type = p.get_mcu_type() assert mcu_type is not None + app_uid = p.get_uid() + usb_serial = p.get_usb_serial() + assert app_uid == usb_serial + p.reset(enter_bootstub=True, reconnect=True) p.close() time.sleep(3) - pp = Panda(p.get_usb_serial()) - assert pp.bootstub - assert pp.get_type() == hw_type, "Bootstub and app hw type mismatch" - assert pp.get_mcu_type() == mcu_type, "Bootstub and app MCU type mismatch" - pp.close() + with Panda(p.get_usb_serial()) as pp: + assert pp.bootstub + assert pp.get_type() == hw_type, "Bootstub and app hw type mismatch" + assert pp.get_mcu_type() == mcu_type, "Bootstub and app MCU type mismatch" + assert pp.get_uid() == app_uid @test_all_pandas @@ -82,3 +86,17 @@ def test_heartbeat(p): assert h['safety_mode'] == Panda.SAFETY_SILENT assert h['safety_param'] == 0 assert h['controls_allowed'] == 0 + +@test_all_pandas +@panda_connect_and_init +def test_microsecond_timer(p): + start_time = p.get_microsecond_timer() + time.sleep(1) + end_time = p.get_microsecond_timer() + + # account for uint32 overflow + if end_time < start_time: + end_time += 2**32 + + time_diff = (end_time - start_time) / 1e6 + assert 0.98 < time_diff < 1.02, f"Timer not running at the correct speed! (got {time_diff:.2f}s instead of 1.0s)" diff --git a/tests/automated/3_usb_to_can.py b/tests/hitl/3_usb_to_can.py similarity index 85% rename from tests/automated/3_usb_to_can.py rename to tests/hitl/3_usb_to_can.py index a2035bd0a7..4aca06affc 100644 --- a/tests/automated/3_usb_to_can.py +++ b/tests/hitl/3_usb_to_can.py @@ -1,9 +1,10 @@ import sys import time +from flaky import flaky from nose.tools import assert_equal, assert_less, assert_greater from panda import Panda -from .helpers import SPEED_NORMAL, SPEED_GMLAN, time_many_sends, test_white_and_grey, panda_type_to_serial, test_all_pandas, panda_connect_and_init +from .helpers import SPEED_NORMAL, SPEED_GMLAN, time_many_sends, test_all_gmlan_pandas, test_all_pandas, panda_connect_and_init @test_all_pandas @panda_connect_and_init @@ -30,26 +31,9 @@ def test_can_loopback(p): assert 0x1aa == sr[0][0] == lb[0][0] assert b"message" == sr[0][2] == lb[0][2] -@test_all_pandas -@panda_connect_and_init -def test_safety_nooutput(p): - p.set_safety_mode(Panda.SAFETY_SILENT) - p.set_can_loopback(True) - - # send a message on bus 0 - p.can_send(0x1aa, b"message", 0) - - # confirm receive nothing - time.sleep(0.05) - r = p.can_recv() - # bus 192 is messages blocked by TX safety hook on bus 0 - assert len([x for x in r if x[3] != 192]) == 0 - assert len([x for x in r if x[3] == 192]) == 1 - @test_all_pandas @panda_connect_and_init def test_reliability(p): - LOOP_COUNT = 100 MSG_COUNT = 100 p.set_safety_mode(Panda.SAFETY_ALLOUTPUT) @@ -59,8 +43,7 @@ def test_reliability(p): addrs = list(range(100, 100 + MSG_COUNT)) ts = [(j, 0, b"\xaa" * 8, 0) for j in addrs] - # 100 loops - for i in range(LOOP_COUNT): + for _ in range(100): st = time.monotonic() p.can_send_many(ts) @@ -84,6 +67,7 @@ def test_reliability(p): sys.stdout.flush() @test_all_pandas +@flaky(max_runs=6, min_passes=1) @panda_connect_and_init def test_throughput(p): # enable output mode @@ -106,8 +90,7 @@ def test_throughput(p): print("loopback 100 messages at speed %d, comp speed is %.2f, percent %.2f" % (speed, comp_kbps, saturation_pct)) -@test_white_and_grey -@panda_type_to_serial +@test_all_gmlan_pandas @panda_connect_and_init def test_gmlan(p): p.set_safety_mode(Panda.SAFETY_ALLOUTPUT) @@ -131,8 +114,7 @@ def test_gmlan(p): print("%d: %.2f kbps vs %.2f kbps" % (bus, comp_kbps_gmlan, comp_kbps_normal)) -@test_white_and_grey -@panda_type_to_serial +@test_all_gmlan_pandas @panda_connect_and_init def test_gmlan_bad_toggle(p): p.set_safety_mode(Panda.SAFETY_ALLOUTPUT) diff --git a/tests/automated/4_can_loopback.py b/tests/hitl/4_can_loopback.py similarity index 94% rename from tests/automated/4_can_loopback.py rename to tests/hitl/4_can_loopback.py index 53b18168a2..103da33ce1 100644 --- a/tests/automated/4_can_loopback.py +++ b/tests/hitl/4_can_loopback.py @@ -2,12 +2,15 @@ import time import random import threading -from panda import Panda +from flaky import flaky from collections import defaultdict from nose.tools import assert_equal, assert_less, assert_greater -from .helpers import panda_jungle, time_many_sends, test_all_pandas, test_all_gen2_pandas, clear_can_buffers, panda_connect_and_init + +from panda import Panda +from .helpers import panda_jungle, time_many_sends, test_all_pandas, test_all_gen2_pandas, clear_can_buffers, panda_connect_and_init, PARTIAL_TESTS @test_all_pandas +@flaky(max_runs=3, min_passes=1) @panda_connect_and_init def test_send_recv(p): def test(p_send, p_recv): @@ -44,6 +47,7 @@ def test(p_send, p_recv): @test_all_pandas +@flaky(max_runs=6, min_passes=1) @panda_connect_and_init def test_latency(p): def test(p_send, p_recv): @@ -75,7 +79,7 @@ def test(p_send, p_recv): num_messages = 100 - for i in range(num_messages): + for _ in range(num_messages): st = time.monotonic() p_send.can_send(0x1ab, b"message", bus) r = [] @@ -166,6 +170,10 @@ def test(p_send, p_recv, address=None): @test_all_pandas @panda_connect_and_init def test_bulk_write(p): + # TODO: doesn't work in partial test mode + if PARTIAL_TESTS: + return + # The TX buffers on pandas is 0x100 in length. NUM_MESSAGES_PER_BUS = 10000 @@ -179,11 +187,10 @@ def flood_tx(panda): packet += [[0xaa, None, msg, 0], [0xaa, None, msg, 1], [0xaa, None, msg, 2]] * NUM_MESSAGES_PER_BUS # Disable timeout + panda.set_safety_mode(Panda.SAFETY_ALLOUTPUT) panda.can_send_many(packet, timeout=0) - print(f"Done sending {4 * NUM_MESSAGES_PER_BUS} messages!") - - # Set safety mode and power saving - p.set_safety_mode(Panda.SAFETY_ALLOUTPUT) + print(f"Done sending {4 * NUM_MESSAGES_PER_BUS} messages!", time.monotonic()) + print(panda.health()) # Start transmisson threading.Thread(target=flood_tx, args=(p,)).start() @@ -195,7 +202,7 @@ def flood_tx(panda): while time.monotonic() - start_time < 5 or len(rx) > old_len: old_len = len(rx) rx.extend(panda_jungle.can_recv()) - print(f"Received {len(rx)} messages") + print(f"Received {len(rx)} messages", time.monotonic()) # All messages should have been received if len(rx) != 4 * NUM_MESSAGES_PER_BUS: diff --git a/tests/automated/5_gps.py b/tests/hitl/5_gps.py similarity index 96% rename from tests/automated/5_gps.py rename to tests/hitl/5_gps.py index ec4bbbcf48..7fb6329c6b 100644 --- a/tests/automated/5_gps.py +++ b/tests/hitl/5_gps.py @@ -7,7 +7,7 @@ def test_gps_version(p): serial = PandaSerial(p, 1, 9600) # Reset and check twice to make sure the enabling works - for i in range(2): + for _ in range(2): # Reset GPS p.set_esp_power(0) time.sleep(2) diff --git a/tests/hitl/6_safety.py b/tests/hitl/6_safety.py new file mode 100644 index 0000000000..375283823d --- /dev/null +++ b/tests/hitl/6_safety.py @@ -0,0 +1,33 @@ +import time +from nose.tools import assert_equal + +from panda import Panda +from .helpers import test_all_pandas, panda_connect_and_init + +@test_all_pandas +@panda_connect_and_init +def test_safety_nooutput(p): + p.set_safety_mode(Panda.SAFETY_SILENT) + p.set_can_loopback(True) + + # send a message on bus 0 + p.can_send(0x1aa, b"message", 0) + + # confirm receive nothing + time.sleep(0.05) + r = p.can_recv() + # bus 192 is messages blocked by TX safety hook on bus 0 + assert len([x for x in r if x[3] != 192]) == 0 + assert len([x for x in r if x[3] == 192]) == 1 + +@test_all_pandas +@panda_connect_and_init +def test_canfd_safety_modes(p): + # works on all pandas + p.set_safety_mode(Panda.SAFETY_TOYOTA) + assert_equal(p.health()['safety_mode'], Panda.SAFETY_TOYOTA) + + # shouldn't be able to set a CAN-FD safety mode on non CAN-FD panda + p.set_safety_mode(Panda.SAFETY_HYUNDAI_CANFD) + expected_mode = Panda.SAFETY_HYUNDAI_CANFD if p.get_type() in Panda.H7_DEVICES else Panda.SAFETY_SILENT + assert_equal(p.health()['safety_mode'], expected_mode) diff --git a/tests/automated/__init__.py b/tests/hitl/__init__.py similarity index 100% rename from tests/automated/__init__.py rename to tests/hitl/__init__.py diff --git a/tests/automated/helpers.py b/tests/hitl/helpers.py similarity index 56% rename from tests/automated/helpers.py rename to tests/hitl/helpers.py index 5e83393771..99070f0f96 100644 --- a/tests/automated/helpers.py +++ b/tests/hitl/helpers.py @@ -1,26 +1,26 @@ +import concurrent.futures import os import time import random import faulthandler from functools import wraps, partial from nose.tools import assert_equal -from parameterized import parameterized, param +from parameterized import parameterized -from panda import Panda, DEFAULT_H7_FW_FN, DEFAULT_FW_FN, MCU_TYPE_H7 +from panda import Panda from panda_jungle import PandaJungle # pylint: disable=import-error SPEED_NORMAL = 500 SPEED_GMLAN = 33.3 BUS_SPEEDS = [(0, SPEED_NORMAL), (1, SPEED_NORMAL), (2, SPEED_NORMAL), (3, SPEED_GMLAN)] -TIMEOUT = 45 H7_HW_TYPES = [Panda.HW_TYPE_RED_PANDA, Panda.HW_TYPE_RED_PANDA_V2] GEN2_HW_TYPES = [Panda.HW_TYPE_BLACK_PANDA, Panda.HW_TYPE_UNO] + H7_HW_TYPES -GPS_HW_TYPES = [Panda.HW_TYPE_GREY_PANDA, Panda.HW_TYPE_BLACK_PANDA, Panda.HW_TYPE_UNO] +GPS_HW_TYPES = [Panda.HW_TYPE_BLACK_PANDA, Panda.HW_TYPE_UNO] PEDAL_SERIAL = 'none' JUNGLE_SERIAL = os.getenv("PANDAS_JUNGLE") -PANDAS_EXCLUDE = [] -if os.getenv("PANDAS_EXCLUDE"): - PANDAS_EXCLUDE = os.getenv("PANDAS_EXCLUDE").strip().split(" ") +PANDAS_EXCLUDE = os.getenv("PANDAS_EXCLUDE", "").strip().split(" ") + +PARTIAL_TESTS = os.environ.get("PARTIAL_TESTS", "0") == "1" # Enable fault debug faulthandler.enable(all_threads=False) @@ -29,9 +29,9 @@ panda_jungle = PandaJungle(JUNGLE_SERIAL) # Find all pandas connected -_all_pandas = None +_all_pandas = [] def init_all_pandas(): - global panda_jungle, _all_pandas + global _all_pandas _all_pandas = [] # power cycle pandas @@ -41,53 +41,47 @@ def init_all_pandas(): time.sleep(5) for serial in Panda.list(): - if serial not in PANDAS_EXCLUDE: - p = Panda(serial=serial) - _all_pandas.append((serial, p.get_type())) - p.close() - print(f"Found {len(_all_pandas)} pandas") + if serial not in PANDAS_EXCLUDE and serial != PEDAL_SERIAL: + with Panda(serial=serial) as p: + _all_pandas.append((serial, p.get_type())) + print(f"{len(_all_pandas)} total pandas") init_all_pandas() _all_panda_serials = [x[0] for x in _all_pandas] +def parameterized_panda_types(types): + serials = [] + for typ in types: + for s, t in _all_pandas: + if t == typ and s not in serials: + serials.append(s) + break + else: + raise IOError("No unused panda found for type: {}".format(typ)) + return parameterized(serials) + # Panda providers -test_all_types = parameterized([ - param(panda_type=Panda.HW_TYPE_WHITE_PANDA), - param(panda_type=Panda.HW_TYPE_GREY_PANDA), - param(panda_type=Panda.HW_TYPE_BLACK_PANDA), - param(panda_type=Panda.HW_TYPE_UNO), - param(panda_type=Panda.HW_TYPE_RED_PANDA), - param(panda_type=Panda.HW_TYPE_RED_PANDA_V2) - ]) -test_all_pandas = parameterized( - list(map(lambda x: x[0], _all_pandas)) # type: ignore - ) -test_all_gen2_pandas = parameterized( - list(map(lambda x: x[0], filter(lambda x: x[1] in GEN2_HW_TYPES, _all_pandas))) # type: ignore - ) -test_all_gps_pandas = parameterized( - list(map(lambda x: x[0], filter(lambda x: x[1] in GPS_HW_TYPES, _all_pandas))) # type: ignore - ) -test_white_and_grey = parameterized([ - param(panda_type=Panda.HW_TYPE_WHITE_PANDA), - param(panda_type=Panda.HW_TYPE_GREY_PANDA) - ]) -test_white = parameterized([ - param(panda_type=Panda.HW_TYPE_WHITE_PANDA) - ]) -test_grey = parameterized([ - param(panda_type=Panda.HW_TYPE_GREY_PANDA) - ]) -test_black = parameterized([ - param(panda_type=Panda.HW_TYPE_BLACK_PANDA) - ]) -test_uno = parameterized([ - param(panda_type=Panda.HW_TYPE_UNO) - ]) +TESTED_HW_TYPES = (Panda.HW_TYPE_WHITE_PANDA, Panda.HW_TYPE_BLACK_PANDA, Panda.HW_TYPE_RED_PANDA, Panda.HW_TYPE_RED_PANDA_V2, Panda.HW_TYPE_UNO) +test_all_pandas = parameterized_panda_types(TESTED_HW_TYPES) +test_all_gen2_pandas = parameterized_panda_types(GEN2_HW_TYPES) +test_all_gps_pandas = parameterized_panda_types(GPS_HW_TYPES) + +# no grey for speedup, should be sufficiently covered by white for these tests +test_all_gmlan_pandas = parameterized_panda_types([Panda.HW_TYPE_WHITE_PANDA, ]) + +if PARTIAL_TESTS: + # minimal set of pandas to get most of our coverage + # * red panda covers STM32H7 + # * black panda covers STM32F4, GEN2, and GPS + partial_pandas = (Panda.HW_TYPE_BLACK_PANDA, Panda.HW_TYPE_RED_PANDA) + test_all_pandas = parameterized_panda_types(partial_pandas) + test_all_gen2_pandas = parameterized_panda_types(partial_pandas) + test_all_gps_pandas = parameterized_panda_types([Panda.HW_TYPE_BLACK_PANDA, ]) + def time_many_sends(p, bus, p_recv=None, msg_count=100, msg_id=None, two_pandas=False): - if p_recv == None: + if p_recv is None: p_recv = p - if msg_id == None: + if msg_id is None: msg_id = random.randint(0x100, 0x200) if p == p_recv and two_pandas: raise ValueError("Cannot have two pandas that are the same panda") @@ -121,40 +115,12 @@ def time_many_sends(p, bus, p_recv=None, msg_count=100, msg_id=None, two_pandas= return comp_kbps -def panda_type_to_serial(fn): - @wraps(fn) - def wrapper(panda_type=None, **kwargs): - # Change panda_types to a list - if panda_type is not None: - if not isinstance(panda_type, list): - panda_type = [panda_type] - - # If not done already, get panda serials and their type - global _all_pandas - if _all_pandas == None: - init_all_pandas() - - # Find a panda with the correct types and add the corresponding serial - serials = [] - for p_type in panda_type: - found = False - for serial, pt in _all_pandas: - # Never take the same panda twice - if (pt == p_type) and (serial not in serials): - serials.append(serial) - found = True - break - if not found: - raise IOError("No unused panda found for type: {}".format(p_type)) - return fn(serials, **kwargs) - return wrapper - -def panda_connect_and_init(fn=None, full_reset=True): +def panda_connect_and_init(fn=None): if not fn: - return partial(panda_connect_and_init, full_reset=full_reset) + return partial(panda_connect_and_init) @wraps(fn) - def wrapper(panda_serials=None, **kwargs): + def wrapper(panda_serials, **kwargs): # Change panda_serials to a list if panda_serials is not None: if not isinstance(panda_serials, list): @@ -167,7 +133,7 @@ def wrapper(panda_serials=None, **kwargs): panda_jungle.set_obd(False) panda_jungle.set_harness_orientation(PandaJungle.HARNESS_ORIENTATION_1) for bus, speed in BUS_SPEEDS: - panda_jungle.set_can_speed_kbps(bus, speed) + panda_jungle.set_can_speed_kbps(bus, speed) # wait for all pandas to come up for _ in range(50): @@ -176,28 +142,28 @@ def wrapper(panda_serials=None, **kwargs): time.sleep(0.1) # Connect to pandas - pandas = [] - for s in _all_panda_serials: + def cnnct(s): if s in panda_serials: p = Panda(serial=s) p.reset(reconnect=True) - pandas.append(p) - elif full_reset and s != PEDAL_SERIAL: - p = Panda(serial=s) - p.reset(reconnect=False) - p.close() - # Initialize pandas - if full_reset: - for panda in pandas: - panda.set_can_loopback(False) - panda.set_gmlan(None) - panda.set_esp_power(False) - panda.set_power_save(False) + p.set_can_loopback(False) + p.set_gmlan(None) + p.set_esp_power(False) + p.set_power_save(False) for bus, speed in BUS_SPEEDS: - panda.set_can_speed_kbps(bus, speed) - clear_can_buffers(panda) - panda.set_power_save(False) + p.set_can_speed_kbps(bus, speed) + clear_can_buffers(p) + p.set_power_save(False) + return p + else: + with Panda(serial=s) as p: + p.reset(reconnect=False) + return None + + with concurrent.futures.ThreadPoolExecutor() as exc: + ps = list(exc.map(cnnct, _all_panda_serials, timeout=20)) + pandas = [p for p in ps if p is not None] try: fn(*pandas, *kwargs) @@ -205,12 +171,11 @@ def wrapper(panda_serials=None, **kwargs): # Check if the pandas did not throw any faults while running test for panda in pandas: if not panda.bootstub: - panda.reconnect() + #panda.reconnect() assert panda.health()['fault_status'] == 0 # Check health of each CAN core after test, normal to fail for test_gen2_loopback on OBD bus, so skipping if fn.__name__ != "test_gen2_loopback": for i in range(3): - print(fn.__name__) can_health = panda.can_health(i) assert can_health['bus_off_cnt'] == 0 assert can_health['receive_error_cnt'] == 0 @@ -218,6 +183,7 @@ def wrapper(panda_serials=None, **kwargs): assert can_health['total_rx_lost_cnt'] == 0 assert can_health['total_tx_lost_cnt'] == 0 assert can_health['total_error_cnt'] == 0 + assert can_health['total_tx_checksum_error_cnt'] == 0 finally: for p in pandas: try: @@ -244,7 +210,6 @@ def clear_can_buffers(panda): def check_signature(p): assert not p.bootstub, "Flashed firmware not booting. Stuck in bootstub." - fn = DEFAULT_H7_FW_FN if p.get_mcu_type() == MCU_TYPE_H7 else DEFAULT_FW_FN - firmware_sig = Panda.get_signature_from_firmware(fn) + firmware_sig = Panda.get_signature_from_firmware(p.get_mcu_type().config.app_path) panda_sig = p.get_signature() assert_equal(panda_sig, firmware_sig) diff --git a/tests/automated/known_bootstub/bootstub.panda_h7.bin b/tests/hitl/known_bootstub/bootstub.panda_h7.bin old mode 100755 new mode 100644 similarity index 100% rename from tests/automated/known_bootstub/bootstub.panda_h7.bin rename to tests/hitl/known_bootstub/bootstub.panda_h7.bin diff --git a/tests/hitl/known_bootstub/bootstub_f4_first_dos_production.panda.bin b/tests/hitl/known_bootstub/bootstub_f4_first_dos_production.panda.bin new file mode 100644 index 0000000000..786acf639e Binary files /dev/null and b/tests/hitl/known_bootstub/bootstub_f4_first_dos_production.panda.bin differ diff --git a/tests/hitl/known_bootstub/bootstub_f4_only_bcd.panda.bin b/tests/hitl/known_bootstub/bootstub_f4_only_bcd.panda.bin new file mode 100755 index 0000000000..000fd26fe4 Binary files /dev/null and b/tests/hitl/known_bootstub/bootstub_f4_only_bcd.panda.bin differ diff --git a/tests/automated/test.sh b/tests/hitl/test.sh similarity index 62% rename from tests/automated/test.sh rename to tests/hitl/test.sh index 72f866a86b..d2de5f3b7d 100755 --- a/tests/automated/test.sh +++ b/tests/hitl/test.sh @@ -1,6 +1,5 @@ #!/bin/bash - set -e DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" -nosetests -x -v -s $(ls $DIR/$1*.py) +nosetests -x -v --with-flaky -s $(ls $DIR/$1*.py) diff --git a/tests/libpanda/SConscript b/tests/libpanda/SConscript new file mode 100644 index 0000000000..71ed743897 --- /dev/null +++ b/tests/libpanda/SConscript @@ -0,0 +1,13 @@ +env = Environment( + CC='gcc', + CFLAGS=[ + '-nostdlib', + '-fno-builtin', + '-std=gnu11', + '-Wfatal-errors', + ], + CPPPATH=[".", "../../board/"], +) +env.PrependENVPath('PATH', '/opt/homebrew/bin') + +env.SharedLibrary("libpanda.so", ["panda.c",]) diff --git a/tests/libpanda/libpanda_py.py b/tests/libpanda/libpanda_py.py new file mode 100644 index 0000000000..420986550b --- /dev/null +++ b/tests/libpanda/libpanda_py.py @@ -0,0 +1,91 @@ +import os +from cffi import FFI +from typing import List + +from panda import LEN_TO_DLC +from panda.tests.libpanda.safety_helpers import PandaSafety, setup_safety_helpers + +libpanda_dir = os.path.dirname(os.path.abspath(__file__)) +libpanda_fn = os.path.join(libpanda_dir, "libpanda.so") + +ffi = FFI() + +ffi.cdef(""" +typedef struct { + unsigned char reserved : 1; + unsigned char bus : 3; + unsigned char data_len_code : 4; + unsigned char rejected : 1; + unsigned char returned : 1; + unsigned char extended : 1; + unsigned int addr : 29; + unsigned char checksum; + unsigned char data[64]; +} CANPacket_t; +""", packed=True) + +ffi.cdef(""" +int safety_rx_hook(CANPacket_t *to_send); +int safety_tx_hook(CANPacket_t *to_push); +int safety_fwd_hook(int bus_num, CANPacket_t *to_fwd); +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); +void can_set_checksum(CANPacket_t *packet); +int comms_can_read(uint8_t *data, uint32_t max_len); +void comms_can_write(uint8_t *data, uint32_t len); +void comms_can_reset(void); +uint32_t can_slots_empty(can_ring *q); +""") + +setup_safety_helpers(ffi) + +class CANPacket: + reserved: int + bus: int + data_len_code: int + rejected: int + returned: int + extended: int + addr: int + data: List[int] + +class Panda(PandaSafety): + # safety + def safety_rx_hook(self, to_send: CANPacket) -> int: ... + def safety_tx_hook(self, to_push: CANPacket) -> int: ... + def safety_fwd_hook(self, bus_num: int, to_fwd: CANPacket) -> int: ... + def set_safety_hooks(self, mode: int, param: int) -> int: ... + + +libpanda: Panda = ffi.dlopen(libpanda_fn) + + +# helpers + +def make_CANPacket(addr: int, bus: int, dat): + ret = ffi.new('CANPacket_t *') + ret[0].extended = 1 if addr >= 0x800 else 0 + ret[0].addr = addr + ret[0].data_len_code = LEN_TO_DLC[len(dat)] + ret[0].bus = bus + ret[0].data = bytes(dat) + libpanda.can_set_checksum(ret) + + return ret 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/safety/test.c b/tests/libpanda/safety_helpers.h similarity index 62% rename from tests/safety/test.c rename to tests/libpanda/safety_helpers.h index b89bbc45ab..7933431d2b 100644 --- a/tests/safety/test.c +++ b/tests/libpanda/safety_helpers.h @@ -1,74 +1,3 @@ -#include -#include -#include -#include - -#include "panda.h" -#include "can_definitions.h" -#include "utils.h" - -#define CANFD - -typedef struct { - uint32_t CNT; -} TIM_TypeDef; - -struct sample_t torque_meas; -struct sample_t torque_driver; - -TIM_TypeDef timer; -TIM_TypeDef *MICROSECOND_TIMER = &timer; -uint32_t microsecond_timer_get(void); - -// from board_declarations.h -#define HW_TYPE_UNKNOWN 0U -#define HW_TYPE_WHITE_PANDA 1U -#define HW_TYPE_GREY_PANDA 2U -#define HW_TYPE_BLACK_PANDA 3U -#define HW_TYPE_PEDAL 4U -#define HW_TYPE_UNO 5U -#define HW_TYPE_DOS 6U - -#define ALLOW_DEBUG - -// from main_declarations.h -uint8_t hw_type = HW_TYPE_UNKNOWN; - -// from config.h -#define MIN(a,b) \ - ({ __typeof__ (a) _a = (a); \ - __typeof__ (b) _b = (b); \ - _a < _b ? _a : _b; }) - -#define MAX(a,b) \ - ({ __typeof__ (a) _a = (a); \ - __typeof__ (b) _b = (b); \ - _a > _b ? _a : _b; }) - -#define ABS(a) \ - ({ __typeof__ (a) _a = (a); \ - (_a > 0) ? _a : (-_a); }) - -// from faults.h -#define FAULT_RELAY_MALFUNCTION (1U << 0) -void fault_occurred(uint32_t fault) { -} -void fault_recovered(uint32_t fault) { -} - -#define UNUSED(x) (void)(x) - -#ifndef PANDA -#define PANDA -#endif -#define NULL ((void*)0) -#define static -#include "safety.h" - -uint32_t microsecond_timer_get(void) { - return MICROSECOND_TIMER->CNT; -} - void safety_tick_current_rx_checks() { safety_tick(current_rx_checks); } @@ -81,9 +10,9 @@ bool addr_checks_valid() { for (int i = 0; i < current_rx_checks->len; i++) { const AddrCheckStruct addr = current_rx_checks->check[i]; - bool valid = addr.msg_seen && !addr.lagging && addr.valid_checksum && (addr.wrong_counters < MAX_WRONG_COUNTERS); + bool valid = addr.msg_seen && !addr.lagging && addr.valid_checksum && (addr.wrong_counters < MAX_WRONG_COUNTERS) && addr.valid_quality_flag; if (!valid) { - //printf("seen %d lagging %d valid checksum %d wrong counters %d\n", addr.msg_seen, addr.lagging, addr.valid_checksum, addr.wrong_counters); + printf("i %d seen %d lagging %d valid checksum %d wrong counters %d valid quality flag %d\n", i, addr.msg_seen, addr.lagging, addr.valid_checksum, addr.wrong_counters, addr.valid_quality_flag); return false; } } @@ -226,10 +155,8 @@ void init_tests(void){ ts_steer_req_mismatch_last = 0; valid_steer_req_count = 0; invalid_steer_req_count = 0; -} -void init_tests_honda(void){ - init_tests(); + // car-specific stuff honda_fwd_brake = false; } diff --git a/tests/libpanda/safety_helpers.py b/tests/libpanda/safety_helpers.py new file mode 100644 index 0000000000..a6ba4b0412 --- /dev/null +++ b/tests/libpanda/safety_helpers.py @@ -0,0 +1,87 @@ +# panda safety helpers, from safety_helpers.c + +def setup_safety_helpers(ffi): + ffi.cdef(""" + void set_controls_allowed(bool c); + bool get_controls_allowed(void); + bool get_longitudinal_allowed(void); + void set_alternative_experience(int mode); + int get_alternative_experience(void); + void set_relay_malfunction(bool c); + bool get_relay_malfunction(void); + void set_gas_interceptor_detected(bool c); + bool get_gas_interceptor_detected(void); + int get_gas_interceptor_prev(void); + bool get_gas_pressed_prev(void); + bool get_brake_pressed_prev(void); + bool get_regen_braking_prev(void); + bool get_acc_main_on(void); + + void set_torque_meas(int min, int max); + int get_torque_meas_min(void); + int get_torque_meas_max(void); + void set_torque_driver(int min, int max); + int get_torque_driver_min(void); + int get_torque_driver_max(void); + void set_desired_torque_last(int t); + void set_rt_torque_last(int t); + void set_desired_angle_last(int t); + + bool get_cruise_engaged_prev(void); + bool get_vehicle_moving(void); + int get_hw_type(void); + void set_timer(uint32_t t); + + void safety_tick_current_rx_checks(); + bool addr_checks_valid(); + + void init_tests(void); + + void set_honda_fwd_brake(bool c); + void set_honda_alt_brake_msg(bool c); + void set_honda_bosch_long(bool c); + int get_honda_hw(void); + """) + +class PandaSafety: + def set_controls_allowed(self, c: bool) -> None: ... + def get_controls_allowed(self) -> bool: ... + def get_longitudinal_allowed(self) -> bool: ... + def set_alternative_experience(self, mode: int) -> None: ... + def get_alternative_experience(self) -> int: ... + def set_relay_malfunction(self, c: bool) -> None: ... + def get_relay_malfunction(self) -> bool: ... + def set_gas_interceptor_detected(self, c: bool) -> None: ... + def get_gas_interceptor_detected(self) -> bool: ... + def get_gas_interceptor_prev(self) -> int: ... + def get_gas_pressed_prev(self) -> bool: ... + def get_brake_pressed_prev(self) -> bool: ... + def get_regen_braking_prev(self) -> bool: ... + def get_acc_main_on(self) -> bool: ... + + def set_torque_meas(self, min: int, max: int) -> None: ... # pylint: disable=redefined-builtin + def get_torque_meas_min(self) -> int: ... + def get_torque_meas_max(self) -> int: ... + def set_torque_driver(self, min: int, max: int) -> None: ... # pylint: disable=redefined-builtin + def get_torque_driver_min(self) -> int: ... + def get_torque_driver_max(self) -> int: ... + def set_desired_torque_last(self, t: int) -> None: ... + def set_rt_torque_last(self, t: int) -> None: ... + def set_desired_angle_last(self, t: int) -> None: ... + + def get_cruise_engaged_prev(self) -> bool: ... + def get_vehicle_moving(self) -> bool: ... + def get_hw_type(self) -> int: ... + def set_timer(self, t: int) -> None: ... + + def safety_tick_current_rx_checks(self) -> None: ... + def addr_checks_valid(self) -> bool: ... + + def init_tests(self) -> None: ... + + def set_honda_fwd_brake(self, c: bool) -> None: ... + def set_honda_alt_brake_msg(self, c: bool) -> None: ... + def set_honda_bosch_long(self, c: bool) -> None: ... + def get_honda_hw(self) -> int: ... + + diff --git a/tests/libs/resetter.py b/tests/libs/resetter.py index d807cc23c8..c9dead2e7e 100644 --- a/tests/libs/resetter.py +++ b/tests/libs/resetter.py @@ -44,11 +44,14 @@ def enable_boot(self, enabled): def cycle_power(self, delay=5, ports=None): if ports is None: - ports = [1,2,3] + ports = [1, 2, 3] + for port in ports: self.enable_power(port, False) + time.sleep(1) + for port in ports: self.enable_power(port, True) - if delay > 0: - time.sleep(delay) + + time.sleep(delay) diff --git a/tests/message_drop_test.py b/tests/message_drop_test.py index c6991ce7a5..31c6f8cbd1 100755 --- a/tests/message_drop_test.py +++ b/tests/message_drop_test.py @@ -5,7 +5,7 @@ import struct import itertools import threading -from typing import Any, List +from typing import Any, Union, List from panda import Panda @@ -35,6 +35,7 @@ def flood_tx(panda): if __name__ == "__main__": serials = Panda.list() + receiver: Union[Panda, PandaJungle] if JUNGLE: sender = Panda() receiver = PandaJungle() diff --git a/tests/pedal/enter_canloader.py b/tests/pedal/enter_canloader.py index c7ae195b58..bdd0d62865 100755 --- a/tests/pedal/enter_canloader.py +++ b/tests/pedal/enter_canloader.py @@ -1,8 +1,7 @@ #!/usr/bin/env python3 import time import argparse -from panda import Panda -from panda.python.dfu import MCU_TYPE_F2 +from panda import Panda, McuType from panda.tests.pedal.canhandle import CanHandle @@ -29,6 +28,6 @@ time.sleep(0.1) print("flashing", args.fn) code = open(args.fn, "rb").read() - Panda.flash_static(CanHandle(p, 0), code, mcu_type=MCU_TYPE_F2) + Panda.flash_static(CanHandle(p, 0), code, mcu_type=McuType.F2) print("can flash done") diff --git a/tests/pedal/test_pedal.py b/tests/pedal/test_pedal.py index 16da13540c..652a90c033 100755 --- a/tests/pedal/test_pedal.py +++ b/tests/pedal/test_pedal.py @@ -47,9 +47,8 @@ def test_usb_fw(self): subprocess.check_output(f"cd {BASEDIR} && PEDAL=1 PEDAL_USB=1 scons", shell=True) self._flash_over_can(PEDAL_BUS, f"{BASEDIR}board/obj/pedal_usb.bin.signed") time.sleep(2) - p = Panda(PEDAL_SERIAL) - self.assertTrue(p.get_type() == Panda.HW_TYPE_PEDAL) - p.close() + with Panda(PEDAL_SERIAL) as p: + self.assertTrue(p.get_type() == Panda.HW_TYPE_PEDAL) self.assertTrue(self._listen_can_frames() > 40) def test_nonusb_fw(self): diff --git a/tests/safety/SConscript b/tests/safety/SConscript deleted file mode 100644 index 809c651740..0000000000 --- a/tests/safety/SConscript +++ /dev/null @@ -1,11 +0,0 @@ -env = Environment( - CC='gcc', - CFLAGS=[ - '-nostdlib', - '-fno-builtin', - '-std=gnu11', - ], - CPPPATH=[".", "../../board"], -) - -env.SharedLibrary("libpandasafety.so", ["test.c"]) diff --git a/tests/safety/common.py b/tests/safety/common.py index 89e736f4ce..71bdff0e89 100644 --- a/tests/safety/common.py +++ b/tests/safety/common.py @@ -3,29 +3,21 @@ import unittest import importlib import numpy as np -from typing import Optional, List, Dict +from typing import Dict, List, Optional from opendbc.can.packer import CANPacker # pylint: disable=import-error -from panda import ALTERNATIVE_EXPERIENCE, LEN_TO_DLC -from panda.tests.safety import libpandasafety_py +from panda import ALTERNATIVE_EXPERIENCE +from panda.tests.libpanda import libpanda_py MAX_WRONG_COUNTERS = 5 -def package_can_msg(msg): - addr, _, dat, bus = msg - ret = libpandasafety_py.ffi.new('CANPacket_t *') - ret[0].extended = 1 if addr >= 0x800 else 0 - ret[0].addr = addr - ret[0].data_len_code = LEN_TO_DLC[len(dat)] - ret[0].bus = bus - ret[0].data = bytes(dat) - - return ret +def sign_of(a): + return 1 if a > 0 else -1 def make_msg(bus, addr, length=8): - return package_can_msg([addr, 0, b'\x00' * length, bus]) + return libpanda_py.make_CANPacket(addr, bus, b'\x00' * length) class CANPackerPanda(CANPacker): @@ -33,7 +25,8 @@ def make_can_msg_panda(self, name_or_addr, bus, values, fix_checksum=None): msg = self.make_can_msg(name_or_addr, bus, values) if fix_checksum is not None: msg = fix_checksum(msg) - return package_can_msg(msg) + addr, _, dat, bus = msg + return libpanda_py.make_CANPacket(addr, bus, dat) def add_regen_tests(cls): @@ -138,7 +131,7 @@ def test_gas_interceptor_safety_check(self): self.assertEqual(send, self._tx(self._interceptor_gas_cmd(gas))) -class TorqueSteeringSafetyTestBase(PandaSafetyTestBase): +class TorqueSteeringSafetyTestBase(PandaSafetyTestBase, abc.ABC): MAX_RATE_UP = 0 MAX_RATE_DOWN = 0 @@ -291,7 +284,7 @@ def test_steer_req_bit_realtime(self): self.assertTrue(self._tx(self._torque_cmd_msg(self.MAX_TORQUE, steer_req=1))) -class DriverTorqueSteeringSafetyTest(TorqueSteeringSafetyTestBase): +class DriverTorqueSteeringSafetyTest(TorqueSteeringSafetyTestBase, abc.ABC): DRIVER_TORQUE_ALLOWANCE = 0 DRIVER_TORQUE_FACTOR = 0 @@ -302,10 +295,6 @@ def setUpClass(cls): cls.safety = None raise unittest.SkipTest - @abc.abstractmethod - def _torque_cmd_msg(self, torque, steer_req=1): - pass - def test_non_realtime_limit_up(self): self.safety.set_torque_driver(0, 0) super().test_non_realtime_limit_up() @@ -376,7 +365,7 @@ def test_realtime_limits(self): self.assertTrue(self._tx(self._torque_cmd_msg(sign * (self.MAX_RT_DELTA + 1)))) -class MotorTorqueSteeringSafetyTest(TorqueSteeringSafetyTestBase): +class MotorTorqueSteeringSafetyTest(TorqueSteeringSafetyTestBase, abc.ABC): MAX_TORQUE_ERROR = 0 TORQUE_MEAS_TOLERANCE = 0 @@ -391,10 +380,6 @@ def setUpClass(cls): def _torque_meas_msg(self, torque): pass - @abc.abstractmethod - def _torque_cmd_msg(self, torque, steer_req=1): - pass - def _set_prev_torque(self, t): super()._set_prev_torque(t) self.safety.set_torque_meas(t, t) @@ -486,6 +471,91 @@ def test_torque_measurements(self): self.assertTrue(self.safety.get_torque_meas_max() in max_range) +class AngleSteeringSafetyTest(PandaSafetyTestBase): + + DEG_TO_CAN: int + + ANGLE_DELTA_BP: List[float] + ANGLE_DELTA_V: List[float] # windup limit + ANGLE_DELTA_VU: List[float] # unwind limit + + @classmethod + def setUpClass(cls): + if cls.__name__ == "AngleSteeringSafetyTest": + cls.safety = None + raise unittest.SkipTest + + @abc.abstractmethod + def _angle_cmd_msg(self, angle: float, enabled: bool): + pass + + @abc.abstractmethod + def _angle_meas_msg(self, angle: float): + pass + + def _set_prev_desired_angle(self, t): + t = int(t * self.DEG_TO_CAN) + self.safety.set_desired_angle_last(t) + + def _angle_meas_msg_array(self, angle): + for _ in range(6): + self._rx(self._angle_meas_msg(angle)) + + def test_angle_cmd_when_enabled(self): + # when controls are allowed, angle cmd rate limit is enforced + speeds = [0., 1., 5., 10., 15., 50.] + angles = [-300, -100, -10, 0, 10, 100, 300] + for a in angles: + for s in speeds: + max_delta_up = np.interp(s, self.ANGLE_DELTA_BP, self.ANGLE_DELTA_V) + max_delta_down = np.interp(s, self.ANGLE_DELTA_BP, self.ANGLE_DELTA_VU) + + # first test against false positives + self._angle_meas_msg_array(a) + self._rx(self._speed_msg(s)) # pylint: disable=no-member + + self._set_prev_desired_angle(a) + self.safety.set_controls_allowed(1) + + # Stay within limits + # Up + self.assertTrue(self._tx(self._angle_cmd_msg(a + sign_of(a) * max_delta_up, True))) + self.assertTrue(self.safety.get_controls_allowed()) + + # Don't change + self.assertTrue(self._tx(self._angle_cmd_msg(a, True))) + self.assertTrue(self.safety.get_controls_allowed()) + + # Down + self.assertTrue(self._tx(self._angle_cmd_msg(a - sign_of(a) * max_delta_down, True))) + self.assertTrue(self.safety.get_controls_allowed()) + + # Inject too high rates + # Up + self.assertFalse(self._tx(self._angle_cmd_msg(a + sign_of(a) * (max_delta_up + 1.1), True))) + + # Don't change + self.safety.set_controls_allowed(1) + self._set_prev_desired_angle(a) + self.assertTrue(self.safety.get_controls_allowed()) + self.assertTrue(self._tx(self._angle_cmd_msg(a, True))) + self.assertTrue(self.safety.get_controls_allowed()) + + # Down + self.assertFalse(self._tx(self._angle_cmd_msg(a - sign_of(a) * (max_delta_down + 1.1), True))) + + # Check desired steer should be the same as steer angle when controls are off + self.safety.set_controls_allowed(0) + self.assertTrue(self._tx(self._angle_cmd_msg(a, False))) + + def test_angle_cmd_when_disabled(self): + self.safety.set_controls_allowed(0) + + self._set_prev_desired_angle(0) + self.assertFalse(self._tx(self._angle_cmd_msg(0, True))) + self.assertFalse(self.safety.get_controls_allowed()) + + @add_regen_tests class PandaSafetyTest(PandaSafetyTestBase): TX_MSGS: Optional[List[List[int]]] = None @@ -518,6 +588,10 @@ def _user_regen_msg(self, regen): def _speed_msg(self, speed): pass + # Safety modes can override if vehicle_moving is driven by a different message + def _vehicle_moving_msg(self, speed: float): + return self._speed_msg(speed) + @abc.abstractmethod def _user_gas_msg(self, gas): pass @@ -641,7 +715,7 @@ def test_allow_user_brake_at_zero_speed(self, _user_brake_msg=None, get_brake_pr _user_brake_msg = self._user_brake_msg # Brake was already pressed - self._rx(self._speed_msg(0)) + self._rx(self._vehicle_moving_msg(0)) self._rx(_user_brake_msg(1)) self.safety.set_controls_allowed(1) self._rx(_user_brake_msg(1)) @@ -663,29 +737,29 @@ def test_not_allow_user_brake_when_moving(self, _user_brake_msg=None, get_brake_ # Brake was already pressed self._rx(_user_brake_msg(1)) self.safety.set_controls_allowed(1) - self._rx(self._speed_msg(self.STANDSTILL_THRESHOLD)) + self._rx(self._vehicle_moving_msg(self.STANDSTILL_THRESHOLD)) self._rx(_user_brake_msg(1)) self.assertTrue(self.safety.get_controls_allowed()) self.assertTrue(self.safety.get_longitudinal_allowed()) - self._rx(self._speed_msg(self.STANDSTILL_THRESHOLD + 1)) + self._rx(self._vehicle_moving_msg(self.STANDSTILL_THRESHOLD + 1)) self._rx(_user_brake_msg(1)) self.assertFalse(self.safety.get_controls_allowed()) self.assertFalse(self.safety.get_longitudinal_allowed()) - self._rx(self._speed_msg(0)) + self._rx(self._vehicle_moving_msg(0)) - def test_sample_speed(self): + def test_vehicle_moving(self): self.assertFalse(self.safety.get_vehicle_moving()) # not moving - self.safety.safety_rx_hook(self._speed_msg(0)) + self.safety.safety_rx_hook(self._vehicle_moving_msg(0)) self.assertFalse(self.safety.get_vehicle_moving()) # speed is at threshold - self.safety.safety_rx_hook(self._speed_msg(self.STANDSTILL_THRESHOLD)) + self.safety.safety_rx_hook(self._vehicle_moving_msg(self.STANDSTILL_THRESHOLD)) self.assertFalse(self.safety.get_vehicle_moving()) # past threshold - self.safety.safety_rx_hook(self._speed_msg(self.STANDSTILL_THRESHOLD + 1)) + self.safety.safety_rx_hook(self._vehicle_moving_msg(self.STANDSTILL_THRESHOLD + 1)) self.assertTrue(self.safety.get_vehicle_moving()) def test_tx_hook_on_wrong_safety_mode(self): @@ -704,19 +778,31 @@ def test_tx_hook_on_wrong_safety_mode(self): # No point in comparing different Tesla safety modes if 'Tesla' in attr and 'Tesla' in current_test: continue - if {attr, current_test}.issubset({'TestToyotaSafety', 'TestToyotaAltBrakeSafety', 'TestToyotaStockLongitudinal'}): + if attr.startswith('TestToyota') and current_test.startswith('TestToyota'): continue if {attr, current_test}.issubset({'TestSubaruSafety', 'TestSubaruGen2Safety'}): continue if {attr, current_test}.issubset({'TestVolkswagenPqSafety', 'TestVolkswagenPqStockSafety', 'TestVolkswagenPqLongSafety'}): continue + if {attr, current_test}.issubset({'TestGmCameraSafety', 'TestGmCameraLongitudinalSafety'}): + continue if attr.startswith('TestHyundaiCanfd') and current_test.startswith('TestHyundaiCanfd'): continue + if {attr, current_test}.issubset({'TestVolkswagenMqbSafety', 'TestVolkswagenMqbStockSafety', 'TestVolkswagenMqbLongSafety'}): + continue # overlapping TX addrs, but they're not actuating messages for either car - if attr == 'TestHyundaiCanfdHDA2Long' and current_test.startswith('TestToyota'): + if attr == 'TestHyundaiCanfdHDA2LongEV' and current_test.startswith('TestToyota'): tx = list(filter(lambda m: m[0] not in [0x160, ], tx)) + # Volkswagen MQB longitudinal actuating message overlaps with the Subaru lateral actuating message + if attr == 'TestVolkswagenMqbLongSafety' and current_test.startswith('TestSubaru'): + tx = list(filter(lambda m: m[0] not in [0x122, ], tx)) + + # Volkswagen MQB and Honda Nidec ACC HUD messages overlap + if attr == 'TestVolkswagenMqbLongSafety' and current_test.startswith('TestHondaNidec'): + tx = list(filter(lambda m: m[0] not in [0x30c, ], tx)) + # TODO: Temporary, should be fixed in panda firmware, safety_honda.h if attr.startswith('TestHonda'): # exceptions for common msgs across different hondas @@ -733,4 +819,4 @@ def test_tx_hook_on_wrong_safety_mode(self): # TODO: this should be blocked if current_test in ["TestNissanSafety", "TestNissanLeafSafety"] and [addr, bus] in self.TX_MSGS: continue - self.assertFalse(self._tx(msg), f"transmit of {addr=:#x} {bus=} from {test_name} was allowed") + self.assertFalse(self._tx(msg), f"transmit of {addr=:#x} {bus=} from {test_name} during {current_test} was allowed") diff --git a/tests/safety/hyundai_common.py b/tests/safety/hyundai_common.py index d380b44b49..dac8e4a617 100644 --- a/tests/safety/hyundai_common.py +++ b/tests/safety/hyundai_common.py @@ -1,7 +1,7 @@ import numpy as np from typing import Tuple -import panda.tests.safety.common as common +from panda.tests.libpanda import libpanda_py from panda.tests.safety.common import make_msg @@ -20,7 +20,7 @@ class Buttons: class HyundaiButtonBase: # pylint: disable=no-member,abstract-method - BUTTONS_BUS = 0 # tx on this bus, rx on 0. added to all `self._tx(self._button_msg(...))` + BUTTONS_TX_BUS = 0 # tx on this bus, rx on 0 SCC_BUS = 0 # rx on this bus def test_button_sends(self): @@ -30,16 +30,16 @@ def test_button_sends(self): - CANCEL allowed while cruise is enabled """ self.safety.set_controls_allowed(0) - self.assertFalse(self._tx(self._button_msg(Buttons.RESUME, bus=self.BUTTONS_BUS))) - self.assertFalse(self._tx(self._button_msg(Buttons.SET, bus=self.BUTTONS_BUS))) + self.assertFalse(self._tx(self._button_msg(Buttons.RESUME, bus=self.BUTTONS_TX_BUS))) + self.assertFalse(self._tx(self._button_msg(Buttons.SET, bus=self.BUTTONS_TX_BUS))) self.safety.set_controls_allowed(1) - self.assertTrue(self._tx(self._button_msg(Buttons.RESUME, bus=self.BUTTONS_BUS))) - self.assertFalse(self._tx(self._button_msg(Buttons.SET, bus=self.BUTTONS_BUS))) + self.assertTrue(self._tx(self._button_msg(Buttons.RESUME, bus=self.BUTTONS_TX_BUS))) + self.assertFalse(self._tx(self._button_msg(Buttons.SET, bus=self.BUTTONS_TX_BUS))) for enabled in (True, False): self._rx(self._pcm_status_msg(enabled)) - self.assertEqual(enabled, self._tx(self._button_msg(Buttons.CANCEL, bus=self.BUTTONS_BUS))) + self.assertEqual(enabled, self._tx(self._button_msg(Buttons.CANCEL, bus=self.BUTTONS_TX_BUS))) def test_enable_control_allowed_from_cruise(self): """ @@ -107,16 +107,21 @@ def test_set_resume_buttons(self): """ SET and RESUME enter controls allowed on their falling edge. """ - for btn in range(8): - self.safety.set_controls_allowed(0) - for _ in range(10): - self._rx(self._button_msg(btn)) - self.assertFalse(self.safety.get_controls_allowed()) - - # should enter controls allowed on falling edge - if btn in (Buttons.RESUME, Buttons.SET): + for btn_prev in range(8): + for btn_cur in range(8): self._rx(self._button_msg(Buttons.NONE)) - self.assertTrue(self.safety.get_controls_allowed()) + self.safety.set_controls_allowed(0) + for _ in range(10): + self._rx(self._button_msg(btn_prev)) + self.assertFalse(self.safety.get_controls_allowed()) + + # should enter controls allowed on falling edge and not transitioning to cancel + should_enable = btn_cur != btn_prev and \ + btn_cur != Buttons.CANCEL and \ + btn_prev in (Buttons.RESUME, Buttons.SET) + + self._rx(self._button_msg(btn_cur)) + self.assertEqual(should_enable, self.safety.get_controls_allowed()) def test_cancel_button(self): self.safety.set_controls_allowed(1) @@ -138,10 +143,10 @@ def test_tester_present_allowed(self): """ addr, bus = self.DISABLED_ECU_UDS_MSG - tester_present = common.package_can_msg((addr, 0, b"\x02\x3E\x80\x00\x00\x00\x00\x00", bus)) + tester_present = libpanda_py.make_CANPacket(addr, bus, b"\x02\x3E\x80\x00\x00\x00\x00\x00") self.assertTrue(self.safety.safety_tx_hook(tester_present)) - not_tester_present = common.package_can_msg((addr, 0, b"\x03\xAA\xAA\x00\x00\x00\x00\x00", bus)) + not_tester_present = libpanda_py.make_CANPacket(addr, bus, b"\x03\xAA\xAA\x00\x00\x00\x00\x00") self.assertFalse(self.safety.safety_tx_hook(not_tester_present)) def test_disabled_ecu_alive(self): diff --git a/tests/safety/libpandasafety_py.py b/tests/safety/libpandasafety_py.py deleted file mode 100644 index 2b370c66ac..0000000000 --- a/tests/safety/libpandasafety_py.py +++ /dev/null @@ -1,136 +0,0 @@ -import os - -from typing import List -from cffi import FFI - -can_dir = os.path.dirname(os.path.abspath(__file__)) -libpandasafety_fn = os.path.join(can_dir, "libpandasafety.so") - -ffi = FFI() -ffi.cdef(""" -typedef struct { - unsigned char reserved : 1; - unsigned char bus : 3; - unsigned char data_len_code : 4; - unsigned char rejected : 1; - unsigned char returned : 1; - unsigned char extended : 1; - unsigned int addr : 29; - unsigned char data[64]; -} CANPacket_t; -""", packed=True) - -ffi.cdef(""" -typedef struct -{ - uint32_t CNT; -} TIM_TypeDef; - -void set_controls_allowed(bool c); -bool get_controls_allowed(void); -bool get_longitudinal_allowed(void); -void set_alternative_experience(int mode); -int get_alternative_experience(void); -void set_relay_malfunction(bool c); -bool get_relay_malfunction(void); -void set_gas_interceptor_detected(bool c); -bool get_gas_interceptor_detected(void); -int get_gas_interceptor_prev(void); -bool get_gas_pressed_prev(void); -bool get_brake_pressed_prev(void); -bool get_regen_braking_prev(void); -bool get_acc_main_on(void); - -void set_torque_meas(int min, int max); -int get_torque_meas_min(void); -int get_torque_meas_max(void); -void set_torque_driver(int min, int max); -int get_torque_driver_min(void); -int get_torque_driver_max(void); -void set_desired_torque_last(int t); -void set_rt_torque_last(int t); -void set_desired_angle_last(int t); - -bool get_cruise_engaged_prev(void); -bool get_vehicle_moving(void); -int get_hw_type(void); -void set_timer(uint32_t t); - -int safety_rx_hook(CANPacket_t *to_send); -int safety_tx_hook(CANPacket_t *to_push); -int safety_fwd_hook(int bus_num, CANPacket_t *to_fwd); -int set_safety_hooks(uint16_t mode, uint16_t param); - -void safety_tick_current_rx_checks(); -bool addr_checks_valid(); - -void init_tests(void); - -void init_tests_honda(void); -void set_honda_fwd_brake(bool c); -void set_honda_alt_brake_msg(bool c); -void set_honda_bosch_long(bool c); -int get_honda_hw(void); - -""") - - -class CANPacket: - reserved: int - bus: int - data_len_code: int - rejected: int - returned: int - extended: int - addr: int - data: List[int] - - -class PandaSafety: - def set_controls_allowed(self, c: bool) -> None: ... - def get_controls_allowed(self) -> bool: ... - def get_longitudinal_allowed(self) -> bool: ... - def set_alternative_experience(self, mode: int) -> None: ... - def get_alternative_experience(self) -> int: ... - def set_relay_malfunction(self, c: bool) -> None: ... - def get_relay_malfunction(self) -> bool: ... - def set_gas_interceptor_detected(self, c: bool) -> None: ... - def get_gas_interceptor_detected(self) -> bool: ... - def get_gas_interceptor_prev(self) -> int: ... - def get_gas_pressed_prev(self) -> bool: ... - def get_brake_pressed_prev(self) -> bool: ... - def get_regen_braking_prev(self) -> bool: ... - def get_acc_main_on(self) -> bool: ... - - def set_torque_meas(self, min: int, max: int) -> None: ... # pylint: disable=redefined-builtin - def get_torque_meas_min(self) -> int: ... - def get_torque_meas_max(self) -> int: ... - def set_torque_driver(self, min: int, max: int) -> None: ... # pylint: disable=redefined-builtin - def get_torque_driver_min(self) -> int: ... - def get_torque_driver_max(self) -> int: ... - def set_desired_torque_last(self, t: int) -> None: ... - def set_rt_torque_last(self, t: int) -> None: ... - def set_desired_angle_last(self, t: int) -> None: ... - - def get_cruise_engaged_prev(self) -> bool: ... - def get_vehicle_moving(self) -> bool: ... - def get_hw_type(self) -> int: ... - def set_timer(self, t: int) -> None: ... - - def safety_rx_hook(self, to_send: CANPacket) -> int: ... - def safety_tx_hook(self, to_push: CANPacket) -> int: ... - def safety_fwd_hook(self, bus_num: int, to_fwd: CANPacket) -> int: ... - def set_safety_hooks(self, mode: int, param: int) -> int: ... - - def safety_tick_current_rx_checks(self) -> None: ... - def addr_checks_valid(self) -> bool: ... - - def init_tests(self) -> None: ... - - def init_tests_honda(self) -> None: ... - def set_honda_fwd_brake(self, c: bool) -> None: ... - def set_honda_alt_brake_msg(self, c: bool) -> None: ... - def set_honda_bosch_long(self, c: bool) -> None: ... - def get_honda_hw(self) -> int: ... - -libpandasafety: PandaSafety = ffi.dlopen(libpandasafety_fn) diff --git a/tests/safety/test.sh b/tests/safety/test.sh index cf48446520..70164ec54e 100755 --- a/tests/safety/test.sh +++ b/tests/safety/test.sh @@ -4,9 +4,8 @@ # Make sure test fails if one HW_TYPE fails set -e -scons -u --test - -for hw_type in {0..7}; do +HW_TYPES=( 6 7 9 ) +for hw_type in "${HW_TYPES[@]}"; do echo "Testing HW_TYPE: $hw_type" HW_TYPE=$hw_type python -m unittest discover . done diff --git a/tests/safety/test_chrysler.py b/tests/safety/test_chrysler.py index bcf83cb49c..b9390f5536 100755 --- a/tests/safety/test_chrysler.py +++ b/tests/safety/test_chrysler.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 import unittest from panda import Panda -from panda.tests.safety import libpandasafety_py +from panda.tests.libpanda import libpanda_py import panda.tests.safety.common as common from panda.tests.safety.common import CANPackerPanda @@ -25,7 +25,7 @@ class TestChryslerSafety(common.PandaSafetyTest, common.MotorTorqueSteeringSafet def setUp(self): self.packer = CANPackerPanda("chrysler_pacifica_2017_hybrid_generated") - self.safety = libpandasafety_py.libpandasafety + self.safety = libpanda_py.libpanda self.safety.set_safety_hooks(Panda.SAFETY_CHRYSLER, 0) self.safety.init_tests() @@ -79,12 +79,13 @@ class TestChryslerRamDTSafety(TestChryslerSafety): MAX_RATE_UP = 6 MAX_RATE_DOWN = 6 + MAX_TORQUE = 350 DAS_BUS = 2 def setUp(self): self.packer = CANPackerPanda("chrysler_ram_dt_generated") - self.safety = libpandasafety_py.libpandasafety + self.safety = libpanda_py.libpanda self.safety.set_safety_hooks(Panda.SAFETY_CHRYSLER, Panda.FLAG_CHRYSLER_RAM_DT) self.safety.init_tests() @@ -106,7 +107,7 @@ class TestChryslerRamHDSafety(TestChryslerSafety): def setUp(self): self.packer = CANPackerPanda("chrysler_ram_hd_generated") - self.safety = libpandasafety_py.libpandasafety + self.safety = libpanda_py.libpanda self.safety.set_safety_hooks(Panda.SAFETY_CHRYSLER, Panda.FLAG_CHRYSLER_RAM_HD) self.safety.init_tests() diff --git a/tests/safety/test_ford.py b/tests/safety/test_ford.py index 4f9eb6687b..8d34598621 100755 --- a/tests/safety/test_ford.py +++ b/tests/safety/test_ford.py @@ -1,14 +1,17 @@ #!/usr/bin/env python3 +import numpy as np import unittest import panda.tests.safety.common as common from panda import Panda -from panda.tests.safety import libpandasafety_py +from panda.tests.libpanda import libpanda_py from panda.tests.safety.common import CANPackerPanda MSG_EngBrakeData = 0x165 # RX from PCM, for driver brake pedal and cruise state MSG_EngVehicleSpThrottle = 0x204 # RX from PCM, for driver throttle input +MSG_BrakeSysFeatures = 0x415 # RX from ABS, for vehicle speed +MSG_Yaw_Data_FD1 = 0x91 # RX from RCM, for yaw rate MSG_Steering_Data_FD1 = 0x083 # TX by OP, various driver switches and LKAS/CC buttons MSG_ACCDATA_3 = 0x18A # TX by OP, ACC/TJA user interface MSG_Lane_Assist_Data1 = 0x3CA # TX by OP, Lane Keep Assist @@ -16,6 +19,29 @@ MSG_IPMA_Data = 0x3D8 # TX by OP, IPMA and LKAS user interface +def checksum(msg): + addr, t, dat, bus = msg + ret = bytearray(dat) + + if addr == MSG_Yaw_Data_FD1: + chksum = dat[0] + dat[1] # VehRol_W_Actl + chksum += dat[2] + dat[3] # VehYaw_W_Actl + chksum += dat[5] # VehRollYaw_No_Cnt + chksum += dat[6] >> 6 # VehRolWActl_D_Qf + chksum += (dat[6] >> 4) & 0x3 # VehYawWActl_D_Qf + chksum = 0xff - (chksum & 0xff) + ret[4] = chksum + + elif addr == MSG_BrakeSysFeatures: + chksum = dat[0] + dat[1] # Veh_V_ActlBrk + chksum += (dat[2] >> 2) & 0xf # VehVActlBrk_No_Cnt + chksum += dat[2] >> 6 # VehVActlBrk_D_Qf + chksum = 0xff - (chksum & 0xff) + ret[3] = chksum + + return addr, t, ret, bus + + class Buttons: CANCEL = 0 RESUME = 1 @@ -34,9 +60,12 @@ class TestFordSafety(common.PandaSafetyTest): FWD_BLACKLISTED_ADDRS = {2: [MSG_ACCDATA_3, MSG_Lane_Assist_Data1, MSG_LateralMotionControl, MSG_IPMA_Data]} FWD_BUS_LOOKUP = {0: 2, 2: 0} + cnt_speed = 0 + cnt_yaw_rate = 0 + def setUp(self): self.packer = CANPackerPanda("ford_lincoln_base_pt") - self.safety = libpandasafety_py.libpandasafety + self.safety = libpanda_py.libpanda self.safety.set_safety_hooks(Panda.SAFETY_FORD, 0) self.safety.init_tests() @@ -51,11 +80,24 @@ def _user_brake_msg(self, brake: bool): } return self.packer.make_can_msg_panda("EngBrakeData", 0, values) + # Vehicle speed + def _speed_msg(self, speed: float, quality_flag=True): + values = {"Veh_V_ActlBrk": speed * 3.6, "VehVActlBrk_D_Qf": 3 if quality_flag else 0, "VehVActlBrk_No_Cnt": self.cnt_speed % 16} + self.__class__.cnt_speed += 1 + return self.packer.make_can_msg_panda("BrakeSysFeatures", 0, values, fix_checksum=checksum) + # Standstill state - def _speed_msg(self, speed: float): + def _vehicle_moving_msg(self, speed: float): values = {"VehStop_D_Stat": 1 if speed <= self.STANDSTILL_THRESHOLD else 0} return self.packer.make_can_msg_panda("DesiredTorqBrk", 0, values) + # Current curvature + def _yaw_rate_msg(self, curvature: float, speed: float, quality_flag=True): + values = {"VehYaw_W_Actl": curvature * speed, "VehYawWActl_D_Qf": 3 if quality_flag else 0, + "VehRolWActl_D_Qf": 3 if quality_flag else 0, "VehRollYaw_No_Cnt": self.cnt_yaw_rate % 256} + self.__class__.cnt_yaw_rate += 1 + return self.packer.make_can_msg_panda("Yaw_Data_FD1", 0, values, fix_checksum=checksum) + # Drive throttle input def _user_gas_msg(self, gas: float): values = {"ApedPos_Pc_ActlArb": gas} @@ -80,9 +122,13 @@ def _lkas_command_msg(self, action: int): return self.packer.make_can_msg_panda("Lane_Assist_Data1", 0, values) # TJA command - def _tja_command_msg(self, enabled: bool): + def _tja_command_msg(self, enabled: bool, path_offset: float, path_angle: float, curvature: float, curvature_rate: float): values = { "LatCtl_D_Rq": 1 if enabled else 0, + "LatCtlPathOffst_L_Actl": path_offset, # Path offset [-5.12|5.11] meter + "LatCtlPath_An_Actl": path_angle, # Path angle [-0.5|0.5235] radians + "LatCtlCurv_NoRate_Actl": curvature_rate, # Curvature rate [-0.001024|0.00102375] 1/meter^2 + "LatCtlCurv_No_Actl": curvature, # Curvature [-0.02|0.02094] 1/meter } return self.packer.make_can_msg_panda("LateralMotionControl", 0, values) @@ -95,14 +141,44 @@ def _acc_button_msg(self, button: int, bus: int): } return self.packer.make_can_msg_panda("Steering_Data_FD1", bus, values) - def test_steer_allowed(self): - self.safety.set_controls_allowed(1) - self.assertTrue(self._tx(self._tja_command_msg(1))) - self.assertTrue(self.safety.get_controls_allowed()) + def test_rx_hook(self): + # checksum, counter, and quality flag checks + for quality_flag in [True, False]: + for msg in ["speed", "yaw"]: + self.safety.set_controls_allowed(True) + # send multiple times to verify counter checks + for _ in range(10): + if msg == "speed": + to_push = self._speed_msg(0, quality_flag=quality_flag) + elif msg == "yaw": + to_push = self._yaw_rate_msg(0, 0, quality_flag=quality_flag) + + self.assertEqual(quality_flag, self._rx(to_push)) + self.assertEqual(quality_flag, self.safety.get_controls_allowed()) + + # Mess with checksum to make it fail + to_push[0].data[3] = 0 # Speed checksum & half of yaw signal + self.assertFalse(self._rx(to_push)) + self.assertFalse(self.safety.get_controls_allowed()) - self.safety.set_controls_allowed(0) - self.assertFalse(self._tx(self._tja_command_msg(1))) - self.assertFalse(self.safety.get_controls_allowed()) + def test_steer_allowed(self): + path_offsets = np.arange(-5.12, 5.11, 1).round() + path_angles = np.arange(-0.5, 0.5235, 0.1).round(1) + curvature_rates = np.arange(-0.001024, 0.00102375, 0.001).round(3) + curvatures = np.arange(-0.02, 0.02094, 0.01).round(2) + + for controls_allowed in (True, False): + for steer_control_enabled in (True, False): + for path_offset in path_offsets: + for path_angle in path_angles: + for curvature_rate in curvature_rates: + for curvature in curvatures: + self.safety.set_controls_allowed(controls_allowed) + enabled = steer_control_enabled or curvature != 0 + + should_tx = path_offset == 0 and path_angle == 0 and curvature_rate == 0 + should_tx = should_tx and (not enabled or controls_allowed) + self.assertEqual(should_tx, self._tx(self._tja_command_msg(steer_control_enabled, path_offset, path_angle, curvature, curvature_rate))) def test_prevent_lkas_action(self): self.safety.set_controls_allowed(1) diff --git a/tests/safety/test_gm.py b/tests/safety/test_gm.py index 60ddfb99cd..ff52471897 100755 --- a/tests/safety/test_gm.py +++ b/tests/safety/test_gm.py @@ -2,13 +2,9 @@ import unittest from typing import Dict, List from panda import Panda -from panda.tests.safety import libpandasafety_py +from panda.tests.libpanda import libpanda_py import panda.tests.safety.common as common -from panda.tests.safety.common import CANPackerPanda, ALTERNATIVE_EXPERIENCE - -MAX_BRAKE = 400 -MAX_GAS = 3072 -MAX_REGEN = 1404 +from panda.tests.safety.common import CANPackerPanda class Buttons: @@ -18,21 +14,69 @@ class Buttons: CANCEL = 6 +class GmLongitudinalBase(common.PandaSafetyTest): + # pylint: disable=no-member,abstract-method + + PCM_CRUISE = False # openpilot can control the PCM state if longitudinal + + def test_set_resume_buttons(self): + """ + SET and RESUME enter controls allowed on their falling and rising edges, respectively. + """ + for btn_prev in range(8): + for btn_cur in range(8): + with self.subTest(btn_prev=btn_prev, btn_cur=btn_cur): + self._rx(self._button_msg(btn_prev)) + self.safety.set_controls_allowed(0) + for _ in range(10): + self._rx(self._button_msg(btn_cur)) + + should_enable = btn_cur != Buttons.DECEL_SET and btn_prev == Buttons.DECEL_SET + should_enable = should_enable or (btn_cur == Buttons.RES_ACCEL and btn_prev != Buttons.RES_ACCEL) + should_enable = should_enable and btn_cur != Buttons.CANCEL + self.assertEqual(should_enable, self.safety.get_controls_allowed()) + + def test_cancel_button(self): + self.safety.set_controls_allowed(1) + self._rx(self._button_msg(Buttons.CANCEL)) + self.assertFalse(self.safety.get_controls_allowed()) + + # override these tests from PandaSafetyTest, GM longitudinal uses button enable + def test_disable_control_allowed_from_cruise(self): + pass + + def test_enable_control_allowed_from_cruise(self): + pass + + def test_cruise_engaged_prev(self): + pass + + def _pcm_status_msg(self, enable): + pass + + class TestGmSafetyBase(common.PandaSafetyTest, common.DriverTorqueSteeringSafetyTest): STANDSTILL_THRESHOLD = 10 * 0.0311 RELAY_MALFUNCTION_ADDR = 384 RELAY_MALFUNCTION_BUS = 0 - BUTTONS_BUS = 0 - USER_BRAKE_THRESHOLD = 0 + BUTTONS_BUS = 0 # rx or tx + BRAKE_BUS = 0 # tx only - MAX_RATE_UP = 7 - MAX_RATE_DOWN = 17 + MAX_RATE_UP = 10 + MAX_RATE_DOWN = 25 MAX_TORQUE = 300 MAX_RT_DELTA = 128 RT_INTERVAL = 250000 DRIVER_TORQUE_ALLOWANCE = 50 DRIVER_TORQUE_FACTOR = 4 + MAX_GAS = 0 + MAX_REGEN = 0 + INACTIVE_REGEN = 0 + MAX_BRAKE = 0 + + PCM_CRUISE = True # openpilot is tied to the PCM state if not longitudinal + @classmethod def setUpClass(cls): if cls.__name__ == "TestGmSafetyBase": @@ -43,12 +87,16 @@ def setUpClass(cls): def setUp(self): self.packer = CANPackerPanda("gm_global_a_powertrain_generated") self.packer_chassis = CANPackerPanda("gm_global_a_chassis") - self.safety = libpandasafety_py.libpandasafety + self.safety = libpanda_py.libpanda self.safety.set_safety_hooks(Panda.SAFETY_GM, 0) self.safety.init_tests() def _pcm_status_msg(self, enable): - raise NotImplementedError + if self.PCM_CRUISE: + values = {"CruiseState": enable} + return self.packer.make_can_msg_panda("AcceleratorPedal2", 0, values) + else: + raise NotImplementedError def _speed_msg(self, speed): values = {"%sWheelSpd" % s: speed for s in ["RL", "RR"]} @@ -56,7 +104,7 @@ def _speed_msg(self, speed): def _user_brake_msg(self, brake): # GM safety has a brake threshold of 8 - values = {"BrakePedalPos": self.USER_BRAKE_THRESHOLD if brake else 0} + values = {"BrakePedalPos": 8 if brake else 0} return self.packer.make_can_msg_panda("ECMAcceleratorPos", 0, values) def _user_regen_msg(self, regen): @@ -65,11 +113,14 @@ def _user_regen_msg(self, regen): def _user_gas_msg(self, gas): values = {"AcceleratorPedal2": 1 if gas else 0} + if self.PCM_CRUISE: + # Fill CruiseState with expected value if the safety mode reads cruise state from gas msg + values["CruiseState"] = self.safety.get_controls_allowed() return self.packer.make_can_msg_panda("AcceleratorPedal2", 0, values) def _send_brake_msg(self, brake): values = {"FrictionBrakeCmd": -brake} - return self.packer_chassis.make_can_msg_panda("EBCMFrictionBrakeCmd", 2, values) + return self.packer_chassis.make_can_msg_panda("EBCMFrictionBrakeCmd", self.BRAKE_BUS, values) def _send_gas_msg(self, gas): values = {"GasRegenCmd": gas} @@ -87,149 +138,76 @@ def _button_msg(self, buttons): values = {"ACCButtons": buttons} return self.packer.make_can_msg_panda("ASCMSteeringButton", self.BUTTONS_BUS, values) - def test_brake_safety_check(self, stock_longitudinal=False): + def test_brake_safety_check(self): for enabled in [0, 1]: for b in range(0, 500): self.safety.set_controls_allowed(enabled) - if abs(b) > MAX_BRAKE or (not enabled and b != 0) or stock_longitudinal: + if abs(b) > self.MAX_BRAKE or (not enabled and b != 0): self.assertFalse(self._tx(self._send_brake_msg(b))) else: self.assertTrue(self._tx(self._send_brake_msg(b))) - def test_gas_safety_check(self, stock_longitudinal=False): + def test_gas_safety_check(self): + # Block if enabled and out of actuation range, disabled and not inactive regen, or if stock longitudinal for enabled in [0, 1]: - for g in range(0, 2**12 - 1): + for gas_regen in range(0, 2 ** 12 - 1): self.safety.set_controls_allowed(enabled) - if abs(g) > MAX_GAS or (not enabled and g != MAX_REGEN) or stock_longitudinal: - self.assertFalse(self._tx(self._send_gas_msg(g))) - else: - self.assertTrue(self._tx(self._send_gas_msg(g))) - - def test_tx_hook_on_pedal_pressed(self): - for pedal in ['brake', 'gas']: - if pedal == 'brake': - # brake_pressed_prev and vehicle_moving - self._rx(self._speed_msg(100)) - self._rx(self._user_brake_msg(1)) - elif pedal == 'gas': - # gas_pressed_prev - self._rx(self._user_gas_msg(MAX_GAS)) - - self.safety.set_controls_allowed(1) - self.assertFalse(self._tx(self._send_brake_msg(MAX_BRAKE))) - self.assertFalse(self._tx(self._torque_cmd_msg(self.MAX_RATE_UP))) - self.assertFalse(self._tx(self._send_gas_msg(MAX_GAS))) - - # reset status - self.safety.set_controls_allowed(0) - self._tx(self._send_brake_msg(0)) - self._tx(self._torque_cmd_msg(0)) - if pedal == 'brake': - self._rx(self._speed_msg(0)) - self._rx(self._user_brake_msg(0)) - elif pedal == 'gas': - self._rx(self._user_gas_msg(0)) - - def test_tx_hook_on_pedal_pressed_on_alternative_gas_experience(self): - for pedal in ['brake', 'gas']: - self.safety.set_alternative_experience(ALTERNATIVE_EXPERIENCE.DISABLE_DISENGAGE_ON_GAS) - if pedal == 'brake': - # brake_pressed_prev and vehicle_moving - self._rx(self._speed_msg(100)) - self._rx(self._user_brake_msg(1)) - allow_ctrl = False - elif pedal == 'gas': - # gas_pressed_prev - self._rx(self._user_gas_msg(MAX_GAS)) - allow_ctrl = True - - # Test we allow lateral on gas press, but never longitudinal - self.safety.set_controls_allowed(1) - self.assertEqual(allow_ctrl, self._tx(self._torque_cmd_msg(self.MAX_RATE_UP))) - self.assertFalse(self._tx(self._send_brake_msg(MAX_BRAKE))) - self.assertFalse(self._tx(self._send_gas_msg(MAX_GAS))) - - # reset status - if pedal == 'brake': - self._rx(self._speed_msg(0)) - self._rx(self._user_brake_msg(0)) - elif pedal == 'gas': - self._rx(self._user_gas_msg(0)) - - -class TestGmAscmSafety(TestGmSafetyBase): + should_tx = ((enabled and self.MAX_REGEN <= gas_regen <= self.MAX_GAS) or + (not enabled and gas_regen == self.INACTIVE_REGEN)) + self.assertEqual(should_tx, self._tx(self._send_gas_msg(gas_regen)), (enabled, gas_regen)) + + +class TestGmAscmSafety(GmLongitudinalBase, TestGmSafetyBase): TX_MSGS = [[384, 0], [1033, 0], [1034, 0], [715, 0], [880, 0], # pt bus [161, 1], [774, 1], [776, 1], [784, 1], # obs bus [789, 2], # ch bus [0x104c006c, 3], [0x10400060, 3]] # gmlan FWD_BLACKLISTED_ADDRS: Dict[int, List[int]] = {} FWD_BUS_LOOKUP: Dict[int, int] = {} - USER_BRAKE_THRESHOLD = 8 + BRAKE_BUS = 2 + + MAX_GAS = 3072 + MAX_REGEN = 1404 + INACTIVE_REGEN = 1404 + MAX_BRAKE = 400 def setUp(self): self.packer = CANPackerPanda("gm_global_a_powertrain_generated") self.packer_chassis = CANPackerPanda("gm_global_a_chassis") - self.safety = libpandasafety_py.libpandasafety + self.safety = libpanda_py.libpanda self.safety.set_safety_hooks(Panda.SAFETY_GM, 0) self.safety.init_tests() - # override these tests from PandaSafetyTest, ASCM GM uses button enable - def test_disable_control_allowed_from_cruise(self): - pass - - def test_enable_control_allowed_from_cruise(self): - pass - - def test_cruise_engaged_prev(self): - pass - def _pcm_status_msg(self, enable): - raise NotImplementedError +class TestGmCameraSafetyBase(TestGmSafetyBase): - def test_set_resume_buttons(self): - """ - SET and RESUME enter controls allowed on their falling edge. - """ - for btn in range(8): - self.safety.set_controls_allowed(0) - for _ in range(10): - self._rx(self._button_msg(btn)) - self.assertFalse(self.safety.get_controls_allowed()) + FWD_BUS_LOOKUP = {0: 2, 2: 0} - # should enter controls allowed on falling edge - if btn in (Buttons.RES_ACCEL, Buttons.DECEL_SET): - self._rx(self._button_msg(Buttons.UNPRESS)) - self.assertTrue(self.safety.get_controls_allowed()) + @classmethod + def setUpClass(cls): + if cls.__name__ == "TestGmCameraSafetyBase": + cls.packer = None + cls.safety = None + raise unittest.SkipTest - def test_cancel_button(self): - self.safety.set_controls_allowed(1) - self._rx(self._button_msg(Buttons.CANCEL)) - self.assertFalse(self.safety.get_controls_allowed()) + def _user_brake_msg(self, brake): + values = {"BrakePressed": brake} + return self.packer.make_can_msg_panda("ECMEngineStatus", 0, values) -class TestGmCameraSafety(TestGmSafetyBase): - TX_MSGS = [[384, 0]] # pt bus - FWD_BLACKLISTED_ADDRS = {2: [384]} # LKAS message, ACC messages are (715, 880, 789) - FWD_BUS_LOOKUP = {0: 2, 2: 0} +class TestGmCameraSafety(TestGmCameraSafetyBase): + TX_MSGS = [[384, 0], # pt bus + [388, 2]] # camera bus + FWD_BLACKLISTED_ADDRS = {2: [384], 0: [388]} # block LKAS message and PSCMStatus BUTTONS_BUS = 2 # tx only - USER_BRAKE_THRESHOLD = 20 def setUp(self): self.packer = CANPackerPanda("gm_global_a_powertrain_generated") self.packer_chassis = CANPackerPanda("gm_global_a_chassis") - self.safety = libpandasafety_py.libpandasafety + self.safety = libpanda_py.libpanda self.safety.set_safety_hooks(Panda.SAFETY_GM, Panda.FLAG_GM_HW_CAM) self.safety.init_tests() - def _user_gas_msg(self, gas): - cruise_active = self.safety.get_controls_allowed() - values = {"AcceleratorPedal2": 1 if gas else 0, "CruiseState": cruise_active} - return self.packer.make_can_msg_panda("AcceleratorPedal2", 0, values) - - def _pcm_status_msg(self, enable): - values = {"CruiseState": enable} - return self.packer.make_can_msg_panda("AcceleratorPedal2", 0, values) - def test_buttons(self): # Only CANCEL button is allowed while cruise is enabled self.safety.set_controls_allowed(0) @@ -244,12 +222,31 @@ def test_buttons(self): self._rx(self._pcm_status_msg(enabled)) self.assertEqual(enabled, self._tx(self._button_msg(Buttons.CANCEL))) - # Uses stock longitudinal, allow no longitudinal actuation - def test_brake_safety_check(self, stock_longitudinal=True): - super().test_brake_safety_check(stock_longitudinal=stock_longitudinal) + # GM Cam safety mode does not allow longitudinal messages + def test_brake_safety_check(self): + pass + + def test_gas_safety_check(self): + pass + + +class TestGmCameraLongitudinalSafety(GmLongitudinalBase, TestGmCameraSafetyBase): + TX_MSGS = [[384, 0], [789, 0], [715, 0], [880, 0], # pt bus + [388, 2]] # camera bus + FWD_BLACKLISTED_ADDRS = {2: [384, 715, 880, 789], 0: [388]} # block LKAS, ACC messages and PSCMStatus + BUTTONS_BUS = 0 # rx only + + MAX_GAS = 3400 + MAX_REGEN = 1514 + INACTIVE_REGEN = 1554 + MAX_BRAKE = 400 - def test_gas_safety_check(self, stock_longitudinal=True): - super().test_gas_safety_check(stock_longitudinal=stock_longitudinal) + def setUp(self): + self.packer = CANPackerPanda("gm_global_a_powertrain_generated") + self.packer_chassis = CANPackerPanda("gm_global_a_chassis") + self.safety = libpanda_py.libpanda + self.safety.set_safety_hooks(Panda.SAFETY_GM, Panda.FLAG_GM_HW_CAM | Panda.FLAG_GM_HW_CAM_LONG) + self.safety.init_tests() if __name__ == "__main__": diff --git a/tests/safety/test_honda.py b/tests/safety/test_honda.py index 0eac21ad07..9a429771a0 100755 --- a/tests/safety/test_honda.py +++ b/tests/safety/test_honda.py @@ -4,9 +4,9 @@ from typing import Optional from panda import Panda -from panda.tests.safety import libpandasafety_py +from panda.tests.libpanda import libpanda_py import panda.tests.safety.common as common -from panda.tests.safety.common import CANPackerPanda, make_msg, MAX_WRONG_COUNTERS, ALTERNATIVE_EXPERIENCE +from panda.tests.safety.common import CANPackerPanda, make_msg, MAX_WRONG_COUNTERS class Btn: NONE = 0 @@ -138,39 +138,6 @@ def test_rx_hook(self): self._rx(self._button_msg(Btn.SET, main_on=True)) self.assertTrue(self.safety.get_controls_allowed()) - def test_tx_hook_on_pedal_pressed(self): - for mode in [ALTERNATIVE_EXPERIENCE.DEFAULT, ALTERNATIVE_EXPERIENCE.DISABLE_DISENGAGE_ON_GAS]: - for pedal in ['brake', 'gas']: - self.safety.set_alternative_experience(mode) - allow_ctrl = False - if pedal == 'brake': - # brake_pressed_prev and vehicle_moving - self._rx(self._speed_msg(100)) - self._rx(self._user_brake_msg(1)) - elif pedal == 'gas': - # gas_pressed_prev - self._rx(self._user_gas_msg(1)) - allow_ctrl = mode == ALTERNATIVE_EXPERIENCE.DISABLE_DISENGAGE_ON_GAS - - self.safety.set_controls_allowed(1) - hw = self.safety.get_honda_hw() - if hw == HONDA_NIDEC: - self.safety.set_honda_fwd_brake(False) - self.assertEqual(allow_ctrl, self._tx(self._send_brake_msg(self.MAX_BRAKE))) - self.assertEqual(allow_ctrl, self._tx(self._send_steer_msg(0x1000))) - - # reset status - self.safety.set_controls_allowed(0) - self.safety.set_alternative_experience(ALTERNATIVE_EXPERIENCE.DEFAULT) - if hw == HONDA_NIDEC: - self._tx(self._send_brake_msg(0)) - self._tx(self._send_steer_msg(0)) - if pedal == 'brake': - self._rx(self._speed_msg(0)) - self._rx(self._user_brake_msg(0)) - elif pedal == 'gas': - self._rx(self._user_gas_msg(0)) - class HondaPcmEnableBase(common.PandaSafetyTest): # pylint: disable=no-member,abstract-method @@ -299,6 +266,7 @@ class TestHondaNidecSafetyBase(HondaBase): BUTTONS_BUS = 0 INTERCEPTOR_THRESHOLD = 492 + MAX_GAS = 198 @classmethod def setUpClass(cls): @@ -308,9 +276,9 @@ def setUpClass(cls): def setUp(self): self.packer = CANPackerPanda("honda_civic_touring_2016_can_generated") - self.safety = libpandasafety_py.libpandasafety + self.safety = libpanda_py.libpanda self.safety.set_safety_hooks(Panda.SAFETY_HONDA_NIDEC, 0) - self.safety.init_tests_honda() + self.safety.init_tests() def _interceptor_gas_cmd(self, gas): return interceptor_msg(gas, 0x200) @@ -330,9 +298,9 @@ def _send_acc_hud_msg(self, pcm_gas, pcm_speed): def test_acc_hud_safety_check(self): for controls_allowed in [True, False]: self.safety.set_controls_allowed(controls_allowed) - for pcm_gas in range(0, 0xc6): + for pcm_gas in range(0, 255): for pcm_speed in range(0, 100): - send = True if controls_allowed else pcm_gas == 0 and pcm_speed == 0 + send = pcm_gas <= self.MAX_GAS if controls_allowed else pcm_gas == 0 and pcm_speed == 0 self.assertEqual(send, self.safety.safety_tx_hook(self._send_acc_hud_msg(pcm_gas, pcm_speed))) def test_fwd_hook(self): @@ -362,22 +330,6 @@ def test_brake_safety_check(self): self.assertEqual(send, self._tx(self._send_brake_msg(brake))) self.safety.set_honda_fwd_brake(False) - def test_tx_hook_on_interceptor_pressed(self): - for mode in [ALTERNATIVE_EXPERIENCE.DEFAULT, ALTERNATIVE_EXPERIENCE.DISABLE_DISENGAGE_ON_GAS]: - self.safety.set_alternative_experience(mode) - # gas_interceptor_prev > INTERCEPTOR_THRESHOLD - self._rx(self._interceptor_user_gas(self.INTERCEPTOR_THRESHOLD + 1)) - self._rx(self._interceptor_user_gas(self.INTERCEPTOR_THRESHOLD + 1)) - allow_ctrl = mode == ALTERNATIVE_EXPERIENCE.DISABLE_DISENGAGE_ON_GAS - - self.safety.set_controls_allowed(1) - self.safety.set_honda_fwd_brake(False) - - # Test we allow lateral, but never longitudinal - self.assertFalse(self._tx(self._interceptor_gas_cmd(self.INTERCEPTOR_THRESHOLD))) - self.assertFalse(self._tx(self._send_brake_msg(self.MAX_BRAKE))) - self.assertEqual(allow_ctrl, self._tx(self._send_steer_msg(0x1000))) - class TestHondaNidecSafety(HondaPcmEnableBase, TestHondaNidecSafetyBase): """ @@ -401,9 +353,9 @@ class TestHondaNidecAltSafety(TestHondaNidecSafety): """ def setUp(self): self.packer = CANPackerPanda("acura_ilx_2016_can_generated") - self.safety = libpandasafety_py.libpandasafety + self.safety = libpanda_py.libpanda self.safety.set_safety_hooks(Panda.SAFETY_HONDA_NIDEC, Panda.FLAG_HONDA_NIDEC_ALT) - self.safety.init_tests_honda() + self.safety.init_tests() def _acc_state_msg(self, main_on): values = {"MAIN_ON": main_on, "COUNTER": self.cnt_acc_state % 4} @@ -423,9 +375,9 @@ class TestHondaNidecAltInterceptorSafety(TestHondaNidecSafety, common.Intercepto """ def setUp(self): self.packer = CANPackerPanda("acura_ilx_2016_can_generated") - self.safety = libpandasafety_py.libpandasafety + self.safety = libpanda_py.libpanda self.safety.set_safety_hooks(Panda.SAFETY_HONDA_NIDEC, Panda.FLAG_HONDA_NIDEC_ALT) - self.safety.init_tests_honda() + self.safety.init_tests() def _acc_state_msg(self, main_on): values = {"MAIN_ON": main_on, "COUNTER": self.cnt_acc_state % 4} @@ -460,7 +412,7 @@ def setUpClass(cls): def setUp(self): self.packer = CANPackerPanda("honda_accord_2018_can_generated") - self.safety = libpandasafety_py.libpandasafety + self.safety = libpanda_py.libpanda def _alt_brake_msg(self, brake): values = {"BRAKE_PRESSED": brake, "COUNTER": self.cnt_brake % 4} @@ -507,7 +459,7 @@ class TestHondaBoschSafety(HondaPcmEnableBase, TestHondaBoschSafetyBase): def setUp(self): super().setUp() self.safety.set_safety_hooks(Panda.SAFETY_HONDA_BOSCH, 0) - self.safety.init_tests_honda() + self.safety.init_tests() class TestHondaBoschLongSafety(HondaButtonEnableBase, TestHondaBoschSafetyBase): @@ -516,7 +468,8 @@ class TestHondaBoschLongSafety(HondaButtonEnableBase, TestHondaBoschSafetyBase): """ NO_GAS = -30000 MAX_GAS = 2000 - MAX_BRAKE = -3.5 + MAX_ACCEL = 2.0 # accel is used for brakes, but openpilot can set positive values + MIN_ACCEL = -3.5 STEER_BUS = 1 TX_MSGS = [[0xE4, 1], [0x1DF, 1], [0x1EF, 1], [0x1FA, 1], [0x30C, 1], [0x33D, 1], [0x33DA, 1], [0x33DB, 1], [0x39F, 1], [0x18DAB0F1, 1]] @@ -525,7 +478,7 @@ class TestHondaBoschLongSafety(HondaButtonEnableBase, TestHondaBoschSafetyBase): def setUp(self): super().setUp() self.safety.set_safety_hooks(Panda.SAFETY_HONDA_BOSCH, Panda.FLAG_HONDA_BOSCH_LONG) - self.safety.init_tests_honda() + self.safety.init_tests() def _send_gas_brake_msg(self, gas, accel): values = { @@ -540,10 +493,10 @@ def test_spam_cancel_safety_check(self): pass def test_diagnostics(self): - tester_present = common.package_can_msg((0x18DAB0F1, 0, b"\x02\x3E\x80\x00\x00\x00\x00\x00", self.PT_BUS)) + tester_present = libpanda_py.make_CANPacket(0x18DAB0F1, self.PT_BUS, b"\x02\x3E\x80\x00\x00\x00\x00\x00") self.assertTrue(self.safety.safety_tx_hook(tester_present)) - not_tester_present = common.package_can_msg((0x18DAB0F1, 0, b"\x03\xAA\xAA\x00\x00\x00\x00\x00", self.PT_BUS)) + not_tester_present = libpanda_py.make_CANPacket(0x18DAB0F1, self.PT_BUS, b"\x03\xAA\xAA\x00\x00\x00\x00\x00") self.assertFalse(self.safety.safety_tx_hook(not_tester_present)) def test_radar_alive(self): @@ -558,14 +511,14 @@ def test_gas_safety_check(self): accel = 0 if gas < 0 else gas / 1000 self.safety.set_controls_allowed(controls_allowed) send = gas <= self.MAX_GAS if controls_allowed else gas == self.NO_GAS - self.assertEqual(send, self.safety.safety_tx_hook(self._send_gas_brake_msg(gas, accel)), gas) + self.assertEqual(send, self.safety.safety_tx_hook(self._send_gas_brake_msg(gas, accel)), (controls_allowed, gas, accel)) def test_brake_safety_check(self): for controls_allowed in [True, False]: - for accel in np.arange(0, self.MAX_BRAKE - 1, -0.01): - accel = round(accel, 2) # floats might not hit exact boundary conditions without rounding + for accel in np.arange(self.MIN_ACCEL - 1, self.MAX_ACCEL + 1, 0.01): + accel = round(accel, 2) # floats might not hit exact boundary conditions without rounding self.safety.set_controls_allowed(controls_allowed) - send = self.MAX_BRAKE <= accel <= 0 if controls_allowed else accel == 0 + send = self.MIN_ACCEL <= accel <= self.MAX_ACCEL if controls_allowed else accel == 0 self.assertEqual(send, self._tx(self._send_gas_brake_msg(self.NO_GAS, accel)), (controls_allowed, accel)) @@ -579,9 +532,9 @@ class TestHondaBoschRadarless(HondaPcmEnableBase, TestHondaBoschSafetyBase): def setUp(self): self.packer = CANPackerPanda("honda_civic_ex_2022_can_generated") - self.safety = libpandasafety_py.libpandasafety + self.safety = libpanda_py.libpanda self.safety.set_safety_hooks(Panda.SAFETY_HONDA_BOSCH, Panda.FLAG_HONDA_RADARLESS) - self.safety.init_tests_honda() + self.safety.init_tests() def test_alt_disengage_on_brake(self): # These cars do not have 0x1BE (BRAKE_MODULE) diff --git a/tests/safety/test_hyundai.py b/tests/safety/test_hyundai.py index cabb9a04ab..d57cadebff 100755 --- a/tests/safety/test_hyundai.py +++ b/tests/safety/test_hyundai.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 import unittest from panda import Panda -from panda.tests.safety import libpandasafety_py +from panda.tests.libpanda import libpanda_py import panda.tests.safety.common as common from panda.tests.safety.common import CANPackerPanda from panda.tests.safety.hyundai_common import HyundaiButtonBase, HyundaiLongitudinalBase @@ -71,7 +71,7 @@ class TestHyundaiSafety(HyundaiButtonBase, common.PandaSafetyTest, common.Driver def setUp(self): self.packer = CANPackerPanda("hyundai_kia_generic") - self.safety = libpandasafety_py.libpandasafety + self.safety = libpanda_py.libpanda self.safety.set_safety_hooks(Panda.SAFETY_HYUNDAI, 0) self.safety.init_tests() @@ -113,13 +113,25 @@ def _torque_cmd_msg(self, torque, steer_req=1): return self.packer.make_can_msg_panda("LKAS11", 0, values) +class TestHyundaiSafetyAltLimits(TestHyundaiSafety): + MAX_RATE_UP = 2 + MAX_RATE_DOWN = 3 + MAX_TORQUE = 270 + + def setUp(self): + self.packer = CANPackerPanda("hyundai_kia_generic") + self.safety = libpanda_py.libpanda + self.safety.set_safety_hooks(Panda.SAFETY_HYUNDAI, Panda.FLAG_HYUNDAI_ALT_LIMITS) + self.safety.init_tests() + + class TestHyundaiSafetyCameraSCC(TestHyundaiSafety): - BUTTONS_BUS = 2 # tx on 2, rx on 0 + BUTTONS_TX_BUS = 2 # tx on 2, rx on 0 SCC_BUS = 2 # rx on 2 def setUp(self): self.packer = CANPackerPanda("hyundai_kia_generic") - self.safety = libpandasafety_py.libpandasafety + self.safety = libpanda_py.libpanda self.safety.set_safety_hooks(Panda.SAFETY_HYUNDAI, Panda.FLAG_HYUNDAI_CAMERA_SCC) self.safety.init_tests() @@ -127,7 +139,7 @@ def setUp(self): class TestHyundaiLegacySafety(TestHyundaiSafety): def setUp(self): self.packer = CANPackerPanda("hyundai_kia_generic") - self.safety = libpandasafety_py.libpandasafety + self.safety = libpanda_py.libpanda self.safety.set_safety_hooks(Panda.SAFETY_HYUNDAI_LEGACY, 0) self.safety.init_tests() @@ -135,7 +147,7 @@ def setUp(self): class TestHyundaiLegacySafetyEV(TestHyundaiSafety): def setUp(self): self.packer = CANPackerPanda("hyundai_kia_generic") - self.safety = libpandasafety_py.libpandasafety + self.safety = libpanda_py.libpanda self.safety.set_safety_hooks(Panda.SAFETY_HYUNDAI_LEGACY, 1) self.safety.init_tests() @@ -147,7 +159,7 @@ def _user_gas_msg(self, gas): class TestHyundaiLegacySafetyHEV(TestHyundaiSafety): def setUp(self): self.packer = CANPackerPanda("hyundai_kia_generic") - self.safety = libpandasafety_py.libpandasafety + self.safety = libpanda_py.libpanda self.safety.set_safety_hooks(Panda.SAFETY_HYUNDAI_LEGACY, 2) self.safety.init_tests() @@ -164,7 +176,7 @@ class TestHyundaiLongitudinalSafety(HyundaiLongitudinalBase, TestHyundaiSafety): def setUp(self): self.packer = CANPackerPanda("hyundai_kia_generic") - self.safety = libpandasafety_py.libpandasafety + self.safety = libpanda_py.libpanda self.safety.set_safety_hooks(Panda.SAFETY_HYUNDAI, Panda.FLAG_HYUNDAI_LONG) self.safety.init_tests() diff --git a/tests/safety/test_hyundai_canfd.py b/tests/safety/test_hyundai_canfd.py index fa058efb0c..4e49eab6a9 100755 --- a/tests/safety/test_hyundai_canfd.py +++ b/tests/safety/test_hyundai_canfd.py @@ -1,11 +1,14 @@ #!/usr/bin/env python3 +from parameterized import parameterized_class import unittest from panda import Panda -from panda.tests.safety import libpandasafety_py +from panda.tests.libpanda import libpanda_py import panda.tests.safety.common as common from panda.tests.safety.common import CANPackerPanda from panda.tests.safety.hyundai_common import HyundaiButtonBase, HyundaiLongitudinalBase + + class TestHyundaiCanfdBase(HyundaiButtonBase, common.PandaSafetyTest, common.DriverTorqueSteeringSafetyTest): TX_MSGS = [[0x50, 0], [0x1CF, 1], [0x2A4, 0]] @@ -31,8 +34,11 @@ class TestHyundaiCanfdBase(HyundaiButtonBase, common.PandaSafetyTest, common.Dri MIN_VALID_STEERING_RT_INTERVAL = 810000 # a ~10% buffer, can send steer up to 110Hz PT_BUS = 0 + SCC_BUS = 2 STEER_BUS = 0 STEER_MSG = "" + GAS_MSG = ("", "") + BUTTONS_TX_BUS = 1 @classmethod def setUpClass(cls): @@ -54,26 +60,28 @@ def _speed_msg(self, speed): return self.packer.make_can_msg_panda("WHEEL_SPEEDS", self.PT_BUS, values) def _user_brake_msg(self, brake): - values = {"BRAKE_PRESSED": brake} - return self.packer.make_can_msg_panda("BRAKE", self.PT_BUS, values) + values = {"DriverBraking": brake} + return self.packer.make_can_msg_panda("TCS", self.PT_BUS, values) def _user_gas_msg(self, gas): - values = {"ACCELERATOR_PEDAL": gas} - return self.packer.make_can_msg_panda("ACCELERATOR", self.PT_BUS, values) + values = {self.GAS_MSG[1]: gas} + return self.packer.make_can_msg_panda(self.GAS_MSG[0], self.PT_BUS, values) def _pcm_status_msg(self, enable): - values = {"CRUISE_ACTIVE": enable} - return self.packer.make_can_msg_panda("SCC1", self.PT_BUS, values) + values = {"ACCMode": 1 if enable else 0} + return self.packer.make_can_msg_panda("SCC_CONTROL", self.SCC_BUS, values) - def _button_msg(self, buttons, main_button=0, bus=1): + def _button_msg(self, buttons, main_button=0, bus=None): + if bus is None: + bus = self.PT_BUS values = { "CRUISE_BUTTONS": buttons, "ADAPTIVE_CRUISE_MAIN_BTN": main_button, } - return self.packer.make_can_msg_panda("CRUISE_BUTTONS", self.PT_BUS, values) + return self.packer.make_can_msg_panda("CRUISE_BUTTONS", bus, values) -class TestHyundaiCanfdHDA1(TestHyundaiCanfdBase): +class TestHyundaiCanfdHDA1Base(TestHyundaiCanfdBase): TX_MSGS = [[0x12A, 0], [0x1A0, 1], [0x1CF, 0], [0x1E0, 0]] RELAY_MALFUNCTION_ADDR = 0x12A @@ -82,23 +90,55 @@ class TestHyundaiCanfdHDA1(TestHyundaiCanfdBase): FWD_BUS_LOOKUP = {0: 2, 2: 0} STEER_MSG = "LFA" + BUTTONS_TX_BUS = 2 + SAFETY_PARAM: int + + @classmethod + def setUpClass(cls): + if cls.__name__ in ("TestHyundaiCanfdHDA1", "TestHyundaiCanfdHDA1AltButtons") or cls.__name__.endswith('Base'): + cls.packer = None + cls.safety = None + raise unittest.SkipTest def setUp(self): self.packer = CANPackerPanda("hyundai_canfd") - self.safety = libpandasafety_py.libpandasafety - self.safety.set_safety_hooks(Panda.SAFETY_HYUNDAI_CANFD, Panda.FLAG_HYUNDAI_HYBRID_GAS) + self.safety = libpanda_py.libpanda + self.safety.set_safety_hooks(Panda.SAFETY_HYUNDAI_CANFD, self.SAFETY_PARAM) self.safety.init_tests() - def _user_gas_msg(self, gas): - values = {"ACCELERATOR_PEDAL": gas} - return self.packer.make_can_msg_panda("ACCELERATOR_ALT", self.PT_BUS, values) -class TestHyundaiCanfdHDA1AltButtons(TestHyundaiCanfdHDA1): +@parameterized_class([ + # Radar SCC + {"GAS_MSG": ("ACCELERATOR_BRAKE_ALT", "ACCELERATOR_PEDAL_PRESSED"), "SCC_BUS": 0, "SAFETY_PARAM": 0}, + {"GAS_MSG": ("ACCELERATOR", "ACCELERATOR_PEDAL"), "SCC_BUS": 0, "SAFETY_PARAM": Panda.FLAG_HYUNDAI_EV_GAS}, + {"GAS_MSG": ("ACCELERATOR_ALT", "ACCELERATOR_PEDAL"), "SCC_BUS": 0, "SAFETY_PARAM": Panda.FLAG_HYUNDAI_HYBRID_GAS}, + # Camera SCC + {"GAS_MSG": ("ACCELERATOR_BRAKE_ALT", "ACCELERATOR_PEDAL_PRESSED"), "SCC_BUS": 2, "SAFETY_PARAM": Panda.FLAG_HYUNDAI_CAMERA_SCC}, + {"GAS_MSG": ("ACCELERATOR", "ACCELERATOR_PEDAL"), "SCC_BUS": 2, "SAFETY_PARAM": Panda.FLAG_HYUNDAI_EV_GAS | Panda.FLAG_HYUNDAI_CAMERA_SCC}, + {"GAS_MSG": ("ACCELERATOR_ALT", "ACCELERATOR_PEDAL"), "SCC_BUS": 2, "SAFETY_PARAM": Panda.FLAG_HYUNDAI_HYBRID_GAS | Panda.FLAG_HYUNDAI_CAMERA_SCC}, +]) +class TestHyundaiCanfdHDA1(TestHyundaiCanfdHDA1Base): + pass + + +@parameterized_class([ + # Radar SCC + {"GAS_MSG": ("ACCELERATOR_BRAKE_ALT", "ACCELERATOR_PEDAL_PRESSED"), "SCC_BUS": 0, "SAFETY_PARAM": 0}, + {"GAS_MSG": ("ACCELERATOR", "ACCELERATOR_PEDAL"), "SCC_BUS": 0, "SAFETY_PARAM": Panda.FLAG_HYUNDAI_EV_GAS}, + {"GAS_MSG": ("ACCELERATOR_ALT", "ACCELERATOR_PEDAL"), "SCC_BUS": 0, "SAFETY_PARAM": Panda.FLAG_HYUNDAI_HYBRID_GAS}, + # Camera SCC + {"GAS_MSG": ("ACCELERATOR_BRAKE_ALT", "ACCELERATOR_PEDAL_PRESSED"), "SCC_BUS": 2, "SAFETY_PARAM": Panda.FLAG_HYUNDAI_CAMERA_SCC}, + {"GAS_MSG": ("ACCELERATOR", "ACCELERATOR_PEDAL"), "SCC_BUS": 2, "SAFETY_PARAM": Panda.FLAG_HYUNDAI_EV_GAS | Panda.FLAG_HYUNDAI_CAMERA_SCC}, + {"GAS_MSG": ("ACCELERATOR_ALT", "ACCELERATOR_PEDAL"), "SCC_BUS": 2, "SAFETY_PARAM": Panda.FLAG_HYUNDAI_HYBRID_GAS | Panda.FLAG_HYUNDAI_CAMERA_SCC}, +]) +class TestHyundaiCanfdHDA1AltButtons(TestHyundaiCanfdHDA1Base): + + SAFETY_PARAM: int def setUp(self): self.packer = CANPackerPanda("hyundai_canfd") - self.safety = libpandasafety_py.libpandasafety - self.safety.set_safety_hooks(Panda.SAFETY_HYUNDAI_CANFD, Panda.FLAG_HYUNDAI_HYBRID_GAS | Panda.FLAG_HYUNDAI_CANFD_ALT_BUTTONS) + self.safety = libpanda_py.libpanda + self.safety.set_safety_hooks(Panda.SAFETY_HYUNDAI_CANFD, Panda.FLAG_HYUNDAI_CANFD_ALT_BUTTONS | self.SAFETY_PARAM) self.safety.init_tests() def _button_msg(self, buttons, main_button=0, bus=1): @@ -118,7 +158,7 @@ def test_button_sends(self): self.assertFalse(self._tx(self._button_msg(btn))) -class TestHyundaiCanfdHDA2(TestHyundaiCanfdBase): +class TestHyundaiCanfdHDA2EV(TestHyundaiCanfdBase): TX_MSGS = [[0x50, 0], [0x1CF, 1], [0x2A4, 0]] RELAY_MALFUNCTION_ADDR = 0x50 @@ -127,16 +167,18 @@ class TestHyundaiCanfdHDA2(TestHyundaiCanfdBase): FWD_BUS_LOOKUP = {0: 2, 2: 0} PT_BUS = 1 + SCC_BUS = 1 STEER_MSG = "LKAS" + GAS_MSG = ("ACCELERATOR", "ACCELERATOR_PEDAL") def setUp(self): self.packer = CANPackerPanda("hyundai_canfd") - self.safety = libpandasafety_py.libpandasafety + self.safety = libpanda_py.libpanda self.safety.set_safety_hooks(Panda.SAFETY_HYUNDAI_CANFD, Panda.FLAG_HYUNDAI_CANFD_HDA2 | Panda.FLAG_HYUNDAI_EV_GAS) self.safety.init_tests() -class TestHyundaiCanfdHDA2Long(HyundaiLongitudinalBase, TestHyundaiCanfdHDA2): +class TestHyundaiCanfdHDA2LongEV(HyundaiLongitudinalBase, TestHyundaiCanfdHDA2EV): TX_MSGS = [[0x50, 0], [0x1CF, 1], [0x2A4, 0], [0x51, 0], [0x730, 1], [0x12a, 1], [0x160, 1], [0x1e0, 1], [0x1a0, 1], [0x1ea, 1], [0x200, 1], [0x345, 1], [0x1da, 1]] @@ -145,20 +187,52 @@ class TestHyundaiCanfdHDA2Long(HyundaiLongitudinalBase, TestHyundaiCanfdHDA2): DISABLED_ECU_ACTUATION_MSG = (0x1a0, 1) STEER_MSG = "LFA" + GAS_MSG = ("ACCELERATOR", "ACCELERATOR_PEDAL") STEER_BUS = 1 + def setUp(self): self.packer = CANPackerPanda("hyundai_canfd") - self.safety = libpandasafety_py.libpandasafety + self.safety = libpanda_py.libpanda self.safety.set_safety_hooks(Panda.SAFETY_HYUNDAI_CANFD, Panda.FLAG_HYUNDAI_CANFD_HDA2 | Panda.FLAG_HYUNDAI_LONG | Panda.FLAG_HYUNDAI_EV_GAS) self.safety.init_tests() def _accel_msg(self, accel, aeb_req=False, aeb_decel=0): values = { - "ACCEL_REQ": accel, - "ACCEL_REQ2": accel, + "aReqRaw": accel, + "aReqValue": accel, + } + return self.packer.make_can_msg_panda("SCC_CONTROL", 1, values) + + +class TestHyundaiCanfdHDALongHybrid(HyundaiLongitudinalBase, TestHyundaiCanfdHDA1Base): + + FWD_BLACKLISTED_ADDRS = {2: [0x12a, 0x1e0, 0x1a0]} + + DISABLED_ECU_UDS_MSG = (0x730, 1) + DISABLED_ECU_ACTUATION_MSG = (0x1a0, 0) + + STEER_MSG = "LFA" + GAS_MSG = ("ACCELERATOR_ALT", "ACCELERATOR_PEDAL") + STEER_BUS = 0 + SCC_BUS = 2 + + def setUp(self): + self.packer = CANPackerPanda("hyundai_canfd") + self.safety = libpanda_py.libpanda + self.safety.set_safety_hooks(Panda.SAFETY_HYUNDAI_CANFD, Panda.FLAG_HYUNDAI_CAMERA_SCC | Panda.FLAG_HYUNDAI_LONG | Panda.FLAG_HYUNDAI_HYBRID_GAS) + self.safety.init_tests() + + def _accel_msg(self, accel, aeb_req=False, aeb_decel=0): + values = { + "aReqRaw": accel, + "aReqValue": accel, } - return self.packer.make_can_msg_panda("CRUISE_INFO", 1, values) + return self.packer.make_can_msg_panda("SCC_CONTROL", 0, values) + + # no knockout + def test_tester_present_allowed(self): + pass if __name__ == "__main__": diff --git a/tests/safety/test_mazda.py b/tests/safety/test_mazda.py index 74ab675666..79397782d2 100755 --- a/tests/safety/test_mazda.py +++ b/tests/safety/test_mazda.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 import unittest from panda import Panda -from panda.tests.safety import libpandasafety_py +from panda.tests.libpanda import libpanda_py import panda.tests.safety.common as common from panda.tests.safety.common import CANPackerPanda @@ -27,7 +27,7 @@ class TestMazdaSafety(common.PandaSafetyTest, common.DriverTorqueSteeringSafetyT def setUp(self): self.packer = CANPackerPanda("mazda_2017") - self.safety = libpandasafety_py.libpandasafety + self.safety = libpanda_py.libpanda self.safety.set_safety_hooks(Panda.SAFETY_MAZDA, 0) self.safety.init_tests() diff --git a/tests/safety/test_nissan.py b/tests/safety/test_nissan.py index c354f4029f..bdd15f177b 100755 --- a/tests/safety/test_nissan.py +++ b/tests/safety/test_nissan.py @@ -1,21 +1,12 @@ #!/usr/bin/env python3 import unittest -import numpy as np from panda import Panda -from panda.tests.safety import libpandasafety_py +from panda.tests.libpanda import libpanda_py import panda.tests.safety.common as common from panda.tests.safety.common import CANPackerPanda -ANGLE_DELTA_BP = [0., 5., 15.] -ANGLE_DELTA_V = [5., .8, .15] # windup limit -ANGLE_DELTA_VU = [5., 3.5, 0.4] # unwind limit - -def sign(a): - return 1 if a > 0 else -1 - - -class TestNissanSafety(common.PandaSafetyTest): +class TestNissanSafety(common.PandaSafetyTest, common.AngleSteeringSafetyTest): TX_MSGS = [[0x169, 0], [0x2b1, 0], [0x4cc, 0], [0x20b, 2], [0x280, 2]] STANDSTILL_THRESHOLD = 0 @@ -25,32 +16,31 @@ class TestNissanSafety(common.PandaSafetyTest): FWD_BLACKLISTED_ADDRS = {0: [0x280], 2: [0x169, 0x2b1, 0x4cc]} FWD_BUS_LOOKUP = {0: 2, 2: 0} + # Angle control limits + DEG_TO_CAN = -100 + + ANGLE_DELTA_BP = [0., 5., 15.] + ANGLE_DELTA_V = [5., .8, .15] # windup limit + ANGLE_DELTA_VU = [5., 3.5, .4] # unwind limit + def setUp(self): self.packer = CANPackerPanda("nissan_x_trail_2017") - self.safety = libpandasafety_py.libpandasafety + self.safety = libpanda_py.libpanda self.safety.set_safety_hooks(Panda.SAFETY_NISSAN, 0) self.safety.init_tests() - def _angle_meas_msg(self, angle): + def _angle_cmd_msg(self, angle: float, enabled: bool): + values = {"DESIRED_ANGLE": angle, "LKA_ACTIVE": 1 if enabled else 0} + return self.packer.make_can_msg_panda("LKAS", 0, values) + + def _angle_meas_msg(self, angle: float): values = {"STEER_ANGLE": angle} return self.packer.make_can_msg_panda("STEER_ANGLE_SENSOR", 0, values) - def _set_prev_angle(self, t): - t = int(t * -100) - self.safety.set_desired_angle_last(t) - - def _angle_meas_msg_array(self, angle): - for _ in range(6): - self._rx(self._angle_meas_msg(angle)) - def _pcm_status_msg(self, enable): values = {"CRUISE_ENABLED": enable} return self.packer.make_can_msg_panda("CRUISE_STATE", 2, values) - def _lkas_control_msg(self, angle, state): - values = {"DESIRED_ANGLE": angle, "LKA_ACTIVE": state} - return self.packer.make_can_msg_panda("LKAS", 0, values) - def _speed_msg(self, speed): # TODO: why the 3.6? m/s to kph? not in dbc values = {"WHEEL_SPEED_%s" % s: speed * 3.6 for s in ["RR", "RL"]} @@ -71,60 +61,6 @@ def _acc_button_cmd(self, cancel=0, propilot=0, flw_dist=0, _set=0, res=0): "RES_BUTTON": res, "NO_BUTTON_PRESSED": no_button} return self.packer.make_can_msg_panda("CRUISE_THROTTLE", 2, values) - def test_angle_cmd_when_enabled(self): - # when controls are allowed, angle cmd rate limit is enforced - speeds = [0., 1., 5., 10., 15., 50.] - angles = [-300, -100, -10, 0, 10, 100, 300] - for a in angles: - for s in speeds: - max_delta_up = np.interp(s, ANGLE_DELTA_BP, ANGLE_DELTA_V) - max_delta_down = np.interp(s, ANGLE_DELTA_BP, ANGLE_DELTA_VU) - - # first test against false positives - self._angle_meas_msg_array(a) - self._rx(self._speed_msg(s)) - - self._set_prev_angle(a) - self.safety.set_controls_allowed(1) - - # Stay within limits - # Up - self.assertEqual(True, self._tx(self._lkas_control_msg(a + sign(a) * max_delta_up, 1))) - self.assertTrue(self.safety.get_controls_allowed()) - - # Don't change - self.assertEqual(True, self._tx(self._lkas_control_msg(a, 1))) - self.assertTrue(self.safety.get_controls_allowed()) - - # Down - self.assertEqual(True, self._tx(self._lkas_control_msg(a - sign(a) * max_delta_down, 1))) - self.assertTrue(self.safety.get_controls_allowed()) - - # Inject too high rates - # Up - self.assertEqual(False, self._tx(self._lkas_control_msg(a + sign(a) * (max_delta_up + 1), 1))) - - # Don't change - self.safety.set_controls_allowed(1) - self._set_prev_angle(a) - self.assertTrue(self.safety.get_controls_allowed()) - self.assertEqual(True, self._tx(self._lkas_control_msg(a, 1))) - self.assertTrue(self.safety.get_controls_allowed()) - - # Down - self.assertEqual(False, self._tx(self._lkas_control_msg(a - sign(a) * (max_delta_down + 1), 1))) - - # Check desired steer should be the same as steer angle when controls are off - self.safety.set_controls_allowed(0) - self.assertEqual(True, self._tx(self._lkas_control_msg(a, 0))) - - def test_angle_cmd_when_disabled(self): - self.safety.set_controls_allowed(0) - - self._set_prev_angle(0) - self.assertFalse(self._tx(self._lkas_control_msg(0, 1))) - self.assertFalse(self.safety.get_controls_allowed()) - def test_acc_buttons(self): btns = [ ("cancel", True), @@ -141,11 +77,12 @@ def test_acc_buttons(self): tx = self._tx(self._acc_button_cmd(**args)) self.assertEqual(tx, should_tx) + class TestNissanLeafSafety(TestNissanSafety): def setUp(self): self.packer = CANPackerPanda("nissan_leaf_2018") - self.safety = libpandasafety_py.libpandasafety + self.safety = libpanda_py.libpanda self.safety.set_safety_hooks(Panda.SAFETY_NISSAN, 0) self.safety.init_tests() @@ -161,5 +98,6 @@ def _user_gas_msg(self, gas): def test_acc_buttons(self): pass + if __name__ == "__main__": unittest.main() diff --git a/tests/safety/test_subaru.py b/tests/safety/test_subaru.py index 48c7653cee..2352792bbb 100755 --- a/tests/safety/test_subaru.py +++ b/tests/safety/test_subaru.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 import unittest from panda import Panda -from panda.tests.safety import libpandasafety_py +from panda.tests.libpanda import libpanda_py import panda.tests.safety.common as common from panda.tests.safety.common import CANPackerPanda @@ -28,7 +28,7 @@ class TestSubaruSafety(common.PandaSafetyTest, common.DriverTorqueSteeringSafety def setUp(self): self.packer = CANPackerPanda("subaru_global_2017_generated") - self.safety = libpandasafety_py.libpandasafety + self.safety = libpanda_py.libpanda self.safety.set_safety_hooks(Panda.SAFETY_SUBARU, 0) self.safety.init_tests() @@ -73,7 +73,7 @@ class TestSubaruGen2Safety(TestSubaruSafety): def setUp(self): self.packer = CANPackerPanda("subaru_global_2017_generated") - self.safety = libpandasafety_py.libpandasafety + self.safety = libpanda_py.libpanda self.safety.set_safety_hooks(Panda.SAFETY_SUBARU, Panda.FLAG_SUBARU_GEN2) self.safety.init_tests() diff --git a/tests/safety/test_subaru_legacy.py b/tests/safety/test_subaru_legacy.py index bc72be6fdd..c00afcf011 100755 --- a/tests/safety/test_subaru_legacy.py +++ b/tests/safety/test_subaru_legacy.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 import unittest from panda import Panda -from panda.tests.safety import libpandasafety_py +from panda.tests.libpanda import libpanda_py import panda.tests.safety.common as common from panda.tests.safety.common import CANPackerPanda @@ -26,7 +26,7 @@ class TestSubaruLegacySafety(common.PandaSafetyTest, common.DriverTorqueSteering def setUp(self): self.packer = CANPackerPanda("subaru_outback_2015_generated") - self.safety = libpandasafety_py.libpandasafety + self.safety = libpanda_py.libpanda self.safety.set_safety_hooks(Panda.SAFETY_SUBARU_LEGACY, 0) self.safety.init_tests() diff --git a/tests/safety/test_tesla.py b/tests/safety/test_tesla.py index 08f686757e..90290c4e48 100755 --- a/tests/safety/test_tesla.py +++ b/tests/safety/test_tesla.py @@ -3,16 +3,13 @@ import numpy as np from panda import Panda import panda.tests.safety.common as common -from panda.tests.safety import libpandasafety_py +from panda.tests.libpanda import libpanda_py from panda.tests.safety.common import CANPackerPanda -ANGLE_DELTA_BP = [0., 5., 15.] -ANGLE_DELTA_V = [5., .8, .15] # windup limit -ANGLE_DELTA_VU = [5., 3.5, 0.4] # unwind limit - MAX_ACCEL = 2.0 MIN_ACCEL = -3.5 + class CONTROL_LEVER_STATE: DN_1ST = 32 UP_1ST = 16 @@ -22,8 +19,6 @@ class CONTROL_LEVER_STATE: FWD = 1 IDLE = 0 -def sign(a): - return 1 if a > 0 else -1 class TestTeslaSafety(common.PandaSafetyTest): STANDSTILL_THRESHOLD = 0 @@ -35,22 +30,6 @@ def setUp(self): self.packer = None raise unittest.SkipTest - def _angle_meas_msg(self, angle): - values = {"EPAS_internalSAS": angle} - return self.packer.make_can_msg_panda("EPAS_sysStatus", 0, values) - - def _set_prev_angle(self, t): - t = int(t * 10) - self.safety.set_desired_angle_last(t) - - def _angle_meas_msg_array(self, angle): - for _ in range(6): - self._rx(self._angle_meas_msg(angle)) - - def _lkas_control_msg(self, angle, enabled): - values = {"DAS_steeringAngleRequest": angle, "DAS_steeringControlType": 1 if enabled else 0} - return self.packer.make_can_msg_panda("DAS_steeringControl", 0, values) - def _speed_msg(self, speed): values = {"DI_vehicleSpeed": speed / 0.447} return self.packer.make_can_msg_panda("DI_torque2", 0, values) @@ -83,70 +62,32 @@ def _long_control_msg(self, set_speed, acc_val=0, jerk_limits=(0, 0), accel_limi } return self.packer.make_can_msg_panda("DAS_control", 0, values) -class TestTeslaSteeringSafety(TestTeslaSafety): + +class TestTeslaSteeringSafety(TestTeslaSafety, common.AngleSteeringSafetyTest): TX_MSGS = [[0x488, 0], [0x45, 0], [0x45, 2]] RELAY_MALFUNCTION_ADDR = 0x488 FWD_BLACKLISTED_ADDRS = {2: [0x488]} + # Angle control limits + DEG_TO_CAN = 10 + + ANGLE_DELTA_BP = [0., 5., 15.] + ANGLE_DELTA_V = [5., .8, .15] # windup limit + ANGLE_DELTA_VU = [5., 3.5, .4] # unwind limit + def setUp(self): self.packer = CANPackerPanda("tesla_can") - self.safety = libpandasafety_py.libpandasafety + self.safety = libpanda_py.libpanda self.safety.set_safety_hooks(Panda.SAFETY_TESLA, 0) self.safety.init_tests() - def test_angle_cmd_when_enabled(self): - # when controls are allowed, angle cmd rate limit is enforced - speeds = [0., 1., 5., 10., 15., 50.] - angles = [-300, -100, -10, 0, 10, 100, 300] - for a in angles: - for s in speeds: - max_delta_up = np.interp(s, ANGLE_DELTA_BP, ANGLE_DELTA_V) - max_delta_down = np.interp(s, ANGLE_DELTA_BP, ANGLE_DELTA_VU) - - # first test against false positives - self._angle_meas_msg_array(a) - self._rx(self._speed_msg(s)) - - self._set_prev_angle(a) - self.safety.set_controls_allowed(1) - - # Stay within limits - # Up - self.assertEqual(True, self._tx(self._lkas_control_msg(a + sign(a) * max_delta_up, 1))) - self.assertTrue(self.safety.get_controls_allowed()) - - # Don't change - self.assertEqual(True, self._tx(self._lkas_control_msg(a, 1))) - self.assertTrue(self.safety.get_controls_allowed()) - - # Down - self.assertEqual(True, self._tx(self._lkas_control_msg(a - sign(a) * max_delta_down, 1))) - self.assertTrue(self.safety.get_controls_allowed()) - - # Inject too high rates - # Up - self.assertEqual(False, self._tx(self._lkas_control_msg(a + sign(a) * (max_delta_up + 1.1), 1))) - - # Don't change - self.safety.set_controls_allowed(1) - self._set_prev_angle(a) - self.assertTrue(self.safety.get_controls_allowed()) - self.assertEqual(True, self._tx(self._lkas_control_msg(a, 1))) - self.assertTrue(self.safety.get_controls_allowed()) - - # Down - self.assertEqual(False, self._tx(self._lkas_control_msg(a - sign(a) * (max_delta_down + 1.1), 1))) - - # Check desired steer should be the same as steer angle when controls are off - self.safety.set_controls_allowed(0) - self.assertEqual(True, self._tx(self._lkas_control_msg(a, 0))) - - def test_angle_cmd_when_disabled(self): - self.safety.set_controls_allowed(0) + def _angle_cmd_msg(self, angle: float, enabled: bool): + values = {"DAS_steeringAngleRequest": angle, "DAS_steeringControlType": 1 if enabled else 0} + return self.packer.make_can_msg_panda("DAS_steeringControl", 0, values) - self._set_prev_angle(0) - self.assertFalse(self._tx(self._lkas_control_msg(0, 1))) - self.assertFalse(self.safety.get_controls_allowed()) + def _angle_meas_msg(self, angle: float): + values = {"EPAS_internalSAS": angle} + return self.packer.make_can_msg_panda("EPAS_sysStatus", 0, values) def test_acc_buttons(self): """ @@ -205,6 +146,7 @@ def test_acc_accel_limits(self): send = np.all(np.isclose([min_accel, max_accel], 0, atol=0.0001)) self.assertEqual(send, self._tx(self._long_control_msg(10, acc_val=4, accel_limits=[min_accel, max_accel]))) + class TestTeslaChassisLongitudinalSafety(TestTeslaLongitudinalSafety): TX_MSGS = [[0x488, 0], [0x45, 0], [0x45, 2], [0x2B9, 0]] RELAY_MALFUNCTION_ADDR = 0x488 @@ -212,10 +154,11 @@ class TestTeslaChassisLongitudinalSafety(TestTeslaLongitudinalSafety): def setUp(self): self.packer = CANPackerPanda("tesla_can") - self.safety = libpandasafety_py.libpandasafety + self.safety = libpanda_py.libpanda self.safety.set_safety_hooks(Panda.SAFETY_TESLA, Panda.FLAG_TESLA_LONG_CONTROL) self.safety.init_tests() + class TestTeslaPTLongitudinalSafety(TestTeslaLongitudinalSafety): TX_MSGS = [[0x2BF, 0]] RELAY_MALFUNCTION_ADDR = 0x2BF @@ -223,9 +166,10 @@ class TestTeslaPTLongitudinalSafety(TestTeslaLongitudinalSafety): def setUp(self): self.packer = CANPackerPanda("tesla_powertrain") - self.safety = libpandasafety_py.libpandasafety + self.safety = libpanda_py.libpanda self.safety.set_safety_hooks(Panda.SAFETY_TESLA, Panda.FLAG_TESLA_LONG_CONTROL | Panda.FLAG_TESLA_POWERTRAIN) self.safety.init_tests() + if __name__ == "__main__": unittest.main() diff --git a/tests/safety/test_toyota.py b/tests/safety/test_toyota.py index e758d7d233..0a6cb8738b 100755 --- a/tests/safety/test_toyota.py +++ b/tests/safety/test_toyota.py @@ -2,9 +2,10 @@ import numpy as np import random import unittest +import itertools from panda import Panda -from panda.tests.safety import libpandasafety_py +from panda.tests.libpanda import libpanda_py import panda.tests.safety.common as common from panda.tests.safety.common import CANPackerPanda, make_msg, ALTERNATIVE_EXPERIENCE @@ -21,8 +22,7 @@ def interceptor_msg(gas, addr): return to_send -class TestToyotaSafety(common.PandaSafetyTest, common.InterceptorSafetyTest, - common.MotorTorqueSteeringSafetyTest): +class TestToyotaSafetyBase(common.PandaSafetyTest, common.InterceptorSafetyTest): TX_MSGS = [[0x283, 0], [0x2E6, 0], [0x2E7, 0], [0x33E, 0], [0x344, 0], [0x365, 0], [0x366, 0], [0x4CB, 0], # DSU bus 0 [0x128, 1], [0x141, 1], [0x160, 1], [0x161, 1], [0x470, 1], # DSU bus 1 @@ -34,37 +34,26 @@ class TestToyotaSafety(common.PandaSafetyTest, common.InterceptorSafetyTest, FWD_BLACKLISTED_ADDRS = {2: [0x2E4, 0x412, 0x191, 0x343]} FWD_BUS_LOOKUP = {0: 2, 2: 0} INTERCEPTOR_THRESHOLD = 805 - - MAX_RATE_UP = 15 - MAX_RATE_DOWN = 25 - MAX_TORQUE = 1500 - MAX_RT_DELTA = 450 - RT_INTERVAL = 250000 - MAX_TORQUE_ERROR = 350 - TORQUE_MEAS_TOLERANCE = 1 # toyota safety adds one to be conservative for rounding EPS_SCALE = 73 - # Safety around steering req bit - MIN_VALID_STEERING_FRAMES = 18 - MAX_INVALID_STEERING_FRAMES = 1 - MIN_VALID_STEERING_RT_INTERVAL = 170000 # a ~10% buffer, can send steer up to 110Hz - - def setUp(self): - self.packer = CANPackerPanda("toyota_nodsu_pt_generated") - self.safety = libpandasafety_py.libpandasafety - self.safety.set_safety_hooks(Panda.SAFETY_TOYOTA, self.EPS_SCALE) - self.safety.init_tests() + @classmethod + def setUpClass(cls): + if cls.__name__.endswith("Base"): + cls.packer = None + cls.safety = None + raise unittest.SkipTest def _torque_meas_msg(self, torque): values = {"STEER_TORQUE_EPS": (torque / self.EPS_SCALE) * 100.} return self.packer.make_can_msg_panda("STEER_TORQUE_SENSOR", 0, values) + # Both torque and angle safety modes test with each other's steering commands def _torque_cmd_msg(self, torque, steer_req=1): values = {"STEER_TORQUE_CMD": torque, "STEER_REQUEST": steer_req} return self.packer.make_can_msg_panda("STEERING_LKA", 0, values) - def _lta_msg(self, req, req2, angle_cmd): - values = {"STEER_REQUEST": req, "STEER_REQUEST_2": req2, "STEER_ANGLE_CMD": angle_cmd} + def _lta_msg(self, req, req2, angle_cmd, setme_x64=100): + values = {"STEER_REQUEST": req, "STEER_REQUEST_2": req2, "STEER_ANGLE_CMD": angle_cmd, "SETME_X64": setme_x64} return self.packer.make_can_msg_panda("STEERING_LTA", 0, values) def _accel_msg(self, accel, cancel_req=0): @@ -102,7 +91,7 @@ def test_block_aeb(self): dat = [random.randint(1, 255) for _ in range(7)] if not bad: dat = [0]*6 + dat[-1:] - msg = common.package_can_msg([0x283, 0, bytes(dat), 0]) + msg = libpanda_py.make_CANPacket(0x283, 0, bytes(dat)) self.assertEqual(not bad, self._tx(msg)) def test_accel_actuation_limits(self, stock_longitudinal=False): @@ -124,23 +113,14 @@ def test_accel_actuation_limits(self, stock_longitudinal=False): # Only allow LTA msgs with no actuation def test_lta_steer_cmd(self): - for engaged in [True, False]: + for engaged, req, req2, setme_x64, angle in itertools.product([True, False], + [0, 1], [0, 1], + [0, 50, 100], + np.linspace(-20, 20, 5)): self.safety.set_controls_allowed(engaged) - # good msg - self.assertTrue(self._tx(self._lta_msg(0, 0, 0))) - - # bad msgs - self.assertFalse(self._tx(self._lta_msg(1, 0, 0))) - self.assertFalse(self._tx(self._lta_msg(0, 1, 0))) - self.assertFalse(self._tx(self._lta_msg(0, 0, 1))) - - for _ in range(20): - req = random.choice([0, 1]) - req2 = random.choice([0, 1]) - angle = random.randint(-50, 50) - should_tx = not req and not req2 and angle == 0 - self.assertEqual(should_tx, self._tx(self._lta_msg(req, req2, angle))) + should_tx = not req and not req2 and angle == 0 and setme_x64 == 0 + self.assertEqual(should_tx, self._tx(self._lta_msg(req, req2, angle, setme_x64))) def test_rx_hook(self): # checksum checks @@ -159,10 +139,52 @@ def test_rx_hook(self): self.assertFalse(self.safety.get_controls_allowed()) -class TestToyotaAltBrakeSafety(TestToyotaSafety): +class TestToyotaSafetyTorque(TestToyotaSafetyBase, common.MotorTorqueSteeringSafetyTest): + + MAX_RATE_UP = 15 + MAX_RATE_DOWN = 25 + MAX_TORQUE = 1500 + MAX_RT_DELTA = 450 + RT_INTERVAL = 250000 + MAX_TORQUE_ERROR = 350 + TORQUE_MEAS_TOLERANCE = 1 # toyota safety adds one to be conservative for rounding + + # Safety around steering req bit + MIN_VALID_STEERING_FRAMES = 18 + MAX_INVALID_STEERING_FRAMES = 1 + MIN_VALID_STEERING_RT_INTERVAL = 170000 # a ~10% buffer, can send steer up to 110Hz + + def setUp(self): + self.packer = CANPackerPanda("toyota_nodsu_pt_generated") + self.safety = libpanda_py.libpanda + self.safety.set_safety_hooks(Panda.SAFETY_TOYOTA, self.EPS_SCALE) + self.safety.init_tests() + + +class TestToyotaSafetyAngle(TestToyotaSafetyBase): + + def setUp(self): + self.packer = CANPackerPanda("toyota_nodsu_pt_generated") + self.safety = libpanda_py.libpanda + self.safety.set_safety_hooks(Panda.SAFETY_TOYOTA, self.EPS_SCALE | Panda.FLAG_TOYOTA_LTA) + self.safety.init_tests() + + # Only allow LKA msgs with no actuation + def test_lka_steer_cmd(self): + for engaged, steer_req, torque in itertools.product([True, False], + [0, 1], + np.linspace(-1500, 1500, 7)): + self.safety.set_controls_allowed(engaged) + + should_tx = not steer_req and torque == 0 + self.assertEqual(should_tx, self._tx(self._torque_cmd_msg(torque, steer_req))) + + +class TestToyotaAltBrakeSafety(TestToyotaSafetyTorque): + def setUp(self): self.packer = CANPackerPanda("toyota_new_mc_pt_generated") - self.safety = libpandasafety_py.libpandasafety + self.safety = libpanda_py.libpanda self.safety.set_safety_hooks(Panda.SAFETY_TOYOTA, self.EPS_SCALE | Panda.FLAG_TOYOTA_ALT_BRAKE) self.safety.init_tests() @@ -170,17 +192,15 @@ def _user_brake_msg(self, brake): values = {"BRAKE_PRESSED": brake} return self.packer.make_can_msg_panda("BRAKE_MODULE", 0, values) - # No LTA on these cars + # No LTA message in the DBC def test_lta_steer_cmd(self): pass -class TestToyotaStockLongitudinal(TestToyotaSafety): - def setUp(self): - self.packer = CANPackerPanda("toyota_nodsu_pt_generated") - self.safety = libpandasafety_py.libpandasafety - self.safety.set_safety_hooks(Panda.SAFETY_TOYOTA, self.EPS_SCALE | Panda.FLAG_TOYOTA_STOCK_LONGITUDINAL) - self.safety.init_tests() +class TestToyotaStockLongitudinalBase(TestToyotaSafetyBase): + + # Base fwd addresses minus ACC_CONTROL (0x343) + FWD_BLACKLISTED_ADDRS = {2: [0x2E4, 0x412, 0x191]} def test_accel_actuation_limits(self, stock_longitudinal=True): super().test_accel_actuation_limits(stock_longitudinal=stock_longitudinal) @@ -196,10 +216,23 @@ def test_acc_cancel(self): should_tx = np.isclose(accel, 0, atol=0.0001) self.assertEqual(should_tx, self._tx(self._accel_msg(accel, cancel_req=1))) - def test_fwd_hook(self): - # forward ACC_CONTROL - self.FWD_BLACKLISTED_ADDRS[2].remove(0x343) - super().test_fwd_hook() + +class TestToyotaStockLongitudinalTorque(TestToyotaStockLongitudinalBase, TestToyotaSafetyTorque): + + def setUp(self): + self.packer = CANPackerPanda("toyota_nodsu_pt_generated") + self.safety = libpanda_py.libpanda + self.safety.set_safety_hooks(Panda.SAFETY_TOYOTA, self.EPS_SCALE | Panda.FLAG_TOYOTA_STOCK_LONGITUDINAL) + self.safety.init_tests() + + +class TestToyotaStockLongitudinalAngle(TestToyotaStockLongitudinalBase, TestToyotaSafetyAngle): + + def setUp(self): + self.packer = CANPackerPanda("toyota_nodsu_pt_generated") + self.safety = libpanda_py.libpanda + self.safety.set_safety_hooks(Panda.SAFETY_TOYOTA, self.EPS_SCALE | Panda.FLAG_TOYOTA_STOCK_LONGITUDINAL | Panda.FLAG_TOYOTA_LTA) + self.safety.init_tests() if __name__ == "__main__": diff --git a/tests/safety/test_volkswagen_mqb.py b/tests/safety/test_volkswagen_mqb.py index f48e9d8ed1..abee4e6fdb 100755 --- a/tests/safety/test_volkswagen_mqb.py +++ b/tests/safety/test_volkswagen_mqb.py @@ -1,22 +1,29 @@ #!/usr/bin/env python3 import unittest +import numpy as np from panda import Panda -from panda.tests.safety import libpandasafety_py +from panda.tests.libpanda import libpanda_py import panda.tests.safety.common as common from panda.tests.safety.common import CANPackerPanda +MAX_ACCEL = 2.0 +MIN_ACCEL = -3.5 + MSG_ESP_19 = 0xB2 # RX from ABS, for wheel speeds MSG_LH_EPS_03 = 0x9F # RX from EPS, for driver steering torque MSG_ESP_05 = 0x106 # RX from ABS, for brake light state MSG_TSK_06 = 0x120 # RX from ECU, for ACC status from drivetrain coordinator MSG_MOTOR_20 = 0x121 # RX from ECU, for driver throttle input +MSG_ACC_06 = 0x122 # TX by OP, ACC control instructions to the drivetrain coordinator MSG_HCA_01 = 0x126 # TX by OP, Heading Control Assist steering torque MSG_GRA_ACC_01 = 0x12B # TX by OP, ACC control buttons for cancel/resume +MSG_ACC_07 = 0x12E # TX by OP, ACC control instructions to the drivetrain coordinator +MSG_ACC_02 = 0x30C # TX by OP, ACC HUD data to the instrument cluster MSG_LDW_02 = 0x397 # TX by OP, Lane line recognition and text alerts class TestVolkswagenMqbSafety(common.PandaSafetyTest, common.DriverTorqueSteeringSafetyTest): - STANDSTILL_THRESHOLD = 1 + STANDSTILL_THRESHOLD = 0 RELAY_MALFUNCTION_ADDR = MSG_HCA_01 RELAY_MALFUNCTION_BUS = 0 @@ -41,21 +48,36 @@ def _speed_msg(self, speed): values = {"ESP_%s_Radgeschw_02" % s: speed for s in ["HL", "HR", "VL", "VR"]} return self.packer.make_can_msg_panda("ESP_19", 0, values) - # Brake light switch _esp_05_msg - def _user_brake_msg(self, brake): + # Driver brake pressure over threshold + def _esp_05_msg(self, brake): values = {"ESP_Fahrer_bremst": brake} return self.packer.make_can_msg_panda("ESP_05", 0, values) + # Brake pedal switch + def _motor_14_msg(self, brake): + values = {"MO_Fahrer_bremst": brake} + return self.packer.make_can_msg_panda("Motor_14", 0, values) + + def _user_brake_msg(self, brake): + return self._motor_14_msg(brake) + # Driver throttle input def _user_gas_msg(self, gas): values = {"MO_Fahrpedalrohwert_01": gas} return self.packer.make_can_msg_panda("Motor_20", 0, values) # ACC engagement status - def _pcm_status_msg(self, enable): - values = {"TSK_Status": 3 if enable else 1} + def _tsk_status_msg(self, enable, main_switch=True): + if main_switch: + tsk_status = 3 if enable else 2 + else: + tsk_status = 0 + values = {"TSK_Status": tsk_status} return self.packer.make_can_msg_panda("TSK_06", 0, values) + def _pcm_status_msg(self, enable): + return self._tsk_status_msg(enable) + # Driver steering input torque def _torque_driver_msg(self, torque): values = {"EPS_Lenkmoment": abs(torque), "EPS_VZ_Lenkmoment": torque < 0} @@ -67,9 +89,31 @@ def _torque_cmd_msg(self, torque, steer_req=1): return self.packer.make_can_msg_panda("HCA_01", 0, values) # Cruise control buttons - def _gra_acc_01_msg(self, cancel=0, resume=0, _set=0): + def _gra_acc_01_msg(self, cancel=0, resume=0, _set=0, bus=2): values = {"GRA_Abbrechen": cancel, "GRA_Tip_Setzen": _set, "GRA_Tip_Wiederaufnahme": resume} - return self.packer.make_can_msg_panda("GRA_ACC_01", 0, values) + return self.packer.make_can_msg_panda("GRA_ACC_01", bus, values) + + # Acceleration request to drivetrain coordinator + def _acc_06_msg(self, accel): + values = {"ACC_Sollbeschleunigung_02": accel} + return self.packer.make_can_msg_panda("ACC_06", 0, values) + + # Acceleration request to drivetrain coordinator + def _acc_07_msg(self, accel, secondary_accel=3.02): + values = {"ACC_Sollbeschleunigung_02": accel, "ACC_Folgebeschl": secondary_accel} + return self.packer.make_can_msg_panda("ACC_07", 0, values) + + # Verify brake_pressed is true if either the switch or pressure threshold signals are true + def test_redundant_brake_signals(self): + test_combinations = [(True, True, True), (True, True, False), (True, False, True), (False, False, False)] + for brake_pressed, motor_14_signal, esp_05_signal in test_combinations: + self._rx(self._motor_14_msg(False)) + self._rx(self._esp_05_msg(False)) + self.assertFalse(self.safety.get_brake_pressed_prev()) + self._rx(self._motor_14_msg(motor_14_signal)) + self._rx(self._esp_05_msg(esp_05_signal)) + self.assertEqual(brake_pressed, self.safety.get_brake_pressed_prev(), + f"expected {brake_pressed=} with {motor_14_signal=} and {esp_05_signal=}") def test_torque_measurements(self): # TODO: make this test work with all cars @@ -99,7 +143,7 @@ class TestVolkswagenMqbStockSafety(TestVolkswagenMqbSafety): def setUp(self): self.packer = CANPackerPanda("vw_mqb_2010") - self.safety = libpandasafety_py.libpandasafety + self.safety = libpanda_py.libpanda self.safety.set_safety_hooks(Panda.SAFETY_VOLKSWAGEN_MQB, 0) self.safety.init_tests() @@ -113,5 +157,68 @@ def test_spam_cancel_safety_check(self): self.assertTrue(self._tx(self._gra_acc_01_msg(resume=1))) +class TestVolkswagenMqbLongSafety(TestVolkswagenMqbSafety): + TX_MSGS = [[MSG_HCA_01, 0], [MSG_LDW_02, 0], [MSG_ACC_02, 0], [MSG_ACC_06, 0], [MSG_ACC_07, 0]] + FWD_BLACKLISTED_ADDRS = {2: [MSG_HCA_01, MSG_LDW_02, MSG_ACC_02, MSG_ACC_06, MSG_ACC_07]} + FWD_BUS_LOOKUP = {0: 2, 2: 0} + INACTIVE_ACCEL = 3.01 + + def setUp(self): + self.packer = CANPackerPanda("vw_mqb_2010") + self.safety = libpanda_py.libpanda + self.safety.set_safety_hooks(Panda.SAFETY_VOLKSWAGEN_MQB, Panda.FLAG_VOLKSWAGEN_LONG_CONTROL) + self.safety.init_tests() + + # stock cruise controls are entirely bypassed under openpilot longitudinal control + def test_disable_control_allowed_from_cruise(self): + pass + + def test_enable_control_allowed_from_cruise(self): + pass + + def test_cruise_engaged_prev(self): + pass + + def test_set_and_resume_buttons(self): + for button in ["set", "resume"]: + # ACC main switch must be on, engage on falling edge + self.safety.set_controls_allowed(0) + self._rx(self._tsk_status_msg(False, main_switch=False)) + self._rx(self._gra_acc_01_msg(_set=(button == "set"), resume=(button == "resume"), bus=0)) + self.assertFalse(self.safety.get_controls_allowed(), f"controls allowed on {button} with main switch off") + self._rx(self._tsk_status_msg(False, main_switch=True)) + self._rx(self._gra_acc_01_msg(_set=(button == "set"), resume=(button == "resume"), bus=0)) + self.assertFalse(self.safety.get_controls_allowed(), f"controls allowed on {button} rising edge") + self._rx(self._gra_acc_01_msg(bus=0)) + self.assertTrue(self.safety.get_controls_allowed(), f"controls not allowed on {button} falling edge") + + def test_cancel_button(self): + # Disable on rising edge of cancel button + self._rx(self._tsk_status_msg(False, main_switch=True)) + self.safety.set_controls_allowed(1) + self._rx(self._gra_acc_01_msg(cancel=True, bus=0)) + self.assertFalse(self.safety.get_controls_allowed(), "controls allowed after cancel") + + def test_main_switch(self): + # Disable as soon as main switch turns off + self._rx(self._tsk_status_msg(False, main_switch=True)) + self.safety.set_controls_allowed(1) + self._rx(self._tsk_status_msg(False, main_switch=False)) + self.assertFalse(self.safety.get_controls_allowed(), "controls allowed after ACC main switch off") + + def test_accel_safety_check(self): + for controls_allowed in [True, False]: + for accel in np.arange(MIN_ACCEL - 2, MAX_ACCEL + 2, 0.03): + accel = round(accel, 2) # floats might not hit exact boundary conditions without rounding + send = MIN_ACCEL <= accel <= MAX_ACCEL if controls_allowed else accel == self.INACTIVE_ACCEL + self.safety.set_controls_allowed(controls_allowed) + # primary accel request used by ECU + self.assertEqual(send, self._tx(self._acc_06_msg(accel)), (controls_allowed, accel)) + # additional accel request used by ABS/ESP + self.assertEqual(send, self._tx(self._acc_07_msg(accel)), (controls_allowed, accel)) + # ensure the optional secondary accel field remains disabled for now + self.assertFalse(self._tx(self._acc_07_msg(accel, secondary_accel=accel)), (controls_allowed, accel)) + + if __name__ == "__main__": unittest.main() diff --git a/tests/safety/test_volkswagen_pq.py b/tests/safety/test_volkswagen_pq.py index 07aec9d645..563d5de011 100755 --- a/tests/safety/test_volkswagen_pq.py +++ b/tests/safety/test_volkswagen_pq.py @@ -2,7 +2,7 @@ import numpy as np import unittest from panda import Panda -from panda.tests.safety import libpandasafety_py +from panda.tests.libpanda import libpanda_py import panda.tests.safety.common as common from panda.tests.safety.common import CANPackerPanda @@ -14,7 +14,7 @@ MSG_MOTOR_3 = 0x380 # RX from ECU, for driver throttle input MSG_GRA_NEU = 0x38A # TX by OP, ACC control buttons for cancel/resume MSG_MOTOR_5 = 0x480 # RX from ECU, for ACC main switch state -MSG_ACC_GRA_ANZIEGE = 0x56A # TX by OP, ACC HUD +MSG_ACC_GRA_ANZEIGE = 0x56A # TX by OP, ACC HUD MSG_LDW_1 = 0x5BE # TX by OP, Lane line recognition and text alerts MAX_ACCEL = 2.0 @@ -23,7 +23,7 @@ class TestVolkswagenPqSafety(common.PandaSafetyTest, common.DriverTorqueSteeringSafetyTest): cruise_engaged = False - STANDSTILL_THRESHOLD = 1 + STANDSTILL_THRESHOLD = 0 RELAY_MALFUNCTION_ADDR = MSG_HCA_1 RELAY_MALFUNCTION_BUS = 0 @@ -128,7 +128,7 @@ class TestVolkswagenPqStockSafety(TestVolkswagenPqSafety): def setUp(self): self.packer = CANPackerPanda("vw_golf_mk4") - self.safety = libpandasafety_py.libpandasafety + self.safety = libpanda_py.libpanda self.safety.set_safety_hooks(Panda.SAFETY_VOLKSWAGEN_PQ, 0) self.safety.init_tests() @@ -143,13 +143,14 @@ def test_spam_cancel_safety_check(self): class TestVolkswagenPqLongSafety(TestVolkswagenPqSafety): - TX_MSGS = [[MSG_HCA_1, 0], [MSG_LDW_1, 0], [MSG_ACC_SYSTEM, 0], [MSG_ACC_GRA_ANZIEGE, 0]] - FWD_BLACKLISTED_ADDRS = {2: [MSG_HCA_1, MSG_LDW_1, MSG_ACC_SYSTEM, MSG_ACC_GRA_ANZIEGE]} + TX_MSGS = [[MSG_HCA_1, 0], [MSG_LDW_1, 0], [MSG_ACC_SYSTEM, 0], [MSG_ACC_GRA_ANZEIGE, 0]] + FWD_BLACKLISTED_ADDRS = {2: [MSG_HCA_1, MSG_LDW_1, MSG_ACC_SYSTEM, MSG_ACC_GRA_ANZEIGE]} FWD_BUS_LOOKUP = {0: 2, 2: 0} + INACTIVE_ACCEL = 3.01 def setUp(self): self.packer = CANPackerPanda("vw_golf_mk4") - self.safety = libpandasafety_py.libpandasafety + self.safety = libpanda_py.libpanda self.safety.set_safety_hooks(Panda.SAFETY_VOLKSWAGEN_PQ, Panda.FLAG_VOLKSWAGEN_LONG_CONTROL) self.safety.init_tests() @@ -193,9 +194,9 @@ def test_main_switch(self): def test_accel_safety_check(self): for controls_allowed in [True, False]: - for accel in np.arange(MIN_ACCEL - 1, MAX_ACCEL + 1, 0.01): + for accel in np.arange(MIN_ACCEL - 2, MAX_ACCEL + 2, 0.005): accel = round(accel, 2) # floats might not hit exact boundary conditions without rounding - send = MIN_ACCEL <= accel <= MAX_ACCEL if controls_allowed else accel == 0 + send = MIN_ACCEL <= accel <= MAX_ACCEL if controls_allowed else accel == self.INACTIVE_ACCEL self.safety.set_controls_allowed(controls_allowed) # primary accel request used by ECU self.assertEqual(send, self._tx(self._accel_msg(accel)), (controls_allowed, accel)) diff --git a/tests/safety_replay/helpers.py b/tests/safety_replay/helpers.py index eabc502361..9eb26ee82e 100644 --- a/tests/safety_replay/helpers.py +++ b/tests/safety_replay/helpers.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -import panda.tests.safety.libpandasafety_py as libpandasafety_py -from panda import Panda, LEN_TO_DLC +import panda.tests.libpanda.libpanda_py as libpanda_py +from panda import Panda def to_signed(d, bits): ret = d @@ -22,50 +22,33 @@ def is_steering_msg(mode, addr): ret = addr == 0x292 elif mode == Panda.SAFETY_SUBARU: ret = addr == 0x122 + elif mode == Panda.SAFETY_FORD: + ret = addr == 0x3d3 return ret -def get_steer_torque(mode, to_send): - ret = 0 +def get_steer_value(mode, to_send): + torque, angle = 0, 0 if mode in (Panda.SAFETY_HONDA_NIDEC, Panda.SAFETY_HONDA_BOSCH): - ret = to_send.RDLR & 0xFFFF0000 + torque = to_send.RDLR & 0xFFFF0000 elif mode == Panda.SAFETY_TOYOTA: - ret = (to_send.RDLR & 0xFF00) | ((to_send.RDLR >> 16) & 0xFF) - ret = to_signed(ret, 16) + torque = (to_send.data[1] << 8) | (to_send.data[2]) + torque = to_signed(torque, 16) elif mode == Panda.SAFETY_GM: - ret = ((to_send.RDLR & 0x7) << 8) + ((to_send.RDLR & 0xFF00) >> 8) - ret = to_signed(ret, 11) + torque = ((to_send.data[0] & 0x7) << 8) | to_send.data[1] + torque = to_signed(torque, 11) elif mode == Panda.SAFETY_HYUNDAI: - ret = ((to_send.RDLR >> 16) & 0x7ff) - 1024 + torque = (((to_send.data[3] & 0x7) << 8) | to_send.data[2]) - 1024 elif mode == Panda.SAFETY_CHRYSLER: - ret = ((to_send.RDLR & 0x7) << 8) + ((to_send.RDLR & 0xFF00) >> 8) - 1024 + torque = (((to_send.data[0] & 0x7) << 8) | to_send.data[1]) - 1024 elif mode == Panda.SAFETY_SUBARU: - ret = ((to_send.RDLR >> 16) & 0x1FFF) - ret = to_signed(ret, 13) - return ret - -def set_desired_torque_last(safety, mode, torque): - if mode in (Panda.SAFETY_HONDA_NIDEC, Panda.SAFETY_HONDA_BOSCH): - pass # honda safety mode doesn't enforce a rate on steering msgs - elif mode == Panda.SAFETY_TOYOTA: - safety.set_toyota_desired_torque_last(torque) - elif mode == Panda.SAFETY_GM: - safety.set_gm_desired_torque_last(torque) - elif mode == Panda.SAFETY_HYUNDAI: - safety.set_hyundai_desired_torque_last(torque) - elif mode == Panda.SAFETY_CHRYSLER: - safety.set_chrysler_desired_torque_last(torque) - elif mode == Panda.SAFETY_SUBARU: - safety.set_subaru_desired_torque_last(torque) + torque = ((to_send.data[3] & 0x1F) << 8) | to_send.data[2] + torque = -to_signed(torque, 13) + elif mode == Panda.SAFETY_FORD: + angle = ((to_send.data[0] << 3) | (to_send.data[1] >> 5)) - 1000 + return torque, angle def package_can_msg(msg): - ret = libpandasafety_py.ffi.new('CANPacket_t *') - ret[0].extended = 1 if msg.address >= 0x800 else 0 - ret[0].addr = msg.address - ret[0].data_len_code = LEN_TO_DLC[len(msg.dat)] - ret[0].bus = msg.src - ret[0].data = msg.dat - - return ret + return libpanda_py.make_CANPacket(msg.address, msg.src % 4, msg.dat) def init_segment(safety, lr, mode): sendcan = (msg for msg in lr if msg.which() == 'sendcan') @@ -77,8 +60,11 @@ def init_segment(safety, lr, mode): return to_send = package_can_msg(msg) - torque = get_steer_torque(mode, to_send) + torque, angle = get_steer_value(mode, to_send) if torque != 0: safety.set_controls_allowed(1) - set_desired_torque_last(safety, mode, torque) - assert safety.safety_tx_hook(to_send), "failed to initialize panda safety for segment" + safety.set_desired_torque_last(torque) + elif angle != 0: + safety.set_controls_allowed(1) + safety.set_desired_angle_last(angle) + assert safety.safety_tx_hook(to_send), "failed to initialize panda safety for segment" diff --git a/tests/safety_replay/replay_drive.py b/tests/safety_replay/replay_drive.py index 6525465426..c9cc135d38 100755 --- a/tests/safety_replay/replay_drive.py +++ b/tests/safety_replay/replay_drive.py @@ -1,13 +1,14 @@ #!/usr/bin/env python3 import argparse import os +from collections import Counter -from panda.tests.safety import libpandasafety_py +from panda.tests.libpanda import libpanda_py from panda.tests.safety_replay.helpers import package_can_msg, init_segment # replay a drive to check for safety violations def replay_drive(lr, safety_mode, param, alternative_experience, segment=False): - safety = libpandasafety_py.libpandasafety + safety = libpanda_py.libpanda err = safety.set_safety_hooks(safety_mode, param) assert err == 0, "invalid safety mode: %d" % safety_mode @@ -18,7 +19,7 @@ def replay_drive(lr, safety_mode, param, alternative_experience, segment=False): lr.reset() rx_tot, rx_invalid, tx_tot, tx_blocked, tx_controls, tx_controls_blocked = 0, 0, 0, 0, 0, 0 - blocked_addrs = set() + blocked_addrs = Counter() invalid_addrs = set() start_t = None @@ -34,7 +35,7 @@ def replay_drive(lr, safety_mode, param, alternative_experience, segment=False): if not sent: tx_blocked += 1 tx_controls_blocked += safety.get_controls_allowed() - blocked_addrs.add(canmsg.address) + blocked_addrs[canmsg.address] += 1 if "DEBUG" in os.environ: print("blocked bus %d msg %d at %f" % (canmsg.src, canmsg.address, (msg.logMonoTime - start_t) / (1e9))) @@ -87,9 +88,9 @@ def replay_drive(lr, safety_mode, param, alternative_experience, segment=False): for msg in lr: if msg.which() == 'carParams': if args.mode is None: - args.mode = msg.carParams.safetyConfigs[0].safetyModel.raw + args.mode = msg.carParams.safetyConfigs[-1].safetyModel.raw if args.param is None: - args.param = msg.carParams.safetyConfigs[0].safetyParam + args.param = msg.carParams.safetyConfigs[-1].safetyParam if args.alternative_experience is None: args.alternative_experience = msg.carParams.alternativeExperience break diff --git a/tests/safety_replay/test_safety_replay.py b/tests/safety_replay/test_safety_replay.py index 8478e6f6b6..dc61eca4f6 100755 --- a/tests/safety_replay/test_safety_replay.py +++ b/tests/safety_replay/test_safety_replay.py @@ -19,7 +19,9 @@ ReplayRoute("6fb4948a7ebe670e|2019-11-12--00-35-53.bz2", Panda.SAFETY_CHRYSLER), # CHRYSLER.PACIFICA_2018_HYBRID ReplayRoute("791340bc01ed993d|2019-04-08--10-26-00.bz2", Panda.SAFETY_SUBARU, # SUBARU.OUTBACK Panda.FLAG_SUBARU_GEN2), - ReplayRoute("76b83eb0245de90e|2020-03-05--19-16-05.bz2", Panda.SAFETY_VOLKSWAGEN_MQB), # VOLKSWAGEN.GOLF (MK7) + ReplayRoute("76b83eb0245de90e|2020-03-05--19-16-05.bz2", Panda.SAFETY_VOLKSWAGEN_MQB), # VOLKSWAGEN.GOLF (stock ACC) + ReplayRoute("3cfdec54aa035f3f|2022-10-13--14-58-58.bz2", Panda.SAFETY_VOLKSWAGEN_MQB, # VOLKSWAGEN.GOLF (openpilot long) + Panda.FLAG_VOLKSWAGEN_LONG_CONTROL), ReplayRoute("3cfdec54aa035f3f|2022-07-19--23-45-10.bz2", Panda.SAFETY_VOLKSWAGEN_PQ, # VOLKSWAGEN.PASSAT_NMS (openpilot longitudinal) Panda.FLAG_VOLKSWAGEN_LONG_CONTROL), ReplayRoute("fbbfa6af821552b9|2020-03-03--08-09-43.bz2", Panda.SAFETY_NISSAN), # NISSAN.XTRAIL @@ -39,7 +41,7 @@ for route, _, _, _ in logs: if not os.path.isfile(route): with open(route, "wb") as f: - r = requests.get(BASE_URL + route) + r = requests.get(BASE_URL + route, timeout=10) r.raise_for_status() f.write(r.content) @@ -51,6 +53,6 @@ if not replay_drive(lr, mode, param, alt_exp): failed.append(route) - for f in failed: # type: ignore + for f in failed: print(f"\n**** failed on {f} ****") assert len(failed) == 0, "\nfailed on %d logs" % len(failed) diff --git a/tests/som_debug.sh b/tests/som_debug.sh new file mode 100755 index 0000000000..9bb4219713 --- /dev/null +++ b/tests/som_debug.sh @@ -0,0 +1,7 @@ +#!/usr/bin/bash +set -e + +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" +cd $DIR + +PYTHONUNBUFFERED=1 NO_COLOR=1 CLAIM=1 PORT=4 ./debug_console.py diff --git a/board/tests/test_rsa.c b/tests/test_rsa.c similarity index 100% rename from board/tests/test_rsa.c rename to tests/test_rsa.c 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 100755 index 0000000000..311b7a9e62 --- /dev/null +++ b/tests/usbprotocol/test_comms.py @@ -0,0 +1,159 @@ +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 setUp(self): + lpp.comms_can_reset() + + 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_comms_reset_rx(self): + # store some test messages in the queue + test_msg = (0x100, 0, b"test", 0) + for _ in range(100): + can_pkt_tx = libpanda_py.make_CANPacket(test_msg[0], test_msg[3], test_msg[2]) + lpp.can_push(lpp.rx_q, can_pkt_tx) + + # read a small chunk such that we have some overflow + TINY_CHUNK_SIZE = 6 + dat = libpanda_py.ffi.new(f"uint8_t[{TINY_CHUNK_SIZE}]") + rx_len = lpp.comms_can_read(dat, TINY_CHUNK_SIZE) + assert rx_len == TINY_CHUNK_SIZE, "comms_can_read returned too little data" + + _, overflow = unpack_can_buffer(bytes(dat)) + assert len(overflow) > 0, "overflow buffer should not be empty" + + # reset the comms to clear the overflow buffer on the panda side + lpp.comms_can_reset() + + # read a large chunk, which should now contain valid messages + LARGE_CHUNK_SIZE = 512 + dat = libpanda_py.ffi.new(f"uint8_t[{LARGE_CHUNK_SIZE}]") + rx_len = lpp.comms_can_read(dat, LARGE_CHUNK_SIZE) + assert rx_len == LARGE_CHUNK_SIZE, "comms_can_read returned too little data" + + msgs, _ = unpack_can_buffer(bytes(dat)) + assert len(msgs) > 0, "message buffer should not be empty" + for m in msgs: + assert m == test_msg, "message buffer should contain valid test messages" + + def test_comms_reset_tx(self): + # store some test messages in the queue + test_msg = (0x100, 0, b"test", 0) + packed = pack_can_buffer([test_msg for _ in range(100)]) + + # write a small chunk such that we have some overflow + TINY_CHUNK_SIZE = 6 + lpp.comms_can_write(packed[0][:TINY_CHUNK_SIZE], TINY_CHUNK_SIZE) + + # reset the comms to clear the overflow buffer on the panda side + lpp.comms_can_reset() + + # write a full valid chunk, which should now contain valid messages + lpp.comms_can_write(packed[1], len(packed[1])) + + # read the messages from the queue and make sure they're valid + queue_msgs = [] + pkt = libpanda_py.ffi.new('CANPacket_t *') + while lpp.can_pop(TX_QUEUES[0], pkt): + queue_msgs.append(unpackage_can_msg(pkt)) + + assert len(queue_msgs) > 0, "message buffer should not be empty" + for m in queue_msgs: + assert m == test_msg, "message buffer should contain valid test messages" + + + 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) + + 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 = [] + overflow_buf = b"" + 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 + unpacked_msgs, overflow_buf = unpack_can_buffer(overflow_buf + buf) + rx_msgs.extend(unpacked_msgs) + + 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 old mode 100644 new mode 100755 index 7f2a3b38d5..c03f246f31 --- a/tests/usbprotocol/test_pandalib.py +++ b/tests/usbprotocol/test_pandalib.py @@ -1,23 +1,26 @@ #!/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): + overflow_buf = b'' + to_pack = [] for _ in range(10000): - address = random.randint(1, 0x1FFFFFFF) - data = bytes([random.getrandbits(8) for _ in range(random.randrange(1, 9))]) + 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))])]) to_pack.append((address, 0, data, 0)) packed = pack_can_buffer(to_pack) unpacked = [] for dat in packed: - unpacked.extend(unpack_can_buffer(dat)) + msgs, overflow_buf = unpack_can_buffer(overflow_buf + dat) + unpacked.extend(msgs) - assert unpacked == to_pack + self.assertEqual(unpacked, to_pack) if __name__ == "__main__": unittest.main()