diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 7e3574f05e..c1be2c832c 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -46,6 +46,10 @@ jobs: run: ${{ env.RUN }} "scons -j4" - name: Build panda with SPI support run: ${{ env.RUN }} "ENABLE_SPI=1 scons -j4" + - name: Build with UBSan + run: ${{ env.RUN }} "scons -j4 --ubsan" + - name: Build jungle firmware with FINAL_PROVISIONING support + run: ${{ env.RUN }} "FINAL_PROVISIONING=1 scons -j4 board/jungle" unit_tests: name: unit tests @@ -59,38 +63,30 @@ jobs: run: $RUN "scons -j4" - name: Test communication protocols run: $RUN "cd tests/usbprotocol && ./test.sh" + - name: Test logging + run: $RUN "cd tests/logging && ./test.sh" safety: name: safety runs-on: ubuntu-20.04 + strategy: + matrix: + flags: ['', '--ubsan'] timeout-minutes: 20 steps: - uses: actions/checkout@v2 - name: Build Docker image run: eval "$BUILD" - name: Run safety tests - timeout-minutes: 3 + timeout-minutes: 5 run: | ${{ env.RUN }} "cd .. && \ scons -c && \ scons -j$(nproc) opendbc/ cereal/ && \ cd panda && \ - scons -j$(nproc) && \ + scons -j$(nproc) ${{ matrix.flags }} && \ tests/safety/test.sh" - safety_replay: - name: safety replay - runs-on: ubuntu-20.04 - timeout-minutes: 20 - steps: - - uses: actions/checkout@v2 - - name: Build Docker image - run: eval "$BUILD" - - name: Run safety replay - run: ${{ env.RUN }} "cd tests/safety_replay && - scons -u .. && - ./test_safety_replay.py" - misra: name: misra c2012 runs-on: ubuntu-20.04 diff --git a/.gitignore b/.gitignore index 403d034ee1..1f96749b0c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +*.tmp *.pyc .*.swp .*.swo @@ -11,7 +12,7 @@ a.out .#* dist/ pandacan.egg-info/ -board/obj/ +obj/ examples/output.csv .DS_Store .vscode* diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 55b89e6075..183e7746bb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,36 +1,18 @@ repos: -- repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.0.1 - hooks: - - id: check-ast - - id: check-yaml - - id: check-merge-conflict - - id: check-symlinks -- repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.910-1 - hooks: - - id: mypy - 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 - args: - - --select=F,E112,E113,E304,E501,E502,E701,E702,E703,E71,E72,E731,W191,W6 - - --exclude=tests/gmbitbang/* - - --max-line-length=160 - - --statistics -- repo: local - hooks: - - id: pylint - name: pylint - entry: pylint - language: system - types: [python] - args: - - -rn - - -sn - - -j0 - - --disable=C,R,W0613,W0511,W0212,W0201,W0311,W0106,W0603,W0621,W0703,W1203,W1514,E1136 - - --generated-members="usb1.*" +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: check-ast + - id: check-yaml + - id: check-merge-conflict + - id: check-symlinks +- repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.4.1 + hooks: + - id: mypy + additional_dependencies: ['git+https://github.com/numpy/numpy-stubs', 'types-requests', 'types-atomicwrites', + 'types-pycurl'] +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.0.282 + hooks: + - id: ruff diff --git a/Dockerfile b/Dockerfile index c885e0bbbf..6049cbee45 100644 --- a/Dockerfile +++ b/Dockerfile @@ -33,6 +33,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ pkg-config \ python \ python-dev \ + qt5-default \ unzip \ wget \ zlib1g-dev \ @@ -50,12 +51,12 @@ RUN curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-instal ENV PATH="/root/.pyenv/bin:/root/.pyenv/shims:${PATH}" ENV PANDA_PATH=/tmp/openpilot/panda -ENV OPENPILOT_REF="ee0dd36a3c775dbd82493c84f4e7272c1eb3fcbd" -ENV OPENDBC_REF="342c0320dd271fb585db3cced397c5122078af85" +ENV OPENPILOT_REF="80bbba14f74e57bbe90216dfd0a99f6f68d77ca2" +ENV OPENDBC_REF="5880fbbccf5a670631b51836f20e446de643795a" COPY requirements.txt /tmp/ -RUN pyenv install 3.8.10 && \ - pyenv global 3.8.10 && \ +RUN pyenv install 3.11.4 && \ + pyenv global 3.11.4 && \ pyenv rehash && \ pip install --no-cache-dir -r /tmp/requirements.txt @@ -69,23 +70,16 @@ RUN cd /tmp && \ cd /tmp/tmppilot && \ git fetch origin $OPENPILOT_REF && \ git checkout $OPENPILOT_REF && \ - git submodule update --init cereal opendbc rednose_repo && \ + git submodule update --init cereal opendbc rednose_repo body && \ 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 && \ + cp -pR SConstruct site_scons/ tools/ selfdrive/ system/ common/ cereal/ opendbc/ rednose/ third_party/ body/ /tmp/openpilot && \ rm -rf /tmp/openpilot/panda && \ rm -rf /tmp/tmppilot -RUN cd /tmp/openpilot && \ - git clone https://github.com/commaai/panda_jungle.git && \ - cd panda_jungle && \ - git fetch && \ - git checkout 3a791be1f1877a69cf45de16a670992380622297 && \ - 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 diff --git a/Jenkinsfile b/Jenkinsfile index adf641aea2..14953f6927 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,7 +1,6 @@ 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} \ --env PYTHONWARNINGS=error \ --volume /dev/bus/usb:/dev/bus/usb \ --volume /var/run/dbus:/var/run/dbus \ @@ -62,7 +61,6 @@ pipeline { agent any environment { CI = "1" - PARTIAL_TESTS = "${env.BRANCH_NAME == 'master' ? ' ' : '1'}" PYTHONWARNINGS= "error" DOCKER_IMAGE_TAG = "panda:build-${env.GIT_COMMIT}" @@ -82,7 +80,8 @@ pipeline { steps { phone_steps("panda-dos", [ ["build", "scons -j4"], - ["flash", "cd tests/ && ./ci_reset_internal_hw.py"], + ["flash", "cd tests/ && ./reflash_internal_panda.py"], + ["flash jungle", "cd board/jungle && ./flash.py"], ["test", "cd tests/hitl && HW_TYPES=6 pytest --durations=0 [2-7]*.py -k 'not test_send_recv'"], ]) } @@ -93,8 +92,9 @@ pipeline { steps { phone_steps("panda-tres", [ ["build", "scons -j4"], - ["flash", "cd tests/ && ./ci_reset_internal_hw.py"], - ["test", "cd tests/hitl && HW_TYPES=9 pytest --durations=0 2*.py [5-7]*.py -k 'not test_fan_controller and not test_fan_overshoot'"], + ["flash", "cd tests/ && ./reflash_internal_panda.py"], + ["flash jungle", "cd board/jungle && ./flash.py"], + ["test", "cd tests/hitl && HW_TYPES=9 pytest --durations=0 2*.py [5-9]*.py"], ]) } } @@ -117,7 +117,9 @@ pipeline { stage('prep') { steps { script { - docker_run("reset hardware", 3, "python ./tests/ci_reset_hw.py") + retry (3) { + docker_run("reset hardware", 3, "python ./tests/ci_reset_hw.py") + } } } } @@ -131,7 +133,8 @@ pipeline { stage('HITL tests') { steps { script { - docker_run("HITL tests", 35, 'PANDAS_JUNGLE=23002d000851393038373731 PANDAS_EXCLUDE="1d0002000c51303136383232 2f002e000c51303136383232" ./tests/hitl/test.sh') + docker_run("parallel tests", 5, 'PANDAS_JUNGLE=23002d000851393038373731 PANDAS_EXCLUDE="1d0002000c51303136383232 2f002e000c51303136383232" ./tests/hitl/run_parallel_tests.sh') + docker_run("serial tests", 9, 'PANDAS_JUNGLE=23002d000851393038373731 PANDAS_EXCLUDE="1d0002000c51303136383232 2f002e000c51303136383232" ./tests/hitl/run_serial_tests.sh') } } } diff --git a/README.md b/README.md index 90bc836477..3c8760b40d 100644 --- a/README.md +++ b/README.md @@ -20,8 +20,8 @@ sudo apt-get install dfu-util gcc-arm-none-eabi python3-pip libffi-dev git ``` ```bash # macOS -brew tap ArmMbed/homebrew-formulae -brew install python dfu-util arm-none-eabi-gcc gcc@12 +brew install --cask gcc-arm-embedded +brew install python3 dfu-util gcc@12 ``` Clone panda repository: @@ -112,7 +112,7 @@ to ensure that the behavior remains unchanged. * compiling the code and flashing it through USB. * receiving, sending, and forwarding CAN messages on all buses, over USB. -In addition, we run the [pylint](https://www.pylint.org/) and [flake8](https://github.com/PyCQA/flake8) linters on all python files within the panda repo. +In addition, we run the [ruff linter](https://github.com/astral-sh/ruff) on all python files within the panda repo. ## Hardware diff --git a/SConscript b/SConscript index ed3aff754d..73414160ae 100644 --- a/SConscript +++ b/SConscript @@ -1,6 +1,190 @@ +import os +import subprocess + + +PREFIX = "arm-none-eabi-" +BUILDER = "DEV" + +common_flags = [] + +panda_root = Dir('.').abspath + +if os.getenv("RELEASE"): + BUILD_TYPE = "RELEASE" + cert_fn = os.getenv("CERT") + assert cert_fn is not None, 'No certificate file specified. Please set CERT env variable' + assert os.path.exists(cert_fn), 'Certificate file not found. Please specify absolute path' +else: + BUILD_TYPE = "DEBUG" + cert_fn = File("./certs/debug").srcnode().abspath + common_flags += ["-DALLOW_DEBUG"] + + if os.getenv("DEBUG"): + common_flags += ["-DDEBUG"] + +def objcopy(source, target, env, for_signature): + return '$OBJCOPY -O binary %s %s' % (source[0], target[0]) + +def get_version(builder, build_type): + try: + git = subprocess.check_output(["git", "rev-parse", "--short=8", "HEAD"], encoding='utf8').strip() + except subprocess.CalledProcessError: + git = "unknown" + return f"{builder}-{git}-{build_type}" + +def get_key_header(name): + from Crypto.PublicKey import RSA + + public_fn = File(f'./certs/{name}.pub').srcnode().abspath + with open(public_fn) as f: + rsa = RSA.importKey(f.read()) + assert(rsa.size_in_bits() == 1024) + + rr = pow(2**1024, 2, rsa.n) + n0inv = 2**32 - pow(rsa.n, -1, 2**32) + + r = [ + f"RSAPublicKey {name}_rsa_key = {{", + f" .len = 0x20,", + f" .n0inv = {n0inv}U,", + f" .n = {to_c_uint32(rsa.n)},", + f" .rr = {to_c_uint32(rr)},", + f" .exponent = {rsa.e},", + f"}};", + ] + return r + +def to_c_uint32(x): + nums = [] + for _ in range(0x20): + nums.append(x % (2**32)) + x //= (2**32) + return "{" + 'U,'.join(map(str, nums)) + "U}" + + +def build_project(project_name, project, extra_flags): + linkerscript_fn = File(project["LINKER_SCRIPT"]).srcnode().abspath + + flags = project["PROJECT_FLAGS"] + extra_flags + common_flags + [ + "-Wall", + "-Wextra", + "-Wstrict-prototypes", + "-Werror", + "-mlittle-endian", + "-mthumb", + "-nostdlib", + "-fno-builtin", + "-std=gnu11", + "-fmax-errors=3", + f"-T{linkerscript_fn}", + ] + + includes = [ + '.', + '..', + panda_root, + f"{panda_root}/board/", + f"{panda_root}/board/stm32fx/inc", + f"{panda_root}/board/stm32h7/inc", + ] + + env = Environment( + ENV=os.environ, + CC=PREFIX + 'gcc', + AS=PREFIX + 'gcc', + OBJCOPY=PREFIX + 'objcopy', + OBJDUMP=PREFIX + 'objdump', + CFLAGS=flags, + ASFLAGS=flags, + LINKFLAGS=flags, + CPPPATH=includes, + ASCOM="$AS $ASFLAGS -o $TARGET -c $SOURCES", + BUILDERS={ + 'Objcopy': Builder(generator=objcopy, suffix='.bin', src_suffix='.elf') + } + ) + + startup = env.Object(f"obj/startup_{project_name}", project["STARTUP_FILE"]) + + # Bootstub + crypto_obj = [ + env.Object(f"rsa-{project_name}", f"{panda_root}/crypto/rsa.c"), + env.Object(f"sha-{project_name}", f"{panda_root}/crypto/sha.c") + ] + bootstub_obj = env.Object(f"bootstub-{project_name}", File(project.get("BOOTSTUB", f"{panda_root}/board/bootstub.c"))) + bootstub_elf = env.Program(f"obj/bootstub.{project_name}.elf", + [startup] + crypto_obj + [bootstub_obj]) + env.Objcopy(f"obj/bootstub.{project_name}.bin", bootstub_elf) + + # Build main + main_obj = env.Object(f"main-{project_name}", project["MAIN"]) + main_elf = env.Program(f"obj/{project_name}.elf", [startup, main_obj], + LINKFLAGS=[f"-Wl,--section-start,.isr_vector={project['APP_START_ADDRESS']}"] + flags) + main_bin = env.Objcopy(f"obj/{project_name}.bin", main_elf) + + # Sign main + sign_py = File(f"{panda_root}/crypto/sign.py").srcnode().abspath + env.Command(f"obj/{project_name}.bin.signed", main_bin, f"SETLEN=1 {sign_py} $SOURCE $TARGET {cert_fn}") + + +base_project_f4 = { + "MAIN": "main.c", + "STARTUP_FILE": File("./board/stm32fx/startup_stm32f413xx.s"), + "LINKER_SCRIPT": File("./board/stm32fx/stm32f4_flash.ld"), + "APP_START_ADDRESS": "0x8004000", + "PROJECT_FLAGS": [ + "-mcpu=cortex-m4", + "-mhard-float", + "-DSTM32F4", + "-DSTM32F413xx", + "-mfpu=fpv4-sp-d16", + "-fsingle-precision-constant", + "-Os", + "-g", + ], +} + +base_project_h7 = { + "MAIN": "main.c", + "STARTUP_FILE": File("./board/stm32h7/startup_stm32h7x5xx.s"), + "LINKER_SCRIPT": File("./board/stm32h7/stm32h7x5_flash.ld"), + "APP_START_ADDRESS": "0x8020000", + "PROJECT_FLAGS": [ + "-mcpu=cortex-m7", + "-mhard-float", + "-DSTM32H7", + "-DSTM32H725xx", + "-mfpu=fpv5-d16", + "-fsingle-precision-constant", + "-Os", + "-g", + ], +} + +Export('base_project_f4', 'base_project_h7', 'build_project') + + +# Common autogenerated includes +with open("board/obj/gitversion.h", "w") as f: + f.write(f'const uint8_t gitversion[] = "{get_version(BUILDER, BUILD_TYPE)}";\n') + +with open("board/obj/version", "w") as f: + f.write(f'{get_version(BUILDER, BUILD_TYPE)}') + +certs = [get_key_header(n) for n in ["debug", "release"]] +with open("board/obj/cert.h", "w") as f: + for cert in certs: + f.write("\n".join(cert) + "\n") + # panda fw SConscript('board/SConscript') +# pedal fw +SConscript('board/pedal/SConscript') + +# panda jungle fw +SConscript('board/jungle/SConscript') + # test files if GetOption('test'): SConscript('tests/libpanda/SConscript') diff --git a/SConstruct b/SConstruct index fd83d87523..62f55787ba 100644 --- a/SConstruct +++ b/SConstruct @@ -3,5 +3,9 @@ AddOption('--test', default=True, help='build test files') +AddOption('--ubsan', + action='store_true', + help='turn on UBSan') + # panda fw & test files SConscript('SConscript') diff --git a/__init__.py b/__init__.py index 8b43e99380..e8e05c52d5 100644 --- a/__init__.py +++ b/__init__.py @@ -1,5 +1,11 @@ -from .python.constants import McuType, BASEDIR # noqa: F401 +from .python.constants import McuType, BASEDIR, FW_PATH, USBPACKET_MAX_SIZE # noqa: F401 +from .python.spi import PandaSpiException, PandaProtocolMismatch # noqa: F401 from .python.serial import PandaSerial # noqa: F401 +from .python.canhandle import CanHandle # 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) + pack_can_buffer, unpack_can_buffer, calculate_checksum, unpack_log, + DLC_TO_LEN, LEN_TO_DLC, ALTERNATIVE_EXPERIENCE, CANPACKET_HEAD_SIZE) + + +# panda jungle +from .board.jungle import PandaJungle, PandaJungleDFU # noqa: F401 diff --git a/board/README.md b/board/README.md index ad9091411f..a6831e3a97 100644 --- a/board/README.md +++ b/board/README.md @@ -4,8 +4,8 @@ Programming **Panda** ``` -./recover.py # flash bootstub ./flash.py # flash application +./recover.py # flash bootstub ``` Troubleshooting @@ -13,7 +13,8 @@ Troubleshooting If your panda will not flash and green LED is on, use `recover.py`. If panda is blinking fast with green LED, use `flash.py`. + Otherwise if LED is off and panda can't be seen with `lsusb` command, use [panda paw](https://comma.ai/shop/products/panda-paw) to go into DFU mode. -[dfu-util](http://github.com/dsigma/dfu-util.git) for flashing +If your device has an internal panda and none of the above works, try running `../tests/reflash_internal_panda.py`. diff --git a/board/SConscript b/board/SConscript index dc095e712a..45fa0f416e 100644 --- a/board/SConscript +++ b/board/SConscript @@ -1,194 +1,18 @@ import os import copy -import subprocess -PREFIX = "arm-none-eabi-" -BUILDER = "DEV" +Import('build_project', 'base_project_f4', 'base_project_h7') -common_flags = [] -build_projects = {} - -build_projects["pedal"] = { - "MAIN": "pedal/main.c", - "STARTUP_FILE": "stm32fx/startup_stm32f205xx.s", - "LINKER_SCRIPT": "stm32fx/stm32f2_flash.ld", - "APP_START_ADDRESS": "0x8004000", - "PROJECT_FLAGS": [ - "-mcpu=cortex-m3", - "-msoft-float", - "-DSTM32F2", - "-DSTM32F205xx", - "-O2", - "-DPEDAL", - ], -} - -build_projects["pedal_usb"] = copy.deepcopy(build_projects["pedal"]) -build_projects["pedal_usb"]["PROJECT_FLAGS"].append("-DPEDAL_USB") - -build_projects["panda"] = { - "MAIN": "main.c", - "STARTUP_FILE": "stm32fx/startup_stm32f413xx.s", - "LINKER_SCRIPT": "stm32fx/stm32f4_flash.ld", - "APP_START_ADDRESS": "0x8004000", - "PROJECT_FLAGS": [ - "-mcpu=cortex-m4", - "-mhard-float", - "-DSTM32F4", - "-DSTM32F413xx", - "-mfpu=fpv4-sp-d16", - "-fsingle-precision-constant", - "-Os", - "-g", - "-DPANDA", - ], +build_projects = { + "panda": base_project_f4, + "panda_h7": base_project_h7, } -build_projects["panda_h7"] = { - "MAIN": "main.c", - "STARTUP_FILE": "stm32h7/startup_stm32h7x5xx.s", - "LINKER_SCRIPT": "stm32h7/stm32h7x5_flash.ld", - "APP_START_ADDRESS": "0x8020000", - "PROJECT_FLAGS": [ - "-mcpu=cortex-m7", - "-mhard-float", - "-DSTM32H7", - "-DSTM32H725xx", - "-mfpu=fpv5-d16", - "-fsingle-precision-constant", - "-Os", - "-g", +for project_name, project in build_projects.items(): + flags = [ "-DPANDA", - ], -} - -if os.getenv("RELEASE"): - BUILD_TYPE = "RELEASE" - cert_fn = os.getenv("CERT") - assert cert_fn is not None, 'No certificate file specified. Please set CERT env variable' - assert os.path.exists(cert_fn), 'Certificate file not found. Please specify absolute path' -else: - BUILD_TYPE = "DEBUG" - cert_fn = File("../certs/debug").srcnode().abspath - common_flags += ["-DALLOW_DEBUG"] - -if os.getenv("DEBUG"): - common_flags += ["-DDEBUG"] - -includes = [ - "stm32fx/inc", - "stm32h7/inc", - "..", - ".", -] - -def get_version(builder, build_type): - try: - git = subprocess.check_output(["git", "rev-parse", "--short=8", "HEAD"], encoding='utf8').strip() - except subprocess.CalledProcessError: - git = "unknown" - return f"{builder}-{git}-{build_type}" - - -def to_c_uint32(x): - nums = [] - for _ in range(0x20): - nums.append(x % (2**32)) - x //= (2**32) - return "{" + 'U,'.join(map(str, nums)) + "U}" - - -def get_key_header(name): - from Crypto.PublicKey import RSA - - public_fn = File(f'../certs/{name}.pub').srcnode().abspath - with open(public_fn) as f: - rsa = RSA.importKey(f.read()) - assert(rsa.size_in_bits() == 1024) - - rr = pow(2**1024, 2, rsa.n) - n0inv = 2**32 - pow(rsa.n, -1, 2**32) - - r = [ - f"RSAPublicKey {name}_rsa_key = {{", - f" .len = 0x20,", - f" .n0inv = {n0inv}U,", - f" .n = {to_c_uint32(rsa.n)},", - f" .rr = {to_c_uint32(rr)},", - f" .exponent = {rsa.e},", - f"}};", ] - return r - -def objcopy(source, target, env, for_signature): - return '$OBJCOPY -O binary %s %s' % (source[0], target[0]) - -# Common autogenerated includes -with open("obj/gitversion.h", "w") as f: - f.write(f'const uint8_t gitversion[] = "{get_version(BUILDER, BUILD_TYPE)}";\n') - -with open("obj/version", "w") as f: - f.write(f'{get_version(BUILDER, BUILD_TYPE)}') - -certs = [get_key_header(n) for n in ["debug", "release"]] -with open("obj/cert.h", "w") as f: - for cert in certs: - f.write("\n".join(cert) + "\n") - - -for project_name in build_projects: - project = build_projects[project_name] - linkerscript_fn = File(project["LINKER_SCRIPT"]).srcnode().abspath - - flags = [ - "-Wall", - "-Wextra", - "-Wstrict-prototypes", - "-Werror", - "-mlittle-endian", - "-mthumb", - "-nostdlib", - "-fno-builtin", - f"-T{linkerscript_fn}", - "-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', - AS=PREFIX + 'gcc', - OBJCOPY=PREFIX + 'objcopy', - OBJDUMP=PREFIX + 'objdump', - ASCOM="$AS $ASFLAGS -o $TARGET -c $SOURCES", - CFLAGS=flags, - ASFLAGS=flags, - LINKFLAGS=flags, - CPPPATH=includes, - BUILDERS={ - 'Objcopy': Builder(generator=objcopy, suffix='.bin', src_suffix='.elf') - } - ) - - startup = project_env.Object(f"obj/startup_{project_name}", project["STARTUP_FILE"]) - - # Bootstub - crypto_obj = [ - project_env.Object(f"rsa-{project_name}", "../crypto/rsa.c"), - project_env.Object(f"sha-{project_name}", "../crypto/sha.c") - ] - bootstub_obj = project_env.Object(f"bootstub-{project_name}", "bootstub.c") - bootstub_elf = project_env.Program(f"obj/bootstub.{project_name}.elf", [startup] + crypto_obj + [bootstub_obj]) - bootstub_bin = project_env.Objcopy(f"obj/bootstub.{project_name}.bin", bootstub_elf) - - # Build main - main_obj = project_env.Object(f"main-{project_name}", project["MAIN"]) - main_elf = project_env.Program(f"obj/{project_name}.elf", [startup, main_obj], - LINKFLAGS=[f"-Wl,--section-start,.isr_vector={project['APP_START_ADDRESS']}"] + flags) - main_bin = project_env.Objcopy(f"obj/{project_name}.bin", main_elf) - - # Sign main - sign_py = File("../crypto/sign.py").srcnode().abspath - panda_bin_signed = project_env.Command(f"obj/{project_name}.bin.signed", main_bin, f"SETLEN=1 {sign_py} $SOURCE $TARGET {cert_fn}") + build_project(project_name, project, flags) diff --git a/board/boards/black.h b/board/boards/black.h index e4d5bb8e58..80b265394e 100644 --- a/board/boards/black.h +++ b/board/boards/black.h @@ -25,7 +25,7 @@ void black_enable_can_transceiver(uint8_t transceiver, bool enabled) { void black_enable_can_transceivers(bool enabled) { for(uint8_t i=1U; i<=4U; i++){ // Leave main CAN always on for CAN-based ignition detection - if((car_harness_status == HARNESS_STATUS_FLIPPED) ? (i == 3U) : (i == 1U)){ + if((harness.status == HARNESS_STATUS_FLIPPED) ? (i == 3U) : (i == 1U)){ black_enable_can_transceiver(i, true); } else { black_enable_can_transceiver(i, enabled); @@ -49,41 +49,17 @@ void black_set_led(uint8_t color, bool enabled) { } } -void black_set_gps_load_switch(bool enabled) { - set_gpio_output(GPIOC, 12, enabled); -} - void black_set_usb_load_switch(bool enabled) { set_gpio_output(GPIOB, 1, !enabled); } -void black_set_gps_mode(uint8_t mode) { - switch (mode) { - case GPS_DISABLED: - // GPS OFF - set_gpio_output(GPIOC, 12, 0); - set_gpio_output(GPIOC, 5, 0); - break; - case GPS_ENABLED: - // GPS ON - set_gpio_output(GPIOC, 12, 1); - set_gpio_output(GPIOC, 5, 1); - break; - case GPS_BOOTMODE: - set_gpio_output(GPIOC, 12, 1); - set_gpio_output(GPIOC, 5, 0); - break; - default: - print("Invalid GPS mode\n"); - break; - } -} - -void black_set_can_mode(uint8_t mode){ +void black_set_can_mode(uint8_t mode) { + black_enable_can_transceiver(2U, false); + black_enable_can_transceiver(4U, false); switch (mode) { case CAN_MODE_NORMAL: case CAN_MODE_OBD_CAN2: - if ((bool)(mode == CAN_MODE_NORMAL) != (bool)(car_harness_status == HARNESS_STATUS_FLIPPED)) { + if ((bool)(mode == CAN_MODE_NORMAL) != (bool)(harness.status == HARNESS_STATUS_FLIPPED)) { // B12,B13: disable OBD mode set_gpio_mode(GPIOB, 12, MODE_INPUT); set_gpio_mode(GPIOB, 13, MODE_INPUT); @@ -91,6 +67,8 @@ void black_set_can_mode(uint8_t mode){ // B5,B6: normal CAN2 mode set_gpio_alternate(GPIOB, 5, GPIO_AF9_CAN2); set_gpio_alternate(GPIOB, 6, GPIO_AF9_CAN2); + black_enable_can_transceiver(2U, true); + } else { // B5,B6: disable normal CAN2 mode set_gpio_mode(GPIOB, 5, MODE_INPUT); @@ -99,6 +77,7 @@ void black_set_can_mode(uint8_t mode){ // B12,B13: OBD mode set_gpio_alternate(GPIOB, 12, GPIO_AF9_CAN2); set_gpio_alternate(GPIOB, 13, GPIO_AF9_CAN2); + black_enable_can_transceiver(4U, true); } break; default: @@ -124,8 +103,9 @@ void black_init(void) { set_gpio_mode(GPIOC, 0, MODE_ANALOG); set_gpio_mode(GPIOC, 3, MODE_ANALOG); - // Set default state of GPS - current_board->set_gps_mode(GPS_ENABLED); + // GPS OFF + set_gpio_output(GPIOC, 5, 0); + set_gpio_output(GPIOC, 12, 0); // C10: OBD_SBU1_RELAY (harness relay driving output) // C11: OBD_SBU2_RELAY (harness relay driving output) @@ -136,9 +116,6 @@ void black_init(void) { set_gpio_output(GPIOC, 10, 1); set_gpio_output(GPIOC, 11, 1); - // Turn on GPS load switch. - black_set_gps_load_switch(true); - // Turn on USB load switch. black_set_usb_load_switch(true); @@ -160,11 +137,17 @@ void black_init(void) { black_set_can_mode(CAN_MODE_NORMAL); // flip CAN0 and CAN2 if we are flipped - if (car_harness_status == HARNESS_STATUS_FLIPPED) { + if (harness.status == HARNESS_STATUS_FLIPPED) { can_flip_buses(0, 2); } } +void black_init_bootloader(void) { + // GPS OFF + set_gpio_output(GPIOC, 5, 0); + set_gpio_output(GPIOC, 12, 0); +} + const harness_configuration black_harness_config = { .has_harness = true, .GPIO_SBU1 = GPIOC, @@ -183,7 +166,6 @@ const board board_black = { .board_type = "Black", .board_tick = unused_board_tick, .harness_config = &black_harness_config, - .has_gps = true, .has_hw_gmlan = false, .has_obd = true, .has_lin = false, @@ -191,14 +173,14 @@ const board board_black = { .has_canfd = false, .has_rtc_battery = false, .fan_max_rpm = 0U, - .adc_scale = 8862U, + .avdd_mV = 3300U, .fan_stall_recovery = false, .fan_enable_cooldown_time = 0U, .init = black_init, + .init_bootloader = black_init_bootloader, .enable_can_transceiver = black_enable_can_transceiver, .enable_can_transceivers = black_enable_can_transceivers, .set_led = black_set_led, - .set_gps_mode = black_set_gps_mode, .set_can_mode = black_set_can_mode, .check_ignition = black_check_ignition, .read_current = unused_read_current, diff --git a/board/boards/board_declarations.h b/board/boards/board_declarations.h index db5c742295..e8b1cdcffa 100644 --- a/board/boards/board_declarations.h +++ b/board/boards/board_declarations.h @@ -1,9 +1,9 @@ // ******************** Prototypes ******************** typedef void (*board_init)(void); +typedef void (*board_init_bootloader)(void); typedef void (*board_enable_can_transceiver)(uint8_t transceiver, bool enabled); typedef void (*board_enable_can_transceivers)(bool enabled); typedef void (*board_set_led)(uint8_t color, bool enabled); -typedef void (*board_set_gps_mode)(uint8_t mode); typedef void (*board_set_can_mode)(uint8_t mode); typedef bool (*board_check_ignition)(void); typedef uint32_t (*board_read_current)(void); @@ -11,13 +11,12 @@ 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_siren)(bool enabled); -typedef void (*board_board_tick)(bool ignition, bool usb_enum, bool heartbeat_seen); +typedef bool (*board_board_tick)(bool ignition, bool usb_enum, bool heartbeat_seen, bool harness_inserted); typedef bool (*board_read_som_gpio)(void); struct board { const char *board_type; const harness_configuration *harness_config; - const bool has_gps; const bool has_hw_gmlan; const bool has_obd; const bool has_lin; @@ -25,14 +24,14 @@ struct board { const bool has_canfd; const bool has_rtc_battery; const uint16_t fan_max_rpm; - const uint16_t adc_scale; + const uint16_t avdd_mV; const bool fan_stall_recovery; const uint8_t fan_enable_cooldown_time; board_init init; + board_init_bootloader init_bootloader; board_enable_can_transceiver enable_can_transceiver; board_enable_can_transceivers enable_can_transceivers; board_set_led set_led; - board_set_gps_mode set_gps_mode; board_set_can_mode set_can_mode; board_check_ignition check_ignition; board_read_current read_current; @@ -68,11 +67,6 @@ struct board { #define USB_POWER_CDP 2U #define USB_POWER_DCP 3U -// GPS modes -#define GPS_DISABLED 0U -#define GPS_ENABLED 1U -#define GPS_BOOTMODE 2U - // CAN modes #define CAN_MODE_NORMAL 0U #define CAN_MODE_GMLAN_CAN2 1U diff --git a/board/boards/dos.h b/board/boards/dos.h index 2af19d7cb0..7e0a270036 100644 --- a/board/boards/dos.h +++ b/board/boards/dos.h @@ -25,7 +25,7 @@ void dos_enable_can_transceiver(uint8_t transceiver, bool enabled) { void dos_enable_can_transceivers(bool enabled) { for(uint8_t i=1U; i<=4U; i++){ // Leave main CAN always on for CAN-based ignition detection - if((car_harness_status == HARNESS_STATUS_FLIPPED) ? (i == 3U) : (i == 1U)){ + if((harness.status == HARNESS_STATUS_FLIPPED) ? (i == 3U) : (i == 1U)){ dos_enable_can_transceiver(i, true); } else { dos_enable_can_transceiver(i, enabled); @@ -53,9 +53,11 @@ void dos_set_bootkick(bool enabled){ set_gpio_output(GPIOC, 4, !enabled); } -void dos_board_tick(bool ignition, bool usb_enum, bool heartbeat_seen) { - if (ignition && !usb_enum) { - // enable bootkick if ignition seen +bool dos_board_tick(bool ignition, bool usb_enum, bool heartbeat_seen, bool harness_inserted) { + bool ret = false; + if ((ignition && !usb_enum) || harness_inserted) { + // enable bootkick if ignition seen or if plugged into a harness + ret = true; dos_set_bootkick(true); } else if (heartbeat_seen) { // disable once openpilot is up @@ -63,13 +65,16 @@ void dos_board_tick(bool ignition, bool usb_enum, bool heartbeat_seen) { } else { } + return ret; } -void dos_set_can_mode(uint8_t mode){ +void dos_set_can_mode(uint8_t mode) { + dos_enable_can_transceiver(2U, false); + dos_enable_can_transceiver(4U, false); switch (mode) { case CAN_MODE_NORMAL: case CAN_MODE_OBD_CAN2: - if ((bool)(mode == CAN_MODE_NORMAL) != (bool)(car_harness_status == HARNESS_STATUS_FLIPPED)) { + if ((bool)(mode == CAN_MODE_NORMAL) != (bool)(harness.status == HARNESS_STATUS_FLIPPED)) { // B12,B13: disable OBD mode set_gpio_mode(GPIOB, 12, MODE_INPUT); set_gpio_mode(GPIOB, 13, MODE_INPUT); @@ -77,6 +82,7 @@ void dos_set_can_mode(uint8_t mode){ // B5,B6: normal CAN2 mode set_gpio_alternate(GPIOB, 5, GPIO_AF9_CAN2); set_gpio_alternate(GPIOB, 6, GPIO_AF9_CAN2); + dos_enable_can_transceiver(2U, true); } else { // B5,B6: disable normal CAN2 mode set_gpio_mode(GPIOB, 5, MODE_INPUT); @@ -85,6 +91,7 @@ void dos_set_can_mode(uint8_t mode){ // B12,B13: OBD mode set_gpio_alternate(GPIOB, 12, GPIO_AF9_CAN2); set_gpio_alternate(GPIOB, 13, GPIO_AF9_CAN2); + dos_enable_can_transceiver(4U, true); } break; default: @@ -177,7 +184,7 @@ void dos_init(void) { dos_set_can_mode(CAN_MODE_NORMAL); // flip CAN0 and CAN2 if we are flipped - if (car_harness_status == HARNESS_STATUS_FLIPPED) { + if (harness.status == HARNESS_STATUS_FLIPPED) { can_flip_buses(0, 2); } @@ -203,7 +210,6 @@ const board board_dos = { .board_type = "Dos", .board_tick = dos_board_tick, .harness_config = &dos_harness_config, - .has_gps = false, .has_hw_gmlan = false, .has_obd = true, .has_lin = false, @@ -215,14 +221,14 @@ const board board_dos = { .has_canfd = false, .has_rtc_battery = true, .fan_max_rpm = 6500U, - .adc_scale = 8862U, + .avdd_mV = 3300U, .fan_stall_recovery = true, .fan_enable_cooldown_time = 3U, .init = dos_init, + .init_bootloader = unused_init_bootloader, .enable_can_transceiver = dos_enable_can_transceiver, .enable_can_transceivers = dos_enable_can_transceivers, .set_led = dos_set_led, - .set_gps_mode = unused_set_gps_mode, .set_can_mode = dos_set_can_mode, .check_ignition = dos_check_ignition, .read_current = unused_read_current, diff --git a/board/boards/grey.h b/board/boards/grey.h index ccc7787203..a74f894480 100644 --- a/board/boards/grey.h +++ b/board/boards/grey.h @@ -4,40 +4,10 @@ // Most hardware functionality is similar to white panda -void grey_init(void) { - white_grey_common_init(); - - // Set default state of GPS - current_board->set_gps_mode(GPS_ENABLED); -} - -void grey_set_gps_mode(uint8_t mode) { - switch (mode) { - case GPS_DISABLED: - // GPS OFF - set_gpio_output(GPIOC, 14, 0); - set_gpio_output(GPIOC, 5, 0); - break; - case GPS_ENABLED: - // GPS ON - set_gpio_output(GPIOC, 14, 1); - set_gpio_output(GPIOC, 5, 1); - break; - case GPS_BOOTMODE: - set_gpio_output(GPIOC, 14, 1); - set_gpio_output(GPIOC, 5, 0); - break; - default: - print("Invalid ESP/GPS mode\n"); - break; - } -} - const board board_grey = { .board_type = "Grey", .board_tick = unused_board_tick, .harness_config = &white_harness_config, - .has_gps = true, .has_hw_gmlan = true, .has_obd = false, .has_lin = true, @@ -45,14 +15,14 @@ const board board_grey = { .has_canfd = false, .has_rtc_battery = false, .fan_max_rpm = 0U, - .adc_scale = 8862U, + .avdd_mV = 3300U, .fan_stall_recovery = false, .fan_enable_cooldown_time = 0U, - .init = grey_init, + .init = white_grey_init, + .init_bootloader = white_grey_init_bootloader, .enable_can_transceiver = white_enable_can_transceiver, .enable_can_transceivers = white_enable_can_transceivers, .set_led = white_set_led, - .set_gps_mode = grey_set_gps_mode, .set_can_mode = white_set_can_mode, .check_ignition = white_check_ignition, .read_current = white_read_current, diff --git a/board/boards/pedal.h b/board/boards/pedal.h index 509a4bb4b0..f2ae367e5d 100644 --- a/board/boards/pedal.h +++ b/board/boards/pedal.h @@ -30,11 +30,6 @@ void pedal_set_led(uint8_t color, bool enabled) { } } -void pedal_set_gps_mode(uint8_t mode) { - UNUSED(mode); - print("Trying to set ESP/GPS mode on pedal. This is not supported.\n"); -} - void pedal_set_can_mode(uint8_t mode){ switch (mode) { case CAN_MODE_NORMAL: @@ -75,7 +70,6 @@ const board board_pedal = { .board_type = "Pedal", .board_tick = unused_board_tick, .harness_config = &pedal_harness_config, - .has_gps = false, .has_hw_gmlan = false, .has_obd = false, .has_lin = false, @@ -83,14 +77,14 @@ const board board_pedal = { .has_canfd = false, .has_rtc_battery = false, .fan_max_rpm = 0U, - .adc_scale = 8862U, + .avdd_mV = 3300U, .fan_stall_recovery = false, .fan_enable_cooldown_time = 0U, .init = pedal_init, + .init_bootloader = unused_init_bootloader, .enable_can_transceiver = pedal_enable_can_transceiver, .enable_can_transceivers = pedal_enable_can_transceivers, .set_led = pedal_set_led, - .set_gps_mode = pedal_set_gps_mode, .set_can_mode = pedal_set_can_mode, .check_ignition = pedal_check_ignition, .read_current = unused_read_current, diff --git a/board/boards/red.h b/board/boards/red.h index 2be859d9f6..987554698e 100644 --- a/board/boards/red.h +++ b/board/boards/red.h @@ -22,7 +22,7 @@ void red_enable_can_transceiver(uint8_t transceiver, bool enabled) { } void red_enable_can_transceivers(bool enabled) { - uint8_t main_bus = (car_harness_status == HARNESS_STATUS_FLIPPED) ? 3U : 1U; + uint8_t main_bus = (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) { @@ -50,10 +50,12 @@ void red_set_led(uint8_t color, bool enabled) { } void red_set_can_mode(uint8_t mode) { + red_enable_can_transceiver(2U, false); + red_enable_can_transceiver(4U, false); switch (mode) { case CAN_MODE_NORMAL: case CAN_MODE_OBD_CAN2: - if ((bool)(mode == CAN_MODE_NORMAL) != (bool)(car_harness_status == HARNESS_STATUS_FLIPPED)) { + if ((bool)(mode == CAN_MODE_NORMAL) != (bool)(harness.status == HARNESS_STATUS_FLIPPED)) { // B12,B13: disable normal mode set_gpio_pullup(GPIOB, 12, PULL_NONE); set_gpio_mode(GPIOB, 12, MODE_ANALOG); @@ -67,6 +69,7 @@ void red_set_can_mode(uint8_t mode) { set_gpio_pullup(GPIOB, 6, PULL_NONE); set_gpio_alternate(GPIOB, 6, GPIO_AF9_FDCAN2); + red_enable_can_transceiver(2U, true); } else { // B5,B6: disable normal mode set_gpio_pullup(GPIOB, 5, PULL_NONE); @@ -80,6 +83,7 @@ void red_set_can_mode(uint8_t mode) { set_gpio_pullup(GPIOB, 13, PULL_NONE); set_gpio_alternate(GPIOB, 13, GPIO_AF9_FDCAN2); + red_enable_can_transceiver(4U, true); } break; default: @@ -147,7 +151,7 @@ void red_init(void) { red_set_can_mode(CAN_MODE_NORMAL); // flip CAN0 and CAN2 if we are flipped - if (car_harness_status == HARNESS_STATUS_FLIPPED) { + if (harness.status == HARNESS_STATUS_FLIPPED) { can_flip_buses(0, 2); } } @@ -170,7 +174,6 @@ const board board_red = { .board_type = "Red", .board_tick = unused_board_tick, .harness_config = &red_harness_config, - .has_gps = false, .has_hw_gmlan = false, .has_obd = true, .has_lin = false, @@ -178,14 +181,14 @@ const board board_red = { .has_canfd = true, .has_rtc_battery = false, .fan_max_rpm = 0U, - .adc_scale = 5539U, + .avdd_mV = 3300U, .fan_stall_recovery = false, .fan_enable_cooldown_time = 0U, .init = red_init, + .init_bootloader = unused_init_bootloader, .enable_can_transceiver = red_enable_can_transceiver, .enable_can_transceivers = red_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, diff --git a/board/boards/red_chiplet.h b/board/boards/red_chiplet.h index c4816a9e90..1301cd3e54 100644 --- a/board/boards/red_chiplet.h +++ b/board/boards/red_chiplet.h @@ -24,7 +24,7 @@ void red_chiplet_enable_can_transceiver(uint8_t transceiver, bool enabled) { } void red_chiplet_enable_can_transceivers(bool enabled) { - uint8_t main_bus = (car_harness_status == HARNESS_STATUS_FLIPPED) ? 3U : 1U; + uint8_t main_bus = (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) { @@ -35,6 +35,48 @@ void red_chiplet_enable_can_transceivers(bool enabled) { } } +void red_chiplet_set_can_mode(uint8_t mode) { + red_chiplet_enable_can_transceiver(2U, false); + red_chiplet_enable_can_transceiver(4U, false); + switch (mode) { + case CAN_MODE_NORMAL: + case CAN_MODE_OBD_CAN2: + if ((bool)(mode == CAN_MODE_NORMAL) != (bool)(harness.status == HARNESS_STATUS_FLIPPED)) { + // B12,B13: disable normal mode + set_gpio_pullup(GPIOB, 12, PULL_NONE); + set_gpio_mode(GPIOB, 12, MODE_ANALOG); + + set_gpio_pullup(GPIOB, 13, PULL_NONE); + set_gpio_mode(GPIOB, 13, MODE_ANALOG); + + // B5,B6: FDCAN2 mode + set_gpio_pullup(GPIOB, 5, PULL_NONE); + set_gpio_alternate(GPIOB, 5, GPIO_AF9_FDCAN2); + + set_gpio_pullup(GPIOB, 6, PULL_NONE); + set_gpio_alternate(GPIOB, 6, GPIO_AF9_FDCAN2); + red_chiplet_enable_can_transceiver(2U, true); + } else { + // B5,B6: disable normal mode + set_gpio_pullup(GPIOB, 5, PULL_NONE); + set_gpio_mode(GPIOB, 5, MODE_ANALOG); + + set_gpio_pullup(GPIOB, 6, PULL_NONE); + set_gpio_mode(GPIOB, 6, MODE_ANALOG); + // B12,B13: FDCAN2 mode + set_gpio_pullup(GPIOB, 12, PULL_NONE); + set_gpio_alternate(GPIOB, 12, GPIO_AF9_FDCAN2); + + set_gpio_pullup(GPIOB, 13, PULL_NONE); + set_gpio_alternate(GPIOB, 13, GPIO_AF9_FDCAN2); + red_chiplet_enable_can_transceiver(4U, true); + } + break; + default: + break; + } +} + void red_chiplet_set_fan_or_usb_load_switch(bool enabled) { set_gpio_output(GPIOD, 3, enabled); } @@ -89,10 +131,10 @@ void red_chiplet_init(void) { red_set_led(LED_BLUE, false); // Set normal CAN mode - red_set_can_mode(CAN_MODE_NORMAL); + red_chiplet_set_can_mode(CAN_MODE_NORMAL); // flip CAN0 and CAN2 if we are flipped - if (car_harness_status == HARNESS_STATUS_FLIPPED) { + if (harness.status == HARNESS_STATUS_FLIPPED) { can_flip_buses(0, 2); } } diff --git a/board/boards/red_v2.h b/board/boards/red_v2.h index 3e8f2f2e0e..df663032b0 100644 --- a/board/boards/red_v2.h +++ b/board/boards/red_v2.h @@ -14,7 +14,6 @@ const board board_red_v2 = { .board_type = "Red_v2", .board_tick = unused_board_tick, .harness_config = &red_chiplet_harness_config, - .has_gps = false, .has_hw_gmlan = false, .has_obd = true, .has_lin = false, @@ -22,19 +21,20 @@ const board board_red_v2 = { .has_canfd = true, .has_rtc_battery = true, .fan_max_rpm = 0U, - .adc_scale = 5539U, + .avdd_mV = 3300U, .fan_stall_recovery = false, .fan_enable_cooldown_time = 0U, .init = red_panda_v2_init, + .init_bootloader = unused_init_bootloader, .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, + .set_can_mode = red_chiplet_set_can_mode, .check_ignition = red_check_ignition, .read_current = unused_read_current, .set_fan_enabled = unused_set_fan_enabled, .set_ir_power = unused_set_ir_power, .set_phone_power = unused_set_phone_power, - .set_siren = unused_set_siren + .set_siren = unused_set_siren, + .read_som_gpio = unused_read_som_gpio }; diff --git a/board/boards/tres.h b/board/boards/tres.h index c7c954ab5a..c1f1f9ec22 100644 --- a/board/boards/tres.h +++ b/board/boards/tres.h @@ -19,10 +19,12 @@ void tres_set_bootkick(bool enabled){ } bool tres_ignition_prev = false; -void tres_board_tick(bool ignition, bool usb_enum, bool heartbeat_seen) { +bool tres_board_tick(bool ignition, bool usb_enum, bool heartbeat_seen, bool harness_inserted) { UNUSED(usb_enum); - if (ignition && !tres_ignition_prev) { + bool ret = false; + if ((ignition && !tres_ignition_prev) || harness_inserted) { // enable bootkick on rising edge of ignition + ret = true; tres_set_bootkick(true); } else if (heartbeat_seen) { // disable once openpilot is up @@ -31,6 +33,7 @@ void tres_board_tick(bool ignition, bool usb_enum, bool heartbeat_seen) { } tres_ignition_prev = ignition; + return ret; } void tres_set_fan_enabled(bool enabled) { @@ -86,7 +89,6 @@ 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, @@ -94,15 +96,15 @@ const board board_tres = { .has_canfd = true, .has_rtc_battery = true, .fan_max_rpm = 6600U, - .adc_scale = 3021U, + .avdd_mV = 1800U, .fan_stall_recovery = false, .fan_enable_cooldown_time = 3U, .init = tres_init, + .init_bootloader = unused_init_bootloader, .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, + .set_can_mode = red_chiplet_set_can_mode, .check_ignition = red_check_ignition, .read_current = unused_read_current, .set_fan_enabled = tres_set_fan_enabled, diff --git a/board/boards/uno.h b/board/boards/uno.h index abf8331c6f..27c2e40297 100644 --- a/board/boards/uno.h +++ b/board/boards/uno.h @@ -27,7 +27,7 @@ void uno_enable_can_transceiver(uint8_t transceiver, bool enabled) { void uno_enable_can_transceivers(bool enabled) { for(uint8_t i=1U; i<=4U; i++){ // Leave main CAN always on for CAN-based ignition detection - if((car_harness_status == HARNESS_STATUS_FLIPPED) ? (i == 3U) : (i == 1U)){ + if((harness.status == HARNESS_STATUS_FLIPPED) ? (i == 3U) : (i == 1U)){ uno_enable_can_transceiver(i, true); } else { uno_enable_can_transceiver(i, enabled); @@ -51,10 +51,6 @@ void uno_set_led(uint8_t color, bool enabled) { } } -void uno_set_gps_load_switch(bool enabled) { - set_gpio_output(GPIOC, 12, enabled); -} - void uno_set_bootkick(bool enabled){ if (enabled) { set_gpio_output(GPIOB, 14, false); @@ -73,36 +69,13 @@ void uno_set_phone_power(bool enabled){ set_gpio_output(GPIOB, 4, enabled); } -void uno_set_gps_mode(uint8_t mode) { - switch (mode) { - case GPS_DISABLED: - // GPS OFF - set_gpio_output(GPIOB, 1, 0); - set_gpio_output(GPIOC, 5, 0); - uno_set_gps_load_switch(false); - break; - case GPS_ENABLED: - // GPS ON - set_gpio_output(GPIOB, 1, 1); - set_gpio_output(GPIOC, 5, 1); - uno_set_gps_load_switch(true); - break; - case GPS_BOOTMODE: - set_gpio_output(GPIOB, 1, 1); - set_gpio_output(GPIOC, 5, 0); - uno_set_gps_load_switch(true); - break; - default: - print("Invalid ESP/GPS mode\n"); - break; - } -} - -void uno_set_can_mode(uint8_t mode){ +void uno_set_can_mode(uint8_t mode) { + uno_enable_can_transceiver(2U, false); + uno_enable_can_transceiver(4U, false); switch (mode) { case CAN_MODE_NORMAL: case CAN_MODE_OBD_CAN2: - if ((bool)(mode == CAN_MODE_NORMAL) != (bool)(car_harness_status == HARNESS_STATUS_FLIPPED)) { + if ((bool)(mode == CAN_MODE_NORMAL) != (bool)(harness.status == HARNESS_STATUS_FLIPPED)) { // B12,B13: disable OBD mode set_gpio_mode(GPIOB, 12, MODE_INPUT); set_gpio_mode(GPIOB, 13, MODE_INPUT); @@ -110,6 +83,7 @@ void uno_set_can_mode(uint8_t mode){ // B5,B6: normal CAN2 mode set_gpio_alternate(GPIOB, 5, GPIO_AF9_CAN2); set_gpio_alternate(GPIOB, 6, GPIO_AF9_CAN2); + uno_enable_can_transceiver(2U, true); } else { // B5,B6: disable normal CAN2 mode set_gpio_mode(GPIOB, 5, MODE_INPUT); @@ -118,6 +92,7 @@ void uno_set_can_mode(uint8_t mode){ // B12,B13: OBD mode set_gpio_alternate(GPIOB, 12, GPIO_AF9_CAN2); set_gpio_alternate(GPIOB, 13, GPIO_AF9_CAN2); + uno_enable_can_transceiver(4U, true); } break; default: @@ -126,15 +101,17 @@ void uno_set_can_mode(uint8_t mode){ } } -void uno_board_tick(bool ignition, bool usb_enum, bool heartbeat_seen) { +bool uno_board_tick(bool ignition, bool usb_enum, bool heartbeat_seen, bool harness_inserted) { UNUSED(ignition); UNUSED(usb_enum); UNUSED(heartbeat_seen); + UNUSED(harness_inserted); if (bootkick_timer != 0U) { bootkick_timer--; } else { uno_set_bootkick(false); } + return false; } bool uno_check_ignition(void){ @@ -166,8 +143,10 @@ void uno_init(void) { set_gpio_mode(GPIOC, 0, MODE_ANALOG); set_gpio_mode(GPIOC, 3, MODE_ANALOG); - // Set default state of GPS - current_board->set_gps_mode(GPS_ENABLED); + // GPS off + set_gpio_output(GPIOB, 1, 0); + set_gpio_output(GPIOC, 5, 0); + set_gpio_output(GPIOC, 12, 0); // C10: OBD_SBU1_RELAY (harness relay driving output) // C11: OBD_SBU2_RELAY (harness relay driving output) @@ -181,9 +160,6 @@ void uno_init(void) { // C8: FAN PWM aka TIM3_CH3 set_gpio_alternate(GPIOC, 8, GPIO_AF2_TIM3); - // Turn on GPS load switch. - uno_set_gps_load_switch(true); - // Turn on phone regulator uno_set_phone_power(true); @@ -210,12 +186,12 @@ void uno_init(void) { uno_set_can_mode(CAN_MODE_NORMAL); // flip CAN0 and CAN2 if we are flipped - if (car_harness_status == HARNESS_STATUS_FLIPPED) { + if (harness.status == HARNESS_STATUS_FLIPPED) { can_flip_buses(0, 2); } // Switch to phone usb mode if harness connection is powered by less than 7V - if(adc_get_voltage(current_board->adc_scale) < 7000U){ + if((adc_get_mV(ADCCHAN_VIN) * VIN_READOUT_DIVIDER) < 7000U){ uno_set_usb_switch(true); } else { uno_set_usb_switch(false); @@ -225,6 +201,13 @@ void uno_init(void) { uno_bootkick(); } +void uno_init_bootloader(void) { + // GPS off + set_gpio_output(GPIOB, 1, 0); + set_gpio_output(GPIOC, 5, 0); + set_gpio_output(GPIOC, 12, 0); +} + const harness_configuration uno_harness_config = { .has_harness = true, .GPIO_SBU1 = GPIOC, @@ -243,7 +226,6 @@ const board board_uno = { .board_type = "Uno", .board_tick = uno_board_tick, .harness_config = &uno_harness_config, - .has_gps = true, .has_hw_gmlan = false, .has_obd = true, .has_lin = false, @@ -251,14 +233,14 @@ const board board_uno = { .has_canfd = false, .has_rtc_battery = true, .fan_max_rpm = 5100U, - .adc_scale = 8862U, + .avdd_mV = 3300U, .fan_stall_recovery = false, .fan_enable_cooldown_time = 0U, .init = uno_init, + .init_bootloader = uno_init_bootloader, .enable_can_transceiver = uno_enable_can_transceiver, .enable_can_transceivers = uno_enable_can_transceivers, .set_led = uno_set_led, - .set_gps_mode = uno_set_gps_mode, .set_can_mode = uno_set_can_mode, .check_ignition = uno_check_ignition, .read_current = unused_read_current, diff --git a/board/boards/unused_funcs.h b/board/boards/unused_funcs.h index d3b4a4c6c8..24279d2bf9 100644 --- a/board/boards/unused_funcs.h +++ b/board/boards/unused_funcs.h @@ -1,5 +1,4 @@ -void unused_set_gps_mode(uint8_t mode) { - UNUSED(mode); +void unused_init_bootloader(void) { } void unused_set_ir_power(uint8_t percentage) { @@ -22,10 +21,12 @@ uint32_t unused_read_current(void) { return 0U; } -void unused_board_tick(bool ignition, bool usb_enum, bool heartbeat_seen) { +bool unused_board_tick(bool ignition, bool usb_enum, bool heartbeat_seen, bool harness_inserted) { UNUSED(ignition); UNUSED(usb_enum); UNUSED(heartbeat_seen); + UNUSED(harness_inserted); + return false; } bool unused_read_som_gpio(void) { diff --git a/board/boards/white.h b/board/boards/white.h index ebca3b2233..f2dedf0107 100644 --- a/board/boards/white.h +++ b/board/boards/white.h @@ -65,23 +65,6 @@ void white_set_usb_power_mode(uint8_t mode){ } } -void white_set_gps_mode(uint8_t mode) { - switch (mode) { - case GPS_DISABLED: - // ESP OFF - set_gpio_output(GPIOC, 14, 0); - set_gpio_output(GPIOC, 5, 0); - break; - case GPS_BOOTMODE: - set_gpio_output(GPIOC, 14, 1); - set_gpio_output(GPIOC, 5, 0); - break; - default: - print("Invalid ESP/GPS mode\n"); - break; - } -} - void white_set_can_mode(uint8_t mode){ switch (mode) { case CAN_MODE_NORMAL: @@ -142,7 +125,7 @@ void white_set_can_mode(uint8_t mode){ } uint32_t white_read_current(void){ - return adc_get(ADCCHAN_CURRENT); + return adc_get_raw(ADCCHAN_CURRENT); } bool white_check_ignition(void){ @@ -150,7 +133,7 @@ bool white_check_ignition(void){ return !get_gpio_input(GPIOA, 1); } -void white_grey_common_init(void) { +void white_grey_init(void) { common_init_gpio(); // C3: current sense @@ -214,7 +197,7 @@ void white_grey_common_init(void) { white_set_can_mode(CAN_MODE_NORMAL); // Init usb power mode - uint32_t voltage = adc_get_voltage(current_board->adc_scale); + uint32_t voltage = adc_get_mV(ADCCHAN_VIN) * VIN_READOUT_DIVIDER; // 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 @@ -222,13 +205,16 @@ void white_grey_common_init(void) { } else { white_set_usb_power_mode(USB_POWER_CLIENT); } -} -void white_init(void) { - white_grey_common_init(); + // ESP/GPS off + set_gpio_output(GPIOC, 5, 0); + set_gpio_output(GPIOC, 14, 0); +} - // Set ESP off by default - current_board->set_gps_mode(GPS_DISABLED); +void white_grey_init_bootloader(void) { + // ESP/GPS off + set_gpio_output(GPIOC, 5, 0); + set_gpio_output(GPIOC, 14, 0); } const harness_configuration white_harness_config = { @@ -239,7 +225,6 @@ const board board_white = { .board_type = "White", .board_tick = unused_board_tick, .harness_config = &white_harness_config, - .has_gps = false, .has_hw_gmlan = true, .has_obd = false, .has_lin = true, @@ -247,14 +232,14 @@ const board board_white = { .has_canfd = false, .has_rtc_battery = false, .fan_max_rpm = 0U, - .adc_scale = 8862U, + .avdd_mV = 3300U, .fan_stall_recovery = false, .fan_enable_cooldown_time = 0U, - .init = white_init, + .init = white_grey_init, + .init_bootloader = white_grey_init_bootloader, .enable_can_transceiver = white_enable_can_transceiver, .enable_can_transceivers = white_enable_can_transceivers, .set_led = white_set_led, - .set_gps_mode = white_set_gps_mode, .set_can_mode = white_set_can_mode, .check_ignition = white_check_ignition, .read_current = white_read_current, diff --git a/board/bootstub.c b/board/bootstub.c index a2bf7dad7a..e2977e938c 100644 --- a/board/bootstub.c +++ b/board/bootstub.c @@ -41,6 +41,10 @@ int main(void) { clock_init(); detect_board_type(); +#ifdef PANDA_JUNGLE + current_board->set_panda_power(true); +#endif + if (enter_bootloader_mode == ENTER_SOFTLOADER_MAGIC) { enter_bootloader_mode = 0; soft_flasher_start(); diff --git a/board/can_comms.h b/board/can_comms.h index 02bc0aef31..980d12ba8d 100644 --- a/board/can_comms.h +++ b/board/can_comms.h @@ -100,6 +100,8 @@ void comms_can_write(uint8_t *data, uint32_t len) { pos += can_write_buffer.ptr; } } + + refresh_can_tx_slots_available(); } void comms_can_reset(void) { @@ -110,8 +112,11 @@ void comms_can_reset(void) { } // 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(); +void refresh_can_tx_slots_available(void) { + if (can_tx_check_min_slots_free(MAX_CAN_MSGS_PER_USB_BULK_TRANSFER)) { + can_tx_comms_resume_usb(); + } + if (can_tx_check_min_slots_free(MAX_CAN_MSGS_PER_SPI_BULK_TRANSFER)) { + can_tx_comms_resume_spi(); } } diff --git a/board/config.h b/board/config.h index 6bc3f80df6..507d19e7d3 100644 --- a/board/config.h +++ b/board/config.h @@ -13,15 +13,26 @@ #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 MAX_CAN_MSGS_PER_USB_BULK_TRANSFER 51U +#define MAX_CAN_MSGS_PER_SPI_BULK_TRANSFER 170U + +#define VIN_READOUT_DIVIDER 11U // USB definitions #define USB_VID 0xBBAAU -#ifdef BOOTSTUB - #define USB_PID 0xDDEEU +#ifdef PANDA_JUNGLE + #ifdef BOOTSTUB + #define USB_PID 0xDDEFU + #else + #define USB_PID 0xDDCFU + #endif #else - #define USB_PID 0xDDCCU + #ifdef BOOTSTUB + #define USB_PID 0xDDEEU + #else + #define USB_PID 0xDDCCU + #endif #endif // platform includes diff --git a/board/crc.h b/board/crc.h index 0d62dd316e..6ceaba07ef 100644 --- a/board/crc.h +++ b/board/crc.h @@ -1,3 +1,5 @@ +#pragma once + uint8_t crc_checksum(uint8_t *dat, int len, const uint8_t poly) { uint8_t crc = 0xFFU; int i; diff --git a/board/debug/README.md b/board/debug/README.md new file mode 100644 index 0000000000..d89eab818a --- /dev/null +++ b/board/debug/README.md @@ -0,0 +1,26 @@ +# In-circuit debugging using openocd and gdb + +## Hardware +Connect an ST-Link V2 programmer to the SWD pins on the board. The pins that need to be connected are: +- GND +- VTref +- SWDIO +- SWCLK +- NRST + +Make sure you're using a genuine one for boards that do not have a 3.3V panda power rail. For example, the tres runs at 1.8V, which is not supported by the clones. + +## Openocd +Install openocd. For Ubuntu 20.04, the one in the package manager works fine: `sudo apt install openocd`. + +To run, use `./debug_f4.sh (TODO)` or `./debug_h7.sh` depending on the panda. + +## GDB +You need `gdb-multiarch`. + +Once openocd is running, you can connect from gdb as follows: +``` +$ gdb-multiarch +(gdb) target ext :3333 +``` +To reset and break, use `monitor reset halt`. diff --git a/board/debug/debug_h7.sh b/board/debug/debug_h7.sh new file mode 100755 index 0000000000..a2bd88434e --- /dev/null +++ b/board/debug/debug_h7.sh @@ -0,0 +1,3 @@ +#!/bin/bash -e + +sudo openocd -f "interface/stlink.cfg" -c "transport select hla_swd" -f "target/stm32h7x.cfg" -c "init" diff --git a/board/drivers/bxcan.h b/board/drivers/bxcan.h index 25105dc536..478bd5858b 100644 --- a/board/drivers/bxcan.h +++ b/board/drivers/bxcan.h @@ -3,6 +3,11 @@ // CAN3_TX, CAN3_RX0, CAN3_SCE CAN_TypeDef *cans[] = {CAN1, CAN2, CAN3}; +uint8_t can_irq_number[3][3] = { + { CAN1_TX_IRQn, CAN1_RX0_IRQn, CAN1_SCE_IRQn }, + { CAN2_TX_IRQn, CAN2_RX0_IRQn, CAN2_SCE_IRQn }, + { CAN3_TX_IRQn, CAN3_RX0_IRQn, CAN3_SCE_IRQn }, +}; bool can_set_speed(uint8_t can_number) { bool ret = true; @@ -68,16 +73,10 @@ void can_set_gmlan(uint8_t bus) { } } -void update_can_health_pkt(uint8_t can_number, bool error_irq) { +void update_can_health_pkt(uint8_t can_number, uint32_t ir_reg) { 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); @@ -90,16 +89,31 @@ 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); + + can_health[can_number].irq0_call_rate = interrupts[can_irq_number[can_number][0]].call_rate; + can_health[can_number].irq1_call_rate = interrupts[can_irq_number[can_number][1]].call_rate; + can_health[can_number].irq2_call_rate = interrupts[can_irq_number[can_number][2]].call_rate; + + if (ir_reg != 0U) { + can_health[can_number].total_error_cnt += 1U; + + // RX message lost due to FIFO overrun + if ((CAN->RF0R & (CAN_RF0R_FOVR0)) != 0) { + can_health[can_number].total_rx_lost_cnt += 1U; + CAN->RF0R &= ~(CAN_RF0R_FOVR0); + } + can_health[can_number].can_core_reset_cnt += 1U; + llcan_clear_send(CAN); + } } -// CAN error +// ***************************** CAN ***************************** +// CANx_SCE IRQ Handler void can_sce(uint8_t can_number) { - ENTER_CRITICAL(); - update_can_health_pkt(can_number, true); - EXIT_CRITICAL(); + update_can_health_pkt(can_number, 1U); } -// ***************************** CAN ***************************** +// CANx_TX IRQ Handler void process_can(uint8_t can_number) { if (can_number != 0xffU) { @@ -111,7 +125,7 @@ void process_can(uint8_t can_number) { // check for empty mailbox CANPacket_t to_send; if ((CAN->TSR & (CAN_TSR_TERR0 | CAN_TSR_ALST0)) != 0) { // last TX failed due to error arbitration lost - can_health[can_number].total_rx_lost_cnt += 1U; + can_health[can_number].total_tx_lost_cnt += 1U; CAN->TSR |= (CAN_TSR_TERR0 | CAN_TSR_ALST0); } if ((CAN->TSR & CAN_TSR_TME0) == CAN_TSR_TME0) { @@ -151,26 +165,20 @@ void process_can(uint8_t can_number) { can_health[can_number].total_tx_checksum_error_cnt += 1U; } - usb_cb_ep3_out_complete(); + refresh_can_tx_slots_available(); } } - update_can_health_pkt(can_number, false); EXIT_CRITICAL(); } } -// CAN receive handlers +// CANx_RX0 IRQ Handler // blink blue when we are receiving CAN messages void can_rx(uint8_t can_number) { CAN_TypeDef *CAN = CANIF_FROM_CAN_NUM(can_number); uint8_t bus_number = BUS_NUM_FROM_CAN_NUM(can_number); - if ((CAN->RF0R & (CAN_RF0R_FOVR0)) != 0) { // RX message lost due to FIFO overrun - can_health[can_number].total_tx_lost_cnt += 1U; - CAN->RF0R &= ~(CAN_RF0R_FOVR0); - } - while ((CAN->RF0R & CAN_RF0R_FMP0) != 0) { can_health[can_number].total_rx_cnt += 1U; @@ -215,7 +223,6 @@ void can_rx(uint8_t can_number) { rx_buffer_overflow += can_push(&can_rx_q, &to_push) ? 0U : 1U; // next - update_can_health_pkt(can_number, false); CAN->RF0R |= CAN_RF0R_RFOM0; } } diff --git a/board/drivers/can_common.h b/board/drivers/can_common.h index 01abc6a073..4ad5b767f5 100644 --- a/board/drivers/can_common.h +++ b/board/drivers/can_common.h @@ -8,6 +8,7 @@ typedef struct { typedef struct { uint8_t bus_lookup; uint8_t can_num_lookup; + int8_t forwarding_bus; uint32_t can_speed; uint32_t can_data_speed; bool canfd_enabled; @@ -147,7 +148,7 @@ void can_clear(can_ring *q) { q->r_ptr = 0; EXIT_CRITICAL(); // handle TX buffer full with zero ECUs awake on the bus - usb_cb_ep3_out_complete(); + refresh_can_tx_slots_available(); } // assign CAN numbering @@ -157,14 +158,15 @@ void can_clear(can_ring *q) { // can number: numeric lookup for MCU CAN interfaces (0 = CAN1, 1 = CAN2, etc); // bus_lookup: Translates from 'can number' to 'bus number'. // can_num_lookup: Translates from 'bus number' to 'can number'. +// forwarding bus: If >= 0, forward all messages from this bus to the specified bus. // Helpers // Panda: Bus 0=CAN1 Bus 1=CAN2 Bus 2=CAN3 bus_config_t bus_config[] = { - { .bus_lookup = 0U, .can_num_lookup = 0U, .can_speed = 5000U, .can_data_speed = 20000U, .canfd_enabled = false, .brs_enabled = false, .canfd_non_iso = false }, - { .bus_lookup = 1U, .can_num_lookup = 1U, .can_speed = 5000U, .can_data_speed = 20000U, .canfd_enabled = false, .brs_enabled = false, .canfd_non_iso = false }, - { .bus_lookup = 2U, .can_num_lookup = 2U, .can_speed = 5000U, .can_data_speed = 20000U, .canfd_enabled = false, .brs_enabled = false, .canfd_non_iso = false }, - { .bus_lookup = 0xFFU, .can_num_lookup = 0xFFU, .can_speed = 333U, .can_data_speed = 333U, .canfd_enabled = false, .brs_enabled = false, .canfd_non_iso = false }, + { .bus_lookup = 0U, .can_num_lookup = 0U, .forwarding_bus = -1, .can_speed = 5000U, .can_data_speed = 20000U, .canfd_enabled = false, .brs_enabled = false, .canfd_non_iso = false }, + { .bus_lookup = 1U, .can_num_lookup = 1U, .forwarding_bus = -1, .can_speed = 5000U, .can_data_speed = 20000U, .canfd_enabled = false, .brs_enabled = false, .canfd_non_iso = false }, + { .bus_lookup = 2U, .can_num_lookup = 2U, .forwarding_bus = -1, .can_speed = 5000U, .can_data_speed = 20000U, .canfd_enabled = false, .brs_enabled = false, .canfd_non_iso = false }, + { .bus_lookup = 0xFFU, .can_num_lookup = 0xFFU, .forwarding_bus = -1, .can_speed = 333U, .can_data_speed = 333U, .canfd_enabled = false, .brs_enabled = false, .canfd_non_iso = false }, }; #define CANIF_FROM_CAN_NUM(num) (cans[num]) @@ -190,6 +192,10 @@ void can_flip_buses(uint8_t bus1, uint8_t bus2){ bus_config[bus2].can_num_lookup = bus1; } +void can_set_forwarding(uint8_t from, uint8_t to) { + bus_config[from].forwarding_bus = to; +} + void ignition_can_hook(CANPacket_t *to_push) { int bus = GET_BUS(to_push); int addr = GET_ADDR(to_push); @@ -199,7 +205,7 @@ void ignition_can_hook(CANPacket_t *to_push) { // 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 = GET_BYTES(to_push, 0, 4) != 0U; ignition_can_cnt = 0U; } diff --git a/board/drivers/clock_source.h b/board/drivers/clock_source.h index d1b5724f16..11b2fa3247 100644 --- a/board/drivers/clock_source.h +++ b/board/drivers/clock_source.h @@ -1,6 +1,10 @@ #define CLOCK_SOURCE_PERIOD_MS 50U #define CLOCK_SOURCE_PULSE_LEN_MS 2U +void clock_source_set_period(uint8_t period) { + register_set(&(TIM1->ARR), ((period*10U) - 1U), 0xFFFFU); +} + void clock_source_init(void) { // Setup timer register_set(&(TIM1->PSC), ((APB2_TIMER_FREQ*100U)-1U), 0xFFFFU); // Tick on 0.1 ms diff --git a/board/drivers/fdcan.h b/board/drivers/fdcan.h index 4dcc3fdf46..ec3cc1255e 100644 --- a/board/drivers/fdcan.h +++ b/board/drivers/fdcan.h @@ -11,6 +11,14 @@ typedef struct { FDCAN_GlobalTypeDef *cans[] = {FDCAN1, FDCAN2, FDCAN3}; +uint8_t can_irq_number[3][2] = { + { FDCAN1_IT0_IRQn, FDCAN1_IT1_IRQn }, + { FDCAN2_IT0_IRQn, FDCAN2_IT1_IRQn }, + { FDCAN3_IT0_IRQn, FDCAN3_IT1_IRQn }, +}; + +#define CAN_ACK_ERROR 3U + bool can_set_speed(uint8_t can_number) { bool ret = true; FDCAN_GlobalTypeDef *CANx = CANIF_FROM_CAN_NUM(can_number); @@ -32,10 +40,7 @@ void can_set_gmlan(uint8_t bus) { print("GMLAN not available on red panda\n"); } -// ***************************** CAN ***************************** -void update_can_health_pkt(uint8_t can_number, bool error_irq) { - ENTER_CRITICAL(); - +void update_can_health_pkt(uint8_t can_number, uint32_t ir_reg) { FDCAN_GlobalTypeDef *CANx = CANIF_FROM_CAN_NUM(can_number); uint32_t psr_reg = CANx->PSR; uint32_t ecr_reg = CANx->ECR; @@ -58,17 +63,30 @@ void update_can_health_pkt(uint8_t can_number, bool error_irq) { can_health[can_number].receive_error_cnt = ((ecr_reg & FDCAN_ECR_REC) >> FDCAN_ECR_REC_Pos); can_health[can_number].transmit_error_cnt = ((ecr_reg & FDCAN_ECR_TEC) >> FDCAN_ECR_TEC_Pos); + can_health[can_number].irq0_call_rate = interrupts[can_irq_number[can_number][0]].call_rate; + can_health[can_number].irq1_call_rate = interrupts[can_irq_number[can_number][1]].call_rate; + - if (error_irq) { + if (ir_reg != 0U) { + // Clear error interrupts + CANx->IR |= (FDCAN_IR_PED | FDCAN_IR_PEA | FDCAN_IR_EP | FDCAN_IR_BO | FDCAN_IR_RF0L); can_health[can_number].total_error_cnt += 1U; - if ((CANx->IR & (FDCAN_IR_TEFL)) != 0) { - can_health[can_number].total_tx_lost_cnt += 1U; + // Check for RX FIFO overflow + if ((ir_reg & (FDCAN_IR_RF0L)) != 0) { + can_health[can_number].total_rx_lost_cnt += 1U; + } + // While multiplexing between buses 1 and 3 we are getting ACK errors that overwhelm CAN core + // By resseting CAN core when no ACK is detected for a while(until TEC counter reaches 127) it can recover faster + if (((can_health[can_number].last_error == CAN_ACK_ERROR) || (can_health[can_number].last_data_error == CAN_ACK_ERROR)) && (can_health[can_number].transmit_error_cnt > 127U)) { + can_health[can_number].can_core_reset_cnt += 1U; + can_health[can_number].total_tx_lost_cnt += (FDCAN_TX_FIFO_EL_CNT - (CANx->TXFQS & FDCAN_TXFQS_TFFL)); // TX FIFO msgs will be lost after reset + llcan_clear_send(CANx); } - llcan_clear_send(CANx); } - EXIT_CRITICAL(); } +// ***************************** CAN ***************************** +// FDCANx_IT1 IRQ Handler (TX) void process_can(uint8_t can_number) { if (can_number != 0xffU) { ENTER_CRITICAL(); @@ -119,103 +137,105 @@ void process_can(uint8_t can_number) { can_health[can_number].total_tx_checksum_error_cnt += 1U; } - usb_cb_ep3_out_complete(); + refresh_can_tx_slots_available(); } } - - update_can_health_pkt(can_number, false); EXIT_CRITICAL(); } } -// CAN receive handlers +// FDCANx_IT0 IRQ Handler (RX and errors) // blink blue when we are receiving CAN messages void can_rx(uint8_t can_number) { FDCAN_GlobalTypeDef *CANx = CANIF_FROM_CAN_NUM(can_number); uint8_t bus_number = BUS_NUM_FROM_CAN_NUM(can_number); - // Rx FIFO 0 new message - if((CANx->IR & FDCAN_IR_RF0N) != 0) { - CANx->IR |= FDCAN_IR_RF0N; - while((CANx->RXF0S & FDCAN_RXF0S_F0FL) != 0) { - can_health[can_number].total_rx_cnt += 1U; + uint32_t ir_reg = CANx->IR; - // can is live - pending_can_live = 1; + // Clear all new messages from Rx FIFO 0 + CANx->IR |= FDCAN_IR_RF0N; + while((CANx->RXF0S & FDCAN_RXF0S_F0FL) != 0) { + can_health[can_number].total_rx_cnt += 1U; - // get the index of the next RX FIFO element (0 to FDCAN_RX_FIFO_0_EL_CNT - 1) - uint8_t rx_fifo_idx = (uint8_t)((CANx->RXF0S >> FDCAN_RXF0S_F0GI_Pos) & 0x3F); + // can is live + pending_can_live = 1; - // Recommended to offset get index by at least +1 if RX FIFO is in overwrite mode and full (datasheet) - if((CANx->RXF0S & FDCAN_RXF0S_F0F) == FDCAN_RXF0S_F0F) { - rx_fifo_idx = ((rx_fifo_idx + 1U) >= FDCAN_RX_FIFO_0_EL_CNT) ? 0U : (rx_fifo_idx + 1U); - can_health[can_number].total_rx_lost_cnt += 1U; // At least one message was lost - } + // get the index of the next RX FIFO element (0 to FDCAN_RX_FIFO_0_EL_CNT - 1) + uint8_t rx_fifo_idx = (uint8_t)((CANx->RXF0S >> FDCAN_RXF0S_F0GI_Pos) & 0x3F); - uint32_t RxFIFO0SA = FDCAN_START_ADDRESS + (can_number * FDCAN_OFFSET); - CANPacket_t to_push; - canfd_fifo *fifo; + // Recommended to offset get index by at least +1 if RX FIFO is in overwrite mode and full (datasheet) + if((CANx->RXF0S & FDCAN_RXF0S_F0F) == FDCAN_RXF0S_F0F) { + rx_fifo_idx = ((rx_fifo_idx + 1U) >= FDCAN_RX_FIFO_0_EL_CNT) ? 0U : (rx_fifo_idx + 1U); + can_health[can_number].total_rx_lost_cnt += 1U; // At least one message was lost + } - // getting address - fifo = (canfd_fifo *)(RxFIFO0SA + (rx_fifo_idx * FDCAN_RX_FIFO_0_EL_SIZE)); + uint32_t RxFIFO0SA = FDCAN_START_ADDRESS + (can_number * FDCAN_OFFSET); + CANPacket_t to_push; + canfd_fifo *fifo; - to_push.returned = 0U; - to_push.rejected = 0U; - to_push.extended = (fifo->header[0] >> 30) & 0x1U; - to_push.addr = ((to_push.extended != 0U) ? (fifo->header[0] & 0x1FFFFFFFU) : ((fifo->header[0] >> 18) & 0x7FFU)); - to_push.bus = bus_number; - to_push.data_len_code = ((fifo->header[1] >> 16) & 0xFU); + // getting address + fifo = (canfd_fifo *)(RxFIFO0SA + (rx_fifo_idx * FDCAN_RX_FIFO_0_EL_SIZE)); - bool canfd_frame = ((fifo->header[1] >> 21) & 0x1U); - bool brs_frame = ((fifo->header[1] >> 20) & 0x1U); + to_push.returned = 0U; + to_push.rejected = 0U; + to_push.extended = (fifo->header[0] >> 30) & 0x1U; + to_push.addr = ((to_push.extended != 0U) ? (fifo->header[0] & 0x1FFFFFFFU) : ((fifo->header[0] >> 18) & 0x7FFU)); + to_push.bus = bus_number; + to_push.data_len_code = ((fifo->header[1] >> 16) & 0xFU); - uint8_t data_len_w = (dlc_to_len[to_push.data_len_code] / 4U); - data_len_w += ((dlc_to_len[to_push.data_len_code] % 4U) > 0U) ? 1U : 0U; - 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.addr); - if (bus_fwd_num != -1) { - CANPacket_t to_send; - - to_send.returned = 0U; - to_send.rejected = 0U; - to_send.extended = to_push.extended; - to_send.addr = to_push.addr; - 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; - } + bool canfd_frame = ((fifo->header[1] >> 21) & 0x1U); + bool brs_frame = ((fifo->header[1] >> 20) & 0x1U); - safety_rx_invalid += safety_rx_hook(&to_push) ? 0U : 1U; - ignition_can_hook(&to_push); + uint8_t data_len_w = (dlc_to_len[to_push.data_len_code] / 4U); + data_len_w += ((dlc_to_len[to_push.data_len_code] % 4U) > 0U) ? 1U : 0U; + 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); - current_board->set_led(LED_BLUE, true); - rx_buffer_overflow += can_push(&can_rx_q, &to_push) ? 0U : 1U; + // forwarding (panda only) + int bus_fwd_num = safety_fwd_hook(bus_number, to_push.addr); + if (bus_fwd_num < 0) { + bus_fwd_num = bus_config[can_number].forwarding_bus; + } + if (bus_fwd_num != -1) { + CANPacket_t to_send; - // Enable CAN FD and BRS if CAN FD message was received - if (!(bus_config[can_number].canfd_enabled) && (canfd_frame)) { - bus_config[can_number].canfd_enabled = true; - } - if (!(bus_config[can_number].brs_enabled) && (brs_frame)) { - bus_config[can_number].brs_enabled = true; - } + to_send.returned = 0U; + to_send.rejected = 0U; + to_send.extended = to_push.extended; + to_send.addr = to_push.addr; + 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; + } - // update read index - CANx->RXF0A = rx_fifo_idx; + safety_rx_invalid += safety_rx_hook(&to_push) ? 0U : 1U; + ignition_can_hook(&to_push); + + current_board->set_led(LED_BLUE, true); + rx_buffer_overflow += can_push(&can_rx_q, &to_push) ? 0U : 1U; + + // Enable CAN FD and BRS if CAN FD message was received + if (!(bus_config[can_number].canfd_enabled) && (canfd_frame)) { + bus_config[can_number].canfd_enabled = true; + } + if (!(bus_config[can_number].brs_enabled) && (brs_frame)) { + bus_config[can_number].brs_enabled = true; } + // update read index + CANx->RXF0A = rx_fifo_idx; } + // Error handling - bool error_irq = ((CANx->IR & (FDCAN_IR_PED | FDCAN_IR_PEA | FDCAN_IR_EW | FDCAN_IR_EP | FDCAN_IR_ELO | FDCAN_IR_BO | FDCAN_IR_TEFL | FDCAN_IR_RF0L)) != 0); - update_can_health_pkt(can_number, error_irq); + if ((ir_reg & (FDCAN_IR_PED | FDCAN_IR_PEA | FDCAN_IR_EP | FDCAN_IR_BO | FDCAN_IR_RF0L)) != 0) { + update_can_health_pkt(can_number, ir_reg); + } } void FDCAN1_IT0_IRQ_Handler(void) { can_rx(0); } diff --git a/board/drivers/gmlan_alt.h b/board/drivers/gmlan_alt.h index 266a683e6e..53f46c2e02 100644 --- a/board/drivers/gmlan_alt.h +++ b/board/drivers/gmlan_alt.h @@ -272,6 +272,7 @@ bool bitbang_gmlan(CANPacket_t *to_bang) { gmlan_send_ok = true; gmlan_alt_mode = BITBANG; +#ifndef STM32H7 if (gmlan_sendmax == -1) { int len = get_bit_message(pkt_stuffed, to_bang); gmlan_fail_count = 0; @@ -285,5 +286,8 @@ bool bitbang_gmlan(CANPacket_t *to_bang) { // 33kbps setup_timer(); } +#else + UNUSED(to_bang); +#endif return gmlan_send_ok; } diff --git a/board/drivers/gpio.h b/board/drivers/gpio.h index 77af87658c..fe3194f3ba 100644 --- a/board/drivers/gpio.h +++ b/board/drivers/gpio.h @@ -10,6 +10,11 @@ #define OUTPUT_TYPE_PUSH_PULL 0U #define OUTPUT_TYPE_OPEN_DRAIN 1U +typedef struct { + GPIO_TypeDef *bank; + uint8_t pin; +} gpio_t; + void set_gpio_mode(GPIO_TypeDef *GPIO, unsigned int pin, unsigned int mode) { ENTER_CRITICAL(); uint32_t tmp = GPIO->MODER; @@ -63,6 +68,18 @@ int get_gpio_input(GPIO_TypeDef *GPIO, unsigned int pin) { return (GPIO->IDR & (1U << pin)) == (1U << pin); } +void gpio_set_all_output(const gpio_t *pins, uint8_t num_pins, bool enabled) { + for (uint8_t i = 0; i < num_pins; i++) { + set_gpio_output(pins[i].bank, pins[i].pin, enabled); + } +} + +void gpio_set_bitmask(const gpio_t *pins, uint8_t num_pins, uint32_t bitmask) { + for (uint8_t i = 0; i < num_pins; i++) { + set_gpio_output(pins[i].bank, pins[i].pin, (bitmask >> i) & 1U); + } +} + // Detection with internal pullup #define PULL_EFFECTIVE_DELAY 4096 bool detect_with_pull(GPIO_TypeDef *GPIO, int pin, int mode) { diff --git a/board/drivers/harness.h b/board/drivers/harness.h index c2b5ac95f8..36f73f2573 100644 --- a/board/drivers/harness.h +++ b/board/drivers/harness.h @@ -1,8 +1,16 @@ -uint8_t car_harness_status = 0U; #define HARNESS_STATUS_NC 0U #define HARNESS_STATUS_NORMAL 1U #define HARNESS_STATUS_FLIPPED 2U +struct harness_t { + uint8_t status; + uint16_t sbu1_voltage_mV; + uint16_t sbu2_voltage_mV; + bool relay_driven; + bool sbu_adc_lock; +}; +struct harness_t harness; + struct harness_configuration { const bool has_harness; GPIO_TypeDef *GPIO_SBU1; @@ -17,26 +25,43 @@ struct harness_configuration { uint8_t adc_channel_SBU2; }; -// this function will be the API for tici -void set_intercept_relay(bool intercept) { - if (car_harness_status != HARNESS_STATUS_NC) { - if (intercept) { - print("switching harness to intercept (relay on)\n"); - } else { - print("switching harness to passthrough (relay off)\n"); +// The ignition relay is only used for testing purposes +void set_intercept_relay(bool intercept, bool ignition_relay) { + if (current_board->harness_config->has_harness) { + bool drive_relay = intercept; + if (harness.status == HARNESS_STATUS_NC) { + // no harness, no relay to drive + drive_relay = false; } - if(car_harness_status == HARNESS_STATUS_NORMAL){ - set_gpio_output(current_board->harness_config->GPIO_relay_SBU2, current_board->harness_config->pin_relay_SBU2, !intercept); + if (drive_relay || ignition_relay) { + harness.relay_driven = true; + } + + // wait until we're not reading the analog voltages anymore + while (harness.sbu_adc_lock == true) {} + + if (harness.status == HARNESS_STATUS_NORMAL) { + set_gpio_output(current_board->harness_config->GPIO_relay_SBU1, current_board->harness_config->pin_relay_SBU1, !ignition_relay); + set_gpio_output(current_board->harness_config->GPIO_relay_SBU2, current_board->harness_config->pin_relay_SBU2, !drive_relay); } else { - set_gpio_output(current_board->harness_config->GPIO_relay_SBU1, current_board->harness_config->pin_relay_SBU1, !intercept); + set_gpio_output(current_board->harness_config->GPIO_relay_SBU1, current_board->harness_config->pin_relay_SBU1, !drive_relay); + set_gpio_output(current_board->harness_config->GPIO_relay_SBU2, current_board->harness_config->pin_relay_SBU2, !ignition_relay); + } + + if (!(drive_relay || ignition_relay)) { + harness.relay_driven = false; } } } bool harness_check_ignition(void) { bool ret = false; - switch(car_harness_status){ + + // wait until we're not reading the analog voltages anymore + while (harness.sbu_adc_lock == true) {} + + switch(harness.status){ case HARNESS_STATUS_NORMAL: ret = !get_gpio_input(current_board->harness_config->GPIO_SBU1, current_board->harness_config->pin_SBU1); break; @@ -50,27 +75,46 @@ bool harness_check_ignition(void) { } uint8_t harness_detect_orientation(void) { - uint8_t ret = HARNESS_STATUS_NC; + uint8_t ret = harness.status; #ifndef BOOTSTUB - uint32_t sbu1_voltage = adc_get(current_board->harness_config->adc_channel_SBU1); - uint32_t sbu2_voltage = adc_get(current_board->harness_config->adc_channel_SBU2); - - // Detect connection and orientation - if((sbu1_voltage < HARNESS_CONNECTED_THRESHOLD) || (sbu2_voltage < HARNESS_CONNECTED_THRESHOLD)){ - if (sbu1_voltage < sbu2_voltage) { - // orientation flipped (PANDA_SBU1->HARNESS_SBU1(relay), PANDA_SBU2->HARNESS_SBU2(ign)) - ret = HARNESS_STATUS_FLIPPED; + // We can't detect orientation if the relay is being driven + if (!harness.relay_driven && current_board->harness_config->has_harness) { + harness.sbu_adc_lock = true; + set_gpio_mode(current_board->harness_config->GPIO_SBU1, current_board->harness_config->pin_SBU1, MODE_ANALOG); + set_gpio_mode(current_board->harness_config->GPIO_SBU2, current_board->harness_config->pin_SBU2, MODE_ANALOG); + + harness.sbu1_voltage_mV = adc_get_mV(current_board->harness_config->adc_channel_SBU1); + harness.sbu2_voltage_mV = adc_get_mV(current_board->harness_config->adc_channel_SBU2); + uint16_t detection_threshold = current_board->avdd_mV / 2U; + + // Detect connection and orientation + if((harness.sbu1_voltage_mV < detection_threshold) || (harness.sbu2_voltage_mV < detection_threshold)){ + if (harness.sbu1_voltage_mV < harness.sbu2_voltage_mV) { + // orientation flipped (PANDA_SBU1->HARNESS_SBU1(relay), PANDA_SBU2->HARNESS_SBU2(ign)) + ret = HARNESS_STATUS_FLIPPED; + } else { + // orientation normal (PANDA_SBU2->HARNESS_SBU1(relay), PANDA_SBU1->HARNESS_SBU2(ign)) + ret = HARNESS_STATUS_NORMAL; + } } else { - // orientation normal (PANDA_SBU2->HARNESS_SBU1(relay), PANDA_SBU1->HARNESS_SBU2(ign)) - ret = HARNESS_STATUS_NORMAL; + ret = HARNESS_STATUS_NC; } + + // Pins are not 5V tolerant in ADC mode + set_gpio_mode(current_board->harness_config->GPIO_SBU1, current_board->harness_config->pin_SBU1, MODE_INPUT); + set_gpio_mode(current_board->harness_config->GPIO_SBU2, current_board->harness_config->pin_SBU2, MODE_INPUT); + harness.sbu_adc_lock = false; } #endif return ret; } +void harness_tick(void) { + harness.status = harness_detect_orientation(); +} + void harness_init(void) { // delay such that the connection is fully made before trying orientation detection current_board->set_led(LED_BLUE, true); @@ -78,18 +122,13 @@ void harness_init(void) { current_board->set_led(LED_BLUE, false); // try to detect orientation - uint8_t ret = harness_detect_orientation(); - if (ret != HARNESS_STATUS_NC) { - 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! - set_gpio_mode(current_board->harness_config->GPIO_SBU1, current_board->harness_config->pin_SBU1, MODE_INPUT); - set_gpio_mode(current_board->harness_config->GPIO_SBU2, current_board->harness_config->pin_SBU2, MODE_INPUT); - - // keep busses connected by default - set_intercept_relay(false); + harness.status = harness_detect_orientation(); + if (harness.status != HARNESS_STATUS_NC) { + print("detected car harness with orientation "); puth2(harness.status); print("\n"); } else { print("failed to detect car harness!\n"); } + + // keep buses connected by default + set_intercept_relay(false, false); } diff --git a/board/drivers/interrupts.h b/board/drivers/interrupts.h index ad1ec5c020..9cb46d4b29 100644 --- a/board/drivers/interrupts.h +++ b/board/drivers/interrupts.h @@ -2,6 +2,7 @@ typedef struct interrupt { IRQn_Type irq_type; void (*handler)(void); uint32_t call_counter; + uint32_t call_rate; uint32_t max_call_rate; // Call rate is defined as the amount of calls each second uint32_t call_rate_fault; } interrupt; @@ -17,11 +18,12 @@ void unused_interrupt_handler(void) { interrupt interrupts[NUM_INTERRUPTS]; -#define REGISTER_INTERRUPT(irq_num, func_ptr, call_rate, rate_fault) \ +#define REGISTER_INTERRUPT(irq_num, func_ptr, call_rate_max, rate_fault) \ interrupts[irq_num].irq_type = (irq_num); \ interrupts[irq_num].handler = (func_ptr); \ interrupts[irq_num].call_counter = 0U; \ - interrupts[irq_num].max_call_rate = (call_rate); \ + interrupts[irq_num].call_rate = 0U; \ + interrupts[irq_num].max_call_rate = (call_rate_max); \ interrupts[irq_num].call_rate_fault = (rate_fault); bool check_interrupt_rate = false; @@ -46,8 +48,7 @@ void handle_interrupt(IRQn_Type irq_type){ interrupts[irq_type].handler(); // Check that the interrupts don't fire too often - if(check_interrupt_rate && (interrupts[irq_type].call_counter > interrupts[irq_type].max_call_rate)){ - print("Interrupt 0x"); puth(irq_type); print(" fired too often (0x"); puth(interrupts[irq_type].call_counter); print("/s)!\n"); + if (check_interrupt_rate && (interrupts[irq_type].call_counter > interrupts[irq_type].max_call_rate)) { fault_occurred(interrupts[irq_type].call_rate_fault); } @@ -64,8 +65,14 @@ void handle_interrupt(IRQn_Type irq_type){ // Every second void interrupt_timer_handler(void) { if (INTERRUPT_TIMER->SR != 0) { - // Reset interrupt counters - for(uint16_t i=0U; i interrupts[i].max_call_rate)) { + print("Interrupt 0x"); puth(i); print(" fired too often (0x"); puth(interrupts[i].call_counter); print("/s)!\n"); + } + + // Reset interrupt counters + interrupts[i].call_rate = interrupts[i].call_counter; interrupts[i].call_counter = 0U; } diff --git a/board/drivers/logging.h b/board/drivers/logging.h new file mode 100644 index 0000000000..28e4275230 --- /dev/null +++ b/board/drivers/logging.h @@ -0,0 +1,193 @@ + +#include "logging_definitions.h" + +#define BANK_SIZE LOGGING_FLASH_SECTOR_SIZE +#define BANK_LOG_CAPACITY (BANK_SIZE / sizeof(log_t)) +#define TOTAL_LOG_CAPACITY (BANK_LOG_CAPACITY * 2U) + +#define LOGGING_MAX_LOGS_PER_MINUTE 10U + +struct logging_state_t { + uint16_t read_index; + uint16_t write_index; + uint16_t last_id; + + uint8_t rate_limit_counter; + uint8_t rate_limit_log_count; +}; +struct logging_state_t log_state = { 0 }; +log_t *log_arr = (log_t *) LOGGING_FLASH_BASE_A; + +uint16_t logging_next_id(uint16_t id) { + return (id + 1U) % 0xFFFEU; +} + +uint16_t logging_next_index(uint16_t index) { + return (index + 1U) % TOTAL_LOG_CAPACITY; +} + +void logging_erase_bank(uint8_t flash_sector) { + print("erasing sector "); puth(flash_sector); print("\n"); + flash_unlock(); + if (!flash_erase_sector(flash_sector)) { + print("failed to erase sector "); puth(flash_sector); print("\n"); + } + flash_lock(); +} + +void logging_erase(void) { + logging_erase_bank(LOGGING_FLASH_SECTOR_A); + logging_erase_bank(LOGGING_FLASH_SECTOR_B); + log_state.read_index = 0U; + log_state.write_index = 0U; +} + +void logging_find_read_index(uint16_t last_id) { + // Figure out the read index by the last empty slot + log_state.read_index = BANK_LOG_CAPACITY; + for (uint16_t i = 0U; i < TOTAL_LOG_CAPACITY; i++) { + if (log_arr[i].id == last_id) { + log_state.read_index = logging_next_index(i); + } + } +} + +void logging_init_read_index(void) { + return logging_find_read_index(0xFFFFU); +} + +void logging_init(void) { + COMPILE_TIME_ASSERT(sizeof(log_t) == 64U); + COMPILE_TIME_ASSERT((LOGGING_FLASH_BASE_A + BANK_SIZE) == LOGGING_FLASH_BASE_B); + + // Make sure all empty-ID logs are fully empty + log_t empty_log; + (void) memset(&empty_log, 0xFF, sizeof(log_t)); + + for (uint16_t i = 0U; i < TOTAL_LOG_CAPACITY; i++) { + if ((log_arr[i].id == 0xFFFFU) && (memcmp(&log_arr[i], &empty_log, sizeof(log_t)) != 0)) { + logging_erase(); + break; + } + } + + logging_init_read_index(); + + // At initialization, the read index should always be at the beginning of a bank + // If not, clean slate + if ((log_state.read_index != 0U) && (log_state.read_index != BANK_LOG_CAPACITY)) { + logging_erase(); + } + + // Figure out the write index + log_state.write_index = log_state.read_index; + log_state.last_id = log_arr[log_state.write_index].id - 1U; + for (uint16_t i = 0U; i < TOTAL_LOG_CAPACITY; i++) { + bool done = false; + if (log_arr[log_state.write_index].id == 0xFFFFU) { + // Found the first empty slot after the read pointer + done = true; + } else if (log_arr[log_state.write_index].id != logging_next_id(log_state.last_id)) { + // Discontinuity in the index, shouldn't happen! + logging_erase(); + done = true; + } else { + log_state.last_id = log_arr[log_state.write_index].id; + log_state.write_index = logging_next_index(log_state.write_index); + } + + if (done) { + break; + } + } + + // Reset rate limit + log_state.rate_limit_counter = 0U; + log_state.rate_limit_log_count = 0U; +} + +// Call at 1Hz +void logging_tick(void) { + flush_write_buffer(); + + log_state.rate_limit_counter++; + if (log_state.rate_limit_counter >= 60U) { + log_state.rate_limit_counter = 0U; + log_state.rate_limit_log_count = 0U; + } +} + +void log(const char* msg){ + if (log_state.rate_limit_log_count < LOGGING_MAX_LOGS_PER_MINUTE) { + ENTER_CRITICAL(); + log_t new_log = {0}; + new_log.id = logging_next_id(log_state.last_id); + log_state.last_id = new_log.id; + new_log.uptime = uptime_cnt; + if (current_board->has_rtc_battery) { + new_log.timestamp = rtc_get_time(); + } + + uint8_t i = 0U; + for (const char *in = msg; *in; in++) { + new_log.msg[i] = *in; + i++; + if (i >= sizeof(new_log.msg)) { + print("log message too long\n"); + break; + } + } + + // If we are at the beginning of a bank, erase it first and move the read pointer if needed + switch (log_state.write_index) { + case ((2U * BANK_LOG_CAPACITY) - 1U): + logging_erase_bank(LOGGING_FLASH_SECTOR_A); + if ((log_state.read_index < BANK_LOG_CAPACITY)) { + log_state.read_index = BANK_LOG_CAPACITY; + } + break; + case (BANK_LOG_CAPACITY - 1U): + // beginning to write in bank B + logging_erase_bank(LOGGING_FLASH_SECTOR_B); + if ((log_state.read_index > BANK_LOG_CAPACITY)) { + log_state.read_index = 0U; + } + break; + default: + break; + } + + // Write! + void *addr = &log_arr[log_state.write_index]; + uint32_t data[sizeof(log_t) / sizeof(uint32_t)]; + (void) memcpy(data, &new_log, sizeof(log_t)); + + flash_unlock(); + for (uint8_t j = 0U; j < sizeof(log_t) / sizeof(uint32_t); j++) { + flash_write_word(&((uint32_t *) addr)[j], data[j]); + } + flash_lock(); + + // Update the write index + log_state.write_index = logging_next_index(log_state.write_index); + EXIT_CRITICAL(); + + log_state.rate_limit_log_count++; + } else { + fault_occurred(FAULT_LOGGING_RATE_LIMIT); + } +} + +uint8_t logging_read(uint8_t *buffer) { + uint8_t ret = 0U; + if ((log_arr[log_state.read_index].id != 0xFFFFU) && (log_state.read_index != log_state.write_index)) { + // Read the log + (void) memcpy(buffer, &log_arr[log_state.read_index], sizeof(log_t)); + + // Update the read index + log_state.read_index = logging_next_index(log_state.read_index); + + ret = sizeof(log_t); + } + return ret; +} diff --git a/board/drivers/logging_definitions.h b/board/drivers/logging_definitions.h new file mode 100644 index 0000000000..9c1bc02e1c --- /dev/null +++ b/board/drivers/logging_definitions.h @@ -0,0 +1,9 @@ + +// Flash is writable in 32-byte lines, this struct is designed to fit in two lines. +// This also matches the USB transfer size. +typedef struct __attribute__((packed)) log_t { + uint16_t id; + timestamp_t timestamp; + uint32_t uptime; + char msg[50]; +} log_t; diff --git a/board/drivers/rtc.h b/board/drivers/rtc.h index df121e3e89..231d88f2bf 100644 --- a/board/drivers/rtc.h +++ b/board/drivers/rtc.h @@ -1,14 +1,7 @@ -#define YEAR_OFFSET 2000U -typedef struct __attribute__((packed)) timestamp_t { - uint16_t year; - uint8_t month; - uint8_t day; - uint8_t weekday; - uint8_t hour; - uint8_t minute; - uint8_t second; -} timestamp_t; +#include "rtc_definitions.h" + +#define YEAR_OFFSET 2000U uint8_t to_bcd(uint16_t value){ return (((value / 10U) & 0x0FU) << 4U) | ((value % 10U) & 0x0FU); @@ -50,14 +43,6 @@ void rtc_set_time(timestamp_t time){ timestamp_t rtc_get_time(void){ timestamp_t result; - // Init with zero values in case there is no RTC running - result.year = 0U; - result.month = 0U; - result.day = 0U; - result.weekday = 0U; - result.hour = 0U; - result.minute = 0U; - result.second = 0U; // Wait until the register sync flag is set while((RTC->ISR & RTC_ISR_RSF) == 0){} diff --git a/board/drivers/rtc_definitions.h b/board/drivers/rtc_definitions.h new file mode 100644 index 0000000000..d308eb7467 --- /dev/null +++ b/board/drivers/rtc_definitions.h @@ -0,0 +1,9 @@ +typedef struct __attribute__((packed)) timestamp_t { + uint16_t year; + uint8_t month; + uint8_t day; + uint8_t weekday; + uint8_t hour; + uint8_t minute; + uint8_t second; +} timestamp_t; diff --git a/board/drivers/spi.h b/board/drivers/spi.h index 0320212241..af9dd8a574 100644 --- a/board/drivers/spi.h +++ b/board/drivers/spi.h @@ -1,12 +1,19 @@ #pragma once -#define SPI_BUF_SIZE 1024U +#include "crc.h" + #define SPI_TIMEOUT_US 10000U +// got max rate from hitting a non-existent endpoint +// in a tight loop, plus some buffer +#define SPI_IRQ_RATE 16000U + #ifdef STM32H7 +#define SPI_BUF_SIZE 2048U __attribute__((section(".ram_d1"))) uint8_t spi_buf_rx[SPI_BUF_SIZE]; -__attribute__((section(".ram_d1"))) uint8_t spi_buf_tx[SPI_BUF_SIZE]; +__attribute__((section(".ram_d2"))) uint8_t spi_buf_tx[SPI_BUF_SIZE]; #else +#define SPI_BUF_SIZE 1024U uint8_t spi_buf_rx[SPI_BUF_SIZE]; uint8_t spi_buf_tx[SPI_BUF_SIZE]; #endif @@ -27,12 +34,13 @@ enum { SPI_STATE_DATA_TX }; +bool spi_tx_dma_done = false; uint8_t spi_state = SPI_STATE_HEADER; uint8_t spi_endpoint; uint16_t spi_data_len_mosi; uint16_t spi_data_len_miso; uint16_t spi_checksum_error_count = 0; - +bool spi_can_tx_ready = false; #define SPI_HEADER_SIZE 7U @@ -41,11 +49,57 @@ 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); +void can_tx_comms_resume_spi(void) { + spi_can_tx_ready = true; +} + +uint16_t spi_version_packet(uint8_t *out) { + // this protocol version request is a stable portion of + // the panda's SPI protocol. its contents match that of the + // panda USB descriptors and are sufficent to list/enumerate + // a panda, determine panda type, and bootstub status. + + // the response is: + // VERSION + 2 byte data length + data + CRC8 + + // echo "VERSION" + (void)memcpy(out, "VERSION", 7); + + // write response + uint16_t data_len = 0; + uint16_t data_pos = 7U + 2U; + + // write serial + #ifdef UID_BASE + (void)memcpy(&out[data_pos], ((uint8_t *)UID_BASE), 12); + data_len += 12U; + #endif + + // HW type + out[data_pos + data_len] = hw_type; + data_len += 1U; + + // bootstub + out[data_pos + data_len] = USB_PID & 0xFFU; + data_len += 1U; + + // SPI protocol version + out[data_pos + data_len] = 0x2; + data_len += 1U; + // data length + out[7] = data_len & 0xFFU; + out[8] = (data_len >> 8) & 0xFFU; + + // CRC8 + uint16_t resp_len = data_pos + data_len; + out[resp_len] = crc_checksum(out, resp_len, 0xD5U); + resp_len += 1U; + + return resp_len; +} + +void spi_init(void) { // platform init llspi_init(); @@ -63,8 +117,9 @@ bool check_checksum(uint8_t *data, uint16_t len) { return checksum == 0U; } -void spi_handle_rx(void) { - uint8_t next_rx_state = SPI_STATE_HEADER; +void spi_rx_done(void) { + uint16_t response_len = 0U; + uint8_t next_rx_state = SPI_STATE_HEADER_NACK; bool checksum_valid = false; // parse header @@ -72,23 +127,26 @@ void spi_handle_rx(void) { 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 (memcmp(spi_buf_rx, "VERSION", 7) == 0) { + response_len = spi_version_packet(spi_buf_tx); + next_rx_state = SPI_STATE_HEADER_NACK;; + } else if (spi_state == SPI_STATE_HEADER) { checksum_valid = check_checksum(spi_buf_rx, SPI_HEADER_SIZE); if ((spi_buf_rx[0] == SPI_SYNC_BYTE) && checksum_valid) { // response: ACK and start receiving data portion spi_buf_tx[0] = SPI_HACK; next_rx_state = SPI_STATE_HEADER_ACK; + response_len = 1U; } 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; + response_len = 1U; } - 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; + bool response_ack = false; checksum_valid = check_checksum(&(spi_buf_rx[SPI_HEADER_SIZE]), spi_data_len_mosi + 1U); if (checksum_valid) { if (spi_endpoint == 0U) { @@ -96,24 +154,30 @@ void spi_handle_rx(void) { 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; + response_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; + response_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; + response_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; + if (spi_can_tx_ready) { + spi_can_tx_ready = false; + comms_can_write(&spi_buf_rx[SPI_HEADER_SIZE], spi_data_len_mosi); + response_ack = true; + } else { + response_ack = false; + print("SPI: CAN NACK\n"); + } } else { print("SPI: did expect data for can_write\n"); } @@ -122,42 +186,56 @@ void spi_handle_rx(void) { } } else { // Checksum was incorrect - reponse_ack = false; - print("- incorrect data checksum\n"); + response_ack = false; + print("- incorrect data checksum "); + puth4(spi_data_len_mosi); + print("\n"); + hexdump(spi_buf_rx, SPI_HEADER_SIZE); + hexdump(&(spi_buf_rx[SPI_HEADER_SIZE]), MIN(spi_data_len_mosi, 64)); + print("\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; + if (!response_ack) { + spi_buf_tx[0] = SPI_NACK; + next_rx_state = SPI_STATE_HEADER_NACK; + response_len = 1U; + } else { + // Setup response header + spi_buf_tx[0] = SPI_DACK; + 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; + response_len += 4U; - // Add checksum - uint8_t checksum = SPI_CHECKSUM_START; - for(uint16_t i = 0U; i < (response_len + 3U); i++) { - checksum ^= spi_buf_tx[i]; + next_rx_state = SPI_STATE_DATA_TX; } - 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"); } + // send out response + if (response_len == 0U) { + print("SPI: no response\n"); + spi_buf_tx[0] = SPI_NACK; + spi_state = SPI_STATE_HEADER_NACK; + response_len = 1U; + } + llspi_miso_dma(spi_buf_tx, response_len); + spi_state = next_rx_state; if (!checksum_valid && (spi_checksum_error_count < __UINT16_MAX__)) { spi_checksum_error_count += 1U; } } -void spi_handle_tx(bool timed_out) { - if (timed_out) { - print("SPI: TX timeout\n"); - } - - if ((spi_state == SPI_STATE_HEADER_NACK) || timed_out) { +void spi_tx_done(bool reset) { + if ((spi_state == SPI_STATE_HEADER_NACK) || reset) { // Reset state spi_state = SPI_STATE_HEADER; llspi_mosi_dma(spi_buf_rx, SPI_HEADER_SIZE); diff --git a/board/drivers/uart.h b/board/drivers/uart.h index b0fc2ed1be..cde8137402 100644 --- a/board/drivers/uart.h +++ b/board/drivers/uart.h @@ -1,8 +1,7 @@ -// IRQs: USART1, USART2, USART3, UART5 +// IRQs: USART2, USART3, UART5 // ***************************** Definitions ***************************** #define FIFO_SIZE_INT 0x400U -#define FIFO_SIZE_DMA 0x1000U typedef struct uart_ring { volatile uint16_t w_ptr_tx; @@ -15,10 +14,10 @@ typedef struct uart_ring { uint32_t rx_fifo_size; USART_TypeDef *uart; void (*callback)(struct uart_ring*); - bool dma_rx; + bool overwrite; } uart_ring; -#define UART_BUFFER(x, size_rx, size_tx, uart_ptr, callback_ptr, rx_dma) \ +#define UART_BUFFER(x, size_rx, size_tx, uart_ptr, callback_ptr, overwrite_mode) \ uint8_t elems_rx_##x[size_rx]; \ uint8_t elems_tx_##x[size_tx]; \ uart_ring uart_ring_##x = { \ @@ -32,7 +31,7 @@ typedef struct uart_ring { .rx_fifo_size = (size_rx), \ .uart = (uart_ptr), \ .callback = (callback_ptr), \ - .dma_rx = (rx_dma) \ + .overwrite = (overwrite_mode) \ }; // ***************************** Function prototypes ***************************** @@ -42,23 +41,20 @@ void uart_send_break(uart_ring *u); // ******************************** UART buffers ******************************** -// gps = USART1 -UART_BUFFER(gps, FIFO_SIZE_DMA, FIFO_SIZE_INT, USART1, NULL, true) - // lin1, K-LINE = UART5 // lin2, L-LINE = USART3 UART_BUFFER(lin1, FIFO_SIZE_INT, FIFO_SIZE_INT, UART5, NULL, false) 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) +UART_BUFFER(debug, FIFO_SIZE_INT, FIFO_SIZE_INT, USART2, debug_ring_callback, true) // SOM debug = UART7 #ifdef STM32H7 - UART_BUFFER(som_debug, FIFO_SIZE_INT, FIFO_SIZE_INT, UART7, NULL, false) + UART_BUFFER(som_debug, FIFO_SIZE_INT, FIFO_SIZE_INT, UART7, NULL, true) #else // UART7 is not available on F4 - UART_BUFFER(som_debug, 1U, 1U, NULL, NULL, false) + UART_BUFFER(som_debug, 1U, 1U, NULL, NULL, true) #endif uart_ring *get_ring_by_number(int a) { @@ -67,9 +63,6 @@ uart_ring *get_ring_by_number(int a) { case 0: ring = &uart_ring_debug; break; - case 1: - ring = &uart_ring_gps; - break; case 2: ring = &uart_ring_lin1; break; @@ -106,7 +99,13 @@ bool injectc(uart_ring *q, char elem) { uint16_t next_w_ptr; ENTER_CRITICAL(); - next_w_ptr = (q->w_ptr_rx + 1U) % q->tx_fifo_size; + next_w_ptr = (q->w_ptr_rx + 1U) % q->rx_fifo_size; + + if ((next_w_ptr == q->r_ptr_rx) && q->overwrite) { + // overwrite mode: drop oldest byte + q->r_ptr_rx = (q->r_ptr_rx + 1U) % q->rx_fifo_size; + } + if (next_w_ptr != q->r_ptr_rx) { q->elems_rx[q->w_ptr_rx] = elem; q->w_ptr_rx = next_w_ptr; @@ -123,6 +122,12 @@ bool putc(uart_ring *q, char elem) { ENTER_CRITICAL(); next_w_ptr = (q->w_ptr_tx + 1U) % q->tx_fifo_size; + + if ((next_w_ptr == q->r_ptr_tx) && q->overwrite) { + // overwrite mode: drop oldest byte + q->r_ptr_tx = (q->r_ptr_tx + 1U) % q->tx_fifo_size; + } + if (next_w_ptr != q->r_ptr_tx) { q->elems_tx[q->w_ptr_tx] = elem; q->w_ptr_tx = next_w_ptr; diff --git a/board/drivers/usb.h b/board/drivers/usb.h index 469299d74e..c291d7e64c 100644 --- a/board/drivers/usb.h +++ b/board/drivers/usb.h @@ -27,8 +27,7 @@ bool usb_enumerated = false; uint16_t usb_last_frame_num = 0U; void usb_init(void); -void usb_cb_ep3_out_complete(void); -void usb_outep3_resume_if_paused(void); +void refresh_can_tx_slots_available(void); // **** supporting defines **** @@ -199,8 +198,13 @@ uint16_t string_product_desc[] = { // default serial number when we're not a panda uint16_t string_serial_desc[] = { +#ifdef PEDAL + STRING_DESCRIPTOR_HEADER(5), + 'p', 'e', 'd', 'a', 'l' +#else STRING_DESCRIPTOR_HEADER(4), 'n', 'o', 'n', 'e' +#endif }; // a string containing the default configuration index @@ -808,7 +812,7 @@ void usb_irqhandler(void) { #endif // NAK cleared by process_can (if tx buffers have room) outep3_processing = false; - usb_cb_ep3_out_complete(); + refresh_can_tx_slots_available(); } else if ((USBx_OUTEP(3)->DOEPINT & 0x2000) != 0) { #ifdef DEBUG_USB print(" OUT3 PACKET WTF\n"); @@ -926,7 +930,7 @@ void usb_irqhandler(void) { //USBx->GINTMSK = 0xFFFFFFFF & ~(USB_OTG_GINTMSK_NPTXFEM | USB_OTG_GINTMSK_PTXFEM | USB_OTG_GINTSTS_SOF | USB_OTG_GINTSTS_EOPF); } -void usb_outep3_resume_if_paused(void) { +void can_tx_comms_resume_usb(void) { ENTER_CRITICAL(); if (!outep3_processing && (USBx_OUTEP(3)->DOEPCTL & USB_OTG_DOEPCTL_NAKSTS) != 0) { USBx_OUTEP(3)->DOEPTSIZ = (32U << 19) | 0x800U; diff --git a/board/drivers/watchdog.h b/board/drivers/watchdog.h new file mode 100644 index 0000000000..d0ee32cb2d --- /dev/null +++ b/board/drivers/watchdog.h @@ -0,0 +1,30 @@ +// TODO: why doesn't it define these? +#ifdef STM32F2 +#define IWDG_PR_PR_Msk 0x7U +#define IWDG_RLR_RL_Msk 0xFFFU +#endif + +typedef enum { + WATCHDOG_50_MS = (400U - 1U), + WATCHDOG_500_MS = 4000U, +} WatchdogTimeout; + +void watchdog_feed(void) { + IND_WDG->KR = 0xAAAAU; +} + +void watchdog_init(WatchdogTimeout timeout) { + // enable watchdog + IND_WDG->KR = 0xCCCCU; + IND_WDG->KR = 0x5555U; + + // 32KHz / 4 prescaler = 8000Hz + register_set(&(IND_WDG->PR), 0x0U, IWDG_PR_PR_Msk); + register_set(&(IND_WDG->RLR), timeout, IWDG_RLR_RL_Msk); + + // wait for watchdog to be updated + while (IND_WDG->SR != 0U); + + // start the countdown + watchdog_feed(); +} diff --git a/board/early_init.h b/board/early_init.h index 6c60b20fbb..ae652aebce 100644 --- a/board/early_init.h +++ b/board/early_init.h @@ -51,9 +51,9 @@ void early_initialization(void) { detect_board_type(); if (enter_bootloader_mode == ENTER_BOOTLOADER_MAGIC) { - #ifdef PANDA - current_board->set_gps_mode(GPS_DISABLED); - #endif + #ifdef PANDA + current_board->init_bootloader(); + #endif current_board->set_led(LED_GREEN, 1); jump_to_bootloader(); } diff --git a/board/fake_stm.h b/board/fake_stm.h index b73a4e8985..e6709c78e0 100644 --- a/board/fake_stm.h +++ b/board/fake_stm.h @@ -2,8 +2,10 @@ #include #include #include +#include #include "utils.h" +#include "drivers/rtc_definitions.h" #define CANFD #define ALLOW_DEBUG @@ -31,3 +33,64 @@ uint32_t microsecond_timer_get(void); uint32_t microsecond_timer_get(void) { return MICROSECOND_TIMER->CNT; } + +// Register functions +void register_set_bits(volatile uint32_t *addr, uint32_t val) {} + +// RTC +timestamp_t rtc_get_time() { + timestamp_t result; + result.year = 1996; + result.month = 4; + result.day = 23; + result.weekday = 2; + result.hour = 4; + result.minute = 20; + result.second = 20; + return result; +} + +// Logging and flash +uint8_t fake_logging_bank[0x40000] __attribute__ ((aligned (4))); +#define LOGGING_FLASH_BASE_A (&fake_logging_bank[0]) +#define LOGGING_FLASH_BASE_B (&fake_logging_bank[0x20000]) +#define LOGGING_FLASH_SECTOR_A 5 +#define LOGGING_FLASH_SECTOR_B 6 +#define LOGGING_FLASH_SECTOR_SIZE 0x20000U + +bool flash_locked = true; +void flash_unlock(void) { + flash_locked = false; +} +void flash_lock(void) { + flash_locked = true; +} + +void *memset(void *str, int c, unsigned int n); + +bool flash_erase_sector(uint8_t sector) { + if (flash_locked) { + return false; + } + + switch (sector) { + case LOGGING_FLASH_SECTOR_A: + memset(LOGGING_FLASH_BASE_A, 0xFF, sizeof(fake_logging_bank)/2); + return true; + case LOGGING_FLASH_SECTOR_B: + memset(LOGGING_FLASH_BASE_B, 0xFF, sizeof(fake_logging_bank)/2); + return true; + default: + return false; + } +} + +void flash_write_word(void *prog_ptr, uint32_t data) { + if (flash_locked || prog_ptr < (void *) LOGGING_FLASH_BASE_A || prog_ptr >= (void *) (LOGGING_FLASH_BASE_A + sizeof(fake_logging_bank))) { + return; + } + + *(uint32_t *)prog_ptr = data; +} + +void flush_write_buffer(void) {} \ No newline at end of file diff --git a/board/faults.h b/board/faults.h index 294ac1176f..a741ea1c41 100644 --- a/board/faults.h +++ b/board/faults.h @@ -30,6 +30,7 @@ #define FAULT_INTERRUPT_RATE_UART_7 (1U << 24) #define FAULT_SIREN_MALFUNCTION (1U << 25) #define FAULT_HEARTBEAT_LOOP_WATCHDOG (1U << 26) +#define FAULT_LOGGING_RATE_LIMIT (1U << 27) // Permanent faults #define PERMANENT_FAULTS 0U @@ -38,18 +39,20 @@ uint8_t fault_status = FAULT_STATUS_NONE; uint32_t faults = 0U; void fault_occurred(uint32_t fault) { - faults |= fault; - if((PERMANENT_FAULTS & fault) != 0U){ - print("Permanent fault occurred: 0x"); puth(fault); print("\n"); - fault_status = FAULT_STATUS_PERMANENT; - } else { - print("Temporary fault occurred: 0x"); puth(fault); print("\n"); - fault_status = FAULT_STATUS_TEMPORARY; + if ((faults & fault) == 0U) { + if ((PERMANENT_FAULTS & fault) != 0U) { + print("Permanent fault occurred: 0x"); puth(fault); print("\n"); + fault_status = FAULT_STATUS_PERMANENT; + } else { + print("Temporary fault occurred: 0x"); puth(fault); print("\n"); + fault_status = FAULT_STATUS_TEMPORARY; + } } + faults |= fault; } void fault_recovered(uint32_t fault) { - if((PERMANENT_FAULTS & fault) == 0U){ + if ((PERMANENT_FAULTS & fault) == 0U) { faults &= ~fault; } else { print("Cannot recover from a permanent fault!\n"); diff --git a/board/flasher.h b/board/flasher.h index 97d81096e5..351a899915 100644 --- a/board/flasher.h +++ b/board/flasher.h @@ -1,6 +1,5 @@ // flasher state variables uint32_t *prog_ptr = NULL; -bool unlocked = false; void spi_init(void); @@ -33,13 +32,12 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { resp[1] = 0xff; } current_board->set_led(LED_GREEN, 1); - unlocked = true; prog_ptr = (uint32_t *)APP_START_ADDRESS; break; // **** 0xb2: erase sector case 0xb2: sec = req->param1; - if (flash_erase_sector(sec, unlocked)) { + if (flash_erase_sector(sec)) { resp[1] = 0xff; } break; @@ -110,7 +108,7 @@ int comms_can_read(uint8_t *data, uint32_t max_len) { return 0; } -void usb_cb_ep3_out_complete(void) {} +void refresh_can_tx_slots_available(void) {} void comms_endpoint2_write(uint8_t *data, uint32_t len) { current_board->set_led(LED_RED, 0); diff --git a/board/health.h b/board/health.h index de6f0e34ee..9d9dcfb381 100644 --- a/board/health.h +++ b/board/health.h @@ -1,6 +1,6 @@ // When changing these structs, python/__init__.py needs to be kept up to date! -#define HEALTH_PACKET_VERSION 13 +#define HEALTH_PACKET_VERSION 14 struct __attribute__((packed)) health_t { uint32_t uptime_pkt; uint32_t voltage_pkt; @@ -27,9 +27,11 @@ struct __attribute__((packed)) health_t { uint8_t safety_rx_checks_invalid; uint16_t spi_checksum_error_count; uint8_t fan_stall_count; + uint16_t sbu1_voltage_mV; + uint16_t sbu2_voltage_mV; }; -#define CAN_HEALTH_PACKET_VERSION 4 +#define CAN_HEALTH_PACKET_VERSION 5 typedef struct __attribute__((packed)) { uint8_t bus_off; uint32_t bus_off_cnt; @@ -53,4 +55,8 @@ typedef struct __attribute__((packed)) { uint8_t canfd_enabled; uint8_t brs_enabled; uint8_t canfd_non_iso; + uint32_t irq0_call_rate; + uint32_t irq1_call_rate; + uint32_t irq2_call_rate; + uint32_t can_core_reset_cnt; } can_health_t; diff --git a/board/jungle/README.md b/board/jungle/README.md new file mode 100644 index 0000000000..4c271cbf21 --- /dev/null +++ b/board/jungle/README.md @@ -0,0 +1,26 @@ +Welcome to the jungle +====== + +Firmware for the Panda Jungle testing board. +Available for purchase at the [comma shop](https://comma.ai/shop/panda-jungle). + +## udev rules + +To make the jungle usable without root permissions, you might need to setup udev rules for it. +On ubuntu, this should do the trick: +``` bash +sudo tee /etc/udev/rules.d/12-panda_jungle.rules < bool: + dfu_serial = self.get_dfu_serial() + + if reset: + self.reset(enter_bootstub=True) + self.reset(enter_bootloader=True) + + if not self.wait_for_dfu(dfu_serial, timeout=timeout): + return False + + dfu = PandaJungleDFU(dfu_serial) + dfu.recover() + + # reflash after recover + self.connect(True, True) + self.flash() + return True + + def get_mcu_type(self) -> McuType: + hw_type = self.get_type() + if hw_type in PandaJungle.F4_DEVICES: + return McuType.F4 + elif hw_type in PandaJungle.H7_DEVICES: + return McuType.H7 + raise ValueError(f"unknown HW type: {hw_type}") + + def up_to_date(self) -> bool: + current = self.get_signature() + fn = os.path.join(FW_PATH, self.get_mcu_type().config.app_fn.replace("panda", "panda_jungle")) + expected = Panda.get_signature_from_firmware(fn) + return (current == expected) + + # ******************* health ******************* + + @ensure_jungle_health_packet_version + def health(self): + dat = self._handle.controlRead(PandaJungle.REQUEST_IN, 0xd2, 0, 0, self.HEALTH_STRUCT.size) + a = self.HEALTH_STRUCT.unpack(dat) + return { + "uptime": a[0], + "ch1_power": a[1], + "ch2_power": a[2], + "ch3_power": a[3], + "ch4_power": a[4], + "ch5_power": a[5], + "ch6_power": a[6], + "ch1_sbu1_voltage": a[7] / 1000.0, + "ch1_sbu2_voltage": a[8] / 1000.0, + "ch2_sbu1_voltage": a[9] / 1000.0, + "ch2_sbu2_voltage": a[10] / 1000.0, + "ch3_sbu1_voltage": a[11] / 1000.0, + "ch3_sbu2_voltage": a[12] / 1000.0, + "ch4_sbu1_voltage": a[13] / 1000.0, + "ch4_sbu2_voltage": a[14] / 1000.0, + "ch5_sbu1_voltage": a[15] / 1000.0, + "ch5_sbu2_voltage": a[16] / 1000.0, + "ch6_sbu1_voltage": a[17] / 1000.0, + "ch6_sbu2_voltage": a[18] / 1000.0, + } + + # ******************* control ******************* + + # Returns tuple with health packet version and CAN packet/USB packet version + def get_packets_versions(self): + dat = self._handle.controlRead(PandaJungle.REQUEST_IN, 0xdd, 0, 0, 3) + if dat and len(dat) == 3: + a = struct.unpack("BBB", dat) + return (a[0], a[1], a[2]) + return (-1, -1, -1) + + # ******************* jungle stuff ******************* + + def set_panda_power(self, enabled): + self._handle.controlWrite(PandaJungle.REQUEST_OUT, 0xa0, int(enabled), 0, b'') + + def set_harness_orientation(self, mode): + self._handle.controlWrite(PandaJungle.REQUEST_OUT, 0xa1, int(mode), 0, b'') + + def set_ignition(self, enabled): + self._handle.controlWrite(PandaJungle.REQUEST_OUT, 0xa2, int(enabled), 0, b'') + + def set_can_silent(self, silent): + self._handle.controlWrite(PandaJungle.REQUEST_OUT, 0xf5, int(silent), 0, b'') + + # ******************* serial ******************* + + def debug_read(self): + ret = [] + while 1: + lret = bytes(self._handle.controlRead(PandaJungle.REQUEST_IN, 0xe0, 0, 0, 0x40)) + if len(lret) == 0: + break + ret.append(lret) + return b''.join(ret) + + # ******************* header pins ******************* + + def set_header_pin(self, pin_num, enabled): + self._handle.controlWrite(Panda.REQUEST_OUT, 0xf7, int(pin_num), int(enabled), b'') diff --git a/board/jungle/boards/board_declarations.h b/board/jungle/boards/board_declarations.h new file mode 100644 index 0000000000..3fe4c2fab9 --- /dev/null +++ b/board/jungle/boards/board_declarations.h @@ -0,0 +1,77 @@ +// ******************** Prototypes ******************** +typedef void (*board_init)(void); +typedef void (*board_set_led)(uint8_t color, bool enabled); +typedef void (*board_board_tick)(void); +typedef bool (*board_get_button)(void); +typedef void (*board_set_panda_power)(bool enabled); +typedef void (*board_set_ignition)(bool enabled); +typedef void (*board_set_individual_ignition)(uint8_t bitmask); +typedef void (*board_set_harness_orientation)(uint8_t orientation); +typedef void (*board_set_can_mode)(uint8_t mode); +typedef void (*board_enable_can_transciever)(uint8_t transciever, bool enabled); +typedef void (*board_enable_header_pin)(uint8_t pin_num, bool enabled); +typedef float (*board_get_channel_power)(uint8_t channel); +typedef uint16_t (*board_get_sbu_mV)(uint8_t channel, uint8_t sbu); + +struct board { + const char *board_type; + const bool has_canfd; + const bool has_sbu_sense; + const uint16_t avdd_mV; + board_init init; + board_set_led set_led; + board_board_tick board_tick; + board_get_button get_button; + board_set_panda_power set_panda_power; + board_set_ignition set_ignition; + board_set_individual_ignition set_individual_ignition; + board_set_harness_orientation set_harness_orientation; + board_set_can_mode set_can_mode; + board_enable_can_transciever enable_can_transciever; + board_enable_header_pin enable_header_pin; + board_get_channel_power get_channel_power; + board_get_sbu_mV get_sbu_mV; + + // TODO: shouldn't need these + bool has_spi; + bool has_hw_gmlan; +}; + +// ******************* Definitions ******************** +#define HW_TYPE_UNKNOWN 0U +#define HW_TYPE_V1 1U +#define HW_TYPE_V2 2U + +// LED colors +#define LED_RED 0U +#define LED_GREEN 1U +#define LED_BLUE 2U + +// CAN modes +#define CAN_MODE_NORMAL 0U +#define CAN_MODE_GMLAN_CAN2 1U +#define CAN_MODE_GMLAN_CAN3 2U +#define CAN_MODE_OBD_CAN2 3U + +// Harness states +#define HARNESS_ORIENTATION_NONE 0U +#define HARNESS_ORIENTATION_1 1U +#define HARNESS_ORIENTATION_2 2U + +#define SBU1 0U +#define SBU2 1U + +// ********************* Globals ********************** +uint8_t harness_orientation = HARNESS_ORIENTATION_NONE; +uint8_t can_mode = CAN_MODE_NORMAL; +uint8_t ignition = 0U; + + +void unused_set_individual_ignition(uint8_t bitmask) { + UNUSED(bitmask); +} + +void unused_board_enable_header_pin(uint8_t pin_num, bool enabled) { + UNUSED(pin_num); + UNUSED(enabled); +} diff --git a/board/jungle/boards/board_v1.h b/board/jungle/boards/board_v1.h new file mode 100644 index 0000000000..8d41b94524 --- /dev/null +++ b/board/jungle/boards/board_v1.h @@ -0,0 +1,175 @@ + +void board_v1_set_led(uint8_t color, bool enabled) { + switch (color) { + case LED_RED: + set_gpio_output(GPIOC, 9, !enabled); + break; + case LED_GREEN: + set_gpio_output(GPIOC, 7, !enabled); + break; + case LED_BLUE: + set_gpio_output(GPIOC, 6, !enabled); + break; + default: + break; + } +} + +void board_v1_enable_can_transciever(uint8_t transciever, bool enabled) { + switch (transciever) { + case 1U: + set_gpio_output(GPIOC, 1, !enabled); + break; + case 2U: + set_gpio_output(GPIOC, 13, !enabled); + break; + case 3U: + set_gpio_output(GPIOA, 0, !enabled); + break; + case 4U: + set_gpio_output(GPIOB, 10, !enabled); + break; + default: + print("Invalid CAN transciever ("); puth(transciever); print("): enabling failed\n"); + break; + } +} + +void board_v1_set_can_mode(uint8_t mode) { + board_v1_enable_can_transciever(2U, false); + board_v1_enable_can_transciever(4U, false); + switch (mode) { + case CAN_MODE_NORMAL: + print("Setting normal CAN mode\n"); + // B12,B13: disable OBD mode + set_gpio_mode(GPIOB, 12, MODE_INPUT); + set_gpio_mode(GPIOB, 13, MODE_INPUT); + + // B5,B6: normal CAN2 mode + set_gpio_alternate(GPIOB, 5, GPIO_AF9_CAN2); + set_gpio_alternate(GPIOB, 6, GPIO_AF9_CAN2); + can_mode = CAN_MODE_NORMAL; + board_v1_enable_can_transciever(2U, true); + break; + case CAN_MODE_OBD_CAN2: + print("Setting OBD CAN mode\n"); + // B5,B6: disable normal CAN2 mode + set_gpio_mode(GPIOB, 5, MODE_INPUT); + set_gpio_mode(GPIOB, 6, MODE_INPUT); + + // B12,B13: OBD mode + set_gpio_alternate(GPIOB, 12, GPIO_AF9_CAN2); + set_gpio_alternate(GPIOB, 13, GPIO_AF9_CAN2); + can_mode = CAN_MODE_OBD_CAN2; + board_v1_enable_can_transciever(4U, true); + break; + default: + print("Tried to set unsupported CAN mode: "); puth(mode); print("\n"); + break; + } +} + +void board_v1_set_harness_orientation(uint8_t orientation) { + switch (orientation) { + case HARNESS_ORIENTATION_NONE: + set_gpio_output(GPIOA, 2, false); + set_gpio_output(GPIOA, 3, false); + set_gpio_output(GPIOA, 4, false); + set_gpio_output(GPIOA, 5, false); + harness_orientation = orientation; + break; + case HARNESS_ORIENTATION_1: + set_gpio_output(GPIOA, 2, false); + set_gpio_output(GPIOA, 3, (ignition != 0U)); + set_gpio_output(GPIOA, 4, true); + set_gpio_output(GPIOA, 5, false); + harness_orientation = orientation; + break; + case HARNESS_ORIENTATION_2: + set_gpio_output(GPIOA, 2, (ignition != 0U)); + set_gpio_output(GPIOA, 3, false); + set_gpio_output(GPIOA, 4, false); + set_gpio_output(GPIOA, 5, true); + harness_orientation = orientation; + break; + default: + print("Tried to set an unsupported harness orientation: "); puth(orientation); print("\n"); + break; + } +} + +bool panda_power = false; +void board_v1_set_panda_power(bool enable) { + panda_power = enable; + set_gpio_output(GPIOB, 14, enable); +} + +bool board_v1_get_button(void) { + return get_gpio_input(GPIOC, 8); +} + +void board_v1_set_ignition(bool enabled) { + ignition = enabled ? 0xFFU : 0U; + board_v1_set_harness_orientation(harness_orientation); +} + +float board_v1_get_channel_power(uint8_t channel) { + UNUSED(channel); + return 0.0f; +} + +uint16_t board_v1_get_sbu_mV(uint8_t channel, uint8_t sbu) { + UNUSED(channel); UNUSED(sbu); + return 0U; +} + +void board_v1_init(void) { + common_init_gpio(); + + // A8,A15: normal CAN3 mode + set_gpio_alternate(GPIOA, 8, GPIO_AF11_CAN3); + set_gpio_alternate(GPIOA, 15, GPIO_AF11_CAN3); + + board_v1_set_can_mode(CAN_MODE_NORMAL); + + // Enable CAN transcievers + for(uint8_t i = 1; i <= 4; i++) { + board_v1_enable_can_transciever(i, true); + } + + // Disable LEDs + board_v1_set_led(LED_RED, false); + board_v1_set_led(LED_GREEN, false); + board_v1_set_led(LED_BLUE, false); + + // Set normal CAN mode + board_v1_set_can_mode(CAN_MODE_NORMAL); + + // Set to no harness orientation + board_v1_set_harness_orientation(HARNESS_ORIENTATION_NONE); + + // Enable panda power by default + board_v1_set_panda_power(true); +} + +void board_v1_tick(void) {} + +const board board_v1 = { + .board_type = "V1", + .has_canfd = false, + .has_sbu_sense = false, + .avdd_mV = 3300U, + .init = &board_v1_init, + .set_led = &board_v1_set_led, + .board_tick = &board_v1_tick, + .get_button = &board_v1_get_button, + .set_panda_power = &board_v1_set_panda_power, + .set_ignition = &board_v1_set_ignition, + .set_individual_ignition = &unused_set_individual_ignition, + .set_harness_orientation = &board_v1_set_harness_orientation, + .set_can_mode = &board_v1_set_can_mode, + .enable_can_transciever = &board_v1_enable_can_transciever, + .enable_header_pin = &unused_board_enable_header_pin, + .get_channel_power = &board_v1_get_channel_power, + .get_sbu_mV = &board_v1_get_sbu_mV, +}; diff --git a/board/jungle/boards/board_v2.h b/board/jungle/boards/board_v2.h new file mode 100644 index 0000000000..0a6e27368c --- /dev/null +++ b/board/jungle/boards/board_v2.h @@ -0,0 +1,308 @@ + +const gpio_t power_pins[] = { + {.bank = GPIOA, .pin = 0}, + {.bank = GPIOA, .pin = 1}, + {.bank = GPIOF, .pin = 12}, + {.bank = GPIOA, .pin = 5}, + {.bank = GPIOC, .pin = 5}, + {.bank = GPIOB, .pin = 2}, +}; + +const gpio_t sbu1_ignition_pins[] = { + {.bank = GPIOD, .pin = 0}, + {.bank = GPIOD, .pin = 5}, + {.bank = GPIOD, .pin = 12}, + {.bank = GPIOD, .pin = 14}, + {.bank = GPIOE, .pin = 5}, + {.bank = GPIOE, .pin = 9}, +}; + +const gpio_t sbu1_relay_pins[] = { + {.bank = GPIOD, .pin = 1}, + {.bank = GPIOD, .pin = 6}, + {.bank = GPIOD, .pin = 11}, + {.bank = GPIOD, .pin = 15}, + {.bank = GPIOE, .pin = 6}, + {.bank = GPIOE, .pin = 10}, +}; + +const gpio_t sbu2_ignition_pins[] = { + {.bank = GPIOD, .pin = 3}, + {.bank = GPIOD, .pin = 8}, + {.bank = GPIOD, .pin = 9}, + {.bank = GPIOE, .pin = 0}, + {.bank = GPIOE, .pin = 7}, + {.bank = GPIOE, .pin = 11}, +}; + +const gpio_t sbu2_relay_pins[] = { + {.bank = GPIOD, .pin = 4}, + {.bank = GPIOD, .pin = 10}, + {.bank = GPIOD, .pin = 13}, + {.bank = GPIOE, .pin = 1}, + {.bank = GPIOE, .pin = 8}, + {.bank = GPIOE, .pin = 12}, +}; + +const adc_channel_t sbu1_channels[] = { + {.adc = ADC3, .channel = 12}, + {.adc = ADC3, .channel = 2}, + {.adc = ADC3, .channel = 4}, + {.adc = ADC3, .channel = 6}, + {.adc = ADC3, .channel = 8}, + {.adc = ADC3, .channel = 10}, +}; + +const adc_channel_t sbu2_channels[] = { + {.adc = ADC1, .channel = 13}, + {.adc = ADC3, .channel = 3}, + {.adc = ADC3, .channel = 5}, + {.adc = ADC3, .channel = 7}, + {.adc = ADC3, .channel = 9}, + {.adc = ADC3, .channel = 11}, +}; + +void board_v2_set_led(uint8_t color, bool enabled) { + switch (color) { + case LED_RED: + set_gpio_output(GPIOE, 4, !enabled); + break; + case LED_GREEN: + set_gpio_output(GPIOE, 3, !enabled); + break; + case LED_BLUE: + set_gpio_output(GPIOE, 2, !enabled); + break; + default: + break; + } +} + +void board_v2_set_harness_orientation(uint8_t orientation) { + switch (orientation) { + case HARNESS_ORIENTATION_NONE: + gpio_set_all_output(sbu1_ignition_pins, sizeof(sbu1_ignition_pins) / sizeof(gpio_t), false); + gpio_set_all_output(sbu1_relay_pins, sizeof(sbu1_relay_pins) / sizeof(gpio_t), false); + gpio_set_all_output(sbu2_ignition_pins, sizeof(sbu2_ignition_pins) / sizeof(gpio_t), false); + gpio_set_all_output(sbu2_relay_pins, sizeof(sbu2_relay_pins) / sizeof(gpio_t), false); + harness_orientation = orientation; + break; + case HARNESS_ORIENTATION_1: + gpio_set_all_output(sbu1_ignition_pins, sizeof(sbu1_ignition_pins) / sizeof(gpio_t), false); + gpio_set_all_output(sbu1_relay_pins, sizeof(sbu1_relay_pins) / sizeof(gpio_t), true); + gpio_set_bitmask(sbu2_ignition_pins, sizeof(sbu2_ignition_pins) / sizeof(gpio_t), ignition); + gpio_set_all_output(sbu2_relay_pins, sizeof(sbu2_relay_pins) / sizeof(gpio_t), false); + harness_orientation = orientation; + break; + case HARNESS_ORIENTATION_2: + gpio_set_bitmask(sbu1_ignition_pins, sizeof(sbu1_ignition_pins) / sizeof(gpio_t), ignition); + gpio_set_all_output(sbu1_relay_pins, sizeof(sbu1_relay_pins) / sizeof(gpio_t), false); + gpio_set_all_output(sbu2_ignition_pins, sizeof(sbu2_ignition_pins) / sizeof(gpio_t), false); + gpio_set_all_output(sbu2_relay_pins, sizeof(sbu2_relay_pins) / sizeof(gpio_t), true); + harness_orientation = orientation; + break; + default: + print("Tried to set an unsupported harness orientation: "); puth(orientation); print("\n"); + break; + } +} + +void board_v2_enable_can_transciever(uint8_t transciever, bool enabled) { + switch (transciever) { + case 1U: + set_gpio_output(GPIOG, 11, !enabled); + break; + case 2U: + set_gpio_output(GPIOB, 3, !enabled); + break; + case 3U: + set_gpio_output(GPIOD, 7, !enabled); + break; + case 4U: + set_gpio_output(GPIOB, 4, !enabled); + break; + default: + print("Invalid CAN transciever ("); puth(transciever); print("): enabling failed\n"); + break; + } +} + +void board_v2_enable_header_pin(uint8_t pin_num, bool enabled) { + if (pin_num < 8U) { + set_gpio_output(GPIOG, pin_num, enabled); + } else { + print("Invalid pin number ("); puth(pin_num); print("): enabling failed\n"); + } +} + +void board_v2_set_can_mode(uint8_t mode) { + board_v2_enable_can_transciever(2U, false); + board_v2_enable_can_transciever(4U, false); + switch (mode) { + case CAN_MODE_NORMAL: + // B12,B13: disable normal mode + set_gpio_pullup(GPIOB, 12, PULL_NONE); + set_gpio_mode(GPIOB, 12, MODE_ANALOG); + + set_gpio_pullup(GPIOB, 13, PULL_NONE); + set_gpio_mode(GPIOB, 13, MODE_ANALOG); + + // B5,B6: FDCAN2 mode + set_gpio_pullup(GPIOB, 5, PULL_NONE); + set_gpio_alternate(GPIOB, 5, GPIO_AF9_FDCAN2); + + set_gpio_pullup(GPIOB, 6, PULL_NONE); + set_gpio_alternate(GPIOB, 6, GPIO_AF9_FDCAN2); + can_mode = CAN_MODE_NORMAL; + board_v2_enable_can_transciever(2U, true); + break; + case CAN_MODE_OBD_CAN2: + // B5,B6: disable normal mode + set_gpio_pullup(GPIOB, 5, PULL_NONE); + set_gpio_mode(GPIOB, 5, MODE_ANALOG); + + set_gpio_pullup(GPIOB, 6, PULL_NONE); + set_gpio_mode(GPIOB, 6, MODE_ANALOG); + // B12,B13: FDCAN2 mode + set_gpio_pullup(GPIOB, 12, PULL_NONE); + set_gpio_alternate(GPIOB, 12, GPIO_AF9_FDCAN2); + + set_gpio_pullup(GPIOB, 13, PULL_NONE); + set_gpio_alternate(GPIOB, 13, GPIO_AF9_FDCAN2); + can_mode = CAN_MODE_OBD_CAN2; + board_v2_enable_can_transciever(4U, true); + break; + default: + break; + } +} + +bool panda_power = false; +void board_v2_set_panda_power(bool enable) { + panda_power = enable; + gpio_set_all_output(power_pins, sizeof(power_pins) / sizeof(gpio_t), enable); +} + +bool board_v2_get_button(void) { + return get_gpio_input(GPIOG, 15); +} + +void board_v2_set_ignition(bool enabled) { + ignition = enabled ? 0xFFU : 0U; + board_v2_set_harness_orientation(harness_orientation); +} + +void board_v2_set_individual_ignition(uint8_t bitmask) { + ignition = bitmask; + board_v2_set_harness_orientation(harness_orientation); +} + +float board_v2_get_channel_power(uint8_t channel) { + float ret = 0.0f; + if ((channel >= 1U) && (channel <= 6U)) { + uint16_t readout = adc_get_mV(ADC1, channel - 1U); // these are mapped nicely in hardware + + ret = (((float) readout / 33e6) - 0.8e-6) / 52e-6 * 12.0f; + } else { + print("Invalid channel ("); puth(channel); print(")\n"); + } + return ret; +} + +uint16_t board_v2_get_sbu_mV(uint8_t channel, uint8_t sbu) { + uint16_t ret = 0U; + if ((channel >= 1U) && (channel <= 6U)) { + switch(sbu){ + case SBU1: + ret = adc_get_mV(sbu1_channels[channel - 1U].adc, sbu1_channels[channel - 1U].channel); + break; + case SBU2: + ret = adc_get_mV(sbu2_channels[channel - 1U].adc, sbu2_channels[channel - 1U].channel); + break; + default: + print("Invalid SBU ("); puth(sbu); print(")\n"); + break; + } + } else { + print("Invalid channel ("); puth(channel); print(")\n"); + } + return ret; +} + +void board_v2_init(void) { + common_init_gpio(); + + // Disable LEDs + board_v2_set_led(LED_RED, false); + board_v2_set_led(LED_GREEN, false); + board_v2_set_led(LED_BLUE, false); + + // Normal CAN mode + board_v2_set_can_mode(CAN_MODE_NORMAL); + + // Enable CAN transcievers + for(uint8_t i = 1; i <= 4; i++) { + board_v2_enable_can_transciever(i, true); + } + + // Set to no harness orientation + board_v2_set_harness_orientation(HARNESS_ORIENTATION_NONE); + + // Enable panda power by default + board_v2_set_panda_power(true); + + // Current monitor channels + adc_init(ADC1); + register_set_bits(&SYSCFG->PMCR, SYSCFG_PMCR_PA0SO | SYSCFG_PMCR_PA1SO); // open up analog switches for PA0_C and PA1_C + set_gpio_mode(GPIOF, 11, MODE_ANALOG); + set_gpio_mode(GPIOA, 6, MODE_ANALOG); + set_gpio_mode(GPIOC, 4, MODE_ANALOG); + set_gpio_mode(GPIOB, 1, MODE_ANALOG); + + // SBU channels + adc_init(ADC3); + set_gpio_mode(GPIOC, 2, MODE_ANALOG); + set_gpio_mode(GPIOC, 3, MODE_ANALOG); + set_gpio_mode(GPIOF, 9, MODE_ANALOG); + set_gpio_mode(GPIOF, 7, MODE_ANALOG); + set_gpio_mode(GPIOF, 5, MODE_ANALOG); + set_gpio_mode(GPIOF, 3, MODE_ANALOG); + set_gpio_mode(GPIOF, 10, MODE_ANALOG); + set_gpio_mode(GPIOF, 8, MODE_ANALOG); + set_gpio_mode(GPIOF, 6, MODE_ANALOG); + set_gpio_mode(GPIOF, 4, MODE_ANALOG); + set_gpio_mode(GPIOC, 0, MODE_ANALOG); + set_gpio_mode(GPIOC, 1, MODE_ANALOG); + + // Header pins + set_gpio_mode(GPIOG, 0, MODE_OUTPUT); + set_gpio_mode(GPIOG, 1, MODE_OUTPUT); + set_gpio_mode(GPIOG, 2, MODE_OUTPUT); + set_gpio_mode(GPIOG, 3, MODE_OUTPUT); + set_gpio_mode(GPIOG, 4, MODE_OUTPUT); + set_gpio_mode(GPIOG, 5, MODE_OUTPUT); + set_gpio_mode(GPIOG, 6, MODE_OUTPUT); + set_gpio_mode(GPIOG, 7, MODE_OUTPUT); +} + +void board_v2_tick(void) {} + +const board board_v2 = { + .board_type = "V2", + .has_canfd = true, + .has_sbu_sense = true, + .avdd_mV = 3300U, + .init = &board_v2_init, + .set_led = &board_v2_set_led, + .board_tick = &board_v2_tick, + .get_button = &board_v2_get_button, + .set_panda_power = &board_v2_set_panda_power, + .set_ignition = &board_v2_set_ignition, + .set_individual_ignition = &board_v2_set_individual_ignition, + .set_harness_orientation = &board_v2_set_harness_orientation, + .set_can_mode = &board_v2_set_can_mode, + .enable_can_transciever = &board_v2_enable_can_transciever, + .enable_header_pin = &board_v2_enable_header_pin, + .get_channel_power = &board_v2_get_channel_power, + .get_sbu_mV = &board_v2_get_sbu_mV, +}; diff --git a/board/jungle/flash.py b/board/jungle/flash.py new file mode 100755 index 0000000000..75a7f0c8ee --- /dev/null +++ b/board/jungle/flash.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 +import os +import subprocess + +from panda import PandaJungle + +board_path = os.path.dirname(os.path.realpath(__file__)) + +if __name__ == "__main__": + subprocess.check_call(f"scons -C {board_path}/.. -u -j$(nproc) {board_path}", shell=True) + + serials = PandaJungle.list() + print(f"found {len(serials)} panda jungle(s) - {serials}") + for s in serials: + print("flashing", s) + with PandaJungle(serial=s) as p: + p.flash() diff --git a/board/jungle/jungle_health.h b/board/jungle/jungle_health.h new file mode 100644 index 0000000000..931ed3715e --- /dev/null +++ b/board/jungle/jungle_health.h @@ -0,0 +1,24 @@ +// When changing these structs, python/__init__.py needs to be kept up to date! + +#define JUNGLE_HEALTH_PACKET_VERSION 1 +struct __attribute__((packed)) jungle_health_t { + uint32_t uptime_pkt; + float ch1_power; + float ch2_power; + float ch3_power; + float ch4_power; + float ch5_power; + float ch6_power; + uint16_t ch1_sbu1_mV; + uint16_t ch1_sbu2_mV; + uint16_t ch2_sbu1_mV; + uint16_t ch2_sbu2_mV; + uint16_t ch3_sbu1_mV; + uint16_t ch3_sbu2_mV; + uint16_t ch4_sbu1_mV; + uint16_t ch4_sbu2_mV; + uint16_t ch5_sbu1_mV; + uint16_t ch5_sbu2_mV; + uint16_t ch6_sbu1_mV; + uint16_t ch6_sbu2_mV; +}; diff --git a/board/jungle/main.c b/board/jungle/main.c new file mode 100644 index 0000000000..1d39074a94 --- /dev/null +++ b/board/jungle/main.c @@ -0,0 +1,210 @@ +// ********************* Includes ********************* +#include "board/config.h" + +#include "board/safety.h" +#include "board/drivers/gmlan_alt.h" + +#include "board/drivers/pwm.h" +#include "board/drivers/usb.h" + +#include "board/early_init.h" +#include "board/provision.h" + +#include "board/health.h" +#include "jungle_health.h" + +#include "board/drivers/can_common.h" + +#ifdef STM32H7 + #include "board/drivers/fdcan.h" +#else + #include "board/drivers/bxcan.h" +#endif + +#include "board/obj/gitversion.h" + +#include "board/can_comms.h" +#include "main_comms.h" + + +// ********************* Serial debugging ********************* + +void debug_ring_callback(uart_ring *ring) { + char rcv; + while (getc(ring, &rcv)) { + (void)injectc(ring, rcv); + } +} + +// ***************************** main code ***************************** + +// cppcheck-suppress unusedFunction ; used in headers not included in cppcheck +void __initialize_hardware_early(void) { + early_initialization(); +} + +void __attribute__ ((noinline)) enable_fpu(void) { + // enable the FPU + SCB->CPACR |= ((3UL << (10U * 2U)) | (3UL << (11U * 2U))); +} + +// called at 8Hz +uint32_t loop_counter = 0U; +uint16_t button_press_cnt = 0U; +void tick_handler(void) { + if (TICK_TIMER->SR != 0) { + // tick drivers at 8Hz + usb_tick(); + + // decimated to 1Hz + if ((loop_counter % 8) == 0U) { + #ifdef DEBUG + 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 + + current_board->board_tick(); + + // check registers + check_registers(); + + // turn off the blue LED, turned on by CAN + current_board->set_led(LED_BLUE, false); + + // Blink and OBD CAN +#ifdef FINAL_PROVISIONING + current_board->set_can_mode(can_mode == CAN_MODE_NORMAL ? CAN_MODE_OBD_CAN2 : CAN_MODE_NORMAL); +#endif + + // on to the next one + uptime_cnt += 1U; + } + + current_board->set_led(LED_GREEN, green_led_enabled); + + // Check on button + bool current_button_status = current_board->get_button(); + + if (current_button_status && button_press_cnt == 10) { + current_board->set_panda_power(!panda_power); + } + +#ifdef FINAL_PROVISIONING + // Ignition blinking + uint8_t ignition_bitmask = 0U; + for (uint8_t i = 0U; i < 6U; i++) { + ignition_bitmask |= ((loop_counter % 12U) < ((uint32_t) i + 2U)) << i; + } + current_board->set_individual_ignition(ignition_bitmask); + + // SBU voltage reporting + if (current_board->has_sbu_sense) { + for (uint8_t i = 0U; i < 6U; i++) { + CANPacket_t pkt = { 0 }; + pkt.data_len_code = 8U; + pkt.addr = 0x100U + i; + *(uint16_t *) &pkt.data[0] = current_board->get_sbu_mV(i + 1U, SBU1); + *(uint16_t *) &pkt.data[2] = current_board->get_sbu_mV(i + 1U, SBU2); + pkt.data[4] = (ignition_bitmask >> i) & 1U; + can_set_checksum(&pkt); + can_send(&pkt, 0U, false); + } + } +#else + // toggle ignition on button press + static bool prev_button_status = false; + if (!current_button_status && prev_button_status && button_press_cnt < 10){ + current_board->set_ignition(!ignition); + } + prev_button_status = current_button_status; +#endif + + button_press_cnt = current_button_status ? button_press_cnt + 1 : 0; + + loop_counter++; + } + TICK_TIMER->SR = 0; +} + + +int main(void) { + // Init interrupt table + init_interrupts(true); + + // shouldn't have interrupts here, but just in case + disable_interrupts(); + + // init early devices + clock_init(); + peripherals_init(); + detect_board_type(); + + // print hello + print("\n\n\n************************ MAIN START ************************\n"); + + // check for non-supported board types + if (hw_type == HW_TYPE_UNKNOWN) { + print("Unsupported board type\n"); + while (1) { /* hang */ } + } + + print("Config:\n"); + print(" Board type: "); print(current_board->board_type); print("\n"); + + // init board + current_board->init(); + + // we have an FPU, let's use it! + enable_fpu(); + + microsecond_timer_init(); + + // 8Hz timer + REGISTER_INTERRUPT(TICK_TIMER_IRQ, tick_handler, 10U, FAULT_INTERRUPT_RATE_TICK) + tick_timer_init(); + +#ifdef DEBUG + print("DEBUG ENABLED\n"); +#endif + // enable USB (right before interrupts or enum can fail!) + usb_init(); + + print("**** INTERRUPTS ON ****\n"); + enable_interrupts(); + + can_silent = ALL_CAN_LIVE; + set_safety_hooks(SAFETY_ALLOUTPUT, 0U); + + can_init_all(); + current_board->set_harness_orientation(HARNESS_ORIENTATION_1); + +#ifdef FINAL_PROVISIONING + print("---- FINAL PROVISIONING BUILD ---- \n"); + can_set_forwarding(0, 2); + can_set_forwarding(1, 2); +#endif + + // LED should keep on blinking all the time + uint64_t cnt = 0; + for (cnt=0;;cnt++) { + // useful for debugging, fade breaks = panda is overloaded + for (uint32_t fade = 0U; fade < MAX_LED_FADE; fade += 1U) { + current_board->set_led(LED_RED, true); + delay(fade >> 4); + current_board->set_led(LED_RED, false); + delay((MAX_LED_FADE - fade) >> 4); + } + + for (uint32_t fade = MAX_LED_FADE; fade > 0U; fade -= 1U) { + current_board->set_led(LED_RED, true); + delay(fade >> 4); + current_board->set_led(LED_RED, false); + delay((MAX_LED_FADE - fade) >> 4); + } + } + + return 0; +} diff --git a/board/jungle/main_comms.h b/board/jungle/main_comms.h new file mode 100644 index 0000000000..252c2d509c --- /dev/null +++ b/board/jungle/main_comms.h @@ -0,0 +1,256 @@ +extern int _app_start[0xc000]; // Only first 3 sectors of size 0x4000 are used + +int get_jungle_health_pkt(void *dat) { + COMPILE_TIME_ASSERT(sizeof(struct jungle_health_t) <= USBPACKET_MAX_SIZE); + struct jungle_health_t * health = (struct jungle_health_t*)dat; + + health->uptime_pkt = uptime_cnt; + health->ch1_power = current_board->get_channel_power(1U); + health->ch2_power = current_board->get_channel_power(2U); + health->ch3_power = current_board->get_channel_power(3U); + health->ch4_power = current_board->get_channel_power(4U); + health->ch5_power = current_board->get_channel_power(5U); + health->ch6_power = current_board->get_channel_power(6U); + + health->ch1_sbu1_mV = current_board->get_sbu_mV(1U, SBU1); + health->ch1_sbu2_mV = current_board->get_sbu_mV(1U, SBU2); + health->ch2_sbu1_mV = current_board->get_sbu_mV(2U, SBU1); + health->ch2_sbu2_mV = current_board->get_sbu_mV(2U, SBU2); + health->ch3_sbu1_mV = current_board->get_sbu_mV(3U, SBU1); + health->ch3_sbu2_mV = current_board->get_sbu_mV(3U, SBU2); + health->ch4_sbu1_mV = current_board->get_sbu_mV(4U, SBU1); + health->ch4_sbu2_mV = current_board->get_sbu_mV(4U, SBU2); + health->ch5_sbu1_mV = current_board->get_sbu_mV(5U, SBU1); + health->ch5_sbu2_mV = current_board->get_sbu_mV(5U, SBU2); + health->ch6_sbu1_mV = current_board->get_sbu_mV(6U, SBU1); + health->ch6_sbu2_mV = current_board->get_sbu_mV(6U, SBU2); + + return sizeof(*health); +} + +// send on serial, first byte to select the ring +void comms_endpoint2_write(uint8_t *data, uint32_t len) { + UNUSED(data); + UNUSED(len); +} + +int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { + unsigned int resp_len = 0; + 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: Set panda power. + case 0xa0: + current_board->set_panda_power((req->param1 == 1U)); + break; + // **** 0xa1: Set harness orientation. + case 0xa1: + current_board->set_harness_orientation(req->param1); + break; + // **** 0xa2: Set ignition. + case 0xa2: + current_board->set_ignition((req->param1 == 1U)); + 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; + // **** 0xc0: reset communications + case 0xc0: + comms_can_reset(); + break; + // **** 0xc1: get hardware type + case 0xc1: + resp[0] = hw_type; + resp_len = 1; + break; + // **** 0xc2: CAN health stats + case 0xc2: + COMPILE_TIME_ASSERT(sizeof(can_health_t) <= USBPACKET_MAX_SIZE); + if (req->param1 < 3U) { + can_health[req->param1].can_speed = (bus_config[req->param1].can_speed / 10U); + can_health[req->param1].can_data_speed = (bus_config[req->param1].can_data_speed / 10U); + can_health[req->param1].canfd_enabled = bus_config[req->param1].canfd_enabled; + can_health[req->param1].brs_enabled = bus_config[req->param1].brs_enabled; + can_health[req->param1].canfd_non_iso = bus_config[req->param1].canfd_non_iso; + resp_len = sizeof(can_health[req->param1]); + (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) { + (void)memcpy(resp, (uint8_t *)DEVICE_SERIAL_NUMBER_ADDRESS, 0x10); + resp_len = 0x10; + } else { + get_provision_chunk(resp); + resp_len = PROVISION_CHUNK_LEN; + } + break; + // **** 0xd1: enter bootloader mode + case 0xd1: + // this allows reflashing of the bootstub + switch (req->param1) { + case 0: + // only allow bootloader entry on debug builds + #ifdef ALLOW_DEBUG + print("-> entering bootloader\n"); + enter_bootloader_mode = ENTER_BOOTLOADER_MAGIC; + NVIC_SystemReset(); + #endif + break; + case 1: + print("-> entering softloader\n"); + enter_bootloader_mode = ENTER_SOFTLOADER_MAGIC; + NVIC_SystemReset(); + break; + default: + print("Bootloader mode invalid\n"); + break; + } + break; + // **** 0xd2: get health packet + case 0xd2: + resp_len = get_jungle_health_pkt(resp); + break; + // **** 0xd3: get first 64 bytes of signature + case 0xd3: + { + resp_len = 64; + char * code = (char*)_app_start; + int code_len = _app_start[0]; + (void)memcpy(resp, &code[code_len], resp_len); + } + break; + // **** 0xd4: get second 64 bytes of signature + case 0xd4: + { + resp_len = 64; + char * code = (char*)_app_start; + int code_len = _app_start[0]; + (void)memcpy(resp, &code[code_len + 64], resp_len); + } + break; + // **** 0xd6: get version + case 0xd6: + COMPILE_TIME_ASSERT(sizeof(gitversion) <= USBPACKET_MAX_SIZE); + (void)memcpy(resp, gitversion, sizeof(gitversion)); + resp_len = sizeof(gitversion) - 1U; + break; + // **** 0xd8: reset ST + case 0xd8: + NVIC_SystemReset(); + break; + // **** 0xdb: set OBD CAN multiplexing mode + case 0xdb: + if (req->param1 == 1U) { + // Enable OBD CAN + current_board->set_can_mode(CAN_MODE_OBD_CAN2); + } else { + // Disable OBD CAN + current_board->set_can_mode(CAN_MODE_NORMAL); + } + break; + // **** 0xdd: get healthpacket and CANPacket versions + case 0xdd: + resp[0] = JUNGLE_HEALTH_PACKET_VERSION; + resp[1] = CAN_PACKET_VERSION; + resp[2] = CAN_HEALTH_PACKET_VERSION; + resp_len = 3; + break; + // **** 0xde: set can bitrate + case 0xde: + 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); + } + break; + // **** 0xe0: debug read + case 0xe0: + // read + while ((resp_len < MIN(req->length, USBPACKET_MAX_SIZE)) && getc(get_ring_by_number(0), (char*)&resp[resp_len])) { + ++resp_len; + } + break; + // **** 0xe5: set CAN loopback (for testing) + case 0xe5: + can_loopback = (req->param1 > 0U); + can_init_all(); + break; + // **** 0xf1: Clear CAN ring buffer. + case 0xf1: + if (req->param1 == 0xFFFFU) { + print("Clearing CAN Rx queue\n"); + can_clear(&can_rx_q); + } else if (req->param1 < PANDA_BUS_CNT) { + print("Clearing CAN Tx queue\n"); + can_clear(can_queues[req->param1]); + } else { + print("Clearing CAN CAN ring buffer failed: wrong bus number\n"); + } + break; + // **** 0xf2: Clear debug ring buffer. + case 0xf2: + print("Clearing debug queue.\n"); + clear_uart_buff(get_ring_by_number(0)); + break; + // **** 0xf4: Set CAN transceiver enable pin + case 0xf4: + current_board->enable_can_transciever(req->param1, req->param2 > 0U); + break; + // **** 0xf5: Set CAN silent mode + case 0xf5: + can_silent = (req->param1 > 0U) ? ALL_CAN_SILENT : ALL_CAN_LIVE; + can_init_all(); + break; + // **** 0xf7: enable/disable header pin by number + case 0xf7: + current_board->enable_header_pin(req->param1, req->param2 > 0U); + break; + // **** 0xf9: set CAN FD data bitrate + case 0xf9: + 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; + bus_config[req->param1].canfd_enabled = (req->param2 >= bus_config[req->param1].can_speed); + bus_config[req->param1].brs_enabled = (req->param2 > bus_config[req->param1].can_speed); + bool ret = can_init(CAN_NUM_FROM_BUS_NUM(req->param1)); + UNUSED(ret); + } + break; + // **** 0xfc: set CAN FD non-ISO mode + case 0xfc: + 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: + print("NO HANDLER "); + puth(req->request); + print("\n"); + break; + } + return resp_len; +} diff --git a/board/jungle/recover.py b/board/jungle/recover.py new file mode 100755 index 0000000000..98afb06748 --- /dev/null +++ b/board/jungle/recover.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +import os +import time +import subprocess + +from panda import PandaJungle, PandaJungleDFU + +board_path = os.path.dirname(os.path.realpath(__file__)) + +if __name__ == "__main__": + subprocess.check_call(f"scons -C {board_path}/.. -u -j$(nproc) {board_path}", shell=True) + + for s in PandaJungle.list(): + print("putting", s, "in DFU mode") + with PandaJungle(serial=s) as p: + p.reset(enter_bootstub=True) + p.reset(enter_bootloader=True) + + # wait for reset panda jungles to come back up + time.sleep(1) + + dfu_serials = PandaJungleDFU.list() + print(f"found {len(dfu_serials)} panda jungle(s) in DFU - {dfu_serials}") + for s in dfu_serials: + print("flashing", s) + PandaJungleDFU(s).recover() diff --git a/board/jungle/scripts/can_health.py b/board/jungle/scripts/can_health.py new file mode 100755 index 0000000000..ff068b5baa --- /dev/null +++ b/board/jungle/scripts/can_health.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 + +import time +from panda import PandaJungle + +if __name__ == "__main__": + jungle = PandaJungle() + + while True: + for bus in range(3): + print(bus, jungle.can_health(bus)) + print() + time.sleep(1) diff --git a/board/jungle/scripts/can_printer.py b/board/jungle/scripts/can_printer.py new file mode 100755 index 0000000000..675fc508a1 --- /dev/null +++ b/board/jungle/scripts/can_printer.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +import os +import time +from collections import defaultdict +import binascii + +from panda import PandaJungle + +# fake +def sec_since_boot(): + return time.time() + +def can_printer(): + p = PandaJungle() + + start = sec_since_boot() + lp = sec_since_boot() + msgs = defaultdict(list) + canbus = int(os.getenv("CAN", "0")) + while True: + can_recv = p.can_recv() + for address, _, dat, src in can_recv: + if src == canbus: + msgs[address].append(dat) + + if sec_since_boot() - lp > 0.1: + dd = chr(27) + "[2J" + dd += "%5.2f\n" % (sec_since_boot() - start) + for k,v in sorted(zip(list(msgs.keys()), [binascii.hexlify(x[-1]) for x in list(msgs.values())], strict=True)): + dd += "%s(%6d) %s\n" % ("%04X(%4d)" % (k,k),len(msgs[k]), v) + print(dd) + lp = sec_since_boot() + +if __name__ == "__main__": + can_printer() diff --git a/board/jungle/scripts/debug_console.py b/board/jungle/scripts/debug_console.py new file mode 100755 index 0000000000..f35388796f --- /dev/null +++ b/board/jungle/scripts/debug_console.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +import os +import sys +import time + +from panda import PandaJungle + +setcolor = ["\033[1;32;40m", "\033[1;31;40m"] +unsetcolor = "\033[00m" + +if __name__ == "__main__": + while True: + try: + claim = os.getenv("CLAIM") is not None + + serials = PandaJungle.list() + if os.getenv("SERIAL"): + serials = [x for x in serials if x==os.getenv("SERIAL")] + + panda_jungles = [PandaJungle(x, claim=claim) for x in serials] + + if not len(panda_jungles): + sys.exit("no panda jungles found") + + while True: + for i, panda_jungle in enumerate(panda_jungles): + while True: + ret = panda_jungle.debug_read() + if len(ret) > 0: + sys.stdout.write(setcolor[i] + ret.decode('ascii') + unsetcolor) + sys.stdout.flush() + else: + break + time.sleep(0.01) + except Exception as e: + print(e) + print("panda jungle disconnected!") + time.sleep(0.5) diff --git a/board/jungle/scripts/echo_loopback_test.py b/board/jungle/scripts/echo_loopback_test.py new file mode 100755 index 0000000000..78b65b5341 --- /dev/null +++ b/board/jungle/scripts/echo_loopback_test.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +import os +import time +import contextlib +import random +from termcolor import cprint + +from panda import PandaJungle + +# This script is intended to be used in conjunction with the echo.py test script from panda. +# It sends messages on bus 0, 1, 2 and checks for a reversed response to be sent back. + +################################################################# +############################# UTILS ############################# +################################################################# +def print_colored(text, color): + cprint(text + " "*40, color, end="\r") + +def get_test_string(): + return b"test"+os.urandom(4) + +################################################################# +############################# TEST ############################## +################################################################# + +def test_loopback(): + for bus in range(3): + # Clear can + jungle.can_clear(bus) + # Send a random message + address = random.randint(1, 2000) + data = get_test_string() + jungle.can_send(address, data, bus) + time.sleep(0.1) + + # Make sure it comes back reversed + incoming = jungle.can_recv() + found = False + for message in incoming: + incomingAddress, _, incomingData, incomingBus = message + if incomingAddress == address and incomingData == data[::-1] and incomingBus == bus: + found = True + break + if not found: + cprint("\nFAILED", "red") + raise AssertionError + +################################################################# +############################# MAIN ############################## +################################################################# +jungle = None +counter = 0 + +if __name__ == "__main__": + # Connect to jungle silently + print_colored("Connecting to jungle", "blue") + with open(os.devnull, "w") as devnull: + with contextlib.redirect_stdout(devnull): + jungle = PandaJungle() + jungle.set_panda_power(True) + jungle.set_ignition(False) + + # Run test + while True: + jungle.can_clear(0xFFFF) + test_loopback() + counter += 1 + print_colored(str(counter) + " loopback cycles complete", "blue") diff --git a/board/jungle/scripts/get_version.py b/board/jungle/scripts/get_version.py new file mode 100755 index 0000000000..ad4a1c4264 --- /dev/null +++ b/board/jungle/scripts/get_version.py @@ -0,0 +1,9 @@ +#!/usr/bin/env python3 +from panda import PandaJungle + +if __name__ == "__main__": + for p in PandaJungle.list(): + pp = PandaJungle(p) + print("%s: %s" % (pp.get_serial()[0], pp.get_version())) + + diff --git a/board/jungle/scripts/health_test.py b/board/jungle/scripts/health_test.py new file mode 100755 index 0000000000..039f840e0b --- /dev/null +++ b/board/jungle/scripts/health_test.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +import time +from pprint import pprint + +from panda import PandaJungle + +if __name__ == "__main__": + i = 0 + pi = 0 + + pj = PandaJungle() + while True: + st = time.monotonic() + while time.monotonic() - st < 1: + pj.health() + pj.can_health(0) + i += 1 + pprint(pj.health()) + print(f"Speed: {i - pi}Hz") + pi = i + diff --git a/board/jungle/scripts/loopback_test.py b/board/jungle/scripts/loopback_test.py new file mode 100755 index 0000000000..10caf42cc4 --- /dev/null +++ b/board/jungle/scripts/loopback_test.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python3 +import os +import time +import contextlib +import random +from termcolor import cprint + +from panda import Panda, PandaJungle + +NUM_PANDAS_PER_TEST = 1 +FOR_RELEASE_BUILDS = os.getenv("RELEASE") is not None # Release builds do not have ALLOUTPUT mode + +BUS_SPEEDS = [125, 500, 1000] + +################################################################# +############################# UTILS ############################# +################################################################# +# To suppress the connection text +def silent_panda_connect(serial): + with open(os.devnull, "w") as devnull: + with contextlib.redirect_stdout(devnull): + panda = Panda(serial) + return panda + +def print_colored(text, color): + cprint(text + " "*40, color, end="\r") + +def connect_to_pandas(): + print_colored("Connecting to pandas", "blue") + # Connect to pandas + pandas = [] + for serial in panda_serials: + pandas.append(silent_panda_connect(serial)) + print_colored("Connected", "blue") + +def start_with_orientation(orientation): + print_colored("Restarting pandas with orientation " + str(orientation), "blue") + jungle.set_panda_power(False) + jungle.set_harness_orientation(orientation) + time.sleep(4) + jungle.set_panda_power(True) + time.sleep(2) + connect_to_pandas() + +def can_loopback(sender): + receivers = list(filter((lambda p: (p != sender)), [jungle] + pandas)) + + for bus in range(4): + obd = False + if bus == 3: + obd = True + bus = 1 + + # Clear buses + for receiver in receivers: + receiver.set_obd(obd) + receiver.can_clear(bus) # TX + receiver.can_clear(0xFFFF) # RX + + # Send a random string + addr = 0x18DB33F1 if FOR_RELEASE_BUILDS else random.randint(1, 2000) + string = b"test"+os.urandom(4) + sender.set_obd(obd) + time.sleep(0.2) + sender.can_send(addr, string, bus) + time.sleep(0.2) + + # Check if all receivers have indeed received them in their receiving buffers + for receiver in receivers: + content = receiver.can_recv() + + # Check amount of messages + if len(content) != 1: + raise Exception("Amount of received CAN messages (" + str(len(content)) + ") does not equal 1. Bus: " + str(bus) +" OBD: " + str(obd)) + + # Check content + if content[0][0] != addr or content[0][2] != string: + raise Exception("Received CAN message content or address does not match") + + # Check bus + if content[0][3] != bus: + raise Exception("Received CAN message bus does not match") + +################################################################# +############################# TEST ############################## +################################################################# + +def test_loopback(): + # disable safety modes + for panda in pandas: + panda.set_safety_mode(Panda.SAFETY_ELM327 if FOR_RELEASE_BUILDS else Panda.SAFETY_ALLOUTPUT) + + # perform loopback with jungle as a sender + can_loopback(jungle) + + # perform loopback with each possible panda as a sender + for panda in pandas: + can_loopback(panda) + + # enable safety modes + for panda in pandas: + panda.set_safety_mode(Panda.SAFETY_SILENT) + +################################################################# +############################# MAIN ############################## +################################################################# +jungle = None +pandas = [] # type: ignore +panda_serials = [] +counter = 0 + +if __name__ == "__main__": + # Connect to jungle silently + print_colored("Connecting to jungle", "blue") + with open(os.devnull, "w") as devnull: + with contextlib.redirect_stdout(devnull): + jungle = PandaJungle() + jungle.set_panda_power(True) + jungle.set_ignition(False) + + # Connect to new pandas before starting tests + print_colored("Waiting for " + str(NUM_PANDAS_PER_TEST) + " pandas to be connected", "yellow") + while True: + connected_serials = Panda.list() + if len(connected_serials) == NUM_PANDAS_PER_TEST: + panda_serials = connected_serials + break + + start_with_orientation(PandaJungle.HARNESS_ORIENTATION_1) + + # Set bus speeds + for device in pandas + [jungle]: + for bus in range(len(BUS_SPEEDS)): + device.set_can_speed_kbps(bus, BUS_SPEEDS[bus]) + + # Run test + while True: + test_loopback() + counter += 1 + print_colored(str(counter) + " loopback cycles complete", "blue") diff --git a/board/jungle/scripts/panda_identification_test.py b/board/jungle/scripts/panda_identification_test.py new file mode 100755 index 0000000000..a61b0d608b --- /dev/null +++ b/board/jungle/scripts/panda_identification_test.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 +import os +import time +import random +import contextlib + +from panda import PandaJungle +from panda import Panda + +PANDA_UNDER_TEST = Panda.HW_TYPE_UNO + +panda_jungle = PandaJungle() + +def silent_panda_connect(): + with open(os.devnull, "w") as devnull: + with contextlib.redirect_stdout(devnull): + panda = Panda() + return panda + +def reboot_panda(harness_orientation=PandaJungle.HARNESS_ORIENTATION_NONE, ignition=False): + print(f"Restarting panda with harness orientation: {harness_orientation} and ignition: {ignition}") + panda_jungle.set_panda_power(False) + panda_jungle.set_harness_orientation(harness_orientation) + panda_jungle.set_ignition(ignition) + time.sleep(2) + panda_jungle.set_panda_power(True) + time.sleep(2) + +count = 0 +if __name__ == "__main__": + while True: + ignition = random.randint(0, 1) + harness_orientation = random.randint(0, 2) + reboot_panda(harness_orientation, ignition) + + p = silent_panda_connect() + assert p.get_type() == PANDA_UNDER_TEST + assert p.health()['car_harness_status'] == harness_orientation + if harness_orientation != PandaJungle.HARNESS_ORIENTATION_NONE: + assert p.health()['ignition_line'] == ignition + + count += 1 + print(f"Passed {count} loops") + + diff --git a/board/jungle/scripts/start.py b/board/jungle/scripts/start.py new file mode 100755 index 0000000000..76afd14ac4 --- /dev/null +++ b/board/jungle/scripts/start.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python +import sys + +from panda import PandaJungle + +if __name__ == "__main__": + jungle = PandaJungle() + + # If first argument specified, it sets the ignition (0 or 1) + if len(sys.argv) > 1: + if sys.argv[1] == '1': + jungle.set_ignition(True) + else: + jungle.set_ignition(False) + jungle.set_harness_orientation(1) + jungle.set_panda_power(True) + + diff --git a/board/jungle/stm32fx/board.h b/board/jungle/stm32fx/board.h new file mode 100644 index 0000000000..0adf7923b2 --- /dev/null +++ b/board/jungle/stm32fx/board.h @@ -0,0 +1,7 @@ +#include "boards/board_declarations.h" +#include "boards/board_v1.h" + +void detect_board_type(void) { + hw_type = HW_TYPE_V1; + current_board = &board_v1; +} diff --git a/board/jungle/stm32h7/board.h b/board/jungle/stm32h7/board.h new file mode 100644 index 0000000000..4fe4fa4637 --- /dev/null +++ b/board/jungle/stm32h7/board.h @@ -0,0 +1,9 @@ +#include "boards/board_declarations.h" + +#include "stm32h7/lladc.h" +#include "boards/board_v2.h" + +void detect_board_type(void) { + hw_type = HW_TYPE_V2; + current_board = &board_v2; +} diff --git a/board/jungle/stm32h7/lladc.h b/board/jungle/stm32h7/lladc.h new file mode 100644 index 0000000000..06b742dd38 --- /dev/null +++ b/board/jungle/stm32h7/lladc.h @@ -0,0 +1,54 @@ + +typedef struct { + ADC_TypeDef *adc; + uint8_t channel; +} adc_channel_t; + +void adc_init(ADC_TypeDef *adc) { + adc->CR &= ~(ADC_CR_DEEPPWD); // Reset deep-power-down mode + adc->CR |= ADC_CR_ADVREGEN; // Enable ADC regulator + while(!(adc->ISR & ADC_ISR_LDORDY) && (adc != ADC3)); + + if (adc != ADC3) { + adc->CR &= ~(ADC_CR_ADCALDIF); // Choose single-ended calibration + adc->CR |= ADC_CR_ADCALLIN; // Lineriality calibration + } + adc->CR |= ADC_CR_ADCAL; // Start calibrtation + while((adc->CR & ADC_CR_ADCAL) != 0); + + adc->ISR |= ADC_ISR_ADRDY; + adc->CR |= ADC_CR_ADEN; + while(!(adc->ISR & ADC_ISR_ADRDY)); +} + +uint16_t adc_get_raw(ADC_TypeDef *adc, uint8_t channel) { + adc->SQR1 &= ~(ADC_SQR1_L); + adc->SQR1 = ((uint32_t) channel << 6U); + + if (channel < 10U) { + adc->SMPR1 = (0x7U << (channel * 3U)); + } else { + adc->SMPR2 = (0x7U << ((channel - 10U) * 3U)); + } + adc->PCSEL_RES0 = (0x1U << channel); + + adc->CR |= ADC_CR_ADSTART; + while (!(adc->ISR & ADC_ISR_EOC)); + + uint16_t res = adc->DR; + + while (!(adc->ISR & ADC_ISR_EOS)); + adc->ISR |= ADC_ISR_EOS; + + return res; +} + +uint16_t adc_get_mV(ADC_TypeDef *adc, uint8_t channel) { + uint16_t ret = 0; + if ((adc == ADC1) || (adc == ADC2)) { + ret = (adc_get_raw(adc, channel) * current_board->avdd_mV) / 65535U; + } else if (adc == ADC3) { + ret = (adc_get_raw(adc, channel) * current_board->avdd_mV) / 4095U; + } else {} + return ret; +} diff --git a/board/main.c b/board/main.c index 5fc8207fca..56abbed527 100644 --- a/board/main.c +++ b/board/main.c @@ -6,11 +6,11 @@ #include "drivers/gmlan_alt.h" #include "drivers/kline_init.h" #include "drivers/simple_watchdog.h" +#include "drivers/logging.h" #include "early_init.h" #include "provision.h" -#include "power_saving.h" #include "safety.h" #include "health.h" @@ -23,6 +23,8 @@ #include "drivers/bxcan.h" #endif +#include "power_saving.h" + #include "obj/gitversion.h" #include "can_comms.h" @@ -80,21 +82,21 @@ void set_safety_mode(uint16_t mode, uint16_t param) { switch (mode_copy) { case SAFETY_SILENT: - set_intercept_relay(false); + set_intercept_relay(false, false); if (current_board->has_obd) { current_board->set_can_mode(CAN_MODE_NORMAL); } can_silent = ALL_CAN_SILENT; break; case SAFETY_NOOUTPUT: - set_intercept_relay(false); + set_intercept_relay(false, false); if (current_board->has_obd) { current_board->set_can_mode(CAN_MODE_NORMAL); } can_silent = ALL_CAN_LIVE; break; case SAFETY_ELM327: - set_intercept_relay(false); + set_intercept_relay(false, false); heartbeat_counter = 0U; heartbeat_lost = false; if (current_board->has_obd) { @@ -107,7 +109,7 @@ void set_safety_mode(uint16_t mode, uint16_t param) { can_silent = ALL_CAN_LIVE; break; default: - set_intercept_relay(true); + set_intercept_relay(true, false); heartbeat_counter = 0U; heartbeat_lost = false; if (current_board->has_obd) { @@ -144,6 +146,9 @@ void __attribute__ ((noinline)) enable_fpu(void) { // called at 8Hz uint8_t loop_counter = 0U; +uint8_t previous_harness_status = HARNESS_STATUS_NC; +uint32_t waiting_to_boot_count = 0; +bool waiting_to_boot = false; void tick_handler(void) { if (TICK_TIMER->SR != 0) { // siren @@ -152,6 +157,7 @@ void tick_handler(void) { // tick drivers at 8Hz fan_tick(); usb_tick(); + harness_tick(); simple_watchdog_kick(); // decimated to 1Hz @@ -180,8 +186,30 @@ void tick_handler(void) { current_board->set_led(LED_BLUE, (uptime_cnt & 1U) && (power_save_status == POWER_SAVE_STATUS_ENABLED)); // tick drivers at 1Hz + logging_tick(); + const bool recent_heartbeat = heartbeat_counter == 0U; - current_board->board_tick(check_started(), usb_enumerated, recent_heartbeat); + const bool harness_inserted = (harness.status != previous_harness_status) && (harness.status != HARNESS_STATUS_NC); + const bool just_bootkicked = current_board->board_tick(check_started(), usb_enumerated, recent_heartbeat, harness_inserted); + previous_harness_status = harness.status; + + // log device boot time + const bool som_running = current_board->read_som_gpio(); + if (just_bootkicked && !som_running) { + log("bootkick"); + waiting_to_boot = true; + } + if (waiting_to_boot) { + if (som_running) { + log("device booted"); + waiting_to_boot = false; + } else if (waiting_to_boot_count == 10U) { + log("not booted after 10s"); + } else { + + } + waiting_to_boot_count += 1U; + } // increase heartbeat counter and cap it at the uint32 limit if (heartbeat_counter < __UINT32_MAX__) { @@ -232,6 +260,9 @@ void tick_handler(void) { heartbeat_lost = true; } + // clear heartbeat engaged state + heartbeat_engaged = false; + if (current_safety_mode != SAFETY_SILENT) { set_safety_mode(SAFETY_SILENT, 0U); } @@ -243,14 +274,11 @@ void tick_handler(void) { // Also disable IR when the heartbeat goes missing current_board->set_ir_power(0U); - // 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); - } + // Run fan when device is up, but not talking to us + // * bootloader enables the SOM GPIO on boot + // * fallback to USB enumerated where supported + bool enabled = usb_enumerated || current_board->read_som_gpio(); + fan_set_power(enabled ? 50U : 0U); } } @@ -321,6 +349,7 @@ int main(void) { peripherals_init(); detect_board_type(); adc_init(); + logging_init(); // print hello print("\n\n\n************************ MAIN START ************************\n"); @@ -340,12 +369,7 @@ int main(void) { // panda has an FPU, let's use it! enable_fpu(); - if (current_board->has_gps) { - uart_init(&uart_ring_gps, 9600); - } else { - // enable ESP uart - uart_init(&uart_ring_gps, 115200); - } + log("main start"); if (current_board->has_lin) { // enable LIN @@ -367,8 +391,8 @@ int main(void) { // enable CAN TXs current_board->enable_can_transceivers(true); - // init watchdog for heartbeat loop, trigger after 4 8Hz cycles - simple_watchdog_init(FAULT_HEARTBEAT_LOOP_WATCHDOG, (4U * 1000000U / 8U)); + // init watchdog for heartbeat loop, fed at 8Hz + simple_watchdog_init(FAULT_HEARTBEAT_LOOP_WATCHDOG, (3U * 1000000U / 8U)); // 8Hz timer REGISTER_INTERRUPT(TICK_TIMER_IRQ, tick_handler, 10U, FAULT_INTERRUPT_RATE_TICK) diff --git a/board/main_comms.h b/board/main_comms.h index 3ccb07cd26..faa18b849a 100644 --- a/board/main_comms.h +++ b/board/main_comms.h @@ -9,10 +9,10 @@ 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(current_board->adc_scale); + health->voltage_pkt = adc_get_mV(ADCCHAN_VIN) * VIN_READOUT_DIVIDER; health->current_pkt = current_board->read_current(); - //Use the GPIO pin to determine ignition or use a CAN based logic + // Use the GPIO pin to determine ignition or use a CAN based logic health->ignition_line_pkt = (uint8_t)(current_board->check_ignition()); health->ignition_can_pkt = (uint8_t)(ignition_can); @@ -23,7 +23,7 @@ int get_health_pkt(void *dat) { health->tx_buffer_overflow_pkt = tx_buffer_overflow; health->rx_buffer_overflow_pkt = rx_buffer_overflow; health->gmlan_send_errs_pkt = gmlan_send_errs; - health->car_harness_status_pkt = car_harness_status; + health->car_harness_status_pkt = harness.status; health->safety_mode_pkt = (uint8_t)(current_safety_mode); health->safety_param_pkt = current_safety_param; health->alternative_experience_pkt = alternative_experience; @@ -41,6 +41,9 @@ int get_health_pkt(void *dat) { health->fan_power = fan_state.power; health->fan_stall_count = fan_state.total_stall_count; + health->sbu1_voltage_mV = harness.sbu1_voltage_mV; + health->sbu2_voltage_mV = harness.sbu2_voltage_mV; + return sizeof(*health); } @@ -164,6 +167,7 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { case 0xc2: COMPILE_TIME_ASSERT(sizeof(can_health_t) <= USBPACKET_MAX_SIZE); if (req->param1 < 3U) { + update_can_health_pkt(req->param1, 0U); can_health[req->param1].can_speed = (bus_config[req->param1].can_speed / 10U); can_health[req->param1].can_data_speed = (bus_config[req->param1].can_data_speed / 10U); can_health[req->param1].canfd_enabled = bus_config[req->param1].canfd_enabled; @@ -178,6 +182,21 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { (void)memcpy(resp, ((uint8_t *)UID_BASE), 12); resp_len = 12; break; + // **** 0xc4: get interrupt call rate + case 0xc4: + if (req->param1 < NUM_INTERRUPTS) { + uint32_t load = interrupts[req->param1].call_rate; + resp[0] = (load & 0x000000FFU); + resp[1] = ((load & 0x0000FF00U) >> 8U); + resp[2] = ((load & 0x00FF0000U) >> 16U); + resp[3] = ((load & 0xFF000000U) >> 24U); + resp_len = 4U; + } + break; + // **** 0xc5: DEBUG: drive relay + case 0xc5: + set_intercept_relay((req->param1 & 0x1U), (req->param1 & 0x2U)); + break; // **** 0xd0: fetch serial (aka the provisioned dongle ID) case 0xd0: // addresses are OTP @@ -243,28 +262,6 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { case 0xd8: NVIC_SystemReset(); break; - // **** 0xd9: set ESP power - case 0xd9: - if (req->param1 == 1U) { - current_board->set_gps_mode(GPS_ENABLED); - } else if (req->param1 == 2U) { - current_board->set_gps_mode(GPS_BOOTMODE); - } else { - current_board->set_gps_mode(GPS_DISABLED); - } - break; - // **** 0xda: reset ESP, with optional boot mode - case 0xda: - current_board->set_gps_mode(GPS_DISABLED); - delay(1000000); - if (req->param1 == 1U) { - current_board->set_gps_mode(GPS_BOOTMODE); - } else { - current_board->set_gps_mode(GPS_ENABLED); - } - delay(1000000); - current_board->set_gps_mode(GPS_ENABLED); - break; // **** 0xdb: set GMLAN (white/grey) or OBD CAN (black) multiplexing mode case 0xdb: if(current_board->has_obd){ @@ -324,11 +321,6 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { break; } - // TODO: Remove this again and fix boardd code to hande the message bursts instead of single chars - if (ur == &uart_ring_gps) { - dma_pointer_handler(ur, DMA2_Stream5->NDTR); - } - // read while ((resp_len < MIN(req->length, USBPACKET_MAX_SIZE)) && getc(ur, (char*)&resp[resp_len])) { @@ -381,6 +373,10 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { can_loopback = (req->param1 > 0U); can_init_all(); break; + // **** 0xe6: set custom clock source period + case 0xe6: + clock_source_set_period(req->param1); + break; // **** 0xe7: set power save state case 0xe7: set_power_save_state(req->param1); @@ -475,6 +471,18 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { UNUSED(ret); } break; + // *** 0xfd: read logs + case 0xfd: + if (req->param1 == 1U) { + logging_init_read_index(); + } + + if (req->param2 != 0xFFFFU) { + logging_find_read_index(req->param2); + } + + resp_len = logging_read(resp); + break; default: print("NO HANDLER "); puth(req->request); diff --git a/board/pedal/README b/board/pedal/README.md similarity index 100% rename from board/pedal/README rename to board/pedal/README.md diff --git a/board/pedal/SConscript b/board/pedal/SConscript new file mode 100644 index 0000000000..bfa3c2c19d --- /dev/null +++ b/board/pedal/SConscript @@ -0,0 +1,28 @@ +import copy + +Import('build_project') + +build_projects = {} + +build_projects["pedal"] = { + "MAIN": "main.c", + "BOOTSTUB": "../bootstub.c", + "STARTUP_FILE": "../stm32fx/startup_stm32f205xx.s", + "LINKER_SCRIPT": "../stm32fx/stm32f2_flash.ld", + "APP_START_ADDRESS": "0x8004000", + "PROJECT_FLAGS": [ + "-mcpu=cortex-m3", + "-msoft-float", + "-DSTM32F2", + "-DSTM32F205xx", + "-O2", + "-DPEDAL", + ], +} + +# build with the USB driver enabled +build_projects["pedal_usb"] = copy.deepcopy(build_projects["pedal"]) +build_projects["pedal_usb"]["PROJECT_FLAGS"].append("-DPEDAL_USB") + +for project_name, project in build_projects.items(): + build_project(project_name, project, []) diff --git a/board/pedal/flash_can.sh b/board/pedal/flash_can.sh index 0225bf8dff..b9edf25f12 100755 --- a/board/pedal/flash_can.sh +++ b/board/pedal/flash_can.sh @@ -2,7 +2,7 @@ set -e cd .. -PEDAL=1 scons -u -j$(nproc) +scons -u -j$(nproc) cd pedal -../../tests/pedal/enter_canloader.py ../obj/pedal.bin.signed +../../tests/pedal/enter_canloader.py obj/pedal.bin.signed diff --git a/board/pedal/main.c b/board/pedal/main.c index 71df7fe4e2..eb40466106 100644 --- a/board/pedal/main.c +++ b/board/pedal/main.c @@ -54,7 +54,7 @@ void comms_endpoint2_write(uint8_t *data, uint32_t len) { UNUSED(data); UNUSED(len); } -void usb_cb_ep3_out_complete(void) {} +void refresh_can_tx_slots_available(void) {} int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { unsigned int resp_len = 0; @@ -244,8 +244,8 @@ void TIM3_IRQ_Handler(void) { void pedal(void) { // read/write - pdl0 = adc_get(ADCCHAN_ACCEL0); - pdl1 = adc_get(ADCCHAN_ACCEL1); + pdl0 = adc_get_raw(ADCCHAN_ACCEL0); + pdl1 = adc_get_raw(ADCCHAN_ACCEL1); // write the pedal to the DAC if (state == NO_FAULT) { @@ -302,7 +302,7 @@ int main(void) { timer_init(TIM3, 15); NVIC_EnableIRQ(TIM3_IRQn); - watchdog_init(); + watchdog_init(WATCHDOG_50_MS); print("**** INTERRUPTS ON ****\n"); enable_interrupts(); diff --git a/board/pedal/recover.sh b/board/pedal/recover.sh index 98c5e19862..d7fe0aff5f 100755 --- a/board/pedal/recover.sh +++ b/board/pedal/recover.sh @@ -4,8 +4,8 @@ set -e DFU_UTIL="dfu-util" cd .. -PEDAL=1 scons -u -j$(nproc) +scons -u -j$(nproc) cd pedal -$DFU_UTIL -d 0483:df11 -a 0 -s 0x08004000 -D ../obj/pedal.bin.signed -$DFU_UTIL -d 0483:df11 -a 0 -s 0x08000000:leave -D ../obj/bootstub.pedal.bin +$DFU_UTIL -d 0483:df11 -a 0 -s 0x08004000 -D obj/pedal.bin.signed +$DFU_UTIL -d 0483:df11 -a 0 -s 0x08000000:leave -D obj/bootstub.pedal.bin diff --git a/board/power_saving.h b/board/power_saving.h index cc271e5da5..2b7d7c9872 100644 --- a/board/power_saving.h +++ b/board/power_saving.h @@ -13,30 +13,29 @@ void set_power_save_state(int state) { bool enable = false; if (state == POWER_SAVE_STATUS_ENABLED) { 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])); + + // Disable CAN interrupts + if (harness.status == HARNESS_STATUS_FLIPPED) { + llcan_irq_disable(cans[0]); + } else { + llcan_irq_disable(cans[2]); } + llcan_irq_disable(cans[1]); } else { 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); - for (unsigned int i = 0; i < sizeof(UBLOX_WAKE_MSG) - 1U; i++) while (!putc(ur, UBLOX_WAKE_MSG[i])); + + if (harness.status == HARNESS_STATUS_FLIPPED) { + llcan_irq_enable(cans[0]); + } else { + llcan_irq_enable(cans[2]); } + llcan_irq_enable(cans[1]); + enable = true; } current_board->enable_can_transceivers(enable); - // Switch EPS/GPS - if (enable) { - current_board->set_gps_mode(GPS_ENABLED); - } else { - current_board->set_gps_mode(GPS_DISABLED); - } - if(current_board->has_hw_gmlan){ // turn on GMLAN set_gpio_output(GPIOB, 14, enable); diff --git a/board/safety.h b/board/safety.h index 80f7181c42..4f9a28ec61 100644 --- a/board/safety.h +++ b/board/safety.h @@ -11,7 +11,7 @@ #include "safety/safety_hyundai.h" #include "safety/safety_chrysler.h" #include "safety/safety_subaru.h" -#include "safety/safety_subaru_legacy.h" +#include "safety/safety_subaru_preglobal.h" #include "safety/safety_mazda.h" #include "safety/safety_nissan.h" #include "safety/safety_volkswagen_mqb.h" @@ -44,7 +44,7 @@ #define SAFETY_NOOUTPUT 19U #define SAFETY_HONDA_BOSCH 20U #define SAFETY_VOLKSWAGEN_PQ 21U -#define SAFETY_SUBARU_LEGACY 22U +#define SAFETY_SUBARU_PREGLOBAL 22U #define SAFETY_HYUNDAI_LEGACY 23U #define SAFETY_HYUNDAI_COMMUNITY 24U #define SAFETY_STELLANTIS 25U @@ -139,7 +139,7 @@ int get_addr_check_index(CANPacket_t *to_push, AddrCheckStruct addr_list[], cons for (int i = 0; i < len; i++) { // if multiple msgs are allowed, determine which one is present on the bus if (!addr_list[i].msg_seen) { - for (uint8_t j = 0U; addr_list[i].msg[j].addr != 0; j++) { + for (uint8_t j = 0U; (j < MAX_ADDR_CHECK_MSGS) && (addr_list[i].msg[j].addr != 0); j++) { if ((addr == addr_list[i].msg[j].addr) && (bus == addr_list[i].msg[j].bus) && (length == addr_list[i].msg[j].len)) { addr_list[i].index = j; @@ -149,11 +149,13 @@ int get_addr_check_index(CANPacket_t *to_push, AddrCheckStruct addr_list[], cons } } - int idx = addr_list[i].index; - if ((addr == addr_list[i].msg[idx].addr) && (bus == addr_list[i].msg[idx].bus) && - (length == addr_list[i].msg[idx].len)) { - index = i; - break; + if (addr_list[i].msg_seen) { + int idx = addr_list[i].index; + if ((addr == addr_list[i].msg[idx].addr) && (bus == addr_list[i].msg[idx].bus) && + (length == addr_list[i].msg[idx].len)) { + index = i; + break; + } } } return index; @@ -305,15 +307,15 @@ const safety_hook_config safety_hook_registry[] = { {SAFETY_HYUNDAI_LEGACY, &hyundai_legacy_hooks}, {SAFETY_MAZDA, &mazda_hooks}, {SAFETY_BODY, &body_hooks}, + {SAFETY_FORD, &ford_hooks}, #ifdef CANFD {SAFETY_HYUNDAI_CANFD, &hyundai_canfd_hooks}, #endif #ifdef ALLOW_DEBUG {SAFETY_TESLA, &tesla_hooks}, - {SAFETY_SUBARU_LEGACY, &subaru_legacy_hooks}, + {SAFETY_SUBARU_PREGLOBAL, &subaru_preglobal_hooks}, {SAFETY_VOLKSWAGEN_PQ, &volkswagen_pq_hooks}, {SAFETY_ALLOUTPUT, &alloutput_hooks}, - {SAFETY_FORD, &ford_hooks}, #endif }; @@ -330,7 +332,6 @@ int set_safety_hooks(uint16_t mode, uint16_t param) { regen_braking = false; regen_braking_prev = false; cruise_engaged_prev = false; - vehicle_speed = 0; vehicle_moving = false; acc_main_on = false; cruise_button_prev = 0; @@ -343,7 +344,9 @@ int set_safety_hooks(uint16_t mode, uint16_t param) { valid_steer_req_count = 0; invalid_steer_req_count = 0; - torque_meas.max = 0; + vehicle_speed.min = 0; + vehicle_speed.max = 0; + torque_meas.min = 0; torque_meas.max = 0; torque_driver.min = 0; torque_driver.max = 0; @@ -409,7 +412,7 @@ bool max_limit_check(int val, const int MAX_VAL, const int MIN_VAL) { return (val > MAX_VAL) || (val < MIN_VAL); } -// check that commanded value isn't too far from measured +// check that commanded torque value isn't too far from measured bool dist_to_meas_check(int val, int val_last, struct sample_t *val_meas, const int MAX_RATE_UP, const int MAX_RATE_DOWN, const int MAX_ERROR) { @@ -422,7 +425,7 @@ bool dist_to_meas_check(int val, int val_last, struct sample_t *val_meas, int lowest_allowed = MAX(lowest_allowed_rl, MIN(val_last + MAX_RATE_DOWN, MIN(val_meas->min, 0) - MAX_ERROR)); // check for violation - return (val < lowest_allowed) || (val > highest_allowed); + return max_limit_check(val, highest_allowed, lowest_allowed); } // check that commanded value isn't fighting against driver @@ -445,7 +448,7 @@ bool driver_limit_check(int val, int val_last, struct sample_t *val_driver, MIN(driver_min_limit, 0))); // check for violation - return (val < lowest_allowed) || (val > highest_allowed); + return max_limit_check(val, highest_allowed, lowest_allowed); } @@ -457,7 +460,7 @@ bool rt_rate_limit_check(int val, int val_last, const int MAX_RT_DELTA) { int lowest_val = MIN(val_last, 0) - MAX_RT_DELTA; // check for violation - return (val < lowest_val) || (val > highest_val); + return max_limit_check(val, highest_val, lowest_val); } @@ -491,6 +494,10 @@ float interpolate(struct lookup_t xy, float x) { return ret; } +int ROUND(float val) { + return val + ((val > 0.0) ? 0.5 : -0.5); +} + // Safety checks for longitudinal actuation bool longitudinal_accel_checks(int desired_accel, const LongitudinalLimits limits) { bool accel_valid = get_longitudinal_allowed() && !max_limit_check(desired_accel, limits.max_accel, limits.min_accel); @@ -502,14 +509,16 @@ bool longitudinal_speed_checks(int desired_speed, const LongitudinalLimits limit return !get_longitudinal_allowed() && (desired_speed != limits.inactive_speed); } +bool longitudinal_alt_checks(int desired_alt, const LongitudinalLimits limits){ + bool alt_valid = get_longitudinal_allowed() && !max_limit_check(desired_alt, limits.max_alt, limits.min_alt); + bool alt_inactive = desired_alt == limits.inactive_alt; + return !(alt_valid || alt_inactive); +} + 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 gas_valid = get_longitudinal_allowed() && !max_limit_check(desired_gas, limits.max_gas, limits.min_gas); + bool gas_inactive = desired_gas == limits.inactive_gas; + return !(gas_valid || gas_inactive); } bool longitudinal_brake_checks(int desired_brake, const LongitudinalLimits limits) { @@ -618,21 +627,42 @@ bool steer_angle_cmd_checks(int desired_angle, bool steer_control_enabled, const // 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 delta_angle_up = (interpolate(limits.angle_rate_up_lookup, (vehicle_speed.min / VEHICLE_SPEED_FACTOR) - 1.) * limits.angle_deg_to_can) + 1.; + int delta_angle_down = (interpolate(limits.angle_rate_down_lookup, (vehicle_speed.min / VEHICLE_SPEED_FACTOR) - 1.) * limits.angle_deg_to_can) + 1.; + // allow down limits at zero since small floats will be rounded to 0 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 that commanded angle value isn't too far from measured, used to limit torque for some safety modes + // ensure we start moving in direction of meas while respecting rate limits if error is exceeded + if (limits.enforce_angle_error && ((vehicle_speed.values[0] / VEHICLE_SPEED_FACTOR) > limits.angle_error_min_speed)) { + // the rate limits above are liberally above openpilot's to avoid false positives. + // likewise, allow a lower rate for moving towards meas when error is exceeded + int delta_angle_up_lower = interpolate(limits.angle_rate_up_lookup, (vehicle_speed.max / VEHICLE_SPEED_FACTOR) + 1.) * limits.angle_deg_to_can; + int delta_angle_down_lower = interpolate(limits.angle_rate_down_lookup, (vehicle_speed.max / VEHICLE_SPEED_FACTOR) + 1.) * limits.angle_deg_to_can; + + int highest_desired_angle_lower = desired_angle_last + ((desired_angle_last > 0) ? delta_angle_up_lower : delta_angle_down_lower); + int lowest_desired_angle_lower = desired_angle_last - ((desired_angle_last >= 0) ? delta_angle_down_lower : delta_angle_up_lower); + + lowest_desired_angle = MIN(MAX(lowest_desired_angle, angle_meas.min - limits.max_angle_error - 1), highest_desired_angle_lower); + highest_desired_angle = MAX(MIN(highest_desired_angle, angle_meas.max + limits.max_angle_error + 1), lowest_desired_angle_lower); + + // don't enforce above the max steer + lowest_desired_angle = CLAMP(lowest_desired_angle, -limits.max_steer, limits.max_steer); + highest_desired_angle = CLAMP(highest_desired_angle, -limits.max_steer, limits.max_steer); + } + // 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)))); + // Angle should either be 0 or same as current angle while not steering + if (!steer_control_enabled) { + violation |= (limits.inactive_angle_is_zero ? (desired_angle != 0) : + max_limit_check(desired_angle, angle_meas.max + 1, angle_meas.min - 1)); + } // No angle control allowed when controls are not allowed violation |= !controls_allowed && steer_control_enabled; diff --git a/board/safety/safety_body.h b/board/safety/safety_body.h index a4d21ef011..61ebc46dc2 100644 --- a/board/safety/safety_body.h +++ b/board/safety/safety_body.h @@ -31,7 +31,7 @@ static int body_tx_hook(CANPacket_t *to_send) { } // 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)) { + if (!controls_allowed && (GET_BYTES(to_send, 0, 4) == 0xdeadfaceU) && (GET_BYTES(to_send, 4, 4) == 0x0ab00b1eU)) { tx = 1; } diff --git a/board/safety/safety_ford.h b/board/safety/safety_ford.h index a0f218e705..4f71650c90 100644 --- a/board/safety/safety_ford.h +++ b/board/safety/safety_ford.h @@ -1,38 +1,75 @@ // Safety-relevant CAN messages for Ford vehicles. -#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 -#define MSG_LateralMotionControl 0x3D3 // TX by OP, Traffic Jam Assist -#define MSG_IPMA_Data 0x3D8 // TX by OP, IPMA and LKAS user interface +#define FORD_EngBrakeData 0x165 // RX from PCM, for driver brake pedal and cruise state +#define FORD_EngVehicleSpThrottle 0x204 // RX from PCM, for driver throttle input +#define FORD_DesiredTorqBrk 0x213 // RX from ABS, for standstill state +#define FORD_BrakeSysFeatures 0x415 // RX from ABS, for vehicle speed +#define FORD_EngVehicleSpThrottle2 0x202 // RX from PCM, for second vehicle speed +#define FORD_Yaw_Data_FD1 0x91 // RX from RCM, for yaw rate +#define FORD_Steering_Data_FD1 0x083 // TX by OP, various driver switches and LKAS/CC buttons +#define FORD_ACCDATA 0x186 // TX by OP, ACC controls +#define FORD_ACCDATA_3 0x18A // TX by OP, ACC/TJA user interface +#define FORD_Lane_Assist_Data1 0x3CA // TX by OP, Lane Keep Assist +#define FORD_LateralMotionControl 0x3D3 // TX by OP, Lateral Control message +#define FORD_LateralMotionControl2 0x3D6 // TX by OP, alternate Lateral Control message +#define FORD_IPMA_Data 0x3D8 // TX by OP, IPMA and LKAS user interface // CAN bus numbers. #define FORD_MAIN_BUS 0U #define FORD_CAM_BUS 2U -const CanMsg FORD_TX_MSGS[] = { - {MSG_Steering_Data_FD1, 0, 8}, - {MSG_Steering_Data_FD1, 2, 8}, - {MSG_ACCDATA_3, 0, 8}, - {MSG_Lane_Assist_Data1, 0, 8}, - {MSG_LateralMotionControl, 0, 8}, - {MSG_IPMA_Data, 0, 8}, +const CanMsg FORD_STOCK_TX_MSGS[] = { + {FORD_Steering_Data_FD1, 0, 8}, + {FORD_Steering_Data_FD1, 2, 8}, + {FORD_ACCDATA_3, 0, 8}, + {FORD_Lane_Assist_Data1, 0, 8}, + {FORD_LateralMotionControl, 0, 8}, + {FORD_IPMA_Data, 0, 8}, }; -#define FORD_TX_LEN (sizeof(FORD_TX_MSGS) / sizeof(FORD_TX_MSGS[0])) +#define FORD_STOCK_TX_LEN (sizeof(FORD_STOCK_TX_MSGS) / sizeof(FORD_STOCK_TX_MSGS[0])) + +const CanMsg FORD_LONG_TX_MSGS[] = { + {FORD_Steering_Data_FD1, 0, 8}, + {FORD_Steering_Data_FD1, 2, 8}, + {FORD_ACCDATA, 0, 8}, + {FORD_ACCDATA_3, 0, 8}, + {FORD_Lane_Assist_Data1, 0, 8}, + {FORD_LateralMotionControl, 0, 8}, + {FORD_IPMA_Data, 0, 8}, +}; +#define FORD_LONG_TX_LEN (sizeof(FORD_LONG_TX_MSGS) / sizeof(FORD_LONG_TX_MSGS[0])) + +const CanMsg FORD_CANFD_STOCK_TX_MSGS[] = { + {FORD_Steering_Data_FD1, 0, 8}, + {FORD_Steering_Data_FD1, 2, 8}, + {FORD_ACCDATA_3, 0, 8}, + {FORD_Lane_Assist_Data1, 0, 8}, + {FORD_LateralMotionControl2, 0, 8}, + {FORD_IPMA_Data, 0, 8}, +}; +#define FORD_CANFD_STOCK_TX_LEN (sizeof(FORD_CANFD_STOCK_TX_MSGS) / sizeof(FORD_CANFD_STOCK_TX_MSGS[0])) + +const CanMsg FORD_CANFD_LONG_TX_MSGS[] = { + {FORD_Steering_Data_FD1, 0, 8}, + {FORD_Steering_Data_FD1, 2, 8}, + {FORD_ACCDATA, 0, 8}, + {FORD_ACCDATA_3, 0, 8}, + {FORD_Lane_Assist_Data1, 0, 8}, + {FORD_LateralMotionControl2, 0, 8}, + {FORD_IPMA_Data, 0, 8}, +}; +#define FORD_CANFD_LONG_TX_LEN (sizeof(FORD_CANFD_LONG_TX_MSGS) / sizeof(FORD_CANFD_LONG_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 }}}, + {.msg = {{FORD_BrakeSysFeatures, 0, 8, .check_checksum = true, .max_counter = 15U, .quality_flag=true, .expected_timestep = 20000U}, { 0 }, { 0 }}}, + // TODO: FORD_EngVehicleSpThrottle2 has a counter that skips by 2, understand and enable counter check + {.msg = {{FORD_EngVehicleSpThrottle2, 0, 8, .check_checksum = true, .quality_flag=true, .expected_timestep = 20000U}, { 0 }, { 0 }}}, + {.msg = {{FORD_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 }}}, + {.msg = {{FORD_EngBrakeData, 0, 8, .expected_timestep = 100000U}, { 0 }, { 0 }}}, + {.msg = {{FORD_EngVehicleSpThrottle, 0, 8, .expected_timestep = 10000U}, { 0 }, { 0 }}}, + {.msg = {{FORD_DesiredTorqBrk, 0, 8, .expected_timestep = 20000U}, { 0 }, { 0 }}}, }; #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}; @@ -41,10 +78,13 @@ static uint8_t ford_get_counter(CANPacket_t *to_push) { int addr = GET_ADDR(to_push); uint8_t cnt; - if (addr == MSG_BrakeSysFeatures) { + if (addr == FORD_BrakeSysFeatures) { // Signal: VehVActlBrk_No_Cnt cnt = (GET_BYTE(to_push, 2) >> 2) & 0xFU; - } else if (addr == MSG_Yaw_Data_FD1) { + } else if (addr == FORD_EngVehicleSpThrottle2) { + // Signal: VehVActlEng_No_Cnt + cnt = (GET_BYTE(to_push, 2) >> 3) & 0xFU; + } else if (addr == FORD_Yaw_Data_FD1) { // Signal: VehRollYaw_No_Cnt cnt = GET_BYTE(to_push, 5); } else { @@ -57,10 +97,13 @@ static uint32_t ford_get_checksum(CANPacket_t *to_push) { int addr = GET_ADDR(to_push); uint8_t chksum; - if (addr == MSG_BrakeSysFeatures) { + if (addr == FORD_BrakeSysFeatures) { // Signal: VehVActlBrk_No_Cs chksum = GET_BYTE(to_push, 3); - } else if (addr == MSG_Yaw_Data_FD1) { + } else if (addr == FORD_EngVehicleSpThrottle2) { + // Signal: VehVActlEng_No_Cs + chksum = GET_BYTE(to_push, 1); + } else if (addr == FORD_Yaw_Data_FD1) { // Signal: VehRollYawW_No_Cs chksum = GET_BYTE(to_push, 4); } else { @@ -73,12 +116,17 @@ static uint32_t ford_compute_checksum(CANPacket_t *to_push) { int addr = GET_ADDR(to_push); uint8_t chksum = 0; - if (addr == MSG_BrakeSysFeatures) { + if (addr == FORD_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) { + } else if (addr == FORD_EngVehicleSpThrottle2) { + chksum += (GET_BYTE(to_push, 2) >> 3) & 0xFU; // VehVActlEng_No_Cnt + chksum += (GET_BYTE(to_push, 4) >> 5) & 0x3U; // VehVActlEng_D_Qf + chksum += GET_BYTE(to_push, 6) + GET_BYTE(to_push, 7); // Veh_V_ActlEng + chksum = 0xFFU - chksum; + } else if (addr == FORD_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 @@ -95,27 +143,75 @@ 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 + if (addr == FORD_BrakeSysFeatures) { + valid = (GET_BYTE(to_push, 2) >> 6) == 0x3U; // VehVActlBrk_D_Qf + } else if (addr == FORD_EngVehicleSpThrottle2) { + valid = ((GET_BYTE(to_push, 4) >> 5) & 0x3U) == 0x3U; // VehVActlEng_D_Qf + } else if (addr == FORD_Yaw_Data_FD1) { + valid = ((GET_BYTE(to_push, 6) >> 4) & 0x3U) == 0x3U; // 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 +const uint16_t FORD_PARAM_LONGITUDINAL = 1; +const uint16_t FORD_PARAM_CANFD = 2; + +bool ford_longitudinal = false; +bool ford_canfd = false; + +const LongitudinalLimits FORD_LONG_LIMITS = { + // acceleration cmd limits (used for brakes) + // Signal: AccBrkTot_A_Rq + .max_accel = 5641, // 1.9999 m/s^s + .min_accel = 4231, // -3.4991 m/s^2 + .inactive_accel = 5128, // -0.0008 m/s^2 + + // gas cmd limits + // Signal: AccPrpl_A_Rq + .max_gas = 700, // 2.0 m/s^2 + .min_gas = 450, // -0.5 m/s^2 + .inactive_gas = 0, // -5.0 m/s^2 +}; + +#define FORD_INACTIVE_CURVATURE 1000U +#define FORD_INACTIVE_CURVATURE_RATE 4096U +#define FORD_INACTIVE_PATH_OFFSET 512U +#define FORD_INACTIVE_PATH_ANGLE 1000U + +#define FORD_CANFD_INACTIVE_CURVATURE_RATE 1024U + +#define FORD_MAX_SPEED_DELTA 2.0 // m/s static bool ford_lkas_msg_check(int addr) { - return (addr == MSG_ACCDATA_3) - || (addr == MSG_Lane_Assist_Data1) - || (addr == MSG_LateralMotionControl) - || (addr == MSG_IPMA_Data); + return (addr == FORD_ACCDATA_3) + || (addr == FORD_Lane_Assist_Data1) + || (addr == FORD_LateralMotionControl) + || (addr == FORD_LateralMotionControl2) + || (addr == FORD_IPMA_Data); } +// Curvature rate limits +const SteeringLimits FORD_STEERING_LIMITS = { + .max_steer = 1000, + .angle_deg_to_can = 50000, // 1 / (2e-5) rad to can + .max_angle_error = 100, // 0.002 * FORD_STEERING_LIMITS.angle_deg_to_can + .angle_rate_up_lookup = { + {5., 25., 25.}, + {0.0002, 0.0001, 0.0001} + }, + .angle_rate_down_lookup = { + {5., 25., 25.}, + {0.000225, 0.00015, 0.00015} + }, + + // no blending at low speed due to lack of torque wind-up and inaccurate current curvature + .angle_error_min_speed = 10.0, // m/s + + .enforce_angle_error = true, + .inactive_angle_is_zero = true, +}; + static int ford_rx_hook(CANPacket_t *to_push) { bool valid = addr_safety_check(to_push, &ford_rx_checks, ford_get_checksum, ford_compute_checksum, ford_get_counter, ford_get_quality_flag_valid); @@ -124,26 +220,45 @@ static int ford_rx_hook(CANPacket_t *to_push) { int addr = GET_ADDR(to_push); // Update in motion state from standstill signal - if (addr == MSG_DesiredTorqBrk) { + if (addr == FORD_DesiredTorqBrk) { // Signal: VehStop_D_Stat vehicle_moving = ((GET_BYTE(to_push, 3) >> 3) & 0x3U) == 0U; } // Update vehicle speed - if (addr == MSG_BrakeSysFeatures) { + if (addr == FORD_BrakeSysFeatures) { // Signal: Veh_V_ActlBrk - vehicle_speed = ((GET_BYTE(to_push, 0) << 8) | GET_BYTE(to_push, 1)) * 0.01 / 3.6; + update_sample(&vehicle_speed, ROUND(((GET_BYTE(to_push, 0) << 8) | GET_BYTE(to_push, 1)) * 0.01 / 3.6 * VEHICLE_SPEED_FACTOR)); + } + + // Check vehicle speed against a second source + if (addr == FORD_EngVehicleSpThrottle2) { + // Disable controls if speeds from ABS and PCM ECUs are too far apart. + // Signal: Veh_V_ActlEng + float filtered_pcm_speed = ((GET_BYTE(to_push, 6) << 8) | GET_BYTE(to_push, 7)) * 0.01 / 3.6; + if (ABS(filtered_pcm_speed - ((float)vehicle_speed.values[0] / VEHICLE_SPEED_FACTOR)) > FORD_MAX_SPEED_DELTA) { + controls_allowed = 0; + } + } + + // Update vehicle yaw rate + if (addr == FORD_Yaw_Data_FD1) { + // Signal: VehYaw_W_Actl + float ford_yaw_rate = (((GET_BYTE(to_push, 2) << 8U) | GET_BYTE(to_push, 3)) * 0.0002) - 6.5; + float current_curvature = ford_yaw_rate / MAX(vehicle_speed.values[0] / VEHICLE_SPEED_FACTOR, 0.1); + // convert current curvature into units on CAN for comparison with desired curvature + update_sample(&angle_meas, ROUND(current_curvature * FORD_STEERING_LIMITS.angle_deg_to_can)); } // Update gas pedal - if (addr == MSG_EngVehicleSpThrottle) { + if (addr == FORD_EngVehicleSpThrottle) { // Pedal position: (0.1 * val) in percent // Signal: ApedPos_Pc_ActlArb gas_pressed = (((GET_BYTE(to_push, 0) & 0x03U) << 8) | GET_BYTE(to_push, 1)) > 0U; } // Update brake pedal and cruise state - if (addr == MSG_EngBrakeData) { + if (addr == FORD_EngBrakeData) { // Signal: BpedDrvAppl_D_Actl brake_pressed = ((GET_BYTE(to_push, 0) >> 4) & 0x3U) == 2U; @@ -162,18 +277,47 @@ static int ford_rx_hook(CANPacket_t *to_push) { } static int ford_tx_hook(CANPacket_t *to_send) { - - int tx = 1; int addr = GET_ADDR(to_send); + int tx; + if (ford_canfd) { + if (ford_longitudinal) { + tx = msg_allowed(to_send, FORD_CANFD_LONG_TX_MSGS, FORD_CANFD_LONG_TX_LEN); + } else { + tx = msg_allowed(to_send, FORD_CANFD_STOCK_TX_MSGS, FORD_CANFD_STOCK_TX_LEN); + } + } else { + if (ford_longitudinal) { + tx = msg_allowed(to_send, FORD_LONG_TX_MSGS, FORD_LONG_TX_LEN); + } else { + tx = msg_allowed(to_send, FORD_STOCK_TX_MSGS, FORD_STOCK_TX_LEN); + } + } + + // Safety check for ACCDATA accel and brake requests + if (addr == FORD_ACCDATA) { + // Signal: AccPrpl_A_Rq + int gas = ((GET_BYTE(to_send, 6) & 0x3U) << 8) | GET_BYTE(to_send, 7); + // Signal: AccBrkTot_A_Rq + int accel = ((GET_BYTE(to_send, 0) & 0x1FU) << 8) | GET_BYTE(to_send, 1); + // Signal: CmbbDeny_B_Actl + int cmbb_deny = GET_BIT(to_send, 37U); + + bool violation = false; + violation |= longitudinal_accel_checks(accel, FORD_LONG_LIMITS); + violation |= longitudinal_gas_checks(gas, FORD_LONG_LIMITS); + + // Safety check for stock AEB + violation |= cmbb_deny != 0; // do not prevent stock AEB actuation - if (!msg_allowed(to_send, FORD_TX_MSGS, FORD_TX_LEN)) { - tx = 0; + if (violation) { + tx = 0; + } } // Safety check for Steering_Data_FD1 button signals // Note: Many other signals in this message are not relevant to safety (e.g. blinkers, wiper switches, high beam) // which we passthru in OP. - if (addr == MSG_Steering_Data_FD1) { + if (addr == FORD_Steering_Data_FD1) { // Violation if resume button is pressed while controls not allowed, or // if cancel button is pressed when cruise isn't engaged. bool violation = false; @@ -186,7 +330,7 @@ static int ford_tx_hook(CANPacket_t *to_send) { } // Safety check for Lane_Assist_Data1 action - if (addr == MSG_Lane_Assist_Data1) { + if (addr == FORD_Lane_Assist_Data1) { // Do not allow steering using Lane_Assist_Data1 (Lane-Departure Aid). // This message must be sent for Lane Centering to work, and can include // values such as the steering angle or lane curvature for debugging, @@ -198,22 +342,43 @@ static int ford_tx_hook(CANPacket_t *to_send) { } // Safety check for LateralMotionControl action - if (addr == MSG_LateralMotionControl) { + if (addr == FORD_LateralMotionControl) { // Signal: LatCtl_D_Rq - unsigned int steer_control_type = (GET_BYTE(to_send, 4) >> 2) & 0x7U; - 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); + bool steer_control_enabled = ((GET_BYTE(to_send, 4) >> 2) & 0x7U) != 0U; + unsigned int raw_curvature = (GET_BYTE(to_send, 0) << 3) | (GET_BYTE(to_send, 1) >> 5); + unsigned int raw_curvature_rate = ((GET_BYTE(to_send, 1) & 0x1FU) << 8) | GET_BYTE(to_send, 2); + unsigned int raw_path_angle = (GET_BYTE(to_send, 3) << 3) | (GET_BYTE(to_send, 4) >> 5); + unsigned int raw_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)) { + bool violation = (raw_curvature_rate != FORD_INACTIVE_CURVATURE_RATE) || (raw_path_angle != FORD_INACTIVE_PATH_ANGLE) || (raw_path_offset != FORD_INACTIVE_PATH_OFFSET); + + // Check angle error and steer_control_enabled + int desired_curvature = raw_curvature - FORD_INACTIVE_CURVATURE; // /FORD_STEERING_LIMITS.angle_deg_to_can to get real curvature + violation |= steer_angle_cmd_checks(desired_curvature, steer_control_enabled, FORD_STEERING_LIMITS); + + if (violation) { tx = 0; } + } + + // Safety check for LateralMotionControl2 action + if (addr == FORD_LateralMotionControl2) { + // Signal: LatCtl_D2_Rq + bool steer_control_enabled = ((GET_BYTE(to_send, 0) >> 4) & 0x7U) != 0U; + unsigned int raw_curvature = (GET_BYTE(to_send, 2) << 3) | (GET_BYTE(to_send, 3) >> 5); + unsigned int raw_curvature_rate = (GET_BYTE(to_send, 6) << 3) | (GET_BYTE(to_send, 7) >> 5); + unsigned int raw_path_angle = ((GET_BYTE(to_send, 3) & 0x1FU) << 6) | (GET_BYTE(to_send, 4) >> 2); + unsigned int raw_path_offset = ((GET_BYTE(to_send, 4) & 0x3U) << 8) | GET_BYTE(to_send, 5); - // 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) { + // These signals are not yet tested with the current safety limits + bool violation = (raw_curvature_rate != FORD_CANFD_INACTIVE_CURVATURE_RATE) || (raw_path_angle != FORD_INACTIVE_PATH_ANGLE) || (raw_path_offset != FORD_INACTIVE_PATH_OFFSET); + + // Check angle error and steer_control_enabled + int desired_curvature = raw_curvature - FORD_INACTIVE_CURVATURE; // /FORD_STEERING_LIMITS.angle_deg_to_can to get real curvature + violation |= steer_angle_cmd_checks(desired_curvature, steer_control_enabled, FORD_STEERING_LIMITS); + + if (violation) { tx = 0; } } @@ -232,8 +397,14 @@ static int ford_fwd_hook(int bus_num, int addr) { break; } case FORD_CAM_BUS: { - // Block stock LKAS messages - if (!ford_lkas_msg_check(addr)) { + if (ford_lkas_msg_check(addr)) { + // Block stock LKAS and UI messages + bus_fwd = -1; + } else if (ford_longitudinal && (addr == FORD_ACCDATA)) { + // Block stock ACC message + bus_fwd = -1; + } else { + // Forward remaining traffic bus_fwd = FORD_MAIN_BUS; } break; @@ -250,7 +421,10 @@ static int ford_fwd_hook(int bus_num, int addr) { static const addr_checks* ford_init(uint16_t param) { UNUSED(param); - +#ifdef ALLOW_DEBUG + ford_longitudinal = GET_FLAG(param, FORD_PARAM_LONGITUDINAL); + ford_canfd = GET_FLAG(param, FORD_PARAM_CANFD); +#endif return &ford_rx_checks; } diff --git a/board/safety/safety_honda.h b/board/safety/safety_honda.h index 26a20bb6d1..83e671a5d3 100644 --- a/board/safety/safety_honda.h +++ b/board/safety/safety_honda.h @@ -23,7 +23,6 @@ const LongitudinalLimits HONDA_BOSCH_LONG_LIMITS = { .min_accel = -350, .max_gas = 2000, - .min_gas = -30000, .inactive_gas = -30000, }; @@ -236,20 +235,18 @@ static int honda_rx_hook(CANPacket_t *to_push) { int bus_rdr_car = (honda_hw == HONDA_BOSCH) ? 0 : 2; // radar bus, car side bool stock_ecu_detected = false; - if (safety_mode_cnt > RELAY_TRNS_TIMEOUT) { - // If steering controls messages are received on the destination bus, it's an indication - // that the relay might be malfunctioning - if ((addr == 0xE4) || (addr == 0x194)) { - if (((honda_hw != HONDA_NIDEC) && (bus == bus_rdr_car)) || ((honda_hw == HONDA_NIDEC) && (bus == 0))) { - stock_ecu_detected = true; - } - } - // If Honda Bosch longitudinal mode is selected we need to ensure the radar is turned off - // Verify this by ensuring ACC_CONTROL (0x1DF) is not received on the PT bus - if (honda_bosch_long && !honda_bosch_radarless && (bus == pt_bus) && (addr == 0x1DF)) { + // If steering controls messages are received on the destination bus, it's an indication + // that the relay might be malfunctioning + if ((addr == 0xE4) || (addr == 0x194)) { + if (((honda_hw != HONDA_NIDEC) && (bus == bus_rdr_car)) || ((honda_hw == HONDA_NIDEC) && (bus == 0))) { stock_ecu_detected = true; } } + // If Honda Bosch longitudinal mode is selected we need to ensure the radar is turned off + // Verify this by ensuring ACC_CONTROL (0x1DF) is not received on the PT bus + if (honda_bosch_long && !honda_bosch_radarless && (bus == pt_bus) && (addr == 0x1DF)) { + stock_ecu_detected = true; + } generic_rx_checks(stock_ecu_detected); } @@ -347,7 +344,7 @@ static int honda_tx_hook(CANPacket_t *to_send) { // Bosch supplemental control check if (addr == 0xE5) { - if ((GET_BYTES_04(to_send) != 0x10800004U) || ((GET_BYTES_48(to_send) & 0x00FFFFFFU) != 0x0U)) { + if ((GET_BYTES(to_send, 0, 4) != 0x10800004U) || ((GET_BYTES(to_send, 4, 4) & 0x00FFFFFFU) != 0x0U)) { tx = 0; } } @@ -370,7 +367,7 @@ static int honda_tx_hook(CANPacket_t *to_send) { // Only tester present ("\x02\x3E\x80\x00\x00\x00\x00\x00") allowed on diagnostics address if (addr == 0x18DAB0F1) { - if ((GET_BYTES_04(to_send) != 0x00803E02U) || (GET_BYTES_48(to_send) != 0x0U)) { + if ((GET_BYTES(to_send, 0, 4) != 0x00803E02U) || (GET_BYTES(to_send, 4, 4) != 0x0U)) { tx = 0; } } diff --git a/board/safety/safety_hyundai.h b/board/safety/safety_hyundai.h index 85bffc48fe..2f332bbe43 100644 --- a/board/safety/safety_hyundai.h +++ b/board/safety/safety_hyundai.h @@ -190,13 +190,13 @@ static int hyundai_rx_hook(CANPacket_t *to_push) { // SCC12 is on bus 2 for camera-based SCC cars, bus 0 on all others if (valid && (addr == 1057) && (((bus == 0) && !hyundai_camera_scc) || ((bus == 2) && hyundai_camera_scc))) { // 2 bits: 13-14 - int cruise_engaged = (GET_BYTES_04(to_push) >> 13) & 0x3U; + int cruise_engaged = (GET_BYTES(to_push, 0, 4) >> 13) & 0x3U; hyundai_common_cruise_state_check(cruise_engaged); } if (valid && (bus == 0)) { if (addr == 593) { - int torque_driver_new = ((GET_BYTES_04(to_push) & 0x7ffU) * 0.79) - 808; // scale down new driver torque signal to match previous one + int torque_driver_new = ((GET_BYTES(to_push, 0, 4) & 0x7ffU) * 0.79) - 808; // scale down new driver torque signal to match previous one // update array of samples update_sample(&torque_driver, torque_driver_new); } @@ -220,7 +220,7 @@ static int hyundai_rx_hook(CANPacket_t *to_push) { // sample wheel speed, averaging opposite corners if (addr == 902) { - uint32_t hyundai_speed = (GET_BYTES_04(to_push) & 0x3FFFU) + ((GET_BYTES_48(to_push) >> 16) & 0x3FFFU); // FL + RR + uint32_t hyundai_speed = (GET_BYTES(to_push, 0, 4) & 0x3FFFU) + ((GET_BYTES(to_push, 4, 4) >> 16) & 0x3FFFU); // FL + RR hyundai_speed /= 2; vehicle_moving = hyundai_speed > HYUNDAI_STANDSTILL_THRSLD; } @@ -289,7 +289,7 @@ static int hyundai_tx_hook(CANPacket_t *to_send) { // LKA STEER: safety check if (addr == 832) { - int desired_torque = ((GET_BYTES_04(to_send) >> 16) & 0x7ffU) - 1024U; + int desired_torque = ((GET_BYTES(to_send, 0, 4) >> 16) & 0x7ffU) - 1024U; bool steer_req = GET_BIT(to_send, 27U) != 0U; const SteeringLimits limits = hyundai_alt_limits ? HYUNDAI_STEERING_LIMITS_ALT : HYUNDAI_STEERING_LIMITS; @@ -300,7 +300,7 @@ static int hyundai_tx_hook(CANPacket_t *to_send) { // UDS: Only tester present ("\x02\x3E\x80\x00\x00\x00\x00\x00") allowed on diagnostics address if ((addr == 2000) && !hyundai_camera_scc) { - if ((GET_BYTES_04(to_send) != 0x00803E02U) || (GET_BYTES_48(to_send) != 0x0U)) { + if ((GET_BYTES(to_send, 0, 4) != 0x00803E02U) || (GET_BYTES(to_send, 4, 4) != 0x0U)) { tx = 0; } } diff --git a/board/safety/safety_hyundai_canfd.h b/board/safety/safety_hyundai_canfd.h index 188a4c6916..2dc6e9bbff 100644 --- a/board/safety/safety_hyundai_canfd.h +++ b/board/safety/safety_hyundai_canfd.h @@ -282,7 +282,7 @@ static int hyundai_canfd_tx_hook(CANPacket_t *to_send) { // UDS: only tester present ("\x02\x3E\x80\x00\x00\x00\x00\x00") allowed on diagnostics address if ((addr == 0x730) && hyundai_canfd_hda2) { - if ((GET_BYTES_04(to_send) != 0x00803E02U) || (GET_BYTES_48(to_send) != 0x0U)) { + if ((GET_BYTES(to_send, 0, 4) != 0x00803E02U) || (GET_BYTES(to_send, 4, 4) != 0x0U)) { tx = 0; } } diff --git a/board/safety/safety_nissan.h b/board/safety/safety_nissan.h index 3f8532d7f3..1f44238e4e 100644 --- a/board/safety/safety_nissan.h +++ b/board/safety/safety_nissan.h @@ -52,7 +52,7 @@ static int nissan_rx_hook(CANPacket_t *to_push) { if (addr == 0x2) { // Current steering angle // Factor -0.1, little endian - int angle_meas_new = (GET_BYTES_04(to_push) & 0xFFFFU); + int angle_meas_new = (GET_BYTES(to_push, 0, 4) & 0xFFFFU); // Need to multiply by 10 here as LKAS and Steering wheel are different base unit angle_meas_new = to_signed(angle_meas_new, 16) * 10; @@ -65,7 +65,7 @@ static int nissan_rx_hook(CANPacket_t *to_push) { 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; + update_sample(&vehicle_speed, ROUND((right_rear + left_rear) / 2.0 * 0.005 / 3.6 * VEHICLE_SPEED_FACTOR)); } // X-Trail 0x15c, Leaf 0x239 diff --git a/board/safety/safety_subaru.h b/board/safety/safety_subaru.h index 450dabc4da..864d041d3a 100644 --- a/board/safety/safety_subaru.h +++ b/board/safety/safety_subaru.h @@ -1,59 +1,69 @@ -const SteeringLimits SUBARU_STEERING_LIMITS = { - .max_steer = 2047, - .max_rt_delta = 940, - .max_rt_interval = 250000, - .max_rate_up = 50, - .max_rate_down = 70, - .driver_torque_factor = 50, - .driver_torque_allowance = 60, - .type = TorqueDriverLimited, -}; - -const SteeringLimits SUBARU_GEN2_STEERING_LIMITS = { - .max_steer = 1000, - .max_rt_delta = 940, - .max_rt_interval = 250000, - .max_rate_up = 40, - .max_rate_down = 40, - .driver_torque_factor = 50, - .driver_torque_allowance = 60, - .type = TorqueDriverLimited, -}; +#define SUBARU_STEERING_LIMITS_GENERATOR(steer_max, rate_up, rate_down) \ + { \ + .max_steer = (steer_max), \ + .max_rt_delta = 940, \ + .max_rt_interval = 250000, \ + .max_rate_up = (rate_up), \ + .max_rate_down = (rate_down), \ + .driver_torque_factor = 50, \ + .driver_torque_allowance = 60, \ + .type = TorqueDriverLimited, \ + } \ + +const SteeringLimits SUBARU_STEERING_LIMITS = SUBARU_STEERING_LIMITS_GENERATOR(2047, 50, 70); +const SteeringLimits SUBARU_GEN2_STEERING_LIMITS = SUBARU_STEERING_LIMITS_GENERATOR(1000, 40, 40); + + +#define MSG_SUBARU_Brake_Status 0x13c +#define MSG_SUBARU_CruiseControl 0x240 +#define MSG_SUBARU_Throttle 0x40 +#define MSG_SUBARU_Steering_Torque 0x119 +#define MSG_SUBARU_Wheel_Speeds 0x13a + +#define MSG_SUBARU_ES_LKAS 0x122 +#define MSG_SUBARU_ES_Brake 0x220 +#define MSG_SUBARU_ES_Distance 0x221 +#define MSG_SUBARU_ES_Status 0x222 +#define MSG_SUBARU_ES_DashStatus 0x321 +#define MSG_SUBARU_ES_LKAS_State 0x322 +#define MSG_SUBARU_ES_Infotainment 0x323 + +#define SUBARU_MAIN_BUS 0 +#define SUBARU_ALT_BUS 1 +#define SUBARU_CAM_BUS 2 + +#define SUBARU_COMMON_TX_MSGS(alt_bus) \ + {MSG_SUBARU_ES_LKAS, SUBARU_MAIN_BUS, 8}, \ + {MSG_SUBARU_ES_Distance, alt_bus, 8}, \ + {MSG_SUBARU_ES_DashStatus, SUBARU_MAIN_BUS, 8}, \ + {MSG_SUBARU_ES_LKAS_State, SUBARU_MAIN_BUS, 8}, \ + {MSG_SUBARU_ES_Infotainment, SUBARU_MAIN_BUS, 8}, \ + +#define SUBARU_COMMON_ADDR_CHECKS(alt_bus) \ + {.msg = {{MSG_SUBARU_Throttle, SUBARU_MAIN_BUS, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 10000U}, { 0 }, { 0 }}}, \ + {.msg = {{MSG_SUBARU_Steering_Torque, SUBARU_MAIN_BUS, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 20000U}, { 0 }, { 0 }}}, \ + {.msg = {{MSG_SUBARU_Wheel_Speeds, alt_bus, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 20000U}, { 0 }, { 0 }}}, \ + {.msg = {{MSG_SUBARU_Brake_Status, alt_bus, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 20000U}, { 0 }, { 0 }}}, \ + {.msg = {{MSG_SUBARU_CruiseControl, alt_bus, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 50000U}, { 0 }, { 0 }}}, \ const CanMsg SUBARU_TX_MSGS[] = { - {0x122, 0, 8}, - {0x221, 0, 8}, - {0x321, 0, 8}, - {0x322, 0, 8}, - {0x323, 0, 8}, + SUBARU_COMMON_TX_MSGS(SUBARU_MAIN_BUS) }; #define SUBARU_TX_MSGS_LEN (sizeof(SUBARU_TX_MSGS) / sizeof(SUBARU_TX_MSGS[0])) const CanMsg SUBARU_GEN2_TX_MSGS[] = { - {0x122, 0, 8}, - {0x221, 1, 8}, - {0x321, 0, 8}, - {0x322, 0, 8}, - {0x323, 0, 8} + SUBARU_COMMON_TX_MSGS(SUBARU_ALT_BUS) }; #define SUBARU_GEN2_TX_MSGS_LEN (sizeof(SUBARU_GEN2_TX_MSGS) / sizeof(SUBARU_GEN2_TX_MSGS[0])) AddrCheckStruct subaru_addr_checks[] = { - {.msg = {{ 0x40, 0, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 10000U}, { 0 }, { 0 }}}, - {.msg = {{0x119, 0, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 20000U}, { 0 }, { 0 }}}, - {.msg = {{0x13a, 0, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 20000U}, { 0 }, { 0 }}}, - {.msg = {{0x13c, 0, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 20000U}, { 0 }, { 0 }}}, - {.msg = {{0x240, 0, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 50000U}, { 0 }, { 0 }}}, + SUBARU_COMMON_ADDR_CHECKS(SUBARU_MAIN_BUS) }; #define SUBARU_ADDR_CHECK_LEN (sizeof(subaru_addr_checks) / sizeof(subaru_addr_checks[0])) addr_checks subaru_rx_checks = {subaru_addr_checks, SUBARU_ADDR_CHECK_LEN}; AddrCheckStruct subaru_gen2_addr_checks[] = { - {.msg = {{ 0x40, 0, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 10000U}, { 0 }, { 0 }}}, - {.msg = {{0x119, 0, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 20000U}, { 0 }, { 0 }}}, - {.msg = {{0x13a, 1, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 20000U}, { 0 }, { 0 }}}, - {.msg = {{0x13c, 1, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 20000U}, { 0 }, { 0 }}}, - {.msg = {{0x240, 1, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 50000U}, { 0 }, { 0 }}}, + SUBARU_COMMON_ADDR_CHECKS(SUBARU_ALT_BUS) }; #define SUBARU_GEN2_ADDR_CHECK_LEN (sizeof(subaru_gen2_addr_checks) / sizeof(subaru_gen2_addr_checks[0])) addr_checks subaru_gen2_rx_checks = {subaru_gen2_addr_checks, SUBARU_GEN2_ADDR_CHECK_LEN}; @@ -88,36 +98,49 @@ static int subaru_rx_hook(CANPacket_t *to_push) { if (valid) { const int bus = GET_BUS(to_push); - const int alt_bus = subaru_gen2 ? 1 : 0; + const int alt_main_bus = subaru_gen2 ? SUBARU_ALT_BUS : SUBARU_MAIN_BUS; int addr = GET_ADDR(to_push); - if ((addr == 0x119) && (bus == 0)) { + if ((addr == MSG_SUBARU_Steering_Torque) && (bus == SUBARU_MAIN_BUS)) { int torque_driver_new; - torque_driver_new = ((GET_BYTES_04(to_push) >> 16) & 0x7FFU); + torque_driver_new = ((GET_BYTES(to_push, 0, 4) >> 16) & 0x7FFU); torque_driver_new = -1 * to_signed(torque_driver_new, 11); update_sample(&torque_driver, torque_driver_new); + + int angle_meas_new = (GET_BYTES(to_push, 4, 2) & 0xFFFFU); + // convert Steering_Torque -> Steering_Angle to centidegrees, to match the ES_LKAS_ANGLE angle request units + angle_meas_new = ROUND(to_signed(angle_meas_new, 16) * 2.17); + update_sample(&angle_meas, angle_meas_new); } // enter controls on rising edge of ACC, exit controls on ACC off - if ((addr == 0x240) && (bus == alt_bus)) { + if ((addr == MSG_SUBARU_CruiseControl) && (bus == alt_main_bus)) { bool cruise_engaged = GET_BIT(to_push, 41U) != 0U; pcm_cruise_check(cruise_engaged); } // update vehicle moving with any non-zero wheel speed - if ((addr == 0x13a) && (bus == alt_bus)) { - vehicle_moving = ((GET_BYTES_04(to_push) >> 12) != 0U) || (GET_BYTES_48(to_push) != 0U); + if ((addr == MSG_SUBARU_Wheel_Speeds) && (bus == alt_main_bus)) { + uint32_t fr = (GET_BYTES(to_push, 1, 3) >> 4) & 0x1FFFU; + uint32_t rr = (GET_BYTES(to_push, 3, 3) >> 1) & 0x1FFFU; + uint32_t rl = (GET_BYTES(to_push, 4, 3) >> 6) & 0x1FFFU; + uint32_t fl = (GET_BYTES(to_push, 6, 2) >> 3) & 0x1FFFU; + + vehicle_moving = (fr > 0U) || (rr > 0U) || (rl > 0U) || (fl > 0U); + + float speed = (fr + rr + rl + fl) / 4U * 0.057; + update_sample(&vehicle_speed, ROUND(speed * VEHICLE_SPEED_FACTOR)); } - if ((addr == 0x13c) && (bus == alt_bus)) { - brake_pressed = ((GET_BYTE(to_push, 7) >> 6) & 1U); + if ((addr == MSG_SUBARU_Brake_Status) && (bus == alt_main_bus)) { + brake_pressed = GET_BIT(to_push, 62U) != 0U; } - if ((addr == 0x40) && (bus == 0)) { + if ((addr == MSG_SUBARU_Throttle) && (bus == SUBARU_MAIN_BUS)) { gas_pressed = GET_BYTE(to_push, 4) != 0U; } - generic_rx_checks((addr == 0x122) && (bus == 0)); + generic_rx_checks((addr == MSG_SUBARU_ES_LKAS) && (bus == SUBARU_MAIN_BUS)); } return valid; } @@ -126,6 +149,7 @@ static int subaru_tx_hook(CANPacket_t *to_send) { int tx = 1; int addr = GET_ADDR(to_send); + bool violation = false; if (subaru_gen2) { tx = msg_allowed(to_send, SUBARU_GEN2_TX_MSGS, SUBARU_GEN2_TX_MSGS_LEN); @@ -134,15 +158,24 @@ static int subaru_tx_hook(CANPacket_t *to_send) { } // steer cmd checks - if (addr == 0x122) { - int desired_torque = ((GET_BYTES_04(to_send) >> 16) & 0x1FFFU); + if (addr == MSG_SUBARU_ES_LKAS) { + int desired_torque = ((GET_BYTES(to_send, 0, 4) >> 16) & 0x1FFFU); desired_torque = -1 * to_signed(desired_torque, 13); const SteeringLimits limits = subaru_gen2 ? SUBARU_GEN2_STEERING_LIMITS : SUBARU_STEERING_LIMITS; - if (steer_torque_cmd_checks(desired_torque, -1, limits)) { - tx = 0; - } + violation |= steer_torque_cmd_checks(desired_torque, -1, limits); + } + + // Only allow ES_Distance when Cruise_Cancel is true, and Cruise_Throttle is "inactive" (1818) + if (addr == MSG_SUBARU_ES_Distance){ + int cruise_throttle = (GET_BYTES(to_send, 2, 2) & 0xFFFU); + bool cruise_cancel = GET_BIT(to_send, 56U) != 0U; + violation |= (cruise_throttle != 1818); + violation |= (!cruise_cancel); + } + if (violation){ + tx = 0; } return tx; } @@ -150,19 +183,18 @@ static int subaru_tx_hook(CANPacket_t *to_send) { static int subaru_fwd_hook(int bus_num, int addr) { int bus_fwd = -1; - if (bus_num == 0) { - bus_fwd = 2; // forward to camera + if (bus_num == SUBARU_MAIN_BUS) { + bus_fwd = SUBARU_CAM_BUS; // forward to camera } - if (bus_num == 2) { + if (bus_num == SUBARU_CAM_BUS) { // Global platform - // 0x122 ES_LKAS - // 0x321 ES_DashStatus - // 0x322 ES_LKAS_State - // 0x323 INFOTAINMENT_STATUS - bool block_lkas = (addr == 0x122) || (addr == 0x321) || (addr == 0x322) || (addr == 0x323); + bool block_lkas = ((addr == MSG_SUBARU_ES_LKAS) || + (addr == MSG_SUBARU_ES_DashStatus) || + (addr == MSG_SUBARU_ES_LKAS_State) || + (addr == MSG_SUBARU_ES_Infotainment)); if (!block_lkas) { - bus_fwd = 0; // Main CAN + bus_fwd = SUBARU_MAIN_BUS; // Main CAN } } @@ -188,5 +220,3 @@ const safety_hooks subaru_hooks = { .tx_lin = nooutput_tx_lin_hook, .fwd = subaru_fwd_hook, }; - - diff --git a/board/safety/safety_subaru_legacy.h b/board/safety/safety_subaru_legacy.h deleted file mode 100644 index 4b5b79437c..0000000000 --- a/board/safety/safety_subaru_legacy.h +++ /dev/null @@ -1,117 +0,0 @@ -const SteeringLimits SUBARU_L_STEERING_LIMITS = { - .max_steer = 2047, - .max_rt_delta = 940, - .max_rt_interval = 250000, - .max_rate_up = 50, - .max_rate_down = 70, - .driver_torque_factor = 10, - .driver_torque_allowance = 75, - .type = TorqueDriverLimited, -}; - -const CanMsg SUBARU_L_TX_MSGS[] = { - {0x161, 0, 8}, - {0x164, 0, 8} -}; -#define SUBARU_L_TX_MSGS_LEN (sizeof(SUBARU_L_TX_MSGS) / sizeof(SUBARU_L_TX_MSGS[0])) - -// TODO: do checksum and counter checks after adding the signals to the outback dbc file -AddrCheckStruct subaru_l_addr_checks[] = { - {.msg = {{0x140, 0, 8, .expected_timestep = 10000U}, { 0 }, { 0 }}}, - {.msg = {{0x371, 0, 8, .expected_timestep = 20000U}, { 0 }, { 0 }}}, - {.msg = {{0x144, 0, 8, .expected_timestep = 50000U}, { 0 }, { 0 }}}, -}; -#define SUBARU_L_ADDR_CHECK_LEN (sizeof(subaru_l_addr_checks) / sizeof(subaru_l_addr_checks[0])) -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, NULL); - - if (valid && (GET_BUS(to_push) == 0U)) { - int addr = GET_ADDR(to_push); - if (addr == 0x371) { - int torque_driver_new; - torque_driver_new = (GET_BYTE(to_push, 3) >> 5) + (GET_BYTE(to_push, 4) << 3); - torque_driver_new = to_signed(torque_driver_new, 11); - update_sample(&torque_driver, torque_driver_new); - } - - // enter controls on rising edge of ACC, exit controls on ACC off - if (addr == 0x144) { - bool cruise_engaged = GET_BIT(to_push, 49U) != 0U; - pcm_cruise_check(cruise_engaged); - } - - // update vehicle moving with any non-zero wheel speed - if (addr == 0xD4) { - vehicle_moving = ((GET_BYTES_04(to_push) >> 12) != 0U) || (GET_BYTES_48(to_push) != 0U); - } - - if (addr == 0xD1) { - brake_pressed = ((GET_BYTES_04(to_push) >> 16) & 0xFFU) > 0U; - } - - if (addr == 0x140) { - gas_pressed = GET_BYTE(to_push, 0) != 0U; - } - - generic_rx_checks((addr == 0x164)); - } - return valid; -} - -static int subaru_legacy_tx_hook(CANPacket_t *to_send) { - - int tx = 1; - int addr = GET_ADDR(to_send); - - if (!msg_allowed(to_send, SUBARU_L_TX_MSGS, SUBARU_L_TX_MSGS_LEN)) { - tx = 0; - } - - // steer cmd checks - if (addr == 0x164) { - int desired_torque = ((GET_BYTES_04(to_send) >> 8) & 0x1FFFU); - desired_torque = -1 * to_signed(desired_torque, 13); - - if (steer_torque_cmd_checks(desired_torque, -1, SUBARU_L_STEERING_LIMITS)) { - tx = 0; - } - - } - return tx; -} - -static int subaru_legacy_fwd_hook(int bus_num, int addr) { - int bus_fwd = -1; - - if (bus_num == 0) { - bus_fwd = 2; // Camera CAN - } - - if (bus_num == 2) { - // Preglobal platform - // 0x161 is ES_CruiseThrottle - // 0x164 is ES_LKAS - int block_msg = ((addr == 0x161) || (addr == 0x164)); - if (!block_msg) { - bus_fwd = 0; // Main CAN - } - } - - return bus_fwd; -} - -static const addr_checks* subaru_legacy_init(uint16_t param) { - UNUSED(param); - return &subaru_l_rx_checks; -} - -const safety_hooks subaru_legacy_hooks = { - .init = subaru_legacy_init, - .rx = subaru_legacy_rx_hook, - .tx = subaru_legacy_tx_hook, - .tx_lin = nooutput_tx_lin_hook, - .fwd = subaru_legacy_fwd_hook, -}; diff --git a/board/safety/safety_subaru_preglobal.h b/board/safety/safety_subaru_preglobal.h new file mode 100644 index 0000000000..b30af3d4aa --- /dev/null +++ b/board/safety/safety_subaru_preglobal.h @@ -0,0 +1,131 @@ +const SteeringLimits SUBARU_PG_STEERING_LIMITS = { + .max_steer = 2047, + .max_rt_delta = 940, + .max_rt_interval = 250000, + .max_rate_up = 50, + .max_rate_down = 70, + .driver_torque_factor = 10, + .driver_torque_allowance = 75, + .type = TorqueDriverLimited, +}; + +// Preglobal platform +// 0x161 is ES_CruiseThrottle +// 0x164 is ES_LKAS + +#define MSG_SUBARU_PG_CruiseControl 0x144 +#define MSG_SUBARU_PG_Throttle 0x140 +#define MSG_SUBARU_PG_Wheel_Speeds 0xD4 +#define MSG_SUBARU_PG_Brake_Pedal 0xD1 +#define MSG_SUBARU_PG_ES_LKAS 0x164 +#define MSG_SUBARU_PG_ES_Distance 0x161 +#define MSG_SUBARU_PG_Steering_Torque 0x371 + +#define SUBARU_PG_MAIN_BUS 0 +#define SUBARU_PG_CAM_BUS 2 + +const CanMsg SUBARU_PG_TX_MSGS[] = { + {MSG_SUBARU_PG_ES_Distance, SUBARU_PG_MAIN_BUS, 8}, + {MSG_SUBARU_PG_ES_LKAS, SUBARU_PG_MAIN_BUS, 8} +}; +#define SUBARU_PG_TX_MSGS_LEN (sizeof(SUBARU_PG_TX_MSGS) / sizeof(SUBARU_PG_TX_MSGS[0])) + +// TODO: do checksum and counter checks after adding the signals to the outback dbc file +AddrCheckStruct subaru_preglobal_addr_checks[] = { + {.msg = {{MSG_SUBARU_PG_Throttle, SUBARU_PG_MAIN_BUS, 8, .expected_timestep = 10000U}, { 0 }, { 0 }}}, + {.msg = {{MSG_SUBARU_PG_Steering_Torque, SUBARU_PG_MAIN_BUS, 8, .expected_timestep = 20000U}, { 0 }, { 0 }}}, + {.msg = {{MSG_SUBARU_PG_CruiseControl, SUBARU_PG_MAIN_BUS, 8, .expected_timestep = 50000U}, { 0 }, { 0 }}}, +}; +#define SUBARU_PG_ADDR_CHECK_LEN (sizeof(subaru_preglobal_addr_checks) / sizeof(subaru_preglobal_addr_checks[0])) +addr_checks subaru_preglobal_rx_checks = {subaru_preglobal_addr_checks, SUBARU_PG_ADDR_CHECK_LEN}; + +static int subaru_preglobal_rx_hook(CANPacket_t *to_push) { + + bool valid = addr_safety_check(to_push, &subaru_preglobal_rx_checks, NULL, NULL, NULL, NULL); + + const int bus = GET_BUS(to_push); + + if (valid && (bus == SUBARU_PG_MAIN_BUS)) { + int addr = GET_ADDR(to_push); + if (addr == MSG_SUBARU_PG_Steering_Torque) { + int torque_driver_new; + torque_driver_new = (GET_BYTE(to_push, 3) >> 5) + (GET_BYTE(to_push, 4) << 3); + torque_driver_new = to_signed(torque_driver_new, 11); + update_sample(&torque_driver, torque_driver_new); + } + + // enter controls on rising edge of ACC, exit controls on ACC off + if (addr == MSG_SUBARU_PG_CruiseControl) { + bool cruise_engaged = GET_BIT(to_push, 49U) != 0U; + pcm_cruise_check(cruise_engaged); + } + + // update vehicle moving with any non-zero wheel speed + if (addr == MSG_SUBARU_PG_Wheel_Speeds) { + vehicle_moving = ((GET_BYTES(to_push, 0, 4) >> 12) != 0U) || (GET_BYTES(to_push, 4, 4) != 0U); + } + + if (addr == MSG_SUBARU_PG_Brake_Pedal) { + brake_pressed = ((GET_BYTES(to_push, 0, 4) >> 16) & 0xFFU) > 0U; + } + + if (addr == MSG_SUBARU_PG_Throttle) { + gas_pressed = GET_BYTE(to_push, 0) != 0U; + } + + generic_rx_checks((addr == MSG_SUBARU_PG_ES_LKAS)); + } + return valid; +} + +static int subaru_preglobal_tx_hook(CANPacket_t *to_send) { + + int tx = 1; + int addr = GET_ADDR(to_send); + + if (!msg_allowed(to_send, SUBARU_PG_TX_MSGS, SUBARU_PG_TX_MSGS_LEN)) { + tx = 0; + } + + // steer cmd checks + if (addr == MSG_SUBARU_PG_ES_LKAS) { + int desired_torque = ((GET_BYTES(to_send, 0, 4) >> 8) & 0x1FFFU); + desired_torque = -1 * to_signed(desired_torque, 13); + + if (steer_torque_cmd_checks(desired_torque, -1, SUBARU_PG_STEERING_LIMITS)) { + tx = 0; + } + + } + return tx; +} + +static int subaru_preglobal_fwd_hook(int bus_num, int addr) { + int bus_fwd = -1; + + if (bus_num == SUBARU_PG_MAIN_BUS) { + bus_fwd = SUBARU_PG_CAM_BUS; // Camera CAN + } + + if (bus_num == SUBARU_PG_CAM_BUS) { + int block_msg = ((addr == MSG_SUBARU_PG_ES_Distance) || (addr == MSG_SUBARU_PG_ES_LKAS)); + if (!block_msg) { + bus_fwd = SUBARU_PG_MAIN_BUS; // Main CAN + } + } + + return bus_fwd; +} + +static const addr_checks* subaru_preglobal_init(uint16_t param) { + UNUSED(param); + return &subaru_preglobal_rx_checks; +} + +const safety_hooks subaru_preglobal_hooks = { + .init = subaru_preglobal_init, + .rx = subaru_preglobal_rx_hook, + .tx = subaru_preglobal_tx_hook, + .tx_lin = nooutput_tx_lin_hook, + .fwd = subaru_preglobal_fwd_hook, +}; diff --git a/board/safety/safety_tesla.h b/board/safety/safety_tesla.h index 96a8e42275..676670aaaf 100644 --- a/board/safety/safety_tesla.h +++ b/board/safety/safety_tesla.h @@ -2,11 +2,11 @@ const SteeringLimits TESLA_STEERING_LIMITS = { .angle_deg_to_can = 10, .angle_rate_up_lookup = { {0., 5., 15.}, - {5., .8, .15} + {10., 1.6, .3} }, .angle_rate_down_lookup = { {0., 5., 15.}, - {5., 3.5, .4} + {10., 7.0, .8} }, }; @@ -34,7 +34,7 @@ const CanMsg TESLA_PT_TX_MSGS[] = { #define TESLA_PT_TX_LEN (sizeof(TESLA_PT_TX_MSGS) / sizeof(TESLA_PT_TX_MSGS[0])) AddrCheckStruct tesla_addr_checks[] = { - {.msg = {{0x2b9, 0, 8, .expected_timestep = 40000U}, { 0 }, { 0 }}}, // DAS_control (25Hz) + {.msg = {{0x2b9, 2, 8, .expected_timestep = 40000U}, { 0 }, { 0 }}}, // DAS_control (25Hz) {.msg = {{0x370, 0, 8, .expected_timestep = 40000U}, { 0 }, { 0 }}}, // EPAS_sysStatus (25Hz) {.msg = {{0x108, 0, 8, .expected_timestep = 10000U}, { 0 }, { 0 }}}, // DI_torque1 (100Hz) {.msg = {{0x118, 0, 6, .expected_timestep = 10000U}, { 0 }, { 0 }}}, // DI_torque2 (100Hz) @@ -49,7 +49,7 @@ AddrCheckStruct tesla_pt_addr_checks[] = { {.msg = {{0x106, 0, 8, .expected_timestep = 10000U}, { 0 }, { 0 }}}, // DI_torque1 (100Hz) {.msg = {{0x116, 0, 6, .expected_timestep = 10000U}, { 0 }, { 0 }}}, // DI_torque2 (100Hz) {.msg = {{0x1f8, 0, 8, .expected_timestep = 20000U}, { 0 }, { 0 }}}, // BrakeMessage (50Hz) - {.msg = {{0x2bf, 0, 8, .expected_timestep = 40000U}, { 0 }, { 0 }}}, // DAS_control (25Hz) + {.msg = {{0x2bf, 2, 8, .expected_timestep = 40000U}, { 0 }, { 0 }}}, // DAS_control (25Hz) {.msg = {{0x256, 0, 8, .expected_timestep = 100000U}, { 0 }, { 0 }}}, // DI_state (10Hz) }; #define TESLA_PT_ADDR_CHECK_LEN (sizeof(tesla_pt_addr_checks) / sizeof(tesla_pt_addr_checks[0])) @@ -80,8 +80,9 @@ static int tesla_rx_hook(CANPacket_t *to_push) { if(addr == (tesla_powertrain ? 0x116 : 0x118)) { // Vehicle speed: ((0.05 * val) - 25) * MPH_TO_MPS - vehicle_speed = (((((GET_BYTE(to_push, 3) & 0x0FU) << 8) | (GET_BYTE(to_push, 2))) * 0.05) - 25) * 0.447; - vehicle_moving = ABS(vehicle_speed) > 0.1; + float speed = (((((GET_BYTE(to_push, 3) & 0x0FU) << 8) | (GET_BYTE(to_push, 2))) * 0.05) - 25) * 0.447; + vehicle_moving = ABS(speed) > 0.1; + update_sample(&vehicle_speed, ROUND(speed * VEHICLE_SPEED_FACTOR)); } if(addr == (tesla_powertrain ? 0x106 : 0x108)) { diff --git a/board/safety/safety_toyota.h b/board/safety/safety_toyota.h index 7aec33a0d9..0bc7f3c6df 100644 --- a/board/safety/safety_toyota.h +++ b/board/safety/safety_toyota.h @@ -103,20 +103,20 @@ static int toyota_rx_hook(CANPacket_t *to_push) { // sample gas pedal if (!gas_interceptor_detected) { - gas_pressed = ((GET_BYTE(to_push, 0) >> 4) & 1U) == 0U; + gas_pressed = GET_BIT(to_push, 4U) == 0U; } } if (addr == 0xaa) { // check that all wheel speeds are at zero value with offset - bool standstill = (GET_BYTES_04(to_push) == 0x6F1A6F1AU) && (GET_BYTES_48(to_push) == 0x6F1A6F1AU); + bool standstill = (GET_BYTES(to_push, 0, 4) == 0x6F1A6F1AU) && (GET_BYTES(to_push, 4, 4) == 0x6F1A6F1AU); vehicle_moving = !standstill; } // most cars have brake_pressed on 0x226, corolla and rav4 on 0x224 if (((addr == 0x224) && toyota_alt_brake) || ((addr == 0x226) && !toyota_alt_brake)) { - int byte = (addr == 0x224) ? 0 : 4; - brake_pressed = ((GET_BYTE(to_push, byte) >> 5) & 1U) != 0U; + uint8_t bit = (addr == 0x224) ? 5U : 37U; + brake_pressed = GET_BIT(to_push, bit) != 0U; } // sample gas interceptor @@ -181,7 +181,7 @@ static int toyota_tx_hook(CANPacket_t *to_send) { // AEB: block all actuation. only used when DSU is unplugged if (addr == 0x283) { // only allow the checksum, which is the last byte - bool block = (GET_BYTES_04(to_send) != 0U) || (GET_BYTE(to_send, 4) != 0U) || (GET_BYTE(to_send, 5) != 0U); + bool block = (GET_BYTES(to_send, 0, 4) != 0U) || (GET_BYTE(to_send, 4) != 0U) || (GET_BYTE(to_send, 5) != 0U); if (block) { tx = 0; } @@ -191,8 +191,8 @@ static int toyota_tx_hook(CANPacket_t *to_send) { // only sent to prevent dash errors, no actuation is accepted if (addr == 0x191) { // 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; + bool lta_request = GET_BIT(to_send, 0U) != 0U; + bool lta_request2 = GET_BIT(to_send, 25U) != 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); diff --git a/board/safety/safety_volkswagen_mqb.h b/board/safety/safety_volkswagen_mqb.h index 9277e8e018..4cd9c2ec31 100644 --- a/board/safety/safety_volkswagen_mqb.h +++ b/board/safety/safety_volkswagen_mqb.h @@ -183,7 +183,7 @@ static int volkswagen_mqb_rx_hook(CANPacket_t *to_push) { // Signal: Motor_20.MO_Fahrpedalrohwert_01 if (addr == MSG_MOTOR_20) { - gas_pressed = ((GET_BYTES_04(to_push) >> 12) & 0xFFU) != 0U; + gas_pressed = ((GET_BYTES(to_push, 0, 4) >> 12) & 0xFFU) != 0U; } // Signal: Motor_14.MO_Fahrer_bremst (ECU detected brake pedal switch F63) diff --git a/board/safety_declarations.h b/board/safety_declarations.h index 6dde0d4a3a..0c40c560f0 100644 --- a/board/safety_declarations.h +++ b/board/safety_declarations.h @@ -2,12 +2,22 @@ #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)) -#define GET_BYTES_48(msg) ((msg)->data[4] | ((msg)->data[5] << 8U) | ((msg)->data[6] << 16U) | ((msg)->data[7] << 24U)) #define GET_FLAG(value, mask) (((__typeof__(mask))(value) & (mask)) == (mask)) +uint32_t GET_BYTES(const CANPacket_t *msg, int start, int len) { + uint32_t ret = 0U; + for (int i = 0; i < len; i++) { + const uint8_t shift = i * 8; + ret |= (((uint32_t)msg->data[start + i]) << shift); + } + return ret; +} + const int MAX_WRONG_COUNTERS = 5; const uint8_t MAX_MISSED_MSGS = 10U; +#define MAX_ADDR_CHECK_MSGS 3U +// used to represent floating point vehicle speed in a sample_t +#define VEHICLE_SPEED_FACTOR 100.0 // sample struct that keeps 6 samples in memory struct sample_t { @@ -57,9 +67,14 @@ typedef struct { const bool has_steer_req_tolerance; // angle cmd limits - const int angle_deg_to_can; + const float angle_deg_to_can; const struct lookup_t angle_rate_up_lookup; const struct lookup_t angle_rate_down_lookup; + const int max_angle_error; // used to limit error between meas and cmd while enabled + const float angle_error_min_speed; // minimum speed to start limiting angle error + + const bool enforce_angle_error; // enables max_angle_error check + const bool inactive_angle_is_zero; // if false, enforces angle near meas when disabled (default) } SteeringLimits; typedef struct { @@ -75,6 +90,11 @@ typedef struct { const int inactive_gas; const int max_brake; + // alternative cmd limits, ex: transmission rpm on subaru + const int max_alt; + const int min_alt; + const int inactive_alt; + // speed cmd limits const int inactive_speed; } LongitudinalLimits; @@ -92,7 +112,7 @@ typedef struct { // params and flags about checksum, counter and frequency checks for each monitored address typedef struct { // const params - const CanMsgCheck msg[3]; // check either messages (e.g. honda steer). Array MUST terminate with an empty struct to know its length. + const CanMsgCheck msg[MAX_ADDR_CHECK_MSGS]; // check either messages (e.g. honda steer) // dynamic flags bool msg_seen; 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 @@ -116,6 +136,8 @@ uint32_t get_ts_elapsed(uint32_t ts, uint32_t ts_last); int to_signed(int d, int bits); void update_sample(struct sample_t *sample, int sample_new); bool max_limit_check(int val, const int MAX, const int MIN); +bool angle_dist_to_meas_check(int val, struct sample_t *val_meas, + const int MAX_ERROR, const int MAX_VAL); bool dist_to_meas_check(int val, int val_last, struct sample_t *val_meas, const int MAX_RATE_UP, const int MAX_RATE_DOWN, const int MAX_ERROR); bool driver_limit_check(int val, int val_last, struct sample_t *val_driver, @@ -124,6 +146,7 @@ bool driver_limit_check(int val, int val_last, struct sample_t *val_driver, bool get_longitudinal_allowed(void); bool rt_rate_limit_check(int val, int val_last, const int MAX_RT_DELTA); float interpolate(struct lookup_t xy, float x); +int ROUND(float val); void gen_crc_lookup_table_8(uint8_t poly, uint8_t crc_lut[]); void gen_crc_lookup_table_16(uint16_t poly, uint16_t crc_lut[]); bool msg_allowed(CANPacket_t *to_send, const CanMsg msg_list[], int len); @@ -145,6 +168,7 @@ bool steer_angle_cmd_checks(int desired_angle, bool steer_control_enabled, const 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_alt_checks(int desired_alt, 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); @@ -177,7 +201,7 @@ bool brake_pressed_prev = false; bool regen_braking = false; bool regen_braking_prev = false; bool cruise_engaged_prev = false; -float vehicle_speed = 0; +struct sample_t vehicle_speed; bool vehicle_moving = false; bool acc_main_on = false; // referred to as "ACC off" in ISO 15622:2018 int cruise_button_prev = 0; @@ -200,7 +224,7 @@ uint32_t heartbeat_engaged_mismatches = 0; // count of mismatches between heart // for safety modes with angle steering control uint32_t ts_angle_last = 0; int desired_angle_last = 0; -struct sample_t angle_meas; // last 6 steer angles +struct sample_t angle_meas; // last 6 steer angles/curvatures // This can be set with a USB command // It enables features that allow alternative experiences, like not disengaging on gas press diff --git a/board/stm32fx/board.h b/board/stm32fx/board.h index 878c1be9ec..76593a5294 100644 --- a/board/stm32fx/board.h +++ b/board/stm32fx/board.h @@ -5,6 +5,7 @@ #include "boards/unused_funcs.h" // ///// Board definition and detection ///// // +#include "stm32fx/lladc.h" #include "drivers/harness.h" #ifdef PANDA #include "drivers/fan.h" diff --git a/board/stm32fx/clock.h b/board/stm32fx/clock.h index fb918d3125..19be574438 100644 --- a/board/stm32fx/clock.h +++ b/board/stm32fx/clock.h @@ -32,17 +32,3 @@ void clock_init(void) { // *** running on PLL *** } - -void watchdog_init(void) { - // setup watchdog - IWDG->KR = 0x5555U; - register_set(&(IWDG->PR), 0x0U, 0x7U); // divider/4 - - // 0 = 0.125 ms, let's have a 50ms watchdog - register_set(&(IWDG->RLR), (400U-1U), 0xFFFU); - IWDG->KR = 0xCCCCU; -} - -void watchdog_feed(void) { - IWDG->KR = 0xAAAAU; -} diff --git a/board/stm32fx/lladc.h b/board/stm32fx/lladc.h index 0d3e8e028a..31c17f4b25 100644 --- a/board/stm32fx/lladc.h +++ b/board/stm32fx/lladc.h @@ -5,7 +5,7 @@ #define ADCCHAN_ACCEL0 10 #define ADCCHAN_ACCEL1 11 -#define ADCCHAN_VOLTAGE 12 +#define ADCCHAN_VIN 12 #define ADCCHAN_CURRENT 13 void register_set(volatile uint32_t *addr, uint32_t val, uint32_t mask); @@ -16,9 +16,9 @@ void adc_init(void) { register_set(&(ADC1->SMPR1), ADC_SMPR1_SMP12 | ADC_SMPR1_SMP13, 0x7FFFFFFU); } -uint32_t adc_get(unsigned int channel) { +uint16_t adc_get_raw(uint8_t channel) { // Select channel - register_set(&(ADC1->JSQR), (channel << 15U), 0x3FFFFFU); + register_set(&(ADC1->JSQR), ((uint32_t) channel << 15U), 0x3FFFFFU); // Start conversion ADC1->SR &= ~(ADC_SR_JEOC); @@ -28,13 +28,6 @@ uint32_t adc_get(unsigned int channel) { return ADC1->JDR1; } -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) - // RETVAL = ADCV * s = VIN_S*1000 - // s = 1000/((4095/3.3)*(1/11)) = 8.8623046875 - - // Avoid needing floating point math, so output in mV - return (adc_get(ADCCHAN_VOLTAGE) * scale) / 1000U; +uint16_t adc_get_mV(uint8_t channel) { + return (adc_get_raw(channel) * current_board->avdd_mV) / 4095U; } diff --git a/board/stm32fx/llbxcan.h b/board/stm32fx/llbxcan.h index 0fdfd398a9..64f9905146 100644 --- a/board/stm32fx/llbxcan.h +++ b/board/stm32fx/llbxcan.h @@ -75,6 +75,44 @@ bool llcan_set_speed(CAN_TypeDef *CAN_obj, uint32_t speed, bool loopback, bool s return ret; } +void llcan_irq_disable(CAN_TypeDef *CAN_obj) { + if (CAN_obj == CAN1) { + NVIC_DisableIRQ(CAN1_TX_IRQn); + NVIC_DisableIRQ(CAN1_RX0_IRQn); + NVIC_DisableIRQ(CAN1_SCE_IRQn); + } else if (CAN_obj == CAN2) { + NVIC_DisableIRQ(CAN2_TX_IRQn); + NVIC_DisableIRQ(CAN2_RX0_IRQn); + NVIC_DisableIRQ(CAN2_SCE_IRQn); + #ifdef CAN3 + } else if (CAN_obj == CAN3) { + NVIC_DisableIRQ(CAN3_TX_IRQn); + NVIC_DisableIRQ(CAN3_RX0_IRQn); + NVIC_DisableIRQ(CAN3_SCE_IRQn); + #endif + } else { + } +} + +void llcan_irq_enable(CAN_TypeDef *CAN_obj) { + if (CAN_obj == CAN1) { + NVIC_EnableIRQ(CAN1_TX_IRQn); + NVIC_EnableIRQ(CAN1_RX0_IRQn); + NVIC_EnableIRQ(CAN1_SCE_IRQn); + } else if (CAN_obj == CAN2) { + NVIC_EnableIRQ(CAN2_TX_IRQn); + NVIC_EnableIRQ(CAN2_RX0_IRQn); + NVIC_EnableIRQ(CAN2_SCE_IRQn); + #ifdef CAN3 + } else if (CAN_obj == CAN3) { + NVIC_EnableIRQ(CAN3_TX_IRQn); + NVIC_EnableIRQ(CAN3_RX0_IRQn); + NVIC_EnableIRQ(CAN3_SCE_IRQn); + #endif + } else { + } +} + bool llcan_init(CAN_TypeDef *CAN_obj) { bool ret = true; @@ -110,23 +148,10 @@ bool llcan_init(CAN_TypeDef *CAN_obj) { // enable certain CAN interrupts register_set_bits(&(CAN_obj->IER), CAN_IER_TMEIE | CAN_IER_FMPIE0 | CAN_IER_ERRIE | CAN_IER_LECIE | CAN_IER_BOFIE | CAN_IER_EPVIE | CAN_IER_EWGIE | CAN_IER_FOVIE0 | CAN_IER_FFIE0); - if (CAN_obj == CAN1) { - NVIC_EnableIRQ(CAN1_TX_IRQn); - NVIC_EnableIRQ(CAN1_RX0_IRQn); - NVIC_EnableIRQ(CAN1_SCE_IRQn); - } else if (CAN_obj == CAN2) { - NVIC_EnableIRQ(CAN2_TX_IRQn); - NVIC_EnableIRQ(CAN2_RX0_IRQn); - NVIC_EnableIRQ(CAN2_SCE_IRQn); - #ifdef CAN3 - } else if (CAN_obj == CAN3) { - NVIC_EnableIRQ(CAN3_TX_IRQn); - NVIC_EnableIRQ(CAN3_RX0_IRQn); - NVIC_EnableIRQ(CAN3_SCE_IRQn); - #endif - } else { - print("Invalid CAN: initialization failed\n"); - } + // clear overrun flag on init + CAN_obj->RF0R &= ~(CAN_RF0R_FOVR0); + + llcan_irq_enable(CAN_obj); } return ret; } diff --git a/board/stm32fx/llexti.h b/board/stm32fx/llexti.h index d4362cd18d..6de13ab7f4 100644 --- a/board/stm32fx/llexti.h +++ b/board/stm32fx/llexti.h @@ -2,7 +2,7 @@ void EXTI_IRQ_Handler(void); void exti_irq_init(void) { SYSCFG->EXTICR[2] &= ~(SYSCFG_EXTICR3_EXTI8_Msk); - if (car_harness_status == HARNESS_STATUS_FLIPPED) { + if (harness.status == HARNESS_STATUS_FLIPPED) { // CAN2_RX current_board->enable_can_transceiver(3U, false); SYSCFG->EXTICR[2] |= (SYSCFG_EXTICR3_EXTI8_PA); diff --git a/board/stm32fx/llflash.h b/board/stm32fx/llflash.h index 61adcd4584..52f628487f 100644 --- a/board/stm32fx/llflash.h +++ b/board/stm32fx/llflash.h @@ -7,22 +7,44 @@ void flash_unlock(void) { FLASH->KEYR = 0xCDEF89AB; } -bool flash_erase_sector(uint8_t sector, bool unlocked) { +void flash_lock(void) { + FLASH->CR |= FLASH_CR_LOCK; +} + +bool flash_erase_sector(uint16_t sector) { +#ifdef BOOTSTUB // don't erase the bootloader(sector 0) - if (sector != 0 && sector < 12 && unlocked) { + uint16_t min_sector = 1U; + uint16_t max_sector = 11U; +#else + uint16_t min_sector = LOGGING_FLASH_SECTOR_A; + uint16_t max_sector = LOGGING_FLASH_SECTOR_B; +#endif + + bool ret = false; + if ((sector >= min_sector) && (sector <= max_sector) && (!flash_is_locked())) { FLASH->CR = (sector << 3) | FLASH_CR_SER; FLASH->CR |= FLASH_CR_STRT; - while (FLASH->SR & FLASH_SR_BSY); - return true; + while ((FLASH->SR & FLASH_SR_BSY) != 0U); + ret = true; } - return false; + return ret; } -void flash_write_word(void *prog_ptr, uint32_t data) { - uint32_t *pp = prog_ptr; - FLASH->CR = FLASH_CR_PSIZE_1 | FLASH_CR_PG; - *pp = data; - while (FLASH->SR & FLASH_SR_BSY); +void flash_write_word(uint32_t *prog_ptr, uint32_t data) { + #ifndef BOOTSTUB + // don't write to any region besides the logging region + if ((prog_ptr >= (uint32_t *)LOGGING_FLASH_BASE_A) && (prog_ptr < (uint32_t *)(LOGGING_FLASH_BASE_B + LOGGING_FLASH_SECTOR_SIZE))) { + #endif + + uint32_t *pp = prog_ptr; + FLASH->CR = FLASH_CR_PSIZE_1 | FLASH_CR_PG; + *pp = data; + while ((FLASH->SR & FLASH_SR_BSY) != 0U); + + #ifndef BOOTSTUB + } + #endif } void flush_write_buffer(void) { } diff --git a/board/stm32fx/llspi.h b/board/stm32fx/llspi.h index 3aa7236bd6..31e419b6f7 100644 --- a/board/stm32fx/llspi.h +++ b/board/stm32fx/llspi.h @@ -36,7 +36,7 @@ void DMA2_Stream2_IRQ_Handler(void) { ENTER_CRITICAL(); DMA2->LIFCR = DMA_LIFCR_CTCIF2; - spi_handle_rx(); + spi_rx_done(); EXIT_CRITICAL(); } @@ -60,14 +60,17 @@ void DMA2_Stream3_IRQ_Handler(void) { (void)dat; SPI1->DR = 0U; - spi_handle_tx(timed_out); + if (timed_out) { + print("SPI: TX timeout\n"); + } + + spi_tx_done(timed_out); } // ***************************** SPI init ***************************** 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) + REGISTER_INTERRUPT(DMA2_Stream2_IRQn, DMA2_Stream2_IRQ_Handler, SPI_IRQ_RATE, FAULT_INTERRUPT_RATE_SPI_DMA) + REGISTER_INTERRUPT(DMA2_Stream3_IRQn, DMA2_Stream3_IRQ_Handler, SPI_IRQ_RATE, FAULT_INTERRUPT_RATE_SPI_DMA) // Setup MOSI DMA register_set(&(DMA2_Stream2->CR), (DMA_SxCR_CHSEL_1 | DMA_SxCR_CHSEL_0 | DMA_SxCR_MINC | DMA_SxCR_TCIE), 0x1E077EFEU); diff --git a/board/stm32fx/lluart.h b/board/stm32fx/lluart.h index cc5b731349..aed99a77f0 100644 --- a/board/stm32fx/lluart.h +++ b/board/stm32fx/lluart.h @@ -21,60 +21,35 @@ void uart_tx_ring(uart_ring *q){ } 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->DR; // 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); - } - } + ENTER_CRITICAL(); - EXIT_CRITICAL(); - } -} + // Read out RX buffer + uint8_t c = q->uart->DR; // This read after reading SR clears a bunch of interrupts -void uart_send_break(uart_ring *u) { - while ((u->uart->CR1 & USART_CR1_SBK) != 0); - u->uart->CR1 |= USART_CR1_SBK; -} + uint16_t next_w_ptr = (q->w_ptr_rx + 1U) % q->rx_fifo_size; -// This function should be called on: -// * Half-transfer DMA interrupt -// * Full-transfer DMA interrupt -// * UART IDLE detection -uint32_t prev_w_index = 0; -void dma_pointer_handler(uart_ring *q, uint32_t dma_ndtr) { - ENTER_CRITICAL(); - uint32_t w_index = (q->rx_fifo_size - dma_ndtr); - - // Check for new data - if (w_index != prev_w_index){ - // Check for overflow - if ( - ((prev_w_index < q->r_ptr_rx) && (q->r_ptr_rx <= w_index)) || // No rollover - ((w_index < prev_w_index) && ((q->r_ptr_rx <= w_index) || (prev_w_index < q->r_ptr_rx))) // Rollover - ){ - // We lost data. Set the new read pointer to the oldest byte still available - q->r_ptr_rx = (w_index + 1U) % q->rx_fifo_size; - } + if ((next_w_ptr == q->r_ptr_rx) && q->overwrite) { + // overwrite mode: drop oldest byte + q->r_ptr_rx = (q->r_ptr_rx + 1U) % q->rx_fifo_size; + } - // Set write pointer - q->w_ptr_rx = w_index; + // 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); + } } - prev_w_index = w_index; EXIT_CRITICAL(); } +void uart_send_break(uart_ring *u) { + while ((u->uart->CR1 & USART_CR1_SBK) != 0); + u->uart->CR1 |= USART_CR1_SBK; +} + // This read after reading SR clears all error interrupts. We don't want compiler warnings, nor optimizations #define UART_READ_DR(uart) volatile uint8_t t = (uart)->DR; UNUSED(t); @@ -100,103 +75,28 @@ void uart_interrupt_handler(uart_ring *q) { // Send if necessary uart_tx_ring(q); - // Run DMA pointer handler if the line is idle - if(q->dma_rx && (status & USART_SR_IDLE)){ - // Reset IDLE flag - UART_READ_DR(q->uart) - - if(q == &uart_ring_gps){ - dma_pointer_handler(&uart_ring_gps, DMA2_Stream5->NDTR); - } else { - #ifdef DEBUG_UART - print("No IDLE dma_pointer_handler implemented for this UART."); - #endif - } - } - EXIT_CRITICAL(); } -void USART1_IRQ_Handler(void) { uart_interrupt_handler(&uart_ring_gps); } void USART2_IRQ_Handler(void) { uart_interrupt_handler(&uart_ring_debug); } void USART3_IRQ_Handler(void) { uart_interrupt_handler(&uart_ring_lin2); } void UART5_IRQ_Handler(void) { uart_interrupt_handler(&uart_ring_lin1); } -void DMA2_Stream5_IRQ_Handler(void) { - ENTER_CRITICAL(); - - // Handle errors - if((DMA2->HISR & DMA_HISR_TEIF5) || (DMA2->HISR & DMA_HISR_DMEIF5) || (DMA2->HISR & DMA_HISR_FEIF5)){ - #ifdef DEBUG_UART - print("Encountered UART DMA error. Clearing and restarting DMA...\n"); - #endif - - // Clear flags - DMA2->HIFCR = DMA_HIFCR_CTEIF5 | DMA_HIFCR_CDMEIF5 | DMA_HIFCR_CFEIF5; - - // Re-enable the DMA if necessary - DMA2_Stream5->CR |= DMA_SxCR_EN; - } - - // Re-calculate write pointer and reset flags - dma_pointer_handler(&uart_ring_gps, DMA2_Stream5->NDTR); - DMA2->HIFCR = DMA_HIFCR_CTCIF5 | DMA_HIFCR_CHTIF5; - - EXIT_CRITICAL(); -} - // ***************************** Hardware setup ***************************** -void dma_rx_init(uart_ring *q) { - // Initialization is UART-dependent - if(q == &uart_ring_gps){ - // DMA2, stream 5, channel 4 - - // Disable FIFO mode (enable direct) - DMA2_Stream5->FCR &= ~DMA_SxFCR_DMDIS; - - // Setup addresses - DMA2_Stream5->PAR = (uint32_t)&(USART1->DR); // Source - DMA2_Stream5->M0AR = (uint32_t)q->elems_rx; // Destination - DMA2_Stream5->NDTR = q->rx_fifo_size; // Number of bytes to copy - - // Circular, Increment memory, byte size, periph -> memory, enable - // Transfer complete, half transfer, transfer error and direct mode error interrupt enable - DMA2_Stream5->CR = DMA_SxCR_CHSEL_2 | DMA_SxCR_MINC | DMA_SxCR_CIRC | DMA_SxCR_HTIE | DMA_SxCR_TCIE | DMA_SxCR_TEIE | DMA_SxCR_DMEIE | DMA_SxCR_EN; - - // Enable DMA receiver in UART - q->uart->CR3 |= USART_CR3_DMAR; - - // Enable UART IDLE interrupt - q->uart->CR1 |= USART_CR1_IDLEIE; - - // Enable interrupt - NVIC_EnableIRQ(DMA2_Stream5_IRQn); - } else { - print("Tried to initialize RX DMA for an unsupported UART\n"); - } -} - #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_set_baud(USART_TypeDef *u, unsigned int baud) { - if (u == USART1) { - // USART1 is on APB2 - u->BRR = __USART_BRR(48000000U, baud); - } else { - u->BRR = __USART_BRR(24000000U, baud); - } + u->BRR = __USART_BRR(24000000U, baud); } void uart_init(uart_ring *q, int baud) { 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){ + 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) @@ -205,9 +105,6 @@ void uart_init(uart_ring *q, int baud) { } 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); @@ -217,9 +114,7 @@ void uart_init(uart_ring *q, int baud) { } // Enable UART interrupts - if(q->uart == USART1){ - NVIC_EnableIRQ(USART1_IRQn); - } else if (q->uart == USART2){ + if (q->uart == USART2){ NVIC_EnableIRQ(USART2_IRQn); } else if (q->uart == USART3){ NVIC_EnableIRQ(USART3_IRQn); @@ -228,10 +123,5 @@ void uart_init(uart_ring *q, int baud) { } else { // UART not used. Skip enabling interrupts } - - // Initialise RX DMA if used - if(q->dma_rx){ - dma_rx_init(q); - } } } diff --git a/board/stm32fx/peripherals.h b/board/stm32fx/peripherals.h index 3eeebf0eb6..33a1f6ffef 100644 --- a/board/stm32fx/peripherals.h +++ b/board/stm32fx/peripherals.h @@ -36,10 +36,6 @@ void common_init_gpio(void) { gpio_usb_init(); - // A9,A10: USART 1 for talking to the GPS - set_gpio_alternate(GPIOA, 9, GPIO_AF7_USART1); - set_gpio_alternate(GPIOA, 10, GPIO_AF7_USART1); - // B8,B9: CAN 1 #ifdef STM32F4 set_gpio_alternate(GPIOB, 8, GPIO_AF8_CAN1); @@ -68,7 +64,7 @@ void peripherals_init(void) { RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN; RCC->APB1ENR |= RCC_APB1ENR_USART2EN; RCC->APB1ENR |= RCC_APB1ENR_USART3EN; - #ifdef PANDA + #ifndef PEDAL RCC->APB1ENR |= RCC_APB1ENR_UART5EN; #endif RCC->APB1ENR |= RCC_APB1ENR_CAN1EN; @@ -84,7 +80,6 @@ void peripherals_init(void) { RCC->APB1ENR |= RCC_APB1ENR_TIM6EN; // interrupt timer RCC->APB1ENR |= RCC_APB1ENR_TIM12EN; // gmlan_alt RCC->APB1ENR |= RCC_APB1ENR_PWREN; // for RTC config - RCC->APB2ENR |= RCC_APB2ENR_USART1EN; RCC->AHB2ENR |= RCC_AHB2ENR_OTGFSEN; RCC->APB2ENR |= RCC_APB2ENR_TIM1EN; // clock source timer RCC->APB2ENR |= RCC_APB2ENR_ADC1EN; diff --git a/board/stm32fx/stm32fx_config.h b/board/stm32fx/stm32fx_config.h index 073cb07774..dd026e98e4 100644 --- a/board/stm32fx/stm32fx_config.h +++ b/board/stm32fx/stm32fx_config.h @@ -23,9 +23,6 @@ #define MAX_LED_FADE 8192U -// Threshold voltage (mV) for either of the SBUs to be below before deciding harness is connected -#define HARNESS_CONNECTED_THRESHOLD 2500U - #define NUM_INTERRUPTS 102U // There are 102 external interrupt sources (see stm32f413.h) #define TICK_TIMER_IRQ TIM1_BRK_TIM9_IRQn @@ -36,17 +33,25 @@ #define INTERRUPT_TIMER_IRQ TIM6_DAC_IRQn #define INTERRUPT_TIMER TIM6 +#define IND_WDG IWDG + #define PROVISION_CHUNK_ADDRESS 0x1FFF79E0U #define DEVICE_SERIAL_NUMBER_ADDRESS 0x1FFF79C0U +#define LOGGING_FLASH_SECTOR_A 10U +#define LOGGING_FLASH_SECTOR_B 11U +#define LOGGING_FLASH_BASE_A 0x080C0000U +#define LOGGING_FLASH_BASE_B 0x080E0000U +#define LOGGING_FLASH_SECTOR_SIZE 0x20000U + #include "can_definitions.h" #include "comms_definitions.h" #ifndef BOOTSTUB - #ifdef PANDA - #include "main_declarations.h" - #else + #ifdef PEDAL #include "pedal/main_declarations.h" + #else + #include "main_declarations.h" #endif #else #include "bootstub_declarations.h" @@ -63,31 +68,30 @@ #include "stm32fx/peripherals.h" #include "stm32fx/interrupt_handlers.h" #include "drivers/timers.h" -#include "stm32fx/lladc.h" #include "stm32fx/board.h" #include "stm32fx/clock.h" +#include "drivers/watchdog.h" +#include "stm32fx/llflash.h" -#if defined(PANDA) || defined(BOOTSTUB) +#if !defined(PEDAL) || defined(BOOTSTUB) #include "drivers/spi.h" #include "stm32fx/llspi.h" #endif -#if !defined(BOOTSTUB) && (defined(PANDA) || defined(PEDAL_USB)) +#if !defined(BOOTSTUB) && (!defined(PEDAL) || defined(PEDAL_USB)) #include "drivers/uart.h" #include "stm32fx/lluart.h" #endif -#if !defined(PEDAL_USB) && !defined(PEDAL) && !defined(BOOTSTUB) +#if defined(PANDA) && !defined(BOOTSTUB) #include "stm32fx/llexti.h" #endif -#ifdef BOOTSTUB - #include "stm32fx/llflash.h" -#else +#ifndef BOOTSTUB #include "stm32fx/llbxcan.h" #endif -#if defined(PANDA) || defined(BOOTSTUB) || defined(PEDAL_USB) +#if !defined(PEDAL) || defined(PEDAL_USB) || defined(BOOTSTUB) #include "stm32fx/llusb.h" #endif diff --git a/board/stm32h7/board.h b/board/stm32h7/board.h index 086db86b3c..d991e433b1 100644 --- a/board/stm32h7/board.h +++ b/board/stm32h7/board.h @@ -5,6 +5,7 @@ #include "boards/unused_funcs.h" // ///// Board definition and detection ///// // +#include "stm32h7/lladc.h" #include "drivers/harness.h" #include "drivers/fan.h" #include "stm32h7/llfan.h" diff --git a/board/stm32h7/interrupt_handlers.h b/board/stm32h7/interrupt_handlers.h index 0811ffe523..fb1298d77b 100644 --- a/board/stm32h7/interrupt_handlers.h +++ b/board/stm32h7/interrupt_handlers.h @@ -46,6 +46,7 @@ void TIM8_CC_IRQHandler(void) {handle_interrupt(TIM8_CC_IRQn);} void DMA1_Stream7_IRQHandler(void) {handle_interrupt(DMA1_Stream7_IRQn);} void TIM5_IRQHandler(void) {handle_interrupt(TIM5_IRQn);} void SPI3_IRQHandler(void) {handle_interrupt(SPI3_IRQn);} +void SPI4_IRQHandler(void) {handle_interrupt(SPI4_IRQn);} void UART4_IRQHandler(void) {handle_interrupt(UART4_IRQn);} void UART5_IRQHandler(void) {handle_interrupt(UART5_IRQn);} void TIM6_DAC_IRQHandler(void) {handle_interrupt(TIM6_DAC_IRQn);} diff --git a/board/stm32h7/lladc.h b/board/stm32h7/lladc.h index 3abfef96b7..0a52b78d37 100644 --- a/board/stm32h7/lladc.h +++ b/board/stm32h7/lladc.h @@ -1,6 +1,6 @@ // 5VOUT_S = ADC12_INP5 // VOLT_S = ADC1_INP2 -#define ADCCHAN_VOLTAGE 2 +#define ADCCHAN_VIN 2 void adc_init(void) { ADC1->CR &= ~(ADC_CR_DEEPPWD); //Reset deep-power-down mode @@ -17,13 +17,13 @@ void adc_init(void) { while(!(ADC1->ISR & ADC_ISR_ADRDY)); } -uint32_t adc_get(unsigned int channel) { - +uint16_t adc_get_raw(uint8_t channel) { ADC1->SQR1 &= ~(ADC_SQR1_L); - ADC1->SQR1 = (channel << 6U); + ADC1->SQR1 = ((uint32_t) channel << 6U); - ADC1->SMPR1 = (0x7U << (channel * 3U) ); + ADC1->SMPR1 = (0x2U << (channel * 3U)); ADC1->PCSEL_RES0 = (0x1U << channel); + ADC1->CFGR2 = (127U << ADC_CFGR2_OVSR_Pos) | (0x7U << ADC_CFGR2_OVSS_Pos) | ADC_CFGR2_ROVSE; ADC1->CR |= ADC_CR_ADSTART; while (!(ADC1->ISR & ADC_ISR_EOC)); @@ -36,14 +36,6 @@ uint32_t adc_get(unsigned int channel) { return res; } -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) * scale) / 10000U; +uint16_t adc_get_mV(uint8_t channel) { + return (adc_get_raw(channel) * current_board->avdd_mV) / 65535U; } diff --git a/board/stm32h7/llexti.h b/board/stm32h7/llexti.h index 7b7c260d8e..46d0acab67 100644 --- a/board/stm32h7/llexti.h +++ b/board/stm32h7/llexti.h @@ -1,7 +1,7 @@ void EXTI_IRQ_Handler(void); void exti_irq_init(void) { - if (car_harness_status == HARNESS_STATUS_FLIPPED) { + if (harness.status == HARNESS_STATUS_FLIPPED) { // CAN2_RX IRQ on falling edge (EXTI10) current_board->enable_can_transceiver(3U, false); SYSCFG->EXTICR[2] &= ~(SYSCFG_EXTICR3_EXTI10_Msk); diff --git a/board/stm32h7/llfdcan.h b/board/stm32h7/llfdcan.h index e5791fc528..0382e8ce2e 100644 --- a/board/stm32h7/llfdcan.h +++ b/board/stm32h7/llfdcan.h @@ -158,6 +158,34 @@ bool llcan_set_speed(FDCAN_GlobalTypeDef *CANx, uint32_t speed, uint32_t data_sp return ret; } +void llcan_irq_disable(FDCAN_GlobalTypeDef *CANx) { + if (CANx == FDCAN1) { + NVIC_DisableIRQ(FDCAN1_IT0_IRQn); + NVIC_DisableIRQ(FDCAN1_IT1_IRQn); + } else if (CANx == FDCAN2) { + NVIC_DisableIRQ(FDCAN2_IT0_IRQn); + NVIC_DisableIRQ(FDCAN2_IT1_IRQn); + } else if (CANx == FDCAN3) { + NVIC_DisableIRQ(FDCAN3_IT0_IRQn); + NVIC_DisableIRQ(FDCAN3_IT1_IRQn); + } else { + } +} + +void llcan_irq_enable(FDCAN_GlobalTypeDef *CANx) { + if (CANx == FDCAN1) { + NVIC_EnableIRQ(FDCAN1_IT0_IRQn); + NVIC_EnableIRQ(FDCAN1_IT1_IRQn); + } else if (CANx == FDCAN2) { + NVIC_EnableIRQ(FDCAN2_IT0_IRQn); + NVIC_EnableIRQ(FDCAN2_IT1_IRQn); + } else if (CANx == FDCAN3) { + NVIC_EnableIRQ(FDCAN3_IT0_IRQn); + NVIC_EnableIRQ(FDCAN3_IT1_IRQn); + } else { + } +} + bool llcan_init(FDCAN_GlobalTypeDef *CANx) { uint32_t can_number = CAN_NUM_FROM_CANIF(CANx); bool ret = fdcan_request_init(CANx); @@ -213,7 +241,7 @@ bool llcan_init(FDCAN_GlobalTypeDef *CANx) { CANx->IE &= 0x0U; // Reset all interrupts // Messages for INT0 CANx->IE |= FDCAN_IE_RF0NE; // Rx FIFO 0 new message - CANx->IE |= FDCAN_IE_PEDE | FDCAN_IE_PEAE | FDCAN_IE_BOE | FDCAN_IE_EPE | FDCAN_IE_ELOE | FDCAN_IE_TEFLE | FDCAN_IE_RF0LE; + CANx->IE |= FDCAN_IE_PEDE | FDCAN_IE_PEAE | FDCAN_IE_BOE | FDCAN_IE_EPE | FDCAN_IE_RF0LE; // Messages for INT1 (Only TFE works??) CANx->ILS |= FDCAN_ILS_TFEL; @@ -224,18 +252,7 @@ bool llcan_init(FDCAN_GlobalTypeDef *CANx) { print(CAN_NAME_FROM_CANIF(CANx)); print(" llcan_init timed out (2)!\n"); } - if (CANx == FDCAN1) { - NVIC_EnableIRQ(FDCAN1_IT0_IRQn); - NVIC_EnableIRQ(FDCAN1_IT1_IRQn); - } else if (CANx == FDCAN2) { - NVIC_EnableIRQ(FDCAN2_IT0_IRQn); - NVIC_EnableIRQ(FDCAN2_IT1_IRQn); - } else if (CANx == FDCAN3) { - NVIC_EnableIRQ(FDCAN3_IT0_IRQn); - NVIC_EnableIRQ(FDCAN3_IT1_IRQn); - } else { - print("Invalid CAN: initialization failed\n"); - } + llcan_irq_enable(CANx); } else { print(CAN_NAME_FROM_CANIF(CANx)); print(" llcan_init timed out (1)!\n"); @@ -244,7 +261,9 @@ bool llcan_init(FDCAN_GlobalTypeDef *CANx) { } void llcan_clear_send(FDCAN_GlobalTypeDef *CANx) { - CANx->TXBCR = 0xFFFFU; // Abort message transmission on error interrupt - // Clear error interrupts - CANx->IR |= (FDCAN_IR_PED | FDCAN_IR_PEA | FDCAN_IR_EW | FDCAN_IR_EP | FDCAN_IR_ELO | FDCAN_IR_BO | FDCAN_IR_TEFL | FDCAN_IR_RF0L); + // from datasheet: "Transmit cancellation is not intended for Tx FIFO operation." + // so we need to clear pending transmission manually by resetting FDCAN core + CANx->IR |= 0x3FCFFFFFU; // clear all interrupts + bool ret = llcan_init(CANx); + UNUSED(ret); } diff --git a/board/stm32h7/llflash.h b/board/stm32h7/llflash.h index b95011a9ed..800d9414b4 100644 --- a/board/stm32h7/llflash.h +++ b/board/stm32h7/llflash.h @@ -7,27 +7,49 @@ void flash_unlock(void) { FLASH->KEYR1 = 0xCDEF89AB; } -bool flash_erase_sector(uint8_t sector, bool unlocked) { - // don't erase the bootloader(sector 0) - if (sector != 0 && sector < 8 && unlocked) { +void flash_lock(void) { + FLASH->CR1 |= FLASH_CR_LOCK; +} + +bool flash_erase_sector(uint16_t sector) { + #ifdef BOOTSTUB + // don't erase the bootloader(sector 0) + uint16_t min_sector = 1U; + uint16_t max_sector = 7U; + #else + uint16_t min_sector = LOGGING_FLASH_SECTOR_A; + uint16_t max_sector = LOGGING_FLASH_SECTOR_B; + #endif + + bool ret = false; + if ((sector >= min_sector) && (sector <= max_sector) && (!flash_is_locked())) { FLASH->CR1 = (sector << 8) | FLASH_CR_SER; FLASH->CR1 |= FLASH_CR_START; - while (FLASH->SR1 & FLASH_SR_QW); - return true; + while ((FLASH->SR1 & FLASH_SR_QW) != 0U); + ret = true; } - return false; + return ret; } -void flash_write_word(void *prog_ptr, uint32_t data) { - uint32_t *pp = prog_ptr; - FLASH->CR1 |= FLASH_CR_PG; - *pp = data; - while (FLASH->SR1 & FLASH_SR_QW); +void flash_write_word(uint32_t *prog_ptr, uint32_t data) { + #ifndef BOOTSTUB + // don't write to any region besides the logging region + if ((prog_ptr >= (uint32_t *)LOGGING_FLASH_BASE_A) && (prog_ptr < (uint32_t *)(LOGGING_FLASH_BASE_B + LOGGING_FLASH_SECTOR_SIZE))) { + #endif + + uint32_t *pp = prog_ptr; + FLASH->CR1 |= FLASH_CR_PG; + *pp = data; + while ((FLASH->SR1 & FLASH_SR_QW) != 0U); + + #ifndef BOOTSTUB + } + #endif } void flush_write_buffer(void) { - if (FLASH->SR1 & FLASH_SR_WBNE) { + if ((FLASH->SR1 & FLASH_SR_WBNE) != 0U) { FLASH->CR1 |= FLASH_CR_FW; - while (FLASH->SR1 & FLASH_CR_FW); + while ((FLASH->SR1 & FLASH_CR_FW) != 0U); } } diff --git a/board/stm32h7/llspi.h b/board/stm32h7/llspi.h index 9e21659b83..1947803ac2 100644 --- a/board/stm32h7/llspi.h +++ b/board/stm32h7/llspi.h @@ -11,6 +11,10 @@ void llspi_mosi_dma(uint8_t *addr, int len) { (void)dat; } + // clear all pending + SPI4->IFCR |= (0x1FFU << 3U); + register_set(&(SPI4->IER), 0, 0x3FFU); + // setup destination and length register_set(&(DMA2_Stream2->M0AR), (uint32_t)addr, 0xFFFFFFFFU); DMA2_Stream2->NDTR = len; @@ -23,59 +27,61 @@ void llspi_mosi_dma(uint8_t *addr, int len) { // panda -> master DMA start void llspi_miso_dma(uint8_t *addr, int len) { - // disable DMA + // disable DMA + SPI DMA2_Stream3->CR &= ~DMA_SxCR_EN; register_clear_bits(&(SPI4->CFG1), SPI_CFG1_TXDMAEN); + register_clear_bits(&(SPI4->CR1), SPI_CR1_SPE); // 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; + SPI4->IFCR |= (0x1FFU << 3U); - // enable DMA + // setup interrupt on TXC + register_set(&(SPI4->IER), (1U << SPI_IER_EOTIE_Pos), 0x3FFU); + + // enable DMA + SPI register_set_bits(&(SPI4->CFG1), SPI_CFG1_TXDMAEN); DMA2_Stream3->CR |= DMA_SxCR_EN; + register_set_bits(&(SPI4->CR1), SPI_CR1_SPE); } // 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(); + spi_rx_done(); } // panda -> master DMA finished void DMA2_Stream3_IRQ_Handler(void) { - // Clear interrupt flag + ENTER_CRITICAL(); + DMA2->LIFCR = DMA_LIFCR_CTCIF3; + spi_tx_dma_done = true; - // 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; + EXIT_CRITICAL(); +} - spi_handle_tx(timed_out); +// panda TX finished +void SPI4_IRQ_Handler(void) { + // clear flag + SPI4->IFCR |= (0x1FFU << 3U); + + if (spi_tx_dma_done && ((SPI4->SR & SPI_SR_TXC) != 0)) { + spi_tx_dma_done = false; + spi_tx_done(false); + } } 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) + REGISTER_INTERRUPT(SPI4_IRQn, SPI4_IRQ_Handler, (SPI_IRQ_RATE * 2U), FAULT_INTERRUPT_RATE_SPI) + REGISTER_INTERRUPT(DMA2_Stream2_IRQn, DMA2_Stream2_IRQ_Handler, SPI_IRQ_RATE, FAULT_INTERRUPT_RATE_SPI_DMA) + REGISTER_INTERRUPT(DMA2_Stream3_IRQn, DMA2_Stream3_IRQ_Handler, SPI_IRQ_RATE, FAULT_INTERRUPT_RATE_SPI_DMA) // Setup MOSI DMA register_set(&(DMAMUX1_Channel10->CCR), 83U, 0xFFFFFFFFU); @@ -88,6 +94,7 @@ void llspi_init(void) { register_set(&(DMA2_Stream3->PAR), (uint32_t)&(SPI4->TXDR), 0xFFFFFFFFU); // Enable SPI + register_set(&(SPI4->IER), 0, 0x3FFU); 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); @@ -95,4 +102,5 @@ void llspi_init(void) { NVIC_EnableIRQ(DMA2_Stream2_IRQn); NVIC_EnableIRQ(DMA2_Stream3_IRQn); + NVIC_EnableIRQ(SPI4_IRQn); } diff --git a/board/stm32h7/lluart.h b/board/stm32h7/lluart.h index 89e47606c8..640ea18d13 100644 --- a/board/stm32h7/lluart.h +++ b/board/stm32h7/lluart.h @@ -1,7 +1,3 @@ - -void dma_pointer_handler(uart_ring *q, uint32_t dma_ndtr) { UNUSED(q); UNUSED(dma_ndtr); } -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) @@ -9,24 +5,28 @@ void dma_rx_init(uart_ring *q) { UNUSED(q); } 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); - } - } + 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; - EXIT_CRITICAL(); + if ((next_w_ptr == q->r_ptr_rx) && q->overwrite) { + // overwrite mode: drop oldest byte + q->r_ptr_rx = (q->r_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){ @@ -90,16 +90,6 @@ void uart_interrupt_handler(uart_ring *q) { // 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(); } @@ -109,10 +99,6 @@ 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; @@ -121,10 +107,5 @@ void uart_init(uart_ring *q, int baud) { // 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 7e3b2e5b16..cf397644ec 100644 --- a/board/stm32h7/peripherals.h +++ b/board/stm32h7/peripherals.h @@ -125,10 +125,15 @@ void peripherals_init(void) { RCC->APB2ENR |= RCC_APB2ENR_TIM1EN; // clock source timer RCC->APB1HENR |= RCC_APB1HENR_FDCANEN; // FDCAN core enable - RCC->AHB1ENR |= RCC_AHB1ENR_ADC12EN; // Enable ADC clocks + RCC->AHB1ENR |= RCC_AHB1ENR_ADC12EN; // Enable ADC12 clocks RCC->APB4ENR |= RCC_APB4ENR_SYSCFGEN; +#ifdef PANDA_JUNGLE + RCC->AHB3ENR |= RCC_AHB3ENR_SDMMC1EN; // SDMMC + RCC->AHB4ENR |= RCC_AHB4ENR_ADC3EN; // Enable ADC3 clocks +#endif + // HS USB enable, also LP is needed for CSleep state(__WFI()) RCC->AHB1ENR |= RCC_AHB1ENR_USB1OTGHSEN; RCC->AHB1LPENR |= RCC_AHB1LPENR_USB1OTGHSLPEN; diff --git a/board/stm32h7/stm32h7_config.h b/board/stm32h7/stm32h7_config.h index 6c4499cdee..f00b5794de 100644 --- a/board/stm32h7/stm32h7_config.h +++ b/board/stm32h7/stm32h7_config.h @@ -30,9 +30,6 @@ separate IRQs for RX and TX. #define MAX_LED_FADE 10240U -// Threshold voltage (mV) for either of the SBUs to be below before deciding harness is connected -#define HARNESS_CONNECTED_THRESHOLD 40000U - // There are 163 external interrupt sources (see stm32f735xx.h) #define NUM_INTERRUPTS 163U @@ -44,9 +41,17 @@ separate IRQs for RX and TX. #define INTERRUPT_TIMER_IRQ TIM6_DAC_IRQn #define INTERRUPT_TIMER TIM6 +#define IND_WDG IWDG1 + #define PROVISION_CHUNK_ADDRESS 0x080FFFE0U #define DEVICE_SERIAL_NUMBER_ADDRESS 0x080FFFC0U +#define LOGGING_FLASH_SECTOR_A 5U +#define LOGGING_FLASH_SECTOR_B 6U +#define LOGGING_FLASH_BASE_A 0x080A0000U +#define LOGGING_FLASH_BASE_B 0x080C0000U +#define LOGGING_FLASH_SECTOR_SIZE 0x20000U + #include "can_definitions.h" #include "comms_definitions.h" @@ -67,9 +72,10 @@ separate IRQs for RX and TX. #include "stm32h7/peripherals.h" #include "stm32h7/interrupt_handlers.h" #include "drivers/timers.h" -#include "stm32h7/lladc.h" +#include "drivers/watchdog.h" +#include "stm32h7/llflash.h" -#if !defined(BOOTSTUB) && defined(PANDA) +#if !defined(BOOTSTUB) #include "drivers/uart.h" #include "stm32h7/lluart.h" #endif @@ -81,9 +87,7 @@ separate IRQs for RX and TX. #include "stm32h7/llexti.h" #endif -#ifdef BOOTSTUB - #include "stm32h7/llflash.h" -#else +#ifndef BOOTSTUB #include "stm32h7/llfdcan.h" #endif @@ -94,7 +98,7 @@ separate IRQs for RX and TX. 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; + GPIOA->MODER = 0xAB000000; GPIOB->MODER = 0; GPIOC->MODER = 0; GPIOD->MODER = 0; GPIOE->MODER = 0; GPIOF->MODER = 0; GPIOG->MODER = 0; GPIOH->MODER = 0; GPIOA->ODR = 0; GPIOB->ODR = 0; GPIOC->ODR = 0; GPIOD->ODR = 0; GPIOE->ODR = 0; GPIOF->ODR = 0; GPIOG->ODR = 0; GPIOH->ODR = 0; GPIOA->PUPDR = 0; GPIOB->PUPDR = 0; GPIOC->PUPDR = 0; GPIOD->PUPDR = 0; GPIOE->PUPDR = 0; GPIOF->PUPDR = 0; GPIOG->PUPDR = 0; GPIOH->PUPDR = 0; } diff --git a/drivers/spi/.gitignore b/drivers/spi/.gitignore new file mode 100644 index 0000000000..49dc09d89f --- /dev/null +++ b/drivers/spi/.gitignore @@ -0,0 +1,7 @@ +spidev.c +*.ko +*.cmd +*.mod +*.symvers +*.order +*.mod.c diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile new file mode 100644 index 0000000000..9a2a0abf67 --- /dev/null +++ b/drivers/spi/Makefile @@ -0,0 +1,14 @@ +obj-m += spidev_panda.o + +KDIR := /lib/modules/$(shell uname -r)/build +PWD := $(shell pwd) + +# GCC9 bug, apply kernel patch instead? +# https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=0b999ae3614d09d97a1575936bcee884f912b10e +ccflags-y := -Wno-missing-attributes + +default: + $(MAKE) -C $(KDIR) M=$(PWD) modules + +clean: + $(MAKE) -C $(KDIR) M=$(PWD) clean diff --git a/drivers/spi/load.sh b/drivers/spi/load.sh new file mode 100755 index 0000000000..b8efdbf708 --- /dev/null +++ b/drivers/spi/load.sh @@ -0,0 +1,27 @@ +#!/bin/bash +set -e + +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" +cd $DIR + +make -j8 + +sudo su -c "echo spi0.0 > /sys/bus/spi/drivers/spidev/unbind" || true + +sudo dmesg -C + +#sudo rmmod -f spidev_panda +sudo rmmod spidev_panda || true +sudo insmod spidev_panda.ko + +sudo su -c "echo 'file $DIR/spidev_panda.c +p' > /sys/kernel/debug/dynamic_debug/control" +sudo su -c "echo 'file $DIR/spi_panda.h +p' > /sys/kernel/debug/dynamic_debug/control" + +sudo lsmod + +echo "loaded" +ls -la /dev/spi* +sudo chmod 666 /dev/spi* +ipython -c "from panda import Panda; print(Panda.list())" +KERN=1 ipython -c "from panda import Panda; print(Panda.list())" +dmesg diff --git a/drivers/spi/patch b/drivers/spi/patch new file mode 100644 index 0000000000..a146303424 --- /dev/null +++ b/drivers/spi/patch @@ -0,0 +1,33 @@ +53c53,54 +< #define SPIDEV_MAJOR 153 /* assigned */ +--- +> int SPIDEV_MAJOR = 0; +> //#define SPIDEV_MAJOR 153 /* assigned */ +354a356,358 +> +> #include "spi_panda.h" +> +413,414c417,419 +< retval = __put_user((spi->mode & SPI_LSB_FIRST) ? 1 : 0, +< (__u8 __user *)arg); +--- +> retval = panda_transfer(spidev, spi, arg); +> //retval = __put_user((spi->mode & SPI_LSB_FIRST) ? 1 : 0, +> // (__u8 __user *)arg); +697,698d701 +< { .compatible = "rohm,dh2228fv" }, +< { .compatible = "lineartechnology,ltc2488" }, +831c834 +< .name = "spidev", +--- +> .name = "spidev_panda", +856c859 +< status = register_chrdev(SPIDEV_MAJOR, "spi", &spidev_fops); +--- +> status = register_chrdev(0, "spi", &spidev_fops); +860c863,865 +< spidev_class = class_create(THIS_MODULE, "spidev"); +--- +> SPIDEV_MAJOR = status; +> +> spidev_class = class_create(THIS_MODULE, "spidev_panda"); diff --git a/drivers/spi/pull-src.sh b/drivers/spi/pull-src.sh new file mode 100755 index 0000000000..f74b94b9e7 --- /dev/null +++ b/drivers/spi/pull-src.sh @@ -0,0 +1,12 @@ +#!/usr/bin/bash +set -e + +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" +cd $DIR + +rm -f spidev.c +wget https://raw.githubusercontent.com/commaai/agnos-kernel-sdm845/master/drivers/spi/spidev.c + +# diff spidev.c spidev_panda.c > patch +# git diff --no-index spidev.c spidev_panda.c +patch -o spidev_panda.c spidev.c -i patch diff --git a/drivers/spi/spi_panda.h b/drivers/spi/spi_panda.h new file mode 100644 index 0000000000..c7da681fc2 --- /dev/null +++ b/drivers/spi/spi_panda.h @@ -0,0 +1,160 @@ +#include +#include +#include + +#define SPI_SYNC 0x5AU +#define SPI_HACK 0x79U +#define SPI_DACK 0x85U +#define SPI_NACK 0x1FU +#define SPI_CHECKSUM_START 0xABU + +struct __attribute__((packed)) spi_header { + u8 sync; + u8 endpoint; + uint16_t tx_len; + uint16_t max_rx_len; +}; + +struct spi_panda_transfer { + __u64 rx_buf; + __u64 tx_buf; + __u32 tx_length; + __u32 rx_length_max; + __u32 timeout; + __u8 endpoint; + __u8 expect_disconnect; +}; + +static u8 panda_calc_checksum(u8 *buf, u16 length) { + int i; + u8 checksum = SPI_CHECKSUM_START; + for (i = 0U; i < length; i++) { + checksum ^= buf[i]; + } + return checksum; +} + +static long panda_wait_for_ack(struct spidev_data *spidev, u8 ack_val, u8 length) { + int i; + int ret; + for (i = 0; i < 1000; i++) { + ret = spidev_sync_read(spidev, length); + if (ret < 0) { + return ret; + } + + if (spidev->rx_buffer[0] == ack_val) { + return 0; + } else if (spidev->rx_buffer[0] == SPI_NACK) { + return -2; + } + if (i > 20) usleep_range(10, 20); + } + return -1; +} + +static long panda_transfer_raw(struct spidev_data *spidev, struct spi_device *spi, unsigned long arg) { + u16 rx_len; + long retval = -1; + struct spi_header header; + struct spi_panda_transfer pt; + + struct spi_transfer t = { + .len = 0, + .tx_buf = spidev->tx_buffer, + .rx_buf = spidev->rx_buffer, + .speed_hz = spidev->spi->max_speed_hz, + }; + + struct spi_message m; + spi_message_init(&m); + spi_message_add_tail(&t, &m); + + // read struct from user + if (!access_ok(VERIFY_WRITE, arg, sizeof(pt))) { + return -1; + } + if (copy_from_user(&pt, (void __user *)arg, sizeof(pt))) { + return -1; + } + dev_dbg(&spi->dev, "ep: %d, tx len: %d\n", pt.endpoint, pt.tx_length); + + // send header + header.sync = 0x5a; + header.endpoint = pt.endpoint; + header.tx_len = pt.tx_length; + header.max_rx_len = pt.rx_length_max; + memcpy(spidev->tx_buffer, &header, sizeof(header)); + spidev->tx_buffer[sizeof(header)] = panda_calc_checksum(spidev->tx_buffer, sizeof(header)); + + t.len = sizeof(header) + 1; + retval = spidev_sync(spidev, &m); + if (retval < 0) { + dev_dbg(&spi->dev, "spi xfer failed %ld\n", retval); + return retval; + } + + // wait for ACK + retval = panda_wait_for_ack(spidev, SPI_HACK, 1); + if (retval < 0) { + dev_dbg(&spi->dev, "no header ack %ld\n", retval); + return retval; + } + + // send data + dev_dbg(&spi->dev, "sending data\n"); + retval = copy_from_user(spidev->tx_buffer, (const u8 __user *)(uintptr_t)pt.tx_buf, pt.tx_length); + spidev->tx_buffer[pt.tx_length] = panda_calc_checksum(spidev->tx_buffer, pt.tx_length); + t.len = pt.tx_length + 1; + retval = spidev_sync(spidev, &m); + + if (pt.expect_disconnect) { + return 0; + } + + // wait for ACK + retval = panda_wait_for_ack(spidev, SPI_DACK, 3); + if (retval < 0) { + dev_dbg(&spi->dev, "no data ack\n"); + return retval; + } + + // get response + t.rx_buf = spidev->rx_buffer + 3; + rx_len = (spidev->rx_buffer[2] << 8) | (spidev->rx_buffer[1]); + dev_dbg(&spi->dev, "rx len %u\n", rx_len); + if (rx_len > pt.rx_length_max) { + dev_dbg(&spi->dev, "RX len greater than max\n"); + return -1; + } + + // do the read + t.len = rx_len + 1; + retval = spidev_sync(spidev, &m); + if (retval < 0) { + dev_dbg(&spi->dev, "spi xfer failed %ld\n", retval); + return retval; + } + if (panda_calc_checksum(spidev->rx_buffer, 3 + rx_len + 1) != 0) { + dev_dbg(&spi->dev, "bad checksum\n"); + return -1; + } + + retval = copy_to_user((u8 __user *)(uintptr_t)pt.rx_buf, spidev->rx_buffer + 3, rx_len); + + return rx_len; +} + +static long panda_transfer(struct spidev_data *spidev, struct spi_device *spi, unsigned long arg) { + int i; + int ret; + dev_dbg(&spi->dev, "=== XFER start ===\n"); + for (i = 0; i < 20; i++) { + ret = panda_transfer_raw(spidev, spi, arg); + if (ret >= 0) { + break; + } + } + dev_dbg(&spi->dev, "took %d tries\n", i+1); + return ret; +} diff --git a/drivers/spi/spidev_panda.c b/drivers/spi/spidev_panda.c new file mode 100644 index 0000000000..f21fe3387b --- /dev/null +++ b/drivers/spi/spidev_panda.c @@ -0,0 +1,891 @@ +/* + * Simple synchronous userspace interface to SPI devices + * + * Copyright (C) 2006 SWAPP + * Andrea Paterniani + * Copyright (C) 2007 David Brownell (simplification, cleanup) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + + +/* + * This supports access to SPI devices using normal userspace I/O calls. + * Note that while traditional UNIX/POSIX I/O semantics are half duplex, + * and often mask message boundaries, full SPI support requires full duplex + * transfers. There are several kinds of internal message boundaries to + * handle chipselect management and other protocol options. + * + * SPI has a character major number assigned. We allocate minor numbers + * dynamically using a bitmask. You must use hotplug tools, such as udev + * (or mdev with busybox) to create and destroy the /dev/spidevB.C device + * nodes, since there is no fixed association of minor numbers with any + * particular SPI bus or device. + */ +int SPIDEV_MAJOR = 0; +//#define SPIDEV_MAJOR 153 /* assigned */ +#define N_SPI_MINORS 32 /* ... up to 256 */ + +static DECLARE_BITMAP(minors, N_SPI_MINORS); + + +/* Bit masks for spi_device.mode management. Note that incorrect + * settings for some settings can cause *lots* of trouble for other + * devices on a shared bus: + * + * - CS_HIGH ... this device will be active when it shouldn't be + * - 3WIRE ... when active, it won't behave as it should + * - NO_CS ... there will be no explicit message boundaries; this + * is completely incompatible with the shared bus model + * - READY ... transfers may proceed when they shouldn't. + * + * REVISIT should changing those flags be privileged? + */ +#define SPI_MODE_MASK (SPI_CPHA | SPI_CPOL | SPI_CS_HIGH \ + | SPI_LSB_FIRST | SPI_3WIRE | SPI_LOOP \ + | SPI_NO_CS | SPI_READY | SPI_TX_DUAL \ + | SPI_TX_QUAD | SPI_RX_DUAL | SPI_RX_QUAD) + +struct spidev_data { + dev_t devt; + spinlock_t spi_lock; + struct spi_device *spi; + struct list_head device_entry; + + /* TX/RX buffers are NULL unless this device is open (users > 0) */ + struct mutex buf_lock; + unsigned users; + u8 *tx_buffer; + u8 *rx_buffer; + u32 speed_hz; +}; + +static LIST_HEAD(device_list); +static DEFINE_MUTEX(device_list_lock); + +static unsigned bufsiz = 4096; +module_param(bufsiz, uint, S_IRUGO); +MODULE_PARM_DESC(bufsiz, "data bytes in biggest supported SPI message"); + +/*-------------------------------------------------------------------------*/ + +static ssize_t +spidev_sync(struct spidev_data *spidev, struct spi_message *message) +{ + DECLARE_COMPLETION_ONSTACK(done); + int status; + struct spi_device *spi; + + spin_lock_irq(&spidev->spi_lock); + spi = spidev->spi; + spin_unlock_irq(&spidev->spi_lock); + + if (spi == NULL) + status = -ESHUTDOWN; + else + status = spi_sync(spi, message); + + if (status == 0) + status = message->actual_length; + + return status; +} + +static inline ssize_t +spidev_sync_write(struct spidev_data *spidev, size_t len) +{ + struct spi_transfer t = { + .tx_buf = spidev->tx_buffer, + .len = len, + .speed_hz = spidev->speed_hz, + }; + struct spi_message m; + + spi_message_init(&m); + spi_message_add_tail(&t, &m); + return spidev_sync(spidev, &m); +} + +static inline ssize_t +spidev_sync_read(struct spidev_data *spidev, size_t len) +{ + struct spi_transfer t = { + .rx_buf = spidev->rx_buffer, + .len = len, + .speed_hz = spidev->speed_hz, + }; + struct spi_message m; + + spi_message_init(&m); + spi_message_add_tail(&t, &m); + return spidev_sync(spidev, &m); +} + +/*-------------------------------------------------------------------------*/ + +/* Read-only message with current device setup */ +static ssize_t +spidev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) +{ + struct spidev_data *spidev; + ssize_t status = 0; + + /* chipselect only toggles at start or end of operation */ + if (count > bufsiz) + return -EMSGSIZE; + + spidev = filp->private_data; + + mutex_lock(&spidev->buf_lock); + status = spidev_sync_read(spidev, count); + if (status > 0) { + unsigned long missing; + + missing = copy_to_user(buf, spidev->rx_buffer, status); + if (missing == status) + status = -EFAULT; + else + status = status - missing; + } + mutex_unlock(&spidev->buf_lock); + + return status; +} + +/* Write-only message with current device setup */ +static ssize_t +spidev_write(struct file *filp, const char __user *buf, + size_t count, loff_t *f_pos) +{ + struct spidev_data *spidev; + ssize_t status = 0; + unsigned long missing; + + /* chipselect only toggles at start or end of operation */ + if (count > bufsiz) + return -EMSGSIZE; + + spidev = filp->private_data; + + mutex_lock(&spidev->buf_lock); + missing = copy_from_user(spidev->tx_buffer, buf, count); + if (missing == 0) + status = spidev_sync_write(spidev, count); + else + status = -EFAULT; + mutex_unlock(&spidev->buf_lock); + + return status; +} + +static int spidev_message(struct spidev_data *spidev, + struct spi_ioc_transfer *u_xfers, unsigned n_xfers) +{ + struct spi_message msg; + struct spi_transfer *k_xfers; + struct spi_transfer *k_tmp; + struct spi_ioc_transfer *u_tmp; + unsigned n, total, tx_total, rx_total; + u8 *tx_buf, *rx_buf; + int status = -EFAULT; + + spi_message_init(&msg); + k_xfers = kcalloc(n_xfers, sizeof(*k_tmp), GFP_KERNEL); + if (k_xfers == NULL) + return -ENOMEM; + + /* Construct spi_message, copying any tx data to bounce buffer. + * We walk the array of user-provided transfers, using each one + * to initialize a kernel version of the same transfer. + */ + tx_buf = spidev->tx_buffer; + rx_buf = spidev->rx_buffer; + total = 0; + tx_total = 0; + rx_total = 0; + for (n = n_xfers, k_tmp = k_xfers, u_tmp = u_xfers; + n; + n--, k_tmp++, u_tmp++) { + k_tmp->len = u_tmp->len; + + total += k_tmp->len; + /* Since the function returns the total length of transfers + * on success, restrict the total to positive int values to + * avoid the return value looking like an error. Also check + * each transfer length to avoid arithmetic overflow. + */ + if (total > INT_MAX || k_tmp->len > INT_MAX) { + status = -EMSGSIZE; + goto done; + } + + if (u_tmp->rx_buf) { + /* this transfer needs space in RX bounce buffer */ + rx_total += k_tmp->len; + if (rx_total > bufsiz) { + status = -EMSGSIZE; + goto done; + } + k_tmp->rx_buf = rx_buf; + if (!access_ok(VERIFY_WRITE, (u8 __user *) + (uintptr_t) u_tmp->rx_buf, + u_tmp->len)) + goto done; + rx_buf += k_tmp->len; + } + if (u_tmp->tx_buf) { + /* this transfer needs space in TX bounce buffer */ + tx_total += k_tmp->len; + if (tx_total > bufsiz) { + status = -EMSGSIZE; + goto done; + } + k_tmp->tx_buf = tx_buf; + if (copy_from_user(tx_buf, (const u8 __user *) + (uintptr_t) u_tmp->tx_buf, + u_tmp->len)) + goto done; + tx_buf += k_tmp->len; + } + + k_tmp->cs_change = !!u_tmp->cs_change; + k_tmp->tx_nbits = u_tmp->tx_nbits; + k_tmp->rx_nbits = u_tmp->rx_nbits; + k_tmp->bits_per_word = u_tmp->bits_per_word; + k_tmp->delay_usecs = u_tmp->delay_usecs; + k_tmp->speed_hz = u_tmp->speed_hz; + if (!k_tmp->speed_hz) + k_tmp->speed_hz = spidev->speed_hz; +#ifdef VERBOSE + dev_dbg(&spidev->spi->dev, + " xfer len %u %s%s%s%dbits %u usec %uHz\n", + u_tmp->len, + u_tmp->rx_buf ? "rx " : "", + u_tmp->tx_buf ? "tx " : "", + u_tmp->cs_change ? "cs " : "", + u_tmp->bits_per_word ? : spidev->spi->bits_per_word, + u_tmp->delay_usecs, + u_tmp->speed_hz ? : spidev->spi->max_speed_hz); +#endif + spi_message_add_tail(k_tmp, &msg); + } + + status = spidev_sync(spidev, &msg); + if (status < 0) + goto done; + + /* copy any rx data out of bounce buffer */ + rx_buf = spidev->rx_buffer; + for (n = n_xfers, u_tmp = u_xfers; n; n--, u_tmp++) { + if (u_tmp->rx_buf) { + if (__copy_to_user((u8 __user *) + (uintptr_t) u_tmp->rx_buf, rx_buf, + u_tmp->len)) { + status = -EFAULT; + goto done; + } + rx_buf += u_tmp->len; + } + } + status = total; + +done: + kfree(k_xfers); + return status; +} + +static struct spi_ioc_transfer * +spidev_get_ioc_message(unsigned int cmd, struct spi_ioc_transfer __user *u_ioc, + unsigned *n_ioc) +{ + struct spi_ioc_transfer *ioc; + u32 tmp; + + /* Check type, command number and direction */ + if (_IOC_TYPE(cmd) != SPI_IOC_MAGIC + || _IOC_NR(cmd) != _IOC_NR(SPI_IOC_MESSAGE(0)) + || _IOC_DIR(cmd) != _IOC_WRITE) + return ERR_PTR(-ENOTTY); + + tmp = _IOC_SIZE(cmd); + if ((tmp % sizeof(struct spi_ioc_transfer)) != 0) + return ERR_PTR(-EINVAL); + *n_ioc = tmp / sizeof(struct spi_ioc_transfer); + if (*n_ioc == 0) + return NULL; + + /* copy into scratch area */ + ioc = kmalloc(tmp, GFP_KERNEL); + if (!ioc) + return ERR_PTR(-ENOMEM); + if (__copy_from_user(ioc, u_ioc, tmp)) { + kfree(ioc); + return ERR_PTR(-EFAULT); + } + return ioc; +} + + +#include "spi_panda.h" + +static long +spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + int err = 0; + int retval = 0; + struct spidev_data *spidev; + struct spi_device *spi; + u32 tmp; + unsigned n_ioc; + struct spi_ioc_transfer *ioc; + + /* Check type and command number */ + if (_IOC_TYPE(cmd) != SPI_IOC_MAGIC) + return -ENOTTY; + + /* Check access direction once here; don't repeat below. + * IOC_DIR is from the user perspective, while access_ok is + * from the kernel perspective; so they look reversed. + */ + if (_IOC_DIR(cmd) & _IOC_READ) + err = !access_ok(VERIFY_WRITE, + (void __user *)arg, _IOC_SIZE(cmd)); + if (err == 0 && _IOC_DIR(cmd) & _IOC_WRITE) + err = !access_ok(VERIFY_READ, + (void __user *)arg, _IOC_SIZE(cmd)); + if (err) + return -EFAULT; + + /* guard against device removal before, or while, + * we issue this ioctl. + */ + spidev = filp->private_data; + spin_lock_irq(&spidev->spi_lock); + spi = spi_dev_get(spidev->spi); + spin_unlock_irq(&spidev->spi_lock); + + if (spi == NULL) + return -ESHUTDOWN; + + /* use the buffer lock here for triple duty: + * - prevent I/O (from us) so calling spi_setup() is safe; + * - prevent concurrent SPI_IOC_WR_* from morphing + * data fields while SPI_IOC_RD_* reads them; + * - SPI_IOC_MESSAGE needs the buffer locked "normally". + */ + mutex_lock(&spidev->buf_lock); + + switch (cmd) { + /* read requests */ + case SPI_IOC_RD_MODE: + retval = __put_user(spi->mode & SPI_MODE_MASK, + (__u8 __user *)arg); + break; + case SPI_IOC_RD_MODE32: + retval = __put_user(spi->mode & SPI_MODE_MASK, + (__u32 __user *)arg); + break; + case SPI_IOC_RD_LSB_FIRST: + retval = panda_transfer(spidev, spi, arg); + //retval = __put_user((spi->mode & SPI_LSB_FIRST) ? 1 : 0, + // (__u8 __user *)arg); + break; + case SPI_IOC_RD_BITS_PER_WORD: + retval = __put_user(spi->bits_per_word, (__u8 __user *)arg); + break; + case SPI_IOC_RD_MAX_SPEED_HZ: + retval = __put_user(spidev->speed_hz, (__u32 __user *)arg); + break; + + /* write requests */ + case SPI_IOC_WR_MODE: + case SPI_IOC_WR_MODE32: + if (cmd == SPI_IOC_WR_MODE) + retval = __get_user(tmp, (u8 __user *)arg); + else + retval = __get_user(tmp, (u32 __user *)arg); + if (retval == 0) { + u32 save = spi->mode; + + if (tmp & ~SPI_MODE_MASK) { + retval = -EINVAL; + break; + } + + tmp |= spi->mode & ~SPI_MODE_MASK; + spi->mode = (u16)tmp; + retval = spi_setup(spi); + if (retval < 0) + spi->mode = save; + else + dev_dbg(&spi->dev, "spi mode %x\n", tmp); + } + break; + case SPI_IOC_WR_LSB_FIRST: + retval = __get_user(tmp, (__u8 __user *)arg); + if (retval == 0) { + u32 save = spi->mode; + + if (tmp) + spi->mode |= SPI_LSB_FIRST; + else + spi->mode &= ~SPI_LSB_FIRST; + retval = spi_setup(spi); + if (retval < 0) + spi->mode = save; + else + dev_dbg(&spi->dev, "%csb first\n", + tmp ? 'l' : 'm'); + } + break; + case SPI_IOC_WR_BITS_PER_WORD: + retval = __get_user(tmp, (__u8 __user *)arg); + if (retval == 0) { + u8 save = spi->bits_per_word; + + spi->bits_per_word = tmp; + retval = spi_setup(spi); + if (retval < 0) + spi->bits_per_word = save; + else + dev_dbg(&spi->dev, "%d bits per word\n", tmp); + } + break; + case SPI_IOC_WR_MAX_SPEED_HZ: + retval = __get_user(tmp, (__u32 __user *)arg); + if (retval == 0) { + u32 save = spi->max_speed_hz; + + spi->max_speed_hz = tmp; + retval = spi_setup(spi); + if (retval >= 0) + spidev->speed_hz = tmp; + else + dev_dbg(&spi->dev, "%d Hz (max)\n", tmp); + spi->max_speed_hz = save; + } + break; + + default: + /* segmented and/or full-duplex I/O request */ + /* Check message and copy into scratch area */ + ioc = spidev_get_ioc_message(cmd, + (struct spi_ioc_transfer __user *)arg, &n_ioc); + if (IS_ERR(ioc)) { + retval = PTR_ERR(ioc); + break; + } + if (!ioc) + break; /* n_ioc is also 0 */ + + /* translate to spi_message, execute */ + retval = spidev_message(spidev, ioc, n_ioc); + kfree(ioc); + break; + } + + mutex_unlock(&spidev->buf_lock); + spi_dev_put(spi); + return retval; +} + +#ifdef CONFIG_COMPAT +static long +spidev_compat_ioc_message(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + struct spi_ioc_transfer __user *u_ioc; + int retval = 0; + struct spidev_data *spidev; + struct spi_device *spi; + unsigned n_ioc, n; + struct spi_ioc_transfer *ioc; + + u_ioc = (struct spi_ioc_transfer __user *) compat_ptr(arg); + if (!access_ok(VERIFY_READ, u_ioc, _IOC_SIZE(cmd))) + return -EFAULT; + + /* guard against device removal before, or while, + * we issue this ioctl. + */ + spidev = filp->private_data; + spin_lock_irq(&spidev->spi_lock); + spi = spi_dev_get(spidev->spi); + spin_unlock_irq(&spidev->spi_lock); + + if (spi == NULL) + return -ESHUTDOWN; + + /* SPI_IOC_MESSAGE needs the buffer locked "normally" */ + mutex_lock(&spidev->buf_lock); + + /* Check message and copy into scratch area */ + ioc = spidev_get_ioc_message(cmd, u_ioc, &n_ioc); + if (IS_ERR(ioc)) { + retval = PTR_ERR(ioc); + goto done; + } + if (!ioc) + goto done; /* n_ioc is also 0 */ + + /* Convert buffer pointers */ + for (n = 0; n < n_ioc; n++) { + ioc[n].rx_buf = (uintptr_t) compat_ptr(ioc[n].rx_buf); + ioc[n].tx_buf = (uintptr_t) compat_ptr(ioc[n].tx_buf); + } + + /* translate to spi_message, execute */ + retval = spidev_message(spidev, ioc, n_ioc); + kfree(ioc); + +done: + mutex_unlock(&spidev->buf_lock); + spi_dev_put(spi); + return retval; +} + +static long +spidev_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + if (_IOC_TYPE(cmd) == SPI_IOC_MAGIC + && _IOC_NR(cmd) == _IOC_NR(SPI_IOC_MESSAGE(0)) + && _IOC_DIR(cmd) == _IOC_WRITE) + return spidev_compat_ioc_message(filp, cmd, arg); + + return spidev_ioctl(filp, cmd, (unsigned long)compat_ptr(arg)); +} +#else +#define spidev_compat_ioctl NULL +#endif /* CONFIG_COMPAT */ + +static int spidev_open(struct inode *inode, struct file *filp) +{ + struct spidev_data *spidev; + int status = -ENXIO; + + mutex_lock(&device_list_lock); + + list_for_each_entry(spidev, &device_list, device_entry) { + if (spidev->devt == inode->i_rdev) { + status = 0; + break; + } + } + + if (status) { + pr_debug("spidev: nothing for minor %d\n", iminor(inode)); + goto err_find_dev; + } + + if (!spidev->tx_buffer) { + spidev->tx_buffer = kmalloc(bufsiz, GFP_KERNEL); + if (!spidev->tx_buffer) { + dev_dbg(&spidev->spi->dev, "open/ENOMEM\n"); + status = -ENOMEM; + goto err_find_dev; + } + } + + if (!spidev->rx_buffer) { + spidev->rx_buffer = kmalloc(bufsiz, GFP_KERNEL); + if (!spidev->rx_buffer) { + dev_dbg(&spidev->spi->dev, "open/ENOMEM\n"); + status = -ENOMEM; + goto err_alloc_rx_buf; + } + } + + spidev->users++; + filp->private_data = spidev; + nonseekable_open(inode, filp); + + mutex_unlock(&device_list_lock); + return 0; + +err_alloc_rx_buf: + kfree(spidev->tx_buffer); + spidev->tx_buffer = NULL; +err_find_dev: + mutex_unlock(&device_list_lock); + return status; +} + +static int spidev_release(struct inode *inode, struct file *filp) +{ + struct spidev_data *spidev; + + mutex_lock(&device_list_lock); + spidev = filp->private_data; + filp->private_data = NULL; + + /* last close? */ + spidev->users--; + if (!spidev->users) { + int dofree; + + kfree(spidev->tx_buffer); + spidev->tx_buffer = NULL; + + kfree(spidev->rx_buffer); + spidev->rx_buffer = NULL; + + spin_lock_irq(&spidev->spi_lock); + if (spidev->spi) + spidev->speed_hz = spidev->spi->max_speed_hz; + + /* ... after we unbound from the underlying device? */ + dofree = (spidev->spi == NULL); + spin_unlock_irq(&spidev->spi_lock); + + if (dofree) + kfree(spidev); + } + mutex_unlock(&device_list_lock); + + return 0; +} + +static const struct file_operations spidev_fops = { + .owner = THIS_MODULE, + /* REVISIT switch to aio primitives, so that userspace + * gets more complete API coverage. It'll simplify things + * too, except for the locking. + */ + .write = spidev_write, + .read = spidev_read, + .unlocked_ioctl = spidev_ioctl, + .compat_ioctl = spidev_compat_ioctl, + .open = spidev_open, + .release = spidev_release, + .llseek = no_llseek, +}; + +/*-------------------------------------------------------------------------*/ + +/* The main reason to have this class is to make mdev/udev create the + * /dev/spidevB.C character device nodes exposing our userspace API. + * It also simplifies memory management. + */ + +static struct class *spidev_class; + +#ifdef CONFIG_OF +static const struct of_device_id spidev_dt_ids[] = { + { .compatible = "commaai,panda" }, + {}, +}; +MODULE_DEVICE_TABLE(of, spidev_dt_ids); +#endif + +#ifdef CONFIG_ACPI + +/* Dummy SPI devices not to be used in production systems */ +#define SPIDEV_ACPI_DUMMY 1 + +static const struct acpi_device_id spidev_acpi_ids[] = { + /* + * The ACPI SPT000* devices are only meant for development and + * testing. Systems used in production should have a proper ACPI + * description of the connected peripheral and they should also use + * a proper driver instead of poking directly to the SPI bus. + */ + { "SPT0001", SPIDEV_ACPI_DUMMY }, + { "SPT0002", SPIDEV_ACPI_DUMMY }, + { "SPT0003", SPIDEV_ACPI_DUMMY }, + {}, +}; +MODULE_DEVICE_TABLE(acpi, spidev_acpi_ids); + +static void spidev_probe_acpi(struct spi_device *spi) +{ + const struct acpi_device_id *id; + + if (!has_acpi_companion(&spi->dev)) + return; + + id = acpi_match_device(spidev_acpi_ids, &spi->dev); + if (WARN_ON(!id)) + return; + + if (id->driver_data == SPIDEV_ACPI_DUMMY) + dev_warn(&spi->dev, "do not use this driver in production systems!\n"); +} +#else +static inline void spidev_probe_acpi(struct spi_device *spi) {} +#endif + +/*-------------------------------------------------------------------------*/ + +static int spidev_probe(struct spi_device *spi) +{ + struct spidev_data *spidev; + int status; + unsigned long minor; + + /* + * spidev should never be referenced in DT without a specific + * compatible string, it is a Linux implementation thing + * rather than a description of the hardware. + */ + if (spi->dev.of_node && !of_match_device(spidev_dt_ids, &spi->dev)) { + dev_err(&spi->dev, "buggy DT: spidev listed directly in DT\n"); + WARN_ON(spi->dev.of_node && + !of_match_device(spidev_dt_ids, &spi->dev)); + } + + spidev_probe_acpi(spi); + + /* Allocate driver data */ + spidev = kzalloc(sizeof(*spidev), GFP_KERNEL); + if (!spidev) + return -ENOMEM; + + /* Initialize the driver data */ + spidev->spi = spi; + spin_lock_init(&spidev->spi_lock); + mutex_init(&spidev->buf_lock); + + INIT_LIST_HEAD(&spidev->device_entry); + + /* If we can allocate a minor number, hook up this device. + * Reusing minors is fine so long as udev or mdev is working. + */ + mutex_lock(&device_list_lock); + minor = find_first_zero_bit(minors, N_SPI_MINORS); + if (minor < N_SPI_MINORS) { + struct device *dev; + + spidev->devt = MKDEV(SPIDEV_MAJOR, minor); + dev = device_create(spidev_class, &spi->dev, spidev->devt, + spidev, "spidev%d.%d", + spi->master->bus_num, spi->chip_select); + status = PTR_ERR_OR_ZERO(dev); + } else { + dev_dbg(&spi->dev, "no minor number available!\n"); + status = -ENODEV; + } + if (status == 0) { + set_bit(minor, minors); + list_add(&spidev->device_entry, &device_list); + } + mutex_unlock(&device_list_lock); + + spidev->speed_hz = spi->max_speed_hz; + + if (status == 0) + spi_set_drvdata(spi, spidev); + else + kfree(spidev); + + return status; +} + +static int spidev_remove(struct spi_device *spi) +{ + struct spidev_data *spidev = spi_get_drvdata(spi); + + /* make sure ops on existing fds can abort cleanly */ + spin_lock_irq(&spidev->spi_lock); + spidev->spi = NULL; + spin_unlock_irq(&spidev->spi_lock); + + /* prevent new opens */ + mutex_lock(&device_list_lock); + list_del(&spidev->device_entry); + device_destroy(spidev_class, spidev->devt); + clear_bit(MINOR(spidev->devt), minors); + if (spidev->users == 0) + kfree(spidev); + mutex_unlock(&device_list_lock); + + return 0; +} + +static struct spi_driver spidev_spi_driver = { + .driver = { + .name = "spidev_panda", + .of_match_table = of_match_ptr(spidev_dt_ids), + .acpi_match_table = ACPI_PTR(spidev_acpi_ids), + }, + .probe = spidev_probe, + .remove = spidev_remove, + + /* NOTE: suspend/resume methods are not necessary here. + * We don't do anything except pass the requests to/from + * the underlying controller. The refrigerator handles + * most issues; the controller driver handles the rest. + */ +}; + +/*-------------------------------------------------------------------------*/ + +static int __init spidev_init(void) +{ + int status; + + /* Claim our 256 reserved device numbers. Then register a class + * that will key udev/mdev to add/remove /dev nodes. Last, register + * the driver which manages those device numbers. + */ + BUILD_BUG_ON(N_SPI_MINORS > 256); + status = register_chrdev(0, "spi", &spidev_fops); + if (status < 0) + return status; + + SPIDEV_MAJOR = status; + + spidev_class = class_create(THIS_MODULE, "spidev_panda"); + if (IS_ERR(spidev_class)) { + unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name); + return PTR_ERR(spidev_class); + } + + status = spi_register_driver(&spidev_spi_driver); + if (status < 0) { + class_destroy(spidev_class); + unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name); + } + return status; +} +module_init(spidev_init); + +static void __exit spidev_exit(void) +{ + spi_unregister_driver(&spidev_spi_driver); + class_destroy(spidev_class); + unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name); +} +module_exit(spidev_exit); + +MODULE_AUTHOR("Andrea Paterniani, "); +MODULE_DESCRIPTION("User mode SPI device interface"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("spi:spidev"); diff --git a/examples/query_fw_versions.py b/examples/query_fw_versions.py index 82a204185e..4f4e3fa66b 100755 --- a/examples/query_fw_versions.py +++ b/examples/query_fw_versions.py @@ -1,8 +1,10 @@ #!/usr/bin/env python3 import argparse +from typing import List, Optional from tqdm import tqdm from panda import Panda -from panda.python.uds import UdsClient, MessageTimeoutError, NegativeResponseError, SESSION_TYPE, DATA_IDENTIFIER_TYPE +from panda.python.uds import UdsClient, MessageTimeoutError, NegativeResponseError, InvalidSubAddressError, \ + SESSION_TYPE, DATA_IDENTIFIER_TYPE if __name__ == "__main__": parser = argparse.ArgumentParser() @@ -11,6 +13,7 @@ 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("--sub_addr", "--subaddr", help="A hex sub-address or `scan` to scan the full sub-address range") parser.add_argument("--bus") parser.add_argument('-s', '--serial', help="Serial number of panda to use") args = parser.parse_args() @@ -22,6 +25,17 @@ addrs += [0x18da0000 + (i << 8) + 0xf1 for i in range(256)] results = {} + sub_addrs: List[Optional[int]] = [None] + if args.sub_addr: + if args.sub_addr == "scan": + sub_addrs = list(range(0xff + 1)) + else: + sub_addrs = [int(args.sub_addr, base=16)] + if sub_addrs[0] > 0xff: # type: ignore + print(f"Invalid sub-address: 0x{sub_addrs[0]:X}, needs to be in range 0x0 to 0xff") + parser.print_help() + exit() + uds_data_ids = {} for std_id in DATA_IDENTIFIER_TYPE: uds_data_ids[std_id.value] = std_id.name @@ -51,42 +65,50 @@ # skip functional broadcast addrs if addr == 0x7df or addr == 0x18db33f1: continue - t.set_description(hex(addr)) if args.bus: bus = int(args.bus) else: bus = 1 if panda.has_obd() else 0 rx_addr = addr + int(args.rxoffset, base=16) if args.rxoffset else None - uds_client = UdsClient(panda, addr, rx_addr, bus, timeout=0.2, debug=args.debug) - # Check for anything alive at this address, and switch to the highest - # available diagnostic session without security access - try: - uds_client.tester_present() - uds_client.diagnostic_session_control(SESSION_TYPE.DEFAULT) - uds_client.diagnostic_session_control(SESSION_TYPE.EXTENDED_DIAGNOSTIC) - except NegativeResponseError: - pass - except MessageTimeoutError: - continue - # Run queries against all standard UDS data identifiers, plus selected - # non-standardized identifier ranges if requested - resp = {} - for uds_data_id in sorted(uds_data_ids): + # Try all sub-addresses for addr. By default, this is None + for sub_addr in sub_addrs: + sub_addr_str = hex(sub_addr) if sub_addr is not None else None + t.set_description(f"{hex(addr)}, {sub_addr_str}") + uds_client = UdsClient(panda, addr, rx_addr, bus, sub_addr=sub_addr, timeout=0.2, debug=args.debug) + # Check for anything alive at this address, and switch to the highest + # available diagnostic session without security access try: - data = uds_client.read_data_by_identifier(uds_data_id) # type: ignore - if data: - resp[uds_data_id] = data - except (NegativeResponseError, MessageTimeoutError): + uds_client.tester_present() + uds_client.diagnostic_session_control(SESSION_TYPE.DEFAULT) + uds_client.diagnostic_session_control(SESSION_TYPE.EXTENDED_DIAGNOSTIC) + except NegativeResponseError: pass + except MessageTimeoutError: + continue + except InvalidSubAddressError as e: + print(f'*** Skipping address {hex(addr)}: {e}') + break + + # Run queries against all standard UDS data identifiers, plus selected + # non-standardized identifier ranges if requested + resp = {} + for uds_data_id in sorted(uds_data_ids): + try: + data = uds_client.read_data_by_identifier(uds_data_id) # type: ignore + if data: + resp[uds_data_id] = data + except (NegativeResponseError, MessageTimeoutError, InvalidSubAddressError): + pass - if resp.keys(): - results[addr] = resp + if resp.keys(): + results[(addr, sub_addr)] = resp if len(results.items()): - for addr, resp in results.items(): - print(f"\n\n*** Results for address 0x{addr:X} ***\n\n") + for (addr, sub_addr), resp in results.items(): + sub_addr_str = f", sub-address 0x{sub_addr:X}" if sub_addr is not None else "" + print(f"\n\n*** Results for address 0x{addr:X}{sub_addr_str} ***\n\n") for rid, dat in resp.items(): print(f"0x{rid:02X} {uds_data_ids[rid]}: {dat}") else: diff --git a/examples/tesla_tester.py b/examples/tesla_tester.py index c849b6378d..966e39d103 100644 --- a/examples/tesla_tester.py +++ b/examples/tesla_tester.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# flake8: noqa import binascii from panda import Panda @@ -16,7 +15,8 @@ def tesla_tester(): print("Setting Panda to output mode...") p.set_safety_mode(Panda.SAFETY_ALLOUTPUT) - # BDY 0x248 is the MCU_commands message, which includes folding mirrors, opening the trunk, frunk, setting the cars lock state and more. For our test, we will edit the 3rd byte, which is MCU_lockRequest. 0x01 will lock, 0x02 will unlock: + # BDY 0x248 is the MCU_commands message, which includes folding mirrors, opening the trunk, frunk, setting the cars lock state and more. + # For our test, we will edit the 3rd byte, which is MCU_lockRequest. 0x01 will lock, 0x02 will unlock: print("Unlocking Tesla...") p.can_send(0x248, b"\x00\x00\x02\x00\x00\x00\x00\x00", body_bus_num) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000000..bac2d7f081 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,7 @@ +# https://beta.ruff.rs/docs/configuration/#using-pyprojecttoml +[tool.ruff] +select = ["E", "F", "W", "PIE", "C4", "ISC", "RUF100", "A"] +ignore = ["W292", "E741", "E402", "C408", "ISC003"] +line-length = 160 +target-version="py311" +flake8-implicit-str-concat.allow-multiline=false diff --git a/python/__init__.py b/python/__init__.py index ee09a55c87..40066af46a 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -7,17 +7,16 @@ import hashlib import binascii import datetime -import warnings import logging from functools import wraps from typing import Optional from itertools import accumulate from .base import BaseHandle -from .constants import McuType +from .constants import FW_PATH, McuType from .dfu import PandaDFU from .isotp import isotp_send, isotp_recv -from .spi import PandaSpiHandle, PandaSpiException +from .spi import PandaSpiHandle, PandaSpiException, PandaProtocolMismatch from .usb import PandaUsbHandle __version__ = '0.0.10' @@ -26,7 +25,6 @@ LOGLEVEL = os.environ.get('LOGLEVEL', 'INFO').upper() logging.basicConfig(level=LOGLEVEL, format='%(message)s') -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)} @@ -122,6 +120,24 @@ def wrapper(self, *args, **kwargs): return fn(self, *args, **kwargs) return wrapper +def parse_timestamp(dat): + a = struct.unpack("HBBBBBB", dat) + if a[0] == 0: + return None + + try: + return datetime.datetime(a[0], a[1], a[2], a[4], a[5], a[6]) + except ValueError: + return None + +def unpack_log(dat): + return { + 'id': struct.unpack("H", dat[:2])[0], + 'timestamp': parse_timestamp(dat[2:10]), + 'uptime': struct.unpack("I", dat[10:14])[0], + 'msg': bytes(dat[14:]).decode('utf-8', 'ignore').strip('\x00'), + } + class ALTERNATIVE_EXPERIENCE: DEFAULT = 0 DISABLE_DISENGAGE_ON_GAS = 1 @@ -150,7 +166,7 @@ class Panda: SAFETY_NOOUTPUT = 19 SAFETY_HONDA_BOSCH = 20 SAFETY_VOLKSWAGEN_PQ = 21 - SAFETY_SUBARU_LEGACY = 22 + SAFETY_SUBARU_PREGLOBAL = 22 SAFETY_HYUNDAI_LEGACY = 23 SAFETY_HYUNDAI_COMMUNITY = 24 SAFETY_STELLANTIS = 25 @@ -167,6 +183,7 @@ class Panda: GMLAN_CAN2 = 1 GMLAN_CAN3 = 2 + USB_PIDS = (0xddee, 0xddcc) REQUEST_IN = usb1.ENDPOINT_IN | usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE REQUEST_OUT = usb1.ENDPOINT_OUT | usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE @@ -182,14 +199,14 @@ class Panda: HW_TYPE_TRES = b'\x09' CAN_PACKET_VERSION = 4 - HEALTH_PACKET_VERSION = 13 - CAN_HEALTH_PACKET_VERSION = 4 - HEALTH_STRUCT = struct.Struct("> 8, ]) - - break - except Exception: - logging.exception("USB connect error") - if not wait or handle is not None: - break - context.close() + context = usb1.USBContext() + context.open() + try: + for device in context.getDeviceList(skip_on_error=True): + if device.getVendorID() == 0xbbaa and device.getProductID() in cls.USB_PIDS: + try: + this_serial = device.getSerialNumber() + except Exception: + continue + + if serial is None or this_serial == serial: + logging.debug("opening device %s %s", this_serial, hex(device.getProductID())) + + usb_serial = this_serial + bootstub = (device.getProductID() & 0xF0) == 0xe0 + handle = device.open() + if sys.platform not in ("win32", "cygwin", "msys", "darwin"): + handle.setAutoDetachKernelDriver(True) + if claim: + handle.claimInterface(0) + # handle.setInterfaceAltSetting(0, 0) # Issue in USB stack + + # bcdDevice wasn't always set to the hw type, ignore if it's the old constant + this_bcd = device.getbcdDevice() + if this_bcd is not None and this_bcd != 0x2300: + bcd = bytearray([this_bcd >> 8, ]) + + break + except Exception: + logging.exception("USB connect error") usb_handle = None if handle is not None: usb_handle = PandaUsbHandle(handle) + else: + context.close() return usb_handle, usb_serial, bootstub, bcd - @staticmethod - def list(): - ret = Panda.usb_list() - ret += Panda.spi_list() + @classmethod + def list(cls): # noqa: A003 + ret = cls.usb_list() + ret += cls.spi_list() return list(set(ret)) - @staticmethod - def usb_list(): + @classmethod + def usb_list(cls): ret = [] try: with usb1.USBContext() as context: for device in context.getDeviceList(skip_on_error=True): - if device.getVendorID() == 0xbbaa and device.getProductID() in (0xddcc, 0xddee): + if device.getVendorID() == 0xbbaa and device.getProductID() in cls.USB_PIDS: try: serial = device.getSerialNumber() - if len(serial) == 24: + if len(serial) == 24 or serial == "pedal": ret.append(serial) else: - warnings.warn(f"found device with panda descriptors but invalid serial: {serial}", RuntimeWarning) + logging.warning(f"found device with panda descriptors but invalid serial: {serial}", RuntimeWarning) except Exception: continue except Exception: logging.exception("exception while listing pandas") return ret - @staticmethod - def spi_list(): - _, serial, _, _ = Panda.spi_connect(None) + @classmethod + def spi_list(cls): + _, serial, _, _ = cls.spi_connect(None, ignore_version=True) if serial is not None: return [serial, ] return [] def reset(self, enter_bootstub=False, enter_bootloader=False, reconnect=True): + # no response is expected since it resets right away + timeout = 5000 if isinstance(self._handle, PandaSpiHandle) else 15000 try: if enter_bootloader: - self._handle.controlWrite(Panda.REQUEST_IN, 0xd1, 0, 0, b'') + self._handle.controlWrite(Panda.REQUEST_IN, 0xd1, 0, 0, b'', timeout=timeout, expect_disconnect=True) else: if enter_bootstub: - self._handle.controlWrite(Panda.REQUEST_IN, 0xd1, 1, 0, b'') + self._handle.controlWrite(Panda.REQUEST_IN, 0xd1, 1, 0, b'', timeout=timeout, expect_disconnect=True) else: - self._handle.controlWrite(Panda.REQUEST_IN, 0xd8, 0, 0, b'') + self._handle.controlWrite(Panda.REQUEST_IN, 0xd8, 0, 0, b'', timeout=timeout, expect_disconnect=True) except Exception: pass if not enter_bootloader and reconnect: @@ -415,23 +463,17 @@ def connected(self) -> bool: def reconnect(self): if self._handle_open: self.close() - time.sleep(1.0) success = False # wait up to 15 seconds - for i in range(0, 15): + for _ in range(0, 15*10): try: self.connect() success = True break except Exception: - logging.debug("reconnecting is taking %d seconds...", i + 1) - try: - dfu = PandaDFU(self.get_dfu_serial()) - dfu.recover() - except Exception: - pass - time.sleep(1.0) + pass + time.sleep(0.1) if not success: raise Exception("reconnect failed") @@ -471,13 +513,13 @@ def flash_static(handle, code, mcu_type): # reset logging.warning("flash: resetting") try: - handle.controlWrite(Panda.REQUEST_IN, 0xd8, 0, 0, b'') + handle.controlWrite(Panda.REQUEST_IN, 0xd8, 0, 0, b'', expect_disconnect=True) except Exception: pass def flash(self, fn=None, code=None, reconnect=True): if not fn: - fn = self._mcu_type.config.app_path + fn = os.path.join(FW_PATH, self._mcu_type.config.app_fn) assert os.path.isfile(fn) logging.debug("flash: main version is %s", self.get_version()) if not self.bootstub: @@ -498,7 +540,7 @@ def flash(self, fn=None, code=None, reconnect=True): if reconnect: self.reconnect() - def recover(self, timeout: Optional[int] = None, reset: bool = True) -> bool: + def recover(self, timeout: Optional[int] = 60, reset: bool = True) -> bool: dfu_serial = self.get_dfu_serial() if reset: @@ -517,18 +559,33 @@ def recover(self, timeout: Optional[int] = None, reset: bool = True) -> bool: return True @staticmethod - def wait_for_dfu(dfu_serial: str, timeout: Optional[int] = None) -> bool: + def wait_for_dfu(dfu_serial: Optional[str], timeout: Optional[int] = None) -> bool: t_start = time.monotonic() - while dfu_serial not in PandaDFU.list(): + dfu_list = PandaDFU.list() + while (dfu_serial is None and len(dfu_list) == 0) or (dfu_serial is not None and dfu_serial not in dfu_list): logging.debug("waiting for DFU...") time.sleep(0.1) if timeout is not None and (time.monotonic() - t_start) > timeout: return False + dfu_list = PandaDFU.list() + return True + + @staticmethod + def wait_for_panda(serial: Optional[str], timeout: int) -> bool: + t_start = time.monotonic() + serials = Panda.list() + while (serial is None and len(serials) == 0) or (serial is not None and serial not in serials): + logging.debug("waiting for panda...") + time.sleep(0.1) + if timeout is not None and (time.monotonic() - t_start) > timeout: + return False + serials = Panda.list() return True def up_to_date(self) -> bool: current = self.get_signature() - expected = Panda.get_signature_from_firmware(self.get_mcu_type().config.app_path) + fn = os.path.join(FW_PATH, self.get_mcu_type().config.app_fn) + expected = Panda.get_signature_from_firmware(fn) return (current == expected) def call_control_api(self, msg): @@ -566,6 +623,8 @@ def health(self): "safety_rx_checks_invalid": a[22], "spi_checksum_error_count": a[23], "fan_stall_count": a[24], + "sbu1_voltage_mV": a[25], + "sbu2_voltage_mV": a[26], } @ensure_can_health_packet_version @@ -605,16 +664,14 @@ def can_health(self, can_number): "canfd_enabled": a[19], "brs_enabled": a[20], "canfd_non_iso": a[21], + "irq0_call_rate": a[22], + "irq1_call_rate": a[23], + "irq2_call_rate": a[24], + "can_core_reset_count": a[25], } # ******************* control ******************* - def enter_bootloader(self): - try: - self._handle.controlWrite(Panda.REQUEST_OUT, 0xd1, 0, 0, b'') - 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') @@ -697,6 +754,10 @@ def get_uid(self): def get_secret(self): return self._handle.controlRead(Panda.REQUEST_IN, 0xd0, 1, 0, 0x10) + def get_interrupt_call_rate(self, irqnum): + dat = self._handle.controlRead(Panda.REQUEST_IN, 0xc4, int(irqnum), 0, 4) + return struct.unpack("I", dat)[0] + # ******************* configuration ******************* def set_power_save(self, power_save_enabled=0): @@ -705,13 +766,6 @@ def set_power_save(self, power_save_enabled=0): def enable_deepsleep(self): self._handle.controlWrite(Panda.REQUEST_OUT, 0xfb, 0, 0, b'') - def set_esp_power(self, on): - self._handle.controlWrite(Panda.REQUEST_OUT, 0xd9, int(on), 0, b'') - - def esp_reset(self, bootmode=0): - self._handle.controlWrite(Panda.REQUEST_OUT, 0xda, int(bootmode), 0, b'') - time.sleep(0.2) - def set_safety_mode(self, mode=SAFETY_SILENT, param=0): self._handle.controlWrite(Panda.REQUEST_OUT, 0xdc, mode, param, b'') @@ -827,7 +881,7 @@ def serial_read(self, port_number): def serial_write(self, port_number, ln): ret = 0 - if type(ln) == str: + if isinstance(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]) @@ -920,8 +974,7 @@ def set_datetime(self, dt): def get_datetime(self): dat = self._handle.controlRead(Panda.REQUEST_IN, 0xa0, 0, 0, 8) - a = struct.unpack("HBBBBBB", dat) - return datetime.datetime(a[0], a[1], a[2], a[4], a[5], a[6]) + return parse_timestamp(dat) # ****************** Timer ***************** def get_microsecond_timer(self): @@ -952,3 +1005,21 @@ def set_siren(self, enabled): # ****************** Debug ***************** def set_green_led(self, enabled): self._handle.controlWrite(Panda.REQUEST_OUT, 0xf7, int(enabled), 0, b'') + + def set_clock_source_period(self, period): + self._handle.controlWrite(Panda.REQUEST_OUT, 0xe6, period, 0, b'') + + def force_relay_drive(self, intercept_relay_drive, ignition_relay_drive): + self._handle.controlWrite(Panda.REQUEST_OUT, 0xc5, (int(intercept_relay_drive) | int(ignition_relay_drive) << 1), 0, b'') + + # ****************** Logging ***************** + def get_logs(self, last_id=None, get_all=False): + assert (last_id is None) or (0 <= last_id < 0xFFFF) + + logs = [] + dat = self._handle.controlRead(Panda.REQUEST_IN, 0xfd, 1 if get_all else 0, last_id if last_id is not None else 0xFFFF, 0x40) + while len(dat) > 0: + if len(dat) == 0x40: + logs.append(unpack_log(dat)) + dat = self._handle.controlRead(Panda.REQUEST_IN, 0xfd, 0, 0xFFFF, 0x40) + return logs diff --git a/python/base.py b/python/base.py index 5bfa564892..509d3ebf77 100644 --- a/python/base.py +++ b/python/base.py @@ -1,5 +1,4 @@ from abc import ABC, abstractmethod -from typing import List from .constants import McuType @@ -16,7 +15,7 @@ def close(self) -> None: ... @abstractmethod - def controlWrite(self, request_type: int, request: int, value: int, index: int, data, timeout: int = TIMEOUT) -> int: + def controlWrite(self, request_type: int, request: int, value: int, index: int, data, timeout: int = TIMEOUT, expect_disconnect: bool = False): ... @abstractmethod @@ -24,7 +23,7 @@ def controlRead(self, request_type: int, request: int, value: int, index: int, l ... @abstractmethod - def bulkWrite(self, endpoint: int, data: List[int], timeout: int = TIMEOUT) -> int: + def bulkWrite(self, endpoint: int, data: bytes, timeout: int = TIMEOUT) -> int: ... @abstractmethod diff --git a/tests/pedal/canhandle.py b/python/canhandle.py similarity index 91% rename from tests/pedal/canhandle.py rename to python/canhandle.py index 0d7582f048..ff6e625552 100644 --- a/tests/pedal/canhandle.py +++ b/python/canhandle.py @@ -1,8 +1,10 @@ import struct import signal +from .base import BaseHandle -class CanHandle(object): + +class CanHandle(BaseHandle): def __init__(self, p, bus): self.p = p self.bus = bus @@ -29,7 +31,10 @@ def _handle_timeout(signum, frame): return ret - def controlWrite(self, request_type, request, value, index, data, timeout=0): + def close(self): + pass + + def controlWrite(self, request_type, request, value, index, data, timeout=0, expect_disconnect=False): # ignore data in reply, panda doesn't use it return self.controlRead(request_type, request, value, index, 0, timeout) diff --git a/python/constants.py b/python/constants.py index c55fd2c9b8..16409ac312 100644 --- a/python/constants.py +++ b/python/constants.py @@ -3,7 +3,9 @@ from typing import List, NamedTuple BASEDIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../") +FW_PATH = os.path.join(BASEDIR, "board/obj/") +USBPACKET_MAX_SIZE = 0x40 class McuConfig(NamedTuple): mcu: str @@ -13,9 +15,9 @@ class McuConfig(NamedTuple): sector_sizes: List[int] serial_number_address: int app_address: int - app_path: str + app_fn: str bootstub_address: int - bootstub_path: str + bootstub_fn: str Fx = ( 0x1FFF7A10, @@ -23,9 +25,9 @@ class McuConfig(NamedTuple): [0x4000 for _ in range(4)] + [0x10000] + [0x20000 for _ in range(11)], 0x1FFF79C0, 0x8004000, - os.path.join(BASEDIR, "board", "obj", "panda.bin.signed"), + "panda.bin.signed", 0x8000000, - os.path.join(BASEDIR, "board", "obj", "bootstub.panda.bin"), + "bootstub.panda.bin", ) F2Config = McuConfig("STM32F2", 0x411, *Fx) F4Config = McuConfig("STM32F4", 0x463, *Fx) @@ -39,9 +41,9 @@ class McuConfig(NamedTuple): [0x20000 for _ in range(7)], 0x080FFFC0, 0x8020000, - os.path.join(BASEDIR, "board", "obj", "panda_h7.bin.signed"), + "panda_h7.bin.signed", 0x8000000, - os.path.join(BASEDIR, "board", "obj", "bootstub.panda_h7.bin"), + "bootstub.panda_h7.bin", ) @enum.unique diff --git a/python/dfu.py b/python/dfu.py index f724bf15d9..39303d5286 100644 --- a/python/dfu.py +++ b/python/dfu.py @@ -1,3 +1,4 @@ +import os import usb1 import struct import binascii @@ -6,7 +7,7 @@ from .base import BaseSTBootloaderHandle from .spi import STBootloaderSPIHandle, PandaSpiException from .usb import STBootloaderUSBHandle -from .constants import McuType +from .constants import FW_PATH, McuType class PandaDFU: @@ -58,7 +59,7 @@ def spi_connect(dfu_serial: Optional[str]) -> Optional[STBootloaderSPIHandle]: return handle @staticmethod - def list() -> List[str]: + def list() -> List[str]: # noqa: A003 ret = PandaDFU.usb_list() ret += PandaDFU.spi_list() return list(set(ret)) @@ -110,10 +111,10 @@ def program_bootstub(self, code_bootstub): self._handle.erase_bootstub() self._handle.erase_app() self._handle.program(self._mcu_type.config.bootstub_address, code_bootstub) - self.reset() def recover(self): - with open(self._mcu_type.config.bootstub_path, "rb") as f: + fn = os.path.join(FW_PATH, self._mcu_type.config.bootstub_fn) + with open(fn, "rb") as f: code = f.read() self.program_bootstub(code) - + self.reset() diff --git a/python/isotp.py b/python/isotp.py index 45acb6abae..3334deb8ed 100644 --- a/python/isotp.py +++ b/python/isotp.py @@ -6,10 +6,8 @@ def msg(x): if DEBUG: print("S:", binascii.hexlify(x)) - if len(x) <= 7: - ret = bytes([len(x)]) + x - else: - assert False + assert len(x) <= 7 + ret = bytes([len(x)]) + x return ret.ljust(8, b"\x00") kmsgs = [] @@ -56,7 +54,7 @@ def isotp_recv_subaddr(panda, addr, bus, sendaddr, subaddr): dat = msg[2:] else: print(binascii.hexlify(msg)) - assert False + raise AssertionError return dat[0:tlen] @@ -133,7 +131,7 @@ def isotp_recv(panda, addr, bus=0, sendaddr=None, subaddr=None): tlen = msg[0] & 0xf dat = msg[1:] else: - assert False + raise AssertionError dat = dat[0:tlen] if DEBUG: diff --git a/python/serial.py b/python/serial.py index 97fe8f1d90..9ac58862b5 100644 --- a/python/serial.py +++ b/python/serial.py @@ -8,7 +8,7 @@ def __init__(self, panda, port, baud): self.panda.set_uart_baud(self.port, baud) self.buf = b"" - def read(self, l=1): # noqa: E741 + def read(self, l=1): tt = self.panda.serial_read(self.port) if len(tt) > 0: self.buf += tt diff --git a/python/spi.py b/python/spi.py index 2e6beb5435..0be28f49c1 100644 --- a/python/spi.py +++ b/python/spi.py @@ -1,4 +1,5 @@ import binascii +import ctypes import os import fcntl import math @@ -8,15 +9,18 @@ import threading from contextlib import contextmanager from functools import reduce -from typing import List, Optional +from typing import Callable, List, Optional from .base import BaseHandle, BaseSTBootloaderHandle, TIMEOUT -from .constants import McuType, MCU_TYPE_BY_IDCODE +from .constants import McuType, MCU_TYPE_BY_IDCODE, USBPACKET_MAX_SIZE +from .utils import crc8_pedal try: import spidev + import spidev2 except ImportError: spidev = None + spidev2 = None # Constants SYNC = 0x5A @@ -28,7 +32,7 @@ MIN_ACK_TIMEOUT_MS = 100 MAX_XFER_RETRY_COUNT = 5 -XFER_SIZE = 1000 +XFER_SIZE = 0x40*31 DEV_PATH = "/dev/spidev0.0" @@ -36,6 +40,9 @@ class PandaSpiException(Exception): pass +class PandaProtocolMismatch(PandaSpiException): + pass + class PandaSpiUnavailable(PandaSpiException): pass @@ -54,11 +61,29 @@ class PandaSpiTransferFailed(PandaSpiException): SPI_LOCK = threading.Lock() +class PandaSpiTransfer(ctypes.Structure): + _fields_ = [ + ('rx_buf', ctypes.c_uint64), + ('tx_buf', ctypes.c_uint64), + ('tx_length', ctypes.c_uint32), + ('rx_length_max', ctypes.c_uint32), + ('timeout', ctypes.c_uint32), + ('endpoint', ctypes.c_uint8), + ('expect_disconnect', ctypes.c_uint8), + ] + class SpiDevice: """ Provides locked, thread-safe access to a panda's SPI interface. """ - def __init__(self, speed): + + # 50MHz is the max of the 845. older rev comma three + # may not support the full 50MHz + MAX_SPEED = 50000000 + + def __init__(self, speed=MAX_SPEED): + assert speed <= self.MAX_SPEED + if not os.path.exists(DEV_PATH): raise PandaSpiUnavailable(f"SPI device not found: {DEV_PATH}") if spidev is None: @@ -82,104 +107,186 @@ def close(self): self._spidev.close() + class PandaSpiHandle(BaseHandle): """ A class that mimics a libusb1 handle for panda SPI communications. """ - def __init__(self): - # 50MHz is the max of the 845. older rev comma three - # may not support the full 50MHz - self.dev = SpiDevice(50000000) + + PROTOCOL_VERSION = 2 + + def __init__(self) -> None: + self.dev = SpiDevice() + + self._transfer_raw: Callable[[SpiDevice, int, bytes, int, int, bool], bytes] = self._transfer_spidev + + if "KERN" in os.environ: + self._transfer_raw = self._transfer_kernel_driver + + self.tx_buf = bytearray(1024) + self.rx_buf = bytearray(1024) + tx_buf_raw = ctypes.c_char.from_buffer(self.tx_buf) + rx_buf_raw = ctypes.c_char.from_buffer(self.rx_buf) + + self.ioctl_data = PandaSpiTransfer() + self.ioctl_data.tx_buf = ctypes.addressof(tx_buf_raw) + self.ioctl_data.rx_buf = ctypes.addressof(rx_buf_raw) + self.fileno = self.dev._spidev.fileno() # helpers - def _calc_checksum(self, data: List[int]) -> int: + def _calc_checksum(self, data: bytes) -> int: cksum = CHECKSUM_START for b in data: cksum ^= b return cksum - def _wait_for_ack(self, spi, ack_val: int, timeout: int) -> None: + def _wait_for_ack(self, spi, ack_val: int, timeout: int, tx: int, length: int = 1) -> bytes: 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: + dat = spi.xfer2([tx, ] * length) + if dat[0] == NACK: raise PandaSpiNackResponse - elif dat == ack_val: - return + elif dat[0] == ack_val: + return bytes(dat) raise PandaSpiMissingAck - def _transfer(self, spi, endpoint: int, data, timeout: int, max_rx_len: int = 1000) -> bytes: + def _transfer_spidev(self, spi, endpoint: int, data, timeout: int, max_rx_len: int = 1000, expect_disconnect: bool = False) -> bytes: + max_rx_len = max(USBPACKET_MAX_SIZE, max_rx_len) + + logging.debug("- send header") + packet = struct.pack(" max_rx_len: + raise PandaSpiException(f"response length greater than max ({max_rx_len} {response_len})") + + # read rest + remaining = (response_len + 1) - preread_len + if remaining > 0: + dat += bytes(spi.readbytes(remaining)) + + + dat = dat[:3 + response_len + 1] + if self._calc_checksum(dat) != 0: + raise PandaSpiBadChecksum + + return dat[3:-1] + + def _transfer_kernel_driver(self, spi, endpoint: int, data, timeout: int, max_rx_len: int = 1000, expect_disconnect: bool = False) -> bytes: + self.tx_buf[:len(data)] = data + self.ioctl_data.endpoint = endpoint + self.ioctl_data.tx_length = len(data) + self.ioctl_data.rx_length_max = max_rx_len + self.ioctl_data.expect_disconnect = int(expect_disconnect) + + # TODO: use our own ioctl request + try: + ret = fcntl.ioctl(self.fileno, spidev2.SPI_IOC_RD_LSB_FIRST, self.ioctl_data) + except OSError as e: + raise PandaSpiException from e + if ret < 0: + raise PandaSpiException(f"ioctl returned {ret}") + return bytes(self.rx_buf[:ret]) + + def _transfer(self, endpoint: int, data, timeout: int, max_rx_len: int = 1000, expect_disconnect: bool = False) -> bytes: logging.debug("starting transfer: endpoint=%d, max_rx_len=%d", endpoint, max_rx_len) logging.debug("==============================================") + n = 0 + start_time = time.monotonic() 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", MAX_XFER_RETRY_COUNT - n - 1, exc_info=True) + while (timeout == 0) or (time.monotonic() - start_time) < timeout*1e-3: + n += 1 + logging.debug("\ntry #%d", n) + with self.dev.acquire() as spi: + try: + return self._transfer_raw(spi, endpoint, data, timeout, max_rx_len, expect_disconnect) + except PandaSpiException as e: + exc = e + logging.debug("SPI transfer failed, retrying", exc_info=True) + + raise exc + + def get_protocol_version(self) -> bytes: + vers_str = b"VERSION" + def _get_version(spi) -> bytes: + spi.writebytes(vers_str) + + logging.debug("- waiting for echo") + start = time.monotonic() + while True: + version_bytes = spi.readbytes(len(vers_str) + 2) + if bytes(version_bytes).startswith(vers_str): + break + if (time.monotonic() - start) > 0.01: + raise PandaSpiMissingAck + + rlen = struct.unpack(" 1000: + raise PandaSpiException("response length greater than max") + + # get response + dat = spi.readbytes(rlen + 1) + resp = dat[:-1] + calculated_crc = crc8_pedal(bytes(version_bytes + resp)) + if calculated_crc != dat[-1]: + raise PandaSpiBadChecksum + return bytes(resp) + + exc = PandaSpiException() + with self.dev.acquire() as spi: + for _ in range(10): + try: + return _get_version(spi) + except PandaSpiException as e: + exc = e + logging.debug("SPI get protocol version failed, retrying", 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) / XFER_SIZE)): - self._transfer(spi, endpoint, data[XFER_SIZE*x:XFER_SIZE*(x+1)], timeout) - return len(data) + def bulkWrite(self, endpoint: int, data: bytes, timeout: int = TIMEOUT) -> int: + for x in range(math.ceil(len(data) / XFER_SIZE)): + self._transfer(endpoint, data[XFER_SIZE*x:XFER_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 / XFER_SIZE)): - d = self._transfer(spi, endpoint, [], timeout, max_rx_len=XFER_SIZE) - ret += d - if len(d) < XFER_SIZE: - break - return bytes(ret) + ret = b"" + for _ in range(math.ceil(length / XFER_SIZE)): + d = self._transfer(endpoint, [], timeout, max_rx_len=XFER_SIZE) + ret += d + if len(d) < XFER_SIZE: + break + return ret class STBootloaderSPIHandle(BaseSTBootloaderHandle): @@ -207,7 +314,7 @@ def __init__(self): self._mcu_type = MCU_TYPE_BY_IDCODE[self.get_chip_id()] except PandaSpiException: - raise PandaSpiException("failed to connect to panda") # pylint: disable=W0707 + raise PandaSpiException("failed to connect to panda") from None def _get_ack(self, spi, timeout=1.0): data = 0x00 @@ -228,7 +335,7 @@ def _cmd_no_retry(self, cmd: int, data: Optional[List[bytes]] = None, read_bytes # sync + command spi.xfer([self.SYNC, ]) spi.xfer([cmd, cmd ^ 0xFF]) - self._get_ack(spi) + self._get_ack(spi, timeout=0.1) # "predata" - for commands that send the first data without a checksum if predata is not None: diff --git a/python/uds.py b/python/uds.py index f727b7507e..9b25dffe66 100644 --- a/python/uds.py +++ b/python/uds.py @@ -229,7 +229,10 @@ def __str__(self): class InvalidServiceIdError(Exception): pass -class InvalidSubFunctioneError(Exception): +class InvalidSubFunctionError(Exception): + pass + +class InvalidSubAddressError(Exception): pass _negative_response_codes = { @@ -299,12 +302,12 @@ def get_dtc_status_names(status): class CanClient(): def __init__(self, can_send: Callable[[int, bytes, int], None], can_recv: Callable[[], List[Tuple[int, int, bytes, int]]], - tx_addr: int, rx_addr: int, bus: int, sub_addr: int = None, debug: bool = False): + tx_addr: int, rx_addr: int, bus: int, sub_addr: Optional[int] = None, debug: bool = False): self.tx = can_send self.rx = can_recv self.tx_addr = tx_addr self.rx_addr = rx_addr - self.rx_buff = deque() # type: Deque[bytes] + self.rx_buff: Deque[bytes] = deque() self.sub_addr = sub_addr self.bus = bus self.debug = debug @@ -345,6 +348,8 @@ def _recv_buffer(self, drain: bool = False) -> None: # Cut off sub addr in first byte if self.sub_addr is not None: + if rx_data[0] != self.sub_addr: + raise InvalidSubAddressError(f"isotp - rx: invalid sub-address: {rx_data[0]}, expected: {self.sub_addr}") rx_data = rx_data[1:] self.rx_buff.append(rx_data) @@ -463,11 +468,12 @@ def recv(self, timeout=None) -> Tuple[Optional[bytes], bool]: print(f"ISO-TP: RESPONSE - {hex(self._can_client.rx_addr)} 0x{bytes.hex(self.rx_dat)}") def _isotp_rx_next(self, rx_data: bytes) -> ISOTP_FRAME_TYPE: - # ISO 15765-2 specifies an eight byte CAN frame for ISO-TP communication - assert len(rx_data) == self.max_len, f"isotp - rx: invalid CAN frame length: {len(rx_data)}" + # TODO: Handle CAN frame data optimization, which is allowed with some frame types + # # ISO 15765-2 specifies an eight byte CAN frame for ISO-TP communication + # assert len(rx_data) == self.max_len, f"isotp - rx: invalid CAN frame length: {len(rx_data)}" if rx_data[0] >> 4 == ISOTP_FRAME_TYPE.SINGLE: - self.rx_len = rx_data[0] & 0xFF + self.rx_len = rx_data[0] & 0x0F assert self.rx_len < self.max_len, f"isotp - rx: invalid single frame length: {self.rx_len}" self.rx_dat = rx_data[1:1 + self.rx_len] self.rx_idx = 0 @@ -565,7 +571,7 @@ def get_rx_addr_for_tx_addr(tx_addr, rx_offset=0x8): class UdsClient(): - def __init__(self, panda, tx_addr: int, rx_addr: int = None, bus: int = 0, sub_addr: int = None, timeout: float = 1, + def __init__(self, panda, tx_addr: int, rx_addr: Optional[int] = None, bus: int = 0, sub_addr: Optional[int] = None, timeout: float = 1, debug: bool = False, tx_timeout: float = 1, response_pending_timeout: float = 10): self.bus = bus self.tx_addr = tx_addr @@ -578,7 +584,7 @@ def __init__(self, panda, tx_addr: int, rx_addr: int = None, bus: int = 0, sub_a self.response_pending_timeout = response_pending_timeout # generic uds request - def _uds_request(self, service_type: SERVICE_TYPE, subfunction: int = None, data: bytes = None) -> bytes: + def _uds_request(self, service_type: SERVICE_TYPE, subfunction: Optional[int] = None, data: Optional[bytes] = None) -> bytes: req = bytes([service_type]) if subfunction is not None: req += bytes([subfunction]) @@ -629,7 +635,7 @@ def _uds_request(self, service_type: SERVICE_TYPE, subfunction: int = None, data resp_sfn = resp[1] if len(resp) > 1 else None if subfunction != resp_sfn: resp_sfn_hex = hex(resp_sfn) if resp_sfn is not None else None - raise InvalidSubFunctioneError(f'invalid response subfunction: {resp_sfn_hex:x}') + raise InvalidSubFunctionError(f'invalid response subfunction: {resp_sfn_hex}') # return data (exclude service id and sub-function id) return resp[(1 if subfunction is None else 2):] @@ -666,7 +672,7 @@ def communication_control(self, control_type: CONTROL_TYPE, message_type: MESSAG def tester_present(self, ): self._uds_request(SERVICE_TYPE.TESTER_PRESENT, subfunction=0x00) - def access_timing_parameter(self, timing_parameter_type: TIMING_PARAMETER_TYPE, parameter_values: bytes = None): + def access_timing_parameter(self, timing_parameter_type: TIMING_PARAMETER_TYPE, parameter_values: Optional[bytes] = None): write_custom_values = timing_parameter_type == TIMING_PARAMETER_TYPE.SET_TO_GIVEN_VALUES read_values = (timing_parameter_type == TIMING_PARAMETER_TYPE.READ_CURRENTLY_ACTIVE or timing_parameter_type == TIMING_PARAMETER_TYPE.READ_EXTENDED_SET) @@ -709,7 +715,7 @@ def response_on_event(self, response_event_type: RESPONSE_EVENT_TYPE, store_even "data": resp[2:], # TODO: parse the reset of response } - def link_control(self, link_control_type: LINK_CONTROL_TYPE, baud_rate_type: BAUD_RATE_TYPE = None): + def link_control(self, link_control_type: LINK_CONTROL_TYPE, baud_rate_type: Optional[BAUD_RATE_TYPE] = None): data: Optional[bytes] if link_control_type == LINK_CONTROL_TYPE.VERIFY_BAUDRATE_TRANSITION_WITH_FIXED_BAUDRATE: diff --git a/python/usb.py b/python/usb.py index 2e236a9999..0f1bff7c16 100644 --- a/python/usb.py +++ b/python/usb.py @@ -1,5 +1,4 @@ import struct -from typing import List from .base import BaseHandle, BaseSTBootloaderHandle, TIMEOUT from .constants import McuType @@ -11,13 +10,13 @@ def __init__(self, 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): + def controlWrite(self, request_type: int, request: int, value: int, index: int, data, timeout: int = TIMEOUT, expect_disconnect: bool = False): 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: + def bulkWrite(self, endpoint: int, data: bytes, 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: @@ -77,7 +76,7 @@ def program(self, address, dat): self._status() # Program - bs = self._mcu_type.config.block_size + bs = min(len(dat), 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] diff --git a/python/utils.py b/python/utils.py new file mode 100644 index 0000000000..f91da64130 --- /dev/null +++ b/python/utils.py @@ -0,0 +1,12 @@ +def crc8_pedal(data): + crc = 0xFF # standard init value + poly = 0xD5 # standard crc8: x8+x7+x6+x4+x2+1 + size = len(data) + for i in range(size - 1, -1, -1): + crc ^= data[i] + for _ in range(8): + if ((crc & 0x80) != 0): + crc = ((crc << 1) ^ poly) & 0xFF + else: + crc <<= 1 + return crc diff --git a/release/make_release.sh b/release/make_release.sh index ce046b8868..5628c4998d 100755 --- a/release/make_release.sh +++ b/release/make_release.sh @@ -2,7 +2,7 @@ set -e DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" -export CERT=$DIR/../../xx/pandaextra/certs/release +export CERT=/home/batman/xx/pandaextra/certs/release if [ ! -f "$CERT" ]; then echo "No release cert found, cannot build release." @@ -19,7 +19,4 @@ rm obj/* scons -u cd obj RELEASE_NAME=$(awk '{print $1}' version) -rm panda.bin panda_h7.bin -mv panda.bin.signed panda.bin -mv panda_h7.bin.signed panda_h7.bin -zip -j ../../release/panda-$RELEASE_NAME.zip version panda.bin bootstub.panda.bin panda_h7.bin bootstub.panda_h7.bin +zip -j ../../release/panda-$RELEASE_NAME.zip version panda.bin.signed bootstub.panda.bin panda_h7.bin.signed bootstub.panda_h7.bin diff --git a/requirements.txt b/requirements.txt index 6b063e91b4..41c5226aa2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,16 +1,19 @@ +ruff libusb1==2.0.1 numpy hexdump>=3.3 pycryptodome==3.9.8 tqdm>=4.14.0 pytest +pytest-mock +pytest-xdist +pytest-timeouts parameterized requests -flake8==3.7.9 cffi==1.14.3 crcmod pre-commit==2.13.0 -pylint==2.15.4 scons==4.4.0 flaky spidev +termcolor diff --git a/tests/SConscript b/tests/SConscript deleted file mode 100644 index 86e9f933d4..0000000000 --- a/tests/SConscript +++ /dev/null @@ -1,13 +0,0 @@ -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/benchmark.py b/tests/benchmark.py new file mode 100755 index 0000000000..c2b0c85c93 --- /dev/null +++ b/tests/benchmark.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +import time +from contextlib import contextmanager + +from panda import Panda, PandaDFU +from panda.tests.hitl.helpers import get_random_can_messages + + +@contextmanager +def print_time(desc): + start = time.perf_counter() + yield + end = time.perf_counter() + print(f"{end - start:.2f}s - {desc}") + + +if __name__ == "__main__": + with print_time("Panda()"): + p = Panda() + + with print_time("PandaDFU.list()"): + PandaDFU.list() + + fxn = [ + 'reset', + 'reconnect', + 'up_to_date', + 'health', + #'flash', + ] + for f in fxn: + with print_time(f"Panda.{f}()"): + getattr(p, f)() + + p.set_can_loopback(True) + + for n in range(6): + msgs = get_random_can_messages(int(10**n)) + with print_time(f"Panda.can_send_many() - {len(msgs)} msgs"): + p.can_send_many(msgs) + + with print_time("Panda.can_recv()"): + m = p.can_recv() diff --git a/tests/black_loopback_test.py b/tests/black_loopback_test.py deleted file mode 100755 index bcfcaefede..0000000000 --- a/tests/black_loopback_test.py +++ /dev/null @@ -1,122 +0,0 @@ -#!/usr/bin/env python3 - -# Loopback test between two black pandas (+ harness and power) -# Tests all buses, including OBD CAN, which is on the same bus as CAN0 in this test. -# To be sure, the test should be run with both harness orientations - - -import os -import sys -import time -import random -import argparse - -sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")) -from panda import Panda # noqa: E402 - -def get_test_string(): - return b"test" + os.urandom(10) - -def run_test(sleep_duration): - pandas = Panda.list() - print(pandas) - - # make sure two pandas are connected - if len(pandas) != 2: - print("Connect white/grey and black panda to run this test!") - assert False - - # connect - pandas[0] = Panda(pandas[0]) - pandas[1] = Panda(pandas[1]) - - # find out the hardware types - if not pandas[0].is_black() or not pandas[1].is_black(): - print("Connect two black pandas to run this test!") - assert False - - for panda in pandas: - # disable safety modes - panda.set_safety_mode(Panda.SAFETY_ALLOUTPUT) - - # test health packet - print("panda health", panda.health()) - - # setup test array (send bus, sender obd, reciever obd, expected busses) - test_array = [ - (0, False, False, [0]), - (1, False, False, [1]), - (2, False, False, [2]), - (0, False, True, [0, 1]), - (1, False, True, []), - (2, False, True, [2]), - (0, True, False, [0]), - (1, True, False, [0]), - (2, True, False, [2]), - (0, True, True, [0, 1]), - (1, True, True, [0, 1]), - (2, True, True, [2]) - ] - - # test both orientations - print("***************** TESTING (0 --> 1) *****************") - test_buses(pandas[0], pandas[1], test_array, sleep_duration) - print("***************** TESTING (1 --> 0) *****************") - test_buses(pandas[1], pandas[0], test_array, sleep_duration) - - -def test_buses(send_panda, recv_panda, test_array, sleep_duration): - for send_bus, send_obd, recv_obd, recv_buses in test_array: - send_panda.send_heartbeat() - recv_panda.send_heartbeat() - print("\nSend bus:", send_bus, " Send OBD:", send_obd, " Recv OBD:", recv_obd) - - # set OBD on pandas - send_panda.set_gmlan(True if send_obd else None) - recv_panda.set_gmlan(True if recv_obd else None) - - # clear and flush - send_panda.can_clear(send_bus) - for recv_bus in recv_buses: - recv_panda.can_clear(recv_bus) - send_panda.can_recv() - recv_panda.can_recv() - - # send the characters - at = random.randint(1, 2000) - st = get_test_string()[0:8] - send_panda.can_send(at, st, send_bus) - time.sleep(0.1) - - # check for receive - _ = send_panda.can_recv() # cans echo - cans_loop = recv_panda.can_recv() - - loop_buses = [] - for loop in cans_loop: - print(" Loop on bus", str(loop[3])) - loop_buses.append(loop[3]) - if len(cans_loop) == 0: - print(" No loop") - - # test loop buses - recv_buses.sort() - loop_buses.sort() - assert recv_buses == loop_buses - print(" TEST PASSED") - - time.sleep(sleep_duration) - print("\n") - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument("-n", type=int, help="Number of test iterations to run") - parser.add_argument("-sleep", type=int, help="Sleep time between tests", default=0) - args = parser.parse_args() - - if args.n is None: - while True: - run_test(sleep_duration=args.sleep) - else: - for i in range(args.n): - run_test(sleep_duration=args.sleep) diff --git a/tests/black_white_loopback_test.py b/tests/black_white_loopback_test.py index f5c6170fe4..5b2312befb 100755 --- a/tests/black_white_loopback_test.py +++ b/tests/black_white_loopback_test.py @@ -6,13 +6,10 @@ import os -import sys import time import random import argparse - -sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")) -from panda import Panda # noqa: E402 +from panda import Panda def get_test_string(): return b"test" + os.urandom(10) @@ -30,8 +27,7 @@ def run_test(sleep_duration): # make sure two pandas are connected if len(pandas) != 2: - print("Connect white/grey and black panda to run this test!") - assert False + raise Exception("Connect white/grey and black panda to run this test!") # connect pandas[0] = Panda(pandas[0]) @@ -48,8 +44,7 @@ def run_test(sleep_duration): black_panda = pandas[1] other_panda = pandas[0] else: - print("Connect white/grey and black panda to run this test!") - assert False + raise Exception("Connect white/grey and black panda to run this test!") # disable safety modes black_panda.set_safety_mode(Panda.SAFETY_ALLOUTPUT) @@ -130,8 +125,7 @@ def test_buses(black_panda, other_panda, direction, test_array, sleep_duration): loop_buses.append(loop[3]) if len(cans_loop) == 0: print(" No loop") - if not os.getenv("NOASSERT"): - assert False + assert not os.getenv("NOASSERT") # test loop buses recv_buses.sort() @@ -141,8 +135,7 @@ def test_buses(black_panda, other_panda, direction, test_array, sleep_duration): zero_bus_errors += 1 else: nonzero_bus_errors += 1 - if not os.getenv("NOASSERT"): - assert False + assert not os.getenv("NOASSERT") else: print(" TEST PASSED") @@ -159,5 +152,5 @@ def test_buses(black_panda, other_panda, direction, test_array, sleep_duration): while True: run_test(sleep_duration=args.sleep) else: - for i in range(args.n): + for _ in range(args.n): run_test(sleep_duration=args.sleep) diff --git a/tests/black_white_relay_endurance.py b/tests/black_white_relay_endurance.py index f44eafd207..db19e72539 100755 --- a/tests/black_white_relay_endurance.py +++ b/tests/black_white_relay_endurance.py @@ -6,13 +6,11 @@ import os -import sys import time import random import argparse -sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")) -from panda import Panda # noqa: E402 +from panda import Panda def get_test_string(): return b"test" + os.urandom(10) @@ -30,8 +28,7 @@ def run_test(sleep_duration): # make sure two pandas are connected if len(pandas) != 2: - print("Connect white/grey and black panda to run this test!") - assert False + raise Exception("Connect white/grey and black panda to run this test!") # connect pandas[0] = Panda(pandas[0]) @@ -48,8 +45,7 @@ def run_test(sleep_duration): black_panda = pandas[1] other_panda = pandas[0] else: - print("Connect white/grey and black panda to run this test!") - assert False + raise Exception("Connect white/grey and black panda to run this test!") # disable safety modes black_panda.set_safety_mode(Panda.SAFETY_ALLOUTPUT) @@ -137,8 +133,7 @@ def test_buses(black_panda, other_panda, direction, test_array, sleep_duration): loop_buses.append(loop[3]) if len(cans_loop) == 0: print(" No loop") - if not os.getenv("NOASSERT"): - assert False + assert os.getenv("NOASSERT") # test loop buses recv_buses.sort() @@ -148,8 +143,7 @@ def test_buses(black_panda, other_panda, direction, test_array, sleep_duration): zero_bus_errors += 1 else: nonzero_bus_errors += 1 - if not os.getenv("NOASSERT"): - assert False + assert os.getenv("NOASSERT") else: print(" TEST PASSED") @@ -166,5 +160,5 @@ def test_buses(black_panda, other_panda, direction, test_array, sleep_duration): while True: run_test(sleep_duration=args.sleep) else: - for i in range(args.n): + for _ in range(args.n): run_test(sleep_duration=args.sleep) diff --git a/tests/black_white_relay_test.py b/tests/black_white_relay_test.py index 4de02ec849..90b33be681 100755 --- a/tests/black_white_relay_test.py +++ b/tests/black_white_relay_test.py @@ -5,13 +5,11 @@ import os -import sys import time import random import argparse -sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")) -from panda import Panda # noqa: E402 +from panda import Panda def get_test_string(): return b"test" + os.urandom(10) @@ -29,8 +27,7 @@ def run_test(sleep_duration): # make sure two pandas are connected if len(pandas) != 2: - print("Connect white/grey and black panda to run this test!") - assert False + raise Exception("Connect white/grey and black panda to run this test!") # connect pandas[0] = Panda(pandas[0]) @@ -50,8 +47,7 @@ def run_test(sleep_duration): black_panda = pandas[1] other_panda = pandas[0] else: - print("Connect white/grey and black panda to run this test!") - assert False + raise Exception("Connect white/grey and black panda to run this test!") # disable safety modes black_panda.set_safety_mode(Panda.SAFETY_ALLOUTPUT) @@ -69,8 +65,7 @@ def run_test(sleep_duration): if not test_buses(black_panda, other_panda, (0, False, [0])): open_errors += 1 - print("Open error") - assert False + raise Exception("Open error") # Switch off relay black_panda.set_safety_mode(Panda.SAFETY_SILENT) @@ -78,8 +73,7 @@ def run_test(sleep_duration): if not test_buses(black_panda, other_panda, (0, False, [0, 2])): closed_errors += 1 - print("Close error") - assert False + raise Exception("Close error") counter += 1 print("Number of cycles:", counter, "Open errors:", open_errors, "Closed errors:", closed_errors, "Content errors:", content_errors) @@ -137,5 +131,5 @@ def test_buses(black_panda, other_panda, test_obj): while True: run_test(sleep_duration=args.sleep) else: - for i in range(args.n): + for _ in range(args.n): run_test(sleep_duration=args.sleep) diff --git a/tests/bulk_write_test.py b/tests/bulk_write_test.py index 047e870ce8..bc5bad3317 100755 --- a/tests/bulk_write_test.py +++ b/tests/bulk_write_test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# type: ignore # for jungle stuff import os import time import threading @@ -9,7 +8,7 @@ JUNGLE = "JUNGLE" in os.environ if JUNGLE: - from panda_jungle import PandaJungle # pylint: disable=import-error + from panda import PandaJungle # The TX buffers on pandas is 0x100 in length. NUM_MESSAGES_PER_BUS = 10000 @@ -30,7 +29,7 @@ def flood_tx(panda): if len(serials) != 2: raise Exception("Connect two pandas to perform this test!") sender = Panda(serials[0]) - receiver = Panda(serials[1]) + receiver = Panda(serials[1]) # type: ignore receiver.set_safety_mode(Panda.SAFETY_ALLOUTPUT) sender.set_safety_mode(Panda.SAFETY_ALLOUTPUT) diff --git a/tests/can_printer.py b/tests/can_printer.py index af0ed0e2ed..15ce89f688 100755 --- a/tests/can_printer.py +++ b/tests/can_printer.py @@ -1,13 +1,10 @@ #!/usr/bin/env python3 - import os -import sys import time from collections import defaultdict import binascii -sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")) -from panda import Panda # noqa: E402 +from panda import Panda # fake def sec_since_boot(): @@ -30,7 +27,7 @@ def can_printer(): if sec_since_boot() - lp > 0.1: dd = chr(27) + "[2J" dd += "%5.2f\n" % (sec_since_boot() - start) - for k, v in sorted(zip(list(msgs.keys()), [binascii.hexlify(x[-1]) for x in list(msgs.values())])): + for k, v in sorted(zip(list(msgs.keys()), [binascii.hexlify(x[-1]) for x in list(msgs.values())], strict=True)): dd += "%s(%6d) %s\n" % ("%04X(%4d)" % (k, k), len(msgs[k]), v) print(dd) lp = sec_since_boot() diff --git a/tests/canfd/test_canfd.py b/tests/canfd/test_canfd.py index 508e0e3992..873bc796ba 100755 --- a/tests/canfd/test_canfd.py +++ b/tests/canfd/test_canfd.py @@ -3,8 +3,9 @@ import time import random from collections import defaultdict -from panda import Panda, calculate_checksum -from panda_jungle import PandaJungle # pylint: disable=import-error +from panda import Panda, calculate_checksum, DLC_TO_LEN +from panda import PandaJungle +from panda.tests.hitl.helpers import time_many_sends H7_HW_TYPES = [Panda.HW_TYPE_RED_PANDA, Panda.HW_TYPE_RED_PANDA_V2] JUNGLE_SERIAL = os.getenv("JUNGLE") @@ -12,13 +13,11 @@ if os.getenv("H7_PANDAS_EXCLUDE"): H7_PANDAS_EXCLUDE = os.getenv("H7_PANDAS_EXCLUDE").strip().split(" ") # type: ignore -#TODO: REMOVE, temporary list of CAN FD lengths, one in panda python lib MUST be used -DLC_TO_LEN = [0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 16, 20, 24, 32, 48] - def panda_reset(): panda_serials = [] panda_jungle = PandaJungle(JUNGLE_SERIAL) + panda_jungle.set_can_silent(True) panda_jungle.set_panda_power(False) time.sleep(1) panda_jungle.set_panda_power(True) @@ -40,6 +39,7 @@ def panda_init(serial, enable_canfd=False, enable_non_iso=False): p = Panda(serial=serial) p.set_power_save(False) for bus in range(3): + p.set_can_speed_kbps(0, 500) if enable_canfd: p.set_can_data_speed_kbps(bus, 2000) if enable_non_iso: @@ -47,6 +47,34 @@ def panda_init(serial, enable_canfd=False, enable_non_iso=False): p.set_safety_mode(Panda.SAFETY_ALLOUTPUT) return p +def test_canfd_throughput(p, p_recv=None): + two_pandas = p_recv is not None + p.set_safety_mode(Panda.SAFETY_ALLOUTPUT) + if two_pandas: + p_recv.set_safety_mode(Panda.SAFETY_ALLOUTPUT) + # enable output mode + else: + p.set_can_loopback(True) + + tests = [ + [500, 1000, 2000], # speeds + [93, 87, 78], # saturation thresholds + ] + + for i in range(len(tests[0])): + # set bus 0 data speed to speed + p.set_can_data_speed_kbps(0, tests[0][i]) + if p_recv is not None: + p_recv.set_can_data_speed_kbps(0, tests[0][i]) + time.sleep(0.05) + + comp_kbps = time_many_sends(p, 0, p_recv=p_recv, msg_count=400, two_pandas=two_pandas, msg_len=64) + + # bit count from https://en.wikipedia.org/wiki/CAN_bus + saturation_pct = (comp_kbps / tests[0][i]) * 100.0 + assert saturation_pct > tests[1][i] + assert saturation_pct < 100 + def canfd_test(p_send, p_recv): for n in range(100): sent_msgs = defaultdict(set) @@ -77,9 +105,6 @@ def canfd_test(p_send, p_recv): for bus in range(3): assert not len(sent_msgs[bus]), f"loop {n}: bus {bus} missing {len(sent_msgs[bus])} messages" - print("Got all messages intact") - - def setup_test(enable_non_iso=False): panda_serials = panda_reset() @@ -97,6 +122,7 @@ def setup_test(enable_non_iso=False): for bus in range(3): p_recv.can_send(0x200, b"dummymessage", bus) p_recv.can_recv() + p_send.can_recv() # Check if all tested buses on sending panda have swithed to CAN FD with BRS for bus in range(3): @@ -116,5 +142,11 @@ def main(): p_send, p_recv = setup_test(enable_non_iso=True) canfd_test(p_send, p_recv) + print("[TEST CAN-FD THROUGHPUT]") + panda_serials = panda_reset() + p_send = panda_init(panda_serials[0], enable_canfd=True) + p_recv = panda_init(panda_serials[1], enable_canfd=True) + test_canfd_throughput(p_send, p_recv) + if __name__ == "__main__": main() diff --git a/tests/ci_reset_hw.py b/tests/ci_reset_hw.py index 6a5427318f..2cdd3f1f4f 100755 --- a/tests/ci_reset_hw.py +++ b/tests/ci_reset_hw.py @@ -1,9 +1,16 @@ #!/usr/bin/env python3 import concurrent.futures -from panda import Panda, PandaDFU +from panda import Panda, PandaDFU, PandaJungle from panda.tests.libs.resetter import Resetter +# all jungles used in the HITL tests +JUNGLES = [ + "058010800f51363038363036", + "23002d000851393038373731" +] + + # Reset + flash all CI hardware to get it into a consistent state # * ports 1-2 are jungles # * port 3 is for the USB hubs @@ -27,7 +34,7 @@ def recover(serial): pandas = Panda.list() print(pandas) - assert len(pandas) == 7 + assert len(pandas) >= 7 with concurrent.futures.ProcessPoolExecutor(max_workers=len(pandas)) as exc: def flash(serial): @@ -36,5 +43,14 @@ def flash(serial): pf.flash() list(exc.map(flash, pandas, timeout=20)) + print("flashing jungle") + # flash jungles + pjs = PandaJungle.list() + assert set(pjs) == set(JUNGLES), f"Got different jungles than expected:\ngot {set(pjs)}\nexpected {set(JUNGLES)}" + for s in pjs: + with PandaJungle(serial=s) as pj: + print(f"- flashing {s}") + pj.flash() + r.cycle_power(delay=0, ports=[1, 2]) r.close() diff --git a/tests/debug_console.py b/tests/debug_console.py index a74cd846e8..8755be1498 100755 --- a/tests/debug_console.py +++ b/tests/debug_console.py @@ -6,8 +6,7 @@ import select import codecs -sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")) -from panda import Panda # noqa: E402 +from panda import Panda setcolor = ["\033[1;32;40m", "\033[1;31;40m"] unsetcolor = "\033[00m" @@ -24,7 +23,7 @@ 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]) + pandas = [Panda(x, claim=claim) for x in serials] decoders = [codecs.getincrementaldecoder('utf-8')() for _ in pandas] if not len(pandas): diff --git a/tests/echo.py b/tests/echo.py index e7cf3e5b3c..90bf4a8c76 100755 --- a/tests/echo.py +++ b/tests/echo.py @@ -1,9 +1,5 @@ #!/usr/bin/env python3 -import os -import sys - -sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")) -from panda import Panda # noqa: E402 +from panda import Panda # This script is intended to be used in conjunction with the echo_loopback_test.py test script from panda jungle. # It sends a reversed response back for every message received containing b"test". diff --git a/tests/elm_car_simulator.py b/tests/elm_car_simulator.py index 8c8360a1e7..27afdbad48 100755 --- a/tests/elm_car_simulator.py +++ b/tests/elm_car_simulator.py @@ -1,18 +1,16 @@ #!/usr/bin/env python3 -# flake8: noqa """Used to Reverse/Test ELM protocol auto detect and OBD message response without a car.""" -import sys import os +import sys import struct import binascii import time import threading from collections import deque -sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")) -from panda import Panda # noqa: E402 +from panda import Panda def lin_checksum(dat): return sum(dat) % 0x100 @@ -100,8 +98,10 @@ def __lin_monitor(self): print("Invalid bytes at start of message") print(" BUFF", lin_buff) continue - if len(lin_buff) < msglen + 4: continue - if lin_checksum(lin_buff[:-1]) != lin_buff[-1]: continue + if len(lin_buff) < msglen + 4: + continue + if lin_checksum(lin_buff[:-1]) != lin_buff[-1]: + continue self.__lin_process_msg(lin_buff[0] & 0xF8, # Priority lin_buff[1], lin_buff[2], lin_buff[3:-1]) lin_buff = bytearray() diff --git a/tests/gmlan_harness_test.py b/tests/gmlan_harness_test.py index f10f98fc3d..950918cff2 100755 --- a/tests/gmlan_harness_test.py +++ b/tests/gmlan_harness_test.py @@ -1,11 +1,8 @@ #!/usr/bin/env python3 -import os -import sys import time -sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")) -from panda import Panda # noqa: E402 +from panda import Panda WHITE_GMLAN_BUS = 3 OTHER_GMLAN_BUS = 1 diff --git a/tests/gps_stability_test.py b/tests/gps_stability_test.py deleted file mode 100755 index f8cb13caab..0000000000 --- a/tests/gps_stability_test.py +++ /dev/null @@ -1,165 +0,0 @@ -#!/usr/bin/env python3 -# flake8: noqa - -import os -import sys -import time -import random -import threading - -sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")) -from panda import Panda, PandaSerial # noqa: E402 - -INIT_GPS_BAUD = 9600 -GPS_BAUD = 460800 - -def connect(): - pandas = Panda.list() - print(pandas) - - # make sure two pandas are connected - if len(pandas) != 2: - print("Connect white and grey/black panda to run this test!") - assert False - - # connect - pandas[0] = Panda(pandas[0]) - pandas[1] = Panda(pandas[1]) - - white_panda = None - gps_panda = None - - # find out which one is white (for spamming the CAN buses) - if pandas[0].get_type() == Panda.HW_TYPE_WHITE_PANDA and pandas[1].get_type() != Panda.HW_TYPE_WHITE_PANDA: - white_panda = pandas[0] - gps_panda = pandas[1] - elif pandas[0].get_type() != Panda.HW_TYPE_WHITE_PANDA and pandas[1].get_type() == Panda.HW_TYPE_WHITE_PANDA: - white_panda = pandas[1] - gps_panda = pandas[0] - else: - print("Connect white and grey/black panda to run this test!") - assert False - return white_panda, gps_panda - -def spam_buses_thread(panda): - try: - panda.set_safety_mode(Panda.SAFETY_ALLOUTPUT) - while True: - at = random.randint(1, 2000) - st = (b"test" + os.urandom(10))[0:8] - bus = random.randint(0, 2) - panda.can_send(at, st, bus) - except Exception as e: - print(e) - -def read_can_thread(panda): - try: - while True: - panda.can_recv() - except Exception as e: - print(e) - -def init_gps(panda): - def add_nmea_checksum(msg): - d = msg[1:] - cs = 0 - for i in d: - cs ^= ord(i) - return msg + "*%02X" % cs - - ser = PandaSerial(panda, 1, INIT_GPS_BAUD) - - # Power cycle the gps by toggling reset - print("Resetting GPS") - panda.set_esp_power(0) - time.sleep(0.5) - panda.set_esp_power(1) - time.sleep(0.5) - - # Upping baud rate - print("Upping GPS baud rate") - msg = str.encode(add_nmea_checksum("$PUBX,41,1,0007,0003,%d,0" % GPS_BAUD) + "\r\n") - ser.write(msg) - time.sleep(1) # needs a wait for it to actually send - - # Reconnecting with the correct baud - ser = PandaSerial(panda, 1, GPS_BAUD) - - # Sending all config messages boardd sends - print("Sending config") - ser.write(b"\xB5\x62\x06\x00\x14\x00\x03\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x1E\x7F") - ser.write(b"\xB5\x62\x06\x3E\x00\x00\x44\xD2") - ser.write(b"\xB5\x62\x06\x00\x14\x00\x00\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x19\x35") - ser.write(b"\xB5\x62\x06\x00\x14\x00\x01\x00\x00\x00\xC0\x08\x00\x00\x00\x08\x07\x00\x01\x00\x01\x00\x00\x00\x00\x00\xF4\x80") - ser.write(b"\xB5\x62\x06\x00\x14\x00\x04\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1D\x85") - ser.write(b"\xB5\x62\x06\x00\x00\x00\x06\x18") - ser.write(b"\xB5\x62\x06\x00\x01\x00\x01\x08\x22") - ser.write(b"\xB5\x62\x06\x00\x01\x00\x02\x09\x23") - ser.write(b"\xB5\x62\x06\x00\x01\x00\x03\x0A\x24") - ser.write(b"\xB5\x62\x06\x08\x06\x00\x64\x00\x01\x00\x00\x00\x79\x10") - ser.write(b"\xB5\x62\x06\x24\x24\x00\x05\x00\x04\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x5A\x63") - ser.write(b"\xB5\x62\x06\x1E\x14\x00\x00\x00\x00\x00\x01\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x3C\x37") - ser.write(b"\xB5\x62\x06\x24\x00\x00\x2A\x84") - ser.write(b"\xB5\x62\x06\x23\x00\x00\x29\x81") - ser.write(b"\xB5\x62\x06\x1E\x00\x00\x24\x72") - ser.write(b"\xB5\x62\x06\x01\x03\x00\x01\x07\x01\x13\x51") - ser.write(b"\xB5\x62\x06\x01\x03\x00\x02\x15\x01\x22\x70") - ser.write(b"\xB5\x62\x06\x01\x03\x00\x02\x13\x01\x20\x6C") - - print("Initialized GPS") - -received_messages = 0 -received_bytes = 0 -send_something = False -def gps_read_thread(panda): - global received_messages, received_bytes, send_something - ser = PandaSerial(panda, 1, GPS_BAUD) - while True: - ret = ser.read(1024) - time.sleep(0.001) - if len(ret): - received_messages += 1 - received_bytes += len(ret) - if send_something: - ser.write("test") - send_something = False - - -CHECK_PERIOD = 5 -MIN_BYTES = 10000 -MAX_BYTES = 50000 - -min_failures = 0 -max_failures = 0 - -if __name__ == "__main__": - white_panda, gps_panda = connect() - - # Start spamming the CAN buses with the white panda. Also read the messages to add load on the GPS panda - threading.Thread(target=spam_buses_thread, args=(white_panda,)).start() - threading.Thread(target=read_can_thread, args=(gps_panda,)).start() - - # Start GPS checking - init_gps(gps_panda) - - read_thread = threading.Thread(target=gps_read_thread, args=(gps_panda,)) - read_thread.start() - while True: - time.sleep(CHECK_PERIOD) - if(received_bytes < MIN_BYTES): - print("Panda is not sending out enough data! Got " + str(received_messages) + " (" + str(received_bytes) + "B) in the last " + str(CHECK_PERIOD) + " seconds") - send_something = True - min_failures += 1 - elif(received_bytes > MAX_BYTES): - print("Panda is not sending out too much data! Got " + str(received_messages) + " (" + str(received_bytes) + "B) in the last " + str(CHECK_PERIOD) + " seconds") - print("Probably not on the right baud rate, got reset somehow? Resetting...") - max_failures += 1 - init_gps(gps_panda) - else: - print("Got " + str(received_messages) + " (" + str(received_bytes) + "B) messages in the last " + str(CHECK_PERIOD) + " seconds.") - if(min_failures > 0): - print("Total min failures: ", min_failures) - if(max_failures > 0): - print("Total max failures: ", max_failures) - received_messages = 0 - received_bytes = 0 diff --git a/tests/hitl/0_dfu.py b/tests/hitl/0_dfu.py deleted file mode 100644 index a8afa24a8a..0000000000 --- a/tests/hitl/0_dfu.py +++ /dev/null @@ -1,43 +0,0 @@ -import pytest -import random - -from panda import Panda, PandaDFU -from panda.python.spi import SpiDevice - -def test_dfu(p): - app_mcu_type = p.get_mcu_type() - dfu_serial = p.get_dfu_serial() - - 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() - - -@pytest.mark.test_panda_types((Panda.HW_TYPE_TRES, )) -def test_dfu_with_spam(p): - dfu_serial = p.get_dfu_serial() - - # enter DFU - p.reset(enter_bootstub=True) - p.reset(enter_bootloader=True) - assert Panda.wait_for_dfu(dfu_serial, timeout=20), "failed to enter DFU" - - # send junk - for _ in range(10): - speed = 1000000 * random.randint(1, 5) - d = SpiDevice(speed=speed) - with d.acquire() as spi: - dat = [random.randint(0, 255) for _ in range(random.randint(1, 100))] - spi.xfer(dat) - - # should still show up - assert dfu_serial in PandaDFU.list() diff --git a/tests/hitl/1_program.py b/tests/hitl/1_program.py index eb0c6a5af3..d4e6e4fd5d 100644 --- a/tests/hitl/1_program.py +++ b/tests/hitl/1_program.py @@ -1,5 +1,6 @@ import os import time +import pytest from panda import Panda, PandaDFU, McuType, BASEDIR @@ -8,9 +9,30 @@ def check_signature(p): assert not p.bootstub, "Flashed firmware not booting. Stuck in bootstub." assert p.up_to_date() + +@pytest.mark.expected_logs(1) +def test_dfu(p): + app_mcu_type = p.get_mcu_type() + dfu_serial = p.get_dfu_serial() + + p.reset(enter_bootstub=True) + p.reset(enter_bootloader=True) + assert Panda.wait_for_dfu(dfu_serial, timeout=19), "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() + # TODO: make more comprehensive bootstub tests and run on a few production ones + current # TODO: also test release-signed app -def test_a_known_bootstub(p): +@pytest.mark.execution_timeout(30) +@pytest.mark.expected_logs(1, 2) +def test_known_bootstub(p): """ Test that compiled app can work with known production bootstub """ @@ -41,6 +63,7 @@ def test_a_known_bootstub(p): code = f.read() dfu.program_bootstub(code) + dfu.reset() p.connect(claim=False, wait=True) @@ -54,11 +77,15 @@ def test_a_known_bootstub(p): check_signature(p) assert not p.bootstub -def test_b_recover(p): +@pytest.mark.execution_timeout(25) +@pytest.mark.expected_logs(1) +def test_recover(p): assert p.recover(timeout=30) check_signature(p) -def test_c_flash(p): +@pytest.mark.execution_timeout(25) +@pytest.mark.expected_logs(3) +def test_flash(p): # test flash from bootstub serial = p._serial assert serial is not None diff --git a/tests/hitl/2_health.py b/tests/hitl/2_health.py index 15321b6427..56d52b46cc 100644 --- a/tests/hitl/2_health.py +++ b/tests/hitl/2_health.py @@ -2,10 +2,10 @@ import pytest from panda import Panda -from panda_jungle import PandaJungle # pylint: disable=import-error +from panda import PandaJungle from panda.tests.hitl.conftest import PandaGroup - +@pytest.mark.expected_logs(1) def test_ignition(p, panda_jungle): # Set harness orientation to #2, since the ignition line is on the wrong SBU bus :/ panda_jungle.set_harness_orientation(PandaJungle.HARNESS_ORIENTATION_2) @@ -18,17 +18,51 @@ def test_ignition(p, panda_jungle): @pytest.mark.test_panda_types(PandaGroup.GEN2) -def test_orientation_detection(p, panda_jungle): - seen_orientations = [] - for i in range(3): - panda_jungle.set_harness_orientation(i) - p.reset() - - detected_harness_orientation = p.health()['car_harness_status'] - print(f"Detected orientation: {detected_harness_orientation}") - if (i == 0 and detected_harness_orientation != 0) or detected_harness_orientation in seen_orientations: - assert False - seen_orientations.append(detected_harness_orientation) +def test_harness_status(p, panda_jungle): + flipped = None + for ignition in [True, False]: + for orientation in [Panda.HARNESS_STATUS_NC, Panda.HARNESS_STATUS_NORMAL, Panda.HARNESS_STATUS_FLIPPED]: + panda_jungle.set_harness_orientation(orientation) + panda_jungle.set_ignition(ignition) + time.sleep(1) + + health = p.health() + detected_orientation = health['car_harness_status'] + print(f"set: {orientation} detected: {detected_orientation}") + + # Orientation + if orientation == Panda.HARNESS_STATUS_NC: + assert detected_orientation == Panda.HARNESS_STATUS_NC + else: + if flipped is None: + flipped = (detected_orientation != orientation) + + if orientation == Panda.HARNESS_STATUS_NORMAL: + assert detected_orientation == (Panda.HARNESS_STATUS_FLIPPED if flipped else Panda.HARNESS_STATUS_NORMAL) + else: + assert detected_orientation == (Panda.HARNESS_STATUS_NORMAL if flipped else Panda.HARNESS_STATUS_FLIPPED) + + # Line ignition + assert health['ignition_line'] == (False if orientation == Panda.HARNESS_STATUS_NC else ignition) + + # SBU voltages + supply_voltage_mV = 1800 if p.get_type() in [Panda.HW_TYPE_TRES, ] else 3300 + + if orientation == Panda.HARNESS_STATUS_NC: + assert health['sbu1_voltage_mV'] > 0.9 * supply_voltage_mV + assert health['sbu2_voltage_mV'] > 0.9 * supply_voltage_mV + else: + relay_line = 'sbu1_voltage_mV' if (detected_orientation == Panda.HARNESS_STATUS_FLIPPED) else 'sbu2_voltage_mV' + ignition_line = 'sbu2_voltage_mV' if (detected_orientation == Panda.HARNESS_STATUS_FLIPPED) else 'sbu1_voltage_mV' + + assert health[relay_line] < 0.1 * supply_voltage_mV + assert health[ignition_line] > health[relay_line] + if ignition: + assert health[ignition_line] < 0.3 * supply_voltage_mV + else: + assert health[ignition_line] > 0.9 * supply_voltage_mV + + @pytest.mark.skip_panda_types((Panda.HW_TYPE_DOS, )) def test_voltage(p): @@ -37,6 +71,7 @@ def test_voltage(p): assert ((voltage > 11000) and (voltage < 13000)) time.sleep(0.1) +@pytest.mark.expected_logs(1) def test_hw_type(p): """ hw type should be same in bootstub as application @@ -89,3 +124,16 @@ def test_microsecond_timer(p): 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)" + +@pytest.mark.expected_logs(1) +def test_logging(p): + p.reset() + + logs = p.get_logs(True) + assert len(logs) > 0 + assert len(p.get_logs()) == 0 + + # we assume the start log is relatively recent + start_logs = list(filter(lambda x: x['msg'] == 'main start', logs[-5:])) + assert len(start_logs) > 0 + assert start_logs[0]['uptime'] < 2 diff --git a/tests/hitl/3_usb_to_can.py b/tests/hitl/3_usb_to_can.py index 22cfde438e..9321eb4e85 100644 --- a/tests/hitl/3_usb_to_can.py +++ b/tests/hitl/3_usb_to_can.py @@ -1,4 +1,3 @@ -import sys import time import pytest from flaky import flaky @@ -60,9 +59,6 @@ def test_reliability(p): et = (time.monotonic() - st) * 1000.0 assert et < 20 - sys.stdout.write("P") - sys.stdout.flush() - @flaky(max_runs=6, min_passes=1) def test_throughput(p): # enable output mode @@ -90,10 +86,6 @@ def test_gmlan(p): p.set_safety_mode(Panda.SAFETY_ALLOUTPUT) p.set_can_loopback(True) - p.set_can_speed_kbps(1, SPEED_NORMAL) - p.set_can_speed_kbps(2, SPEED_NORMAL) - p.set_can_speed_kbps(3, SPEED_GMLAN) - # set gmlan on CAN2 for bus in [Panda.GMLAN_CAN2, Panda.GMLAN_CAN3, Panda.GMLAN_CAN2, Panda.GMLAN_CAN3]: p.set_gmlan(bus) diff --git a/tests/hitl/4_can_loopback.py b/tests/hitl/4_can_loopback.py index f0241d2b78..28986f58b8 100644 --- a/tests/hitl/4_can_loopback.py +++ b/tests/hitl/4_can_loopback.py @@ -7,29 +7,17 @@ from collections import defaultdict from panda import Panda -from panda.tests.hitl.conftest import PandaGroup, PARTIAL_TESTS -from panda.tests.hitl.helpers import time_many_sends, clear_can_buffers +from panda.tests.hitl.conftest import PandaGroup +from panda.tests.hitl.helpers import time_many_sends, get_random_can_messages, clear_can_buffers @flaky(max_runs=3, min_passes=1) +@pytest.mark.execution_timeout(35) def test_send_recv(p, panda_jungle): def test(p_send, p_recv): - p_send.set_can_loopback(False) - p_recv.set_can_loopback(False) - - p_send.can_send_many([(0x1ba, 0, b"message", 0)] * 2) - time.sleep(0.05) - p_recv.can_recv() - p_send.can_recv() - for bus in (0, 1, 2): for speed in (10, 20, 50, 100, 125, 250, 500, 1000): - p_send.set_can_speed_kbps(bus, speed) - p_recv.set_can_speed_kbps(bus, speed) - time.sleep(0.1) - - clear_can_buffers(p_send) - clear_can_buffers(p_recv) - time.sleep(0.1) + clear_can_buffers(p_send, speed) + clear_can_buffers(p_recv, speed) comp_kbps = time_many_sends(p_send, bus, p_recv, two_pandas=True) @@ -45,29 +33,13 @@ def test(p_send, p_recv): @flaky(max_runs=6, min_passes=1) +@pytest.mark.execution_timeout(30) def test_latency(p, panda_jungle): def test(p_send, p_recv): - p_send.set_can_loopback(False) - p_recv.set_can_loopback(False) - - p_send.set_can_speed_kbps(0, 500) - p_recv.set_can_speed_kbps(0, 500) - time.sleep(0.05) - - p_send.can_send_many([(0x1ba, 0, b"testmsg", 0)] * 10) - time.sleep(0.05) - p_recv.can_recv() - p_send.can_recv() - for bus in (0, 1, 2): for speed in (10, 20, 50, 100, 125, 250, 500, 1000): - p_send.set_can_speed_kbps(bus, speed) - p_recv.set_can_speed_kbps(bus, speed) - time.sleep(0.1) - - # clear can buffers - clear_can_buffers(p_send) - clear_can_buffers(p_recv) + clear_can_buffers(p_send, speed) + clear_can_buffers(p_recv, speed) latencies = [] comp_kbps_list = [] @@ -163,11 +135,11 @@ def test(p_send, p_recv, address=None): test(p, panda_jungle, 0x18DB33F1) test(panda_jungle, p, 0x18DB33F1) -def test_bulk_write(p, panda_jungle): - # TODO: doesn't work in partial test mode - if PARTIAL_TESTS: - return + # TODO: why it's not being reset by fixtures reinit? + p.set_obd(False) + panda_jungle.set_obd(False) +def test_bulk_write(p, panda_jungle): # The TX buffers on pandas is 0x100 in length. NUM_MESSAGES_PER_BUS = 10000 @@ -202,27 +174,15 @@ def flood_tx(panda): if len(rx) != 4 * NUM_MESSAGES_PER_BUS: raise Exception("Did not receive all messages!") - # Set back to silent mode - p.set_safety_mode(Panda.SAFETY_SILENT) - def test_message_integrity(p): - clear_can_buffers(p) - p.set_safety_mode(Panda.SAFETY_ALLOUTPUT) - p.set_can_loopback(True) - - n = 250 - for i in range(n): + for i in range(250): sent_msgs = defaultdict(set) for _ in range(random.randrange(10)): - to_send = [] - for __ in range(random.randrange(100)): - bus = random.randrange(3) - addr = random.randrange(1, 1<<29) - dat = bytes([random.getrandbits(8) for _ in range(random.randrange(1, 9))]) - sent_msgs[bus].add((addr, dat)) - to_send.append([addr, None, dat, bus]) + to_send = get_random_can_messages(random.randrange(100)) + for m in to_send: + sent_msgs[m[3]].add((m[0], m[2])) p.can_send_many(to_send, timeout=0) start_time = time.monotonic() @@ -231,13 +191,12 @@ def test_message_integrity(p): for msg in recvd: if msg[3] >= 128: k = (msg[0], bytes(msg[2])) - assert k in sent_msgs[msg[3]-128], f"message {k} was never sent on bus {bus}" + bus = msg[3]-128 + assert k in sent_msgs[bus], f"message {k} was never sent on bus {bus}" sent_msgs[msg[3]-128].discard(k) # if a set isn't empty, messages got dropped for bus in range(3): assert not len(sent_msgs[bus]), f"loop {i}: bus {bus} missing {len(sent_msgs[bus])} messages" - # Set back to silent mode - p.set_safety_mode(Panda.SAFETY_SILENT) print("Got all messages intact") diff --git a/tests/hitl/5_gps.py b/tests/hitl/5_gps.py deleted file mode 100644 index 3cead333ed..0000000000 --- a/tests/hitl/5_gps.py +++ /dev/null @@ -1,21 +0,0 @@ -import time -import pytest - -from panda import PandaSerial -from panda.tests.hitl.conftest import PandaGroup - - -@pytest.mark.test_panda_types(PandaGroup.GPS) -def test_gps_version(p): - serial = PandaSerial(p, 1, 9600) - # Reset and check twice to make sure the enabling works - for _ in range(2): - # Reset GPS - p.set_esp_power(0) - time.sleep(2) - p.set_esp_power(1) - time.sleep(1) - - # Read startup message and check if version is contained - dat = serial.read(0x1000) # Read one full panda DMA buffer. This should include the startup message - assert b'HPG 1.40ROV' in dat \ No newline at end of file diff --git a/tests/hitl/5_spi.py b/tests/hitl/5_spi.py new file mode 100644 index 0000000000..d67f7c4ea5 --- /dev/null +++ b/tests/hitl/5_spi.py @@ -0,0 +1,106 @@ +import binascii +import pytest +import random +from unittest.mock import patch + +from panda import Panda, PandaDFU +from panda.python.spi import SpiDevice, PandaProtocolMismatch, PandaSpiNackResponse + +pytestmark = [ + pytest.mark.test_panda_types((Panda.HW_TYPE_TRES, )) +] + +@pytest.mark.skip("doesn't work, bootloader seems to ignore commands once it sees junk") +@pytest.mark.expected_logs(0) +def test_dfu_with_spam(p): + dfu_serial = p.get_dfu_serial() + + # enter DFU + p.reset(enter_bootstub=True) + p.reset(enter_bootloader=True) + assert Panda.wait_for_dfu(dfu_serial, timeout=19), "failed to enter DFU" + + # send junk + d = SpiDevice() + for _ in range(9): + with d.acquire() as spi: + dat = [random.randint(-1, 255) for _ in range(random.randint(1, 100))] + spi.xfer(dat) + + # should still show up + assert dfu_serial in PandaDFU.list() + +class TestSpi: + def _ping(self, mocker, panda): + # should work with no retries + spy = mocker.spy(panda._handle, '_wait_for_ack') + panda.health() + assert spy.call_count == 2 + mocker.stop(spy) + + @pytest.mark.expected_logs(2) + def test_protocol_version_check(self, p): + for bootstub in (False, True): + p.reset(enter_bootstub=bootstub) + with patch('panda.python.spi.PandaSpiHandle.PROTOCOL_VERSION', return_value="abc"): + # list should still work with wrong version + assert p._serial in Panda.list() + + # connect but raise protocol error + with pytest.raises(PandaProtocolMismatch): + Panda(p._serial) + + @pytest.mark.expected_logs(2) + def test_protocol_version_data(self, p): + for bootstub in (False, True): + p.reset(enter_bootstub=bootstub) + v = p._handle.get_protocol_version() + + uid = binascii.hexlify(v[:12]).decode() + assert uid == p.get_uid() + + hwtype = v[12] + assert hwtype == ord(p.get_type()) + + bstub = v[13] + assert bstub == (0xEE if bootstub else 0xCC) + + def test_all_comm_types(self, mocker, p): + spy = mocker.spy(p._handle, '_wait_for_ack') + + # controlRead + controlWrite + p.health() + p.can_clear(0) + assert spy.call_count == 2*2 + + # bulkRead + bulkWrite + p.can_recv() + p.can_send(0x123, b"somedata", 0) + assert spy.call_count == 2*4 + + def test_bad_header(self, mocker, p): + with patch('panda.python.spi.SYNC', return_value=0): + with pytest.raises(PandaSpiNackResponse): + p._handle.controlRead(Panda.REQUEST_IN, 0xd2, 0, 0, p.HEALTH_STRUCT.size, timeout=50) + self._ping(mocker, p) + + def test_bad_checksum(self, mocker, p): + cnt = p.health()['spi_checksum_error_count'] + with patch('panda.python.spi.PandaSpiHandle._calc_checksum', return_value=0): + with pytest.raises(PandaSpiNackResponse): + p._handle.controlRead(Panda.REQUEST_IN, 0xd2, 0, 0, p.HEALTH_STRUCT.size, timeout=50) + self._ping(mocker, p) + assert (p.health()['spi_checksum_error_count'] - cnt) > 0 + + def test_non_existent_endpoint(self, mocker, p): + for _ in range(10): + ep = random.randint(4, 20) + with pytest.raises(PandaSpiNackResponse): + p._handle.bulkRead(ep, random.randint(1, 1000), timeout=35) + + self._ping(mocker, p) + + with pytest.raises(PandaSpiNackResponse): + p._handle.bulkWrite(ep, b"abc", timeout=35) + + self._ping(mocker, p) diff --git a/tests/hitl/7_internal.py b/tests/hitl/7_internal.py index 08374cbdbf..9717a24959 100644 --- a/tests/hitl/7_internal.py +++ b/tests/hitl/7_internal.py @@ -8,6 +8,7 @@ pytest.mark.test_panda_types(Panda.INTERNAL_DEVICES) ] +@pytest.mark.execution_timeout(2*60) def test_fan_controller(p): start_health = p.health() @@ -46,8 +47,10 @@ def test_fan_cooldown(p): assert p.get_fan_rpm() <= 7000 time.sleep(0.5) -@pytest.mark.skip(reason="fan controller overshoots on fans that need stall recovery") def test_fan_overshoot(p): + if p.get_type() == Panda.HW_TYPE_DOS: + pytest.skip("panda's fan controller overshoots on the comma three fans that need stall recovery") + # make sure it's stopped completely p.set_fan_power(0) while p.get_fan_rpm() > 0: diff --git a/tests/hitl/conftest.py b/tests/hitl/conftest.py index 05fa21ffdb..6deb2e0eee 100644 --- a/tests/hitl/conftest.py +++ b/tests/hitl/conftest.py @@ -1,41 +1,38 @@ -import concurrent.futures import os -import time import pytest +import concurrent.futures -from panda import Panda, PandaDFU +from panda import Panda, PandaDFU, PandaJungle from panda.tests.hitl.helpers import clear_can_buffers -NO_JUNGLE = os.environ.get("NO_JUNGLE", "0") == "1" -if not NO_JUNGLE: - from panda_jungle import PandaJungle # pylint: disable=import-error - +# needed to get output when using xdist +if "DEBUG" in os.environ: + import sys + sys.stdout = sys.stderr SPEED_NORMAL = 500 SPEED_GMLAN = 33.3 BUS_SPEEDS = [(0, SPEED_NORMAL), (1, SPEED_NORMAL), (2, SPEED_NORMAL), (3, SPEED_GMLAN)] -PEDAL_SERIAL = 'none' JUNGLE_SERIAL = os.getenv("PANDAS_JUNGLE") +NO_JUNGLE = os.environ.get("NO_JUNGLE", "0") == "1" PANDAS_EXCLUDE = os.getenv("PANDAS_EXCLUDE", "").strip().split(" ") -PARTIAL_TESTS = os.environ.get("PARTIAL_TESTS", "0") == "1" HW_TYPES = os.environ.get("HW_TYPES", None) +PARALLEL = "PARALLEL" in os.environ +NON_PARALLEL = "NON_PARALLEL" in os.environ +if PARALLEL: + NO_JUNGLE = True + class PandaGroup: H7 = (Panda.HW_TYPE_RED_PANDA, Panda.HW_TYPE_RED_PANDA_V2, Panda.HW_TYPE_TRES) GEN2 = (Panda.HW_TYPE_BLACK_PANDA, Panda.HW_TYPE_UNO, Panda.HW_TYPE_DOS) + H7 - GPS = (Panda.HW_TYPE_BLACK_PANDA, Panda.HW_TYPE_UNO) GMLAN = (Panda.HW_TYPE_WHITE_PANDA, Panda.HW_TYPE_GREY_PANDA) TESTED = (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) -if PARTIAL_TESTS: - # minimal set of pandas to get most of our coverage - # * red panda covers GEN2, STM32H7 - # * black panda covers STM32F4, GEN2, and GPS - PandaGroup.TESTED = (Panda.HW_TYPE_BLACK_PANDA, Panda.HW_TYPE_RED_PANDA) # type: ignore -elif HW_TYPES is not None: +if HW_TYPES is not None: PandaGroup.TESTED = [bytes([int(x), ]) for x in HW_TYPES.strip().split(",")] # type: ignore @@ -49,8 +46,8 @@ def init_all_pandas(): _panda_jungle.set_panda_power(True) for serial in Panda.list(): - if serial not in PANDAS_EXCLUDE and serial != PEDAL_SERIAL: - with Panda(serial=serial) as p: + if serial not in PANDAS_EXCLUDE: + with Panda(serial=serial, claim=False) as p: ptype = bytes(p.get_type()) if ptype in PandaGroup.TESTED: _all_pandas[serial] = ptype @@ -61,7 +58,7 @@ def init_all_pandas(): print(f"{len(_all_pandas)} total pandas") init_all_pandas() -_all_panda_serials = list(_all_pandas.keys()) +_all_panda_serials = sorted(_all_pandas.keys()) def init_jungle(): @@ -75,6 +72,9 @@ def init_jungle(): for bus, speed in BUS_SPEEDS: _panda_jungle.set_can_speed_kbps(bus, speed) + # ensure FW hasn't changed + assert _panda_jungle.up_to_date() + def pytest_configure(config): config.addinivalue_line( @@ -86,7 +86,29 @@ def pytest_configure(config): config.addinivalue_line( "markers", "panda_expect_can_error: mark test to ignore CAN health errors" ) + config.addinivalue_line( + "markers", "expected_logs(amount, ...): mark test to expect a certain amount of panda logs" + ) + +@pytest.hookimpl(tryfirst=True) +def pytest_collection_modifyitems(items): + for item in items: + if item.get_closest_marker('execution_timeout') is None: + item.add_marker(pytest.mark.execution_timeout(10)) + item.add_marker(pytest.mark.setup_timeout(20)) + item.add_marker(pytest.mark.teardown_timeout(20)) + + # xdist grouping by panda + serial = item.name.split("serial=")[1].split(",")[0] + assert len(serial) == 24 + item.add_marker(pytest.mark.xdist_group(serial)) + + needs_jungle = "panda_jungle" in item.fixturenames + if PARALLEL and needs_jungle: + item.add_marker(pytest.mark.skip(reason="no jungle tests in PARALLEL mode")) + elif NON_PARALLEL and not needs_jungle: + item.add_marker(pytest.mark.skip(reason="only running jungle tests")) def pytest_make_parametrize_id(config, val, argname): if val in _all_pandas: @@ -96,12 +118,12 @@ def pytest_make_parametrize_id(config, val, argname): return None -@pytest.fixture(name='panda_jungle') +@pytest.fixture(name='panda_jungle', scope='function') def fixture_panda_jungle(request): init_jungle() return _panda_jungle -@pytest.fixture(name='p') +@pytest.fixture(name='p', scope='function') def func_fixture_panda(request, module_panda): p = module_panda @@ -122,6 +144,11 @@ def func_fixture_panda(request, module_panda): # TODO: reset is slow (2+ seconds) p.reset() + logs = p.get_logs() + last_log_id = logs[-1]['id'] if len(logs) > 0 else 0 + + # ensure FW hasn't changed + assert p.up_to_date() # Run test yield p @@ -137,6 +164,8 @@ def func_fixture_panda(request, module_panda): if p.bootstub: p.reset() + assert not p.bootstub + # TODO: would be nice to make these common checks in the teardown # show up as failed tests instead of "errors" @@ -144,6 +173,20 @@ def func_fixture_panda(request, module_panda): assert p.health()['faults'] == 0 assert p.health()['fault_status'] == 0 + # Make sure that there are no unexpected logs + min_expected_logs = 0 + max_expected_logs = 0 + mark = request.node.get_closest_marker('expected_logs') + if mark: + assert len(mark.args) > 0, "Missing expected logs argument in mark" + min_expected_logs = mark.args[0] + max_expected_logs = mark.args[1] if len(mark.args) > 1 else min_expected_logs + + logs.extend(p.get_logs(True)) + log_id = logs[-1]['id'] if len(logs) > 0 else last_log_id + + assert min_expected_logs <= ((log_id - last_log_id) % 0xFFFE) <= max_expected_logs, f"Unexpected amount of logs. Last 5: {logs[-5:]}" + # Check for SPI errors #assert p.health()['spi_checksum_error_count'] == 0 @@ -154,8 +197,10 @@ def func_fixture_panda(request, module_panda): for i in range(3): can_health = p.can_health(i) assert can_health['bus_off_cnt'] == 0 - assert can_health['receive_error_cnt'] == 0 - assert can_health['transmit_error_cnt'] == 0 + assert can_health['receive_error_cnt'] < 127 + assert can_health['transmit_error_cnt'] < 255 + assert can_health['error_passive'] == 0 + assert can_health['error_warning'] == 0 assert can_health['total_rx_lost_cnt'] == 0 assert can_health['total_tx_lost_cnt'] == 0 assert can_health['total_error_cnt'] == 0 @@ -171,12 +216,6 @@ def fixture_panda_setup(request): # Initialize jungle init_jungle() - # wait for all pandas to come up - for _ in range(50): - if set(_all_panda_serials).issubset(set(Panda.list())): - break - time.sleep(0.1) - # Connect to pandas def cnnct(s): if s == panda_serial: @@ -185,14 +224,13 @@ def cnnct(s): 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: p.set_can_speed_kbps(bus, speed) clear_can_buffers(p) p.set_power_save(False) return p - else: + elif not PARALLEL: with Panda(serial=s) as p: p.reset(reconnect=False) return None diff --git a/tests/hitl/helpers.py b/tests/hitl/helpers.py index 97b0b9607d..d7ac4da0d6 100644 --- a/tests/hitl/helpers.py +++ b/tests/hitl/helpers.py @@ -1,15 +1,26 @@ import time import random +from typing import Optional -def time_many_sends(p, bus, p_recv=None, msg_count=100, two_pandas=False): +def get_random_can_messages(n): + m = [] + for _ in range(n): + bus = random.randrange(3) + addr = random.randrange(1 << 29) + dat = bytes([random.getrandbits(8) for _ in range(random.randrange(1, 9))]) + m.append([addr, None, dat, bus]) + return m + + +def time_many_sends(p, bus, p_recv=None, msg_count=100, two_pandas=False, msg_len=8): if p_recv is None: p_recv = p if p == p_recv and two_pandas: raise ValueError("Cannot have two pandas that are the same panda") msg_id = random.randint(0x100, 0x200) - to_send = [(msg_id, 0, b"\xaa" * 8, bus)] * msg_count + to_send = [(msg_id, 0, b"\xaa" * msg_len, bus)] * msg_count start_time = time.monotonic() p.can_send_many(to_send) @@ -36,12 +47,16 @@ def time_many_sends(p, bus, p_recv=None, msg_count=100, two_pandas=False): assert len(sent_echo) == msg_count end_time = (end_time - start_time) * 1000.0 - comp_kbps = (1 + 11 + 1 + 1 + 1 + 4 + 8 * 8 + 15 + 1 + 1 + 1 + 7) * msg_count / end_time + comp_kbps = (1 + 11 + 1 + 1 + 1 + 4 + (msg_len * 8) + 15 + 1 + 1 + 1 + 7) * msg_count / end_time return comp_kbps -def clear_can_buffers(panda): +def clear_can_buffers(panda, speed: Optional[int] = None): + if speed is not None: + for bus in range(3): + panda.set_can_speed_kbps(bus, speed) + # clear tx buffers for i in range(4): panda.can_clear(i) @@ -54,5 +69,4 @@ def clear_can_buffers(panda): r = panda.can_recv() time.sleep(0.05) if (time.monotonic() - st) > 10: - print("Unable to clear can buffers for panda ", panda.get_serial()) - assert False + raise Exception("Unable to clear can buffers for panda ", panda.get_serial()) diff --git a/tests/hitl/known_bootstub/bootstub.panda_h7.bin b/tests/hitl/known_bootstub/bootstub.panda_h7.bin old mode 100644 new mode 100755 index f587637f92..a6d856bd2c Binary files a/tests/hitl/known_bootstub/bootstub.panda_h7.bin and b/tests/hitl/known_bootstub/bootstub.panda_h7.bin differ diff --git a/tests/hitl/run_parallel_tests.sh b/tests/hitl/run_parallel_tests.sh new file mode 100755 index 0000000000..b6b79d99f6 --- /dev/null +++ b/tests/hitl/run_parallel_tests.sh @@ -0,0 +1,8 @@ +#!/bin/bash +set -e + +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" +cd $DIR + +# n = number of pandas tested +PARALLEL=1 pytest --durations=0 *.py -n 5 --dist loadgroup -x diff --git a/tests/hitl/test.sh b/tests/hitl/run_serial_tests.sh similarity index 67% rename from tests/hitl/test.sh rename to tests/hitl/run_serial_tests.sh index 7382ac5988..31270f044c 100755 --- a/tests/hitl/test.sh +++ b/tests/hitl/run_serial_tests.sh @@ -4,4 +4,4 @@ set -e DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" cd $DIR -pytest --durations=0 --maxfail=1 *.py +NON_PARALLEL=1 pytest --durations=0 *.py -x diff --git a/tests/ir_test.py b/tests/ir_test.py index 8566f37715..e41decf255 100755 --- a/tests/ir_test.py +++ b/tests/ir_test.py @@ -1,10 +1,7 @@ #!/usr/bin/env python3 -import os -import sys import time -sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")) -from panda import Panda # noqa: E402 +from panda import Panda power = 0 if __name__ == "__main__": diff --git a/tests/libpanda/SConscript b/tests/libpanda/SConscript index 71ed743897..c1f8dd683b 100644 --- a/tests/libpanda/SConscript +++ b/tests/libpanda/SConscript @@ -1,13 +1,32 @@ +import platform + +CC = 'gcc' +system = platform.system() +if system == 'Darwin': + # gcc installed by homebrew has version suffix (e.g. gcc-12) in order to be + # distinguishable from system one - which acts as a symlink to clang + CC += '-12' + env = Environment( - CC='gcc', + CC=CC, CFLAGS=[ '-nostdlib', '-fno-builtin', '-std=gnu11', '-Wfatal-errors', + '-Wno-pointer-to-int-cast', ], CPPPATH=[".", "../../board/"], ) -env.PrependENVPath('PATH', '/opt/homebrew/bin') +if system == "Darwin": + env.PrependENVPath('PATH', '/opt/homebrew/bin') + +if GetOption('ubsan'): + flags = [ + "-fsanitize=undefined", + "-fno-sanitize-recover=undefined", + ] + env['CFLAGS'] += flags + env['LINKFLAGS'] += flags env.SharedLibrary("libpanda.so", ["panda.c",]) diff --git a/tests/libpanda/libpanda_py.py b/tests/libpanda/libpanda_py.py index 2beec5ccad..e8f063bf97 100644 --- a/tests/libpanda/libpanda_py.py +++ b/tests/libpanda/libpanda_py.py @@ -1,6 +1,6 @@ import os from cffi import FFI -from typing import List +from typing import Any, List, Protocol from panda import LEN_TO_DLC from panda.tests.libpanda.safety_helpers import PandaSafety, setup_safety_helpers @@ -54,6 +54,34 @@ uint32_t can_slots_empty(can_ring *q); """) +ffi.cdef(""" + typedef struct timestamp_t { + uint16_t year; + uint8_t month; + uint8_t day; + uint8_t weekday; + uint8_t hour; + uint8_t minute; + uint8_t second; + } timestamp_t; + + typedef struct { + uint16_t id; + timestamp_t timestamp; + uint32_t uptime; + char msg[50]; + } log_t; + + extern uint32_t *logging_bank; + extern uint32_t logging_bank_size; + extern uint32_t logging_rate_limit; + + void logging_init(void); + void logging_tick(void); + void log(const char* msg); + uint8_t logging_read(uint8_t *buffer); +""") + setup_safety_helpers(ffi) class CANPacket: @@ -66,13 +94,28 @@ class CANPacket: addr: int data: List[int] -class Panda(PandaSafety): +class Panda(PandaSafety, Protocol): + # CAN + tx1_q: Any + tx2_q: Any + tx3_q: Any + txgmlan_q: Any + def can_set_checksum(self, p: CANPacket) -> None: ... + # 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, addr: int) -> int: ... def set_safety_hooks(self, mode: int, param: int) -> int: ... + # logging + def logging_init(self) -> None: ... + def logging_tick(self) -> None: ... + def log(self, msg: bytearray) -> None: ... + def logging_read(self, buffer: bytearray) -> int: ... + logging_bank: bytearray + logging_bank_size: int + logging_rate_limit: int libpanda: Panda = ffi.dlopen(libpanda_fn) diff --git a/tests/libpanda/panda.c b/tests/libpanda/panda.c index 7f1ecdb133..050e102ad4 100644 --- a/tests/libpanda/panda.c +++ b/tests/libpanda/panda.c @@ -8,8 +8,9 @@ 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) { }; +void refresh_can_tx_slots_available(void); +void can_tx_comms_resume_usb(void) { }; +void can_tx_comms_resume_spi(void) { }; #include "health.h" #include "faults.h" @@ -18,6 +19,13 @@ void usb_outep3_resume_if_paused(void) { }; #include "safety.h" #include "main_declarations.h" #include "drivers/can_common.h" +#include "drivers/logging.h" + +// Board +const board fake_board = { + .has_rtc_battery = true, +}; +const board *current_board = &fake_board; can_ring *rx_q = &can_rx_q; can_ring *txgmlan_q = &can_txgmlan_q; @@ -25,6 +33,10 @@ can_ring *tx1_q = &can_tx1_q; can_ring *tx2_q = &can_tx2_q; can_ring *tx3_q = &can_tx3_q; +uint32_t *logging_bank = (uint32_t *) fake_logging_bank; +uint32_t logging_bank_size = sizeof(fake_logging_bank); +uint32_t logging_rate_limit = LOGGING_MAX_LOGS_PER_MINUTE; + #include "comms_definitions.h" #include "can_comms.h" diff --git a/tests/libpanda/safety_helpers.h b/tests/libpanda/safety_helpers.h index d74d8a2621..22a3c4f13a 100644 --- a/tests/libpanda/safety_helpers.h +++ b/tests/libpanda/safety_helpers.h @@ -71,6 +71,10 @@ bool get_cruise_engaged_prev(void){ return cruise_engaged_prev; } +void set_cruise_engaged_prev(bool engaged){ + cruise_engaged_prev = engaged; +} + bool get_vehicle_moving(void){ return vehicle_moving; } @@ -79,6 +83,22 @@ bool get_acc_main_on(void){ return acc_main_on; } +int get_vehicle_speed_min(void){ + return vehicle_speed.min; +} + +int get_vehicle_speed_max(void){ + return vehicle_speed.max; +} + +int get_current_safety_mode(void){ + return current_safety_mode; +} + +int get_current_safety_param(void){ + return current_safety_param; +} + int get_hw_type(void){ return hw_type; } @@ -125,6 +145,18 @@ void set_desired_angle_last(int t){ desired_angle_last = t; } +int get_desired_angle_last(void){ + return desired_angle_last; +} + +int get_angle_meas_min(void){ + return angle_meas.min; +} + +int get_angle_meas_max(void){ + return angle_meas.max; +} + // ***** car specific helpers ***** diff --git a/tests/libpanda/safety_helpers.py b/tests/libpanda/safety_helpers.py index a6ba4b0412..d10a82b004 100644 --- a/tests/libpanda/safety_helpers.py +++ b/tests/libpanda/safety_helpers.py @@ -1,4 +1,5 @@ # panda safety helpers, from safety_helpers.c +from typing import Protocol def setup_safety_helpers(ffi): ffi.cdef(""" @@ -16,6 +17,10 @@ def setup_safety_helpers(ffi): bool get_brake_pressed_prev(void); bool get_regen_braking_prev(void); bool get_acc_main_on(void); + int get_vehicle_speed_min(void); + int get_vehicle_speed_max(void); + int get_current_safety_mode(void); + int get_current_safety_param(void); void set_torque_meas(int min, int max); int get_torque_meas_min(void); @@ -26,8 +31,12 @@ def setup_safety_helpers(ffi): void set_desired_torque_last(int t); void set_rt_torque_last(int t); void set_desired_angle_last(int t); + int get_desired_angle_last(); + int get_angle_meas_min(void); + int get_angle_meas_max(void); bool get_cruise_engaged_prev(void); + void set_cruise_engaged_prev(bool engaged); bool get_vehicle_moving(void); int get_hw_type(void); void set_timer(uint32_t t); @@ -43,7 +52,7 @@ def setup_safety_helpers(ffi): int get_honda_hw(void); """) -class PandaSafety: +class PandaSafety(Protocol): def set_controls_allowed(self, c: bool) -> None: ... def get_controls_allowed(self) -> bool: ... def get_longitudinal_allowed(self) -> bool: ... @@ -58,18 +67,26 @@ 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 get_vehicle_speed_min(self) -> int: ... + def get_vehicle_speed_max(self) -> int: ... + def get_current_safety_mode(self) -> int: ... + def get_current_safety_param(self) -> int: ... - def set_torque_meas(self, min: int, max: int) -> None: ... # pylint: disable=redefined-builtin + def set_torque_meas(self, min: int, max: int) -> None: ... # noqa: A002 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 set_torque_driver(self, min: int, max: int) -> None: ... # noqa: A002 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_desired_angle_last(self) -> int: ... + def get_angle_meas_min(self) -> int: ... + def get_angle_meas_max(self) -> int: ... def get_cruise_engaged_prev(self) -> bool: ... + def set_cruise_engaged_prev(self, enabled: bool) -> None: ... def get_vehicle_moving(self) -> bool: ... def get_hw_type(self) -> int: ... def set_timer(self, t: int) -> None: ... diff --git a/tests/location_listener.py b/tests/location_listener.py deleted file mode 100755 index d997327ead..0000000000 --- a/tests/location_listener.py +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env python3 -import os -import time -import sys - -sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")) -from panda import Panda, PandaSerial # noqa: 402 - -def add_nmea_checksum(msg): - d = msg[1:] - cs = 0 - for i in d: - cs ^= ord(i) - return msg + "*%02X" % cs - -if __name__ == "__main__": - panda = Panda() - ser = PandaSerial(panda, 1, 9600) - - # power cycle by toggling reset - print("resetting") - panda.set_esp_power(0) - time.sleep(0.5) - panda.set_esp_power(1) - time.sleep(0.5) - print("done") - print(ser.read(1024)) - - # upping baud rate - baudrate = 460800 - - print("upping baud rate") - msg = str.encode(add_nmea_checksum("$PUBX,41,1,0007,0003,%d,0" % baudrate) + "\r\n") - print(msg) - ser.write(msg) - time.sleep(0.1) # needs a wait for it to actually send - - # new panda serial - ser = PandaSerial(panda, 1, baudrate) - - while True: - ret = ser.read(1024) - if len(ret) > 0: - sys.stdout.write(ret.decode('ascii', 'ignore')) - sys.stdout.flush() diff --git a/tests/logging/test.sh b/tests/logging/test.sh new file mode 100755 index 0000000000..9e084772f3 --- /dev/null +++ b/tests/logging/test.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -e + +python -m unittest discover . diff --git a/tests/logging/test_logging.py b/tests/logging/test_logging.py new file mode 100755 index 0000000000..dc24f8b2a1 --- /dev/null +++ b/tests/logging/test_logging.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 + +import random +import unittest +import datetime + +from panda import unpack_log +from panda.tests.libpanda import libpanda_py + +lpp = libpanda_py.libpanda + +class TestLogging(unittest.TestCase): + def setUp(self): + # Clear buffer and setup state + for i in range(lpp.logging_bank_size // 4): + lpp.logging_bank[i] = 0xFFFFFFFF + lpp.logging_init() + + def _get_logs(self): + logs = [] + while True: + log = libpanda_py.ffi.new("uint8_t[64]") + if lpp.logging_read(log) == 0: + break + logs.append(log) + return logs + + def _log(self, msg): + lpp.log(libpanda_py.ffi.new("char[]", msg.encode("utf-8"))) + + def test_random_buffer_init(self): + for i in range(lpp.logging_bank_size // 4): + lpp.logging_bank[i] = random.randint(0, 0xFFFFFFFF) + + lpp.logging_init() + + for i in range(lpp.logging_bank_size // 4): + assert lpp.logging_bank[i] == 0xFFFFFFFF + self.assertEqual(lpp.logging_read(libpanda_py.ffi.new("uint8_t[64]")), 0) + + def test_rate_limit(self): + for _ in range(lpp.logging_rate_limit + 5): + self._log("test") + self.assertEqual(len(self._get_logs()), lpp.logging_rate_limit) + self.assertEqual(len(self._get_logs()), 0) + + for _ in range(62): + lpp.logging_tick() + + for _ in range(lpp.logging_rate_limit + 5): + self._log("test") + + self.assertEqual(len(self._get_logs()), lpp.logging_rate_limit) + self.assertEqual(len(self._get_logs()), 0) + + def test_logging(self): + msg = "testing 123" + self._log(msg) + logs = self._get_logs() + self.assertEqual(len(logs), 1) + log = unpack_log(bytes(logs[0])) + self.assertEqual(log['msg'], msg) + self.assertEqual(log['uptime'], 0) + self.assertEqual(log['id'], 1) + self.assertEqual(log['timestamp'], datetime.datetime(1996, 4, 23, 4, 20, 20)) + + def test_bank_overflow(self): + for i in range(1, 10000): + self._log("test") + + if i % 5 == 0: + lpp.logging_init() + expected_messages = (i % 2048) + (2048 if i >= 2048 else 0) + self.assertEqual(len(self._get_logs()), expected_messages) + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/tests/loopback_test.py b/tests/loopback_test.py index 84925952ae..73bb91be71 100755 --- a/tests/loopback_test.py +++ b/tests/loopback_test.py @@ -1,7 +1,6 @@ #!/usr/bin/env python3 import os -import sys import time import random import argparse @@ -9,8 +8,7 @@ from hexdump import hexdump from itertools import permutations -sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")) -from panda import Panda # noqa: E402 +from panda import Panda def get_test_string(): return b"test" + os.urandom(10) @@ -20,13 +18,12 @@ def run_test(sleep_duration): print(pandas) if len(pandas) < 2: - print("Minimum two pandas are needed for test") - assert False + raise Exception("Minimum two pandas are needed for test") run_test_w_pandas(pandas, sleep_duration) def run_test_w_pandas(pandas, sleep_duration): - h = list([Panda(x) for x in pandas]) + h = [Panda(x) for x in pandas] print("H", h) for hh in h: @@ -116,5 +113,5 @@ def run_test_w_pandas(pandas, sleep_duration): while True: run_test(sleep_duration=args.sleep) else: - for i in range(args.n): + for _ in range(args.n): run_test(sleep_duration=args.sleep) diff --git a/tests/message_drop_test.py b/tests/message_drop_test.py index 31c6f8cbd1..99d35b043d 100755 --- a/tests/message_drop_test.py +++ b/tests/message_drop_test.py @@ -11,12 +11,12 @@ JUNGLE = "JUNGLE" in os.environ if JUNGLE: - from panda_jungle import PandaJungle # pylint: disable=import-error + from panda import PandaJungle # Generate unique messages NUM_MESSAGES_PER_BUS = 10000 messages = [bytes(struct.pack("Q", i)) for i in range(NUM_MESSAGES_PER_BUS)] -tx_messages = list(itertools.chain.from_iterable(map(lambda msg: [[0xaa, None, msg, 0], [0xaa, None, msg, 1], [0xaa, None, msg, 2]], messages))) +tx_messages = list(itertools.chain.from_iterable(([[0xaa, None, msg, 0], [0xaa, None, msg, 1], [0xaa, None, msg, 2]] for msg in messages))) def flood_tx(panda): print('Sending!') @@ -65,6 +65,6 @@ def flood_tx(panda): # Check if we received everything for bus in range(3): - received_msgs = set(map(lambda m: bytes(m[2]), filter(lambda m, b=bus: m[3] == b, rx))) # type: ignore + received_msgs = {bytes(m[2]) for m in filter(lambda m, b=bus: m[3] == b, rx)} # type: ignore dropped_msgs = set(messages).difference(received_msgs) print(f"Bus {bus} dropped msgs: {len(list(dropped_msgs))} / {len(messages)}") diff --git a/tests/pedal/enter_canloader.py b/tests/pedal/enter_canloader.py index bdd0d62865..7465c0a072 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, McuType -from panda.tests.pedal.canhandle import CanHandle +from panda import Panda, CanHandle, McuType if __name__ == "__main__": diff --git a/tests/pedal/test_pedal.py b/tests/pedal/test_pedal.py index 05a0512267..0d0676c45a 100755 --- a/tests/pedal/test_pedal.py +++ b/tests/pedal/test_pedal.py @@ -1,15 +1,12 @@ #!/usr/bin/env python3 import os import time -import subprocess import unittest -from panda import Panda, BASEDIR -from panda_jungle import PandaJungle # pylint: disable=import-error -from panda.tests.pedal.canhandle import CanHandle + +from panda import Panda, PandaJungle, CanHandle, McuType, BASEDIR JUNGLE_SERIAL = os.getenv("PEDAL_JUNGLE") -PEDAL_SERIAL = 'none' PEDAL_BUS = 1 class TestPedal(unittest.TestCase): @@ -30,7 +27,7 @@ def _flash_over_can(self, bus, fw_file): time.sleep(0.1) with open(fw_file, "rb") as code: - PandaJungle.flash_static(CanHandle(self.jungle, bus), code.read()) + PandaJungle.flash_static(CanHandle(self.jungle, bus), code.read(), McuType.F2) def _listen_can_frames(self): self.jungle.can_clear(0xFFFF) @@ -45,16 +42,14 @@ def _listen_can_frames(self): return msgs 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") + self._flash_over_can(PEDAL_BUS, f"{BASEDIR}/board/pedal/obj/pedal_usb.bin.signed") time.sleep(2) - with Panda(PEDAL_SERIAL) as p: + with Panda('pedal') as p: self.assertTrue(p.get_type() == Panda.HW_TYPE_PEDAL) self.assertTrue(self._listen_can_frames() > 40) def test_nonusb_fw(self): - subprocess.check_output(f"cd {BASEDIR} && PEDAL=1 scons", shell=True) - self._flash_over_can(PEDAL_BUS, f"{BASEDIR}board/obj/pedal.bin.signed") + self._flash_over_can(PEDAL_BUS, f"{BASEDIR}board/pedal/obj/pedal.bin.signed") time.sleep(2) self.assertTrue(self._listen_can_frames() > 40) diff --git a/tests/print_logs.py b/tests/print_logs.py new file mode 100755 index 0000000000..dedcdeddbf --- /dev/null +++ b/tests/print_logs.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python3 + +from panda import Panda + +if __name__ == "__main__": + p = Panda() + for l in p.get_logs(): + print(f"{l['id']:<6d} {l['timestamp']} {l['uptime']:6d} - {l['msg']}") diff --git a/tests/ci_reset_internal_hw.py b/tests/reflash_internal_panda.py similarity index 91% rename from tests/ci_reset_internal_hw.py rename to tests/reflash_internal_panda.py index e18b966b6f..c2ad9f8964 100755 --- a/tests/ci_reset_internal_hw.py +++ b/tests/reflash_internal_panda.py @@ -27,6 +27,7 @@ def gpio_set(pin, high): gpio_set(GPIO.HUB_RST_N, 1) # flash bootstub + print("resetting into DFU") gpio_set(GPIO.STM_RST_N, 1) gpio_set(GPIO.STM_BOOT0, 1) time.sleep(1) @@ -34,6 +35,7 @@ def gpio_set(pin, high): gpio_set(GPIO.STM_BOOT0, 0) time.sleep(1) + print("flashing bootstub") PandaDFU(None).recover() gpio_set(GPIO.STM_RST_N, 1) @@ -41,7 +43,7 @@ def gpio_set(pin, high): gpio_set(GPIO.STM_RST_N, 0) time.sleep(1) - # flash app + print("flashing app") p = Panda() assert p.bootstub p.flash() diff --git a/tests/rtc_test.py b/tests/rtc_test.py index 24edf0976e..01c9f4ddd8 100755 --- a/tests/rtc_test.py +++ b/tests/rtc_test.py @@ -1,10 +1,7 @@ #!/usr/bin/env python -import os -import sys import datetime -sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")) -from panda import Panda # noqa: E402 +from panda import Panda if __name__ == "__main__": p = Panda() diff --git a/tests/safety/common.py b/tests/safety/common.py index be5e914c0b..2ff771d536 100644 --- a/tests/safety/common.py +++ b/tests/safety/common.py @@ -3,14 +3,16 @@ import unittest import importlib import numpy as np -from typing import Dict, List, Optional +from typing import Callable, Dict, List, Optional from opendbc.can.packer import CANPacker # pylint: disable=import-error from panda import ALTERNATIVE_EXPERIENCE from panda.tests.libpanda import libpanda_py MAX_WRONG_COUNTERS = 5 +VEHICLE_SPEED_FACTOR = 100 +MessageFunction = Callable[[float], libpanda_py.CANPacket] def sign_of(a): return 1 if a > 0 else -1 @@ -52,18 +54,50 @@ def _regen_test(self): class PandaSafetyTestBase(unittest.TestCase): + safety: libpanda_py.Panda + @classmethod def setUpClass(cls): if cls.__name__ == "PandaSafetyTestBase": cls.safety = None raise unittest.SkipTest + def _reset_safety_hooks(self): + self.safety.set_safety_hooks(self.safety.get_current_safety_mode(), + self.safety.get_current_safety_param()) + def _rx(self, msg): return self.safety.safety_rx_hook(msg) def _tx(self, msg): return self.safety.safety_tx_hook(msg) + def _generic_limit_safety_check(self, msg_function: MessageFunction, min_allowed_value: float, max_allowed_value: float, + min_possible_value: float, max_possible_value: float, test_delta: float = 1, inactive_value: float = 0, + msg_allowed = True, additional_setup: Optional[Callable[[float], None]] = None): + """ + Enforces that a signal within a message is only allowed to be sent within a specific range, min_allowed_value -> max_allowed_value. + Tests the range of min_possible_value -> max_possible_value with a delta of test_delta. + Message is also only allowed to be sent when controls_allowed is true, unless the value is equal to inactive_value. + Message is never allowed if msg_allowed is false, for example when stock longitudinal is enabled and you are sending acceleration requests. + additional_setup is used for extra setup before each _tx, ex: for setting the previous torque for rate limits + """ + + # Ensure that we at least test the allowed_value range + self.assertGreater(max_possible_value, max_allowed_value) + self.assertLessEqual(min_possible_value, min_allowed_value) + + for controls_allowed in [False, True]: + # enforce we don't skip over 0 or inactive + for v in np.concatenate((np.arange(min_possible_value, max_possible_value, test_delta), np.array([0, inactive_value]))): + v = round(v, 2) # floats might not hit exact boundary conditions without rounding + self.safety.set_controls_allowed(controls_allowed) + if additional_setup is not None: + additional_setup(v) + should_tx = controls_allowed and min_allowed_value <= v <= max_allowed_value + should_tx = (should_tx or v == inactive_value) and msg_allowed + self.assertEqual(self._tx(msg_function(v)), should_tx, (controls_allowed, should_tx, v)) + class InterceptorSafetyTest(PandaSafetyTestBase): @@ -170,6 +204,39 @@ def test_accel_actuation_limits(self, stock_longitudinal=False): self.assertEqual(should_tx, self._tx(self._accel_msg(accel))) +class LongitudinalGasBrakeSafetyTest(PandaSafetyTestBase, abc.ABC): + + MIN_BRAKE: int = 0 + MAX_BRAKE: Optional[int] = None + MAX_POSSIBLE_BRAKE: Optional[int] = None + + MIN_GAS: int = 0 + MAX_GAS: Optional[int] = None + INACTIVE_GAS = 0 + MAX_POSSIBLE_GAS: Optional[int] = None + + def test_gas_brake_limits_correct(self): + self.assertIsNotNone(self.MAX_POSSIBLE_BRAKE) + self.assertIsNotNone(self.MAX_POSSIBLE_GAS) + + self.assertGreater(self.MAX_BRAKE, self.MIN_BRAKE) + self.assertGreater(self.MAX_GAS, self.MIN_GAS) + + @abc.abstractmethod + def _send_gas_msg(self, gas: int): + pass + + @abc.abstractmethod + def _send_brake_msg(self, brake: int): + pass + + def test_brake_safety_check(self): + self._generic_limit_safety_check(self._send_brake_msg, self.MIN_BRAKE, self.MAX_BRAKE, 0, self.MAX_POSSIBLE_BRAKE, 1) + + def test_gas_safety_check(self): + self._generic_limit_safety_check(self._send_gas_msg, self.MIN_GAS, self.MAX_GAS, 0, self.MAX_POSSIBLE_GAS, 1, self.INACTIVE_GAS) + + class TorqueSteeringSafetyTestBase(PandaSafetyTestBase, abc.ABC): MAX_RATE_UP = 0 @@ -330,10 +397,15 @@ class DriverTorqueSteeringSafetyTest(TorqueSteeringSafetyTestBase, abc.ABC): @classmethod def setUpClass(cls): + super().setUpClass() if cls.__name__ == "DriverTorqueSteeringSafetyTest": cls.safety = None raise unittest.SkipTest + @abc.abstractmethod + def _torque_driver_msg(self, torque): + pass + def test_non_realtime_limit_up(self): self.safety.set_torque_driver(0, 0) super().test_non_realtime_limit_up() @@ -399,6 +471,17 @@ def test_realtime_limits(self): self.assertTrue(self._tx(self._torque_cmd_msg(sign * (self.MAX_RT_DELTA - 1)))) self.assertTrue(self._tx(self._torque_cmd_msg(sign * (self.MAX_RT_DELTA + 1)))) + def test_reset_driver_torque_measurements(self): + # Tests that the driver torque measurement sample_t is reset on safety mode init + for t in np.linspace(-self.MAX_TORQUE, self.MAX_TORQUE, 6): + self.assertTrue(self._rx(self._torque_driver_msg(t))) + + # reset sample_t by reinitializing the safety mode + self._reset_safety_hooks() + + self.assertEqual(self.safety.get_torque_driver_min(), 0) + self.assertEqual(self.safety.get_torque_driver_max(), 0) + class MotorTorqueSteeringSafetyTest(TorqueSteeringSafetyTestBase, abc.ABC): @@ -505,49 +588,95 @@ def test_torque_measurements(self): self.assertTrue(self.safety.get_torque_meas_min() in min_range) self.assertTrue(self.safety.get_torque_meas_max() in max_range) + def test_reset_torque_measurements(self): + # Tests that the torque measurement sample_t is reset on safety mode init + for t in np.linspace(-self.MAX_TORQUE, self.MAX_TORQUE, 6): + self.assertTrue(self._rx(self._torque_meas_msg(t))) -class AngleSteeringSafetyTest(PandaSafetyTestBase): + # reset sample_t by reinitializing the safety mode + self._reset_safety_hooks() - DEG_TO_CAN: int + self.assertEqual(self.safety.get_torque_meas_min(), 0) + self.assertEqual(self.safety.get_torque_meas_max(), 0) - ANGLE_DELTA_BP: List[float] - ANGLE_DELTA_V: List[float] # windup limit - ANGLE_DELTA_VU: List[float] # unwind limit +class MeasurementSafetyTest(PandaSafetyTestBase): + DEG_TO_CAN: float = 1 @classmethod def setUpClass(cls): - if cls.__name__ == "AngleSteeringSafetyTest": + if cls.__name__ == "MeasurementSafetyTest": cls.safety = None raise unittest.SkipTest @abc.abstractmethod - def _angle_cmd_msg(self, angle: float, enabled: bool): + def _angle_meas_msg(self, angle: float): pass @abc.abstractmethod - def _angle_meas_msg(self, angle: float): + def _speed_msg(self, speed): + pass + + def common_measurement_test(self, msg_func, min_value, max_value, factor, get_min_func, get_max_func): + for val in np.arange(min_value, max_value, 0.5): + for i in range(6): + self.assertTrue(self._rx(msg_func(val + i * 0.1))) + + # assert close by one decimal place + self.assertLessEqual(abs(get_min_func() - val * factor), 1 * abs(factor)) + self.assertLessEqual(abs(get_max_func() - (val + 0.5) * factor), 1 * abs(factor)) + + # reset sample_t by reinitializing the safety mode + self._reset_safety_hooks() + + self.assertEqual(get_min_func(), 0) + self.assertEqual(get_max_func(), 0) + + def test_vehicle_speed_measurements(self): + self.common_measurement_test(self._speed_msg, 0, 80, VEHICLE_SPEED_FACTOR, self.safety.get_vehicle_speed_min, self.safety.get_vehicle_speed_max) + + def test_steering_angle_measurements(self): + self.common_measurement_test(self._angle_meas_msg, -180, 180, self.DEG_TO_CAN, self.safety.get_angle_meas_min, self.safety.get_angle_meas_max) + + +class AngleSteeringSafetyTest(MeasurementSafetyTest): + ANGLE_RATE_BP: List[float] + ANGLE_RATE_UP: List[float] # windup limit + ANGLE_RATE_DOWN: 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 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): + def _reset_angle_measurement(self, angle): for _ in range(6): self._rx(self._angle_meas_msg(angle)) + def _reset_speed_measurement(self, speed): + for _ in range(6): + self._rx(self._speed_msg(speed)) + 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) + max_delta_up = np.interp(s, self.ANGLE_RATE_BP, self.ANGLE_RATE_UP) + max_delta_down = np.interp(s, self.ANGLE_RATE_BP, self.ANGLE_RATE_DOWN) # first test against false positives - self._angle_meas_msg_array(a) - self._rx(self._speed_msg(s)) # pylint: disable=no-member + self._reset_angle_measurement(a) + self._reset_speed_measurement(s) self._set_prev_desired_angle(a) self.safety.set_controls_allowed(1) @@ -584,11 +713,21 @@ def test_angle_cmd_when_enabled(self): self.assertTrue(self._tx(self._angle_cmd_msg(a, False))) def test_angle_cmd_when_disabled(self): - self.safety.set_controls_allowed(0) + # Tests that only angles close to the meas are allowed while + # steer actuation bit is 0, regardless of controls allowed. + for controls_allowed in (True, False): + self.safety.set_controls_allowed(controls_allowed) - self._set_prev_desired_angle(0) - self.assertFalse(self._tx(self._angle_cmd_msg(0, True))) - self.assertFalse(self.safety.get_controls_allowed()) + for steer_control_enabled in (True, False): + for angle_meas in np.arange(-90, 91, 10): + self._reset_angle_measurement(angle_meas) + + for angle_cmd in np.arange(-90, 91, 10): + self._set_prev_desired_angle(angle_cmd) + + # controls_allowed is checked if actuation bit is 1, else the angle must be close to meas (inactive) + should_tx = controls_allowed if steer_control_enabled else angle_cmd == angle_meas + self.assertEqual(should_tx, self._tx(self._angle_cmd_msg(angle_cmd, steer_control_enabled))) @add_regen_tests @@ -785,15 +924,15 @@ def test_vehicle_moving(self): self.assertFalse(self.safety.get_vehicle_moving()) # not moving - self.safety.safety_rx_hook(self._vehicle_moving_msg(0)) + self._rx(self._vehicle_moving_msg(0)) self.assertFalse(self.safety.get_vehicle_moving()) # speed is at threshold - self.safety.safety_rx_hook(self._vehicle_moving_msg(self.STANDSTILL_THRESHOLD)) + self._rx(self._vehicle_moving_msg(self.STANDSTILL_THRESHOLD)) self.assertFalse(self.safety.get_vehicle_moving()) # past threshold - self.safety.safety_rx_hook(self._vehicle_moving_msg(self.STANDSTILL_THRESHOLD + 1)) + self._rx(self._vehicle_moving_msg(self.STANDSTILL_THRESHOLD + 1)) self.assertTrue(self.safety.get_vehicle_moving()) def test_tx_hook_on_wrong_safety_mode(self): @@ -807,19 +946,22 @@ def test_tx_hook_on_wrong_safety_mode(self): test = importlib.import_module("panda.tests.safety."+tf[:-3]) for attr in dir(test): if attr.startswith("Test") and attr != current_test: - tx = getattr(getattr(test, attr), "TX_MSGS") + tc = getattr(test, attr) + tx = tc.TX_MSGS if tx is not None and not attr.endswith('Base'): # No point in comparing different Tesla safety modes if 'Tesla' in attr and 'Tesla' in current_test: continue if attr.startswith('TestToyota') and current_test.startswith('TestToyota'): continue - if {attr, current_test}.issubset({'TestSubaruSafety', 'TestSubaruGen2Safety'}): + if {attr, current_test}.issubset({'TestSubaruGen1TorqueStockLongitudinalSafety', 'TestSubaruGen2TorqueStockLongitudinalSafety'}): continue if {attr, current_test}.issubset({'TestVolkswagenPqSafety', 'TestVolkswagenPqStockSafety', 'TestVolkswagenPqLongSafety'}): continue if {attr, current_test}.issubset({'TestGmCameraSafety', 'TestGmCameraLongitudinalSafety'}): continue + if attr.startswith('TestFord') and current_test.startswith('TestFord'): + continue if attr.startswith('TestHyundaiCanfd') and current_test.startswith('TestHyundaiCanfd'): continue if {attr, current_test}.issubset({'TestVolkswagenMqbSafety', 'TestVolkswagenMqbStockSafety', 'TestVolkswagenMqbLongSafety'}): @@ -845,7 +987,7 @@ def test_tx_hook_on_wrong_safety_mode(self): if attr.startswith('TestHonda'): # exceptions for common msgs across different hondas tx = list(filter(lambda m: m[0] not in [0x1FA, 0x30C, 0x33D], tx)) - all_tx.append(list([m[0], m[1], attr] for m in tx)) + all_tx.append([m[0], m[1], attr] for m in tx) # make sure we got all the msgs self.assertTrue(len(all_tx) >= len(test_files)-1) diff --git a/tests/safety/hyundai_common.py b/tests/safety/hyundai_common.py index fde8f77ce2..ad93809714 100644 --- a/tests/safety/hyundai_common.py +++ b/tests/safety/hyundai_common.py @@ -141,10 +141,10 @@ def test_tester_present_allowed(self): addr, bus = self.DISABLED_ECU_UDS_MSG 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)) + self.assertTrue(self._tx(tester_present)) 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)) + self.assertFalse(self._tx(not_tester_present)) def test_disabled_ecu_alive(self): """ diff --git a/tests/safety/test.sh b/tests/safety/test.sh index 70164ec54e..1c427ee2ca 100755 --- a/tests/safety/test.sh +++ b/tests/safety/test.sh @@ -1,10 +1,7 @@ #!/usr/bin/env bash - -# Loop over all HW_TYPEs, see board/boards/board_declarations.h -# Make sure test fails if one HW_TYPE fails set -e -HW_TYPES=( 6 7 9 ) +HW_TYPES=( 6 9 ) for hw_type in "${HW_TYPES[@]}"; do echo "Testing HW_TYPE: $hw_type" HW_TYPE=$hw_type python -m unittest discover . diff --git a/tests/safety/test_ford.py b/tests/safety/test_ford.py index 8d34598621..7d2a128f53 100755 --- a/tests/safety/test_ford.py +++ b/tests/safety/test_ford.py @@ -8,15 +8,18 @@ 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 -MSG_LateralMotionControl = 0x3D3 # TX by OP, Traffic Jam Assist -MSG_IPMA_Data = 0x3D8 # TX by OP, IPMA and LKAS user interface +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_EngVehicleSpThrottle2 = 0x202 # RX from PCM, for second 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 = 0x186 # TX by OP, ACC controls +MSG_ACCDATA_3 = 0x18A # TX by OP, ACC/TJA user interface +MSG_Lane_Assist_Data1 = 0x3CA # TX by OP, Lane Keep Assist +MSG_LateralMotionControl = 0x3D3 # TX by OP, Lateral Control message +MSG_LateralMotionControl2 = 0x3D6 # TX by OP, alternate Lateral Control message +MSG_IPMA_Data = 0x3D8 # TX by OP, IPMA and LKAS user interface def checksum(msg): @@ -39,6 +42,13 @@ def checksum(msg): chksum = 0xff - (chksum & 0xff) ret[3] = chksum + elif addr == MSG_EngVehicleSpThrottle2: + chksum = (dat[2] >> 3) & 0xf # VehVActlEng_No_Cnt + chksum += (dat[4] >> 5) & 0x3 # VehVActlEng_D_Qf + chksum += dat[6] + dat[7] # Veh_V_ActlEng + chksum = 0xff - (chksum & 0xff) + ret[1] = chksum + return addr, t, ret, bus @@ -48,26 +58,56 @@ class Buttons: TJA_TOGGLE = 2 -class TestFordSafety(common.PandaSafetyTest): +# Ford safety has four different configurations tested here: +# * CAN with stock longitudinal +# * CAN with openpilot longitudinal +# * CAN FD with stock longitudinal +# * CAN FD with openpilot longitudinal + +class TestFordSafetyBase(common.PandaSafetyTest): STANDSTILL_THRESHOLD = 1 RELAY_MALFUNCTION_ADDR = MSG_IPMA_Data RELAY_MALFUNCTION_BUS = 0 - TX_MSGS = [ - [MSG_Steering_Data_FD1, 0], [MSG_Steering_Data_FD1, 2], [MSG_ACCDATA_3, 0], [MSG_Lane_Assist_Data1, 0], - [MSG_LateralMotionControl, 0], [MSG_IPMA_Data, 0], - ] - FWD_BLACKLISTED_ADDRS = {2: [MSG_ACCDATA_3, MSG_Lane_Assist_Data1, MSG_LateralMotionControl, MSG_IPMA_Data]} + FWD_BLACKLISTED_ADDRS = {2: [MSG_ACCDATA_3, MSG_Lane_Assist_Data1, MSG_LateralMotionControl, + MSG_LateralMotionControl2, MSG_IPMA_Data]} FWD_BUS_LOOKUP = {0: 2, 2: 0} + # Max allowed delta between car speeds + MAX_SPEED_DELTA = 2.0 # m/s + + STEER_MESSAGE = 0 + + # Curvature control limits + DEG_TO_CAN = 50000 # 1 / (2e-5) rad to can + MAX_CURVATURE = 0.02 + MAX_CURVATURE_ERROR = 0.002 + CURVATURE_ERROR_MIN_SPEED = 10.0 # m/s + + ANGLE_RATE_BP = [5., 25., 25.] + ANGLE_RATE_UP = [0.0002, 0.0001, 0.0001] # windup limit + ANGLE_RATE_DOWN = [0.000225, 0.00015, 0.00015] # unwind limit + cnt_speed = 0 + cnt_speed_2 = 0 cnt_yaw_rate = 0 - def setUp(self): - self.packer = CANPackerPanda("ford_lincoln_base_pt") - self.safety = libpanda_py.libpanda - self.safety.set_safety_hooks(Panda.SAFETY_FORD, 0) - self.safety.init_tests() + packer: CANPackerPanda + safety: libpanda_py.Panda + + @classmethod + def setUpClass(cls): + if cls.__name__ == "TestFordSafetyBase": + raise unittest.SkipTest + + def _set_prev_desired_angle(self, t): + t = round(t * self.DEG_TO_CAN) + self.safety.set_desired_angle_last(t) + + def _reset_curvature_measurement(self, curvature, speed): + for _ in range(6): + self._rx(self._speed_msg(speed)) + self._rx(self._yaw_rate_msg(curvature, speed)) # Driver brake pedal def _user_brake_msg(self, brake: bool): @@ -80,12 +120,18 @@ def _user_brake_msg(self, brake: bool): } return self.packer.make_can_msg_panda("EngBrakeData", 0, values) - # Vehicle speed + # ABS 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) + # PCM vehicle speed + def _speed_msg_2(self, speed: float, quality_flag=True): + values = {"Veh_V_ActlEng": speed * 3.6, "VehVActlEng_D_Qf": 3 if quality_flag else 0, "VehVActlEng_No_Cnt": self.cnt_speed_2 % 16} + self.__class__.cnt_speed_2 += 1 + return self.packer.make_can_msg_panda("EngVehicleSpThrottle2", 0, values, fix_checksum=checksum) + # Standstill state def _vehicle_moving_msg(self, speed: float): values = {"VehStop_D_Stat": 1 if speed <= self.STANDSTILL_THRESHOLD else 0} @@ -94,7 +140,7 @@ def _vehicle_moving_msg(self, speed: float): # 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} + "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) @@ -121,16 +167,26 @@ 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, 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) + # LCA command + def _lat_ctl_msg(self, enabled: bool, path_offset: float, path_angle: float, curvature: float, curvature_rate: float): + if self.STEER_MESSAGE == MSG_LateralMotionControl: + 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) + elif self.STEER_MESSAGE == MSG_LateralMotionControl2: + values = { + "LatCtl_D2_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 + "LatCtlCrv_NoRate2_Actl": curvature_rate, # Curvature rate [-0.001024|0.001023] 1/meter^2 + "LatCtlCurv_No_Actl": curvature, # Curvature [-0.02|0.02094] 1/meter + } + return self.packer.make_can_msg_panda("LateralMotionControl2", 0, values) # Cruise control buttons def _acc_button_msg(self, button: int, bus: int): @@ -144,12 +200,14 @@ def _acc_button_msg(self, button: int, bus: int): def test_rx_hook(self): # checksum, counter, and quality flag checks for quality_flag in [True, False]: - for msg in ["speed", "yaw"]: + for msg in ["speed", "speed_2", "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 == "speed_2": + to_push = self._speed_msg_2(0, quality_flag=quality_flag) elif msg == "yaw": to_push = self._yaw_rate_msg(0, 0, quality_flag=quality_flag) @@ -157,28 +215,119 @@ def test_rx_hook(self): self.assertEqual(quality_flag, self.safety.get_controls_allowed()) # Mess with checksum to make it fail + to_push[0].data[1] = 0 # Speed 2 checksum 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()) + def test_rx_hook_speed_mismatch(self): + # Ford relies on speed for driver curvature limiting, so it checks two sources + for speed in np.arange(0, 40, 0.5): + for speed_delta in np.arange(-5, 5, 0.1): + speed_2 = round(max(speed + speed_delta, 0), 1) + # Set controls allowed in between rx since first message can reset it + self._rx(self._speed_msg(speed)) + self.safety.set_controls_allowed(True) + self._rx(self._speed_msg_2(speed_2)) + + within_delta = abs(speed - speed_2) <= self.MAX_SPEED_DELTA + self.assertEqual(self.safety.get_controls_allowed(), within_delta) + + def test_angle_measurements(self): + """Tests rx hook correctly parses the curvature measurement from the vehicle speed and yaw rate""" + for speed in np.arange(0.5, 40, 0.5): + for curvature in np.arange(0, self.MAX_CURVATURE * 2, 2e-3): + self._rx(self._speed_msg(speed)) + for c in (curvature, -curvature, 0, 0, 0, 0): + self._rx(self._yaw_rate_msg(c, speed)) + + self.assertEqual(self.safety.get_angle_meas_min(), round(-curvature * self.DEG_TO_CAN)) + self.assertEqual(self.safety.get_angle_meas_max(), round(curvature * self.DEG_TO_CAN)) + + self._rx(self._yaw_rate_msg(0, speed)) + self.assertEqual(self.safety.get_angle_meas_min(), round(-curvature * self.DEG_TO_CAN)) + self.assertEqual(self.safety.get_angle_meas_max(), 0) + + self._rx(self._yaw_rate_msg(0, speed)) + self.assertEqual(self.safety.get_angle_meas_min(), 0) + self.assertEqual(self.safety.get_angle_meas_max(), 0) + 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))) + for speed in (self.CURVATURE_ERROR_MIN_SPEED - 1, + self.CURVATURE_ERROR_MIN_SPEED + 1): + 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) + self._set_prev_desired_angle(curvature) + self._reset_curvature_measurement(curvature, speed) + + should_tx = path_offset == 0 and path_angle == 0 and curvature_rate == 0 + # when request bit is 0, only allow curvature of 0 since the signal range + # is not large enough to enforce it tracking measured + should_tx = should_tx and (controls_allowed if steer_control_enabled else curvature == 0) + with self.subTest(controls_allowed=controls_allowed, steer_control_enabled=steer_control_enabled, + path_offset=path_offset, path_angle=path_angle, curvature_rate=curvature_rate, + curvature=curvature): + self.assertEqual(should_tx, self._tx(self._lat_ctl_msg(steer_control_enabled, path_offset, path_angle, curvature, curvature_rate))) + + def test_curvature_rate_limit_up(self): + """ + When the curvature error is exceeded, commanded curvature must start moving towards meas respecting rate limits. + Since panda allows higher rate limits to avoid false positives, we need to allow a lower rate to move towards meas. + """ + self.safety.set_controls_allowed(True) + small_curvature = 2 / self.DEG_TO_CAN # significant small amount of curvature to cross boundary + + for speed in np.arange(0, 40, 0.5): + limit_command = speed > self.CURVATURE_ERROR_MIN_SPEED + max_delta_up = np.interp(speed, self.ANGLE_RATE_BP, self.ANGLE_RATE_UP) + max_delta_up_lower = np.interp(speed + 1, self.ANGLE_RATE_BP, self.ANGLE_RATE_UP) + + cases = [ + (not limit_command, 0), + (not limit_command, max_delta_up_lower - small_curvature), + (True, max_delta_up_lower), + (True, max_delta_up), + (False, max_delta_up + small_curvature), + ] + + for sign in (-1, 1): + self._reset_curvature_measurement(sign * (self.MAX_CURVATURE_ERROR + 1e-3), speed) + for should_tx, curvature in cases: + self._set_prev_desired_angle(sign * small_curvature) + self.assertEqual(should_tx, self._tx(self._lat_ctl_msg(True, 0, 0, sign * (small_curvature + curvature), 0))) + + def test_curvature_rate_limit_down(self): + self.safety.set_controls_allowed(True) + small_curvature = 2 / self.DEG_TO_CAN # significant small amount of curvature to cross boundary + + for speed in np.arange(0, 40, 0.5): + limit_command = speed > self.CURVATURE_ERROR_MIN_SPEED + max_delta_down = np.interp(speed, self.ANGLE_RATE_BP, self.ANGLE_RATE_DOWN) + max_delta_down_lower = np.interp(speed + 1, self.ANGLE_RATE_BP, self.ANGLE_RATE_DOWN) + + cases = [ + (not limit_command, self.MAX_CURVATURE), + (not limit_command, self.MAX_CURVATURE - max_delta_down_lower + small_curvature), + (True, self.MAX_CURVATURE - max_delta_down_lower), + (True, self.MAX_CURVATURE - max_delta_down), + (False, self.MAX_CURVATURE - max_delta_down - small_curvature), + ] + + for sign in (-1, 1): + self._reset_curvature_measurement(sign * (self.MAX_CURVATURE - self.MAX_CURVATURE_ERROR - 1e-3), speed) + for should_tx, curvature in cases: + self._set_prev_desired_angle(sign * self.MAX_CURVATURE) + self.assertEqual(should_tx, self._tx(self._lat_ctl_msg(True, 0, 0, sign * curvature, 0))) def test_prevent_lkas_action(self): self.safety.set_controls_allowed(1) @@ -205,5 +354,118 @@ def test_acc_buttons(self): self.assertEqual(enabled, self._tx(self._acc_button_msg(Buttons.CANCEL, bus))) +class TestFordStockSafety(TestFordSafetyBase): + STEER_MESSAGE = MSG_LateralMotionControl + + TX_MSGS = [ + [MSG_Steering_Data_FD1, 0], [MSG_Steering_Data_FD1, 2], [MSG_ACCDATA_3, 0], [MSG_Lane_Assist_Data1, 0], + [MSG_LateralMotionControl, 0], [MSG_IPMA_Data, 0], + ] + + def setUp(self): + self.packer = CANPackerPanda("ford_lincoln_base_pt") + self.safety = libpanda_py.libpanda + self.safety.set_safety_hooks(Panda.SAFETY_FORD, 0) + self.safety.init_tests() + + +class TestFordCANFDStockSafety(TestFordSafetyBase): + STEER_MESSAGE = MSG_LateralMotionControl2 + + TX_MSGS = [ + [MSG_Steering_Data_FD1, 0], [MSG_Steering_Data_FD1, 2], [MSG_ACCDATA_3, 0], [MSG_Lane_Assist_Data1, 0], + [MSG_LateralMotionControl2, 0], [MSG_IPMA_Data, 0], + ] + + def setUp(self): + self.packer = CANPackerPanda("ford_lincoln_base_pt") + self.safety = libpanda_py.libpanda + self.safety.set_safety_hooks(Panda.SAFETY_FORD, Panda.FLAG_FORD_CANFD) + self.safety.init_tests() + + +class TestFordLongitudinalSafetyBase(TestFordSafetyBase): + FWD_BLACKLISTED_ADDRS = {2: [MSG_ACCDATA, MSG_ACCDATA_3, MSG_Lane_Assist_Data1, MSG_LateralMotionControl, + MSG_LateralMotionControl2, MSG_IPMA_Data]} + + MAX_ACCEL = 2.0 # accel is used for brakes, but openpilot can set positive values + MIN_ACCEL = -3.5 + INACTIVE_ACCEL = 0.0 + + MAX_GAS = 2.0 + MIN_GAS = -0.5 + INACTIVE_GAS = -5.0 + + @classmethod + def setUpClass(cls): + if cls.__name__ == "TestFordLongitudinalSafetyBase": + raise unittest.SkipTest + + # ACC command + def _acc_command_msg(self, gas: float, brake: float, cmbb_deny: bool = False): + values = { + "AccPrpl_A_Rq": gas, # [-5|5.23] m/s^2 + "AccBrkTot_A_Rq": brake, # [-20|11.9449] m/s^2 + "CmbbDeny_B_Actl": 1 if cmbb_deny else 0, # [0|1] deny AEB actuation + } + return self.packer.make_can_msg_panda("ACCDATA", 0, values) + + def test_stock_aeb(self): + # Test that CmbbDeny_B_Actl is never 1, it prevents the ABS module from actuating AEB requests from ACCDATA_2 + for controls_allowed in (True, False): + self.safety.set_controls_allowed(controls_allowed) + for cmbb_deny in (True, False): + should_tx = not cmbb_deny + self.assertEqual(should_tx, self._tx(self._acc_command_msg(self.INACTIVE_GAS, self.INACTIVE_ACCEL, cmbb_deny))) + should_tx = controls_allowed and not cmbb_deny + self.assertEqual(should_tx, self._tx(self._acc_command_msg(self.MAX_GAS, self.MAX_ACCEL, cmbb_deny))) + + def test_gas_safety_check(self): + for controls_allowed in (True, False): + self.safety.set_controls_allowed(controls_allowed) + for gas in np.concatenate((np.arange(self.MIN_GAS - 2, self.MAX_GAS + 2, 0.05), [self.INACTIVE_GAS])): + gas = round(gas, 2) # floats might not hit exact boundary conditions without rounding + should_tx = (controls_allowed and self.MIN_GAS <= gas <= self.MAX_GAS) or gas == self.INACTIVE_GAS + self.assertEqual(should_tx, self._tx(self._acc_command_msg(gas, self.INACTIVE_ACCEL))) + + def test_brake_safety_check(self): + for controls_allowed in (True, False): + self.safety.set_controls_allowed(controls_allowed) + for brake in np.arange(self.MIN_ACCEL - 2, self.MAX_ACCEL + 2, 0.05): + brake = round(brake, 2) # floats might not hit exact boundary conditions without rounding + should_tx = (controls_allowed and self.MIN_ACCEL <= brake <= self.MAX_ACCEL) or brake == self.INACTIVE_ACCEL + self.assertEqual(should_tx, self._tx(self._acc_command_msg(self.INACTIVE_GAS, brake))) + + +class TestFordLongitudinalSafety(TestFordLongitudinalSafetyBase): + STEER_MESSAGE = MSG_LateralMotionControl + + TX_MSGS = [ + [MSG_Steering_Data_FD1, 0], [MSG_Steering_Data_FD1, 2], [MSG_ACCDATA, 0], [MSG_ACCDATA_3, 0], [MSG_Lane_Assist_Data1, 0], + [MSG_LateralMotionControl, 0], [MSG_IPMA_Data, 0], + ] + + def setUp(self): + self.packer = CANPackerPanda("ford_lincoln_base_pt") + self.safety = libpanda_py.libpanda + self.safety.set_safety_hooks(Panda.SAFETY_FORD, Panda.FLAG_FORD_LONG_CONTROL) + self.safety.init_tests() + + +class TestFordCANFDLongitudinalSafety(TestFordLongitudinalSafetyBase): + STEER_MESSAGE = MSG_LateralMotionControl2 + + TX_MSGS = [ + [MSG_Steering_Data_FD1, 0], [MSG_Steering_Data_FD1, 2], [MSG_ACCDATA, 0], [MSG_ACCDATA_3, 0], [MSG_Lane_Assist_Data1, 0], + [MSG_LateralMotionControl2, 0], [MSG_IPMA_Data, 0], + ] + + def setUp(self): + self.packer = CANPackerPanda("ford_lincoln_base_pt") + self.safety = libpanda_py.libpanda + self.safety.set_safety_hooks(Panda.SAFETY_FORD, Panda.FLAG_FORD_LONG_CONTROL | Panda.FLAG_FORD_CANFD) + self.safety.init_tests() + + if __name__ == "__main__": unittest.main() diff --git a/tests/safety/test_gm.py b/tests/safety/test_gm.py index 02a7b6d9d0..8b84d1cb4e 100755 --- a/tests/safety/test_gm.py +++ b/tests/safety/test_gm.py @@ -17,8 +17,34 @@ class Buttons: class GmLongitudinalBase(common.PandaSafetyTest): # pylint: disable=no-member,abstract-method + MAX_GAS = 0 + MAX_REGEN = 0 + INACTIVE_REGEN = 0 + MAX_BRAKE = 400 + PCM_CRUISE = False # openpilot can control the PCM state if longitudinal + def _send_brake_msg(self, brake): + values = {"FrictionBrakeCmd": -brake} + return self.packer_chassis.make_can_msg_panda("EBCMFrictionBrakeCmd", self.BRAKE_BUS, values) + + def _send_gas_msg(self, gas): + values = {"GasRegenCmd": gas} + return self.packer.make_can_msg_panda("ASCMGasRegenCmd", 0, values) + + # override these tests from PandaSafetyTest, GM longitudinal uses button enable + def _pcm_status_msg(self, enable): + raise NotImplementedError + + 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_resume_buttons(self): """ SET and RESUME enter controls allowed on their falling and rising edges, respectively. @@ -41,18 +67,23 @@ def test_cancel_button(self): 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 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) > 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 _pcm_status_msg(self, enable): - pass + 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 gas_regen in range(0, 2 ** 12 - 1): + self.safety.set_controls_allowed(enabled) + should_tx = ((enabled and self.MAX_REGEN <= gas_regen <= self.MAX_GAS) or + gas_regen == self.INACTIVE_REGEN) + self.assertEqual(should_tx, self._tx(self._send_gas_msg(gas_regen)), (enabled, gas_regen)) class TestGmSafetyBase(common.PandaSafetyTest, common.DriverTorqueSteeringSafetyTest): @@ -70,11 +101,6 @@ class TestGmSafetyBase(common.PandaSafetyTest, common.DriverTorqueSteeringSafety DRIVER_TORQUE_ALLOWANCE = 65 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 @@ -118,14 +144,6 @@ def _user_gas_msg(self, gas): 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", self.BRAKE_BUS, values) - - def _send_gas_msg(self, gas): - values = {"GasRegenCmd": gas} - return self.packer.make_can_msg_panda("ASCMGasRegenCmd", 0, values) - def _torque_driver_msg(self, torque): values = {"LKADriverAppldTrq": torque} return self.packer.make_can_msg_panda("PSCMStatus", 0, values) @@ -138,24 +156,6 @@ 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): - for enabled in [0, 1]: - for b in range(0, 500): - self.safety.set_controls_allowed(enabled) - 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): - # Block if enabled and out of actuation range, disabled and not inactive regen, or if stock longitudinal - for enabled in [0, 1]: - for gas_regen in range(0, 2 ** 12 - 1): - self.safety.set_controls_allowed(enabled) - 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 @@ -169,7 +169,6 @@ class TestGmAscmSafety(GmLongitudinalBase, TestGmSafetyBase): MAX_GAS = 3072 MAX_REGEN = 1404 INACTIVE_REGEN = 1404 - MAX_BRAKE = 400 def setUp(self): self.packer = CANPackerPanda("gm_global_a_powertrain_generated") @@ -222,13 +221,6 @@ def test_buttons(self): self._rx(self._pcm_status_msg(enabled)) self.assertEqual(enabled, self._tx(self._button_msg(Buttons.CANCEL))) - # 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 @@ -239,7 +231,6 @@ class TestGmCameraLongitudinalSafety(GmLongitudinalBase, TestGmCameraSafetyBase) MAX_GAS = 3400 MAX_REGEN = 1514 INACTIVE_REGEN = 1554 - MAX_BRAKE = 400 def setUp(self): self.packer = CANPackerPanda("gm_global_a_powertrain_generated") diff --git a/tests/safety/test_honda.py b/tests/safety/test_honda.py index 8e6bed0f8c..11b1c623e9 100755 --- a/tests/safety/test_honda.py +++ b/tests/safety/test_honda.py @@ -132,7 +132,7 @@ def test_rx_hook(self): self.assertFalse(self.safety.get_controls_allowed()) # restore counters for future tests with a couple of good messages - for i in range(2): + for _ in range(2): self.safety.set_controls_allowed(1) self._rx(self._button_msg(Btn.SET)) self._rx(self._speed_msg(0)) @@ -296,8 +296,8 @@ def test_acc_hud_safety_check(self): self.safety.set_controls_allowed(controls_allowed) for pcm_gas in range(0, 255): for pcm_speed in range(0, 100): - 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))) + send = (controls_allowed and pcm_gas <= self.MAX_GAS) or (pcm_gas == 0 and pcm_speed == 0) + self.assertEqual(send, self._tx(self._send_acc_hud_msg(pcm_gas, pcm_speed))) def test_fwd_hook(self): # normal operation, not forwarding AEB @@ -483,10 +483,10 @@ def test_spam_cancel_safety_check(self): def test_diagnostics(self): 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)) + self.assertTrue(self._tx(tester_present)) 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)) + self.assertFalse(self._tx(not_tester_present)) def test_radar_alive(self): # If the radar knockout failed, make sure the relay malfunction is shown @@ -499,8 +499,8 @@ def test_gas_safety_check(self): for gas in np.arange(self.NO_GAS, self.MAX_GAS + 2000, 100): 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)), (controls_allowed, gas, accel)) + send = (controls_allowed and 0 <= gas <= self.MAX_GAS) or gas == self.NO_GAS + self.assertEqual(send, self._tx(self._send_gas_brake_msg(gas, accel)), (controls_allowed, gas, accel)) def test_brake_safety_check(self): for controls_allowed in [True, False]: diff --git a/tests/safety/test_hyundai_canfd.py b/tests/safety/test_hyundai_canfd.py index 4e49eab6a9..39b3845ba7 100755 --- a/tests/safety/test_hyundai_canfd.py +++ b/tests/safety/test_hyundai_canfd.py @@ -42,6 +42,7 @@ class TestHyundaiCanfdBase(HyundaiButtonBase, common.PandaSafetyTest, common.Dri @classmethod def setUpClass(cls): + super().setUpClass() if cls.__name__ == "TestHyundaiCanfdBase": cls.packer = None cls.safety = None @@ -95,7 +96,8 @@ class TestHyundaiCanfdHDA1Base(TestHyundaiCanfdBase): @classmethod def setUpClass(cls): - if cls.__name__ in ("TestHyundaiCanfdHDA1", "TestHyundaiCanfdHDA1AltButtons") or cls.__name__.endswith('Base'): + super().setUpClass() + if cls.__name__ in ("TestHyundaiCanfdHDA1", "TestHyundaiCanfdHDA1AltButtons"): cls.packer = None cls.safety = None raise unittest.SkipTest diff --git a/tests/safety/test_mazda.py b/tests/safety/test_mazda.py index 79397782d2..7db588dea3 100755 --- a/tests/safety/test_mazda.py +++ b/tests/safety/test_mazda.py @@ -35,9 +35,9 @@ def _torque_meas_msg(self, torque): values = {"STEER_TORQUE_MOTOR": torque} return self.packer.make_can_msg_panda("STEER_TORQUE", 0, values) -# def _torque_driver_msg(self, torque): -# values = {"STEER_TORQUE_DRIVER": torque} -# return self.packer.make_can_msg_panda("STEER_TORQUE", 0, values) + def _torque_driver_msg(self, torque): + values = {"STEER_TORQUE_SENSOR": torque} + return self.packer.make_can_msg_panda("STEER_TORQUE", 0, values) def _torque_cmd_msg(self, torque, steer_req=1): values = {"LKAS_REQUEST": torque} diff --git a/tests/safety/test_nissan.py b/tests/safety/test_nissan.py index bdd15f177b..80d97f50f6 100755 --- a/tests/safety/test_nissan.py +++ b/tests/safety/test_nissan.py @@ -19,12 +19,12 @@ class TestNissanSafety(common.PandaSafetyTest, common.AngleSteeringSafetyTest): # 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 + ANGLE_RATE_BP = [0., 5., 15.] + ANGLE_RATE_UP = [5., .8, .15] # windup limit + ANGLE_RATE_DOWN = [5., 3.5, .4] # unwind limit def setUp(self): - self.packer = CANPackerPanda("nissan_x_trail_2017") + self.packer = CANPackerPanda("nissan_x_trail_2017_generated") self.safety = libpanda_py.libpanda self.safety.set_safety_hooks(Panda.SAFETY_NISSAN, 0) self.safety.init_tests() @@ -81,7 +81,7 @@ def test_acc_buttons(self): class TestNissanLeafSafety(TestNissanSafety): def setUp(self): - self.packer = CANPackerPanda("nissan_leaf_2018") + self.packer = CANPackerPanda("nissan_leaf_2018_generated") self.safety = libpanda_py.libpanda self.safety.set_safety_hooks(Panda.SAFETY_NISSAN, 0) self.safety.init_tests() diff --git a/tests/safety/test_subaru.py b/tests/safety/test_subaru.py index fea72bfce9..bbf9143f20 100755 --- a/tests/safety/test_subaru.py +++ b/tests/safety/test_subaru.py @@ -3,20 +3,43 @@ from panda import Panda from panda.tests.libpanda import libpanda_py import panda.tests.safety.common as common -from panda.tests.safety.common import CANPackerPanda - - -class TestSubaruSafety(common.PandaSafetyTest, common.DriverTorqueSteeringSafetyTest): - TX_MSGS = [[0x122, 0], [0x221, 0], [0x321, 0], [0x322, 0], [0x323, 0]] - STANDSTILL_THRESHOLD = 0 # kph - RELAY_MALFUNCTION_ADDR = 0x122 - RELAY_MALFUNCTION_BUS = 0 - FWD_BLACKLISTED_ADDRS = {2: [0x122, 0x321, 0x322, 0x323]} - FWD_BUS_LOOKUP = {0: 2, 2: 0} - - MAX_RATE_UP = 50 - MAX_RATE_DOWN = 70 - MAX_TORQUE = 2047 +from panda.tests.safety.common import CANPackerPanda, MeasurementSafetyTest +from functools import partial + + +MSG_SUBARU_Brake_Status = 0x13c +MSG_SUBARU_CruiseControl = 0x240 +MSG_SUBARU_Throttle = 0x40 +MSG_SUBARU_Steering_Torque = 0x119 +MSG_SUBARU_Wheel_Speeds = 0x13a +MSG_SUBARU_ES_LKAS = 0x122 +MSG_SUBARU_ES_Brake = 0x220 +MSG_SUBARU_ES_Distance = 0x221 +MSG_SUBARU_ES_Status = 0x222 +MSG_SUBARU_ES_DashStatus = 0x321 +MSG_SUBARU_ES_LKAS_State = 0x322 +MSG_SUBARU_ES_Infotainment = 0x323 + +SUBARU_MAIN_BUS = 0 +SUBARU_ALT_BUS = 1 +SUBARU_CAM_BUS = 2 + + +def lkas_tx_msgs(alt_bus): + return [[MSG_SUBARU_ES_LKAS, SUBARU_MAIN_BUS], + [MSG_SUBARU_ES_Distance, alt_bus], + [MSG_SUBARU_ES_DashStatus, SUBARU_MAIN_BUS], + [MSG_SUBARU_ES_LKAS_State, SUBARU_MAIN_BUS], + [MSG_SUBARU_ES_Infotainment, SUBARU_MAIN_BUS]] + + +class TestSubaruSafetyBase(common.PandaSafetyTest, MeasurementSafetyTest): + FLAGS = 0 + STANDSTILL_THRESHOLD = 0 # kph + RELAY_MALFUNCTION_ADDR = MSG_SUBARU_ES_LKAS + RELAY_MALFUNCTION_BUS = SUBARU_MAIN_BUS + FWD_BUS_LOOKUP = {SUBARU_MAIN_BUS: SUBARU_CAM_BUS, SUBARU_CAM_BUS: SUBARU_MAIN_BUS} + FWD_BLACKLISTED_ADDRS = {SUBARU_CAM_BUS: [MSG_SUBARU_ES_LKAS, MSG_SUBARU_ES_DashStatus, MSG_SUBARU_ES_LKAS_State, MSG_SUBARU_ES_Infotainment]} MAX_RT_DELTA = 940 RT_INTERVAL = 250000 @@ -24,12 +47,16 @@ class TestSubaruSafety(common.PandaSafetyTest, common.DriverTorqueSteeringSafety DRIVER_TORQUE_ALLOWANCE = 60 DRIVER_TORQUE_FACTOR = 50 - ALT_BUS = 0 + ALT_BUS = SUBARU_MAIN_BUS + + DEG_TO_CAN = -100 + + INACTIVE_GAS = 1818 def setUp(self): self.packer = CANPackerPanda("subaru_global_2017_generated") self.safety = libpanda_py.libpanda - self.safety.set_safety_hooks(Panda.SAFETY_SUBARU, 0) + self.safety.set_safety_hooks(Panda.SAFETY_SUBARU, self.FLAGS) self.safety.init_tests() def _set_prev_torque(self, t): @@ -42,18 +69,17 @@ def _torque_driver_msg(self, torque): return self.packer.make_can_msg_panda("Steering_Torque", 0, values) def _speed_msg(self, speed): - # subaru safety doesn't use the scaled value, so undo the scaling - values = {s: speed * 0.057 for s in ["FR", "FL", "RR", "RL"]} + values = {s: speed for s in ["FR", "FL", "RR", "RL"]} return self.packer.make_can_msg_panda("Wheel_Speeds", self.ALT_BUS, values) + def _angle_meas_msg(self, angle): + values = {"Steering_Angle": angle} + return self.packer.make_can_msg_panda("Steering_Torque", 0, values) + def _user_brake_msg(self, brake): values = {"Brake": brake} return self.packer.make_can_msg_panda("Brake_Status", self.ALT_BUS, values) - def _torque_cmd_msg(self, torque, steer_req=1): - values = {"LKAS_Output": torque} - return self.packer.make_can_msg_panda("ES_LKAS", 0, values) - def _user_gas_msg(self, gas): values = {"Throttle_Pedal": gas} return self.packer.make_can_msg_panda("Throttle", 0, values) @@ -63,19 +89,41 @@ def _pcm_status_msg(self, enable): return self.packer.make_can_msg_panda("CruiseControl", self.ALT_BUS, values) -class TestSubaruGen2Safety(TestSubaruSafety): - TX_MSGS = [[0x122, 0], [0x221, 1], [0x321, 0], [0x322, 0], [0x323, 0]] - ALT_BUS = 1 +class TestSubaruStockLongitudinalSafetyBase(TestSubaruSafetyBase): + def _cancel_msg(self, cancel, cruise_throttle=0): + values = {"Cruise_Cancel": cancel, "Cruise_Throttle": cruise_throttle} + return self.packer.make_can_msg_panda("ES_Distance", self.ALT_BUS, values) + + def test_cancel_message(self): + # test that we can only send the cancel message (ES_Distance) with inactive throttle (1818) and Cruise_Cancel=1 + for cancel in [True, False]: + self._generic_limit_safety_check(partial(self._cancel_msg, cancel), self.INACTIVE_GAS, self.INACTIVE_GAS, 0, 2**12, 1, self.INACTIVE_GAS, cancel) + + +class TestSubaruTorqueSafetyBase(TestSubaruSafetyBase, common.DriverTorqueSteeringSafetyTest): + MAX_RATE_UP = 50 + MAX_RATE_DOWN = 70 + MAX_TORQUE = 2047 + + def _torque_cmd_msg(self, torque, steer_req=1): + values = {"LKAS_Output": torque} + return self.packer.make_can_msg_panda("ES_LKAS", 0, values) + + +class TestSubaruGen1TorqueStockLongitudinalSafety(TestSubaruStockLongitudinalSafetyBase, TestSubaruTorqueSafetyBase): + FLAGS = 0 + TX_MSGS = lkas_tx_msgs(SUBARU_MAIN_BUS) + + +class TestSubaruGen2TorqueStockLongitudinalSafety(TestSubaruStockLongitudinalSafetyBase, TestSubaruTorqueSafetyBase): + ALT_BUS = SUBARU_ALT_BUS MAX_RATE_UP = 40 MAX_RATE_DOWN = 40 MAX_TORQUE = 1000 - def setUp(self): - self.packer = CANPackerPanda("subaru_global_2017_generated") - self.safety = libpanda_py.libpanda - self.safety.set_safety_hooks(Panda.SAFETY_SUBARU, Panda.FLAG_SUBARU_GEN2) - self.safety.init_tests() + FLAGS = Panda.FLAG_SUBARU_GEN2 + TX_MSGS = lkas_tx_msgs(SUBARU_ALT_BUS) if __name__ == "__main__": diff --git a/tests/safety/test_subaru_legacy.py b/tests/safety/test_subaru_preglobal.py similarity index 92% rename from tests/safety/test_subaru_legacy.py rename to tests/safety/test_subaru_preglobal.py index c00afcf011..40362e3728 100755 --- a/tests/safety/test_subaru_legacy.py +++ b/tests/safety/test_subaru_preglobal.py @@ -6,7 +6,7 @@ from panda.tests.safety.common import CANPackerPanda -class TestSubaruLegacySafety(common.PandaSafetyTest, common.DriverTorqueSteeringSafetyTest): +class TestSubaruPreglobalSafety(common.PandaSafetyTest, common.DriverTorqueSteeringSafetyTest): TX_MSGS = [[0x161, 0], [0x164, 0]] STANDSTILL_THRESHOLD = 0 # kph RELAY_MALFUNCTION_ADDR = 0x164 @@ -27,7 +27,7 @@ class TestSubaruLegacySafety(common.PandaSafetyTest, common.DriverTorqueSteering def setUp(self): self.packer = CANPackerPanda("subaru_outback_2015_generated") self.safety = libpanda_py.libpanda - self.safety.set_safety_hooks(Panda.SAFETY_SUBARU_LEGACY, 0) + self.safety.set_safety_hooks(Panda.SAFETY_SUBARU_PREGLOBAL, 0) self.safety.init_tests() def _set_prev_torque(self, t): diff --git a/tests/safety/test_tesla.py b/tests/safety/test_tesla.py index fffbfcf4d2..83e58352bb 100755 --- a/tests/safety/test_tesla.py +++ b/tests/safety/test_tesla.py @@ -71,9 +71,9 @@ class TestTeslaSteeringSafety(TestTeslaSafety, common.AngleSteeringSafetyTest): # 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 + ANGLE_RATE_BP = [0., 5., 15.] + ANGLE_RATE_UP = [10., 1.6, .3] # windup limit + ANGLE_RATE_DOWN = [10., 7.0, .8] # unwind limit def setUp(self): self.packer = CANPackerPanda("tesla_can") diff --git a/tests/safety_replay/replay_drive.py b/tests/safety_replay/replay_drive.py index c9cc135d38..77ee61d5d8 100755 --- a/tests/safety_replay/replay_drive.py +++ b/tests/safety_replay/replay_drive.py @@ -19,14 +19,20 @@ 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 + safety_tick_rx_invalid = False blocked_addrs = Counter() invalid_addrs = set() - start_t = None - for msg in lr: - if start_t is None: - start_t = msg.logMonoTime - safety.set_timer(((msg.logMonoTime // 1000)) % 0xFFFFFFFF) + can_msgs = [m for m in lr if m.which() in ('can', 'sendcan')] + start_t = can_msgs[0].logMonoTime + end_t = can_msgs[-1].logMonoTime + for msg in can_msgs: + safety.set_timer((msg.logMonoTime // 1000) % 0xFFFFFFFF) + + # skip start and end of route, warm up/down period + if msg.logMonoTime - start_t > 1e9 and end_t - msg.logMonoTime > 1e9: + safety.safety_tick_current_rx_checks() + safety_tick_rx_invalid |= not safety.addr_checks_valid() or safety_tick_rx_invalid if msg.which() == 'sendcan': for canmsg in msg.sendcan: @@ -38,14 +44,12 @@ def replay_drive(lr, safety_mode, param, alternative_experience, segment=False): 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))) + print("blocked bus %d msg %d at %f" % (canmsg.src, canmsg.address, (msg.logMonoTime - start_t) / 1e9)) tx_controls += safety.get_controls_allowed() tx_tot += 1 elif msg.which() == 'can': - for canmsg in msg.can: - # ignore msgs we sent - if canmsg.src >= 128: - continue + # ignore msgs we sent + for canmsg in filter(lambda m: m.src < 128, msg.can): to_push = package_can_msg(canmsg) recv = safety.safety_rx_hook(to_push) if not recv: @@ -56,6 +60,7 @@ def replay_drive(lr, safety_mode, param, alternative_experience, segment=False): print("\nRX") print("total rx msgs:", rx_tot) print("invalid rx msgs:", rx_invalid) + print("safety tick rx invalid:", safety_tick_rx_invalid) print("invalid addrs:", invalid_addrs) print("\nTX") print("total openpilot msgs:", tx_tot) @@ -64,7 +69,7 @@ def replay_drive(lr, safety_mode, param, alternative_experience, segment=False): print("blocked with controls allowed:", tx_controls_blocked) print("blocked addrs:", blocked_addrs) - return tx_controls_blocked == 0 and rx_invalid == 0 + return tx_controls_blocked == 0 and rx_invalid == 0 and not safety_tick_rx_invalid if __name__ == "__main__": from tools.lib.route import Route, SegmentName @@ -82,7 +87,7 @@ def replay_drive(lr, safety_mode, param, alternative_experience, segment=False): r = Route(s.route_name.canonical_name) logs = r.log_paths()[s.segment_num:s.segment_num+1] if s.segment_num >= 0 else r.log_paths() - lr = MultiLogIterator(logs) + lr = MultiLogIterator(logs, sort_by_time=True) if None in (args.mode, args.param, args.alternative_experience): for msg in lr: diff --git a/tests/safety_replay/test_safety_replay.py b/tests/safety_replay/test_safety_replay.py deleted file mode 100755 index dc61eca4f6..0000000000 --- a/tests/safety_replay/test_safety_replay.py +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/env python3 -from collections import namedtuple -import os -import requests - -from panda import Panda -from panda.python import ALTERNATIVE_EXPERIENCE as ALT_EXP -from panda.tests.safety_replay.replay_drive import replay_drive -from tools.lib.logreader import LogReader # pylint: disable=import-error - -BASE_URL = "https://commadataci.blob.core.windows.net/openpilotci/" - -ReplayRoute = namedtuple("ReplayRoute", ("route", "safety_mode", "param", "alternative_experience"), defaults=(0, 0)) - -logs = [ - ReplayRoute("2425568437959f9d|2019-12-22--16-24-37.bz2", Panda.SAFETY_HONDA_NIDEC), # HONDA.CIVIC (fcw presents: 0x1FA blocked as expected) - ReplayRoute("38bfd238edecbcd7|2019-06-07--10-15-25.bz2", Panda.SAFETY_TOYOTA, 66), # TOYOTA.PRIUS - ReplayRoute("f89c604cf653e2bf|2018-09-29--13-46-50.bz2", Panda.SAFETY_GM), # GM.VOLT - 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 (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 - ReplayRoute("5b7c365c50084530_2020-04-15--16-13-24--3--rlog.bz2", Panda.SAFETY_HYUNDAI), # HYUNDAI.SONATA - ReplayRoute("610ebb9faaad6b43|2020-06-13--15-28-36.bz2", Panda.SAFETY_HYUNDAI_LEGACY), # HYUNDAI.IONIQ_EV_LTD - ReplayRoute("5ab784f361e19b78_2020-06-08--16-30-41.bz2", Panda.SAFETY_SUBARU_LEGACY), # SUBARU.OUTBACK_PREGLOBAL - ReplayRoute("bb50caf5f0945ab1|2021-06-19--17-20-18.bz2", Panda.SAFETY_TESLA), # TESLA.AP2_MODELS - ReplayRoute("bd6a637565e91581_2021-10-29--22-18-31--1--rlog.bz2", Panda.SAFETY_MAZDA), # MAZDA.CX9_2021 - ReplayRoute("1a5d045d2c531a6d_2022-06-07--22-03-00--1--rlog.bz2", Panda.SAFETY_HONDA_BOSCH, # HONDA.CIVIC_2022 - Panda.FLAG_HONDA_RADARLESS, ALT_EXP.DISABLE_DISENGAGE_ON_GAS), -] - - -if __name__ == "__main__": - - # get all the routes - for route, _, _, _ in logs: - if not os.path.isfile(route): - with open(route, "wb") as f: - r = requests.get(BASE_URL + route, timeout=10) - r.raise_for_status() - f.write(r.content) - - failed = [] - for route, mode, param, alt_exp in logs: - lr = LogReader(route) - - print("\nreplaying %s with safety mode %d, param %s, alternative experience %s" % (route, mode, param, alt_exp)) - if not replay_drive(lr, mode, param, alt_exp): - failed.append(route) - - for f in failed: - print(f"\n**** failed on {f} ****") - assert len(failed) == 0, "\nfailed on %d logs" % len(failed) diff --git a/tests/setup_device_ci.sh b/tests/setup_device_ci.sh index 6bf0e509fb..e876b94375 100755 --- a/tests/setup_device_ci.sh +++ b/tests/setup_device_ci.sh @@ -50,15 +50,11 @@ if [ ! -d "$SOURCE_DIR" ]; then git clone https://github.com/commaai/panda.git $SOURCE_DIR fi -# setup panda_jungle -cd $SOURCE_DIR/../ -if [ ! -d panda_jungle/ ]; then - git clone https://github.com/commaai/panda_jungle.git -fi -cd panda_jungle -git fetch --all -git checkout -f master -git reset --hard origin/master +# setup device/SOM state +SOM_ST_IO=49 +echo $SOM_ST_IO > /sys/class/gpio/export || true +echo out > /sys/class/gpio/gpio${SOM_ST_IO}/direction +echo 1 > /sys/class/gpio/gpio${SOM_ST_IO}/value # checkout panda commit cd $SOURCE_DIR diff --git a/tests/spam_can.py b/tests/spam_can.py index 8c80fb45cb..3154cc0463 100755 --- a/tests/spam_can.py +++ b/tests/spam_can.py @@ -1,10 +1,8 @@ #!/usr/bin/env python3 import os -import sys import random -sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")) -from panda import Panda # noqa: E402 +from panda import Panda def get_test_string(): return b"test" + os.urandom(10) diff --git a/tests/standalone_test.py b/tests/standalone_test.py index 6580b06d14..7ec5559697 100755 --- a/tests/standalone_test.py +++ b/tests/standalone_test.py @@ -1,11 +1,8 @@ #!/usr/bin/env python3 -import os -import sys import struct import time -sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")) -from panda import Panda # noqa: E402 +from panda import Panda if __name__ == "__main__": p = Panda() @@ -13,7 +10,7 @@ print(p.health()) t1 = time.time() - for i in range(100): + for _ in range(100): p.get_serial() t2 = time.time() print("100 requests took %.2f ms" % ((t2 - t1) * 1000)) diff --git a/tests/tucan_loopback.py b/tests/tucan_loopback.py index ff037f3e35..1bf1254a16 100755 --- a/tests/tucan_loopback.py +++ b/tests/tucan_loopback.py @@ -1,7 +1,6 @@ #!/usr/bin/env python3 import os -import sys import time import random import argparse @@ -9,8 +8,7 @@ from hexdump import hexdump from itertools import permutations -sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")) -from panda import Panda # noqa: E402 +from panda import Panda def get_test_string(): return b"test" + os.urandom(10) @@ -20,13 +18,12 @@ def run_test(sleep_duration): print(pandas) if len(pandas) < 2: - print("Two pandas are needed for test") - assert False + raise Exception("Two pandas are needed for test") run_test_w_pandas(pandas, sleep_duration) def run_test_w_pandas(pandas, sleep_duration): - h = list([Panda(x) for x in pandas]) + h = [Panda(x) for x in pandas] print("H", h) for hh in h: @@ -114,5 +111,5 @@ def run_test_w_pandas(pandas, sleep_duration): while True: run_test(sleep_duration=args.sleep) else: - for i in range(args.n): + for _ in range(args.n): run_test(sleep_duration=args.sleep) diff --git a/tests/usbprotocol/test_comms.py b/tests/usbprotocol/test_comms.py index 311b7a9e62..9b020f2347 100755 --- a/tests/usbprotocol/test_comms.py +++ b/tests/usbprotocol/test_comms.py @@ -156,4 +156,4 @@ def test_can_receive_usb(self): if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main()