├── .checkpatch.conf ├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ ├── 001_bug_report.md │ └── 002_feature_request.md ├── dependabot.yml └── workflows │ ├── build.yml │ └── docs.yml ├── .gitignore ├── 99-cannectivity.rules ├── CMakeLists.txt ├── Kconfig ├── LICENSE ├── README.md ├── app ├── CMakeLists.txt ├── Kconfig ├── Kconfig.sysbuild ├── VERSION ├── app.overlay ├── boards │ ├── arduino_giga_r1_stm32h747xx_m7.overlay │ ├── canbardo_same70n20b.conf │ ├── canbardo_same70n20b.overlay │ ├── candlelight_stm32f072xb.conf │ ├── candlelight_stm32f072xb.overlay │ ├── candlelightfd_stm32g0b1xx.overlay │ ├── candlelightfd_stm32g0b1xx_dual.conf │ ├── candlelightfd_stm32g0b1xx_dual.overlay │ ├── lpcxpresso55s16_lpc55s16.overlay │ ├── mks_canable_v20_stm32g431xx.conf │ ├── mks_canable_v20_stm32g431xx.overlay │ ├── native_sim.conf │ ├── native_sim.overlay │ ├── native_sim_native_64.conf │ ├── native_sim_native_64.overlay │ ├── nucleo_h723zg_stm32h723xx.overlay │ ├── ucan_stm32f072xb.conf │ ├── ucan_stm32f072xb.overlay │ └── usb2canfdv1_stm32g0b1xx.overlay ├── dts │ └── bindings │ │ └── cannectivity.yaml ├── prj.conf ├── prj_release.conf ├── prj_usbd_next.conf ├── prj_usbd_next_release.conf ├── sample.yaml ├── src │ ├── cannectivity.h │ ├── dfu.c │ ├── led.c │ ├── main.c │ ├── termination.c │ ├── timestamp.c │ └── usb.c ├── sysbuild-dfu.conf ├── sysbuild.cmake └── sysbuild │ └── mcuboot │ ├── app.overlay │ ├── boards │ ├── canbardo_same70n20b.conf │ └── frdm_k64f_mk64f12.conf │ └── prj.conf ├── cmake └── cannectivity.cmake ├── doc ├── LICENSE ├── Makefile ├── building.rst ├── conf.py ├── index.rst ├── module.rst ├── porting.rst ├── requirements.txt └── static │ └── CANnectivity.png ├── dts └── bindings │ └── usb │ └── gs_usb.yaml ├── include └── cannectivity │ └── usb │ └── class │ └── gs_usb.h ├── scripts ├── requirements-run-test.txt └── requirements.txt ├── subsys ├── CMakeLists.txt ├── Kconfig └── usb │ ├── CMakeLists.txt │ ├── Kconfig │ ├── device │ ├── CMakeLists.txt │ ├── Kconfig │ └── class │ │ ├── CMakeLists.txt │ │ ├── Kconfig │ │ ├── Kconfig.gs_usb │ │ └── gs_usb.c │ └── device_next │ ├── CMakeLists.txt │ ├── Kconfig │ └── class │ ├── Kconfig │ ├── Kconfig.gs_usb │ └── gs_usb.c ├── tests └── subsys │ └── usb │ └── gs_usb │ ├── cxx │ ├── CMakeLists.txt │ ├── app.overlay │ ├── prj.conf │ ├── src │ │ └── main.cpp │ └── testcase.yaml │ └── host │ ├── CMakeLists.txt │ ├── Kconfig │ ├── README.rst │ ├── app.overlay │ ├── prj.conf │ ├── prj_usbd_next.conf │ ├── pytest │ ├── conftest.py │ ├── gs_usb.py │ └── test_gs_usb.py │ ├── src │ ├── main.c │ ├── shell.c │ ├── test.h │ └── usb.c │ └── testcase.yaml ├── west.yml └── zephyr └── module.yml /.checkpatch.conf: -------------------------------------------------------------------------------- 1 | --emacs 2 | --summary-file 3 | --show-types 4 | --max-line-length=100 5 | --min-conf-desc-length=1 6 | 7 | --ignore BRACES 8 | --ignore PRINTK_WITHOUT_KERN_LEVEL 9 | --ignore SPLIT_STRING 10 | --ignore VOLATILE 11 | --ignore CONFIG_EXPERIMENTAL 12 | --ignore PREFER_KERNEL_TYPES 13 | --ignore PREFER_SECTION 14 | --ignore AVOID_EXTERNS 15 | --ignore NETWORKING_BLOCK_COMMENT_STYLE 16 | --ignore DATE_TIME 17 | --ignore MINMAX 18 | --ignore CONST_STRUCT 19 | --ignore FILE_PATH_CHANGES 20 | --ignore SPDX_LICENSE_TAG 21 | --ignore C99_COMMENT_TOLERANCE 22 | --ignore REPEATED_WORD 23 | --ignore UNDOCUMENTED_DT_STRING 24 | --ignore DT_SPLIT_BINDING_PATCH 25 | --ignore DT_SCHEMA_BINDING_PATCH 26 | --ignore TRAILING_SEMICOLON 27 | --ignore COMPLEX_MACRO 28 | --ignore MULTISTATEMENT_MACRO_USE_DO_WHILE 29 | --ignore ENOSYS 30 | --ignore IS_ENABLED_CONFIG 31 | --ignore EXPORT_SYMBOL 32 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig: https://editorconfig.org/ 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # All (Defaults) 7 | [*] 8 | charset = utf-8 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | max_line_length = 100 13 | 14 | # Assembly 15 | [*.S] 16 | indent_style = tab 17 | indent_size = 8 18 | 19 | # C 20 | [*.{c,h}] 21 | indent_style = tab 22 | indent_size = 8 23 | 24 | # Linker Script 25 | [*.ld] 26 | indent_style = tab 27 | indent_size = 8 28 | 29 | # Python 30 | [*.py] 31 | indent_style = space 32 | indent_size = 4 33 | 34 | # reStructuredText 35 | [*.rst] 36 | indent_style = space 37 | indent_size = 3 38 | 39 | # YAML 40 | [*.{yml,yaml}] 41 | indent_style = space 42 | indent_size = 2 43 | 44 | # Shell Script 45 | [*.sh] 46 | indent_style = space 47 | indent_size = 4 48 | 49 | # CMake 50 | [{CMakeLists.txt,*.cmake}] 51 | indent_style = space 52 | indent_size = 2 53 | 54 | # Device tree 55 | [*.{dts,dtsi,overlay}] 56 | indent_style = tab 57 | indent_size = 8 58 | 59 | # Git commit messages 60 | [COMMIT_EDITMSG] 61 | max_line_length = 75 62 | 63 | # Kconfig 64 | [Kconfig*] 65 | indent_style = tab 66 | indent_size = 8 67 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/001_bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help improving CANnectivity 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | --- 8 | 9 | ## Describe the bug 10 | 19 | 20 | ## To Reproduce 21 | 27 | 28 | ## Expected behavior 29 | 30 | 31 | ## Logs and console output 32 | 37 | 38 | ## Environment 39 | 40 | - Target board: 41 | - OS: 42 | - Toolchain: 43 | - CANnectivity Git commit SHA: 44 | - Zephyr RTOS Git commit SHA: 45 | 46 | ## Additional context 47 | 51 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/002_feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest a new feature for CANnectivity 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | --- 8 | 9 | ## Describe the requested feature 10 | 11 | 12 | ## Describe alternatives considered 13 | 14 | 15 | ## Additional context 16 | 19 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | updates: 4 | # Check for updates to GitHub actions 5 | - package-ecosystem: github-actions 6 | directory: / 7 | schedule: 8 | interval: daily 9 | # Check for updates to Python packages 10 | - package-ecosystem: pip 11 | directory: / 12 | schedule: 13 | interval: daily 14 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | pull_request: 6 | schedule: 7 | - cron: "0 2 * * *" 8 | 9 | jobs: 10 | build: 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | os: [ubuntu-24.04, macos-13, macos-14, windows-2022] 15 | runs-on: ${{ matrix.os }} 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v4 19 | with: 20 | path: cannectivity 21 | 22 | - name: Set up Python 23 | uses: actions/setup-python@v5 24 | with: 25 | python-version: 3.11 26 | 27 | - name: Setup Zephyr project 28 | uses: zephyrproject-rtos/action-zephyr-setup@v1 29 | with: 30 | app-path: cannectivity 31 | toolchains: arm-zephyr-eabi 32 | 33 | - name: Build firmware 34 | working-directory: cannectivity 35 | shell: bash 36 | run: | 37 | if [ "${{ runner.os }}" = "Windows" ]; then 38 | EXTRA_TWISTER_FLAGS="--short-build-path -O/tmp/twister-out" 39 | fi 40 | west twister -T app -v --inline-logs --integration $EXTRA_TWISTER_FLAGS 41 | 42 | - name: Run firmware tests 43 | working-directory: cannectivity 44 | if: startsWith(runner.os, 'Linux') 45 | shell: bash 46 | run: | 47 | # Limit to one concurrent instance as the USBIP port is reused between instances 48 | west twister -T app -v --inline-logs --platform native_sim/native/64 -j1 49 | 50 | - name: Run tests 51 | working-directory: cannectivity 52 | shell: bash 53 | run: | 54 | if [ "${{ runner.os }}" = "Windows" ]; then 55 | EXTRA_TWISTER_FLAGS="--short-build-path -O/tmp/twister-out" 56 | fi 57 | west twister -T tests -v --inline-logs --integration $EXTRA_TWISTER_FLAGS 58 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Documentation 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | - v*-branch 8 | push: 9 | branches: 10 | - main 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-24.04 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v4 18 | 19 | - name: Install dependencies 20 | run: | 21 | pip install -r doc/requirements.txt 22 | 23 | - name: Build 24 | run: | 25 | cd doc 26 | env SPHINXOPTS="-W" make html 27 | 28 | - name: Setup pages 29 | if: github.event_name != 'pull_request' 30 | uses: actions/configure-pages@v5 31 | 32 | - name: Upload pages artifact 33 | if: github.event_name != 'pull_request' 34 | uses: actions/upload-pages-artifact@v3 35 | with: 36 | path: doc/build/html 37 | 38 | - name: Upload artifact 39 | if: github.event_name == 'pull_request' 40 | uses: actions/upload-artifact@v4 41 | with: 42 | path: doc/build/html 43 | 44 | deploy: 45 | runs-on: ubuntu-24.04 46 | needs: build 47 | if: github.event_name != 'pull_request' 48 | permissions: 49 | pages: write 50 | id-token: write 51 | steps: 52 | - name: Deploy to GitHub Pages 53 | uses: actions/deploy-pages@v4 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # editors 2 | .vscode 3 | *.swp 4 | *~ 5 | 6 | # python 7 | .venv 8 | 9 | # build 10 | /twister-out* 11 | **/build* 12 | 13 | __pycache__/ 14 | -------------------------------------------------------------------------------- /99-cannectivity.rules: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024 Henrik Brix Andersen 2 | # SPDX-License-Identifier: Apache-2.0 3 | # 4 | # NOTE: These udev rules are only strictly needed for Linux kernels prior to v6.15-rc1 and for 5 | # running the CANnectivity test suites with user access. 6 | # 7 | # 1. Copy this file to /etc/udev/rules.d/99-cannectivity.rules 8 | # 9 | # 2. Activate the new rules by running the following commands: 10 | # sudo udevadm control --reload-rules 11 | # sudo udevadm trigger 12 | 13 | ACTION!="add", SUBSYSTEM!="usb_device", GOTO="cannectivity_rules_end" 14 | 15 | # Replace VID and PID with CONFIG_CANNECTIVITY_USB_VID and CONFIG_CANNECTIVITY_USB_PID values 16 | ATTR{idVendor}=="1209", ATTR{idProduct}=="ca01", RUN+="/sbin/modprobe -b gs_usb" MODE="660", GROUP="plugdev", TAG+="uaccess" 17 | SUBSYSTEM=="drivers", ENV{DEVPATH}=="/bus/usb/drivers/gs_usb", ATTR{new_id}="1209 ca01" 18 | 19 | # Replace VID and PID with SB_CONFIG_CANNECTIVITY_USB_DFU_VID and SB_CONFIG_CANNECTIVITY_USB_DFU_PID values 20 | ATTR{idVendor}=="1209", ATTR{idProduct}=="ca02", MODE="660", GROUP="plugdev", TAG+="uaccess" 21 | 22 | # Used for pytest suites 23 | ATTR{idVendor}=="1209", ATTR{idProduct}=="0001", MODE="660", GROUP="plugdev", TAG+="uaccess" 24 | 25 | LABEL="cannectivity_rules_end" 26 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024 Henrik Brix Andersen 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | zephyr_include_directories(include) 5 | 6 | add_subdirectory(subsys) 7 | 8 | include(cmake/cannectivity.cmake) 9 | -------------------------------------------------------------------------------- /Kconfig: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024 Henrik Brix Andersen 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | rsource "subsys/Kconfig" 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | # CANnectivity 9 | 10 | ## Overview 11 | 12 | CANnectivity is an open source firmware for Universal Serial Bus (USB) to Controller Area Network 13 | (CAN) adapters. 14 | 15 | The firmware implements the Geschwister Schneider USB/CAN device protocol (often referred to as 16 | "gs_usb"). This protocol is supported by the Linux kernel SocketCAN [gs_usb 17 | driver](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/net/can/usb/gs_usb.c), 18 | by [python-can](https://python-can.readthedocs.io/en/stable/interfaces/gs_usb.html), and by many 19 | other software packages. 20 | 21 | The firmware, which is based on the [Zephyr RTOS](https://www.zephyrproject.org), allows turning 22 | your favorite microcontroller development board into a full-fledged USB to CAN adapter. 23 | 24 | CANnectivity is licensed under the [Apache-2.0 license](LICENSE). The CANnectivity documentation is 25 | licensed under the [CC BY 4.0 license](doc/LICENSE). 26 | 27 | ## Firmware Features 28 | 29 | The CANnectivity firmware supports the following features, some of which depend on hardware support: 30 | 31 | - CAN classic 32 | - CAN FD (flexible data rate) 33 | - Fast-speed and Hi-speed USB 34 | - Multiple, independent CAN channels 35 | - LEDs: 36 | - CAN channel state LEDs 37 | - CAN channel activity LEDs 38 | - Visual CAN channel identification 39 | - GPIO-controlled CAN bus termination resistors 40 | - Hardware timestamping of received and transmitted CAN frames 41 | - CAN bus state reporting 42 | - CAN bus error reporting 43 | - Automatic gs_usb driver loading under Linux using custom [udev rules](99-cannectivity.rules) 44 | - Automatic WinUSB driver installation under Microsoft Windows 8.1 and newer 45 | - USB Device Firmware Upgrade (DFU) mode 46 | 47 | ## Hardware Requirements 48 | 49 | Since the CANnectivity firmware is based on the Zephyr RTOS, it requires Zephyr support for the 50 | board it is to run on. The board configuration must support both an USB device driver and at least 51 | one CAN controller. 52 | 53 | Check the list of [supported boards](https://docs.zephyrproject.org/latest/boards/index.html) in the 54 | Zephyr documentation to see if your board is already supported. If not, have a look at the 55 | instructions in the [board porting 56 | guide](https://docs.zephyrproject.org/latest/hardware/porting/board_porting.html). 57 | 58 | ## Board Configuration 59 | 60 | By default, CANnectivity relies on the 61 | [devicetree](https://docs.zephyrproject.org/latest/build/dts/index.html) `zephyr,canbus` chosen node 62 | property for specifying the CAN controller to use. If a devicetree alias for `led0` is present, it 63 | is used as status LED. This means that virtually any Zephyr board configuration supporting USB 64 | device, a CAN controller, and an optional user LED will work without any further configuration. 65 | 66 | Advanced board configuration (e.g. multiple CAN controllers, multiple LEDs, hardware timestamp 67 | counter) is also supported via devicetree overlays. Check the description for the 68 | [cannectivity](app/dts/bindings/cannectivity.yaml) devicetree binding and [example devicetree 69 | overlays](app/boards). 70 | 71 | ## Building and Running 72 | 73 | Building the CANnectivity firmware requires a proper Zephyr development environment. Follow the 74 | official [Zephyr Getting Started 75 | Guide](https://docs.zephyrproject.org/latest/getting_started/index.html) to establish one. 76 | 77 | Once a proper Zephyr development environment is established, inialize the workspace folder (here 78 | `my-workspace`). This will clone the CANnectivity firmware repository and download the necessary 79 | Zephyr modules: 80 | 81 | ```shell 82 | west init -m https://github.com/CANnectivity/cannectivity --mr main my-workspace 83 | cd my-workspace 84 | west update 85 | ``` 86 | 87 | Next, build the CANnectivity firmware in its default configuration for your board (here 88 | `lpcxpresso55s16`) using `west`: 89 | 90 | ```shell 91 | west build -b lpcxpresso55s16/lpc55s16 cannectivity/app/ 92 | ``` 93 | 94 | To use the `release` configuration, which has reduced log levels, set `FILE_SUFFIX=release`: 95 | 96 | ```shell 97 | west build -b lpcxpresso55s16/lpc55s16 cannectivity/app/ -- -DFILE_SUFFIX=release 98 | ``` 99 | 100 | After building, the firmware can be flashed to the board by running the `west flash` command. 101 | 102 | > **Note:** Build configurations for using the experimental `device_next` USB device stack in 103 | > Zephyr are also provided. These can be selected by setting either `FILE_SUFFIX=usbd_next` or 104 | > `FILE_SUFFIX=usbd_next_release`. 105 | 106 | ## USB Device Firmware Upgrade (DFU) Mode 107 | 108 | CANnectivity supports USB Device Firmware Upgrade 109 | ([DFU](https://docs.zephyrproject.org/latest/services/device_mgmt/dfu.html)) via the 110 | [MCUboot](https://www.trustedfirmware.org/projects/mcuboot/) bootloader. This is intended for use 111 | with boards without an on-board programmer. 112 | 113 | To build CANnectivity with MCUboot integration for USB DFU use 114 | [sysbuild](https://docs.zephyrproject.org/latest/build/sysbuild/index.html) with the 115 | `sysbuild-dfu.conf` configuration file when building for your board (here `frdm_k64f`): 116 | 117 | ```shell 118 | west build -b frdm_k64f/mk64f12 --sysbuild ../custom/cannectivity/app/ -- -DSB_CONF_FILE=sysbuild-dfu.conf 119 | ``` 120 | 121 | After building, MCUboot and the CANnectivity firmware can be flashed to the board by running the 122 | `west flash` command. 123 | 124 | Subsequent CANnectivity firmware updates can be applied via USB DFU. In order to do so, the board 125 | must first be booted into USB DFU mode. If your board has a dedicated DFU button (identified by the 126 | `mcuboot-button0` devicetree alias) press and hold it for 5 seconds or press and hold the button 127 | while powering up the board. If your board has a DFU LED (identified by the `mcuboot-led0` 128 | devicetree alias), the LED will flash while the DFU button is being held and change to constant on 129 | once DFU mode is activated. Refer to your board documentation for further details. 130 | 131 | Once in DFU mode, the CANnectivity firmware can be updated using 132 | [dfu-util](https://dfu-util.sourceforge.net/): 133 | 134 | ```shell 135 | dfu-util -a 1 -D build/app/zephyr/zephyr.signed.bin.dfu 136 | ``` 137 | 138 | ## CANnectivity as a Zephyr Module 139 | 140 | The CANnectivity firmware repository is a [Zephyr 141 | module](https://docs.zephyrproject.org/latest/develop/modules.html) which allows for reuse of its 142 | components (i.e. the "gs_usb" protocol implementation) outside of the CANnectivity firmware 143 | application. 144 | 145 | To pull in CANnectivity as a Zephyr module, either add it as a West project in the `west.yaml` file 146 | or pull it in by adding a submanifest (e.g. `zephyr/submanifests/cannectivity.yaml`) file with the 147 | following content and run `west update`: 148 | 149 | ```yaml 150 | manifest: 151 | projects: 152 | - name: cannectivity 153 | url: https://github.com/CANnectivity/cannectivity.git 154 | revision: main 155 | path: custom/cannectivity # adjust the path as needed 156 | ``` 157 | -------------------------------------------------------------------------------- /app/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024 Henrik Brix Andersen 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | cmake_minimum_required(VERSION 3.13.1) 5 | find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) 6 | 7 | project(cannectivity LANGUAGES C) 8 | 9 | if(CONFIG_CANNECTIVITY_USB_VID EQUAL 0x1209) 10 | if(CONFIG_CANNECTIVITY_USB_PID LESS_EQUAL 0x0010) 11 | message(WARNING 12 | "CONFIG_CANNECTIVITY_USB_PID is set to a generic pid.codes Test PID (${CONFIG_CANNECTIVITY_USB_PID}). 13 | This PID is not unique and should not be used outside test environments." 14 | ) 15 | endif() 16 | endif() 17 | 18 | target_sources(app PRIVATE 19 | src/main.c 20 | src/usb.c 21 | ) 22 | 23 | target_sources_ifdef(CONFIG_CANNECTIVITY_LED app PRIVATE 24 | src/led.c 25 | ) 26 | 27 | target_sources_ifdef(CONFIG_CANNECTIVITY_TIMESTAMP_COUNTER app PRIVATE 28 | src/timestamp.c 29 | ) 30 | 31 | target_sources_ifdef(CONFIG_CANNECTIVITY_TERMINATION_GPIO app PRIVATE 32 | src/termination.c 33 | ) 34 | 35 | target_sources_ifdef(CONFIG_BOOTLOADER_MCUBOOT app PRIVATE 36 | src/dfu.c 37 | ) 38 | -------------------------------------------------------------------------------- /app/Kconfig: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024 Henrik Brix Andersen 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | menu "CANnectivity" 5 | 6 | module = CANNECTIVITY 7 | module-str = CANnectivity 8 | source "subsys/logging/Kconfig.template.log_config" 9 | 10 | config CANNECTIVITY_BOOT_BANNER 11 | bool "CANnectivity boot banner" 12 | default y if BOOT_BANNER 13 | select PRINTK 14 | help 15 | Print the CANnectivity firmware version during boot up. 16 | 17 | config CANNECTIVITY_USB_MANUFACTURER 18 | string "USB device manufacturer string" 19 | default "CANnectivity" 20 | help 21 | CANnectivity USB device manufacturer string. 22 | 23 | config CANNECTIVITY_USB_PRODUCT 24 | string "USB device product string" 25 | default "CANnectivity USB to CAN adapter" 26 | help 27 | CANnectivity USB product string. 28 | 29 | config CANNECTIVITY_USB_VID 30 | hex "USB device Vendor ID (VID)" 31 | default 0x1209 32 | help 33 | CANnectivity USB device Vendor ID (VID). 34 | 35 | config CANNECTIVITY_USB_PID 36 | hex "USB device Product ID (PID)" 37 | default 0xca01 38 | help 39 | CANnectivity USB device Product ID (PID). 40 | 41 | config CANNECTIVITY_USB_SELF_POWERED 42 | bool "USB device is self-powered" 43 | default y 44 | help 45 | CANnectivity USB device is self-powered. 46 | 47 | config CANNECTIVITY_USB_MAX_POWER 48 | int "USB device maximum power" 49 | default 125 50 | range 0 250 51 | help 52 | CANnectivity USB maximum current draw in milliampere (mA) divided by 2. 53 | A value of 125 results in a maximum current draw value of 250 mA. 54 | 55 | if USB_DEVICE_STACK 56 | 57 | configdefault USB_DEVICE_MANUFACTURER 58 | default CANNECTIVITY_USB_MANUFACTURER 59 | 60 | configdefault USB_DEVICE_PRODUCT 61 | default CANNECTIVITY_USB_PRODUCT 62 | 63 | configdefault USB_DEVICE_VID 64 | default CANNECTIVITY_USB_VID 65 | 66 | configdefault USB_DEVICE_PID 67 | default CANNECTIVITY_USB_PID 68 | 69 | configdefault USB_SELF_POWERED 70 | default CANNECTIVITY_USB_SELF_POWERED 71 | 72 | configdefault USB_MAX_POWER 73 | default CANNECTIVITY_USB_MAX_POWER 74 | 75 | endif # USB_DEVICE_STACK 76 | 77 | config CANNECTIVITY_LED 78 | bool "LED support" 79 | default y 80 | depends on $(dt_compat_any_has_prop,cannectivity-channel,state-led) || \ 81 | $(dt_compat_any_has_prop,cannectivity-channel,activity-leds) || \ 82 | ($(dt_alias_enabled,led0) && !$(dt_compat_enabled,cannectivity-channel)) 83 | select LED 84 | select SMF 85 | select SMF_ANCESTOR_SUPPORT 86 | select SMF_INITIAL_TRANSITION 87 | select POLL 88 | select USB_DEVICE_GS_USB_IDENTIFICATION if USB_DEVICE_GS_USB 89 | select USBD_GS_USB_IDENTIFICATION if USBD_GS_USB 90 | help 91 | Enable support for CAN channel status/activity LEDs. 92 | 93 | if CANNECTIVITY_LED 94 | 95 | config CANNECTIVITY_LED_EVENT_MSGQ_SIZE 96 | int "LED event message queue size" 97 | default 10 98 | help 99 | Number of per-channel LED events that can be queued for the finite-state machine. 100 | 101 | config CANNECTIVITY_LED_THREAD_STACK_SIZE 102 | int "LED thread stack size" 103 | default 1024 104 | help 105 | Size of the stack used for the LED finite-state machine thread. 106 | 107 | config CANNECTIVITY_LED_THREAD_PRIO 108 | int "LED thread priority" 109 | default 10 110 | help 111 | Priority level for the LED finite-state machine thread. 112 | 113 | endif # CANNECTIVITY_LED 114 | 115 | config CANNECTIVITY_TIMESTAMP_COUNTER 116 | bool "Hardware timestamp support" 117 | default y 118 | depends on $(dt_nodelabel_has_prop,cannectivity,timestamp-counter) 119 | select USB_DEVICE_GS_USB_TIMESTAMP if USB_DEVICE_GS_USB 120 | select USBD_GS_USB_TIMESTAMP if USBD_GS_USB 121 | select COUNTER 122 | help 123 | Enable support for hardware timestamps. This requires a 32-bit counter instance running @ 124 | 1MHz. 125 | 126 | config CANNECTIVITY_TERMINATION_GPIO 127 | bool "CAN bus termination resistor support" 128 | default y 129 | depends on $(dt_compat_any_has_prop,cannectivity-channel,termination-gpios) 130 | select USB_DEVICE_GS_USB_TERMINATION if USB_DEVICE_GS_USB 131 | select USBD_GS_USB_TERMINATION if USBD_GS_USB 132 | select GPIO 133 | help 134 | Enable support for GPIO-controlled CAN bus termination resistors. 135 | 136 | config CANNECTIVITY_TERMINATION_DEFAULT_ON 137 | bool "Enable CAN bus termination resistors on boot-up" 138 | default y 139 | depends on CANNECTIVITY_TERMINATION_GPIO 140 | help 141 | Enable CAN bus termination resistors on boot-up. 142 | 143 | if BOOTLOADER_MCUBOOT 144 | 145 | config CANNECTIVITY_DFU_BUTTON 146 | bool "DFU button support" 147 | default y 148 | depends on $(dt_alias_enabled,mcuboot-button0) 149 | select GPIO 150 | select REBOOT 151 | help 152 | Enable support for rebooting into Device Firmware Upgrade (DFU) mode by holding the DFU 153 | button (identified by the "mcuboot-button0" devicetree alias). 154 | 155 | config CANNECTIVITY_DFU_BUTTON_HOLD_TIME 156 | int "DFU button hold time in seconds" 157 | depends on CANNECTIVITY_DFU_BUTTON 158 | range 1 60 159 | default 4 160 | help 161 | Number of seconds the Device Firmware Upgrade (DFU) button must be held to reboot into DFU 162 | mode. 163 | 164 | config CANNECTIVITY_DFU_LED 165 | bool # hidden 166 | default y 167 | depends on $(dt_alias_enabled,mcuboot-led0) 168 | select LED 169 | help 170 | Enable support for controlling the Device Firmware Upgrade (DFU) LED (identified by the 171 | "mcuboot-led0" devicetree alias). 172 | 173 | configdefault FLASH 174 | default y 175 | 176 | configdefault STREAM_FLASH 177 | default y 178 | 179 | configdefault FLASH_MAP 180 | default y 181 | 182 | configdefault IMG_MANAGER 183 | default y 184 | 185 | endif # BOOTLOADER_MCUBOOT 186 | 187 | menuconfig CANNECTIVITY_GENERATE_USB_DFU_IMAGE 188 | bool "Generate USB DFU image" 189 | select BUILD_OUTPUT_BIN 190 | help 191 | Enabling this configuration allows automatic generation of an image with USB Device 192 | Firmware Upgrade (DFU) suffix. This depends on the dfu-suffix utility from the dfu-util 193 | software package. 194 | 195 | if CANNECTIVITY_GENERATE_USB_DFU_IMAGE 196 | 197 | config CANNECTIVITY_USB_DFU_VID 198 | hex "USB DFU image Vendor ID (VID)" 199 | default 0xffff 200 | help 201 | CANnectivity USB DFU image Vendor ID (VID). 202 | 203 | config CANNECTIVITY_USB_DFU_PID 204 | hex "USB DFU image Product ID (PID)" 205 | default 0xffff 206 | help 207 | CANnectivity USB DFU image Product ID (PID). 208 | 209 | config CANNECTIVITY_USB_DFU_DID 210 | hex "USB DFU image Device ID" 211 | default 0xffff 212 | help 213 | CANnectivity USB DFU image Device ID. 214 | 215 | config CANNECTIVITY_USB_DFU_SPEC_ID 216 | hex "USB DFU image Specification ID" 217 | default 0x0100 218 | help 219 | CANnectivity USB DFU image Specification ID. 220 | 221 | endif # CANNECTIVITY_GENERATE_USB_DFU_IMAGE 222 | 223 | endmenu 224 | 225 | source "Kconfig.zephyr" 226 | -------------------------------------------------------------------------------- /app/Kconfig.sysbuild: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024 Henrik Brix Andersen 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | menuconfig CANNECTIVITY_USB_DFU 5 | bool "CANnectivity USB DFU mode" 6 | help 7 | Enable support for CANnectivity USB Device Firmware Upgrade (DFU) mode using the MCUboot 8 | bootloader. 9 | 10 | if CANNECTIVITY_USB_DFU 11 | 12 | config CANNECTIVITY_USB_DFU_MANUFACTURER 13 | string "USB DFU mode manufacturer string" 14 | default "CANnectivity" 15 | help 16 | CANnectivity USB DFU mode manufacturer string. 17 | 18 | config CANNECTIVITY_USB_DFU_PRODUCT 19 | string "USB DFU mode product string" 20 | default "CANnectivity USB to CAN adapter in DFU mode" 21 | help 22 | CANnectivity USB DFU mode product string. 23 | 24 | config CANNECTIVITY_USB_DFU_VID 25 | hex "USB DFU mode Vendor ID (VID)" 26 | default 0x1209 27 | help 28 | CANnectivity USB DFU mode Vendor ID (VID). 29 | 30 | config CANNECTIVITY_USB_DFU_PID 31 | hex "USB DFU mode Product ID (PID)" 32 | default 0xca02 33 | help 34 | CANnectivity USB DFU mode Product ID (PID). 35 | 36 | config CANNECTIVITY_USB_DFU_MAX_POWER 37 | int "USB DFU mode maximum power" 38 | default 125 39 | range 0 250 40 | help 41 | CANnectivity USB DFU mode maximum current draw in milliampere (mA) divided by 2. 42 | A value of 125 results in a maximum current draw value of 250 mA. 43 | 44 | endif # CANNECTIVITY_USB_DFU 45 | 46 | source "share/sysbuild/Kconfig" 47 | -------------------------------------------------------------------------------- /app/VERSION: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024-2025 Henrik Brix Andersen 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | VERSION_MAJOR = 1 5 | VERSION_MINOR = 2 6 | PATCHLEVEL = 0 7 | VERSION_TWEAK = 0 8 | EXTRAVERSION = dev 9 | -------------------------------------------------------------------------------- /app/app.overlay: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022-2025 Henrik Brix Andersen 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | / { 8 | gs_usb0: gs_usb0 { 9 | compatible = "gs_usb"; 10 | label = "gs_usb"; 11 | }; 12 | }; 13 | -------------------------------------------------------------------------------- /app/boards/arduino_giga_r1_stm32h747xx_m7.overlay: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Felipe Neves 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #include "../app.overlay" 8 | 9 | / { 10 | cannectivity: cannectivity { 11 | compatible = "cannectivity"; 12 | timestamp-counter = <&counter0>; 13 | 14 | channel0 { 15 | compatible = "cannectivity-channel"; 16 | can-controller = <&fdcan2>; 17 | state-led = <&blue_led>; 18 | activity-leds = <&green_led>; 19 | }; 20 | }; 21 | }; 22 | 23 | &timers2 { 24 | st,prescaler = <239>; 25 | status = "okay"; 26 | counter0: counter { 27 | status = "okay"; 28 | }; 29 | }; 30 | -------------------------------------------------------------------------------- /app/boards/canbardo_same70n20b.conf: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024-2025 Henrik Brix Andersen 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | CONFIG_CANNECTIVITY_USB_SELF_POWERED=n 5 | CONFIG_USB_DEVICE_GS_USB_COMPATIBILITY_MODE=n 6 | CONFIG_USB_DEVICE_GS_USB_MAX_CHANNELS=2 7 | -------------------------------------------------------------------------------- /app/boards/canbardo_same70n20b.overlay: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024-2025 Henrik Brix Andersen 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #include 8 | #include "../app.overlay" 9 | 10 | / { 11 | cannectivity: cannectivity { 12 | compatible = "cannectivity"; 13 | 14 | channel0 { 15 | compatible = "cannectivity-channel"; 16 | can-controller = <&can0>; 17 | termination-gpios = <&piod 1 GPIO_ACTIVE_HIGH>; 18 | state-led = <&can_0_ledg>; 19 | activity-leds = <&can_0_ledy>; 20 | }; 21 | 22 | channel1 { 23 | compatible = "cannectivity-channel"; 24 | can-controller = <&can1>; 25 | termination-gpios = <&piod 13 GPIO_ACTIVE_HIGH>; 26 | state-led = <&can_1_ledg>; 27 | activity-leds = <&can_1_ledy>; 28 | }; 29 | }; 30 | }; 31 | 32 | &can0 { 33 | bosch,mram-cfg = <0x0 1 1 10 10 0 10 10>; 34 | }; 35 | 36 | &can1 { 37 | bosch,mram-cfg = <0x0 1 1 10 10 0 10 10>; 38 | }; 39 | -------------------------------------------------------------------------------- /app/boards/candlelight_stm32f072xb.conf: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024-2025 Henrik Brix Andersen 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | CONFIG_CANNECTIVITY_USB_SELF_POWERED=n 5 | CONFIG_CAN_FD_MODE=n 6 | -------------------------------------------------------------------------------- /app/boards/candlelight_stm32f072xb.overlay: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Henrik Brix Andersen 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #include 8 | #include "../app.overlay" 9 | 10 | / { 11 | cannectivity: cannectivity { 12 | compatible = "cannectivity"; 13 | timestamp-counter = <&counter2>; 14 | 15 | channel0 { 16 | compatible = "cannectivity-channel"; 17 | can-controller = <&can1>; 18 | activity-leds = <&led_rx &led_tx>; 19 | }; 20 | }; 21 | }; 22 | 23 | &timers2 { 24 | status = "okay"; 25 | st,prescaler = <47>; 26 | 27 | counter2: counter2 { 28 | compatible = "st,stm32-counter"; 29 | status = "okay"; 30 | }; 31 | }; 32 | -------------------------------------------------------------------------------- /app/boards/candlelightfd_stm32g0b1xx.overlay: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024-2025 Henrik Brix Andersen 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #include 8 | #include "../app.overlay" 9 | 10 | / { 11 | cannectivity: cannectivity { 12 | compatible = "cannectivity"; 13 | timestamp-counter = <&counter2>; 14 | 15 | channel0 { 16 | compatible = "cannectivity-channel"; 17 | can-controller = <&fdcan1>; 18 | activity-leds = <&led_rx &led_tx>; 19 | }; 20 | }; 21 | }; 22 | 23 | &timers2 { 24 | status = "okay"; 25 | st,prescaler = <59>; 26 | 27 | counter2: counter2 { 28 | compatible = "st,stm32-counter"; 29 | status = "okay"; 30 | }; 31 | }; 32 | -------------------------------------------------------------------------------- /app/boards/candlelightfd_stm32g0b1xx_dual.conf: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024-2025 Henrik Brix Andersen 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | CONFIG_CANNECTIVITY_USB_SELF_POWERED=n 5 | CONFIG_USB_DEVICE_GS_USB_MAX_CHANNELS=2 6 | -------------------------------------------------------------------------------- /app/boards/candlelightfd_stm32g0b1xx_dual.overlay: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024-2025 Henrik Brix Andersen 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #include 8 | #include "../app.overlay" 9 | 10 | / { 11 | cannectivity: cannectivity { 12 | compatible = "cannectivity"; 13 | timestamp-counter = <&counter2>; 14 | 15 | channel0 { 16 | compatible = "cannectivity-channel"; 17 | can-controller = <&fdcan1>; 18 | /* Use RX LED for channel 0 state + activity */ 19 | state-led = <&led_rx>; 20 | }; 21 | 22 | channel1 { 23 | compatible = "cannectivity-channel"; 24 | can-controller = <&fdcan2>; 25 | /* Use TX LED for channel 0 state + activity */ 26 | state-led = <&led_tx>; 27 | }; 28 | }; 29 | }; 30 | 31 | &timers2 { 32 | status = "okay"; 33 | st,prescaler = <59>; 34 | 35 | counter2: counter2 { 36 | compatible = "st,stm32-counter"; 37 | status = "okay"; 38 | }; 39 | }; 40 | -------------------------------------------------------------------------------- /app/boards/lpcxpresso55s16_lpc55s16.overlay: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Henrik Brix Andersen 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #include 8 | #include "../app.overlay" 9 | 10 | / { 11 | cannectivity: cannectivity { 12 | compatible = "cannectivity"; 13 | timestamp-counter = <&ctimer0>; 14 | 15 | channel0 { 16 | compatible = "cannectivity-channel"; 17 | can-controller = <&can0>; 18 | termination-gpios = <&gpio1 4 GPIO_ACTIVE_HIGH>; /* Red LED for testing */ 19 | state-led = <&blue_led>; 20 | activity-leds = <&green_led>; 21 | }; 22 | }; 23 | }; 24 | 25 | &can0 { 26 | bosch,mram-cfg = <0x0 1 1 64 64 0 10 10>; 27 | }; 28 | 29 | &ctimer0 { 30 | prescale = <95>; 31 | }; 32 | -------------------------------------------------------------------------------- /app/boards/mks_canable_v20_stm32g431xx.conf: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2025 Henrik Brix Andersen 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | CONFIG_CANNECTIVITY_USB_SELF_POWERED=n 5 | 6 | # The two lines below are needed to reduce flash usage 7 | CONFIG_LTO=y 8 | CONFIG_ISR_TABLES_LOCAL_DECLARATION=y 9 | -------------------------------------------------------------------------------- /app/boards/mks_canable_v20_stm32g431xx.overlay: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Alexander Kozhinov 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #include 8 | #include "../app.overlay" 9 | 10 | / { 11 | cannectivity: cannectivity { 12 | compatible = "cannectivity"; 13 | timestamp-counter = <&counters2>; 14 | 15 | channel0 { 16 | compatible = "cannectivity-channel"; 17 | can-controller = <&fdcan1>; 18 | state-led = <&blue_led>; 19 | activity-leds = <&green_led>; 20 | }; 21 | }; 22 | }; 23 | 24 | &timers2 { 25 | st,prescaler = <159>; 26 | counters2: counter { 27 | status = "okay"; 28 | }; 29 | }; 30 | -------------------------------------------------------------------------------- /app/boards/native_sim.conf: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024 Henrik Brix Andersen 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | CONFIG_USB_DEVICE_GS_USB_MAX_CHANNELS=3 5 | CONFIG_COUNTER_NATIVE_POSIX_FREQUENCY=1000000 6 | -------------------------------------------------------------------------------- /app/boards/native_sim.overlay: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Henrik Brix Andersen 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #include 8 | #include "../app.overlay" 9 | 10 | / { 11 | leds { 12 | compatible = "gpio-leds"; 13 | 14 | led_ch0_state: led_ch0_state { 15 | gpios = <&gpio0 0 GPIO_ACTIVE_HIGH>; 16 | label = "Channel 0 state LED"; 17 | }; 18 | 19 | led_ch0_activity: led_ch0_activity { 20 | gpios = <&gpio0 1 GPIO_ACTIVE_HIGH>; 21 | label = "Channel 0 activity LED"; 22 | }; 23 | 24 | led_ch1_state: led_ch1_state { 25 | gpios = <&gpio0 2 GPIO_ACTIVE_HIGH>; 26 | label = "Channel 1 state LED"; 27 | }; 28 | 29 | led_ch1_activity: led_ch1_activity { 30 | gpios = <&gpio0 3 GPIO_ACTIVE_HIGH>; 31 | label = "Channel 1 activity LED"; 32 | }; 33 | 34 | led_ch2_state: led_ch2_state { 35 | gpios = <&gpio0 4 GPIO_ACTIVE_HIGH>; 36 | label = "Channel 2 state LED"; 37 | }; 38 | 39 | led_ch2_activity: led_ch2_activity { 40 | gpios = <&gpio0 5 GPIO_ACTIVE_HIGH>; 41 | label = "Channel 2 activity LED"; 42 | }; 43 | }; 44 | 45 | cannectivity: cannectivity { 46 | compatible = "cannectivity"; 47 | timestamp-counter = <&counter0>; 48 | 49 | channel0 { 50 | compatible = "cannectivity-channel"; 51 | can-controller = <&can_loopback0>; 52 | termination-gpios = <&gpio0 0 GPIO_ACTIVE_HIGH>; 53 | state-led = <&led_ch0_state>; 54 | activity-leds = <&led_ch0_activity>; 55 | }; 56 | 57 | channel1 { 58 | compatible = "cannectivity-channel"; 59 | can-controller = <&can_loopback1>; 60 | termination-gpios = <&gpio0 3 GPIO_ACTIVE_HIGH>; 61 | state-led = <&led_ch1_state>; 62 | activity-leds = <&led_ch1_activity>; 63 | }; 64 | 65 | channel2 { 66 | compatible = "cannectivity-channel"; 67 | can-controller = <&can_loopback2>; 68 | termination-gpios = <&gpio0 6 GPIO_ACTIVE_HIGH>; 69 | state-led = <&led_ch2_state>; 70 | activity-leds = <&led_ch2_activity>; 71 | }; 72 | }; 73 | 74 | can_loopback0: can_loopback0 { 75 | status = "okay"; 76 | compatible = "zephyr,can-loopback"; 77 | }; 78 | 79 | can_loopback1: can_loopback1 { 80 | status = "okay"; 81 | compatible = "zephyr,can-loopback"; 82 | }; 83 | 84 | can_loopback2: can_loopback2 { 85 | status = "okay"; 86 | compatible = "zephyr,can-loopback"; 87 | }; 88 | 89 | }; 90 | -------------------------------------------------------------------------------- /app/boards/native_sim_native_64.conf: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024 Henrik Brix Andersen 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | CONFIG_USB_DEVICE_GS_USB_MAX_CHANNELS=3 5 | CONFIG_COUNTER_NATIVE_POSIX_FREQUENCY=1000000 6 | -------------------------------------------------------------------------------- /app/boards/native_sim_native_64.overlay: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Henrik Brix Andersen 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #include "native_sim.overlay" 8 | -------------------------------------------------------------------------------- /app/boards/nucleo_h723zg_stm32h723xx.overlay: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Henrik Brix Andersen 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #include 8 | #include "../app.overlay" 9 | 10 | / { 11 | cannectivity: cannectivity { 12 | compatible = "cannectivity"; 13 | timestamp-counter = <&counter2>; 14 | 15 | channel0 { 16 | compatible = "cannectivity-channel"; 17 | can-controller = <&fdcan1>; 18 | state-led = <&green_led>; 19 | activity-leds = <&yellow_led>; 20 | }; 21 | }; 22 | }; 23 | 24 | &fdcan1 { 25 | /* Allocate half of the M_CAN message RAM to FDCAN1 */ 26 | reg = <0x4000a000 0x400>, <0x4000ac00 0x1400>; 27 | reg-names = "m_can", "message_ram"; 28 | bosch,mram-cfg = <0x0 1 1 10 10 0 10 10>; 29 | }; 30 | 31 | &timers2 { 32 | st,prescaler = <274>; 33 | counter2: counter { 34 | status = "okay"; 35 | }; 36 | }; 37 | -------------------------------------------------------------------------------- /app/boards/ucan_stm32f072xb.conf: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024-2025 Henrik Brix Andersen 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | CONFIG_CANNECTIVITY_USB_SELF_POWERED=n 5 | CONFIG_CAN_FD_MODE=n 6 | -------------------------------------------------------------------------------- /app/boards/ucan_stm32f072xb.overlay: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Henrik Brix Andersen 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #include 8 | #include "../app.overlay" 9 | 10 | / { 11 | cannectivity: cannectivity { 12 | compatible = "cannectivity"; 13 | timestamp-counter = <&counter2>; 14 | 15 | channel0 { 16 | compatible = "cannectivity-channel"; 17 | can-controller = <&can1>; 18 | activity-leds = <&led_rx &led_tx>; 19 | }; 20 | }; 21 | }; 22 | 23 | &timers2 { 24 | status = "okay"; 25 | st,prescaler = <47>; 26 | 27 | counter2: counter2 { 28 | compatible = "st,stm32-counter"; 29 | status = "okay"; 30 | }; 31 | }; 32 | -------------------------------------------------------------------------------- /app/boards/usb2canfdv1_stm32g0b1xx.overlay: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Henrik Brix Andersen 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #include 8 | #include "../app.overlay" 9 | 10 | / { 11 | cannectivity: cannectivity { 12 | compatible = "cannectivity"; 13 | timestamp-counter = <&counter2>; 14 | 15 | channel0 { 16 | compatible = "cannectivity-channel"; 17 | can-controller = <&fdcan1>; 18 | state-led = <&led_ready>; 19 | activity-leds = <&led_rxd &led_txd>; 20 | }; 21 | }; 22 | }; 23 | 24 | &timers2 { 25 | status = "okay"; 26 | st,prescaler = <59>; 27 | 28 | counter2: counter2 { 29 | compatible = "st,stm32-counter"; 30 | status = "okay"; 31 | }; 32 | }; 33 | -------------------------------------------------------------------------------- /app/dts/bindings/cannectivity.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024 Henrik Brix Andersen 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | description: | 5 | CANnectivity hardware configuration 6 | 7 | Example: 8 | cannectivity: cannectivity { 9 | compatible = "cannectivity"; 10 | timestamp-counter = <&counter0>; 11 | 12 | channel { 13 | compatible = "cannectivity-channel"; 14 | can-controller = <&can0>; 15 | termination-gpios = <&gpio0 6 GPIO_ACTIVE_HIGH>; 16 | state-led = <&ch0_state_led>; 17 | activity-leds = <&ch0_activity_led>; 18 | }; 19 | 20 | channel { 21 | compatible = "cannectivity-channel"; 22 | can-controller = <&can1>; 23 | termination-gpios = <&gpio0 9 GPIO_ACTIVE_HIGH>; 24 | state-led = <&ch1_state_led>; 25 | activity-leds = <&ch1_activity_led>; 26 | }; 27 | }; 28 | 29 | compatible: "cannectivity" 30 | 31 | include: base.yaml 32 | 33 | properties: 34 | timestamp-counter: 35 | type: phandle 36 | description: | 37 | Phandle to 32-bit counter node running @ 1MHz. This counter is used for hardware timestamping 38 | of received and transmitted CAN frames. If not specified, hardware timestamping support will 39 | be disabled. 40 | 41 | child-binding: 42 | description: | 43 | CANnectivity CAN channel configuration. Channels are indexed in the order they appear in the 44 | final devicetree. 45 | 46 | compatible: "cannectivity-channel" 47 | 48 | properties: 49 | can-controller: 50 | type: phandle 51 | required: true 52 | description: | 53 | Phandle to the CAN controller node for this CAN channel. 54 | 55 | termination-gpios: 56 | type: phandle-array 57 | description: | 58 | GPIO to use to enable/disable the CAN channel termination resistor. This GPIO is driven 59 | active when the CAN termination is enabled and inactive when the CAN termination is 60 | disabled. 61 | 62 | state-led: 63 | type: phandle 64 | description: | 65 | Phandle for the CAN channel state LED. 66 | 67 | activity-leds: 68 | type: phandles 69 | description: | 70 | Phandle(s) for the CAN channel activity LED(s). 71 | 72 | If two LED phandles are specified, the LED phandle at index 0 will be used for indicating RX 73 | activity, and the LED phandle at index 1 will be used for indicating TX activity. 74 | -------------------------------------------------------------------------------- /app/prj.conf: -------------------------------------------------------------------------------- 1 | CONFIG_LOG=y 2 | CONFIG_USB_DRIVER_LOG_LEVEL_ERR=y 3 | CONFIG_USB_DEVICE_LOG_LEVEL_ERR=y 4 | CONFIG_USB_DEVICE_GS_USB_LOG_LEVEL_DBG=y 5 | CONFIG_CANNECTIVITY_LOG_LEVEL_DBG=y 6 | 7 | CONFIG_USB_DEVICE_STACK=y 8 | CONFIG_USB_COMPOSITE_DEVICE=y 9 | CONFIG_USB_DEVICE_BOS=y 10 | CONFIG_USB_DEVICE_INITIALIZE_AT_BOOT=n 11 | 12 | CONFIG_CAN=y 13 | CONFIG_CAN_FD_MODE=y 14 | -------------------------------------------------------------------------------- /app/prj_release.conf: -------------------------------------------------------------------------------- 1 | CONFIG_LOG=y 2 | CONFIG_USB_DRIVER_LOG_LEVEL_ERR=y 3 | CONFIG_USB_DEVICE_LOG_LEVEL_ERR=y 4 | 5 | CONFIG_USB_DEVICE_STACK=y 6 | CONFIG_USB_COMPOSITE_DEVICE=y 7 | CONFIG_USB_DEVICE_BOS=y 8 | CONFIG_USB_DEVICE_INITIALIZE_AT_BOOT=n 9 | 10 | CONFIG_CAN=y 11 | CONFIG_CAN_FD_MODE=y 12 | -------------------------------------------------------------------------------- /app/prj_usbd_next.conf: -------------------------------------------------------------------------------- 1 | CONFIG_WARN_EXPERIMENTAL=y 2 | 3 | CONFIG_LOG=y 4 | CONFIG_UDC_DRIVER_LOG_LEVEL_WRN=y 5 | CONFIG_USBD_LOG_LEVEL_WRN=y 6 | CONFIG_USBD_GS_USB_LOG_LEVEL_DBG=y 7 | CONFIG_CANNECTIVITY_LOG_LEVEL_DBG=y 8 | 9 | CONFIG_USB_DEVICE_STACK_NEXT=y 10 | 11 | CONFIG_CAN=y 12 | CONFIG_CAN_FD_MODE=y 13 | -------------------------------------------------------------------------------- /app/prj_usbd_next_release.conf: -------------------------------------------------------------------------------- 1 | CONFIG_WARN_EXPERIMENTAL=y 2 | 3 | CONFIG_LOG=y 4 | CONFIG_UDC_DRIVER_LOG_LEVEL_WRN=y 5 | CONFIG_USBD_LOG_LEVEL_WRN=y 6 | 7 | CONFIG_USB_DEVICE_STACK_NEXT=y 8 | 9 | CONFIG_CAN=y 10 | CONFIG_CAN_FD_MODE=y 11 | -------------------------------------------------------------------------------- /app/sample.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024 Henrik Brix Andersen 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | sample: 5 | description: CANnectivity USB-to-CAN adapter firmware 6 | name: cannectivity 7 | common: 8 | tags: 9 | - usb 10 | - can 11 | platform_exclude: 12 | - native_sim 13 | harness: console 14 | harness_config: 15 | type: one_line 16 | regex: 17 | - "CANnectivity firmware initialized with .*" 18 | tests: 19 | app.cannectivity: 20 | depends_on: 21 | - usb_device 22 | - can 23 | integration_platforms: 24 | - frdm_k64f 25 | - lpcxpresso55s16 26 | - nucleo_h723zg 27 | - usb2canfdv1 28 | - candlelight 29 | - ucan 30 | - mks_canable_v20 31 | - canbardo 32 | app.cannectivity.sof: 33 | depends_on: 34 | - usb_device 35 | - can 36 | integration_platforms: 37 | - nucleo_h723zg 38 | extra_configs: 39 | - CONFIG_USB_DEVICE_GS_USB_TIMESTAMP_SOF=y 40 | app.cannectivity.release: 41 | depends_on: 42 | - usb_device 43 | - can 44 | extra_args: 45 | - FILE_SUFFIX=release 46 | integration_platforms: 47 | - frdm_k64f 48 | - lpcxpresso55s16 49 | - nucleo_h723zg 50 | - canbardo 51 | app.cannectivity.usbd_next: 52 | depends_on: 53 | - usbd 54 | - can 55 | extra_args: 56 | - FILE_SUFFIX=usbd_next 57 | integration_platforms: 58 | - frdm_k64f 59 | - lpcxpresso55s16 60 | - usb2canfdv1 61 | app.cannectivity.usbd_next.sof: 62 | depends_on: 63 | - usbd 64 | - can 65 | extra_args: 66 | - FILE_SUFFIX=usbd_next 67 | integration_platforms: 68 | - lpcxpresso55s16 69 | extra_configs: 70 | - CONFIG_USBD_GS_USB_TIMESTAMP_SOF=y 71 | app.cannectivity.usbd_next.release: 72 | depends_on: 73 | - usbd 74 | - can 75 | extra_args: 76 | - FILE_SUFFIX=usbd_next_release 77 | integration_platforms: 78 | - frdm_k64f 79 | - lpcxpresso55s16 80 | app.cannectivity.dfu: 81 | sysbuild: true 82 | depends_on: 83 | - usb_device 84 | - can 85 | platform_allow: 86 | - frdm_k64f 87 | - canbardo 88 | extra_args: SB_CONF_FILE=sysbuild-dfu.conf 89 | -------------------------------------------------------------------------------- /app/src/cannectivity.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Henrik Brix Andersen 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #ifndef CANNECTIVITY_APP_CANNECTIVITY_H_ 8 | #define CANNECTIVITY_APP_CANNECTIVITY_H_ 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | /** 15 | * @brief The CANnectivity devicetree hardware configuration node identifier 16 | */ 17 | #define CANNECTIVITY_DT_NODE_ID DT_NODELABEL(cannectivity) 18 | 19 | /** 20 | * @brief True if one or more CANnectivity channels are present in the devicetree, false otherwise 21 | */ 22 | #define CANNECTIVITY_DT_HAS_CHANNEL DT_HAS_COMPAT_STATUS_OKAY(cannectivity_channel) 23 | 24 | /** 25 | * @brief Invokes @p fn for each CANnectivity devicetree channel node with a separator 26 | * 27 | * The macro @p fn must take one parameter, which will be the node identifier of a CANnectivity 28 | * channel node. 29 | * 30 | * @param fn The macro to invoke. 31 | * @param sep Separator (e.g. comma or semicolon). Must be in parentheses; 32 | * this is required to enable providing a comma as separator. 33 | */ 34 | #define CANNECTIVITY_DT_FOREACH_CHANNEL_SEP(fn, sep) \ 35 | DT_FOREACH_CHILD_STATUS_OKAY_SEP(CANNECTIVITY_DT_NODE_ID, fn, sep) 36 | 37 | /** 38 | * @brief CANnectivity CAN channel LED event callback 39 | * 40 | * @param dev Pointer to the device structure for the driver instance. 41 | * @param ch CAN channel number. 42 | * @param event Channel event. 43 | * @param user_data User data provided when registering the callback. 44 | * @return 0 on success, negative error number otherwise. 45 | */ 46 | int cannectivity_led_event(const struct device *dev, uint16_t ch, enum gs_usb_event event, 47 | void *user_data); 48 | 49 | /** 50 | * @brief CANnectivity LED initialization function 51 | * 52 | * @return 0 on success, negative error number otherwise. 53 | */ 54 | int cannectivity_led_init(void); 55 | 56 | /** 57 | * @brief CANnectivity CAN channel set CAN bus termination callback 58 | * 59 | * @param dev Pointer to the device structure for the driver instance. 60 | * @param ch CAN channel number. 61 | * @param terminate True if the channel termination is active, false otherwise. 62 | * @param user_data User data provided when registering the callback. 63 | * @return 0 on success, negative error number otherwise. 64 | */ 65 | int cannectivity_set_termination(const struct device *dev, uint16_t ch, bool terminate, 66 | void *user_data); 67 | 68 | /** 69 | * @brief CANnectivity CAN channel get CAN bus termination callback 70 | * 71 | * @param dev Pointer to the device structure for the driver instance. 72 | * @param ch CAN channel number. 73 | * @param[out] terminated True if the channel termination is active, false otherwise. 74 | * @param user_data User data provided when registering the callback. 75 | * @return 0 on success, negative error number otherwise. 76 | */ 77 | int cannectivity_get_termination(const struct device *dev, uint16_t ch, bool *terminated, 78 | void *user_data); 79 | 80 | /** 81 | * @brief CANnectivity CAN bus termination initialization function 82 | * 83 | * @return 0 on success, negative error number otherwise. 84 | */ 85 | int cannectivity_termination_init(void); 86 | 87 | /** 88 | * @brief CANnectivity get hardware timestamp callback 89 | * 90 | * @param dev Pointer to the device structure for the driver instance. 91 | * @param[out] timestamp Current timestamp value. 92 | * @param user_data User data provided when registering the callback. 93 | * @return 0 on success, negative error number otherwise. 94 | */ 95 | int cannectivity_timestamp_get(const struct device *dev, uint32_t *timestamp, void *user_data); 96 | 97 | /** 98 | * @brief CANnectivity hardware timestamp initialization function 99 | * 100 | * @return 0 on success, negative error number otherwise. 101 | */ 102 | int cannectivity_timestamp_init(void); 103 | 104 | /** 105 | * @brief CANnectivity USB device initialization function 106 | * 107 | * @return 0 on success, negative error number otherwise. 108 | */ 109 | int cannectivity_usb_init(void); 110 | 111 | /** 112 | * @brief CANnectivity USB DFU initialization function 113 | * 114 | * @return 0 on success, negative error number otherwise. 115 | */ 116 | int cannectivity_dfu_init(void); 117 | 118 | #endif /* CANNECTIVITY_APP_CANNECTIVITY_H_ */ 119 | -------------------------------------------------------------------------------- /app/src/dfu.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Henrik Brix Andersen 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "cannectivity.h" 16 | 17 | LOG_MODULE_REGISTER(dfu, CONFIG_CANNECTIVITY_LOG_LEVEL); 18 | 19 | /* DFU button poll timing */ 20 | #define DFU_BUTTON_POLL_HZ 5 21 | #define DFU_BUTTON_POLL_INTERVAL_MS (MSEC_PER_SEC / DFU_BUTTON_POLL_HZ) 22 | #define DFU_BUTTON_POLL_TOTAL (CONFIG_CANNECTIVITY_DFU_BUTTON_HOLD_TIME * DFU_BUTTON_POLL_HZ) 23 | 24 | /* DFU button and LED devicetree nodes */ 25 | #define DFU_LED_NODE DT_ALIAS(mcuboot_led0) 26 | #define DFU_BUTTON_NODE DT_ALIAS(mcuboot_button0) 27 | 28 | #ifdef CONFIG_CANNECTIVITY_DFU_LED 29 | struct led_dt_spec dfu_led = LED_DT_SPEC_GET(DFU_LED_NODE); 30 | #endif /* CONFIG_CANNECTIVITY_DFU_LED */ 31 | 32 | #ifdef CONFIG_CANNECTIVITY_DFU_BUTTON 33 | struct gpio_dt_spec dfu_button = GPIO_DT_SPEC_GET(DFU_BUTTON_NODE, gpios); 34 | static struct gpio_callback dfu_button_cb; 35 | static struct k_work_delayable dfu_button_work; 36 | static struct k_sem dfu_button_sem; 37 | 38 | static void dfu_button_poll(struct k_work *work) 39 | { 40 | struct k_work_delayable *dwork = k_work_delayable_from_work(work); 41 | int err; 42 | 43 | err = gpio_pin_get_dt(&dfu_button); 44 | if (err < 0) { 45 | LOG_ERR("failed to get DFU button state (err %d)", err); 46 | goto done; 47 | } 48 | 49 | if (err > 0) { 50 | #ifdef CONFIG_CANNECTIVITY_DFU_LED 51 | if (k_sem_count_get(&dfu_button_sem) % 2U == 0U) { 52 | err = led_on_dt(&dfu_led); 53 | } else { 54 | err = led_off_dt(&dfu_led); 55 | } 56 | if (err != 0) { 57 | LOG_ERR("failed to toggle DFU LED (err %d)", err); 58 | goto done; 59 | } 60 | #endif /* CONFIG_CANNECTIVITY_DFU_LED */ 61 | 62 | k_sem_give(&dfu_button_sem); 63 | if (k_sem_count_get(&dfu_button_sem) >= DFU_BUTTON_POLL_TOTAL) { 64 | LOG_INF("rebooting"); 65 | sys_reboot(SYS_REBOOT_COLD); 66 | } 67 | 68 | k_work_reschedule(dwork, K_MSEC(DFU_BUTTON_POLL_INTERVAL_MS)); 69 | return; 70 | } 71 | 72 | done: 73 | #ifdef CONFIG_CANNECTIVITY_DFU_LED 74 | err = led_off_dt(&dfu_led); 75 | if (err != 0) { 76 | LOG_ERR("failed to turn off DFU LED (err %d)", err); 77 | return; 78 | } 79 | #endif /* CONFIG_CANNECTIVITY_DFU_LED */ 80 | } 81 | 82 | static void dfu_button_interrupt(const struct device *port, struct gpio_callback *cb, 83 | gpio_port_pins_t pins) 84 | { 85 | ARG_UNUSED(port); 86 | ARG_UNUSED(cb); 87 | ARG_UNUSED(pins); 88 | 89 | k_sem_reset(&dfu_button_sem); 90 | k_work_reschedule(&dfu_button_work, K_NO_WAIT); 91 | } 92 | 93 | static int dfu_button_init(void) 94 | { 95 | int err; 96 | 97 | err = k_sem_init(&dfu_button_sem, 0, K_SEM_MAX_LIMIT); 98 | if (err != 0) { 99 | LOG_ERR("failed to initialize DFU button semaphore (err %d)", err); 100 | return err; 101 | } 102 | 103 | if (!gpio_is_ready_dt(&dfu_button)) { 104 | LOG_ERR("DFU button device not ready"); 105 | return -ENODEV; 106 | } 107 | 108 | err = gpio_pin_configure_dt(&dfu_button, GPIO_INPUT); 109 | if (err != 0) { 110 | LOG_ERR("failed to configure DFU button (err %d)", err); 111 | return err; 112 | } 113 | 114 | err = gpio_pin_interrupt_configure_dt(&dfu_button, GPIO_INT_EDGE_TO_ACTIVE); 115 | if (err != 0) { 116 | LOG_ERR("failed to configure DFU button interrupt (err %d)", err); 117 | return err; 118 | } 119 | 120 | k_work_init_delayable(&dfu_button_work, dfu_button_poll); 121 | 122 | gpio_init_callback(&dfu_button_cb, dfu_button_interrupt, BIT(dfu_button.pin)); 123 | err = gpio_add_callback_dt(&dfu_button, &dfu_button_cb); 124 | if (err != 0) { 125 | LOG_ERR("failed to add DFU button callback (err %d)", err); 126 | return err; 127 | } 128 | 129 | return 0; 130 | } 131 | #endif /* CONFIG_CANNECTIVITY_DFU_BUTTON */ 132 | 133 | #ifdef CONFIG_CANNECTIVITY_DFU_LED 134 | static int dfu_led_init(void) 135 | { 136 | if (!led_is_ready_dt(&dfu_led)) { 137 | LOG_ERR("DFU LED device not ready"); 138 | return -ENODEV; 139 | } 140 | 141 | return 0; 142 | } 143 | #endif /* CONFIG_CANNECTIVITY_DFU_LED */ 144 | 145 | int cannectivity_dfu_init(void) 146 | { 147 | int err; 148 | 149 | /* 150 | * Confirm updated image if running under MCUboot booloader. This could be done on 151 | * successful USB enumeration instead, but that could cause unwanted image reverts on 152 | * e.g. self-powered development boards. 153 | */ 154 | if (!boot_is_img_confirmed()) { 155 | err = boot_write_img_confirmed(); 156 | if (err != 0) { 157 | LOG_ERR("failed to confirm image (err %d)", err); 158 | return err; 159 | } 160 | 161 | LOG_INF("image confirmed"); 162 | } 163 | 164 | #ifdef CONFIG_CANNECTIVITY_DFU_LED 165 | err = dfu_led_init(); 166 | if (err != 0) { 167 | return err; 168 | } 169 | #endif /* CONFIG_CANNECTIVITY_DFU_LED */ 170 | 171 | #ifdef CONFIG_CANNECTIVITY_DFU_BUTTON 172 | err = dfu_button_init(); 173 | if (err != 0) { 174 | return err; 175 | } 176 | #endif /* CONFIG_CANNECTIVITY_DFU_BUTTON */ 177 | 178 | return 0; 179 | } 180 | -------------------------------------------------------------------------------- /app/src/led.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Henrik Brix Andersen 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "cannectivity.h" 15 | 16 | LOG_MODULE_REGISTER(led, CONFIG_CANNECTIVITY_LOG_LEVEL); 17 | 18 | /* LED ticks */ 19 | #define LED_TICK_MS 50U 20 | #define LED_TICKS_ACTIVITY 2U 21 | #define LED_TICKS_IDENTIFY 20U 22 | 23 | /* LED finite-state machine states */ 24 | enum led_state { 25 | LED_STATE_NORMAL, 26 | LED_STATE_NORMAL_STARTED, 27 | LED_STATE_NORMAL_STOPPED, 28 | LED_STATE_IDENTIFY, 29 | }; 30 | 31 | /* LED finite-state machine events */ 32 | enum led_event { 33 | LED_EVENT_TICK, 34 | LED_EVENT_CHANNEL_IDENTIFY_ON, 35 | LED_EVENT_CHANNEL_IDENTIFY_OFF, 36 | LED_EVENT_CHANNEL_STARTED, 37 | LED_EVENT_CHANNEL_STOPPED, 38 | LED_EVENT_CHANNEL_ACTIVITY_RX, 39 | LED_EVENT_CHANNEL_ACTIVITY_TX, 40 | }; 41 | 42 | /* Activity types */ 43 | enum led_activity { 44 | LED_ACTIVITY_RX = 0U, 45 | LED_ACTIVITY_TX, 46 | LED_ACTIVITY_COUNT, 47 | }; 48 | 49 | /* LED finite-state machine event data type */ 50 | typedef uint32_t led_event_t; 51 | 52 | /* LED finite-state machine per-channel context */ 53 | struct led_ctx { 54 | struct smf_ctx ctx; 55 | char eventq_buf[sizeof(led_event_t) * CONFIG_CANNECTIVITY_LED_EVENT_MSGQ_SIZE]; 56 | struct k_msgq eventq; 57 | led_event_t event; 58 | uint16_t ch; 59 | bool started; 60 | struct led_dt_spec state_led; 61 | int identify_ticks; 62 | k_timepoint_t activity[LED_ACTIVITY_COUNT]; 63 | int ticks[LED_ACTIVITY_COUNT]; 64 | struct led_dt_spec activity_led[LED_ACTIVITY_COUNT]; 65 | }; 66 | 67 | /* Devicetree accessor macros */ 68 | #define CHANNEL_LED_DT_SPEC_GET(node_id) \ 69 | { \ 70 | .state_led = LED_DT_SPEC_GET_OR(DT_PHANDLE(node_id, state_led), {0}), \ 71 | .activity_led[0] = LED_DT_SPEC_GET_OR(DT_PHANDLE_BY_IDX(node_id, activity_leds, 0),\ 72 | {0}), \ 73 | .activity_led[1] = LED_DT_SPEC_GET_OR(DT_PHANDLE_BY_IDX(node_id, activity_leds, 1),\ 74 | {0}), \ 75 | } 76 | 77 | #define CHANNEL_LED0_DT_SPEC_GET() \ 78 | { \ 79 | .state_led = LED_DT_SPEC_GET(DT_ALIAS(led0)), \ 80 | } 81 | 82 | /* Array of LED finite-state machine channel contexts */ 83 | struct led_ctx led_channel_ctx[] = { 84 | #if CANNECTIVITY_DT_HAS_CHANNEL 85 | CANNECTIVITY_DT_FOREACH_CHANNEL_SEP(CHANNEL_LED_DT_SPEC_GET, (,)) 86 | #else /* CANNECTIVITY_DT_HAS_CHANNEL */ 87 | CHANNEL_LED0_DT_SPEC_GET() 88 | #endif /* !CANNECTIVITY_DT_HAS_CHANNEL */ 89 | }; 90 | 91 | /* Helper macros */ 92 | #define LED_CTX_HAS_STATE_LED(_led_ctx) \ 93 | (_led_ctx->state_led.dev != NULL) 94 | #define LED_CTX_HAS_ACTIVITY_LED(_led_ctx) \ 95 | (_led_ctx->activity_led[LED_ACTIVITY_RX].dev != NULL) 96 | #define LED_CTX_HAS_DUAL_ACTIVITY_LEDS(_led_ctx) \ 97 | (_led_ctx->activity_led[LED_ACTIVITY_TX].dev != NULL) 98 | 99 | /* Forward declarations */ 100 | static const struct smf_state led_states[]; 101 | static void led_tick(struct k_timer *timer); 102 | 103 | /* Variables */ 104 | static K_TIMER_DEFINE(led_tick_timer, led_tick, NULL); 105 | static struct k_thread led_thread_data; 106 | static K_THREAD_STACK_DEFINE(led_thread_stack, CONFIG_CANNECTIVITY_LED_THREAD_STACK_SIZE); 107 | struct k_poll_event led_poll_events[ARRAY_SIZE(led_channel_ctx)]; 108 | 109 | BUILD_ASSERT(ARRAY_SIZE(led_channel_ctx) == ARRAY_SIZE(led_poll_events)); 110 | 111 | static int led_event_enqueue(uint16_t ch, led_event_t event) 112 | { 113 | struct led_ctx *lctx; 114 | int err; 115 | 116 | if (ch >= ARRAY_SIZE(led_channel_ctx)) { 117 | LOG_ERR("event %u for non-existing channel %u", event, ch); 118 | return -EINVAL; 119 | } 120 | 121 | lctx = &led_channel_ctx[ch]; 122 | err = k_msgq_put(&lctx->eventq, &event, K_NO_WAIT); 123 | if (err != 0) { 124 | LOG_ERR("failed to enqueue event %u for channel %u (err %d)", event, ch, err); 125 | } 126 | 127 | return 0; 128 | } 129 | 130 | static void led_indicate_state(struct led_ctx *lctx, bool state) 131 | { 132 | int err; 133 | 134 | if (LED_CTX_HAS_STATE_LED(lctx)) { 135 | if (state) { 136 | err = led_on_dt(&lctx->state_led); 137 | } else { 138 | err = led_off_dt(&lctx->state_led); 139 | } 140 | if (err != 0) { 141 | LOG_ERR("failed to turn %s channel %u state LED (err %d)", 142 | state ? "on" : "off", lctx->ch, err); 143 | } 144 | } 145 | } 146 | 147 | static void led_indicate_activity(struct led_ctx *lctx, enum led_activity type, bool activity) 148 | { 149 | struct led_dt_spec *led = NULL; 150 | int value = activity; 151 | int err; 152 | 153 | switch (type) { 154 | case LED_ACTIVITY_RX: 155 | if (LED_CTX_HAS_ACTIVITY_LED(lctx)) { 156 | led = &lctx->activity_led[LED_ACTIVITY_RX]; 157 | } 158 | break; 159 | 160 | case LED_ACTIVITY_TX: 161 | if (LED_CTX_HAS_DUAL_ACTIVITY_LEDS(lctx)) { 162 | led = &lctx->activity_led[LED_ACTIVITY_TX]; 163 | } else if (LED_CTX_HAS_ACTIVITY_LED(lctx)) { 164 | led = &lctx->activity_led[LED_ACTIVITY_RX]; 165 | } 166 | 167 | break; 168 | 169 | default: 170 | __ASSERT_NO_MSG(false); 171 | break; 172 | } 173 | 174 | if (led == NULL && lctx->started && LED_CTX_HAS_STATE_LED(lctx)) { 175 | led = &lctx->state_led; 176 | value = !value; 177 | } 178 | 179 | if (led != NULL) { 180 | if (value) { 181 | err = led_on_dt(led); 182 | } else { 183 | err = led_off_dt(led); 184 | } 185 | if (err != 0) { 186 | LOG_ERR("failed to turn %s channel %u activity LED (err %d)", 187 | value ? "on" : "off", lctx->ch, err); 188 | } 189 | } 190 | } 191 | 192 | static void led_state_normal_run(void *obj) 193 | { 194 | struct led_ctx *lctx = obj; 195 | 196 | switch (lctx->event) { 197 | case LED_EVENT_CHANNEL_IDENTIFY_ON: 198 | smf_set_state(SMF_CTX(lctx), &led_states[LED_STATE_IDENTIFY]); 199 | break; 200 | default: 201 | /* Event ignored */ 202 | } 203 | } 204 | 205 | static void led_state_normal_stopped_entry(void *obj) 206 | { 207 | struct led_ctx *lctx = obj; 208 | 209 | if (lctx->started) { 210 | smf_set_state(SMF_CTX(obj), &led_states[LED_STATE_NORMAL_STARTED]); 211 | return; 212 | } 213 | 214 | led_indicate_state(lctx, false); 215 | led_indicate_activity(lctx, LED_ACTIVITY_RX, false); 216 | led_indicate_activity(lctx, LED_ACTIVITY_TX, false); 217 | } 218 | 219 | static void led_state_normal_stopped_run(void *obj) 220 | { 221 | struct led_ctx *lctx = obj; 222 | 223 | switch (lctx->event) { 224 | case LED_EVENT_TICK: 225 | smf_set_handled(SMF_CTX(lctx)); 226 | break; 227 | case LED_EVENT_CHANNEL_STARTED: 228 | lctx->started = true; 229 | smf_set_state(SMF_CTX(lctx), &led_states[LED_STATE_NORMAL_STARTED]); 230 | break; 231 | default: 232 | /* Event ignored */ 233 | } 234 | } 235 | 236 | static void led_state_normal_started_entry(void *obj) 237 | { 238 | struct led_ctx *lctx = obj; 239 | 240 | led_indicate_state(lctx, true); 241 | led_indicate_activity(lctx, LED_ACTIVITY_RX, false); 242 | led_indicate_activity(lctx, LED_ACTIVITY_TX, false); 243 | } 244 | 245 | static void led_state_normal_started_run(void *obj) 246 | { 247 | struct led_ctx *lctx = obj; 248 | int i; 249 | 250 | switch (lctx->event) { 251 | case LED_EVENT_TICK: 252 | for (i = 0; i < ARRAY_SIZE(lctx->ticks); i++) { 253 | if (lctx->ticks[i] != 0U) { 254 | lctx->ticks[i]--; 255 | if (lctx->ticks[i] == (LED_TICKS_ACTIVITY / 2U)) { 256 | led_indicate_activity(lctx, i, true); 257 | } else if (lctx->ticks[i] == 0U) { 258 | led_indicate_activity(lctx, i, false); 259 | } 260 | } 261 | } 262 | 263 | smf_set_handled(SMF_CTX(lctx)); 264 | break; 265 | case LED_EVENT_CHANNEL_STOPPED: 266 | lctx->started = false; 267 | smf_set_state(SMF_CTX(lctx), &led_states[LED_STATE_NORMAL_STOPPED]); 268 | break; 269 | case LED_EVENT_CHANNEL_ACTIVITY_TX: 270 | lctx->ticks[LED_ACTIVITY_TX] = LED_TICKS_ACTIVITY; 271 | smf_set_handled(SMF_CTX(lctx)); 272 | break; 273 | case LED_EVENT_CHANNEL_ACTIVITY_RX: 274 | lctx->ticks[LED_ACTIVITY_RX] = LED_TICKS_ACTIVITY; 275 | smf_set_handled(SMF_CTX(lctx)); 276 | break; 277 | default: 278 | /* Event ignored */ 279 | } 280 | } 281 | 282 | static void led_state_identify_entry(void *obj) 283 | { 284 | struct led_ctx *lctx = obj; 285 | 286 | lctx->identify_ticks = LED_TICKS_IDENTIFY; 287 | 288 | led_indicate_state(lctx, true); 289 | led_indicate_activity(lctx, LED_ACTIVITY_RX, true); 290 | led_indicate_activity(lctx, LED_ACTIVITY_TX, true); 291 | } 292 | 293 | static void led_state_identify_run(void *obj) 294 | { 295 | struct led_ctx *lctx = obj; 296 | struct led_dt_spec *leds[3]; 297 | int err; 298 | int i; 299 | 300 | switch (lctx->event) { 301 | case LED_EVENT_TICK: 302 | leds[0] = &lctx->state_led; 303 | leds[1] = &lctx->activity_led[LED_ACTIVITY_RX]; 304 | leds[2] = &lctx->activity_led[LED_ACTIVITY_TX]; 305 | 306 | lctx->identify_ticks--; 307 | 308 | if (lctx->identify_ticks == LED_TICKS_IDENTIFY / 2U) { 309 | for (i = 0; i < ARRAY_SIZE(leds); i++) { 310 | if (leds[i]->dev != NULL) { 311 | err = led_off_dt(leds[i]); 312 | if (err != 0) { 313 | LOG_ERR("failed to turn channel %u LED %d off" 314 | "(err %d)", lctx->ch, i, err); 315 | } 316 | } 317 | } 318 | } else if (lctx->identify_ticks == 0U) { 319 | for (i = 0; i < ARRAY_SIZE(leds); i++) { 320 | if (leds[i]->dev != NULL) { 321 | err = led_on_dt(leds[i]); 322 | if (err != 0) { 323 | LOG_ERR("failed to turn channel %u LED %d on " 324 | "(err %d)", lctx->ch, i, err); 325 | } 326 | } 327 | } 328 | 329 | lctx->identify_ticks = LED_TICKS_IDENTIFY; 330 | } 331 | break; 332 | case LED_EVENT_CHANNEL_STARTED: 333 | lctx->started = true; 334 | break; 335 | case LED_EVENT_CHANNEL_STOPPED: 336 | lctx->started = false; 337 | break; 338 | case LED_EVENT_CHANNEL_IDENTIFY_OFF: 339 | smf_set_state(SMF_CTX(lctx), &led_states[LED_STATE_NORMAL]); 340 | break; 341 | default: 342 | /* Event ignored */ 343 | } 344 | } 345 | 346 | /* clang-format off */ 347 | static const struct smf_state led_states[] = { 348 | [LED_STATE_NORMAL] = SMF_CREATE_STATE(NULL, 349 | led_state_normal_run, 350 | NULL, 351 | NULL, 352 | &led_states[LED_STATE_NORMAL_STOPPED]), 353 | [LED_STATE_NORMAL_STOPPED] = SMF_CREATE_STATE(led_state_normal_stopped_entry, 354 | led_state_normal_stopped_run, 355 | NULL, 356 | &led_states[LED_STATE_NORMAL], 357 | NULL), 358 | [LED_STATE_NORMAL_STARTED] = SMF_CREATE_STATE(led_state_normal_started_entry, 359 | led_state_normal_started_run, 360 | NULL, 361 | &led_states[LED_STATE_NORMAL], 362 | NULL), 363 | [LED_STATE_IDENTIFY] = SMF_CREATE_STATE(led_state_identify_entry, 364 | led_state_identify_run, 365 | NULL, 366 | NULL, 367 | NULL), 368 | }; 369 | /* clang-format on */ 370 | 371 | static void led_tick(struct k_timer *timer) 372 | { 373 | uint16_t ch; 374 | int err; 375 | 376 | ARG_UNUSED(timer); 377 | 378 | for (ch = 0; ch < ARRAY_SIZE(led_channel_ctx); ch++) { 379 | err = led_event_enqueue(ch, LED_EVENT_TICK); 380 | if (err != 0) { 381 | LOG_WRN("failed to enqueue LED tick event for channel %u (err %d)", ch, 382 | err); 383 | } 384 | } 385 | } 386 | 387 | int cannectivity_led_event(const struct device *dev, uint16_t ch, enum gs_usb_event event, 388 | void *user_data) 389 | { 390 | led_event_t led_event; 391 | struct led_ctx *lctx; 392 | int err; 393 | 394 | ARG_UNUSED(dev); 395 | ARG_UNUSED(user_data); 396 | 397 | if (ch >= ARRAY_SIZE(led_channel_ctx)) { 398 | LOG_ERR("event for non-existing channel %u", ch); 399 | return -EINVAL; 400 | } 401 | 402 | lctx = &led_channel_ctx[ch]; 403 | 404 | switch (event) { 405 | case GS_USB_EVENT_CHANNEL_STARTED: 406 | LOG_DBG("channel %u started", ch); 407 | led_event = LED_EVENT_CHANNEL_STARTED; 408 | break; 409 | case GS_USB_EVENT_CHANNEL_STOPPED: 410 | LOG_DBG("channel %u stopped", ch); 411 | led_event = LED_EVENT_CHANNEL_STOPPED; 412 | break; 413 | case GS_USB_EVENT_CHANNEL_ACTIVITY_RX: 414 | __fallthrough; 415 | case GS_USB_EVENT_CHANNEL_ACTIVITY_TX: 416 | /* Low-pass filtering of RX/TX activity events is combined if no TX LED */ 417 | led_event = LED_EVENT_CHANNEL_ACTIVITY_RX; 418 | int idx = LED_ACTIVITY_RX; 419 | 420 | if (event == GS_USB_EVENT_CHANNEL_ACTIVITY_TX) { 421 | led_event = LED_EVENT_CHANNEL_ACTIVITY_TX; 422 | 423 | if (LED_CTX_HAS_DUAL_ACTIVITY_LEDS(lctx)) { 424 | idx = LED_ACTIVITY_TX; 425 | } 426 | } 427 | 428 | if (!sys_timepoint_expired(lctx->activity[idx])) { 429 | goto skipped; 430 | } 431 | 432 | lctx->activity[idx] = sys_timepoint_calc(K_MSEC(LED_TICK_MS * LED_TICKS_ACTIVITY)); 433 | break; 434 | case GS_USB_EVENT_CHANNEL_IDENTIFY_ON: 435 | LOG_DBG("identify channel %u on", ch); 436 | led_event = LED_EVENT_CHANNEL_IDENTIFY_ON; 437 | break; 438 | case GS_USB_EVENT_CHANNEL_IDENTIFY_OFF: 439 | LOG_DBG("identify channel %u off", ch); 440 | led_event = LED_EVENT_CHANNEL_IDENTIFY_OFF; 441 | break; 442 | default: 443 | /* Unsupported event */ 444 | goto skipped; 445 | } 446 | 447 | err = led_event_enqueue(ch, led_event); 448 | if (err != 0) { 449 | LOG_ERR("failed to enqueue LED event for channel %u (err %d)", ch, err); 450 | } 451 | 452 | skipped: 453 | return 0; 454 | } 455 | 456 | static void led_thread(void *arg1, void *arg2, void *arg3) 457 | { 458 | struct led_ctx *lctx; 459 | led_event_t event; 460 | uint16_t ch; 461 | int err; 462 | 463 | ARG_UNUSED(arg1); 464 | ARG_UNUSED(arg2); 465 | ARG_UNUSED(arg3); 466 | 467 | for (ch = 0; ch < ARRAY_SIZE(led_channel_ctx); ch++) { 468 | lctx = &led_channel_ctx[ch]; 469 | 470 | smf_set_initial(SMF_CTX(lctx), &led_states[LED_STATE_NORMAL]); 471 | } 472 | 473 | while (true) { 474 | err = k_poll(led_poll_events, ARRAY_SIZE(led_poll_events), K_FOREVER); 475 | if (err == 0) { 476 | for (ch = 0; ch < ARRAY_SIZE(led_poll_events); ch++) { 477 | lctx = &led_channel_ctx[ch]; 478 | 479 | if (led_poll_events[ch].state != K_POLL_STATE_MSGQ_DATA_AVAILABLE) { 480 | continue; 481 | } 482 | 483 | err = k_msgq_get(&lctx->eventq, &event, K_NO_WAIT); 484 | if (err == 0) { 485 | lctx->event = event; 486 | 487 | err = smf_run_state(SMF_CTX(lctx)); 488 | if (err != 0) { 489 | break; 490 | } 491 | } 492 | 493 | led_poll_events[ch].state = K_POLL_STATE_NOT_READY; 494 | } 495 | } 496 | } 497 | 498 | LOG_ERR("LED finite-state machine terminated (err %d)", err); 499 | } 500 | 501 | int cannectivity_led_init(void) 502 | { 503 | struct led_ctx *lctx; 504 | uint16_t ch; 505 | int i; 506 | 507 | for (ch = 0; ch < ARRAY_SIZE(led_channel_ctx); ch++) { 508 | lctx = &led_channel_ctx[ch]; 509 | lctx->ch = ch; 510 | 511 | for (i = 0; i < ARRAY_SIZE(lctx->activity); i++) { 512 | lctx->activity[i] = sys_timepoint_calc(K_NO_WAIT); 513 | } 514 | 515 | if (LED_CTX_HAS_STATE_LED(lctx)) { 516 | if (!led_is_ready_dt(&lctx->state_led)) { 517 | LOG_ERR("state LED for channel %u not ready", ch); 518 | return -ENODEV; 519 | } 520 | } 521 | 522 | for (i = 0; i < ARRAY_SIZE(lctx->activity_led); i++) { 523 | if (lctx->activity_led[i].dev != NULL) { 524 | if (!led_is_ready_dt(&lctx->activity_led[i])) { 525 | LOG_ERR("activity LED %d for channel %u not ready", i, ch); 526 | return -ENODEV; 527 | } 528 | } 529 | } 530 | 531 | k_msgq_init(&lctx->eventq, lctx->eventq_buf, sizeof(led_event_t), 532 | CONFIG_CANNECTIVITY_LED_EVENT_MSGQ_SIZE); 533 | 534 | k_poll_event_init(&led_poll_events[ch], K_POLL_TYPE_MSGQ_DATA_AVAILABLE, 535 | K_POLL_MODE_NOTIFY_ONLY, &lctx->eventq); 536 | } 537 | 538 | k_thread_create(&led_thread_data, led_thread_stack, K_THREAD_STACK_SIZEOF(led_thread_stack), 539 | led_thread, NULL, NULL, NULL, CONFIG_CANNECTIVITY_LED_THREAD_PRIO, 0, 540 | K_NO_WAIT); 541 | k_thread_name_set(&led_thread_data, "led"); 542 | 543 | k_timer_start(&led_tick_timer, K_MSEC(LED_TICK_MS), K_MSEC(LED_TICK_MS)); 544 | 545 | return 0; 546 | } 547 | -------------------------------------------------------------------------------- /app/src/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Henrik Brix Andersen 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | #include "cannectivity.h" 15 | #include "zephyr/app_version.h" 16 | 17 | LOG_MODULE_REGISTER(main, CONFIG_CANNECTIVITY_LOG_LEVEL); 18 | 19 | #ifdef CONFIG_CANNECTIVITY_BOOT_BANNER 20 | #if defined(APP_BUILD_VERSION) && !IS_EMPTY(APP_BUILD_VERSION) 21 | #define CANNECTIVITY_BANNER_VERSION STRINGIFY(APP_BUILD_VERSION) 22 | #else 23 | #define CANNECTIVITY_BANNER_VERSION APP_VERSION_STRING 24 | #endif 25 | #endif /* CONFIG_CANNECTIVITY_BOOT_BANNER */ 26 | 27 | #define CHANNEL_CAN_CONTROLLER_DT_GET(node_id) DEVICE_DT_GET(DT_PHANDLE(node_id, can_controller)) 28 | 29 | static const struct gs_usb_ops gs_usb_ops = { 30 | #ifdef CONFIG_CANNECTIVITY_TIMESTAMP_COUNTER 31 | .timestamp = cannectivity_timestamp_get, 32 | #endif 33 | #ifdef CONFIG_CANNECTIVITY_LED 34 | .event = cannectivity_led_event, 35 | #endif 36 | #ifdef CONFIG_CANNECTIVITY_TERMINATION_GPIO 37 | .set_termination = cannectivity_set_termination, 38 | .get_termination = cannectivity_get_termination, 39 | #endif 40 | }; 41 | 42 | int main(void) 43 | { 44 | const struct device *gs_usb = DEVICE_DT_GET(DT_NODELABEL(gs_usb0)); 45 | const struct device *channels[] = { 46 | #if CANNECTIVITY_DT_HAS_CHANNEL 47 | CANNECTIVITY_DT_FOREACH_CHANNEL_SEP(CHANNEL_CAN_CONTROLLER_DT_GET, (,)) 48 | #else /* CANNECTIVITY_DT_HAS_CHANNEL */ 49 | DEVICE_DT_GET(DT_CHOSEN(zephyr_canbus)) 50 | #endif /* !CANNECTIVITY_DT_HAS_CHANNEL */ 51 | }; 52 | int err; 53 | 54 | #ifdef CONFIG_CANNECTIVITY_BOOT_BANNER 55 | printk("*** CANnectivity firmware " CANNECTIVITY_BANNER_VERSION " ***\n"); 56 | #endif /* CONFIG_CANNECTIVITY_BOOT_BANNER */ 57 | 58 | if (!device_is_ready(gs_usb)) { 59 | LOG_ERR("gs_usb USB device not ready"); 60 | return 0; 61 | } 62 | 63 | if (IS_ENABLED(CONFIG_CANNECTIVITY_LED)) { 64 | err = cannectivity_led_init(); 65 | if (err != 0) { 66 | return 0; 67 | } 68 | } 69 | 70 | if (IS_ENABLED(CONFIG_CANNECTIVITY_TERMINATION_GPIO)) { 71 | err = cannectivity_termination_init(); 72 | if (err != 0) { 73 | return 0; 74 | } 75 | } 76 | 77 | if (IS_ENABLED(CONFIG_CANNECTIVITY_TIMESTAMP_COUNTER)) { 78 | err = cannectivity_timestamp_init(); 79 | if (err != 0) { 80 | return 0; 81 | } 82 | } 83 | 84 | err = gs_usb_register(gs_usb, channels, ARRAY_SIZE(channels), &gs_usb_ops, NULL); 85 | if (err != 0U) { 86 | LOG_ERR("failed to register gs_usb (err %d)", err); 87 | return 0; 88 | } 89 | 90 | err = cannectivity_usb_init(); 91 | if (err) { 92 | LOG_ERR("failed to enable USB device"); 93 | return err; 94 | } 95 | 96 | if (IS_ENABLED(CONFIG_BOOTLOADER_MCUBOOT)) { 97 | err = cannectivity_dfu_init(); 98 | if (err) { 99 | LOG_ERR("failed to initialize DFU"); 100 | return err; 101 | } 102 | } 103 | 104 | LOG_INF("CANnectivity firmware initialized with %u channel%s", ARRAY_SIZE(channels), 105 | ARRAY_SIZE(channels) > 1 ? "s" : ""); 106 | } 107 | -------------------------------------------------------------------------------- /app/src/termination.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Henrik Brix Andersen 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "cannectivity.h" 13 | 14 | LOG_MODULE_REGISTER(termination, CONFIG_CANNECTIVITY_LOG_LEVEL); 15 | 16 | struct termination_gpio_dt_spec { 17 | const struct gpio_dt_spec gpio; 18 | bool terminated; 19 | }; 20 | 21 | #define CHANNEL_TERMINATION_GPIO_DT_SPEC_GET(node_id) \ 22 | { \ 23 | .gpio = GPIO_DT_SPEC_GET_OR(node_id, termination_gpios, {0}), \ 24 | .terminated = IS_ENABLED(CONFIG_CANNECTIVITY_TERMINATION_DEFAULT_ON), \ 25 | } 26 | 27 | static struct termination_gpio_dt_spec termination_gpios[] = { 28 | CANNECTIVITY_DT_FOREACH_CHANNEL_SEP(CHANNEL_TERMINATION_GPIO_DT_SPEC_GET, (,)) 29 | }; 30 | 31 | int cannectivity_set_termination(const struct device *dev, uint16_t ch, bool terminate, 32 | void *user_data) 33 | { 34 | struct termination_gpio_dt_spec *spec; 35 | int err; 36 | 37 | ARG_UNUSED(dev); 38 | ARG_UNUSED(user_data); 39 | 40 | LOG_DBG("set termination for channel %u: %s", ch, terminate ? "on" : "off"); 41 | 42 | if (ch >= ARRAY_SIZE(termination_gpios)) { 43 | LOG_ERR("set termination for non-existing channel %d", ch); 44 | return -EINVAL; 45 | } 46 | 47 | spec = &termination_gpios[ch]; 48 | 49 | if (spec->gpio.port == NULL) { 50 | return -ENODEV; 51 | } 52 | 53 | err = gpio_pin_set_dt(&spec->gpio, (int)terminate); 54 | if (err != 0) { 55 | LOG_ERR("failed to set termination for channel %u to %s (err %d)", ch, 56 | terminate ? "on" : "off", err); 57 | return err; 58 | } 59 | 60 | spec->terminated = terminate; 61 | 62 | return 0; 63 | } 64 | 65 | int cannectivity_get_termination(const struct device *dev, uint16_t ch, bool *terminated, 66 | void *user_data) 67 | { 68 | ARG_UNUSED(dev); 69 | ARG_UNUSED(user_data); 70 | 71 | if (ch >= ARRAY_SIZE(termination_gpios)) { 72 | LOG_ERR("set termination for non-existing channel %d", ch); 73 | return -EINVAL; 74 | } 75 | 76 | *terminated = termination_gpios[ch].terminated; 77 | LOG_DBG("get termination for channel %u: %s", ch, *terminated ? "on" : "off"); 78 | 79 | return 0; 80 | } 81 | 82 | int cannectivity_termination_init(void) 83 | { 84 | struct termination_gpio_dt_spec *spec; 85 | uint16_t ch; 86 | int err; 87 | 88 | for (ch = 0; ch < ARRAY_SIZE(termination_gpios); ch++) { 89 | spec = &termination_gpios[ch]; 90 | 91 | if (spec->gpio.port == NULL) { 92 | continue; 93 | } 94 | 95 | if (!gpio_is_ready_dt(&spec->gpio)) { 96 | LOG_ERR("channel %d termination GPIO not ready", ch); 97 | return -ENODEV; 98 | } 99 | 100 | err = gpio_pin_configure_dt(&spec->gpio, spec->terminated ? GPIO_OUTPUT_ACTIVE 101 | : GPIO_OUTPUT_INACTIVE); 102 | if (err != 0) { 103 | LOG_ERR("failed to configure channel %d termination GPIO (err %d)", ch, 104 | err); 105 | return err; 106 | } 107 | }; 108 | 109 | return 0; 110 | } 111 | -------------------------------------------------------------------------------- /app/src/timestamp.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Henrik Brix Andersen 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "cannectivity.h" 12 | 13 | LOG_MODULE_REGISTER(timestamp, CONFIG_CANNECTIVITY_LOG_LEVEL); 14 | 15 | const struct device *counter = DEVICE_DT_GET(DT_PHANDLE(DT_PATH(cannectivity), timestamp_counter)); 16 | 17 | int cannectivity_timestamp_get(const struct device *dev, uint32_t *timestamp, void *user_data) 18 | { 19 | ARG_UNUSED(dev); 20 | ARG_UNUSED(user_data); 21 | 22 | return counter_get_value(counter, timestamp); 23 | } 24 | 25 | int cannectivity_timestamp_init(void) 26 | { 27 | int err; 28 | 29 | if (!device_is_ready(counter)) { 30 | LOG_ERR("timestamp device not ready"); 31 | return -ENODEV; 32 | } 33 | 34 | if (counter_get_frequency(counter) != MHZ(1)) { 35 | LOG_ERR("wrong timestamp counter frequency (%u)", counter_get_frequency(counter)); 36 | return -EINVAL; 37 | } 38 | 39 | if (counter_get_max_top_value(counter) != UINT32_MAX) { 40 | LOG_ERR("timestamp counter is not 32 bit wide"); 41 | return -EINVAL; 42 | } 43 | 44 | err = counter_start(counter); 45 | if (err != 0) { 46 | LOG_ERR("failed to start timestamp counter (err %d)", err); 47 | return err; 48 | }; 49 | 50 | return 0; 51 | } 52 | -------------------------------------------------------------------------------- /app/src/usb.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024-2025 Henrik Brix Andersen 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #ifdef CONFIG_USB_DEVICE_STACK_NEXT 12 | #include 13 | #else /* CONFIG_USB_DEVICE_STACK_NEXT */ 14 | #include 15 | #include 16 | #include 17 | #endif /* !CONFIG_USB_DEVICE_STACK_NEXT*/ 18 | 19 | #include 20 | 21 | #include "cannectivity.h" 22 | #include "zephyr/app_version.h" 23 | 24 | LOG_MODULE_REGISTER(usb, CONFIG_CANNECTIVITY_LOG_LEVEL); 25 | 26 | #define GS_USB_CLASS_INSTANCE_NAME "gs_usb_0" 27 | 28 | #define CANNECTIVITY_USB_BCD_DRN \ 29 | (USB_DEC_TO_BCD(APP_VERSION_MAJOR) << 8 | USB_DEC_TO_BCD(APP_VERSION_MINOR)) 30 | 31 | #ifdef CONFIG_USB_DEVICE_STACK_NEXT 32 | #define CANNECTIVITY_BOS_DESC_DEFINE_CAP static 33 | #else /* CONFIG_USB_DEVICE_STACK_NEXT */ 34 | #define CANNECTIVITY_BOS_DESC_DEFINE_CAP USB_DEVICE_BOS_DESC_DEFINE_CAP 35 | #endif /* !CONFIG_USB_DEVICE_STACK_NEXT */ 36 | 37 | CANNECTIVITY_BOS_DESC_DEFINE_CAP const struct usb_bos_capability_lpm bos_cap_lpm = { 38 | .bLength = sizeof(struct usb_bos_capability_lpm), 39 | .bDescriptorType = USB_DESC_DEVICE_CAPABILITY, 40 | .bDevCapabilityType = USB_BOS_CAPABILITY_EXTENSION, 41 | .bmAttributes = 0UL, 42 | }; 43 | 44 | struct cannectivity_msosv2_descriptor { 45 | struct msosv2_descriptor_set_header header; 46 | struct msosv2_compatible_id compatible_id; 47 | struct msosv2_guids_property guids_property; 48 | struct msosv2_vendor_revision vendor_revision; 49 | } __packed; 50 | 51 | #define COMPATIBLE_ID_WINUSB 'W', 'I', 'N', 'U', 'S', 'B', 0x00, 0x00 52 | 53 | /* clang-format off */ 54 | /* CANnectivity (random) DeviceInterfaceUUID: {B24D8379-235F-4853-95E7-7772516FA2D5} */ 55 | #define CANNECTIVITY_DEVICE_INTERFACE_GUID \ 56 | '{', 0x00, 'B', 0x00, '2', 0x00, '4', 0x00, 'D', 0x00, '8', 0x00, \ 57 | '3', 0x00, '7', 0x00, '9', 0x00, '-', 0x00, '2', 0x00, '3', 0x00, \ 58 | '5', 0x00, 'F', 0x00, '-', 0x00, '4', 0x00, '8', 0x00, '5', 0x00, \ 59 | '3', 0x00, '-', 0x00, '9', 0x00, '5', 0x00, 'E', 0x00, '7', 0x00, \ 60 | '-', 0x00, '7', 0x00, '7', 0x00, '7', 0x00, '2', 0x00, '5', 0x00, \ 61 | '1', 0x00, '6', 0x00, 'F', 0x00, 'A', 0x00, '2', 0x00, 'D', 0x00, \ 62 | '5', 0x00, '}', 0x00, 0x00, 0x00 63 | 64 | static const struct cannectivity_msosv2_descriptor cannectivity_msosv2_descriptor = { 65 | .header = { 66 | .wLength = sizeof(struct msosv2_descriptor_set_header), 67 | .wDescriptorType = MS_OS_20_SET_HEADER_DESCRIPTOR, 68 | /* Windows version (8.1) (0x06030000) */ 69 | .dwWindowsVersion = 0x06030000, 70 | .wTotalLength = sizeof(struct cannectivity_msosv2_descriptor), 71 | }, 72 | .compatible_id = { 73 | .wLength = sizeof(struct msosv2_compatible_id), 74 | .wDescriptorType = MS_OS_20_FEATURE_COMPATIBLE_ID, 75 | .CompatibleID = {COMPATIBLE_ID_WINUSB}, 76 | }, 77 | .guids_property = { 78 | .wLength = sizeof(struct msosv2_guids_property), 79 | .wDescriptorType = MS_OS_20_FEATURE_REG_PROPERTY, 80 | .wPropertyDataType = MS_OS_20_PROPERTY_DATA_REG_MULTI_SZ, 81 | .wPropertyNameLength = 42, 82 | .PropertyName = {DEVICE_INTERFACE_GUIDS_PROPERTY_NAME}, 83 | .wPropertyDataLength = 80, 84 | .bPropertyData = {CANNECTIVITY_DEVICE_INTERFACE_GUID}, 85 | }, 86 | .vendor_revision = { 87 | .wLength = sizeof(struct msosv2_vendor_revision), 88 | .wDescriptorType = MS_OS_20_FEATURE_VENDOR_REVISION, 89 | .VendorRevision = 1U, 90 | }, 91 | }; 92 | 93 | struct usb_bos_capability_msosv2 { 94 | struct usb_bos_platform_descriptor platform; 95 | struct usb_bos_capability_msos cap; 96 | } __packed; 97 | 98 | CANNECTIVITY_BOS_DESC_DEFINE_CAP const struct usb_bos_capability_msosv2 bos_cap_msosv2 = { 99 | .platform = { 100 | .bLength = sizeof(struct usb_bos_capability_msosv2), 101 | .bDescriptorType = USB_DESC_DEVICE_CAPABILITY, 102 | .bDevCapabilityType = USB_BOS_CAPABILITY_PLATFORM, 103 | .bReserved = 0, 104 | .PlatformCapabilityUUID = { 105 | /* MS OS 2.0 Platform Capability ID: D8DD60DF-4589-4CC7-9CD2-659D9E648A9F */ 106 | 0xDF, 0x60, 0xDD, 0xD8, 107 | 0x89, 0x45, 108 | 0xC7, 0x4C, 109 | 0x9C, 0xD2, 110 | 0x65, 0x9D, 0x9E, 0x64, 0x8A, 0x9F, 111 | }, 112 | }, 113 | .cap = { 114 | /* Windows version (8.1) (0x06030000) */ 115 | .dwWindowsVersion = sys_cpu_to_le32(0x06030000), 116 | .wMSOSDescriptorSetTotalLength = 117 | sys_cpu_to_le16(sizeof(struct cannectivity_msosv2_descriptor)), 118 | .bMS_VendorCode = GS_USB_MS_VENDORCODE, 119 | .bAltEnumCode = 0x00 120 | }, 121 | }; 122 | /* clang-format on */ 123 | 124 | #ifdef CONFIG_USB_DEVICE_STACK_NEXT 125 | 126 | USBD_DEVICE_DEFINE(usbd, DEVICE_DT_GET(DT_NODELABEL(zephyr_udc0)), CONFIG_CANNECTIVITY_USB_VID, 127 | CONFIG_CANNECTIVITY_USB_PID); 128 | 129 | USBD_DESC_LANG_DEFINE(lang); 130 | USBD_DESC_MANUFACTURER_DEFINE(mfr, CONFIG_CANNECTIVITY_USB_MANUFACTURER); 131 | USBD_DESC_PRODUCT_DEFINE(product, CONFIG_CANNECTIVITY_USB_PRODUCT); 132 | USBD_DESC_SERIAL_NUMBER_DEFINE(sn); 133 | USBD_DESC_CONFIG_DEFINE(fs_config_desc, "Full-Speed Configuration"); 134 | USBD_DESC_CONFIG_DEFINE(hs_config_desc, "High-Speed Configuration"); 135 | 136 | static const uint8_t attr = 137 | (IS_ENABLED(CONFIG_CANNECTIVITY_USB_SELF_POWERED) ? USB_SCD_SELF_POWERED : 0U); 138 | USBD_CONFIGURATION_DEFINE(fs_config, attr, CONFIG_CANNECTIVITY_USB_MAX_POWER, &fs_config_desc); 139 | USBD_CONFIGURATION_DEFINE(hs_config, attr, CONFIG_CANNECTIVITY_USB_MAX_POWER, &hs_config_desc); 140 | 141 | USBD_DESC_BOS_DEFINE(usbext, sizeof(bos_cap_lpm), &bos_cap_lpm); 142 | 143 | static int cannectivity_usb_vendorcode_handler(const struct usbd_context *const ctx, 144 | const struct usb_setup_packet *const setup, 145 | struct net_buf *const buf) 146 | { 147 | if (setup->bRequest == GS_USB_MS_VENDORCODE && setup->wIndex == MS_OS_20_DESCRIPTOR_INDEX) { 148 | net_buf_add_mem(buf, &cannectivity_msosv2_descriptor, 149 | MIN(net_buf_tailroom(buf), sizeof(cannectivity_msosv2_descriptor))); 150 | 151 | return 0; 152 | } 153 | 154 | return -ENOTSUP; 155 | } 156 | 157 | USBD_DESC_BOS_VREQ_DEFINE(msosv2, sizeof(bos_cap_msosv2), &bos_cap_msosv2, 158 | GS_USB_MS_VENDORCODE, cannectivity_usb_vendorcode_handler, NULL); 159 | 160 | static int cannectivity_usb_init_usbd(void) 161 | { 162 | int err; 163 | 164 | err = usbd_add_descriptor(&usbd, &lang); 165 | if (err != 0) { 166 | LOG_ERR("failed to add language descriptor (err %d)", err); 167 | return err; 168 | } 169 | 170 | err = usbd_add_descriptor(&usbd, &mfr); 171 | if (err != 0) { 172 | LOG_ERR("failed to add manufacturer descriptor (err %d)", err); 173 | return err; 174 | } 175 | 176 | err = usbd_add_descriptor(&usbd, &product); 177 | if (err != 0) { 178 | LOG_ERR("failed to add product descriptor (%d)", err); 179 | return err; 180 | } 181 | 182 | err = usbd_add_descriptor(&usbd, &sn); 183 | if (err != 0) { 184 | LOG_ERR("failed to add S/N descriptor (err %d)", err); 185 | return err; 186 | } 187 | 188 | if (usbd_caps_speed(&usbd) == USBD_SPEED_HS) { 189 | err = usbd_add_configuration(&usbd, USBD_SPEED_HS, &hs_config); 190 | if (err != 0) { 191 | LOG_ERR("failed to add high-speed configuration (err %d)", err); 192 | return err; 193 | } 194 | 195 | err = usbd_register_class(&usbd, GS_USB_CLASS_INSTANCE_NAME, USBD_SPEED_HS, 1); 196 | if (err != 0) { 197 | LOG_ERR("failed to register high-speed class instance (err %d)", err); 198 | return err; 199 | } 200 | 201 | err = usbd_device_set_code_triple(&usbd, USBD_SPEED_HS, 0, 0, 0); 202 | if (err != 0) { 203 | LOG_ERR("failed to set high-speed code triple (err %d)", err); 204 | return err; 205 | } 206 | 207 | err = usbd_device_set_bcd_usb(&usbd, USBD_SPEED_HS, USB_SRN_2_0_1); 208 | if (err != 0) { 209 | LOG_ERR("failed to set high-speed bcdUSB (err %d)", err); 210 | return err; 211 | } 212 | } 213 | 214 | err = usbd_add_configuration(&usbd, USBD_SPEED_FS, &fs_config); 215 | if (err != 0) { 216 | LOG_ERR("failed to add full-speed configuration (err %d)", err); 217 | return err; 218 | } 219 | 220 | err = usbd_register_class(&usbd, GS_USB_CLASS_INSTANCE_NAME, USBD_SPEED_FS, 1); 221 | if (err != 0) { 222 | LOG_ERR("failed to register full-speed class instance (err %d)", err); 223 | return err; 224 | } 225 | 226 | err = usbd_device_set_code_triple(&usbd, USBD_SPEED_FS, 0, 0, 0); 227 | if (err != 0) { 228 | LOG_ERR("failed to set full-speed code triple (err %d)", err); 229 | return err; 230 | } 231 | 232 | err = usbd_device_set_bcd_usb(&usbd, USBD_SPEED_FS, USB_SRN_2_0_1); 233 | if (err != 0) { 234 | LOG_ERR("failed to set full-speed bcdUSB (err %d)", err); 235 | return err; 236 | } 237 | 238 | err = usbd_device_set_bcd_device(&usbd, sys_cpu_to_le16(CANNECTIVITY_USB_BCD_DRN)); 239 | if (err != 0) { 240 | LOG_ERR("failed to set bcdDevice (err %d)", err); 241 | return err; 242 | } 243 | 244 | err = usbd_add_descriptor(&usbd, &usbext); 245 | if (err != 0) { 246 | LOG_ERR("failed to add USB 2.0 extension descriptor (err %d)", err); 247 | return err; 248 | } 249 | 250 | err = usbd_add_descriptor(&usbd, &msosv2); 251 | if (err != 0) { 252 | LOG_ERR("failed to add Microsoft OS 2.0 descriptor (err %d)", err); 253 | return err; 254 | } 255 | 256 | err = usbd_init(&usbd); 257 | if (err != 0) { 258 | LOG_ERR("failed to initialize USB device support (err %d)", err); 259 | return err; 260 | } 261 | 262 | err = usbd_enable(&usbd); 263 | if (err != 0) { 264 | LOG_ERR("failed to enable USB device"); 265 | return err; 266 | } 267 | 268 | return 0; 269 | } 270 | #else /* CONFIG_USB_DEVICE_STACK_NEXT */ 271 | static int cannectivity_usb_vendorcode_handler(int32_t *tlen, uint8_t **tdata) 272 | { 273 | *tdata = (uint8_t *)(&cannectivity_msosv2_descriptor); 274 | *tlen = sizeof(cannectivity_msosv2_descriptor); 275 | 276 | return 0; 277 | } 278 | #endif /* !CONFIG_USB_DEVICE_STACK_NEXT */ 279 | 280 | int cannectivity_usb_init(void) 281 | { 282 | #ifdef CONFIG_USB_DEVICE_STACK_NEXT 283 | return cannectivity_usb_init_usbd(); 284 | #else /* CONFIG_USB_DEVICE_STACK_NEXT */ 285 | struct usb_device_descriptor *desc = 286 | (struct usb_device_descriptor *)usb_get_device_descriptor(); 287 | 288 | desc->bcdDevice = sys_cpu_to_le16(CANNECTIVITY_USB_BCD_DRN); 289 | 290 | usb_bos_register_cap((void *)&bos_cap_lpm); 291 | usb_bos_register_cap((void *)&bos_cap_msosv2); 292 | 293 | gs_usb_register_vendorcode_callback(cannectivity_usb_vendorcode_handler); 294 | 295 | return usb_enable(NULL); 296 | #endif /* !CONFIG_USB_DEVICE_STACK_NEXT */ 297 | } 298 | -------------------------------------------------------------------------------- /app/sysbuild-dfu.conf: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024 Henrik Brix Andersen 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | SB_CONFIG_BOOTLOADER_MCUBOOT=y 5 | SB_CONFIG_BOOT_SIGNATURE_TYPE_NONE=y 6 | SB_CONFIG_CANNECTIVITY_USB_DFU=y 7 | -------------------------------------------------------------------------------- /app/sysbuild.cmake: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024 Henrik Brix Andersen 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | if(SB_CONFIG_CANNECTIVITY_USB_DFU) 5 | if(SB_CONFIG_CANNECTIVITY_USB_DFU_VID EQUAL 0x1209) 6 | if(SB_CONFIG_CANNECTIVITY_USB_DFU_PID LESS_EQUAL 0x0010) 7 | message(WARNING 8 | "SB_CONFIG_CANNECTIVITY_USB_DFU_PID is set to a generic pid.codes Test PID (${SB_CONFIG_CANNECTIVITY_USB_DFU_PID}). 9 | This PID is not unique and should not be used outside test environments." 10 | ) 11 | endif() 12 | endif() 13 | 14 | # Override MCUboot options 15 | set_config_string(mcuboot CONFIG_USB_DEVICE_MANUFACTURER "${SB_CONFIG_CANNECTIVITY_USB_DFU_MANUFACTURER}") 16 | set_config_string(mcuboot CONFIG_USB_DEVICE_PRODUCT "${SB_CONFIG_CANNECTIVITY_USB_DFU_PRODUCT}") 17 | set_config_int(mcuboot CONFIG_USB_DEVICE_VID ${SB_CONFIG_CANNECTIVITY_USB_DFU_VID}) 18 | set_config_int(mcuboot CONFIG_USB_DEVICE_PID ${SB_CONFIG_CANNECTIVITY_USB_DFU_PID}) 19 | set_config_int(mcuboot CONFIG_USB_DEVICE_DFU_PID ${SB_CONFIG_CANNECTIVITY_USB_DFU_PID}) 20 | set_config_int(mcuboot CONFIG_USB_MAX_POWER ${SB_CONFIG_CANNECTIVITY_USB_DFU_MAX_POWER}) 21 | 22 | # Only override CANnectivity firmware application options if the dfu-suffix utility is available 23 | find_program(DFU_SUFFIX dfu-suffix) 24 | if(NOT ${DFU_SUFFIX} STREQUAL DFU_SUFFIX-NOTFOUND) 25 | set_config_bool(${DEFAULT_IMAGE} CONFIG_CANNECTIVITY_GENERATE_USB_DFU_IMAGE TRUE) 26 | set_config_int(${DEFAULT_IMAGE} CONFIG_CANNECTIVITY_USB_DFU_VID ${SB_CONFIG_CANNECTIVITY_USB_DFU_VID}) 27 | set_config_int(${DEFAULT_IMAGE} CONFIG_CANNECTIVITY_USB_DFU_PID ${SB_CONFIG_CANNECTIVITY_USB_DFU_PID}) 28 | endif() 29 | endif() 30 | -------------------------------------------------------------------------------- /app/sysbuild/mcuboot/app.overlay: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Henrik Brix Andersen 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | / { 8 | chosen { 9 | zephyr,code-partition = &boot_partition; 10 | }; 11 | }; 12 | -------------------------------------------------------------------------------- /app/sysbuild/mcuboot/boards/canbardo_same70n20b.conf: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024-2025 Henrik Brix Andersen 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | CONFIG_BOOT_USB_DFU_GPIO=y 5 | CONFIG_MCUBOOT_INDICATION_LED=y 6 | -------------------------------------------------------------------------------- /app/sysbuild/mcuboot/boards/frdm_k64f_mk64f12.conf: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024 Henrik Brix Andersen 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | CONFIG_BOOT_USB_DFU_GPIO=y 5 | -------------------------------------------------------------------------------- /app/sysbuild/mcuboot/prj.conf: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024 Henrik Brix Andersen 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | CONFIG_MAIN_STACK_SIZE=10240 5 | 6 | CONFIG_FLASH=y 7 | CONFIG_PM=n 8 | 9 | CONFIG_CBPRINTF_NANO=y 10 | CONFIG_MINIMAL_LIBC=y 11 | 12 | CONFIG_LOG=y 13 | CONFIG_LOG_MODE_MINIMAL=y 14 | CONFIG_LOG_DEFAULT_LEVEL=0 15 | CONFIG_MCUBOOT_LOG_LEVEL_INF=y 16 | 17 | CONFIG_MBEDTLS_CFG_FILE="mcuboot-mbedtls-cfg.h" 18 | -------------------------------------------------------------------------------- /cmake/cannectivity.cmake: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024 Henrik Brix Andersen 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | function(cannectivity_generate_usb_dfu_image) 5 | if(CONFIG_CANNECTIVITY_GENERATE_USB_DFU_IMAGE) 6 | find_program(DFU_SUFFIX dfu-suffix) 7 | 8 | if(NOT ${DFU_SUFFIX} STREQUAL DFU_SUFFIX-NOTFOUND) 9 | if(CONFIG_BOOTLOADER_MCUBOOT) 10 | set(bin_image ${PROJECT_BINARY_DIR}/${CONFIG_KERNEL_BIN_NAME}.signed.bin) 11 | else() 12 | set(bin_image ${PROJECT_BINARY_DIR}/${CONFIG_KERNEL_BIN_NAME}.bin) 13 | endif() 14 | set(dfu_image ${bin_image}.dfu) 15 | get_filename_component(dfu_image_name ${dfu_image} NAME) 16 | 17 | add_custom_command( 18 | OUTPUT ${dfu_image} 19 | COMMAND ${CMAKE_COMMAND} -E copy ${bin_image} ${dfu_image} 20 | COMMAND ${DFU_SUFFIX} 21 | --vid ${CONFIG_CANNECTIVITY_USB_DFU_VID} 22 | --pid ${CONFIG_CANNECTIVITY_USB_DFU_PID} 23 | --did ${CONFIG_CANNECTIVITY_USB_DFU_DID} 24 | --spec ${CONFIG_CANNECTIVITY_USB_DFU_SPEC_ID} 25 | --add ${dfu_image} 26 | WORKING_DIRECTORY ${PROJECT_BINARY_DIR} 27 | DEPENDS ${bin_image} 28 | COMMENT "Generating ${dfu_image_name}" 29 | ) 30 | 31 | add_custom_target( 32 | cannectivity_usb_dfu_image 33 | ALL 34 | DEPENDS ${dfu_image} 35 | ) 36 | else() 37 | message(FATAL_ERROR "The dfu-suffix utility was not found, USB DFU image cannot be generated") 38 | endif() 39 | endif() 40 | endfunction() 41 | 42 | cannectivity_generate_usb_dfu_image() 43 | -------------------------------------------------------------------------------- /doc/LICENSE: -------------------------------------------------------------------------------- 1 | Attribution 4.0 International 2 | 3 | ======================================================================= 4 | 5 | Creative Commons Corporation ("Creative Commons") is not a law firm and 6 | does not provide legal services or legal advice. Distribution of 7 | Creative Commons public licenses does not create a lawyer-client or 8 | other relationship. Creative Commons makes its licenses and related 9 | information available on an "as-is" basis. Creative Commons gives no 10 | warranties regarding its licenses, any material licensed under their 11 | terms and conditions, or any related information. Creative Commons 12 | disclaims all liability for damages resulting from their use to the 13 | fullest extent possible. 14 | 15 | Using Creative Commons Public Licenses 16 | 17 | Creative Commons public licenses provide a standard set of terms and 18 | conditions that creators and other rights holders may use to share 19 | original works of authorship and other material subject to copyright 20 | and certain other rights specified in the public license below. The 21 | following considerations are for informational purposes only, are not 22 | exhaustive, and do not form part of our licenses. 23 | 24 | Considerations for licensors: Our public licenses are 25 | intended for use by those authorized to give the public 26 | permission to use material in ways otherwise restricted by 27 | copyright and certain other rights. Our licenses are 28 | irrevocable. Licensors should read and understand the terms 29 | and conditions of the license they choose before applying it. 30 | Licensors should also secure all rights necessary before 31 | applying our licenses so that the public can reuse the 32 | material as expected. Licensors should clearly mark any 33 | material not subject to the license. This includes other CC- 34 | licensed material, or material used under an exception or 35 | limitation to copyright. More considerations for licensors: 36 | wiki.creativecommons.org/Considerations_for_licensors 37 | 38 | Considerations for the public: By using one of our public 39 | licenses, a licensor grants the public permission to use the 40 | licensed material under specified terms and conditions. If 41 | the licensor's permission is not necessary for any reason--for 42 | example, because of any applicable exception or limitation to 43 | copyright--then that use is not regulated by the license. Our 44 | licenses grant only permissions under copyright and certain 45 | other rights that a licensor has authority to grant. Use of 46 | the licensed material may still be restricted for other 47 | reasons, including because others have copyright or other 48 | rights in the material. A licensor may make special requests, 49 | such as asking that all changes be marked or described. 50 | Although not required by our licenses, you are encouraged to 51 | respect those requests where reasonable. More considerations 52 | for the public: 53 | wiki.creativecommons.org/Considerations_for_licensees 54 | 55 | ======================================================================= 56 | 57 | Creative Commons Attribution 4.0 International Public License 58 | 59 | By exercising the Licensed Rights (defined below), You accept and agree 60 | to be bound by the terms and conditions of this Creative Commons 61 | Attribution 4.0 International Public License ("Public License"). To the 62 | extent this Public License may be interpreted as a contract, You are 63 | granted the Licensed Rights in consideration of Your acceptance of 64 | these terms and conditions, and the Licensor grants You such rights in 65 | consideration of benefits the Licensor receives from making the 66 | Licensed Material available under these terms and conditions. 67 | 68 | 69 | Section 1 -- Definitions. 70 | 71 | a. Adapted Material means material subject to Copyright and Similar 72 | Rights that is derived from or based upon the Licensed Material 73 | and in which the Licensed Material is translated, altered, 74 | arranged, transformed, or otherwise modified in a manner requiring 75 | permission under the Copyright and Similar Rights held by the 76 | Licensor. For purposes of this Public License, where the Licensed 77 | Material is a musical work, performance, or sound recording, 78 | Adapted Material is always produced where the Licensed Material is 79 | synched in timed relation with a moving image. 80 | 81 | b. Adapter's License means the license You apply to Your Copyright 82 | and Similar Rights in Your contributions to Adapted Material in 83 | accordance with the terms and conditions of this Public License. 84 | 85 | c. Copyright and Similar Rights means copyright and/or similar rights 86 | closely related to copyright including, without limitation, 87 | performance, broadcast, sound recording, and Sui Generis Database 88 | Rights, without regard to how the rights are labeled or 89 | categorized. For purposes of this Public License, the rights 90 | specified in Section 2(b)(1)-(2) are not Copyright and Similar 91 | Rights. 92 | 93 | d. Effective Technological Measures means those measures that, in the 94 | absence of proper authority, may not be circumvented under laws 95 | fulfilling obligations under Article 11 of the WIPO Copyright 96 | Treaty adopted on December 20, 1996, and/or similar international 97 | agreements. 98 | 99 | e. Exceptions and Limitations means fair use, fair dealing, and/or 100 | any other exception or limitation to Copyright and Similar Rights 101 | that applies to Your use of the Licensed Material. 102 | 103 | f. Licensed Material means the artistic or literary work, database, 104 | or other material to which the Licensor applied this Public 105 | License. 106 | 107 | g. Licensed Rights means the rights granted to You subject to the 108 | terms and conditions of this Public License, which are limited to 109 | all Copyright and Similar Rights that apply to Your use of the 110 | Licensed Material and that the Licensor has authority to license. 111 | 112 | h. Licensor means the individual(s) or entity(ies) granting rights 113 | under this Public License. 114 | 115 | i. Share means to provide material to the public by any means or 116 | process that requires permission under the Licensed Rights, such 117 | as reproduction, public display, public performance, distribution, 118 | dissemination, communication, or importation, and to make material 119 | available to the public including in ways that members of the 120 | public may access the material from a place and at a time 121 | individually chosen by them. 122 | 123 | j. Sui Generis Database Rights means rights other than copyright 124 | resulting from Directive 96/9/EC of the European Parliament and of 125 | the Council of 11 March 1996 on the legal protection of databases, 126 | as amended and/or succeeded, as well as other essentially 127 | equivalent rights anywhere in the world. 128 | 129 | k. You means the individual or entity exercising the Licensed Rights 130 | under this Public License. Your has a corresponding meaning. 131 | 132 | 133 | Section 2 -- Scope. 134 | 135 | a. License grant. 136 | 137 | 1. Subject to the terms and conditions of this Public License, 138 | the Licensor hereby grants You a worldwide, royalty-free, 139 | non-sublicensable, non-exclusive, irrevocable license to 140 | exercise the Licensed Rights in the Licensed Material to: 141 | 142 | a. reproduce and Share the Licensed Material, in whole or 143 | in part; and 144 | 145 | b. produce, reproduce, and Share Adapted Material. 146 | 147 | 2. Exceptions and Limitations. For the avoidance of doubt, where 148 | Exceptions and Limitations apply to Your use, this Public 149 | License does not apply, and You do not need to comply with 150 | its terms and conditions. 151 | 152 | 3. Term. The term of this Public License is specified in Section 153 | 6(a). 154 | 155 | 4. Media and formats; technical modifications allowed. The 156 | Licensor authorizes You to exercise the Licensed Rights in 157 | all media and formats whether now known or hereafter created, 158 | and to make technical modifications necessary to do so. The 159 | Licensor waives and/or agrees not to assert any right or 160 | authority to forbid You from making technical modifications 161 | necessary to exercise the Licensed Rights, including 162 | technical modifications necessary to circumvent Effective 163 | Technological Measures. For purposes of this Public License, 164 | simply making modifications authorized by this Section 2(a) 165 | (4) never produces Adapted Material. 166 | 167 | 5. Downstream recipients. 168 | 169 | a. Offer from the Licensor -- Licensed Material. Every 170 | recipient of the Licensed Material automatically 171 | receives an offer from the Licensor to exercise the 172 | Licensed Rights under the terms and conditions of this 173 | Public License. 174 | 175 | b. No downstream restrictions. You may not offer or impose 176 | any additional or different terms or conditions on, or 177 | apply any Effective Technological Measures to, the 178 | Licensed Material if doing so restricts exercise of the 179 | Licensed Rights by any recipient of the Licensed 180 | Material. 181 | 182 | 6. No endorsement. Nothing in this Public License constitutes or 183 | may be construed as permission to assert or imply that You 184 | are, or that Your use of the Licensed Material is, connected 185 | with, or sponsored, endorsed, or granted official status by, 186 | the Licensor or others designated to receive attribution as 187 | provided in Section 3(a)(1)(A)(i). 188 | 189 | b. Other rights. 190 | 191 | 1. Moral rights, such as the right of integrity, are not 192 | licensed under this Public License, nor are publicity, 193 | privacy, and/or other similar personality rights; however, to 194 | the extent possible, the Licensor waives and/or agrees not to 195 | assert any such rights held by the Licensor to the limited 196 | extent necessary to allow You to exercise the Licensed 197 | Rights, but not otherwise. 198 | 199 | 2. Patent and trademark rights are not licensed under this 200 | Public License. 201 | 202 | 3. To the extent possible, the Licensor waives any right to 203 | collect royalties from You for the exercise of the Licensed 204 | Rights, whether directly or through a collecting society 205 | under any voluntary or waivable statutory or compulsory 206 | licensing scheme. In all other cases the Licensor expressly 207 | reserves any right to collect such royalties. 208 | 209 | 210 | Section 3 -- License Conditions. 211 | 212 | Your exercise of the Licensed Rights is expressly made subject to the 213 | following conditions. 214 | 215 | a. Attribution. 216 | 217 | 1. If You Share the Licensed Material (including in modified 218 | form), You must: 219 | 220 | a. retain the following if it is supplied by the Licensor 221 | with the Licensed Material: 222 | 223 | i. identification of the creator(s) of the Licensed 224 | Material and any others designated to receive 225 | attribution, in any reasonable manner requested by 226 | the Licensor (including by pseudonym if 227 | designated); 228 | 229 | ii. a copyright notice; 230 | 231 | iii. a notice that refers to this Public License; 232 | 233 | iv. a notice that refers to the disclaimer of 234 | warranties; 235 | 236 | v. a URI or hyperlink to the Licensed Material to the 237 | extent reasonably practicable; 238 | 239 | b. indicate if You modified the Licensed Material and 240 | retain an indication of any previous modifications; and 241 | 242 | c. indicate the Licensed Material is licensed under this 243 | Public License, and include the text of, or the URI or 244 | hyperlink to, this Public License. 245 | 246 | 2. You may satisfy the conditions in Section 3(a)(1) in any 247 | reasonable manner based on the medium, means, and context in 248 | which You Share the Licensed Material. For example, it may be 249 | reasonable to satisfy the conditions by providing a URI or 250 | hyperlink to a resource that includes the required 251 | information. 252 | 253 | 3. If requested by the Licensor, You must remove any of the 254 | information required by Section 3(a)(1)(A) to the extent 255 | reasonably practicable. 256 | 257 | 4. If You Share Adapted Material You produce, the Adapter's 258 | License You apply must not prevent recipients of the Adapted 259 | Material from complying with this Public License. 260 | 261 | 262 | Section 4 -- Sui Generis Database Rights. 263 | 264 | Where the Licensed Rights include Sui Generis Database Rights that 265 | apply to Your use of the Licensed Material: 266 | 267 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right 268 | to extract, reuse, reproduce, and Share all or a substantial 269 | portion of the contents of the database; 270 | 271 | b. if You include all or a substantial portion of the database 272 | contents in a database in which You have Sui Generis Database 273 | Rights, then the database in which You have Sui Generis Database 274 | Rights (but not its individual contents) is Adapted Material; and 275 | 276 | c. You must comply with the conditions in Section 3(a) if You Share 277 | all or a substantial portion of the contents of the database. 278 | 279 | For the avoidance of doubt, this Section 4 supplements and does not 280 | replace Your obligations under this Public License where the Licensed 281 | Rights include other Copyright and Similar Rights. 282 | 283 | 284 | Section 5 -- Disclaimer of Warranties and Limitation of Liability. 285 | 286 | a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE 287 | EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS 288 | AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF 289 | ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, 290 | IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, 291 | WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR 292 | PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, 293 | ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT 294 | KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT 295 | ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. 296 | 297 | b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE 298 | TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, 299 | NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, 300 | INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, 301 | COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR 302 | USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN 303 | ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR 304 | DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR 305 | IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. 306 | 307 | c. The disclaimer of warranties and limitation of liability provided 308 | above shall be interpreted in a manner that, to the extent 309 | possible, most closely approximates an absolute disclaimer and 310 | waiver of all liability. 311 | 312 | 313 | Section 6 -- Term and Termination. 314 | 315 | a. This Public License applies for the term of the Copyright and 316 | Similar Rights licensed here. However, if You fail to comply with 317 | this Public License, then Your rights under this Public License 318 | terminate automatically. 319 | 320 | b. Where Your right to use the Licensed Material has terminated under 321 | Section 6(a), it reinstates: 322 | 323 | 1. automatically as of the date the violation is cured, provided 324 | it is cured within 30 days of Your discovery of the 325 | violation; or 326 | 327 | 2. upon express reinstatement by the Licensor. 328 | 329 | For the avoidance of doubt, this Section 6(b) does not affect any 330 | right the Licensor may have to seek remedies for Your violations 331 | of this Public License. 332 | 333 | c. For the avoidance of doubt, the Licensor may also offer the 334 | Licensed Material under separate terms or conditions or stop 335 | distributing the Licensed Material at any time; however, doing so 336 | will not terminate this Public License. 337 | 338 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public 339 | License. 340 | 341 | 342 | Section 7 -- Other Terms and Conditions. 343 | 344 | a. The Licensor shall not be bound by any additional or different 345 | terms or conditions communicated by You unless expressly agreed. 346 | 347 | b. Any arrangements, understandings, or agreements regarding the 348 | Licensed Material not stated herein are separate from and 349 | independent of the terms and conditions of this Public License. 350 | 351 | 352 | Section 8 -- Interpretation. 353 | 354 | a. For the avoidance of doubt, this Public License does not, and 355 | shall not be interpreted to, reduce, limit, restrict, or impose 356 | conditions on any use of the Licensed Material that could lawfully 357 | be made without permission under this Public License. 358 | 359 | b. To the extent possible, if any provision of this Public License is 360 | deemed unenforceable, it shall be automatically reformed to the 361 | minimum extent necessary to make it enforceable. If the provision 362 | cannot be reformed, it shall be severed from this Public License 363 | without affecting the enforceability of the remaining terms and 364 | conditions. 365 | 366 | c. No term or condition of this Public License will be waived and no 367 | failure to comply consented to unless expressly agreed to by the 368 | Licensor. 369 | 370 | d. Nothing in this Public License constitutes or may be interpreted 371 | as a limitation upon, or waiver of, any privileges and immunities 372 | that apply to the Licensor or You, including from the legal 373 | processes of any jurisdiction or authority. 374 | 375 | 376 | ======================================================================= 377 | 378 | Creative Commons is not a party to its public 379 | licenses. Notwithstanding, Creative Commons may elect to apply one of 380 | its public licenses to material it publishes and in those instances 381 | will be considered the “Licensor.” The text of the Creative Commons 382 | public licenses is dedicated to the public domain under the CC0 Public 383 | Domain Dedication. Except for the limited purpose of indicating that 384 | material is shared under a Creative Commons public license or as 385 | otherwise permitted by the Creative Commons policies published at 386 | creativecommons.org/policies, Creative Commons does not authorize the 387 | use of the trademark "Creative Commons" or any other trademark or logo 388 | of Creative Commons without its prior written consent including, 389 | without limitation, in connection with any unauthorized modifications 390 | to any of its public licenses or any other arrangements, 391 | understandings, or agreements concerning use of licensed material. For 392 | the avoidance of doubt, this paragraph does not form part of the 393 | public licenses. 394 | 395 | Creative Commons may be contacted at creativecommons.org. 396 | 397 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024 Henrik Brix Andersen 2 | # SPDX-License-Identifier: Apache-2.0 3 | # 4 | # Minimal Makefile for Sphinx documentation 5 | 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | 9 | SOURCEDIR = . 10 | BUILDDIR = build 11 | 12 | .PHONY: Makefile clean help html 13 | 14 | html: Makefile 15 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 16 | 17 | help: 18 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 19 | 20 | clean: 21 | $(RM) -r build 22 | -------------------------------------------------------------------------------- /doc/building.rst: -------------------------------------------------------------------------------- 1 | .. 2 | Copyright (c) 2024-2025 Henrik Brix Andersen 3 | SPDX-License-Identifier: CC-BY-4.0 4 | 5 | :hide-toc: 6 | 7 | Building and Running 8 | ==================== 9 | 10 | Building the CANnectivity firmware requires a proper Zephyr development environment. Follow the 11 | official Zephyr :external+zephyr:ref:`Getting Started Guide ` to establish one. 12 | 13 | Once a proper Zephyr development environment is established, inialize the workspace folder (here 14 | ``my-workspace``). This will clone the CANnectivity firmware repository and download the necessary 15 | Zephyr modules: 16 | 17 | .. code-block:: console 18 | 19 | west init -m https://github.com/CANnectivity/cannectivity --mr main my-workspace 20 | cd my-workspace 21 | west update 22 | 23 | Next, build the CANnectivity firmware in its default configuration for your board (here 24 | ``lpcxpresso55s16``) using ``west``: 25 | 26 | .. code-block:: console 27 | 28 | west build -b lpcxpresso55s16/lpc55s16 cannectivity/app/ 29 | 30 | To use the ``release`` configuration, which has reduced log levels, set ``FILE_SUFFIX=release``: 31 | 32 | .. code-block:: console 33 | 34 | west build -b lpcxpresso55s16/lpc55s16 cannectivity/app/ -- -DFILE_SUFFIX=release 35 | 36 | After building, the firmware can be flashed to the board by running the ``west flash`` command. 37 | 38 | .. note:: 39 | 40 | Build configurations for using the experimental ``device_next`` USB device stack in Zephyr are 41 | also provided. These can be selected by setting either ``FILE_SUFFIX=usbd_next`` or 42 | ``FILE_SUFFIX=usbd_next_release``. 43 | 44 | USB Device Firmware Upgrade (DFU) Mode 45 | -------------------------------------- 46 | 47 | CANnectivity supports USB :external+zephyr:ref:`Device Firmware Upgrade ` (DFU) via the 48 | `MCUboot`_ bootloader. This is intended for use with boards without an on-board programmer. 49 | 50 | To build CANnectivity with MCUboot integration for USB DFU use :external+zephyr:ref:`sysbuild 51 | ` with the ``sysbuild-dfu.conf`` configuration file when building for your board (here 52 | ``frdm_k64f``): 53 | 54 | .. code-block:: console 55 | 56 | west build -b frdm_k64f/mk64f12 --sysbuild ../custom/cannectivity/app/ -- -DSB_CONF_FILE=sysbuild-dfu.conf 57 | 58 | After building, MCUboot and the CANnectivity firmware can be flashed to the board by running the 59 | ``west flash`` command. 60 | 61 | Subsequent CANnectivity firmware updates can be applied via USB DFU. In order to do so, the board 62 | must first be booted into USB DFU mode. If your board has a dedicated DFU button (identified by the 63 | ``mcuboot-button0`` devicetree alias) press and hold it for 5 seconds or press and hold the button 64 | while powering up the board. If your board has a DFU LED (identified by the ``mcuboot-led0`` 65 | devicetree alias), the LED will flash while the DFU button is being held and change to constant on 66 | once DFU mode is activated. Refer to your board documentation for further details. 67 | 68 | Once in DFU mode, the CANnectivity firmware can be updated using 69 | `dfu-util`_: 70 | 71 | .. code-block:: console 72 | 73 | dfu-util -a 1 -D build/app/zephyr/zephyr.signed.bin.dfu 74 | 75 | .. _MCUboot: 76 | https://www.trustedfirmware.org/projects/mcuboot/ 77 | 78 | .. _dfu-util: 79 | https://dfu-util.sourceforge.net/ 80 | -------------------------------------------------------------------------------- /doc/conf.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024-2025 Henrik Brix Andersen 2 | # SPDX-License-Identifier: Apache-2.0 3 | # 4 | # Configuration file for the Sphinx documentation builder 5 | 6 | from pathlib import Path 7 | import re 8 | import sys 9 | 10 | project = 'CANnectivity' 11 | copyright = '2024-2025, The CANnectivity Developers' 12 | author = 'The CANnectivity Developers' 13 | 14 | extensions = [ 15 | 'sphinx_copybutton', 16 | 'sphinx.ext.githubpages', 17 | 'sphinx.ext.intersphinx', 18 | ] 19 | 20 | exclude_patterns = ['build', 'Thumbs.db', '.DS_Store'] 21 | 22 | intersphinx_mapping = {'zephyr': ('https://docs.zephyrproject.org/latest/', None)} 23 | 24 | html_baseurl = 'https://cannectivity.org/' 25 | html_title = 'CANnectivity USB to CAN adapter firmware' 26 | html_logo = 'static/CANnectivity.png' 27 | html_last_updated_fmt = "%Y-%m-%d" 28 | html_show_sourcelink = False 29 | html_show_sphinx = False 30 | html_static_path = ['static'] 31 | html_theme = 'furo' 32 | html_copy_source = False 33 | html_theme_options = { 34 | "light_css_variables": { 35 | "color-sidebar-caption-text": "#019966", 36 | "color-brand-primary": "#404040", 37 | "color-brand-content": "#404040", 38 | }, 39 | "dark_css_variables": { 40 | "color-sidebar-caption-text": "#019966", 41 | "color-brand-primary": "#e0e0e0", 42 | "color-brand-content": "#e0e0e0", 43 | }, 44 | "top_of_page_buttons": ["view"], 45 | "source_repository": "https://github.com/CANnectivity/cannectivity/", 46 | "source_branch": "main", 47 | "source_directory": "doc/", 48 | "sidebar_hide_name": True, 49 | "footer_icons": [ 50 | { 51 | "name": "GitHub", 52 | "url": "https://github.com/CANnectivity/cannectivity", 53 | "html": """ 54 | 55 | 56 | 57 | """, 58 | "class": "", 59 | }, 60 | ], 61 | } 62 | -------------------------------------------------------------------------------- /doc/index.rst: -------------------------------------------------------------------------------- 1 | .. 2 | Copyright (c) 2024-2025 Henrik Brix Andersen 3 | SPDX-License-Identifier: CC-BY-4.0 4 | 5 | :hide-toc: 6 | 7 | Introduction 8 | ============ 9 | 10 | .. toctree:: 11 | :maxdepth: 1 12 | :hidden: 13 | :caption: Contents: 14 | 15 | self 16 | building 17 | porting 18 | module 19 | 20 | CANnectivity is an open source firmware for Universal Serial Bus (USB) to Controller Area Network 21 | (CAN) adapters. 22 | 23 | The firmware implements the Geschwister Schneider USB/CAN device protocol (often referred to as 24 | "gs_usb"). This protocol is supported by the Linux kernel SocketCAN `gs_usb driver`_, by 25 | `python-can`_, and by many other software packages. 26 | 27 | The firmware, which is based on the `Zephyr RTOS`_, allows turning your favorite microcontroller 28 | development board into a full-fledged USB to CAN adapter. 29 | 30 | CANnectivity is licenced under the `Apache-2.0 license`_. The CANnectivity documentation is licensed 31 | under the `CC BY 4.0 license`_. 32 | 33 | Firmware Features 34 | ----------------- 35 | 36 | The CANnectivity firmware supports the following features, some of which depend on hardware support: 37 | 38 | * CAN classic 39 | * CAN FD (flexible data rate) 40 | * Fast-speed and Hi-speed USB 41 | * Multiple, independent CAN channels 42 | * LEDs: 43 | 44 | * CAN channel state LEDs 45 | * CAN channel activity LEDs 46 | * Visual CAN channel identification 47 | 48 | * GPIO-controlled CAN bus termination resistors 49 | * Hardware timestamping of received and transmitted CAN frames 50 | * CAN bus state reporting 51 | * CAN bus error reporting 52 | * Automatic gs_usb driver loading under Linux using custom udev rules 53 | * Automatic WinUSB driver installation under Microsoft Windows 8.1 and newer 54 | * USB Device Firmware Upgrade (DFU) mode 55 | 56 | .. _gs_usb driver: 57 | https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/net/can/usb/gs_usb.c 58 | 59 | .. _python-can: 60 | https://python-can.readthedocs.io/en/stable/interfaces/gs_usb.html 61 | 62 | .. _Zephyr RTOS: 63 | https://www.zephyrproject.org 64 | 65 | .. _Apache-2.0 license: 66 | http://www.apache.org/licenses/LICENSE-2.0 67 | 68 | .. _CC BY 4.0 license: 69 | https://creativecommons.org/licenses/by/4.0/ 70 | -------------------------------------------------------------------------------- /doc/module.rst: -------------------------------------------------------------------------------- 1 | .. 2 | Copyright (c) 2024-2025 Henrik Brix Andersen 3 | SPDX-License-Identifier: CC-BY-4.0 4 | 5 | :hide-toc: 6 | 7 | Zephyr Module 8 | ============= 9 | 10 | The CANnectivity firmware repository is a :external+zephyr:ref:`Zephyr module ` which 11 | allows for reuse of its components (i.e. the ``gs_usb`` protocol implementation) outside of the 12 | CANnectivity firmware application. 13 | 14 | To pull in CANnectivity as a Zephyr module, either add it as a West project in the ``west.yaml`` 15 | file or pull it in by adding a submanifest (e.g. ``zephyr/submanifests/cannectivity.yaml``) file 16 | with the following content and run ``west update``: 17 | 18 | .. code-block:: yaml 19 | 20 | manifest: 21 | projects: 22 | - name: cannectivity 23 | url: https://github.com/CANnectivity/cannectivity.git 24 | revision: main 25 | path: custom/cannectivity # adjust the path as needed 26 | -------------------------------------------------------------------------------- /doc/porting.rst: -------------------------------------------------------------------------------- 1 | .. 2 | Copyright (c) 2024-2025 Henrik Brix Andersen 3 | SPDX-License-Identifier: CC-BY-4.0 4 | 5 | :hide-toc: 6 | 7 | Board Porting Guide 8 | =================== 9 | 10 | Hardware Requirements 11 | --------------------- 12 | 13 | Since the CANnectivity firmware is based on the Zephyr RTOS, it requires Zephyr support for the 14 | board it is to run on. The board configuration must support both an USB device driver and at least 15 | one CAN controller. 16 | 17 | Check the list of :external+zephyr:ref:`supported boards ` in the Zephyr documentation to 18 | see if your board is already supported. If not, have a look at the instructions in the Zephyr 19 | :external+zephyr:ref:`Board Porting Guide `. 20 | 21 | Board Configuration 22 | ------------------- 23 | 24 | By default, CANnectivity relies on the :external+zephyr:ref:`devicetree ` 25 | ``zephyr,canbus`` chosen node property for specifying the CAN controller to use. If a devicetree 26 | alias for ``led0`` is present, it is used as status LED. This means that virtually any Zephyr board 27 | configuration supporting USB device, a CAN controller, and an optional user LED will work without 28 | any further configuration. 29 | 30 | Advanced board configuration (e.g. multiple CAN controllers, multiple LEDs, hardware timestamp 31 | counter) is also supported via devicetree overlays. Check the description for the ``cannectivity`` 32 | devicetree binding ``app/dts/bindings/cannectivity.yaml`` and the example devicetree overlays under 33 | ``app/boards/``. 34 | -------------------------------------------------------------------------------- /doc/requirements.txt: -------------------------------------------------------------------------------- 1 | furo 2 | sphinx 3 | sphinx-copybutton 4 | -------------------------------------------------------------------------------- /doc/static/CANnectivity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CANnectivity/cannectivity/24a6d3492e7beacc0619bf9e3635b0d985e99cd4/doc/static/CANnectivity.png -------------------------------------------------------------------------------- /dts/bindings/usb/gs_usb.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022-2025 Henrik Brix Andersen 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | description: Geschwister Schneider USB/CAN 5 | 6 | compatible: "gs_usb" 7 | 8 | include: base.yaml 9 | 10 | properties: 11 | label: 12 | description: | 13 | Optional string for use as the USB interface string descriptor. 14 | -------------------------------------------------------------------------------- /include/cannectivity/usb/class/gs_usb.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022-2024 Henrik Brix Andersen 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | /** 8 | * @file 9 | * @brief Geschwister Schneider USB/CAN Device Class API 10 | * 11 | * The Geschwister Schneider USB/CAN protocol supports 1 to 255 independent CAN channels per USB 12 | * device, each corresponding to a CAN controller. 13 | * 14 | * @code{.text} 15 | * +--------+ +----------+--------------+ 16 | * | | | | Channel 0 | 17 | * | |--------\| +--------------+ 18 | * | Host | USB | Device | Channel ... | 19 | * | |--------/| +--------------+ 20 | * | | | | Channel N | 21 | * +--------+ +----------+--------------+ 22 | * @endcode 23 | * 24 | */ 25 | 26 | #ifndef CANNECTIVITY_INCLUDE_USB_CLASS_GS_USB_H_ 27 | #define CANNECTIVITY_INCLUDE_USB_CLASS_GS_USB_H_ 28 | 29 | #include 30 | 31 | #ifdef __cplusplus 32 | extern "C" { 33 | #endif 34 | 35 | /** 36 | * @name Geschwister Schneider USB/CAN protocol software/hardware version definitions 37 | * @{ 38 | */ 39 | 40 | /** 41 | * @brief Software version 42 | */ 43 | #define GS_USB_SW_VERSION 2U 44 | 45 | /** 46 | * @brief Hardware version 47 | */ 48 | #define GS_USB_HW_VERSION 1U 49 | 50 | /** @} */ 51 | 52 | /** 53 | * @brief Geschwister Schneider USB/CAN protocol USB bRequest types 54 | */ 55 | enum { 56 | /** Host format (little endian vs. big endian) */ 57 | GS_USB_REQUEST_HOST_FORMAT = 0, 58 | /** Set CAN channel bit timing (CAN classic) */ 59 | GS_USB_REQUEST_BITTIMING, 60 | /** Set CAN channel operational mode */ 61 | GS_USB_REQUEST_MODE, 62 | /** CAN channel bus error (unsupported) */ 63 | GS_USB_REQUEST_BERR, 64 | /** Get CAN channel bit timing limits (CAN classic) */ 65 | GS_USB_REQUEST_BT_CONST, 66 | /** Get device configuration */ 67 | GS_USB_REQUEST_DEVICE_CONFIG, 68 | /** Get device hardware timestamp */ 69 | GS_USB_REQUEST_TIMESTAMP, 70 | /** Set CAN channel identify */ 71 | GS_USB_REQUEST_IDENTIFY, 72 | /** Get device user ID (unsupported) */ 73 | GS_USB_REQUEST_GET_USER_ID, 74 | /** Set device user ID (unsupported) */ 75 | GS_USB_REQUEST_SET_USER_ID, 76 | /** Set CAN channel bit timing (CAN FD data phase) */ 77 | GS_USB_REQUEST_DATA_BITTIMING, 78 | /** Get CAN channel bit timing limits (CAN FD) */ 79 | GS_USB_REQUEST_BT_CONST_EXT, 80 | /** Set CAN channel bus termination */ 81 | GS_USB_REQUEST_SET_TERMINATION, 82 | /** Get CAN channel bus termination */ 83 | GS_USB_REQUEST_GET_TERMINATION, 84 | /** Get CAN channel bus state */ 85 | GS_USB_REQUEST_GET_STATE, 86 | }; 87 | 88 | /** 89 | * @brief Geschwister Schneider USB/CAN protocol CAN channel modes 90 | */ 91 | enum { 92 | /** Reset CAN channel */ 93 | GS_USB_CHANNEL_MODE_RESET = 0, 94 | /** Start CAN channel */ 95 | GS_USB_CHANNEL_MODE_START, 96 | }; 97 | 98 | /** 99 | * @brief Geschwister Schneider USB/CAN protocol CAN channel states 100 | */ 101 | enum { 102 | /** Error-active state (RX/TX error count < 96). */ 103 | GS_USB_CHANNEL_STATE_ERROR_ACTIVE = 0, 104 | /** Error-warning state (RX/TX error count < 128). */ 105 | GS_USB_CHANNEL_STATE_ERROR_WARNING, 106 | /** Error-passive state (RX/TX error count < 256). */ 107 | GS_USB_CHANNEL_STATE_ERROR_PASSIVE, 108 | /** Bus-off state (RX/TX error count >= 256). */ 109 | GS_USB_CHANNEL_STATE_BUS_OFF, 110 | /** CAN controller is stopped and does not participate in CAN communication. */ 111 | GS_USB_CHANNEL_STATE_STOPPED, 112 | /** CAN controller is sleeping (unused) */ 113 | GS_USB_CHANNEL_STATE_SLEEPING, 114 | }; 115 | 116 | /** 117 | * @brief Geschwister Schneider USB/CAN protocol CAN channel identify modes 118 | */ 119 | enum { 120 | /** Identify mode off */ 121 | GS_USB_CHANNEL_IDENTIFY_MODE_OFF = 0, 122 | /** Identify mode on */ 123 | GS_USB_CHANNEL_IDENTIFY_MODE_ON, 124 | }; 125 | 126 | /** 127 | * @brief Geschwister Schneider USB/CAN protocol CAN channel termination states 128 | */ 129 | enum { 130 | /** Termination off */ 131 | GS_USB_CHANNEL_TERMINATION_STATE_OFF = 0, 132 | /** Termination on */ 133 | GS_USB_CHANNEL_TERMINATION_STATE_ON, 134 | }; 135 | 136 | /** 137 | * @name Geschwister Schneider USB/CAN protocol CAN channel features 138 | * @{ 139 | */ 140 | 141 | /** CAN channel supports listen-onlu mode, in which it is not allowed to send dominant bits. */ 142 | #define GS_USB_CAN_FEATURE_LISTEN_ONLY BIT(0) 143 | /** CAN channel supports in loopback mode, which it receives own frames. */ 144 | #define GS_USB_CAN_FEATURE_LOOP_BACK BIT(1) 145 | /** CAN channel supports triple sampling mode */ 146 | #define GS_USB_CAN_FEATURE_TRIPLE_SAMPLE BIT(2) 147 | /** CAN channel supports not retransmitting in case of lost arbitration or missing ACK. */ 148 | #define GS_USB_CAN_FEATURE_ONE_SHOT BIT(3) 149 | /** CAN channel supports hardware timestamping of CAN frames. */ 150 | #define GS_USB_CAN_FEATURE_HW_TIMESTAMP BIT(4) 151 | /** CAN channel supports visual identification. */ 152 | #define GS_USB_CAN_FEATURE_IDENTIFY BIT(5) 153 | /** CAN channel supports user IDs (unsupported). */ 154 | #define GS_USB_CAN_FEATURE_USER_ID BIT(6) 155 | /** CAN channel supports padding of host frames (unsupported). */ 156 | #define GS_USB_CAN_FEATURE_PAD_PKTS_TO_MAX_PKT_SIZE BIT(7) 157 | /** CAN channel supports transmitting/receiving CAN FD frames. */ 158 | #define GS_USB_CAN_FEATURE_FD BIT(8) 159 | /** CAN channel support LCP546xx specific quirks (Unused) */ 160 | #define GS_USB_CAN_FEATURE_REQ_USB_QUIRK_LPC546XX BIT(9) 161 | /** CAN channel supports extended bit timing limits. */ 162 | #define GS_USB_CAN_FEATURE_BT_CONST_EXT BIT(10) 163 | /** CAN channel supports configurable bus termination. */ 164 | #define GS_USB_CAN_FEATURE_TERMINATION BIT(11) 165 | /** CAN channel supports bus error reporting (Unsupported, always enabled) */ 166 | #define GS_USB_CAN_FEATURE_BERR_REPORTING BIT(12) 167 | /** CAN channel supports reporting of bus state. */ 168 | #define GS_USB_CAN_FEATURE_GET_STATE BIT(13) 169 | 170 | /** @} */ 171 | 172 | /** 173 | * @name Geschwister Schneider USB/CAN protocol CAN channel flags 174 | * 175 | * Bit positions match corresponding channel feature bits. 176 | * @{ 177 | */ 178 | 179 | /** CAN channel is in normal mode. */ 180 | #define GS_USB_CAN_MODE_NORMAL 0U 181 | /** CAN channel is not allowed to send dominant bits. */ 182 | #define GS_USB_CAN_MODE_LISTEN_ONLY BIT(0) 183 | /** CAN channel is in loopback mode (receives own frames). */ 184 | #define GS_USB_CAN_MODE_LOOP_BACK BIT(1) 185 | /** CAN channel uses triple sampling mode */ 186 | #define GS_USB_CAN_MODE_TRIPLE_SAMPLE BIT(2) 187 | /** CAN channel does not retransmit in case of lost arbitration or missing ACK */ 188 | #define GS_USB_CAN_MODE_ONE_SHOT BIT(3) 189 | /** CAN channel frames are timestamped. */ 190 | #define GS_USB_CAN_MODE_HW_TIMESTAMP BIT(4) 191 | /** CAN channel host frames are padded (unsupported). */ 192 | #define GS_USB_CAN_MODE_PAD_PKTS_TO_MAX_PKT_SIZE BIT(7) 193 | /** CAN channel allows transmitting/receiving CAN FD frames. */ 194 | #define GS_USB_CAN_MODE_FD BIT(8) 195 | /** CAN channel uses bus error reporting (unsupported, always enabled). */ 196 | #define GS_USB_CAN_MODE_BERR_REPORTING BIT(12) 197 | 198 | /** @} */ 199 | 200 | /** 201 | * @name Geschwister Schneider USB/CAN protocol host frame CAN flags 202 | * @{ 203 | */ 204 | 205 | /** RX overflow occurred. */ 206 | #define GS_USB_CAN_FLAG_OVERFLOW BIT(0) 207 | /** CAN frame is in CAN FD frame format. */ 208 | #define GS_USB_CAN_FLAG_FD BIT(1) 209 | /** CAN frame uses CAN FD Baud Rate Switch (BRS). */ 210 | #define GS_USB_CAN_FLAG_BRS BIT(2) 211 | /** CAN frame has the CAN FD Error State Indicator (ESI) set. */ 212 | #define GS_USB_CAN_FLAG_ESI BIT(3) 213 | 214 | /** @} */ 215 | 216 | /** 217 | * @name Geschwister Schneider USB/CAN protocol host frame CAN ID flags (nonexhaustive) 218 | * 219 | * These correspond to the definitions in linux/include/uapi/linux/can.h and 220 | * linux/include/uapi/linux/can/error.h 221 | * 222 | * @{ 223 | */ 224 | 225 | /** CAN controller errors, details in data[1] */ 226 | #define GS_USB_CAN_ID_FLAG_ERR_CRTL BIT(2) 227 | /** CAN controller is in bus off state */ 228 | #define GS_USB_CAN_ID_FLAG_ERR_BUSOFF BIT(6) 229 | /** CAN controller restarted */ 230 | #define GS_USB_CAN_ID_FLAG_ERR_RESTARTED BIT(8) 231 | /** CAN controller TX/RX error counters in data[6]/data[7] */ 232 | #define GS_USB_CAN_ID_FLAG_ERR_CNT BIT(9) 233 | /** CAN frame is an error frame */ 234 | #define GS_USB_CAN_ID_FLAG_ERR BIT(29) 235 | /** CAN frame is a Remote Transmission Request (RTR) frame */ 236 | #define GS_USB_CAN_ID_FLAG_RTR BIT(30) 237 | /** CAN frame uses extended (29-bit) CAN ID */ 238 | #define GS_USB_CAN_ID_FLAG_IDE BIT(31) 239 | 240 | /** @} */ 241 | 242 | /** 243 | * @name Geschwister Schneider USB/CAN protocol CAN controller error flags (nonexhaustive) 244 | * 245 | * These are set in data[1] for CAN error frames and correspond to the definitions in 246 | * linux/include/uapi/linux/can/error.h 247 | * 248 | * @{ 249 | */ 250 | 251 | /** RX error-warning state (RX error count < 128). */ 252 | #define GS_USB_CAN_ERR_CRTL_RX_WARNING BIT(2) 253 | /** TX error-warning state (TX error count < 128). */ 254 | #define GS_USB_CAN_ERR_CRTL_TX_WARNING BIT(3) 255 | /** RX error-passive state (RX error count < 256). */ 256 | #define GS_USB_CAN_ERR_CRTL_RX_PASSIVE BIT(4) 257 | /** TX error-passive state (TX error count < 256). */ 258 | #define GS_USB_CAN_ERR_CRTL_TX_PASSIVE BIT(5) 259 | /** Error-active state (RX/TX error count < 96). */ 260 | #define GS_USB_CAN_ERR_CRTL_ACTIVE BIT(6) 261 | 262 | /** @} */ 263 | 264 | /** 265 | * @brief Geschwister Schneider USB/CAN protocol supported host byte order format 266 | */ 267 | #define GS_USB_HOST_FORMAT_LITTLE_ENDIAN 0x0000beef 268 | 269 | /** 270 | * @brief Geschwister Schneider USB/CAN protocol host frame echo ID for RX frames 271 | */ 272 | #define GS_USB_HOST_FRAME_ECHO_ID_RX_FRAME UINT32_MAX 273 | 274 | /** 275 | * @brief Geschwister Schneider USB/CAN protocol @a GS_USB_REQUEST_HOST_FORMAT payload 276 | */ 277 | struct gs_usb_host_config { 278 | /** Byte order identification string, see @a GS_USB_HOST_FORMAT_LITTLE_ENDIAN */ 279 | uint32_t byte_order; 280 | } __packed; 281 | 282 | /** 283 | * @brief Geschwister Schneider USB/CAN protocol @a GS_USB_REQUEST_DEVICE_CONFIG payload 284 | */ 285 | struct gs_usb_device_config { 286 | /** Reserved */ 287 | uint8_t reserved1; 288 | /** Reserved */ 289 | uint8_t reserved2; 290 | /** Reserved */ 291 | uint8_t reserved3; 292 | /** Number of CAN channels on the device minus 1 (a value of zero means one channel) */ 293 | uint8_t nchannels; 294 | /** Device software version */ 295 | uint32_t sw_version; 296 | /** Device hardware version */ 297 | uint32_t hw_version; 298 | } __packed; 299 | 300 | /** 301 | * @brief Geschwister Schneider USB/CAN protocol @a GS_USB_REQUEST_MODE payload 302 | */ 303 | struct gs_usb_device_mode { 304 | /** CAN channel mode */ 305 | uint32_t mode; 306 | /** CAN channel flags */ 307 | uint32_t flags; 308 | } __packed; 309 | 310 | /** 311 | * @brief Geschwister Schneider USB/CAN protocol @a GS_USB_REQUEST_GET_STATE payload 312 | */ 313 | struct gs_usb_device_state { 314 | /** CAN channel state */ 315 | uint32_t state; 316 | /** CAN channel RX bus error count */ 317 | uint32_t rxerr; 318 | /** CAN channel TX bus error count */ 319 | uint32_t txerr; 320 | } __packed; 321 | 322 | /** 323 | * @brief Geschwister Schneider USB/CAN protocol @a GS_USB_REQUEST_BITTIMING payload 324 | */ 325 | struct gs_usb_device_bittiming { 326 | /** Propagation segment (tq) */ 327 | uint32_t prop_seg; 328 | /** Phase segment 1 (tq) */ 329 | uint32_t phase_seg1; 330 | /** Phase segment 1 (tq) */ 331 | uint32_t phase_seg2; 332 | /** Synchronisation jump width (tq) */ 333 | uint32_t sjw; 334 | /** Bitrate prescaler */ 335 | uint32_t brp; 336 | } __packed; 337 | 338 | /** 339 | * @brief Geschwister Schneider USB/CAN protocol @a GS_USB_REQUEST_IDENTIFY payload 340 | */ 341 | struct gs_usb_identify_mode { 342 | /** @a GS_USB_CHANNEL_IDENTIFY_MODE_OFF or @a GS_USB_CHANNEL_IDENTIFY_MODE_ON */ 343 | uint32_t mode; 344 | } __packed; 345 | 346 | 347 | /** 348 | * @brief Geschwister Schneider USB/CAN protocol @a GS_USB_REQUEST_SET_TERMINATION and @a 349 | * GS_USB_REQUEST_GET_TERMINATION payload 350 | */ 351 | struct gs_usb_device_termination_state { 352 | /** @a GS_USB_CHANNEL_TERMINATION_STATE_OFF or @a GS_USB_CHANNEL_TERMINATION_STATE_ON */ 353 | uint32_t state; 354 | } __packed; 355 | 356 | /** 357 | * @brief Geschwister Schneider USB/CAN protocol @a GS_USB_REQUEST_BT_CONST payload 358 | */ 359 | struct gs_usb_device_bt_const { 360 | /** Supported CAN channel features */ 361 | uint32_t feature; 362 | /** CAN core clock frequency */ 363 | uint32_t fclk_can; 364 | /** Time segment 1 minimum value (tq) */ 365 | uint32_t tseg1_min; 366 | /** Time segment 1 maximum value (tq) */ 367 | uint32_t tseg1_max; 368 | /** Time segment 2 minimum value (tq) */ 369 | uint32_t tseg2_min; 370 | /** Time segment 2 maximum value (tq) */ 371 | uint32_t tseg2_max; 372 | /** Synchronisation jump width (SJW) maximum value (tq) */ 373 | uint32_t sjw_max; 374 | /** Bitrate prescaler minimum value */ 375 | uint32_t brp_min; 376 | /** Bitrate prescaler maximum value */ 377 | uint32_t brp_max; 378 | /** Bitrate prescaler increment */ 379 | uint32_t brp_inc; 380 | } __packed; 381 | 382 | /** 383 | * @brief Geschwister Schneider USB/CAN protocol @a GS_USB_REQUEST_BT_CONST_EXT payload 384 | */ 385 | struct gs_usb_device_bt_const_ext { 386 | /** Supported CAN channel features */ 387 | uint32_t feature; 388 | /** CAN core clock frequency */ 389 | uint32_t fclk_can; 390 | /** Time segment 1 minimum value (tq) */ 391 | uint32_t tseg1_min; 392 | /** Time segment 1 maximum value (tq) */ 393 | uint32_t tseg1_max; 394 | /** Time segment 2 minimum value (tq) */ 395 | uint32_t tseg2_min; 396 | /** Time segment 2 maximum value (tq) */ 397 | uint32_t tseg2_max; 398 | /** Synchronisation jump width (SJW) maximum value (tq) */ 399 | uint32_t sjw_max; 400 | /** Bitrate prescaler minimum value */ 401 | uint32_t brp_min; 402 | /** Bitrate prescaler maximum value */ 403 | uint32_t brp_max; 404 | /** Bitrate prescaler increment */ 405 | uint32_t brp_inc; 406 | /** Data phase time segment 1 minimum value (tq) */ 407 | uint32_t dtseg1_min; 408 | /** Data phase time segment 1 maximum value (tq) */ 409 | uint32_t dtseg1_max; 410 | /** Data phase time segment 2 minimum value (tq) */ 411 | uint32_t dtseg2_min; 412 | /** Data phase time segment 2 maximum value (tq) */ 413 | uint32_t dtseg2_max; 414 | /** Data phasde synchronisation jump width (SJW) maximum value (tq) */ 415 | uint32_t dsjw_max; 416 | /** Data phase bitrate prescaler minimum value */ 417 | uint32_t dbrp_min; 418 | /** Data phase bitrate prescaler maximum value */ 419 | uint32_t dbrp_max; 420 | /** Data phase bitrate prescaler increment */ 421 | uint32_t dbrp_inc; 422 | } __packed; 423 | 424 | /** 425 | * @brief Geschwister Schneider USB/CAN protocol CAN classic host frame data 426 | */ 427 | struct gs_usb_can_frame { 428 | /** CAN frame payload */ 429 | uint8_t data[8]; 430 | } __packed __aligned(4); 431 | 432 | /** 433 | * @brief Geschwister Schneider USB/CAN protocol CAN FD host frame data 434 | */ 435 | struct gs_usb_canfd_frame { 436 | /** CAN frame payload */ 437 | uint8_t data[64]; 438 | } __packed __aligned(4); 439 | 440 | /** 441 | * @brief Geschwister Schneider USB/CAN protocol host frame header 442 | */ 443 | struct gs_usb_host_frame_hdr { 444 | /** Echo ID */ 445 | uint32_t echo_id; 446 | /** CAN ID */ 447 | uint32_t can_id; 448 | /** CAN DLC */ 449 | uint8_t can_dlc; 450 | /** CAN channel */ 451 | uint8_t channel; 452 | /** Host frame flags */ 453 | uint8_t flags; 454 | /** Reserved */ 455 | uint8_t reserved; 456 | } __packed __aligned(4); 457 | 458 | /** 459 | * @name USB endpoint addresses 460 | * 461 | * @{ 462 | */ 463 | 464 | /** USB bulk IN endpoint address */ 465 | #define GS_USB_IN_EP_ADDR 0x81 466 | /** USB bulk OUT1 endpoint address */ 467 | #define GS_USB_OUT1_EP_ADDR 0x01 468 | /** USB bulk OUT2 endpoint address */ 469 | #define GS_USB_OUT2_EP_ADDR 0x02 470 | 471 | /** @} */ 472 | 473 | /** 474 | * @brief Geschwister Schneider USB/CAN protocol timestamp field size 475 | */ 476 | #if defined(CONFIG_USB_DEVICE_GS_USB_TIMESTAMP) || defined(CONFIG_USBD_GS_USB_TIMESTAMP) 477 | #define GS_USB_TIMESTAMP_SIZE sizeof(uint32_t) 478 | #else 479 | #define GS_USB_TIMESTAMP_SIZE 0U 480 | #endif 481 | 482 | /** 483 | * @name Geschwister Schneider USB/CAN protocol host frame sizes 484 | * 485 | * @{ 486 | */ 487 | 488 | /** CAN classic host frame size */ 489 | #define GS_USB_HOST_FRAME_CAN_FRAME_SIZE \ 490 | (sizeof(struct gs_usb_host_frame_hdr) + sizeof(struct gs_usb_can_frame)) + \ 491 | GS_USB_TIMESTAMP_SIZE 492 | 493 | /** CAN FD host frame size */ 494 | #define GS_USB_HOST_FRAME_CANFD_FRAME_SIZE \ 495 | (sizeof(struct gs_usb_host_frame_hdr) + sizeof(struct gs_usb_canfd_frame)) + \ 496 | GS_USB_TIMESTAMP_SIZE 497 | 498 | /** Maximum host frame size */ 499 | #ifdef CONFIG_CAN_FD_MODE 500 | #define GS_USB_HOST_FRAME_MAX_SIZE GS_USB_HOST_FRAME_CANFD_FRAME_SIZE 501 | #else /* CONFIG_CAN_FD_MODE */ 502 | #define GS_USB_HOST_FRAME_MAX_SIZE GS_USB_HOST_FRAME_CAN_FRAME_SIZE 503 | #endif /* !CONFIG_CAN_FD_MODE */ 504 | 505 | /** @} */ 506 | 507 | /** 508 | * @brief Channel events. 509 | * 510 | * @see gs_usb_event_callback_t 511 | */ 512 | enum gs_usb_event { 513 | /** Channel started. */ 514 | GS_USB_EVENT_CHANNEL_STARTED, 515 | /** Channel stopped. */ 516 | GS_USB_EVENT_CHANNEL_STOPPED, 517 | /** Channel RX activity. */ 518 | GS_USB_EVENT_CHANNEL_ACTIVITY_RX, 519 | /** Channel TX activity. */ 520 | GS_USB_EVENT_CHANNEL_ACTIVITY_TX, 521 | #if defined(CONFIG_USB_DEVICE_GS_USB_IDENTIFICATION) || defined(CONFIG_USBD_GS_USB_IDENTIFICATION) 522 | /** Visual channel identification on. */ 523 | GS_USB_EVENT_CHANNEL_IDENTIFY_ON, 524 | /** Visual channel identification off. */ 525 | GS_USB_EVENT_CHANNEL_IDENTIFY_OFF, 526 | #endif 527 | }; 528 | 529 | /** 530 | * @brief Custom (random) MSOSv2 vendor code 531 | */ 532 | #define GS_USB_MS_VENDORCODE 0xaa 533 | 534 | #ifdef CONFIG_USB_DEVICE_GS_USB 535 | /** 536 | * @brief Defines the callback signature for responding to MSOSv2 vendor code USB requests 537 | * 538 | * @param[out] tlen Length of the MSOSv2 USB descriptor. 539 | * @param[out] tdata The MSOSv2 USB descriptor. 540 | * @return 0 on success, negative error number otherwise. 541 | */ 542 | typedef int (*gs_usb_vendorcode_callback_t)(int32_t *tlen, uint8_t **tdata); 543 | 544 | void gs_usb_register_vendorcode_callback(gs_usb_vendorcode_callback_t callback); 545 | #endif /* CONFIG_USB_DEVICE_GS_USB */ 546 | 547 | /** 548 | * @brief Defines the callback signature for obtaining a hardware timestamp 549 | * 550 | * Provided hardware timestamps must be 32-bit wide, incrementing with a rate of 1MHz. Hardware 551 | * timestamps are used for timestamping of received and transmitted CAN frames. 552 | * 553 | * @param dev Pointer to the device structure for the driver instance. 554 | * @param[out] timestamp Current timestamp value. 555 | * @param user_data User data provided when registering the callback. 556 | * @return 0 on success, negative error number otherwise. 557 | */ 558 | typedef int (*gs_usb_timestamp_callback_t)(const struct device *dev, uint32_t *timestamp, 559 | void *user_data); 560 | 561 | /** 562 | * @brief Defines the callback signature for setting the bus termination of a given CAN channel 563 | * 564 | * @param dev Pointer to the device structure for the driver instance. 565 | * @param ch CAN channel number. 566 | * @param terminate True if the channel termination is active, false otherwise. 567 | * @param user_data User data provided when registering the callback. 568 | * @return 0 on success, negative error number otherwise. 569 | */ 570 | typedef int (*gs_usb_set_termination_callback_t)(const struct device *dev, uint16_t ch, 571 | bool terminate, void *user_data); 572 | 573 | /** 574 | * @brief Defines the callback signature for getting the bus termination of a given CAN channel 575 | * 576 | * @param dev Pointer to the device structure for the driver instance. 577 | * @param ch CAN channel number. 578 | * @param[out] terminated True if the channel termination is active, false otherwise. 579 | * @param user_data User data provided when registering the callback. 580 | * @return 0 on success, negative error number otherwise. 581 | */ 582 | typedef int (*gs_usb_get_termination_callback_t)(const struct device *dev, uint16_t ch, 583 | bool *terminated, void *user_data); 584 | 585 | /** 586 | * @brief Defines the callback signature for reporting events for a given CAN channel 587 | * 588 | * @param dev Pointer to the device structure for the driver instance. 589 | * @param ch CAN channel number. 590 | * @param event Event type. 591 | * @param user_data User data provided when registering the callback. 592 | * @return 0 on success, negative error number otherwise. 593 | */ 594 | typedef int (*gs_usb_event_callback_t)(const struct device *dev, uint16_t ch, 595 | enum gs_usb_event event, void *user_data); 596 | 597 | /** 598 | * @brief Callback operations structure. 599 | */ 600 | struct gs_usb_ops { 601 | #if defined(CONFIG_USB_DEVICE_GS_USB_TIMESTAMP) || defined(CONFIG_USBD_GS_USB_TIMESTAMP) 602 | /** Optional timestamp callback */ 603 | gs_usb_timestamp_callback_t timestamp; 604 | #endif 605 | #if defined(CONFIG_USB_DEVICE_GS_USB_TERMINATION) || defined(CONFIG_USBD_GS_USB_TERMINATION) 606 | /** Optional CAN channel set termination callback */ 607 | gs_usb_set_termination_callback_t set_termination; 608 | /** Optional CAN channel get termination callback */ 609 | gs_usb_get_termination_callback_t get_termination; 610 | #endif 611 | /** CAN channel event callback */ 612 | gs_usb_event_callback_t event; 613 | }; 614 | 615 | /** 616 | * @brief Register a Geschwister Schneider USB/CAN device class driver instance 617 | * 618 | * @param dev Pointer to the device structure for the driver instance. 619 | * @param channels Pointer to an array of pointer for the CAN controller driver instances to 620 | * register. 621 | * @param nchannels Number of entries in the channels array. 622 | * @param ops Pointer to the callbacks structure. 623 | * @param user_data User data to pass to the callback functions. 624 | * @return 0 on success, negative error number otherwise. 625 | */ 626 | int gs_usb_register(const struct device *dev, const struct device **channels, size_t nchannels, 627 | const struct gs_usb_ops *ops, void *user_data); 628 | 629 | #ifdef __cplusplus 630 | } 631 | #endif 632 | 633 | #endif /* CANNECTIVITY_INCLUDE_USB_CLASS_GS_USB_H_ */ 634 | -------------------------------------------------------------------------------- /scripts/requirements-run-test.txt: -------------------------------------------------------------------------------- 1 | pyusb 2 | -------------------------------------------------------------------------------- /scripts/requirements.txt: -------------------------------------------------------------------------------- 1 | -r requirements-run-test.txt 2 | -------------------------------------------------------------------------------- /subsys/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024 Henrik Brix Andersen 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | add_subdirectory(usb) 5 | -------------------------------------------------------------------------------- /subsys/Kconfig: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024 Henrik Brix Andersen 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | rsource "usb/Kconfig" 5 | -------------------------------------------------------------------------------- /subsys/usb/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024 Henrik Brix Andersen 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | add_subdirectory_ifdef(CONFIG_USB_DEVICE_STACK device) 5 | add_subdirectory_ifdef(CONFIG_USB_DEVICE_STACK_NEXT device_next) 6 | -------------------------------------------------------------------------------- /subsys/usb/Kconfig: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024 Henrik Brix Andersen 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | rsource "device/Kconfig" 5 | rsource "device_next/Kconfig" 6 | -------------------------------------------------------------------------------- /subsys/usb/device/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024 Henrik Brix Andersen 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | if(CONFIG_USB_DEVICE_STACK) 5 | add_subdirectory(class) 6 | endif() 7 | -------------------------------------------------------------------------------- /subsys/usb/device/Kconfig: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024 Henrik Brix Andersen 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | if USB_DEVICE_STACK 5 | 6 | rsource "class/Kconfig" 7 | 8 | endif # USB_DEVICE_STACK 9 | -------------------------------------------------------------------------------- /subsys/usb/device/class/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024 Henrik Brix Andersen 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | zephyr_sources_ifdef(CONFIG_USB_DEVICE_GS_USB gs_usb.c) 5 | -------------------------------------------------------------------------------- /subsys/usb/device/class/Kconfig: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024 Henrik Brix Andersen 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | rsource "Kconfig.gs_usb" 5 | -------------------------------------------------------------------------------- /subsys/usb/device/class/Kconfig.gs_usb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022-2024 Henrik Brix Andersen 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | config USB_DEVICE_GS_USB 5 | bool "Geschwister Schneider USB/CAN Device Class support" 6 | default y 7 | depends on DT_HAS_GS_USB_ENABLED 8 | select NET_BUF 9 | select CAN 10 | imply CAN_ACCEPT_RTR 11 | help 12 | Geschwister Schneider USB/CAN (gs_usb) device class support. 13 | 14 | if USB_DEVICE_GS_USB 15 | 16 | module = USB_DEVICE_GS_USB 17 | module-str = gs_usb 18 | source "subsys/logging/Kconfig.template.log_config" 19 | 20 | config USB_DEVICE_GS_USB_MAX_CHANNELS 21 | int "Maximum number of supported channels" 22 | default 1 23 | range 1 256 24 | help 25 | Maximum number of supported Geschwister Schneider USB/CAN (gs_usb) channels. Each channel 26 | represents one CAN controller. 27 | 28 | The Linux kernel driver supports up to 3 channels. Other drivers may support just one 29 | channel. 30 | 31 | config USB_DEVICE_GS_USB_POOL_SIZE 32 | int "Host frame buffer pool size" 33 | default 20 34 | help 35 | Size of the pool used for allocating Geschwister Schneider USB/CAN (gs_usb) host 36 | frames. The pool is used for both RX and TX, and shared between all channels of a given 37 | gs_usb instance. 38 | 39 | config USB_DEVICE_GS_USB_IDENTIFICATION 40 | bool "Enable support for CAN channel identification" 41 | help 42 | Enable support for CAN channel identification callback provided by the application. 43 | 44 | config USB_DEVICE_GS_USB_TIMESTAMP 45 | bool "Enable support for hardware timestamps" 46 | help 47 | Enable support for hardware timestamps provided by the application. 48 | 49 | config USB_DEVICE_GS_USB_TIMESTAMP_SOF 50 | bool "Capture hardware timestamp on USB SoF" 51 | depends on USB_DEVICE_GS_USB_TIMESTAMP 52 | select USB_DEVICE_SOF 53 | help 54 | Capture the hardware timestamp on each USB Start of Frame event. This improves the 55 | timestamp accurracy with the cost of a higher CPU load. 56 | 57 | config USB_DEVICE_GS_USB_TERMINATION 58 | bool "Enable support for CAN bus termination resistors" 59 | help 60 | Enable support for software-controlled CAN termination resistors. 61 | 62 | config USB_DEVICE_GS_USB_RX_THREAD_STACK_SIZE 63 | int "Stack size for the RX thread" 64 | default 1024 65 | help 66 | Size of the stack used for the internal RX thread. 67 | 68 | config USB_DEVICE_GS_USB_RX_THREAD_PRIO 69 | int "Priority for the RX thread" 70 | default 0 71 | help 72 | Priority level for the internal RX thread. 73 | 74 | config USB_DEVICE_GS_USB_TX_THREAD_STACK_SIZE 75 | int "Stack size for the TX thread" 76 | default 1024 77 | help 78 | Size of the stack used for the internal TX thread. 79 | 80 | config USB_DEVICE_GS_USB_TX_THREAD_PRIO 81 | int "Priority for the TX thread" 82 | default 0 83 | help 84 | Priority level for the internal TX thread. 85 | 86 | config USB_DEVICE_GS_USB_MAX_PACKET_SIZE 87 | int "Maximum bulk endpoint packet size" 88 | default 512 if USB_DC_HAS_HS_SUPPORT 89 | default 64 90 | range 64 512 91 | help 92 | Maximum bulk endpoint packet size in bytes. Classic CAN host frames fit into 64 byte 93 | packets, whereas CAN FD host frames can benefit from a maximum packet size of 512 bytes. 94 | 95 | config USB_DEVICE_GS_USB_COMPATIBILITY_MODE 96 | bool "Enable compatibility mode" 97 | default y 98 | help 99 | Enable Geschwister Schneider USB/CAN (gs_usb) driver compatibility mode. 100 | 101 | The Linux kernel gs_usb driver prior to kernel v6.12.5 uses hardcoded USB endpoint 102 | addresses 0x81 (bulk IN) and 0x02 (bulk OUT). The same assumption on USB endpoint 103 | addresses may be present in other drivers as well (e.g. python-can). 104 | 105 | Depending on the capabilities of the USB device controller, the requested USB endpoint 106 | addresses may be rewritten by the Zephyr USB device stack at runtime. Enabling 107 | compatibility mode will include a second bulk OUT endpoint to ensure correct operation 108 | with gs_usb drivers using the hardcoded USB endpoint addresses described above. 109 | 110 | It is safe to enable compatibility mode regardless of the driver being used, but the 111 | resulting firmware will require slightly more RAM and flash resources. 112 | 113 | endif # USB_DEVICE_GS_USB 114 | -------------------------------------------------------------------------------- /subsys/usb/device_next/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024 Henrik Brix Andersen 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | zephyr_library_amend() 5 | 6 | zephyr_library_sources_ifdef(CONFIG_USBD_GS_USB class/gs_usb.c) 7 | -------------------------------------------------------------------------------- /subsys/usb/device_next/Kconfig: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024 Henrik Brix Andersen 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | if USB_DEVICE_STACK_NEXT 5 | 6 | rsource "class/Kconfig" 7 | 8 | endif # USB_DEVICE_STACK_NEXT 9 | -------------------------------------------------------------------------------- /subsys/usb/device_next/class/Kconfig: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024 Henrik Brix Andersen 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | rsource "Kconfig.gs_usb" 5 | -------------------------------------------------------------------------------- /subsys/usb/device_next/class/Kconfig.gs_usb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022-2024 Henrik Brix Andersen 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | config USBD_GS_USB 5 | bool "Geschwister Schneider USB/CAN Device Class support [EXPERIMENTAL]" 6 | default y 7 | depends on DT_HAS_GS_USB_ENABLED 8 | select EXPERIMENTAL 9 | select NET_BUF 10 | select CAN 11 | imply CAN_ACCEPT_RTR 12 | help 13 | Geschwister Schneider USB/CAN (gs_usb) device class support. 14 | 15 | if USBD_GS_USB 16 | 17 | module = USBD_GS_USB 18 | module-str = gs_usb 19 | source "subsys/logging/Kconfig.template.log_config" 20 | 21 | config USBD_GS_USB_MAX_CHANNELS 22 | int "Maximum number of supported channels" 23 | default 1 24 | range 1 256 25 | help 26 | Maximum number of supported Geschwister Schneider USB/CAN (gs_usb) channels. Each channel 27 | represents one CAN controller. 28 | 29 | The Linux kernel driver supports up to 3 channels. Other drivers may support just one 30 | channel. 31 | 32 | config USBD_GS_USB_POOL_SIZE 33 | int "Host frame buffer pool size" 34 | default 20 if !USBD_GS_USB_COMPATIBILITY_MODE 35 | default 21 if USBD_GS_USB_COMPATIBILITY_MODE 36 | help 37 | Size of the pool used for allocating Geschwister Schneider USB/CAN (gs_usb) host 38 | frames. The pool is used for both RX and TX, and shared between all channels of a given 39 | gs_usb instance. 40 | 41 | config USBD_GS_USB_IDENTIFICATION 42 | bool "Enable support for CAN channel identification" 43 | help 44 | Enable support for CAN channel identification callback provided by the application. 45 | 46 | config USBD_GS_USB_TIMESTAMP 47 | bool "Enable support for hardware timestamps" 48 | help 49 | Enable support for hardware timestamps provided by the application. 50 | 51 | config USBD_GS_USB_TIMESTAMP_SOF 52 | bool "Capture hardware timestamp on USB SoF" 53 | depends on USBD_GS_USB_TIMESTAMP 54 | help 55 | Capture the hardware timestamp on each USB Start of Frame event. This improves the 56 | timestamp accurracy with the cost of a higher CPU load. 57 | 58 | config USBD_GS_USB_TERMINATION 59 | bool "Enable support for CAN bus termination resistors" 60 | help 61 | Enable support for software-controlled CAN termination resistors. 62 | 63 | config USBD_GS_USB_RX_THREAD_STACK_SIZE 64 | int "Stack size for the RX thread" 65 | default 1024 66 | help 67 | Size of the stack used for the internal RX thread. 68 | 69 | config USBD_GS_USB_RX_THREAD_PRIO 70 | int "Priority for the RX thread" 71 | default 0 72 | help 73 | Priority level for the internal RX thread. 74 | 75 | config USBD_GS_USB_TX_THREAD_STACK_SIZE 76 | int "Stack size for the TX thread" 77 | default 1024 78 | help 79 | Size of the stack used for the internal TX thread. 80 | 81 | config USBD_GS_USB_TX_THREAD_PRIO 82 | int "Priority for the TX thread" 83 | default 0 84 | help 85 | Priority level for the internal TX thread. 86 | 87 | config USBD_GS_USB_COMPATIBILITY_MODE 88 | bool "Enable compatibility mode" 89 | default y 90 | help 91 | Enable Geschwister Schneider USB/CAN (gs_usb) driver compatibility mode. 92 | 93 | The Linux kernel gs_usb driver prior to kernel v6.12.5 uses hardcoded USB endpoint 94 | addresses 0x81 (bulk IN) and 0x02 (bulk OUT). The same assumption on USB endpoint 95 | addresses may be present in other drivers as well (e.g. python-can). 96 | 97 | Depending on the capabilities of the USB device controller, the requested USB endpoint 98 | addresses may be rewritten by the Zephyr USB device stack at runtime. Enabling 99 | compatibility mode will include a second bulk OUT endpoint to ensure correct operation 100 | with gs_usb drivers using the hardcoded USB endpoint addresses described above. 101 | 102 | It is safe to enable compatibility mode regardless of the driver being used, but the 103 | resulting firmware will require slightly more RAM and flash resources. 104 | 105 | endif # USBD_GS_USB 106 | -------------------------------------------------------------------------------- /tests/subsys/usb/gs_usb/cxx/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024 Henrik Brix Andersen 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | cmake_minimum_required(VERSION 3.13.1) 5 | find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) 6 | 7 | project(cxx LANGUAGES CXX) 8 | 9 | FILE(GLOB app_sources src/*.cpp) 10 | target_sources(app PRIVATE ${app_sources}) 11 | -------------------------------------------------------------------------------- /tests/subsys/usb/gs_usb/cxx/app.overlay: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024-2025 Henrik Brix Andersen 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | / { 8 | gs_usb0: gs_usb0 { 9 | compatible = "gs_usb"; 10 | }; 11 | }; 12 | -------------------------------------------------------------------------------- /tests/subsys/usb/gs_usb/cxx/prj.conf: -------------------------------------------------------------------------------- 1 | CONFIG_CPP=y 2 | 3 | CONFIG_USB_DEVICE_STACK=y 4 | 5 | CONFIG_USB_DEVICE_GS_USB_IDENTIFICATION=y 6 | CONFIG_USB_DEVICE_GS_USB_TIMESTAMP=y 7 | CONFIG_USB_DEVICE_GS_USB_TERMINATION=y 8 | -------------------------------------------------------------------------------- /tests/subsys/usb/gs_usb/cxx/src/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Henrik Brix Andersen 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #include 8 | -------------------------------------------------------------------------------- /tests/subsys/usb/gs_usb/cxx/testcase.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024 Henrik Brix Andersen 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | common: 5 | tags: 6 | - usb 7 | - can 8 | depends_on: usb_device 9 | build_only: true 10 | integration_platforms: 11 | - frdm_k64f 12 | platform_exclude: 13 | - native_sim 14 | tests: 15 | usb.gs_usb.cxx98: 16 | extra_configs: 17 | - CONFIG_STD_CPP98=y 18 | usb.gs_usb.cxx11: 19 | extra_configs: 20 | - CONFIG_STD_CPP11=y 21 | usb.gs_usb.cxx14: 22 | extra_configs: 23 | - CONFIG_STD_CPP14=y 24 | usb.gs_usb.cxx17: 25 | extra_configs: 26 | - CONFIG_STD_CPP17=y 27 | usb.gs_usb.cxx2a: 28 | extra_configs: 29 | - CONFIG_STD_CPP2A=y 30 | usb.gs_usb.cxx20: 31 | extra_configs: 32 | - CONFIG_STD_CPP20=y 33 | usb.gs_usb.cxx2b: 34 | extra_configs: 35 | - CONFIG_STD_CPP2B=y 36 | -------------------------------------------------------------------------------- /tests/subsys/usb/gs_usb/host/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024 Henrik Brix Andersen 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | cmake_minimum_required(VERSION 3.20.0) 5 | find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) 6 | project(host) 7 | 8 | FILE(GLOB app_sources src/*.c) 9 | target_sources(app PRIVATE ${app_sources}) 10 | -------------------------------------------------------------------------------- /tests/subsys/usb/gs_usb/host/Kconfig: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024 Henrik Brix Andersen 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | menu "Test suite for gs_usb" 5 | 6 | config TEST_USB_MANUFACTURER 7 | string "USB device manufacturer string" 8 | default "CANnectivity" 9 | help 10 | USB device manufacturer string. 11 | 12 | config TEST_USB_PRODUCT 13 | string "USB device product string" 14 | default "CANnectivity gs_usb Test Suite" 15 | help 16 | USB product string. 17 | 18 | config TEST_USB_VID 19 | hex "USB device Vendor ID (VID)" 20 | default 0x1209 21 | help 22 | USB device Vendor ID (VID). 23 | 24 | config TEST_USB_PID 25 | hex "USB device Product ID (PID)" 26 | default 0x0001 27 | help 28 | USB device Product ID (PID). 29 | 30 | config TEST_USB_MAX_POWER 31 | int "USB device maximum power" 32 | default 125 33 | range 0 250 34 | help 35 | USB maximum current draw in milliampere (mA) divided by 2. 36 | A value of 125 results in a maximum current draw value of 250 mA. 37 | 38 | if USB_DEVICE_STACK 39 | 40 | configdefault USB_DEVICE_MANUFACTURER 41 | default TEST_USB_MANUFACTURER 42 | 43 | configdefault USB_DEVICE_PRODUCT 44 | default TEST_USB_PRODUCT 45 | 46 | configdefault USB_DEVICE_VID 47 | default TEST_USB_VID 48 | 49 | configdefault USB_DEVICE_PID 50 | default TEST_USB_PID 51 | 52 | configdefault USB_MAX_POWER 53 | default TEST_USB_MAX_POWER 54 | 55 | endif # USB_DEVICE_STACK 56 | 57 | endmenu 58 | 59 | source "Kconfig.zephyr" 60 | -------------------------------------------------------------------------------- /tests/subsys/usb/gs_usb/host/README.rst: -------------------------------------------------------------------------------- 1 | Geschwister Schneider USB/CAN Protocol Tests 2 | ############################################ 3 | 4 | Overview 5 | ******** 6 | 7 | This test suite uses `PyUSB`_ for testing the Geschwister Schneider USB/CAN protocol implementation. 8 | 9 | Prerequisites 10 | ************* 11 | 12 | The test suite has the following prerequisites: 13 | 14 | * The PyUSB library must be installed on the host PC. 15 | * The DUT must be connected to the host PC via USB. 16 | 17 | Building and Running 18 | ******************** 19 | 20 | Below is an example for running the test suite on the ``frdm_k64f`` board: 21 | 22 | .. code-block:: shell 23 | 24 | west twister -v -p frdm_k64f/mk64f12 --device-testing --device-serial /dev/ttyACM0 -X usb -T tests/subsys/usb/gs_usb/host/ 25 | 26 | .. _PyUSB: 27 | https://pyusb.github.io/pyusb/ 28 | -------------------------------------------------------------------------------- /tests/subsys/usb/gs_usb/host/app.overlay: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022-2025 Henrik Brix Andersen 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | / { 8 | fake_can0: fake_can0 { 9 | status = "okay"; 10 | compatible = "zephyr,fake-can"; 11 | }; 12 | 13 | fake_can1: fake_can1 { 14 | status = "okay"; 15 | compatible = "zephyr,fake-can"; 16 | }; 17 | 18 | can_loopback0: can_loopback0 { 19 | status = "okay"; 20 | compatible = "zephyr,can-loopback"; 21 | }; 22 | 23 | can_loopback1: can_loopback1 { 24 | status = "okay"; 25 | compatible = "zephyr,can-loopback"; 26 | }; 27 | 28 | gs_usb0: gs_usb0 { 29 | compatible = "gs_usb"; 30 | }; 31 | }; 32 | -------------------------------------------------------------------------------- /tests/subsys/usb/gs_usb/host/prj.conf: -------------------------------------------------------------------------------- 1 | CONFIG_TEST=y 2 | 3 | CONFIG_LOG=y 4 | CONFIG_USB_DRIVER_LOG_LEVEL_ERR=y 5 | CONFIG_USB_DEVICE_LOG_LEVEL_ERR=y 6 | CONFIG_USB_DEVICE_GS_USB_LOG_LEVEL_DBG=y 7 | 8 | CONFIG_SHELL=y 9 | 10 | CONFIG_USB_DEVICE_STACK=y 11 | CONFIG_USB_DEVICE_BOS=y 12 | CONFIG_USB_DEVICE_INITIALIZE_AT_BOOT=n 13 | CONFIG_USB_COMPOSITE_DEVICE=y 14 | 15 | CONFIG_CAN=y 16 | CONFIG_CAN_FD_MODE=y 17 | 18 | CONFIG_USB_DEVICE_GS_USB_MAX_CHANNELS=4 19 | CONFIG_USB_DEVICE_GS_USB_IDENTIFICATION=y 20 | CONFIG_USB_DEVICE_GS_USB_TIMESTAMP=y 21 | CONFIG_USB_DEVICE_GS_USB_TERMINATION=y 22 | -------------------------------------------------------------------------------- /tests/subsys/usb/gs_usb/host/prj_usbd_next.conf: -------------------------------------------------------------------------------- 1 | CONFIG_TEST=y 2 | 3 | CONFIG_LOG=y 4 | CONFIG_UDC_DRIVER_LOG_LEVEL_WRN=y 5 | CONFIG_USBD_LOG_LEVEL_WRN=y 6 | CONFIG_USBD_GS_USB_LOG_LEVEL_DBG=y 7 | 8 | CONFIG_SHELL=y 9 | 10 | CONFIG_USB_DEVICE_STACK_NEXT=y 11 | 12 | CONFIG_CAN=y 13 | CONFIG_CAN_FD_MODE=y 14 | 15 | CONFIG_USBD_GS_USB_MAX_CHANNELS=4 16 | CONFIG_USBD_GS_USB_IDENTIFICATION=y 17 | CONFIG_USBD_GS_USB_TIMESTAMP=y 18 | CONFIG_USBD_GS_USB_TERMINATION=y 19 | -------------------------------------------------------------------------------- /tests/subsys/usb/gs_usb/host/pytest/conftest.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024 Henrik Brix Andersen 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | """ 5 | Configuration of gs_usb test suite. 6 | """ 7 | 8 | import re 9 | import logging 10 | import time 11 | import pytest 12 | 13 | from twister_harness import DeviceAdapter, Shell 14 | from gs_usb import GsUSB 15 | 16 | logger = logging.getLogger(__name__) 17 | 18 | def pytest_addoption(parser) -> None: 19 | """Add local parser options to pytest.""" 20 | parser.addoption('--usb-delay', default=5, 21 | help='Delay to wait for USB enumeration after flashing (default: 5 seconds)') 22 | parser.addoption('--usb-sn', default=None, 23 | help='USB serial number of the DUT (default: None)') 24 | 25 | @pytest.fixture(name='usb_vid', scope='module') 26 | def fixture_usb_vid(shell: Shell) -> int: 27 | """Return the USB VID used by the DUT.""" 28 | regex = re.compile(r'USB VID:\s+(\S+)') 29 | lines = shell.get_filtered_output(shell.exec_command('gs_usb vid')) 30 | 31 | for line in lines: 32 | m = regex.match(line) 33 | if m: 34 | vid = int(m.groups()[0], 16) 35 | return vid 36 | 37 | pytest.fail('USB VID not found') 38 | return 0x0000 39 | 40 | @pytest.fixture(name='usb_pid', scope='module') 41 | def fixture_usb_pid(shell: Shell) -> int: 42 | """Return the USB PID used by the DUT.""" 43 | regex = re.compile(r'USB PID:\s+(\S+)') 44 | lines = shell.get_filtered_output(shell.exec_command('gs_usb pid')) 45 | 46 | for line in lines: 47 | m = regex.match(line) 48 | if m: 49 | pid = int(m.groups()[0], 16) 50 | return pid 51 | 52 | pytest.fail('USB PID not found') 53 | return 0x0000 54 | 55 | @pytest.fixture(name='usb_sn', scope='module') 56 | def fixture_usb_sn(request, dut: DeviceAdapter) -> str: 57 | """Return the USB serial number used by the DUT.""" 58 | 59 | sn = request.config.getoption('--usb-sn') 60 | 61 | if sn is None: 62 | for fixture in dut.device_config.fixtures: 63 | if fixture.startswith('usb:'): 64 | sn = fixture.split(sep=':', maxsplit=1)[1] 65 | break 66 | 67 | if sn is not None: 68 | logger.info('using USB S/N: "%s"', sn) 69 | 70 | return sn 71 | 72 | @pytest.fixture(name='dev', scope='module') 73 | def fixture_gs_usb(request, usb_vid: int, usb_pid: int, usb_sn: str) -> GsUSB: 74 | """Return gs_usb instance for testing""" 75 | 76 | delay = request.config.getoption('--usb-delay') 77 | logger.info('Waiting %d seconds for USB enumeration...', delay) 78 | time.sleep(delay) 79 | 80 | return GsUSB(usb_vid, usb_pid, usb_sn) 81 | -------------------------------------------------------------------------------- /tests/subsys/usb/gs_usb/host/pytest/gs_usb.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024 Henrik Brix Andersen 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | """ 5 | Utility class implementing the gs_usb protocol. 6 | """ 7 | 8 | import logging 9 | import struct 10 | 11 | from dataclasses import dataclass, astuple 12 | from enum import IntEnum, IntFlag 13 | 14 | import usb.core 15 | import usb.util 16 | 17 | logger = logging.getLogger(__name__) 18 | 19 | class GsUSBDeviceNotFound(Exception): 20 | """ 21 | Geschwister Schneider USB/CAN device not found. 22 | """ 23 | 24 | class GsUSBbRequest(IntEnum): 25 | """ 26 | Geschwister Schneider USB/CAN protocol bRequest types. 27 | """ 28 | # Host format (little endian vs. big endian) 29 | HOST_FORMAT = 0 30 | # Set CAN channel bit timing (CAN classic) 31 | BITTIMING = 1 32 | # Set CAN channel operational mode 33 | MODE = 2 34 | # CAN channel bus error (unsupported) 35 | BERR = 3 36 | # Get CAN channel bit timing limits (CAN classic) 37 | BT_CONST = 4 38 | # Get device configuration 39 | DEVICE_CONFIG = 5 40 | # Get device hardware timestamp 41 | TIMESTAMP = 6 42 | # Set CAN channel identify 43 | IDENTIFY = 7 44 | # Get device user ID (unsupported) 45 | GET_USER_ID = 8 46 | # Set device user ID (unsupported) 47 | SET_USER_ID = 9 48 | # Set CAN channel bit timing (CAN FD data phase) 49 | DATA_BITTIMING = 10 50 | # Get CAN channel bit timing limits (CAN FD) 51 | BT_CONST_EXT = 11 52 | # Set CAN channel bus termination 53 | SET_TERMINATION = 12 54 | # Get CAN channel bus termination 55 | GET_TERMINATION = 13 56 | # Get CAN channel bus state 57 | GET_STATE = 14 58 | 59 | class GsUSBCANChannelFeature(IntFlag): 60 | """ 61 | Geschwister Schneider USB/CAN protocol CAN channel features. 62 | """ 63 | # CAN channel supports listen-onlu mode, in which it is not allowed to send dominant bits. 64 | LISTEN_ONLY = 2**0 65 | # CAN channel supports in loopback mode, which it receives own frames. 66 | LOOP_BACK = 2**1 67 | # CAN channel supports triple sampling mode 68 | TRIPLE_SAMPLE = 2**2 69 | # CAN channel supports not retransmitting in case of lost arbitration or missing ACK. 70 | ONE_SHOT = 2**3 71 | # CAN channel supports hardware timestamping of CAN frames. 72 | HW_TIMESTAMP = 2**4 73 | # CAN channel supports visual identification. 74 | IDENTIFY = 2**5 75 | # CAN channel supports user IDs (unsupported). 76 | USER_ID = 2**6 77 | # CAN channel supports padding of host frames (unsupported). 78 | PAD_PKTS_TO_MAX_PKT_SIZE = 2**7 79 | # CAN channel supports transmitting/receiving CAN FD frames. 80 | FD = 2**8 81 | # CAN channel support LCP546xx specific quirks (Unused) 82 | REQ_USB_QUIRK_LPC546XX = 2**9 83 | # CAN channel supports extended bit timing limits. 84 | BT_CONST_EXT = 2**10 85 | # CAN channel supports configurable bus termination. 86 | TERMINATION = 2**11 87 | # CAN channel supports bus error reporting (Unsupported, always enabled) 88 | BERR_REPORTING = 2**12 89 | # CAN channel supports reporting of bus state. 90 | GET_STATE = 2**13 91 | 92 | class GsUSBCANChannelMode(IntEnum): 93 | """ 94 | Geschwister Schneider USB/CAN protocol CAN channel mode. 95 | """ 96 | # Reset CAN channel 97 | RESET = 0 98 | # Start CAN channel 99 | START = 1 100 | 101 | class GsUSBCANChannelFlag(IntFlag): 102 | """ 103 | Geschwister Schneider USB/CAN protocol CAN channel flags. 104 | """ 105 | # CAN channel is in normal mode. 106 | NORMAL = 0 107 | # CAN channel is not allowed to send dominant bits. 108 | LISTEN_ONLY = 2**0 109 | # CAN channel is in loopback mode (receives own frames). 110 | LOOP_BACK = 2**1 111 | # CAN channel uses triple sampling mode. 112 | TRIPLE_SAMPLE = 2**2 113 | # CAN channel does not retransmit in case of lost arbitration or missing ACK 114 | ONE_SHOT = 2**3 115 | # CAN channel frames are timestamped. 116 | HW_TIMESTAMP = 2**4 117 | # CAN channel host frames are padded (unsupported). 118 | PAD_PKTS_TO_MAX_PKT_SIZE = 2**7 119 | # CAN channel allows transmitting/receiving CAN FD frames. 120 | FD = 2**8 121 | # CAN channel uses bus error reporting (unsupported, always enabled). 122 | BERR_REPORTING = 2**12 123 | 124 | class GsUSBCANChannelState(IntEnum): 125 | """ 126 | Geschwister Schneider USB/CAN protocol CAN channel state. 127 | """ 128 | # Error-active state (RX/TX error count < 96). 129 | ERROR_ACTIVE = 0 130 | # Error-warning state (RX/TX error count < 128). 131 | ERROR_WARNING = 1 132 | # Error-passive state (RX/TX error count < 256). 133 | ERROR_PASSIVE = 2 134 | # Bus-off state (RX/TX error count >= 256). 135 | BUS_OFF = 3 136 | # CAN controller is stopped and does not participate in CAN communication. 137 | STOPPED = 4 138 | # CAN controller is sleeping (unused) 139 | SLEEPING = 5 140 | 141 | @dataclass 142 | class GsUSBDeviceBTConst: # pylint: disable=too-many-instance-attributes 143 | """ 144 | Geschwister Schneider USB/CAN protocol CAN classic timing limits. 145 | """ 146 | # Supported CAN channel features. 147 | feature: GsUSBCANChannelFeature 148 | # CAN core clock frequency. 149 | fclk_can: int 150 | # Time segment 1 minimum value (tq). 151 | tseg1_min: int 152 | # Time segment 1 maximum value (tq). 153 | tseg1_max: int 154 | # Time segment 2 minimum value (tq). 155 | tseg2_min: int 156 | # Time segment 2 maximum value (tq). 157 | tseg2_max: int 158 | # Synchronisation jump width (SJW) maximum value (tq). 159 | sjw_max: int 160 | # Bitrate prescaler minimum value. 161 | brp_min: int 162 | # Bitrate prescaler maximum value. 163 | brp_max: int 164 | # Bitrate prescaler increment. 165 | brp_inc: int 166 | 167 | @dataclass 168 | class GsUSBDeviceBTConstExt: # pylint: disable=too-many-instance-attributes 169 | """ 170 | Geschwister Schneider USB/CAN protocol CAN classic extended timing limits. 171 | """ 172 | # Supported CAN channel features. 173 | feature: GsUSBCANChannelFeature 174 | # CAN core clock frequency. 175 | fclk_can: int 176 | # Time segment 1 minimum value (tq). 177 | tseg1_min: int 178 | # Time segment 1 maximum value (tq). 179 | tseg1_max: int 180 | # Time segment 2 minimum value (tq). 181 | tseg2_min: int 182 | # Time segment 2 maximum value (tq). 183 | tseg2_max: int 184 | # Synchronisation jump width (SJW) maximum value (tq). 185 | sjw_max: int 186 | # Bitrate prescaler minimum value. 187 | brp_min: int 188 | # Bitrate prescaler maximum value. 189 | brp_max: int 190 | # Bitrate prescaler increment. 191 | brp_inc: int 192 | # Data phase time segment 1 minimum value (tq). 193 | dtseg1_min: int 194 | # Data phase time segment 1 maximum value (tq). 195 | dtseg1_max: int 196 | # Data phase time segment 2 minimum value (tq). 197 | dtseg2_min: int 198 | # Data phase time segment 2 maximum value (tq). 199 | dtseg2_max: int 200 | # Data phase synchronisation jump width (SJW) maximum value (tq). 201 | dsjw_max: int 202 | # Data phase bitrate prescaler minimum value. 203 | dbrp_min: int 204 | # Data phase bitrate prescaler maximum value. 205 | dbrp_max: int 206 | # Data phase bitrate prescaler increment. 207 | dbrp_inc: int 208 | 209 | @dataclass 210 | class GsUSBDeviceBittiming: 211 | """ 212 | Geschwister Schneider USB/CAN protocol device bittiming. 213 | """ 214 | # Propagation segment (tq) */ 215 | prop_seg: int 216 | # Phase segment 1 (tq) */ 217 | phase_seg1: int 218 | # Phase segment 1 (tq) */ 219 | phase_seg2: int 220 | # Synchronisation jump width (tq) */ 221 | sjw: int 222 | # Bitrate prescaler */ 223 | brp: int 224 | 225 | @dataclass 226 | class GsUSBDeviceConfig: 227 | """ 228 | Geschwister Schneider USB/CAN protocol device configuration. 229 | """ 230 | # Number of CAN channels on the device minus 1 (a value of zero means one channel) 231 | nchannels: int 232 | # Device software version 233 | sw_version: int 234 | # Device hardware version 235 | hw_version: int 236 | 237 | @dataclass 238 | class GsUSBDeviceMode: 239 | """ 240 | Geschwister Schneider USB/CAN protocol CAN device mode. 241 | """ 242 | # CAN channel mode. 243 | mode: int 244 | # CAN channel flags. 245 | flags: GsUSBCANChannelFlag 246 | 247 | @dataclass 248 | class GsUSBDeviceState: 249 | """ 250 | Geschwister Schneider USB/CAN protocol CAN device state. 251 | """ 252 | # CAN channel state. 253 | state: GsUSBCANChannelState 254 | # CAN channel RX bus error count. 255 | rxerr: int 256 | # CAN channel TX bus error count. 257 | txerr: int 258 | 259 | class GsUSB(): 260 | """ 261 | Utility class implementing the gs_usb protocol. 262 | """ 263 | 264 | def __init__(self, usb_vid: int, usb_pid: int, usb_sn: str) -> None: 265 | device = None 266 | 267 | if usb_sn is None: 268 | device = usb.core.find(idVendor=usb_vid, idProduct=usb_pid) 269 | else: 270 | devices = usb.core.find(find_all=True, idVendor=usb_vid, idProduct=usb_pid) 271 | for d in devices: 272 | if d.serial_number == usb_sn: 273 | device = d 274 | break 275 | 276 | if device is None: 277 | logger.error('USB device %04x:%04x S/N %s not found', usb_vid, usb_pid, usb_sn) 278 | raise GsUSBDeviceNotFound 279 | 280 | device.set_configuration() 281 | self.device = device 282 | 283 | rtype = usb.util.build_request_type(usb.util.CTRL_OUT, 284 | usb.util.CTRL_TYPE_VENDOR, 285 | usb.util.CTRL_RECIPIENT_INTERFACE) 286 | # This class assumes little endian transfer format, let the device know 287 | data = struct.pack(' None: 294 | """Send a bittiming request.""" 295 | rtype = usb.util.build_request_type(usb.util.CTRL_OUT, 296 | usb.util.CTRL_TYPE_VENDOR, 297 | usb.util.CTRL_RECIPIENT_INTERFACE) 298 | data = struct.pack('<5I', *astuple(bittiming)) 299 | length = self.device.ctrl_transfer(bmRequestType = rtype, 300 | bRequest = GsUSBbRequest.BITTIMING, 301 | wValue = ch, data_or_wLength = data) 302 | assert length == len(data) 303 | 304 | def mode(self, ch: int, mode: GsUSBDeviceMode) -> None: 305 | """Send a mode request.""" 306 | rtype = usb.util.build_request_type(usb.util.CTRL_OUT, 307 | usb.util.CTRL_TYPE_VENDOR, 308 | usb.util.CTRL_RECIPIENT_INTERFACE) 309 | data = struct.pack('<2I', *astuple(mode)) 310 | length = self.device.ctrl_transfer(bmRequestType = rtype, 311 | bRequest = GsUSBbRequest.MODE, 312 | wValue = ch, data_or_wLength = data) 313 | assert length == len(data) 314 | 315 | def bt_const(self, ch: int) -> GsUSBDeviceBTConst: 316 | """Send a bt_const request.""" 317 | rtype = usb.util.build_request_type(usb.util.CTRL_IN, 318 | usb.util.CTRL_TYPE_VENDOR, 319 | usb.util.CTRL_RECIPIENT_INTERFACE) 320 | data = self.device.ctrl_transfer(bmRequestType = rtype, 321 | bRequest = GsUSBbRequest.BT_CONST, 322 | wValue = ch, data_or_wLength = struct.calcsize('<10I')) 323 | 324 | return GsUSBDeviceBTConst(*struct.unpack('<10I', data)) 325 | 326 | def device_config(self) -> GsUSBDeviceConfig: 327 | """Send a device_config request.""" 328 | rtype = usb.util.build_request_type(usb.util.CTRL_IN, 329 | usb.util.CTRL_TYPE_VENDOR, 330 | usb.util.CTRL_RECIPIENT_INTERFACE) 331 | data = self.device.ctrl_transfer(bmRequestType = rtype, 332 | bRequest = GsUSBbRequest.DEVICE_CONFIG, 333 | data_or_wLength = struct.calcsize(' int: 338 | """Send a timestamp request.""" 339 | rtype = usb.util.build_request_type(usb.util.CTRL_IN, 340 | usb.util.CTRL_TYPE_VENDOR, 341 | usb.util.CTRL_RECIPIENT_INTERFACE) 342 | data = self.device.ctrl_transfer(bmRequestType = rtype, 343 | bRequest = GsUSBbRequest.TIMESTAMP, 344 | data_or_wLength = struct.calcsize(' None: 349 | """Send an identify on/off request for the given CAN channel.""" 350 | rtype = usb.util.build_request_type(usb.util.CTRL_OUT, 351 | usb.util.CTRL_TYPE_VENDOR, 352 | usb.util.CTRL_RECIPIENT_INTERFACE) 353 | data = struct.pack(' None: 360 | """Send a data_bittiming request.""" 361 | rtype = usb.util.build_request_type(usb.util.CTRL_OUT, 362 | usb.util.CTRL_TYPE_VENDOR, 363 | usb.util.CTRL_RECIPIENT_INTERFACE) 364 | data = struct.pack('<5I', *astuple(bittiming)) 365 | length = self.device.ctrl_transfer(bmRequestType = rtype, 366 | bRequest = GsUSBbRequest.DATA_BITTIMING, 367 | wValue = ch, data_or_wLength = data) 368 | assert length == len(data) 369 | 370 | def bt_const_ext(self, ch: int) -> GsUSBDeviceBTConstExt: 371 | """Send a bt_const_ext request.""" 372 | rtype = usb.util.build_request_type(usb.util.CTRL_IN, 373 | usb.util.CTRL_TYPE_VENDOR, 374 | usb.util.CTRL_RECIPIENT_INTERFACE) 375 | data = self.device.ctrl_transfer(bmRequestType = rtype, 376 | bRequest = GsUSBbRequest.BT_CONST_EXT, 377 | wValue = ch, data_or_wLength = struct.calcsize('<18I')) 378 | 379 | return GsUSBDeviceBTConstExt(*struct.unpack('<18I', data)) 380 | 381 | def set_termination(self, ch: int, terminate: bool) -> None: 382 | """Send a CAN bus termination on/off request for the given CAN channel.""" 383 | rtype = usb.util.build_request_type(usb.util.CTRL_OUT, 384 | usb.util.CTRL_TYPE_VENDOR, 385 | usb.util.CTRL_RECIPIENT_INTERFACE) 386 | data = struct.pack(' bool: 394 | """Send a CAN bus termination get request for the given CAN channel.""" 395 | rtype = usb.util.build_request_type(usb.util.CTRL_IN, 396 | usb.util.CTRL_TYPE_VENDOR, 397 | usb.util.CTRL_RECIPIENT_INTERFACE) 398 | data = self.device.ctrl_transfer(bmRequestType = rtype, 399 | bRequest = GsUSBbRequest.GET_TERMINATION, 400 | wValue = ch, data_or_wLength = struct.calcsize(' GsUSBDeviceState: 405 | """Send a get_state request.""" 406 | rtype = usb.util.build_request_type(usb.util.CTRL_IN, 407 | usb.util.CTRL_TYPE_VENDOR, 408 | usb.util.CTRL_RECIPIENT_INTERFACE) 409 | data = self.device.ctrl_transfer(bmRequestType = rtype, 410 | bRequest = GsUSBbRequest.GET_STATE, 411 | wValue = ch, data_or_wLength = struct.calcsize('<3I')) 412 | 413 | return GsUSBDeviceState(*struct.unpack('<3I', data)) 414 | -------------------------------------------------------------------------------- /tests/subsys/usb/gs_usb/host/pytest/test_gs_usb.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024 Henrik Brix Andersen 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | """ 5 | Test suites for testing gs_usb 6 | """ 7 | 8 | import logging 9 | import pytest 10 | 11 | from gs_usb import GsUSBCANChannelFeature, GsUSBCANChannelFlag, GsUSBCANChannelMode, \ 12 | GsUSBCANChannelState, GsUSBDeviceBittiming, GsUSBDeviceMode 13 | 14 | logger = logging.getLogger(__name__) 15 | 16 | TIMEOUT = 1.0 17 | 18 | DEV_NAME = 'gs_usb0' 19 | USER_DATA = '0x12345678' 20 | 21 | FAKE_CHANNELS = [ 0, 1 ] 22 | LOOPBACK_CHANNELS = [ 2, 3 ] 23 | NUM_CHANNELS = len(FAKE_CHANNELS + LOOPBACK_CHANNELS) 24 | 25 | @pytest.mark.usefixtures('dut', 'dev') 26 | class TestGsUsbRequests(): 27 | """ 28 | Class for testing gs_usb requests. 29 | """ 30 | 31 | def test_bittiming(self, dut, dev) -> None: 32 | """Test the bittiming request""" 33 | timing = GsUSBDeviceBittiming(0, 139, 20, 10, 1) 34 | 35 | for ch in FAKE_CHANNELS: 36 | dev.bittiming(ch, timing) 37 | regex = fr'fake_can{ch}: sjw = 10, prop_seg = 0, phase_seg1 = 139, phase_seg2 = 20, ' \ 38 | 'prescaler = 1' 39 | dut.readlines_until(regex=regex, timeout=TIMEOUT) 40 | 41 | def test_mode(self, dut, dev) -> None: 42 | """Test the mode request""" 43 | mode = GsUSBDeviceMode(GsUSBCANChannelMode.RESET, GsUSBCANChannelFlag.NORMAL) 44 | 45 | for ch in FAKE_CHANNELS: 46 | mode = GsUSBDeviceMode(GsUSBCANChannelMode.START, GsUSBCANChannelFlag.NORMAL) 47 | dev.mode(ch, mode) 48 | 49 | regex = fr'fake_can{ch}: mode = 0' 50 | dut.readlines_until(regex=regex, timeout=TIMEOUT) 51 | 52 | regex = fr'fake_can{ch}: start' 53 | dut.readlines_until(regex=regex, timeout=TIMEOUT) 54 | 55 | regex = fr'dev = {DEV_NAME}, ch = {ch}, started = 1, user_data = {USER_DATA}' 56 | dut.readlines_until(regex=regex, timeout=TIMEOUT) 57 | 58 | mode = GsUSBDeviceMode(GsUSBCANChannelMode.RESET, GsUSBCANChannelFlag.NORMAL) 59 | dev.mode(ch, mode) 60 | 61 | regex = fr'fake_can{ch}: stop' 62 | dut.readlines_until(regex=regex, timeout=TIMEOUT) 63 | 64 | regex = fr'dev = {DEV_NAME}, ch = {ch}, started = 0, user_data = {USER_DATA}' 65 | dut.readlines_until(regex=regex, timeout=TIMEOUT) 66 | 67 | def test_bt_const(self, dev) -> None: 68 | """Test the bt_const request""" 69 | for ch in range(NUM_CHANNELS): 70 | features = GsUSBCANChannelFeature.HW_TIMESTAMP | \ 71 | GsUSBCANChannelFeature.IDENTIFY | \ 72 | GsUSBCANChannelFeature.FD | \ 73 | GsUSBCANChannelFeature.BT_CONST_EXT | \ 74 | GsUSBCANChannelFeature.TERMINATION | \ 75 | GsUSBCANChannelFeature.GET_STATE 76 | 77 | btc = dev.bt_const(ch) 78 | logger.debug('ch%d = %s', ch, btc) 79 | 80 | assert btc.fclk_can == 80000000 81 | 82 | assert btc.tseg1_min == 2 83 | assert btc.tseg1_max == 256 84 | assert btc.tseg2_min == 2 85 | assert btc.tseg2_max == 128 86 | assert btc.sjw_max == 128 87 | assert btc.brp_min == 1 88 | assert btc.brp_max == 32 89 | assert btc.brp_inc == 1 90 | 91 | if ch in LOOPBACK_CHANNELS: 92 | features |= GsUSBCANChannelFeature.LOOP_BACK 93 | 94 | assert btc.feature == features 95 | 96 | def test_device_config(self, dev) -> None: 97 | """Test the device_config request""" 98 | cfg = dev.device_config() 99 | assert cfg.nchannels == NUM_CHANNELS - 1 100 | assert cfg.sw_version == 2 101 | assert cfg.hw_version == 1 102 | 103 | def test_timestamp(self, dut, dev) -> None: 104 | """test the timestamp request""" 105 | timestamp = dev.timestamp() 106 | regex = fr'dev = {DEV_NAME}, timestamp = 0x{timestamp:08x}, user_data = {USER_DATA}' 107 | dut.readlines_until(regex=regex, timeout=TIMEOUT) 108 | 109 | def test_identify(self, dut, dev) -> None: 110 | """Test the identify request""" 111 | for ident in (False, True): 112 | for ch in range(NUM_CHANNELS): 113 | dev.identify(ch, ident) 114 | regex = fr'dev = {DEV_NAME}, ch = {ch}, identify = {int(ident)}, ' \ 115 | fr'user_data = {USER_DATA}' 116 | dut.readlines_until(regex=regex, timeout=TIMEOUT) 117 | 118 | def test_data_bittiming(self, dut, dev) -> None: 119 | """Test the data_bittiming request""" 120 | timing = GsUSBDeviceBittiming(0, 14, 5, 2, 1) 121 | 122 | for ch in FAKE_CHANNELS: 123 | dev.data_bittiming(ch, timing) 124 | regex = fr'fake_can{ch}: sjw = 2, prop_seg = 0, phase_seg1 = 14, phase_seg2 = 5, ' \ 125 | 'prescaler = 1' 126 | dut.readlines_until(regex=regex, timeout=TIMEOUT) 127 | 128 | def test_bt_const_ext(self, dev) -> None: 129 | """Test the bt_const_ext request""" 130 | for ch in range(NUM_CHANNELS): 131 | features = GsUSBCANChannelFeature.HW_TIMESTAMP | \ 132 | GsUSBCANChannelFeature.IDENTIFY | \ 133 | GsUSBCANChannelFeature.FD | \ 134 | GsUSBCANChannelFeature.BT_CONST_EXT | \ 135 | GsUSBCANChannelFeature.TERMINATION | \ 136 | GsUSBCANChannelFeature.GET_STATE 137 | 138 | btce = dev.bt_const_ext(ch) 139 | logger.debug('ch%d = %s', ch, btce) 140 | 141 | assert btce.fclk_can == 80000000 142 | 143 | assert btce.tseg1_min == 2 144 | assert btce.tseg1_max == 256 145 | assert btce.tseg2_min == 2 146 | assert btce.tseg2_max == 128 147 | assert btce.sjw_max == 128 148 | assert btce.brp_min == 1 149 | assert btce.brp_max == 32 150 | assert btce.brp_inc == 1 151 | 152 | assert btce.dtseg1_min == 1 153 | assert btce.dtseg1_max == 32 154 | assert btce.dtseg2_min == 1 155 | assert btce.dtseg2_max == 16 156 | assert btce.dsjw_max == 16 157 | assert btce.dbrp_min == 1 158 | assert btce.dbrp_max == 32 159 | assert btce.dbrp_inc == 1 160 | 161 | if ch in LOOPBACK_CHANNELS: 162 | features |= GsUSBCANChannelFeature.LOOP_BACK 163 | 164 | assert btce.feature == features 165 | 166 | def test_set_termination(self, dut, dev) -> None: 167 | """Test the set_termination request""" 168 | for term in (False, True): 169 | for ch in range(NUM_CHANNELS): 170 | dev.set_termination(ch, term) 171 | regex = fr'dev = {DEV_NAME}, ch = {ch}, terminate = {int(term)}, ' \ 172 | fr'user_data = {USER_DATA}' 173 | dut.readlines_until(regex=regex, timeout=TIMEOUT) 174 | 175 | def test_get_termination(self, dut, dev) -> None: 176 | """Test the get_termination request""" 177 | for ch in range(NUM_CHANNELS): 178 | term = int(dev.get_termination(ch)) 179 | regex = fr'dev = {DEV_NAME}, ch = {ch}, terminated = {term}, user_data = {USER_DATA}' 180 | dut.readlines_until(regex=regex, timeout=TIMEOUT) 181 | 182 | def test_get_state(self, dev) -> None: 183 | """Test the get_state request""" 184 | for ch in range(NUM_CHANNELS): 185 | state = dev.get_state(ch) 186 | logger.debug('ch%d = %s', ch, state) 187 | 188 | if ch in FAKE_CHANNELS: 189 | assert state.state == GsUSBCANChannelState.ERROR_PASSIVE 190 | assert state.rxerr == 96 191 | assert state.txerr == 128 192 | else: 193 | assert state.state == GsUSBCANChannelState.STOPPED 194 | assert state.rxerr == 0 195 | assert state.txerr == 0 196 | -------------------------------------------------------------------------------- /tests/subsys/usb/gs_usb/host/src/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Henrik Brix Andersen 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | #include "test.h" 18 | 19 | LOG_MODULE_REGISTER(main, LOG_LEVEL_DBG); 20 | 21 | DEFINE_FFF_GLOBALS; 22 | 23 | #define USER_DATA 0x12345678 24 | #define TIMESTAMP 0xdeadbeef 25 | 26 | static int fake_can_get_capabilities_delegate(const struct device *dev, can_mode_t *cap) 27 | { 28 | *cap = CAN_MODE_NORMAL | CAN_MODE_FD; 29 | 30 | return 0; 31 | } 32 | 33 | static int fake_can_get_state_delegate(const struct device *dev, enum can_state *state, 34 | struct can_bus_err_cnt *err_cnt) 35 | { 36 | *state = CAN_STATE_ERROR_PASSIVE; 37 | 38 | err_cnt->tx_err_cnt = 128; 39 | err_cnt->rx_err_cnt = 96; 40 | 41 | return 0; 42 | } 43 | 44 | static int fake_can_set_timing_delegate(const struct device *dev, const struct can_timing *timing) 45 | { 46 | LOG_DBG("%s: sjw = %u, prop_seg = %u, phase_seg1 = %u, phase_seg2 = %u, " 47 | "prescaler = %u", dev->name, timing->sjw, timing->prop_seg, timing->phase_seg1, 48 | timing->phase_seg2, timing->prescaler); 49 | 50 | return 0; 51 | } 52 | 53 | static int fake_can_set_timing_data_delegate(const struct device *dev, 54 | const struct can_timing *timing_data) 55 | { 56 | LOG_DBG("%s: sjw = %u, prop_seg = %u, phase_seg1 = %u, phase_seg2 = %u, " 57 | "prescaler = %u", dev->name, timing_data->sjw, timing_data->prop_seg, 58 | timing_data->phase_seg1, timing_data->phase_seg2, timing_data->prescaler); 59 | 60 | return 0; 61 | } 62 | 63 | static int fake_can_start_delegate(const struct device *dev) 64 | { 65 | LOG_DBG("%s: start", dev->name); 66 | 67 | return 0; 68 | } 69 | 70 | static int fake_can_stop_delegate(const struct device *dev) 71 | { 72 | LOG_DBG("%s: stop", dev->name); 73 | 74 | return 0; 75 | } 76 | 77 | static int fake_can_set_mode_delegate(const struct device *dev, can_mode_t mode) 78 | { 79 | LOG_DBG("%s: mode = 0x%08x", dev->name, mode); 80 | 81 | return 0; 82 | } 83 | 84 | static int event_cb(const struct device *dev, uint16_t ch, enum gs_usb_event event, void *user_data) 85 | { 86 | uint32_t ud = POINTER_TO_UINT(user_data); 87 | 88 | switch (event) { 89 | case GS_USB_EVENT_CHANNEL_STARTED: 90 | LOG_DBG("dev = %s, ch = %u, started = 1, user_data = 0x%08x", dev->name, ch, ud); 91 | break; 92 | case GS_USB_EVENT_CHANNEL_STOPPED: 93 | LOG_DBG("dev = %s, ch = %u, started = 0, user_data = 0x%08x", dev->name, ch, ud); 94 | break; 95 | case GS_USB_EVENT_CHANNEL_ACTIVITY_RX: 96 | LOG_DBG("dev = %s, ch = %u, rx activity = 1, user_data = 0x%08x", dev->name, ch, 97 | ud); 98 | break; 99 | case GS_USB_EVENT_CHANNEL_ACTIVITY_TX: 100 | LOG_DBG("dev = %s, ch = %u, tx activity = 1, user_data = 0x%08x", dev->name, ch, 101 | ud); 102 | break; 103 | case GS_USB_EVENT_CHANNEL_IDENTIFY_ON: 104 | LOG_DBG("dev = %s, ch = %u, identify = 1, user_data = 0x%08x", dev->name, ch, ud); 105 | break; 106 | case GS_USB_EVENT_CHANNEL_IDENTIFY_OFF: 107 | LOG_DBG("dev = %s, ch = %u, identify = 0, user_data = 0x%08x", dev->name, ch, ud); 108 | break; 109 | } 110 | 111 | return 0; 112 | } 113 | 114 | static int set_termination_cb(const struct device *dev, uint16_t ch, bool terminate, 115 | void *user_data) 116 | { 117 | uint32_t ud = POINTER_TO_UINT(user_data); 118 | 119 | LOG_DBG("dev = %s, ch = %u, terminate = %u, user_data = 0x%08x", dev->name, ch, terminate, 120 | ud); 121 | 122 | return 0; 123 | } 124 | 125 | static int get_termination_cb(const struct device *dev, uint16_t ch, bool *terminated, 126 | void *user_data) 127 | { 128 | uint32_t ud = POINTER_TO_UINT(user_data); 129 | 130 | *terminated = (ch % 2 == 0) ? true : false; 131 | LOG_DBG("dev = %s, ch = %u, terminated = %u, user_data = 0x%08x", dev->name, ch, 132 | *terminated, ud); 133 | 134 | return 0; 135 | } 136 | 137 | static int timestamp_get_cb(const struct device *dev, uint32_t *timestamp, void *user_data) 138 | { 139 | uint32_t ud = POINTER_TO_UINT(user_data); 140 | 141 | *timestamp = TIMESTAMP; 142 | LOG_DBG("dev = %s, timestamp = 0x%08x, user_data = 0x%08x", dev->name, *timestamp, ud); 143 | 144 | return 0; 145 | } 146 | 147 | static const struct gs_usb_ops gs_usb_ops = { 148 | .timestamp = timestamp_get_cb, 149 | .event = event_cb, 150 | .set_termination = set_termination_cb, 151 | .get_termination = get_termination_cb, 152 | }; 153 | 154 | int main(void) 155 | { 156 | const struct device *gs_usb = DEVICE_DT_GET(DT_NODELABEL(gs_usb0)); 157 | const struct device *channels[] = { 158 | DEVICE_DT_GET(DT_NODELABEL(fake_can0)), 159 | DEVICE_DT_GET(DT_NODELABEL(fake_can1)), 160 | DEVICE_DT_GET(DT_NODELABEL(can_loopback0)), 161 | DEVICE_DT_GET(DT_NODELABEL(can_loopback1)), 162 | }; 163 | int err; 164 | int i; 165 | 166 | fake_can_get_capabilities_fake.custom_fake = fake_can_get_capabilities_delegate; 167 | fake_can_get_state_fake.custom_fake = fake_can_get_state_delegate; 168 | fake_can_set_timing_fake.custom_fake = fake_can_set_timing_delegate; 169 | fake_can_set_timing_data_fake.custom_fake = fake_can_set_timing_data_delegate; 170 | fake_can_start_fake.custom_fake = fake_can_start_delegate; 171 | fake_can_stop_fake.custom_fake = fake_can_stop_delegate; 172 | fake_can_set_mode_fake.custom_fake = fake_can_set_mode_delegate; 173 | 174 | if (!device_is_ready(gs_usb)) { 175 | LOG_ERR("gs_usb USB device not ready"); 176 | return 0; 177 | } 178 | 179 | for (i = 0; i < ARRAY_SIZE(channels); i++) { 180 | if (!device_is_ready(channels[i])) { 181 | LOG_ERR("CAN controller channel %d not ready", i); 182 | return 0; 183 | } 184 | } 185 | 186 | err = gs_usb_register(gs_usb, channels, ARRAY_SIZE(channels), &gs_usb_ops, 187 | UINT_TO_POINTER(USER_DATA)); 188 | if (err != 0U) { 189 | LOG_ERR("failed to register gs_usb (err %d)", err); 190 | return 0; 191 | } 192 | 193 | err = test_usb_init(); 194 | if (err != 0) { 195 | LOG_ERR("failed to initialize USB (err %d)", err); 196 | return 0; 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /tests/subsys/usb/gs_usb/host/src/shell.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Henrik Brix Andersen 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #include 8 | 9 | static int cmd_gs_usb_vid(const struct shell *sh, size_t argc, char **argv) 10 | { 11 | ARG_UNUSED(argc); 12 | ARG_UNUSED(argv); 13 | 14 | shell_print(sh, "USB VID: 0x%04x", CONFIG_TEST_USB_VID); 15 | 16 | return 0; 17 | } 18 | 19 | static int cmd_gs_usb_pid(const struct shell *sh, size_t argc, char **argv) 20 | { 21 | ARG_UNUSED(argc); 22 | ARG_UNUSED(argv); 23 | 24 | shell_print(sh, "USB PID: 0x%04x", CONFIG_TEST_USB_PID); 25 | 26 | return 0; 27 | } 28 | 29 | SHELL_STATIC_SUBCMD_SET_CREATE(sub_gs_usb_cmds, 30 | SHELL_CMD(vid, NULL, 31 | "Get USB VID\n" 32 | "Usage: gs_usb vid", 33 | cmd_gs_usb_vid), 34 | SHELL_CMD(pid, NULL, 35 | "Get USB PID\n" 36 | "Usage: gs_usb pid", 37 | cmd_gs_usb_pid), 38 | SHELL_SUBCMD_SET_END 39 | ); 40 | 41 | SHELL_CMD_REGISTER(gs_usb, &sub_gs_usb_cmds, "gs_usb test commands", NULL); 42 | -------------------------------------------------------------------------------- /tests/subsys/usb/gs_usb/host/src/test.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Henrik Brix Andersen 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #ifndef CANNECTIVITY_TEST_SUBSYS_USB_GS_USB_TEST_H_ 8 | #define CANNECTIVITY_TEST_SUBSYS_USB_GS_USB_TEST_H_ 9 | 10 | int test_usb_init(void); 11 | 12 | #endif /* CANNECTIVITY_TEST_SUBSYS_USB_GS_USB_TEST_H_ */ 13 | -------------------------------------------------------------------------------- /tests/subsys/usb/gs_usb/host/src/usb.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024-2025 Henrik Brix Andersen 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #include 8 | #include 9 | 10 | #ifdef CONFIG_USB_DEVICE_STACK_NEXT 11 | #include 12 | #else /* CONFIG_USB_DEVICE_STACK_NEXT */ 13 | #include 14 | #include 15 | #endif /* !CONFIG_USB_DEVICE_STACK_NEXT*/ 16 | 17 | #include "test.h" 18 | 19 | LOG_MODULE_REGISTER(usb, LOG_LEVEL_DBG); 20 | 21 | #define GS_USB_CLASS_INSTANCE_NAME "gs_usb_0" 22 | 23 | #ifdef CONFIG_USB_DEVICE_STACK_NEXT 24 | #define TEST_BOS_DESC_DEFINE_CAP static 25 | #else /* CONFIG_USB_DEVICE_STACK_NEXT */ 26 | #define TEST_BOS_DESC_DEFINE_CAP USB_DEVICE_BOS_DESC_DEFINE_CAP 27 | #endif /* !CONFIG_USB_DEVICE_STACK_NEXT */ 28 | 29 | TEST_BOS_DESC_DEFINE_CAP const struct usb_bos_capability_lpm bos_cap_lpm = { 30 | .bLength = sizeof(struct usb_bos_capability_lpm), 31 | .bDescriptorType = USB_DESC_DEVICE_CAPABILITY, 32 | .bDevCapabilityType = USB_BOS_CAPABILITY_EXTENSION, 33 | .bmAttributes = 0UL, 34 | }; 35 | 36 | #ifdef CONFIG_USB_DEVICE_STACK_NEXT 37 | 38 | USBD_DEVICE_DEFINE(usbd, DEVICE_DT_GET(DT_NODELABEL(zephyr_udc0)), CONFIG_TEST_USB_VID, 39 | CONFIG_TEST_USB_PID); 40 | 41 | USBD_DESC_LANG_DEFINE(lang); 42 | USBD_DESC_MANUFACTURER_DEFINE(mfr, CONFIG_TEST_USB_MANUFACTURER); 43 | USBD_DESC_PRODUCT_DEFINE(product, CONFIG_TEST_USB_PRODUCT); 44 | USBD_DESC_SERIAL_NUMBER_DEFINE(sn); 45 | USBD_DESC_CONFIG_DEFINE(fs_config_desc, "Full-Speed Configuration"); 46 | USBD_DESC_CONFIG_DEFINE(hs_config_desc, "High-Speed Configuration"); 47 | 48 | USBD_CONFIGURATION_DEFINE(fs_config, 0U, CONFIG_TEST_USB_MAX_POWER, &fs_config_desc); 49 | USBD_CONFIGURATION_DEFINE(hs_config, 0U, CONFIG_TEST_USB_MAX_POWER, &hs_config_desc); 50 | 51 | USBD_DESC_BOS_DEFINE(usbext, sizeof(bos_cap_lpm), &bos_cap_lpm); 52 | 53 | static int test_usb_init_usbd(void) 54 | { 55 | int err; 56 | 57 | err = usbd_add_descriptor(&usbd, &lang); 58 | if (err != 0) { 59 | LOG_ERR("failed to add language descriptor (err %d)", err); 60 | return err; 61 | } 62 | 63 | err = usbd_add_descriptor(&usbd, &mfr); 64 | if (err != 0) { 65 | LOG_ERR("failed to add manufacturer descriptor (err %d)", err); 66 | return err; 67 | } 68 | 69 | err = usbd_add_descriptor(&usbd, &product); 70 | if (err != 0) { 71 | LOG_ERR("failed to add product descriptor (%d)", err); 72 | return err; 73 | } 74 | 75 | err = usbd_add_descriptor(&usbd, &sn); 76 | if (err != 0) { 77 | LOG_ERR("failed to add S/N descriptor (err %d)", err); 78 | return err; 79 | } 80 | 81 | if (usbd_caps_speed(&usbd) == USBD_SPEED_HS) { 82 | err = usbd_add_configuration(&usbd, USBD_SPEED_HS, &hs_config); 83 | if (err != 0) { 84 | LOG_ERR("failed to add high-speed configuration (err %d)", err); 85 | return err; 86 | } 87 | 88 | err = usbd_register_class(&usbd, GS_USB_CLASS_INSTANCE_NAME, USBD_SPEED_HS, 1); 89 | if (err != 0) { 90 | LOG_ERR("failed to register high-speed class instance (err %d)", err); 91 | return err; 92 | } 93 | 94 | err = usbd_device_set_code_triple(&usbd, USBD_SPEED_HS, 0, 0, 0); 95 | if (err != 0) { 96 | LOG_ERR("failed to set high-speed code triple (err %d)", err); 97 | return err; 98 | } 99 | 100 | err = usbd_device_set_bcd_usb(&usbd, USBD_SPEED_HS, USB_SRN_2_0_1); 101 | if (err != 0) { 102 | LOG_ERR("failed to set high-speed bcdUSB (err %d)", err); 103 | return err; 104 | } 105 | } 106 | 107 | err = usbd_add_configuration(&usbd, USBD_SPEED_FS, &fs_config); 108 | if (err != 0) { 109 | LOG_ERR("failed to add full-speed configuration (err %d)", err); 110 | return err; 111 | } 112 | 113 | err = usbd_register_class(&usbd, GS_USB_CLASS_INSTANCE_NAME, USBD_SPEED_FS, 1); 114 | if (err != 0) { 115 | LOG_ERR("failed to register full-speed class instance (err %d)", err); 116 | return err; 117 | } 118 | 119 | err = usbd_device_set_code_triple(&usbd, USBD_SPEED_FS, 0, 0, 0); 120 | if (err != 0) { 121 | LOG_ERR("failed to set full-speed code triple (err %d)", err); 122 | return err; 123 | } 124 | 125 | err = usbd_device_set_bcd_usb(&usbd, USBD_SPEED_FS, USB_SRN_2_0_1); 126 | if (err != 0) { 127 | LOG_ERR("failed to set full-speed bcdUSB (err %d)", err); 128 | return err; 129 | } 130 | 131 | err = usbd_add_descriptor(&usbd, &usbext); 132 | if (err != 0) { 133 | LOG_ERR("failed to add USB 2.0 extension descriptor (err %d)", err); 134 | return err; 135 | } 136 | 137 | err = usbd_init(&usbd); 138 | if (err != 0) { 139 | LOG_ERR("failed to initialize USB device support (err %d)", err); 140 | return err; 141 | } 142 | 143 | err = usbd_enable(&usbd); 144 | if (err != 0) { 145 | LOG_ERR("failed to enable USB device"); 146 | return err; 147 | } 148 | 149 | return 0; 150 | } 151 | #endif /* !CONFIG_USB_DEVICE_STACK_NEXT */ 152 | 153 | int test_usb_init(void) 154 | { 155 | #ifdef CONFIG_USB_DEVICE_STACK_NEXT 156 | return test_usb_init_usbd(); 157 | #else /* CONFIG_USB_DEVICE_STACK_NEXT */ 158 | usb_bos_register_cap((void *)&bos_cap_lpm); 159 | 160 | return usb_enable(NULL); 161 | #endif /* !CONFIG_USB_DEVICE_STACK_NEXT */ 162 | } 163 | -------------------------------------------------------------------------------- /tests/subsys/usb/gs_usb/host/testcase.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024 Henrik Brix Andersen 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | common: 5 | tags: 6 | - usb 7 | - can 8 | harness: pytest 9 | harness_config: 10 | pytest_dut_scope: session 11 | fixture: usb 12 | platform_exclude: 13 | - native_sim 14 | tests: 15 | usb.gs_usb.host: 16 | depends_on: 17 | - usb_device 18 | - can 19 | integration_platforms: 20 | - frdm_k64f/mk64f12 21 | usb.gs_usb.host.usbd_next: 22 | depends_on: 23 | - usbd 24 | - can 25 | integration_platforms: 26 | - frdm_k64f/mk64f12 27 | extra_args: 28 | - FILE_SUFFIX=usbd_next 29 | -------------------------------------------------------------------------------- /west.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024-2025 Henrik Brix Andersen 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | manifest: 5 | remotes: 6 | - name: zephyrproject-rtos 7 | url-base: https://github.com/zephyrproject-rtos 8 | projects: 9 | - name: zephyr 10 | remote: zephyrproject-rtos 11 | revision: main 12 | clone-depth: 1 13 | import: 14 | name-allowlist: 15 | - cmsis_6 16 | - hal_atmel 17 | - hal_nxp 18 | - hal_stm32 19 | - mcuboot 20 | - segger 21 | -------------------------------------------------------------------------------- /zephyr/module.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024 Henrik Brix Andersen 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | name: cannectivity 5 | build: 6 | kconfig: Kconfig 7 | cmake: . 8 | settings: 9 | dts_root: . 10 | --------------------------------------------------------------------------------